Files
suwayomi-material-you-webui/src/lib/ui/AppThemes.ts

276 lines
7.9 KiB
TypeScript
Raw Normal View History

2024-09-05 02:35:08 +02:00
/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
2024-09-05 12:20:21 +02:00
import { ThemeOptions } from '@mui/material/styles';
2024-09-05 02:35:08 +02:00
import { t as translate } from 'i18next';
2024-09-07 01:07:02 +02:00
import WebFont from 'webfontloader';
2024-09-05 12:20:21 +02:00
import { defaultPromiseErrorHandler } from '@/util/defaultPromiseErrorHandler.ts';
2024-09-07 01:07:02 +02:00
import { ControlledPromise } from '@/lib/ControlledPromise.ts';
2024-09-05 02:35:08 +02:00
2024-09-05 12:20:21 +02:00
type Theme = { isCustom: boolean; getName: () => string; muiTheme: ThemeOptions };
2024-09-05 02:35:08 +02:00
const themes = {
default: {
2024-09-05 12:20:21 +02:00
isCustom: false,
2024-09-05 02:35:08 +02:00
getName: () => translate('global.label.default'),
2024-09-05 12:20:21 +02:00
muiTheme: {
palette: {
primary: {
main: '#5b74ef',
},
secondary: {
main: '#efd65b',
},
2024-09-05 02:35:08 +02:00
},
},
},
lavender: {
2024-09-05 12:20:21 +02:00
isCustom: false,
getName: () => translate('settings.appearance.theme.themes.lavender'),
muiTheme: {
palette: {
primary: {
main: '#5c58dc',
},
secondary: {
main: '#d8dc58',
},
2024-09-05 02:35:08 +02:00
},
},
},
dune: {
2024-09-05 12:20:21 +02:00
isCustom: false,
getName: () => translate('settings.appearance.theme.themes.dune'),
muiTheme: {
palette: {
primary: {
main: '#897869',
},
secondary: {
main: '#697a89',
},
2024-09-05 02:35:08 +02:00
},
},
},
rosegold: {
2024-09-05 12:20:21 +02:00
isCustom: false,
getName: () => translate('settings.appearance.theme.themes.rosegold'),
muiTheme: {
palette: {
primary: {
main: '#E9A7A1',
2024-09-05 12:20:21 +02:00
},
secondary: {
main: '#A1E3E9',
2024-09-05 12:20:21 +02:00
},
2024-09-05 02:35:08 +02:00
},
},
},
2024-09-05 12:20:21 +02:00
'forest dew': {
isCustom: false,
getName: () => translate('settings.appearance.theme.themes.forest_dew'),
muiTheme: {
palette: {
primary: {
main: '#53a584',
},
secondary: {
main: '#a55374',
},
2024-09-05 02:35:08 +02:00
},
},
},
2024-09-05 12:20:21 +02:00
'montain sunset': {
isCustom: false,
getName: () => translate('settings.appearance.theme.themes.mountain_sunset'),
muiTheme: {
palette: {
primary: {
main: '#c55a77',
},
secondary: {
main: '#5ac5a8',
},
2024-09-05 02:35:08 +02:00
},
},
},
crimson: {
2024-09-05 12:20:21 +02:00
isCustom: false,
getName: () => translate('settings.appearance.theme.themes.crimson'),
muiTheme: {
palette: {
primary: {
main: '#DC143C',
2024-09-05 12:20:21 +02:00
},
secondary: {
main: '#14DCB4',
2024-09-05 12:20:21 +02:00
},
2024-09-05 02:35:08 +02:00
},
},
},
2024-09-05 12:20:21 +02:00
'minty miracles': {
isCustom: false,
getName: () => translate('settings.appearance.theme.themes.minty_miracles'),
muiTheme: {
palette: {
primary: {
main: '#5CE6A1',
2024-09-05 12:20:21 +02:00
},
secondary: {
main: '#E65CA1',
2024-09-05 12:20:21 +02:00
},
2024-09-05 02:35:08 +02:00
},
},
},
2024-09-05 12:20:21 +02:00
'orange juice': {
isCustom: false,
getName: () => translate('settings.appearance.theme.themes.orange_juice'),
muiTheme: {
palette: {
primary: {
main: '#ffb546',
},
secondary: {
main: '#4690ff',
},
2024-09-09 21:11:13 +02:00
},
},
},
'bright pink': {
isCustom: false,
getName: () => translate('settings.appearance.theme.themes.bright_pink'),
muiTheme: {
palette: {
primary: {
main: '#FF007F',
},
secondary: {
main: '#00FF80',
},
},
},
},
veronica: {
isCustom: false,
getName: () => translate('settings.appearance.theme.themes.veronica'),
muiTheme: {
palette: {
primary: {
main: '#A020F0',
},
secondary: {
main: '#70F020',
},
},
},
},
'tree frog green': {
isCustom: false,
getName: () => translate('settings.appearance.theme.themes.tree_frog_green'),
muiTheme: {
palette: {
primary: {
main: '#8ace31',
},
secondary: {
main: '#7531CE',
},
2024-09-05 02:35:08 +02:00
},
},
},
} as const satisfies Record<string, Theme>;
2024-09-05 12:20:21 +02:00
export type AppThemes = keyof typeof themes | string;
2024-09-05 02:35:08 +02:00
2024-09-05 12:20:21 +02:00
export type AppTheme = Theme & { id: AppThemes; isCustom: boolean };
2024-09-05 02:35:08 +02:00
export const appThemes = (Object.entries(themes) as [AppThemes, Theme][]).map(([id, theme]) => ({
id,
...theme,
})) satisfies AppTheme[];
2024-09-05 12:20:21 +02:00
export const getTheme = (id: AppThemes, customThemes: Record<string, AppTheme> = {}): AppTheme => {
try {
const allThemes = { ...themes, ...customThemes };
const theme = (allThemes[id as keyof typeof themes] as AppTheme) ?? themes.default;
return {
// @ts-ignore - custom themes do not have a "getName" function
getName: () => id,
...theme,
};
} catch (e) {
defaultPromiseErrorHandler('getTheme')(e);
}
return { id, ...themes[id as keyof typeof themes] };
};
export const isThemeNameUnique = (id: string, customThemes: Record<string, AppTheme>): boolean =>
Object.keys({ ...themes, ...customThemes }).every((themeId) => themeId.toLowerCase() !== id.toLowerCase());
2024-09-07 01:07:02 +02:00
const getFontsFromTheme = (obj: Record<string, any>, fonts: string[] = []): string[] => {
const tmpFonts = [...fonts];
const propertyNames = Object.keys(obj);
for (const propertyName of propertyNames) {
const propertyValue = obj[propertyName];
const propertyType = typeof obj[propertyName];
const isValidFontProperty = propertyName === 'fontFamily' && propertyType === 'string';
if (isValidFontProperty) {
tmpFonts.push(propertyValue as string);
// eslint-disable-next-line no-continue
continue;
}
const isObject = propertyType === 'object';
if (isObject) {
tmpFonts.push(...getFontsFromTheme(propertyValue as Record<string, any>, tmpFonts));
}
}
return [...new Set(tmpFonts)];
};
const loadedFonts: string[] = [];
2024-09-07 01:47:06 +02:00
export const hasMissingFonts = (theme: ThemeOptions): boolean => {
const themeFonts = getFontsFromTheme(theme.typography ?? {});
return !!themeFonts.length && themeFonts.some((font) => !loadedFonts.includes(font));
};
2024-09-07 01:07:02 +02:00
export const loadThemeFonts = async (theme: ThemeOptions): Promise<void> => {
2024-09-07 01:47:06 +02:00
const themeFonts = getFontsFromTheme(theme.typography ?? {});
2024-09-07 01:07:02 +02:00
const missingThemeFonts = themeFonts.filter((font) => !loadedFonts.includes(font));
if (!missingThemeFonts.length) {
return;
}
const loadFontsPromise = new ControlledPromise();
try {
WebFont.load({
google: {
families: missingThemeFonts,
},
active: () => {
loadedFonts.push(...missingThemeFonts);
loadFontsPromise.resolve();
},
inactive: () => {
loadFontsPromise.reject();
},
});
} catch (e) {
defaultPromiseErrorHandler('AppThemes::loadFonts')(e);
loadFontsPromise.reject(e);
}
await loadFontsPromise.promise;
};