import type { GlobalThemeOverrides } from 'naive-ui'; import { addColorAlpha, getColorPalette, getPaletteColorByNumber, getRgb } from '@sa/color'; import { overrideThemeSettings, themeSettings } from '@/theme/settings'; import { themeVars } from '@/theme/vars'; import { toggleHtmlClass } from '@/utils/common'; import { localStg } from '@/utils/storage'; const DARK_CLASS = 'dark'; /** Init theme settings */ export function initThemeSettings() { const isProd = import.meta.env.PROD; // if it is development mode, the theme settings will not be cached, by update `themeSettings` in `src/theme/settings.ts` to update theme settings if (!isProd) return themeSettings; // if it is production mode, the theme settings will be cached in localStorage // if want to update theme settings when publish new version, please update `overrideThemeSettings` in `src/theme/settings.ts` const settings = localStg.get('themeSettings') || themeSettings; const isOverride = localStg.get('overrideThemeFlag') === BUILD_TIME; if (!isOverride) { Object.assign(settings, overrideThemeSettings); localStg.set('overrideThemeFlag', BUILD_TIME); } return settings; } /** * Create theme token * * @param colors Theme colors */ export function createThemeToken(colors: App.Theme.ThemeColor) { const paletteColors = createThemePaletteColors(colors); const themeTokens: App.Theme.ThemeToken = { colors: { ...paletteColors, nprogress: paletteColors.primary, container: 'rgb(255, 255, 255)', layout: 'rgb(247, 250, 252)', inverted: 'rgb(0, 20, 40)', base_text: 'rgb(31, 31, 31)' }, boxShadow: { header: '0 1px 2px rgb(0, 21, 41, 0.08)', sider: '2px 0 8px 0 rgb(29, 35, 41, 0.05)', tab: '0 1px 2px rgb(0, 21, 41, 0.08)' } }; const darkThemeTokens: App.Theme.ThemeToken = { colors: { ...themeTokens.colors, container: 'rgb(28, 28, 28)', layout: 'rgb(18, 18, 18)', base_text: 'rgb(224, 224, 224)' }, boxShadow: { ...themeTokens.boxShadow } }; return { themeTokens, darkThemeTokens }; } /** * Create theme palette colors * * @param colors Theme colors */ function createThemePaletteColors(colors: App.Theme.ThemeColor, recommended = false) { const colorKeys = Object.keys(colors) as App.Theme.ThemeColorKey[]; const colorPaletteVar = {} as App.Theme.ThemePaletteColor; colorKeys.forEach(key => { const colorMap = getColorPalette(colors[key], recommended); colorPaletteVar[key] = colorMap.get(500)!; colorMap.forEach((hex, number) => { colorPaletteVar[`${key}-${number}`] = hex; }); }); return colorPaletteVar; } /** * Get css var by tokens * * @param tokens Theme base tokens */ function getCssVarByTokens(tokens: App.Theme.BaseToken) { const styles: string[] = []; function removeVarPrefix(value: string) { return value.replace('var(', '').replace(')', ''); } function removeRgbPrefix(value: string) { return value.replace('rgb(', '').replace(')', ''); } for (const [key, tokenValues] of Object.entries(themeVars)) { for (const [tokenKey, tokenValue] of Object.entries(tokenValues)) { let cssVarsKey = removeVarPrefix(tokenValue); let cssValue = tokens[key][tokenKey]; if (key === 'colors') { cssVarsKey = removeRgbPrefix(cssVarsKey); const { r, g, b } = getRgb(cssValue); cssValue = `${r} ${g} ${b}`; } styles.push(`${cssVarsKey}: ${cssValue}`); } } const styleStr = styles.join(';'); return styleStr; } /** * Add theme vars to html * * @param tokens */ export function addThemeVarsToHtml(tokens: App.Theme.BaseToken, darkTokens: App.Theme.BaseToken) { const cssVarStr = getCssVarByTokens(tokens); const darkCssVarStr = getCssVarByTokens(darkTokens); const css = ` html { ${cssVarStr} } `; const darkCss = ` html.${DARK_CLASS} { ${darkCssVarStr} } `; const styleId = 'theme-vars'; const style = document.querySelector(`#${styleId}`) || document.createElement('style'); style.id = styleId; style.textContent = css + darkCss; document.head.appendChild(style); } /** * Toggle css dark mode * * @param darkMode Is dark mode */ export function toggleCssDarkMode(darkMode = false) { const { add, remove } = toggleHtmlClass(DARK_CLASS); if (darkMode) { add(); } else { remove(); } } /** * Toggle grayscale mode * * @param grayscaleMode Is grayscale mode */ export function toggleGrayscaleMode(grayscaleMode = false) { const GRAYSCALE_CLASS = 'grayscale'; const { add, remove } = toggleHtmlClass(GRAYSCALE_CLASS); if (grayscaleMode) { add(); } else { remove(); } } type NaiveColorScene = '' | 'Suppl' | 'Hover' | 'Pressed' | 'Active'; type NaiveColorKey = `${App.Theme.ThemeColorKey}Color${NaiveColorScene}`; type NaiveThemeColor = Partial>; interface NaiveColorAction { scene: NaiveColorScene; handler: (color: string) => string; } /** * Get naive theme colors * * @param colors Theme colors */ function getNaiveThemeColors(colors: App.Theme.ThemeColor, recommended = false) { const colorActions: NaiveColorAction[] = [ { scene: '', handler: color => color }, { scene: 'Suppl', handler: color => color }, { scene: 'Hover', handler: color => getPaletteColorByNumber(color, 500, recommended) }, { scene: 'Pressed', handler: color => getPaletteColorByNumber(color, 700, recommended) }, { scene: 'Active', handler: color => addColorAlpha(color, 0.1) } ]; const themeColors: NaiveThemeColor = {}; const colorEntries = Object.entries(colors) as [App.Theme.ThemeColorKey, string][]; colorEntries.forEach(color => { colorActions.forEach(action => { const [colorType, colorValue] = color; const colorKey: NaiveColorKey = `${colorType}Color${action.scene}`; themeColors[colorKey] = action.handler(colorValue); }); }); return themeColors; } /** * Get naive theme * * @param colors Theme colors */ export function getNaiveTheme(colors: App.Theme.ThemeColor) { const { primary: colorLoading } = colors; const theme: GlobalThemeOverrides = { common: { ...getNaiveThemeColors(colors), borderRadius: '6px' }, LoadingBar: { colorLoading }, Tag: { borderRadius: '6px' } }; return theme; }