Add core themes

Chocobozzz 2024-11-15 15:45:41 +01:00
parent 8de0a388fc
commit 10a9ec2eaa
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
11 changed files with 125 additions and 34 deletions

View File

@ -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<ServerConfigTheme, 'name' | 'version'>
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

View File

@ -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;

View File

@ -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;

View File

@ -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)};
// Fallback from old theme
--primary: var(--mainColor);
--secondary: var(--greyBackgroundColor);
--main-fg: var(--mainForegroundColor);
--main-fg-500: var(--greyForegroundColor);
// 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

View File

@ -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);

View File

@ -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);
}

View File

@ -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),

View File

@ -123,6 +123,9 @@ export interface ServerConfig {
theme: {
registered: ServerConfigTheme[]
builtIn: { name: 'peertube-core-light' | 'peertube-core-dark' }[]
default: string
}

View File

@ -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) {

View File

@ -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)
}

View File

@ -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 => ({