diff --git a/public/locales/en.json b/public/locales/en.json index ae33e2e7..03e0b0bc 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -819,6 +819,10 @@ } }, "appearance": { + "manga_thumbnail_backdrop": { + "description": "Sets the manga thumbnail as the background image on the manga page", + "title": "Manga thumbnail as background" + }, "theme": { "create": { "action": "Creating theme…", diff --git a/src/components/manga/MangaDetails.tsx b/src/components/manga/MangaDetails.tsx index 1a7550a6..bb8b6c5c 100644 --- a/src/components/manga/MangaDetails.tsx +++ b/src/components/manga/MangaDetails.tsx @@ -37,6 +37,7 @@ import { defaultPromiseErrorHandler } from '@/util/defaultPromiseErrorHandler.ts import { MangaType, SourceType } from '@/lib/graphql/generated/graphql.ts'; import { useLocalStorage } from '@/util/useStorage.tsx'; import { useResizeObserver } from '@/util/useResizeObserver.tsx'; +import { useMetadataServerSettings } from '@/lib/metadata/metadataServerSettings.ts'; const DetailsWrapper = styled('div')(({ theme }) => ({ display: 'flex', @@ -50,7 +51,29 @@ const DetailsWrapper = styled('div')(({ theme }) => ({ }, })); -const TopContentWrapper = styled('div')(() => ({})); +const TopContentWrapper = styled('div', { + shouldForwardProp: (prop) => !['url', 'mangaThumbnailBackdrop'].includes(prop as string), +})<{ + url: string; + mangaThumbnailBackdrop: boolean; +}>(({ theme, url, mangaThumbnailBackdrop }) => ({ + position: 'relative', + backgroundImage: mangaThumbnailBackdrop ? `url(${url})` : undefined, + backgroundRepeat: mangaThumbnailBackdrop ? 'no-repeat' : undefined, + backgroundSize: mangaThumbnailBackdrop ? 'cover' : undefined, + borderRadius: mangaThumbnailBackdrop ? theme.shape.borderRadius : undefined, + '&::before': mangaThumbnailBackdrop && { + position: 'absolute', + display: 'inline-block', + content: '""', + top: 0, + left: 0, + width: '100%', + height: '100%', + background: `linear-gradient(to top, ${theme.palette.background.default}, transparent 100%, transparent 1px),linear-gradient(to right, ${theme.palette.background.default}, transparent 50%, transparent 1px),linear-gradient(to bottom, ${theme.palette.background.default}, transparent 50%, transparent 1px),linear-gradient(to left, ${theme.palette.background.default}, transparent 50%, transparent 1px)`, + backdropFilter: 'blur(4.5px) brightness(0.75)', + }, +})); const ThumbnailMetadataWrapper = styled('div')(({ theme }) => ({ display: 'flex', @@ -58,6 +81,7 @@ const ThumbnailMetadataWrapper = styled('div')(({ theme }) => ({ })); const MetadataContainer = styled('div')(({ theme }) => ({ + zIndex: 1, marginLeft: theme.spacing(1), })); @@ -261,6 +285,10 @@ export const MangaDetails = ({ }) => { const { t } = useTranslation(); + const { + settings: { mangaThumbnailBackdrop }, + } = useMetadataServerSettings(); + useEffect(() => { if (!manga.source) { makeToast(translate('source.error.label.source_not_found'), 'error'); @@ -281,7 +309,7 @@ export const MangaDetails = ({ return ( <> - + diff --git a/src/lib/metadata/metadata.ts b/src/lib/metadata/metadata.ts index 22bbe958..3ecd2f2c 100644 --- a/src/lib/metadata/metadata.ts +++ b/src/lib/metadata/metadata.ts @@ -60,6 +60,7 @@ const GLOBAL_METADATA_KEYS: AppMetadataKeys[] = [ // themes 'customThemes', + 'mangaThumbnailBackdrop', ]; /** diff --git a/src/lib/metadata/metadataServerSettings.ts b/src/lib/metadata/metadataServerSettings.ts index 615938d7..5231a83d 100644 --- a/src/lib/metadata/metadataServerSettings.ts +++ b/src/lib/metadata/metadataServerSettings.ts @@ -54,6 +54,7 @@ export const getDefaultSettings = (): MetadataServerSettings => ({ // themes customThemes: {}, + mangaThumbnailBackdrop: true, }); export const convertSettingsToMetadata = ( diff --git a/src/screens/settings/appearance/Appearance.tsx b/src/screens/settings/appearance/Appearance.tsx index 218eb871..b708724b 100644 --- a/src/screens/settings/appearance/Appearance.tsx +++ b/src/screens/settings/appearance/Appearance.tsx @@ -25,12 +25,19 @@ import { I18nResourceCode, i18nResources } from '@/i18n'; import { langCodeToName } from '@/util/language.tsx'; import { getTheme } from '@/lib/ui/AppThemes.ts'; import { ThemeList } from '@/screens/settings/appearance/theme/ThemeList.tsx'; +import { + createUpdateMetadataServerSettings, + useMetadataServerSettings, +} from '@/lib/metadata/metadataServerSettings.ts'; +import { LoadingPlaceholder } from '@/components/util/LoadingPlaceholder.tsx'; +import { EmptyViewAbsoluteCentered } from '@/components/util/EmptyViewAbsoluteCentered.tsx'; +import { defaultPromiseErrorHandler } from '@/util/defaultPromiseErrorHandler.ts'; +import { MetadataThemeSettings } from '@/typings.ts'; +import { makeToast } from '@/components/util/Toast.tsx'; export const Appearance = () => { const { t, i18n } = useTranslation(); const { themeMode, setThemeMode, pureBlackMode, setPureBlackMode, appTheme } = useContext(ThemeModeContext); - const isDarkMode = - getTheme(appTheme).muiTheme.palette?.mode === 'dark' || MediaQuery.getThemeMode() === ThemeMode.DARK; const { setTitle, setAction } = useContext(NavBarContext); useEffect(() => { @@ -43,9 +50,34 @@ export const Appearance = () => { }; }, [t]); + const { + settings, + request: { loading, error, refetch }, + } = useMetadataServerSettings(); + const updateMetadataSetting = createUpdateMetadataServerSettings(() => + makeToast(t('global.error.label.failed_to_save_changes'), 'error'), + ); + + const isDarkMode = + getTheme(appTheme).muiTheme.palette?.mode === 'dark' || MediaQuery.getThemeMode() === ThemeMode.DARK; + const DEFAULT_ITEM_WIDTH = 300; const [itemWidth, setItemWidth] = useLocalStorage('ItemWidth', DEFAULT_ITEM_WIDTH); + if (loading) { + return ; + } + + if (error) { + return ( + refetch().catch(defaultPromiseErrorHandler('Appearance::refetch'))} + /> + ); + } + return ( { showSlider handleUpdate={setItemWidth} /> + + + + updateMetadataSetting('mangaThumbnailBackdrop', e.target.checked)} + /> + ); diff --git a/src/typings.ts b/src/typings.ts index e8cfa5a5..35926a5d 100644 --- a/src/typings.ts +++ b/src/typings.ts @@ -159,6 +159,7 @@ export type MetadataUpdateSettings = { export type MetadataThemeSettings = { customThemes: Record; + mangaThumbnailBackdrop: boolean; }; export type MetadataServerSettings = MetadataDownloadSettings &