From 10a9ec2eaa2623db03f95335ac03c09c12671006 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 15 Nov 2024 15:45:41 +0100 Subject: [PATCH] Add core themes --- client/src/app/core/theme/theme.service.ts | 74 ++++++++++++++++--- .../notification-dropdown.component.scss | 6 +- client/src/app/menu/menu.component.scss | 1 - client/src/sass/application.scss | 36 +++++++-- client/src/sass/include/_miniature.scss | 2 +- client/src/sass/include/_mixins.scss | 6 +- client/src/sass/include/_variables.scss | 2 + .../models/src/server/server-config.model.ts | 3 + .../core/helpers/custom-validators/plugins.ts | 3 +- server/core/lib/plugins/theme-utils.ts | 14 ++-- server/core/lib/server-config-manager.ts | 12 +++ 11 files changed, 125 insertions(+), 34 deletions(-) diff --git a/client/src/app/core/theme/theme.service.ts b/client/src/app/core/theme/theme.service.ts index 0ce16919d..72589a1f2 100644 --- a/client/src/app/core/theme/theme.service.ts +++ b/client/src/app/core/theme/theme.service.ts @@ -3,7 +3,7 @@ import { HTMLServerConfig, ServerConfigTheme } from '@peertube/peertube-models' import { logger } from '@root-helpers/logger' import { capitalizeFirstLetter } from '@root-helpers/string' 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 { environment } from '../../../environments/environment' import { AuthService } from '../auth' @@ -19,9 +19,10 @@ export class ThemeService { private oldInjectedProperties: string[] = [] private oldThemeName: string + private internalThemes: string[] = [] private themes: ServerConfigTheme[] = [] - private themeFromLocalStorage: ServerConfigTheme + private themeFromLocalStorage: Pick private themeDOMLinksFromLocalStorage: HTMLLinkElement[] = [] private serverConfig: HTMLServerConfig @@ -39,6 +40,8 @@ export class ThemeService { this.loadAndSetFromLocalStorage() this.serverConfig = this.server.getHTMLConfig() + this.internalThemes = this.serverConfig.theme.builtIn.map(t => t.name) + const themes = this.serverConfig.theme.registered this.removeThemeFromLocalStorageIfNeeded(themes) @@ -49,15 +52,28 @@ export class ThemeService { getDefaultThemeLabel () { 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 () { - return this.serverConfig.theme.registered - .map(t => ({ id: t.name, label: capitalizeFirstLetter(t.name) })) + return [ + ...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) { @@ -116,6 +132,8 @@ export class ThemeService { link.disabled = link.getAttribute('title') !== name } } + + document.body.dataset.ptTheme = name } private updateCurrentTheme () { @@ -128,7 +146,12 @@ export class ThemeService { this.loadThemeStyle(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}`) this.pluginService.addPlugin(theme, true) @@ -156,9 +179,34 @@ export class ThemeService { 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) + 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) { console.error(`Cannot create palette of unexisting "--${prefix}" CSS body variable`) continue @@ -172,7 +220,7 @@ export class ThemeService { const key = `--${prefix}-${suffix}` 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})` rootStyle.setProperty(key, newColor) @@ -205,6 +253,14 @@ export class ThemeService { const lastActiveThemeString = this.localStorageService.getItem(UserLocalStorageKeys.LAST_ACTIVE_THEME) if (!lastActiveThemeString) return + // Internal theme + if (lastActiveThemeString === 'string') { + this.themeFromLocalStorage = { name: lastActiveThemeString, version: undefined } + + this.updateCurrentTheme() + return + } + try { const lastActiveTheme = JSON.parse(lastActiveThemeString) this.themeFromLocalStorage = lastActiveTheme diff --git a/client/src/app/header/notification-dropdown.component.scss b/client/src/app/header/notification-dropdown.component.scss index f148dfa71..9ee6b418f 100644 --- a/client/src/app/header/notification-dropdown.component.scss +++ b/client/src/app/header/notification-dropdown.component.scss @@ -116,9 +116,9 @@ align-items: center; justify-content: center; - background-color: pvar(--primary-400); - color: pvar(--main-fg-500); - border: 1px solid pvar(--main-fg-500); + background-color: pvar(--primary); + color: pvar(--on-primary); + border: 1px solid pvar(--on-primary); font-weight: $font-bold; font-size: 10px; diff --git a/client/src/app/menu/menu.component.scss b/client/src/app/menu/menu.component.scss index d29561953..492e766e1 100644 --- a/client/src/app/menu/menu.component.scss +++ b/client/src/app/menu/menu.component.scss @@ -145,7 +145,6 @@ align-items: center; color: pvar(--main-fg-450); - color: #2b2f32; white-space: normal; word-break: break-word; transition: background-color .1s ease-in-out; diff --git a/client/src/sass/application.scss b/client/src/sass/application.scss index 64f21c51a..7933633c4 100644 --- a/client/src/sass/application.scss +++ b/client/src/sass/application.scss @@ -33,7 +33,7 @@ body { --mainHoverColor: #{$main-hover-color}; --mainBackgroundHoverColor: #{$main-background-hover-color}; - --bg: #{$bg-color}; + --mainBackgroundColor: #{$bg-color}; --mainForegroundColor: #{$fg-color}; --greyForegroundColor: #{$grey-foreground-color}; @@ -68,19 +68,41 @@ body { --mainColWidth: calc(100vw - #{$menu-width}); // 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%); - --secondary: hsl(0 10% 76%); + // New theme with fallback + --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-weight: $font-regular; color: pvar(--fg); background-color: pvar(--bg); - font-size: 1rem; // 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 diff --git a/client/src/sass/include/_miniature.scss b/client/src/sass/include/_miniature.scss index a953f9b7d..ddeb873ed 100644 --- a/client/src/sass/include/_miniature.scss +++ b/client/src/sass/include/_miniature.scss @@ -5,7 +5,7 @@ @mixin miniature-name { transition: color 0.2s; font-weight: $font-bold; - color: pvar(--mainForegroundColor); + color: pvar(--fg); @include peertube-word-wrap(false); diff --git a/client/src/sass/include/_mixins.scss b/client/src/sass/include/_mixins.scss index f97603ddc..804567729 100644 --- a/client/src/sass/include/_mixins.scss +++ b/client/src/sass/include/_mixins.scss @@ -133,7 +133,7 @@ &, &:active, &:focus { - color: pvar(--fg); + color: pvar(--on-primary); background-color: pvar(--primary-450); border: 1px solid pvar(--primary-450); } @@ -142,13 +142,13 @@ &.btn:active, &.btn:focus-visible, &.btn.show { - color: pvar(--fg) !important; + color: pvar(--on-primary) !important; background-color: pvar(--primary-450) !important; border: 1px solid pvar(--primary-450) !important; } &:hover { - color: pvar(--fg); + color: pvar(--on-primary); background-color: pvar(--primary-400); } diff --git a/client/src/sass/include/_variables.scss b/client/src/sass/include/_variables.scss index 8fd596de0..7eb337033 100644 --- a/client/src/sass/include/_variables.scss +++ b/client/src/sass/include/_variables.scss @@ -166,6 +166,8 @@ $variables: ( --main-fg-150: var(--main-fg-150), --main-fg-100: var(--main-fg-100), + --on-primary: var(--on-primary), + --primary: var(--primary), --primary-800: var(--primary-800), --primary-750: var(--primary-750), diff --git a/packages/models/src/server/server-config.model.ts b/packages/models/src/server/server-config.model.ts index 474f9c32c..02c9787dd 100644 --- a/packages/models/src/server/server-config.model.ts +++ b/packages/models/src/server/server-config.model.ts @@ -123,6 +123,9 @@ export interface ServerConfig { theme: { registered: ServerConfigTheme[] + + builtIn: { name: 'peertube-core-light' | 'peertube-core-dark' }[] + default: string } diff --git a/server/core/helpers/custom-validators/plugins.ts b/server/core/helpers/custom-validators/plugins.ts index 875482f53..99ee476b7 100644 --- a/server/core/helpers/custom-validators/plugins.ts +++ b/server/core/helpers/custom-validators/plugins.ts @@ -93,7 +93,8 @@ function areCSSPathsValid (css: any[]) { } 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) { diff --git a/server/core/lib/plugins/theme-utils.ts b/server/core/lib/plugins/theme-utils.ts index 4a64a579a..651ddd195 100644 --- a/server/core/lib/plugins/theme-utils.ts +++ b/server/core/lib/plugins/theme-utils.ts @@ -1,8 +1,9 @@ import { DEFAULT_THEME_NAME, DEFAULT_USER_THEME_NAME } from '../../initializers/constants.js' import { PluginManager } from './plugin-manager.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 // Fallback to admin default theme @@ -11,14 +12,9 @@ function getThemeOrDefault (name: string, defaultTheme: string) { return defaultTheme } -function isThemeRegistered (name: string) { +export function isThemeRegistered (name: string) { if (name === DEFAULT_THEME_NAME || name === DEFAULT_USER_THEME_NAME) return true - return !!PluginManager.Instance.getRegisteredThemes() - .find(r => r.name === name) -} - -export { - getThemeOrDefault, - isThemeRegistered + return PluginManager.Instance.getRegisteredThemes().some(r => r.name === name) || + ServerConfigManager.Instance.getBuiltInThemes().some(r => r.name === name) } diff --git a/server/core/lib/server-config-manager.ts b/server/core/lib/server-config-manager.ts index d5cf761a6..1259e85a4 100644 --- a/server/core/lib/server-config-manager.ts +++ b/server/core/lib/server-config-manager.ts @@ -130,6 +130,7 @@ class ServerConfigManager { }, theme: { registered: this.getRegisteredThemes(), + builtIn: this.getBuiltInThemes(), default: defaultTheme }, 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 () { return PluginManager.Instance.getRegisteredPlugins() .map(p => ({