Move theme files into new folder
This commit is contained in:
@@ -58,7 +58,7 @@ const { LibraryDuplicates } = loadable(
|
|||||||
() => import('@/modules/library/screens/LibraryDuplicates.tsx'),
|
() => import('@/modules/library/screens/LibraryDuplicates.tsx'),
|
||||||
lazyLoadFallback,
|
lazyLoadFallback,
|
||||||
);
|
);
|
||||||
const { Appearance } = loadable(() => import('@/screens/settings/appearance/Appearance.tsx'), lazyLoadFallback);
|
const { Appearance } = loadable(() => import('@/screens/settings/Appearance.tsx'), lazyLoadFallback);
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
// Adds messages only in a dev environment
|
// Adds messages only in a dev environment
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import {
|
|||||||
Metadata,
|
Metadata,
|
||||||
MetadataServerSettingKeys,
|
MetadataServerSettingKeys,
|
||||||
MetadataServerSettings,
|
MetadataServerSettings,
|
||||||
MetadataThemeSettings,
|
|
||||||
} from '@/typings.ts';
|
} from '@/typings.ts';
|
||||||
import { requestManager } from '@/lib/requests/requests/RequestManager.ts';
|
import { requestManager } from '@/lib/requests/requests/RequestManager.ts';
|
||||||
import { convertFromGqlMeta, getMetadataFrom, requestUpdateServerMetadata } from '@/lib/metadata/metadata.ts';
|
import { convertFromGqlMeta, getMetadataFrom, requestUpdateServerMetadata } from '@/lib/metadata/metadata.ts';
|
||||||
@@ -21,6 +20,7 @@ import { DEFAULT_DEVICE } from '@/util/device.ts';
|
|||||||
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
|
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
|
||||||
import { DEFAULT_SORT_SETTINGS } from '@/modules/migration/Migration.constants.ts';
|
import { DEFAULT_SORT_SETTINGS } from '@/modules/migration/Migration.constants.ts';
|
||||||
import { MetadataMigrationSettings } from '@/modules/migration/Migration.types.ts';
|
import { MetadataMigrationSettings } from '@/modules/migration/Migration.types.ts';
|
||||||
|
import { MetadataThemeSettings } from '@/modules/theme/AppTheme.types.ts';
|
||||||
|
|
||||||
export const getDefaultSettings = (): MetadataServerSettings => ({
|
export const getDefaultSettings = (): MetadataServerSettings => ({
|
||||||
// downloads
|
// downloads
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import useMediaQuery from '@mui/material/useMediaQuery';
|
|||||||
import { Breakpoint } from '@mui/material/styles';
|
import { Breakpoint } from '@mui/material/styles';
|
||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
import { getCurrentTheme } from '@/theme.tsx';
|
import { getCurrentTheme } from '@/theme.tsx';
|
||||||
import { ThemeMode } from '@/components/context/ThemeModeContext.tsx';
|
import { ThemeMode } from '@/modules/theme/contexts/ThemeModeContext.tsx';
|
||||||
import { AppStorage } from '@/lib/AppStorage.ts';
|
import { AppStorage } from '@/lib/AppStorage.ts';
|
||||||
import { useResizeObserver } from '@/modules/core/hooks/useResizeObserver.tsx';
|
import { useResizeObserver } from '@/modules/core/hooks/useResizeObserver.tsx';
|
||||||
|
|
||||||
|
|||||||
@@ -19,12 +19,12 @@ import rtlPlugin from 'stylis-plugin-rtl';
|
|||||||
import { SnackbarProvider } from 'notistack';
|
import { SnackbarProvider } from 'notistack';
|
||||||
import { createAndSetTheme } from '@/theme.tsx';
|
import { createAndSetTheme } from '@/theme.tsx';
|
||||||
import { useLocalStorage } from '@/modules/core/hooks/useStorage.tsx';
|
import { useLocalStorage } from '@/modules/core/hooks/useStorage.tsx';
|
||||||
import { ThemeMode, ThemeModeContext } from '@/components/context/ThemeModeContext.tsx';
|
import { ThemeMode, ThemeModeContext } from '@/modules/theme/contexts/ThemeModeContext.tsx';
|
||||||
import { NavBarContextProvider } from '@/components/navbar/NavBarContextProvider.tsx';
|
import { NavBarContextProvider } from '@/components/navbar/NavBarContextProvider.tsx';
|
||||||
import { LibraryOptionsContextProvider } from '@/modules/library/contexts/LibraryOptionsProvider.tsx';
|
import { LibraryOptionsContextProvider } from '@/modules/library/contexts/LibraryOptionsProvider.tsx';
|
||||||
import { ActiveDevice, DEFAULT_DEVICE, setActiveDevice } from '@/util/device.ts';
|
import { ActiveDevice, DEFAULT_DEVICE, setActiveDevice } from '@/util/device.ts';
|
||||||
import { MediaQuery } from '@/lib/ui/MediaQuery.tsx';
|
import { MediaQuery } from '@/lib/ui/MediaQuery.tsx';
|
||||||
import { AppThemes, getTheme } from '@/lib/ui/AppThemes.ts';
|
import { AppThemes, getTheme } from '@/modules/theme/services/AppThemes.ts';
|
||||||
import { useMetadataServerSettings } from '@/lib/metadata/metadataServerSettings.ts';
|
import { useMetadataServerSettings } from '@/lib/metadata/metadataServerSettings.ts';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|||||||
14
src/modules/theme/AppTheme.types.ts
Normal file
14
src/modules/theme/AppTheme.types.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
/*
|
||||||
|
* 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/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { AppTheme } from '@/modules/theme/services/AppThemes.ts';
|
||||||
|
|
||||||
|
export type MetadataThemeSettings = {
|
||||||
|
customThemes: Record<string, AppTheme>;
|
||||||
|
mangaThumbnailBackdrop: boolean;
|
||||||
|
};
|
||||||
@@ -8,13 +8,10 @@
|
|||||||
|
|
||||||
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 '@/lib/DefaultPromiseErrorHandler.ts';
|
|
||||||
import { ControlledPromise } from '@/lib/ControlledPromise.ts';
|
|
||||||
|
|
||||||
type Theme = { isCustom: boolean; getName: () => string; muiTheme: ThemeOptions };
|
export type TBaseTheme = { isCustom: boolean; getName: () => string; muiTheme: ThemeOptions };
|
||||||
|
|
||||||
const themes = {
|
export const themes = {
|
||||||
default: {
|
default: {
|
||||||
isCustom: false,
|
isCustom: false,
|
||||||
getName: () => translate('global.label.default'),
|
getName: () => translate('global.label.default'),
|
||||||
@@ -183,93 +180,4 @@ const themes = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as const satisfies Record<string, Theme>;
|
} as const satisfies Record<string, TBaseTheme>;
|
||||||
|
|
||||||
export type AppThemes = keyof typeof themes | string;
|
|
||||||
|
|
||||||
export type AppTheme = Theme & { id: AppThemes; isCustom: boolean };
|
|
||||||
|
|
||||||
export const appThemes = (Object.entries(themes) as [AppThemes, Theme][]).map(([id, theme]) => ({
|
|
||||||
id,
|
|
||||||
...theme,
|
|
||||||
})) satisfies AppTheme[];
|
|
||||||
|
|
||||||
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());
|
|
||||||
|
|
||||||
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 hasMissingFonts = (theme: ThemeOptions): boolean => {
|
|
||||||
const themeFonts = getFontsFromTheme(theme.typography ?? {});
|
|
||||||
return !!themeFonts.length && themeFonts.some((font) => !loadedFonts.includes(font));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const loadThemeFonts = async (theme: ThemeOptions): Promise<void> => {
|
|
||||||
const themeFonts = getFontsFromTheme(theme.typography ?? {});
|
|
||||||
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;
|
|
||||||
};
|
|
||||||
@@ -12,7 +12,7 @@ import Button from '@mui/material/Button';
|
|||||||
import AddCircleIcon from '@mui/icons-material/AddCircle';
|
import AddCircleIcon from '@mui/icons-material/AddCircle';
|
||||||
import { bindDialog, bindTrigger, usePopupState } from 'material-ui-popup-state/hooks';
|
import { bindDialog, bindTrigger, usePopupState } from 'material-ui-popup-state/hooks';
|
||||||
import Tooltip from '@mui/material/Tooltip';
|
import Tooltip from '@mui/material/Tooltip';
|
||||||
import { ThemeCreationDialog } from '@/screens/settings/appearance/theme/CreateThemeDialog.tsx';
|
import { ThemeCreationDialog } from '@/modules/theme/components/CreateThemeDialog.tsx';
|
||||||
import { TypographyMaxLines } from '@/modules/core/components/TypographyMaxLines.tsx';
|
import { TypographyMaxLines } from '@/modules/core/components/TypographyMaxLines.tsx';
|
||||||
|
|
||||||
export const CreateThemeButton = () => {
|
export const CreateThemeButton = () => {
|
||||||
@@ -20,17 +20,17 @@ import TextField from '@mui/material/TextField';
|
|||||||
import Link from '@mui/material/Link';
|
import Link from '@mui/material/Link';
|
||||||
import { jsonrepair } from 'jsonrepair';
|
import { jsonrepair } from 'jsonrepair';
|
||||||
import { jsonSaveParse } from '@/lib/HelperFunctions.ts';
|
import { jsonSaveParse } from '@/lib/HelperFunctions.ts';
|
||||||
import { AppTheme, isThemeNameUnique } from '@/lib/ui/AppThemes.ts';
|
import { AppTheme, isThemeNameUnique } from '@/modules/theme/services/AppThemes.ts';
|
||||||
import {
|
import {
|
||||||
createUpdateMetadataServerSettings,
|
createUpdateMetadataServerSettings,
|
||||||
useMetadataServerSettings,
|
useMetadataServerSettings,
|
||||||
} from '@/lib/metadata/metadataServerSettings.ts';
|
} from '@/lib/metadata/metadataServerSettings.ts';
|
||||||
import { MetadataThemeSettings } from '@/typings.ts';
|
|
||||||
import { makeToast } from '@/lib/ui/Toast.ts';
|
import { makeToast } from '@/lib/ui/Toast.ts';
|
||||||
import { EmptyView } from '@/modules/core/components/placeholder/EmptyView.tsx';
|
import { EmptyView } from '@/modules/core/components/placeholder/EmptyView.tsx';
|
||||||
import { LoadingPlaceholder } from '@/modules/core/components/placeholder/LoadingPlaceholder.tsx';
|
import { LoadingPlaceholder } from '@/modules/core/components/placeholder/LoadingPlaceholder.tsx';
|
||||||
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
|
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
|
||||||
import { TranslationKey } from '@/Base.types.ts';
|
import { TranslationKey } from '@/Base.types.ts';
|
||||||
|
import { MetadataThemeSettings } from '@/modules/theme/AppTheme.types.ts';
|
||||||
|
|
||||||
const baseCustomTheme: AppTheme = {
|
const baseCustomTheme: AppTheme = {
|
||||||
id: '',
|
id: '',
|
||||||
@@ -9,18 +9,18 @@
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import Stack from '@mui/material/Stack';
|
import Stack from '@mui/material/Stack';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { appThemes } from '@/lib/ui/AppThemes.ts';
|
import { appThemes } from '@/modules/theme/services/AppThemes.ts';
|
||||||
import {
|
import {
|
||||||
createUpdateMetadataServerSettings,
|
createUpdateMetadataServerSettings,
|
||||||
useMetadataServerSettings,
|
useMetadataServerSettings,
|
||||||
} from '@/lib/metadata/metadataServerSettings.ts';
|
} from '@/lib/metadata/metadataServerSettings.ts';
|
||||||
import { MetadataThemeSettings } from '@/typings.ts';
|
|
||||||
import { makeToast } from '@/lib/ui/Toast.ts';
|
import { makeToast } from '@/lib/ui/Toast.ts';
|
||||||
import { EmptyView } from '@/modules/core/components/placeholder/EmptyView.tsx';
|
import { EmptyView } from '@/modules/core/components/placeholder/EmptyView.tsx';
|
||||||
import { LoadingPlaceholder } from '@/modules/core/components/placeholder/LoadingPlaceholder.tsx';
|
import { LoadingPlaceholder } from '@/modules/core/components/placeholder/LoadingPlaceholder.tsx';
|
||||||
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
|
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
|
||||||
import { CreateThemeButton } from '@/screens/settings/appearance/theme/CreateThemeButton.tsx';
|
import { CreateThemeButton } from '@/modules/theme/components/CreateThemeButton.tsx';
|
||||||
import { ThemePreview } from '@/screens/settings/appearance/theme/ThemePreview.tsx';
|
import { ThemePreview } from '@/modules/theme/components/ThemePreview.tsx';
|
||||||
|
import { MetadataThemeSettings } from '@/modules/theme/AppTheme.types.ts';
|
||||||
|
|
||||||
export const ThemeList = () => {
|
export const ThemeList = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -19,10 +19,10 @@ import IconButton from '@mui/material/IconButton';
|
|||||||
import Tooltip from '@mui/material/Tooltip';
|
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 '@/modules/theme/contexts/ThemeModeContext.tsx';
|
||||||
import { AppTheme, hasMissingFonts, loadThemeFonts } from '@/lib/ui/AppThemes.ts';
|
import { AppTheme, hasMissingFonts, loadThemeFonts } from '@/modules/theme/services/AppThemes.ts';
|
||||||
import { createTheme } from '@/theme.tsx';
|
import { createTheme } from '@/theme.tsx';
|
||||||
import { ThemeCreationDialog } from '@/screens/settings/appearance/theme/CreateThemeDialog.tsx';
|
import { ThemeCreationDialog } from '@/modules/theme/components/CreateThemeDialog.tsx';
|
||||||
import { makeToast } from '@/lib/ui/Toast.ts';
|
import { makeToast } from '@/lib/ui/Toast.ts';
|
||||||
import { TypographyMaxLines } from '@/modules/core/components/TypographyMaxLines.tsx';
|
import { TypographyMaxLines } from '@/modules/core/components/TypographyMaxLines.tsx';
|
||||||
|
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { AppThemes } from '@/lib/ui/AppThemes.ts';
|
import { AppThemes } from '@/modules/theme/services/AppThemes.ts';
|
||||||
|
|
||||||
export enum ThemeMode {
|
export enum ThemeMode {
|
||||||
SYSTEM = 'system',
|
SYSTEM = 'system',
|
||||||
102
src/modules/theme/services/AppThemes.ts
Normal file
102
src/modules/theme/services/AppThemes.ts
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
/*
|
||||||
|
* 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/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { ThemeOptions } from '@mui/material/styles';
|
||||||
|
import WebFont from 'webfontloader';
|
||||||
|
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
|
||||||
|
import { ControlledPromise } from '@/lib/ControlledPromise.ts';
|
||||||
|
import { TBaseTheme, themes } from '@/modules/theme/Themes.ts';
|
||||||
|
|
||||||
|
export type AppThemes = keyof typeof themes | string;
|
||||||
|
|
||||||
|
export type AppTheme = TBaseTheme & { id: AppThemes; isCustom: boolean };
|
||||||
|
|
||||||
|
export const appThemes = (Object.entries(themes) as [AppThemes, TBaseTheme][]).map(([id, theme]) => ({
|
||||||
|
id,
|
||||||
|
...theme,
|
||||||
|
})) satisfies AppTheme[];
|
||||||
|
|
||||||
|
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());
|
||||||
|
|
||||||
|
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 hasMissingFonts = (theme: ThemeOptions): boolean => {
|
||||||
|
const themeFonts = getFontsFromTheme(theme.typography ?? {});
|
||||||
|
return !!themeFonts.length && themeFonts.some((font) => !loadedFonts.includes(font));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const loadThemeFonts = async (theme: ThemeOptions): Promise<void> => {
|
||||||
|
const themeFonts = getFontsFromTheme(theme.typography ?? {});
|
||||||
|
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;
|
||||||
|
};
|
||||||
@@ -16,15 +16,15 @@ import ListSubheader from '@mui/material/ListSubheader';
|
|||||||
import Switch from '@mui/material/Switch';
|
import Switch from '@mui/material/Switch';
|
||||||
import Link from '@mui/material/Link';
|
import Link from '@mui/material/Link';
|
||||||
import { NavBarContext } from '@/components/context/NavbarContext.tsx';
|
import { NavBarContext } from '@/components/context/NavbarContext.tsx';
|
||||||
import { ThemeMode, ThemeModeContext } from '@/components/context/ThemeModeContext.tsx';
|
import { ThemeMode, ThemeModeContext } from '@/modules/theme/contexts/ThemeModeContext.tsx';
|
||||||
import { Select } from '@/modules/core/components/inputs/Select.tsx';
|
import { Select } from '@/modules/core/components/inputs/Select.tsx';
|
||||||
import { MediaQuery } from '@/lib/ui/MediaQuery.tsx';
|
import { MediaQuery } from '@/lib/ui/MediaQuery.tsx';
|
||||||
import { NumberSetting } from '@/modules/core/components/settings/NumberSetting.tsx';
|
import { NumberSetting } from '@/modules/core/components/settings/NumberSetting.tsx';
|
||||||
import { useLocalStorage } from '@/modules/core/hooks/useStorage.tsx';
|
import { useLocalStorage } from '@/modules/core/hooks/useStorage.tsx';
|
||||||
import { I18nResourceCode, i18nResources } from '@/i18n';
|
import { I18nResourceCode, i18nResources } from '@/i18n';
|
||||||
import { langCodeToName } from '@/lib/Languages.tsx';
|
import { langCodeToName } from '@/lib/Languages.tsx';
|
||||||
import { getTheme } from '@/lib/ui/AppThemes.ts';
|
import { getTheme } from '@/modules/theme/services/AppThemes.ts';
|
||||||
import { ThemeList } from '@/screens/settings/appearance/theme/ThemeList.tsx';
|
import { ThemeList } from '@/modules/theme/components/ThemeList.tsx';
|
||||||
import {
|
import {
|
||||||
createUpdateMetadataServerSettings,
|
createUpdateMetadataServerSettings,
|
||||||
useMetadataServerSettings,
|
useMetadataServerSettings,
|
||||||
@@ -32,8 +32,8 @@ import {
|
|||||||
import { LoadingPlaceholder } from '@/modules/core/components/placeholder/LoadingPlaceholder.tsx';
|
import { LoadingPlaceholder } from '@/modules/core/components/placeholder/LoadingPlaceholder.tsx';
|
||||||
import { EmptyViewAbsoluteCentered } from '@/modules/core/components/placeholder/EmptyViewAbsoluteCentered.tsx';
|
import { EmptyViewAbsoluteCentered } from '@/modules/core/components/placeholder/EmptyViewAbsoluteCentered.tsx';
|
||||||
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
|
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
|
||||||
import { MetadataThemeSettings } from '@/typings.ts';
|
|
||||||
import { makeToast } from '@/lib/ui/Toast.ts';
|
import { makeToast } from '@/lib/ui/Toast.ts';
|
||||||
|
import { MetadataThemeSettings } from '@/modules/theme/AppTheme.types.ts';
|
||||||
|
|
||||||
export const Appearance = () => {
|
export const Appearance = () => {
|
||||||
const { t, i18n } = useTranslation();
|
const { t, i18n } = useTranslation();
|
||||||
@@ -17,9 +17,9 @@ import {
|
|||||||
useTheme,
|
useTheme,
|
||||||
} from '@mui/material/styles';
|
} from '@mui/material/styles';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { ThemeMode } from '@/components/context/ThemeModeContext.tsx';
|
import { ThemeMode } from '@/modules/theme/contexts/ThemeModeContext.tsx';
|
||||||
import { MediaQuery } from '@/lib/ui/MediaQuery.tsx';
|
import { MediaQuery } from '@/lib/ui/MediaQuery.tsx';
|
||||||
import { AppTheme, loadThemeFonts } from '@/lib/ui/AppThemes.ts';
|
import { AppTheme, loadThemeFonts } from '@/modules/theme/services/AppThemes.ts';
|
||||||
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
|
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
|
||||||
|
|
||||||
const SCROLLBAR_SIZE = 14;
|
const SCROLLBAR_SIZE = 14;
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
import { OverridableComponent } from '@mui/material/OverridableComponent';
|
import { OverridableComponent } from '@mui/material/OverridableComponent';
|
||||||
import { SvgIconTypeMap } from '@mui/material/SvgIcon';
|
import { SvgIconTypeMap } from '@mui/material/SvgIcon';
|
||||||
import { GetServerSettingsQuery, MetaType } from '@/lib/graphql/generated/graphql.ts';
|
import { GetServerSettingsQuery, MetaType } from '@/lib/graphql/generated/graphql.ts';
|
||||||
import { AppTheme } from '@/lib/ui/AppThemes.ts';
|
|
||||||
import { MetadataMigrationSettings } from '@/modules/migration/Migration.types.ts';
|
import { MetadataMigrationSettings } from '@/modules/migration/Migration.types.ts';
|
||||||
import { TranslationKey } from '@/Base.types.ts';
|
import { TranslationKey } from '@/Base.types.ts';
|
||||||
import { MangaMetadataKeys } from '@/modules/manga/MangaCard.types.tsx';
|
import { MangaMetadataKeys } from '@/modules/manga/MangaCard.types.tsx';
|
||||||
@@ -18,6 +17,7 @@ import { SourceMetadataKeys } from '@/modules/source/Source.types.ts';
|
|||||||
import { MetadataTrackingSettings } from '@/modules/tracker/Tracker.types.ts';
|
import { MetadataTrackingSettings } from '@/modules/tracker/Tracker.types.ts';
|
||||||
import { CategoryMetadataKeys } from '@/modules/category/Category.types.ts';
|
import { CategoryMetadataKeys } from '@/modules/category/Category.types.ts';
|
||||||
import { MetadataUpdateSettings } from '@/modules/app-updates/AppUpdateChecker.types.ts';
|
import { MetadataUpdateSettings } from '@/modules/app-updates/AppUpdateChecker.types.ts';
|
||||||
|
import { MetadataThemeSettings } from '@/modules/theme/AppTheme.types.ts';
|
||||||
|
|
||||||
export interface IMetadataMigration {
|
export interface IMetadataMigration {
|
||||||
appKeyPrefix?: { oldPrefix: string; newPrefix: string };
|
appKeyPrefix?: { oldPrefix: string; newPrefix: string };
|
||||||
@@ -78,11 +78,6 @@ export type MetadataBrowseSettings = {
|
|||||||
hideLibraryEntries: boolean;
|
hideLibraryEntries: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type MetadataThemeSettings = {
|
|
||||||
customThemes: Record<string, AppTheme>;
|
|
||||||
mangaThumbnailBackdrop: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type MetadataServerSettings = MetadataDownloadSettings &
|
export type MetadataServerSettings = MetadataDownloadSettings &
|
||||||
MetadataLibrarySettings &
|
MetadataLibrarySettings &
|
||||||
MetadataClientSettings &
|
MetadataClientSettings &
|
||||||
|
|||||||
Reference in New Issue
Block a user