Support theme fonts
This commit is contained in:
@@ -64,7 +64,8 @@
|
|||||||
"stylis": "4.3.4",
|
"stylis": "4.3.4",
|
||||||
"stylis-plugin-rtl": "2.1.1",
|
"stylis-plugin-rtl": "2.1.1",
|
||||||
"use-long-press": "3.2.0",
|
"use-long-press": "3.2.0",
|
||||||
"use-query-params": "2.2.1"
|
"use-query-params": "2.2.1",
|
||||||
|
"webfontloader": "1.6.28"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@graphql-codegen/cli": "5.0.2",
|
"@graphql-codegen/cli": "5.0.2",
|
||||||
@@ -78,6 +79,7 @@
|
|||||||
"@types/react-dom": "18.3.0",
|
"@types/react-dom": "18.3.0",
|
||||||
"@types/sanitize-html": "2.13.0",
|
"@types/sanitize-html": "2.13.0",
|
||||||
"@types/stylis": "4.2.6",
|
"@types/stylis": "4.2.6",
|
||||||
|
"@types/webfontloader": "1.6.38",
|
||||||
"@types/yargs": "17.0.33",
|
"@types/yargs": "17.0.33",
|
||||||
"@typescript-eslint/eslint-plugin": "7.16.1",
|
"@typescript-eslint/eslint-plugin": "7.16.1",
|
||||||
"@typescript-eslint/parser": "7.16.1",
|
"@typescript-eslint/parser": "7.16.1",
|
||||||
|
|||||||
@@ -850,6 +850,13 @@
|
|||||||
"title": "Edit theme"
|
"title": "Edit theme"
|
||||||
},
|
},
|
||||||
"pure_black_mode": "Pure black dark mode",
|
"pure_black_mode": "Pure black dark mode",
|
||||||
|
"select": {
|
||||||
|
"fonts": {
|
||||||
|
"error": "Could not load theme fonts",
|
||||||
|
"loading": "Loading theme fonts…"
|
||||||
|
},
|
||||||
|
"success": "Selected theme"
|
||||||
|
},
|
||||||
"themes": {
|
"themes": {
|
||||||
"crimson": "Crimson",
|
"crimson": "Crimson",
|
||||||
"dune": "Dune",
|
"dune": "Dune",
|
||||||
|
|||||||
@@ -8,7 +8,9 @@
|
|||||||
|
|
||||||
import { ThemeOptions } from '@mui/material/styles';
|
import { ThemeOptions } from '@mui/material/styles';
|
||||||
import { t as translate } from 'i18next';
|
import { t as translate } from 'i18next';
|
||||||
|
import WebFont from 'webfontloader';
|
||||||
import { defaultPromiseErrorHandler } from '@/util/defaultPromiseErrorHandler.ts';
|
import { defaultPromiseErrorHandler } from '@/util/defaultPromiseErrorHandler.ts';
|
||||||
|
import { ControlledPromise } from '@/lib/ControlledPromise.ts';
|
||||||
|
|
||||||
type Theme = { isCustom: boolean; getName: () => string; muiTheme: ThemeOptions };
|
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 =>
|
export const isThemeNameUnique = (id: string, customThemes: Record<string, AppTheme>): boolean =>
|
||||||
Object.keys({ ...themes, ...customThemes }).every((themeId) => themeId.toLowerCase() !== id.toLowerCase());
|
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 EditIcon from '@mui/icons-material/Edit';
|
||||||
import { bindDialog, usePopupState } from 'material-ui-popup-state/hooks';
|
import { bindDialog, usePopupState } from 'material-ui-popup-state/hooks';
|
||||||
import { ThemeModeContext } from '@/components/context/ThemeModeContext.tsx';
|
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 { createTheme } from '@/theme.ts';
|
||||||
import { ThemeCreationDialog } from '@/screens/settings/appearance/theme/CreateThemeDialog.tsx';
|
import { ThemeCreationDialog } from '@/screens/settings/appearance/theme/CreateThemeDialog.tsx';
|
||||||
|
import { makeToast } from '@/components/util/Toast.tsx';
|
||||||
|
|
||||||
const ThemePreviewBadge = styled(Box)(() => ({
|
const ThemePreviewBadge = styled(Box)(() => ({
|
||||||
width: '15px',
|
width: '15px',
|
||||||
@@ -82,7 +83,15 @@ export const ThemePreview = ({ theme, onDelete }: { theme: AppTheme; onDelete: (
|
|||||||
height: '100%',
|
height: '100%',
|
||||||
backgroundColor: 'background.default',
|
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%', m: 0 }}>
|
||||||
<Stack sx={{ height: '100%', gap: 2, p: 2 }}>
|
<Stack sx={{ height: '100%', gap: 2, p: 2 }}>
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ import {
|
|||||||
} from '@mui/material/styles';
|
} from '@mui/material/styles';
|
||||||
import { ThemeMode } from '@/components/context/ThemeModeContext.tsx';
|
import { ThemeMode } from '@/components/context/ThemeModeContext.tsx';
|
||||||
import { MediaQuery } from '@/lib/ui/MediaQuery.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;
|
const SCROLLBAR_SIZE = 14;
|
||||||
|
|
||||||
@@ -106,6 +107,8 @@ let theme: Theme;
|
|||||||
export const getCurrentTheme = () => theme;
|
export const getCurrentTheme = () => theme;
|
||||||
export const createAndSetTheme = (...args: Parameters<typeof createTheme>) => {
|
export const createAndSetTheme = (...args: Parameters<typeof createTheme>) => {
|
||||||
theme = createTheme(...args);
|
theme = createTheme(...args);
|
||||||
|
loadThemeFonts(theme).catch(defaultPromiseErrorHandler('theme::createAndSetTheme'));
|
||||||
|
|
||||||
return theme;
|
return theme;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
10
yarn.lock
10
yarn.lock
@@ -3161,6 +3161,11 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/stylis/-/stylis-4.2.6.tgz#20341df2bdcc394f2d825d8651cfd6e5bf2d3b32"
|
resolved "https://registry.yarnpkg.com/@types/stylis/-/stylis-4.2.6.tgz#20341df2bdcc394f2d825d8651cfd6e5bf2d3b32"
|
||||||
integrity sha512-4nebF2ZJGzQk0ka0O6+FZUWceyFv4vWq/0dXBMmrSeAwzOuOd/GxE5Pa64d/ndeNLG73dXoBsRzvtsVsYUv6Uw==
|
integrity sha512-4nebF2ZJGzQk0ka0O6+FZUWceyFv4vWq/0dXBMmrSeAwzOuOd/GxE5Pa64d/ndeNLG73dXoBsRzvtsVsYUv6Uw==
|
||||||
|
|
||||||
|
"@types/webfontloader@1.6.38":
|
||||||
|
version "1.6.38"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/webfontloader/-/webfontloader-1.6.38.tgz#2cb965a7e7e4a83af6bac67688bc7d1c076107e8"
|
||||||
|
integrity sha512-kUaF72Fv202suFx6yBrwXqeVRMx7hGtJTesyESZgn9sEPCUeDXm2p0SiyS1MTqW74nQP4p7JyrOCwZ7pNFns4w==
|
||||||
|
|
||||||
"@types/ws@^8.0.0":
|
"@types/ws@^8.0.0":
|
||||||
version "8.5.10"
|
version "8.5.10"
|
||||||
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.10.tgz#4acfb517970853fa6574a3a6886791d04a396787"
|
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.10.tgz#4acfb517970853fa6574a3a6886791d04a396787"
|
||||||
@@ -7804,6 +7809,11 @@ webcrypto-core@^1.7.9:
|
|||||||
pvtsutils "^1.3.5"
|
pvtsutils "^1.3.5"
|
||||||
tslib "^2.6.2"
|
tslib "^2.6.2"
|
||||||
|
|
||||||
|
webfontloader@1.6.28:
|
||||||
|
version "1.6.28"
|
||||||
|
resolved "https://registry.yarnpkg.com/webfontloader/-/webfontloader-1.6.28.tgz#db786129253cb6e8eae54c2fb05f870af6675bae"
|
||||||
|
integrity sha512-Egb0oFEga6f+nSgasH3E0M405Pzn6y3/9tOVanv/DLfa1YBIgcv90L18YyWnvXkRbIM17v5Kv6IT2N6g1x5tvQ==
|
||||||
|
|
||||||
webidl-conversions@^3.0.0:
|
webidl-conversions@^3.0.0:
|
||||||
version "3.0.1"
|
version "3.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
|
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
|
||||||
|
|||||||
Reference in New Issue
Block a user