Support theme fonts
This commit is contained in:
@@ -8,7 +8,9 @@
|
||||
|
||||
import { ThemeOptions } from '@mui/material/styles';
|
||||
import { t as translate } from 'i18next';
|
||||
import WebFont from 'webfontloader';
|
||||
import { defaultPromiseErrorHandler } from '@/util/defaultPromiseErrorHandler.ts';
|
||||
import { ControlledPromise } from '@/lib/ControlledPromise.ts';
|
||||
|
||||
type Theme = { isCustom: boolean; getName: () => string; muiTheme: ThemeOptions };
|
||||
|
||||
@@ -168,3 +170,58 @@ export const getTheme = (id: AppThemes, customThemes: Record<string, AppTheme> =
|
||||
|
||||
export const isThemeNameUnique = (id: string, customThemes: Record<string, AppTheme>): boolean =>
|
||||
Object.keys({ ...themes, ...customThemes }).every((themeId) => themeId.toLowerCase() !== id.toLowerCase());
|
||||
|
||||
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[] = [];
|
||||
export const loadThemeFonts = async (theme: ThemeOptions): Promise<void> => {
|
||||
const themeFonts = getFontsFromTheme(theme);
|
||||
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;
|
||||
};
|
||||
|
||||
@@ -21,9 +21,10 @@ import Tooltip from '@mui/material/Tooltip';
|
||||
import EditIcon from '@mui/icons-material/Edit';
|
||||
import { bindDialog, usePopupState } from 'material-ui-popup-state/hooks';
|
||||
import { ThemeModeContext } from '@/components/context/ThemeModeContext.tsx';
|
||||
import { AppTheme } from '@/lib/ui/AppThemes.ts';
|
||||
import { AppTheme, loadThemeFonts } from '@/lib/ui/AppThemes.ts';
|
||||
import { createTheme } from '@/theme.ts';
|
||||
import { ThemeCreationDialog } from '@/screens/settings/appearance/theme/CreateThemeDialog.tsx';
|
||||
import { makeToast } from '@/components/util/Toast.tsx';
|
||||
|
||||
const ThemePreviewBadge = styled(Box)(() => ({
|
||||
width: '15px',
|
||||
@@ -82,7 +83,15 @@ export const ThemePreview = ({ theme, onDelete }: { theme: AppTheme; onDelete: (
|
||||
height: '100%',
|
||||
backgroundColor: 'background.default',
|
||||
}}
|
||||
onClick={() => setAppTheme(theme.id)}
|
||||
onClick={() => {
|
||||
makeToast(t('settings.appearance.theme.select.fonts.loading'), 'info');
|
||||
loadThemeFonts(theme.muiTheme)
|
||||
.then(() => {
|
||||
setAppTheme(theme.id);
|
||||
makeToast(t('settings.appearance.theme.select.success'), 'success');
|
||||
})
|
||||
.catch(() => makeToast(t('settings.appearance.theme.select.fonts.error'), 'error'));
|
||||
}}
|
||||
>
|
||||
<Stack sx={{ height: '100%', m: 0 }}>
|
||||
<Stack sx={{ height: '100%', gap: 2, p: 2 }}>
|
||||
|
||||
@@ -17,7 +17,8 @@ import {
|
||||
} from '@mui/material/styles';
|
||||
import { ThemeMode } from '@/components/context/ThemeModeContext.tsx';
|
||||
import { MediaQuery } from '@/lib/ui/MediaQuery.tsx';
|
||||
import { AppTheme } from '@/lib/ui/AppThemes.ts';
|
||||
import { AppTheme, loadThemeFonts } from '@/lib/ui/AppThemes.ts';
|
||||
import { defaultPromiseErrorHandler } from '@/util/defaultPromiseErrorHandler.ts';
|
||||
|
||||
const SCROLLBAR_SIZE = 14;
|
||||
|
||||
@@ -106,6 +107,8 @@ let theme: Theme;
|
||||
export const getCurrentTheme = () => theme;
|
||||
export const createAndSetTheme = (...args: Parameters<typeof createTheme>) => {
|
||||
theme = createTheme(...args);
|
||||
loadThemeFonts(theme).catch(defaultPromiseErrorHandler('theme::createAndSetTheme'));
|
||||
|
||||
return theme;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user