mirror of https://github.com/Chocobozzz/PeerTube
Add core themes
parent
8de0a388fc
commit
10a9ec2eaa
|
@ -3,7 +3,7 @@ import { HTMLServerConfig, ServerConfigTheme } from '@peertube/peertube-models'
|
||||||
import { logger } from '@root-helpers/logger'
|
import { logger } from '@root-helpers/logger'
|
||||||
import { capitalizeFirstLetter } from '@root-helpers/string'
|
import { capitalizeFirstLetter } from '@root-helpers/string'
|
||||||
import { UserLocalStorageKeys } from '@root-helpers/users'
|
import { UserLocalStorageKeys } from '@root-helpers/users'
|
||||||
import { format, parse, toHSLA } from 'color-bits'
|
import { getLuminance, parse, toHSLA } from 'color-bits'
|
||||||
import debug from 'debug'
|
import debug from 'debug'
|
||||||
import { environment } from '../../../environments/environment'
|
import { environment } from '../../../environments/environment'
|
||||||
import { AuthService } from '../auth'
|
import { AuthService } from '../auth'
|
||||||
|
@ -19,9 +19,10 @@ export class ThemeService {
|
||||||
private oldInjectedProperties: string[] = []
|
private oldInjectedProperties: string[] = []
|
||||||
private oldThemeName: string
|
private oldThemeName: string
|
||||||
|
|
||||||
|
private internalThemes: string[] = []
|
||||||
private themes: ServerConfigTheme[] = []
|
private themes: ServerConfigTheme[] = []
|
||||||
|
|
||||||
private themeFromLocalStorage: ServerConfigTheme
|
private themeFromLocalStorage: Pick<ServerConfigTheme, 'name' | 'version'>
|
||||||
private themeDOMLinksFromLocalStorage: HTMLLinkElement[] = []
|
private themeDOMLinksFromLocalStorage: HTMLLinkElement[] = []
|
||||||
|
|
||||||
private serverConfig: HTMLServerConfig
|
private serverConfig: HTMLServerConfig
|
||||||
|
@ -39,6 +40,8 @@ export class ThemeService {
|
||||||
this.loadAndSetFromLocalStorage()
|
this.loadAndSetFromLocalStorage()
|
||||||
|
|
||||||
this.serverConfig = this.server.getHTMLConfig()
|
this.serverConfig = this.server.getHTMLConfig()
|
||||||
|
this.internalThemes = this.serverConfig.theme.builtIn.map(t => t.name)
|
||||||
|
|
||||||
const themes = this.serverConfig.theme.registered
|
const themes = this.serverConfig.theme.registered
|
||||||
|
|
||||||
this.removeThemeFromLocalStorageIfNeeded(themes)
|
this.removeThemeFromLocalStorageIfNeeded(themes)
|
||||||
|
@ -49,15 +52,28 @@ export class ThemeService {
|
||||||
|
|
||||||
getDefaultThemeLabel () {
|
getDefaultThemeLabel () {
|
||||||
if (this.hasDarkTheme()) {
|
if (this.hasDarkTheme()) {
|
||||||
return $localize`Light/Orange or Dark`
|
return $localize`Light (Orange) or Dark (Brown)`
|
||||||
}
|
}
|
||||||
|
|
||||||
return $localize`Light/Orange`
|
return $localize`Light (Orange)`
|
||||||
}
|
}
|
||||||
|
|
||||||
buildAvailableThemes () {
|
buildAvailableThemes () {
|
||||||
return this.serverConfig.theme.registered
|
return [
|
||||||
.map(t => ({ id: t.name, label: capitalizeFirstLetter(t.name) }))
|
...this.serverConfig.theme.registered.map(t => ({ id: t.name, label: capitalizeFirstLetter(t.name) })),
|
||||||
|
|
||||||
|
...this.serverConfig.theme.builtIn.map(t => {
|
||||||
|
if (t.name === 'peertube-core-dark') {
|
||||||
|
return { id: t.name, label: $localize`Dark (Brown)` }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (t.name === 'peertube-core-light') {
|
||||||
|
return { id: t.name, label: $localize`Light (Orange)` }
|
||||||
|
}
|
||||||
|
|
||||||
|
return { id: t.name, label: capitalizeFirstLetter(t.name) }
|
||||||
|
})
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
private injectThemes (themes: ServerConfigTheme[], fromLocalStorage = false) {
|
private injectThemes (themes: ServerConfigTheme[], fromLocalStorage = false) {
|
||||||
|
@ -116,6 +132,8 @@ export class ThemeService {
|
||||||
link.disabled = link.getAttribute('title') !== name
|
link.disabled = link.getAttribute('title') !== name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
document.body.dataset.ptTheme = name
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateCurrentTheme () {
|
private updateCurrentTheme () {
|
||||||
|
@ -128,7 +146,12 @@ export class ThemeService {
|
||||||
this.loadThemeStyle(currentTheme)
|
this.loadThemeStyle(currentTheme)
|
||||||
|
|
||||||
const theme = this.getTheme(currentTheme)
|
const theme = this.getTheme(currentTheme)
|
||||||
if (theme) {
|
|
||||||
|
if (this.internalThemes.includes(currentTheme)) {
|
||||||
|
logger.info(`Enabling internal theme ${currentTheme}`)
|
||||||
|
|
||||||
|
this.localStorageService.setItem(UserLocalStorageKeys.LAST_ACTIVE_THEME, JSON.stringify({ name: currentTheme }), false)
|
||||||
|
} else if (theme) {
|
||||||
logger.info(`Adding scripts of theme ${currentTheme}`)
|
logger.info(`Adding scripts of theme ${currentTheme}`)
|
||||||
|
|
||||||
this.pluginService.addPlugin(theme, true)
|
this.pluginService.addPlugin(theme, true)
|
||||||
|
@ -156,9 +179,34 @@ export class ThemeService {
|
||||||
|
|
||||||
this.oldInjectedProperties = []
|
this.oldInjectedProperties = []
|
||||||
|
|
||||||
for (const prefix of [ 'primary', 'secondary', 'main-fg' ]) {
|
const toProcess = [
|
||||||
|
{ prefix: 'primary', invertIfDark: false },
|
||||||
|
{ prefix: 'secondary', invertIfDark: true },
|
||||||
|
{ prefix: 'main-fg', invertIfDark: true }
|
||||||
|
]
|
||||||
|
|
||||||
|
for (const { prefix, invertIfDark } of toProcess) {
|
||||||
const mainColor = computedStyle.getPropertyValue('--' + prefix)
|
const mainColor = computedStyle.getPropertyValue('--' + prefix)
|
||||||
|
|
||||||
|
let darkInverter = 1
|
||||||
|
|
||||||
|
if (invertIfDark) {
|
||||||
|
const deprecatedFG = computedStyle.getPropertyValue('--mainForegroundColor')
|
||||||
|
const deprecatedBG = computedStyle.getPropertyValue('--mainBackgroundColor')
|
||||||
|
|
||||||
|
if (computedStyle.getPropertyValue('--is-dark') === '1') {
|
||||||
|
darkInverter = -1
|
||||||
|
} else if (deprecatedFG && deprecatedBG) {
|
||||||
|
try {
|
||||||
|
if (getLuminance(parse(deprecatedBG)) < getLuminance(parse(deprecatedFG))) {
|
||||||
|
darkInverter = -1
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Cannot parse deprecated CSS variables', err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!mainColor) {
|
if (!mainColor) {
|
||||||
console.error(`Cannot create palette of unexisting "--${prefix}" CSS body variable`)
|
console.error(`Cannot create palette of unexisting "--${prefix}" CSS body variable`)
|
||||||
continue
|
continue
|
||||||
|
@ -172,7 +220,7 @@ export class ThemeService {
|
||||||
const key = `--${prefix}-${suffix}`
|
const key = `--${prefix}-${suffix}`
|
||||||
|
|
||||||
if (!computedStyle.getPropertyValue(key)) {
|
if (!computedStyle.getPropertyValue(key)) {
|
||||||
const newLuminance = Math.max(Math.min(100, Math.round(mainColorHSL.l + (i * 5 * -1))), 0)
|
const newLuminance = Math.max(Math.min(100, Math.round(mainColorHSL.l + (i * 5 * -1 * darkInverter))), 0)
|
||||||
const newColor = `hsl(${Math.round(mainColorHSL.h)} ${Math.round(mainColorHSL.s)}% ${newLuminance}% / ${mainColorHSL.a})`
|
const newColor = `hsl(${Math.round(mainColorHSL.h)} ${Math.round(mainColorHSL.s)}% ${newLuminance}% / ${mainColorHSL.a})`
|
||||||
|
|
||||||
rootStyle.setProperty(key, newColor)
|
rootStyle.setProperty(key, newColor)
|
||||||
|
@ -205,6 +253,14 @@ export class ThemeService {
|
||||||
const lastActiveThemeString = this.localStorageService.getItem(UserLocalStorageKeys.LAST_ACTIVE_THEME)
|
const lastActiveThemeString = this.localStorageService.getItem(UserLocalStorageKeys.LAST_ACTIVE_THEME)
|
||||||
if (!lastActiveThemeString) return
|
if (!lastActiveThemeString) return
|
||||||
|
|
||||||
|
// Internal theme
|
||||||
|
if (lastActiveThemeString === 'string') {
|
||||||
|
this.themeFromLocalStorage = { name: lastActiveThemeString, version: undefined }
|
||||||
|
|
||||||
|
this.updateCurrentTheme()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const lastActiveTheme = JSON.parse(lastActiveThemeString)
|
const lastActiveTheme = JSON.parse(lastActiveThemeString)
|
||||||
this.themeFromLocalStorage = lastActiveTheme
|
this.themeFromLocalStorage = lastActiveTheme
|
||||||
|
|
|
@ -116,9 +116,9 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
background-color: pvar(--primary-400);
|
background-color: pvar(--primary);
|
||||||
color: pvar(--main-fg-500);
|
color: pvar(--on-primary);
|
||||||
border: 1px solid pvar(--main-fg-500);
|
border: 1px solid pvar(--on-primary);
|
||||||
font-weight: $font-bold;
|
font-weight: $font-bold;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
|
|
||||||
|
|
|
@ -145,7 +145,6 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
color: pvar(--main-fg-450);
|
color: pvar(--main-fg-450);
|
||||||
color: #2b2f32;
|
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
transition: background-color .1s ease-in-out;
|
transition: background-color .1s ease-in-out;
|
||||||
|
|
|
@ -33,7 +33,7 @@ body {
|
||||||
--mainHoverColor: #{$main-hover-color};
|
--mainHoverColor: #{$main-hover-color};
|
||||||
--mainBackgroundHoverColor: #{$main-background-hover-color};
|
--mainBackgroundHoverColor: #{$main-background-hover-color};
|
||||||
|
|
||||||
--bg: #{$bg-color};
|
--mainBackgroundColor: #{$bg-color};
|
||||||
--mainForegroundColor: #{$fg-color};
|
--mainForegroundColor: #{$fg-color};
|
||||||
|
|
||||||
--greyForegroundColor: #{$grey-foreground-color};
|
--greyForegroundColor: #{$grey-foreground-color};
|
||||||
|
@ -68,19 +68,41 @@ body {
|
||||||
--mainColWidth: calc(100vw - #{$menu-width});
|
--mainColWidth: calc(100vw - #{$menu-width});
|
||||||
|
|
||||||
// Create a theme system using CSS variables
|
// Create a theme system using CSS variables
|
||||||
--fg: #{pvar(--main-fg-600)};
|
|
||||||
--bg: #{pvar(--secondary-300)};
|
|
||||||
|
|
||||||
--primary: #F2690D;
|
// Fallback from old theme
|
||||||
|
--primary: var(--mainColor);
|
||||||
|
--secondary: var(--greyBackgroundColor);
|
||||||
|
--main-fg: var(--mainForegroundColor);
|
||||||
|
--main-fg-500: var(--greyForegroundColor);
|
||||||
|
|
||||||
--main-fg: hsl(0 14% 12%);
|
// New theme with fallback
|
||||||
--secondary: hsl(0 10% 76%);
|
--fg: var(--mainForegroundColor, #{pvar(--main-fg-600)});
|
||||||
|
--bg: var(--mainBackgroundColor, #{pvar(--secondary-300)});
|
||||||
|
--on-primary: var(--inputForegroundColor, #{pvar(--fg)});
|
||||||
|
|
||||||
|
// Light theme
|
||||||
|
&[data-pt-theme=peertube-core-light],
|
||||||
|
&[data-pt-theme=default] {
|
||||||
|
--is-dark: 0;
|
||||||
|
--primary: #F2690D;
|
||||||
|
--main-fg: hsl(0 14% 12%);
|
||||||
|
--secondary: hsl(0 10% 76%);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Brown
|
||||||
|
&[data-pt-theme=peertube-core-dark] {
|
||||||
|
--is-dark: 1;
|
||||||
|
--primary: #FD9C50;
|
||||||
|
--main-fg: hsl(0 10% 86%);
|
||||||
|
--secondary: hsl(0 14% 22%);
|
||||||
|
--bg: #{pvar(--secondary-350)};
|
||||||
|
--on-primary: #{pvar(--secondary-600)};
|
||||||
|
}
|
||||||
|
|
||||||
font-family: $main-fonts;
|
font-family: $main-fonts;
|
||||||
font-weight: $font-regular;
|
font-weight: $font-regular;
|
||||||
color: pvar(--fg);
|
color: pvar(--fg);
|
||||||
background-color: pvar(--bg);
|
background-color: pvar(--bg);
|
||||||
font-size: 1rem;
|
|
||||||
|
|
||||||
// On desktop browsers, make sure vertical scroll bar is always visible
|
// On desktop browsers, make sure vertical scroll bar is always visible
|
||||||
// Allow to disable the scrollbar instead of hide it when the content fit the body
|
// Allow to disable the scrollbar instead of hide it when the content fit the body
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
@mixin miniature-name {
|
@mixin miniature-name {
|
||||||
transition: color 0.2s;
|
transition: color 0.2s;
|
||||||
font-weight: $font-bold;
|
font-weight: $font-bold;
|
||||||
color: pvar(--mainForegroundColor);
|
color: pvar(--fg);
|
||||||
|
|
||||||
@include peertube-word-wrap(false);
|
@include peertube-word-wrap(false);
|
||||||
|
|
||||||
|
|
|
@ -133,7 +133,7 @@
|
||||||
&,
|
&,
|
||||||
&:active,
|
&:active,
|
||||||
&:focus {
|
&:focus {
|
||||||
color: pvar(--fg);
|
color: pvar(--on-primary);
|
||||||
background-color: pvar(--primary-450);
|
background-color: pvar(--primary-450);
|
||||||
border: 1px solid pvar(--primary-450);
|
border: 1px solid pvar(--primary-450);
|
||||||
}
|
}
|
||||||
|
@ -142,13 +142,13 @@
|
||||||
&.btn:active,
|
&.btn:active,
|
||||||
&.btn:focus-visible,
|
&.btn:focus-visible,
|
||||||
&.btn.show {
|
&.btn.show {
|
||||||
color: pvar(--fg) !important;
|
color: pvar(--on-primary) !important;
|
||||||
background-color: pvar(--primary-450) !important;
|
background-color: pvar(--primary-450) !important;
|
||||||
border: 1px solid pvar(--primary-450) !important;
|
border: 1px solid pvar(--primary-450) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: pvar(--fg);
|
color: pvar(--on-primary);
|
||||||
background-color: pvar(--primary-400);
|
background-color: pvar(--primary-400);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -166,6 +166,8 @@ $variables: (
|
||||||
--main-fg-150: var(--main-fg-150),
|
--main-fg-150: var(--main-fg-150),
|
||||||
--main-fg-100: var(--main-fg-100),
|
--main-fg-100: var(--main-fg-100),
|
||||||
|
|
||||||
|
--on-primary: var(--on-primary),
|
||||||
|
|
||||||
--primary: var(--primary),
|
--primary: var(--primary),
|
||||||
--primary-800: var(--primary-800),
|
--primary-800: var(--primary-800),
|
||||||
--primary-750: var(--primary-750),
|
--primary-750: var(--primary-750),
|
||||||
|
|
|
@ -123,6 +123,9 @@ export interface ServerConfig {
|
||||||
|
|
||||||
theme: {
|
theme: {
|
||||||
registered: ServerConfigTheme[]
|
registered: ServerConfigTheme[]
|
||||||
|
|
||||||
|
builtIn: { name: 'peertube-core-light' | 'peertube-core-dark' }[]
|
||||||
|
|
||||||
default: string
|
default: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -93,7 +93,8 @@ function areCSSPathsValid (css: any[]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function isThemeNameValid (name: string) {
|
function isThemeNameValid (name: string) {
|
||||||
return isPluginNameValid(name)
|
return name && typeof name === 'string' &&
|
||||||
|
(isPluginNameValid(name) || name.startsWith('peertube-core-'))
|
||||||
}
|
}
|
||||||
|
|
||||||
function isPackageJSONValid (packageJSON: PluginPackageJSON, pluginType: PluginType_Type) {
|
function isPackageJSONValid (packageJSON: PluginPackageJSON, pluginType: PluginType_Type) {
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import { DEFAULT_THEME_NAME, DEFAULT_USER_THEME_NAME } from '../../initializers/constants.js'
|
import { DEFAULT_THEME_NAME, DEFAULT_USER_THEME_NAME } from '../../initializers/constants.js'
|
||||||
import { PluginManager } from './plugin-manager.js'
|
import { PluginManager } from './plugin-manager.js'
|
||||||
import { CONFIG } from '../../initializers/config.js'
|
import { CONFIG } from '../../initializers/config.js'
|
||||||
|
import { ServerConfigManager } from '../server-config-manager.js'
|
||||||
|
|
||||||
function getThemeOrDefault (name: string, defaultTheme: string) {
|
export function getThemeOrDefault (name: string, defaultTheme: string) {
|
||||||
if (isThemeRegistered(name)) return name
|
if (isThemeRegistered(name)) return name
|
||||||
|
|
||||||
// Fallback to admin default theme
|
// Fallback to admin default theme
|
||||||
|
@ -11,14 +12,9 @@ function getThemeOrDefault (name: string, defaultTheme: string) {
|
||||||
return defaultTheme
|
return defaultTheme
|
||||||
}
|
}
|
||||||
|
|
||||||
function isThemeRegistered (name: string) {
|
export function isThemeRegistered (name: string) {
|
||||||
if (name === DEFAULT_THEME_NAME || name === DEFAULT_USER_THEME_NAME) return true
|
if (name === DEFAULT_THEME_NAME || name === DEFAULT_USER_THEME_NAME) return true
|
||||||
|
|
||||||
return !!PluginManager.Instance.getRegisteredThemes()
|
return PluginManager.Instance.getRegisteredThemes().some(r => r.name === name) ||
|
||||||
.find(r => r.name === name)
|
ServerConfigManager.Instance.getBuiltInThemes().some(r => r.name === name)
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
|
||||||
getThemeOrDefault,
|
|
||||||
isThemeRegistered
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -130,6 +130,7 @@ class ServerConfigManager {
|
||||||
},
|
},
|
||||||
theme: {
|
theme: {
|
||||||
registered: this.getRegisteredThemes(),
|
registered: this.getRegisteredThemes(),
|
||||||
|
builtIn: this.getBuiltInThemes(),
|
||||||
default: defaultTheme
|
default: defaultTheme
|
||||||
},
|
},
|
||||||
email: {
|
email: {
|
||||||
|
@ -370,6 +371,17 @@ class ServerConfigManager {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getBuiltInThemes () {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: 'peertube-core-dark' as 'peertube-core-dark'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'peertube-core-light' as 'peertube-core-light'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
getRegisteredPlugins () {
|
getRegisteredPlugins () {
|
||||||
return PluginManager.Instance.getRegisteredPlugins()
|
return PluginManager.Instance.getRegisteredPlugins()
|
||||||
.map(p => ({
|
.map(p => ({
|
||||||
|
|
Loading…
Reference in New Issue