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 { 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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -123,6 +123,9 @@ export interface ServerConfig {
|
|||
|
||||
theme: {
|
||||
registered: ServerConfigTheme[]
|
||||
|
||||
builtIn: { name: 'peertube-core-light' | 'peertube-core-dark' }[]
|
||||
|
||||
default: string
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 => ({
|
||||
|
|
Loading…
Reference in New Issue