Set manga thumbnail as background image
This commit is contained in:
@@ -819,6 +819,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"appearance": {
|
"appearance": {
|
||||||
|
"manga_thumbnail_backdrop": {
|
||||||
|
"description": "Sets the manga thumbnail as the background image on the manga page",
|
||||||
|
"title": "Manga thumbnail as background"
|
||||||
|
},
|
||||||
"theme": {
|
"theme": {
|
||||||
"create": {
|
"create": {
|
||||||
"action": "Creating theme…",
|
"action": "Creating theme…",
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import { defaultPromiseErrorHandler } from '@/util/defaultPromiseErrorHandler.ts
|
|||||||
import { MangaType, SourceType } from '@/lib/graphql/generated/graphql.ts';
|
import { MangaType, SourceType } from '@/lib/graphql/generated/graphql.ts';
|
||||||
import { useLocalStorage } from '@/util/useStorage.tsx';
|
import { useLocalStorage } from '@/util/useStorage.tsx';
|
||||||
import { useResizeObserver } from '@/util/useResizeObserver.tsx';
|
import { useResizeObserver } from '@/util/useResizeObserver.tsx';
|
||||||
|
import { useMetadataServerSettings } from '@/lib/metadata/metadataServerSettings.ts';
|
||||||
|
|
||||||
const DetailsWrapper = styled('div')(({ theme }) => ({
|
const DetailsWrapper = styled('div')(({ theme }) => ({
|
||||||
display: 'flex',
|
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 }) => ({
|
const ThumbnailMetadataWrapper = styled('div')(({ theme }) => ({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@@ -58,6 +81,7 @@ const ThumbnailMetadataWrapper = styled('div')(({ theme }) => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
const MetadataContainer = styled('div')(({ theme }) => ({
|
const MetadataContainer = styled('div')(({ theme }) => ({
|
||||||
|
zIndex: 1,
|
||||||
marginLeft: theme.spacing(1),
|
marginLeft: theme.spacing(1),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -261,6 +285,10 @@ export const MangaDetails = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const {
|
||||||
|
settings: { mangaThumbnailBackdrop },
|
||||||
|
} = useMetadataServerSettings();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!manga.source) {
|
if (!manga.source) {
|
||||||
makeToast(translate('source.error.label.source_not_found'), 'error');
|
makeToast(translate('source.error.label.source_not_found'), 'error');
|
||||||
@@ -281,7 +309,7 @@ export const MangaDetails = ({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<DetailsWrapper>
|
<DetailsWrapper>
|
||||||
<TopContentWrapper>
|
<TopContentWrapper url={Mangas.getThumbnailUrl(manga)} mangaThumbnailBackdrop={mangaThumbnailBackdrop}>
|
||||||
<ThumbnailMetadataWrapper>
|
<ThumbnailMetadataWrapper>
|
||||||
<Thumbnail manga={manga} />
|
<Thumbnail manga={manga} />
|
||||||
<MetadataContainer>
|
<MetadataContainer>
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ const GLOBAL_METADATA_KEYS: AppMetadataKeys[] = [
|
|||||||
|
|
||||||
// themes
|
// themes
|
||||||
'customThemes',
|
'customThemes',
|
||||||
|
'mangaThumbnailBackdrop',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ export const getDefaultSettings = (): MetadataServerSettings => ({
|
|||||||
|
|
||||||
// themes
|
// themes
|
||||||
customThemes: {},
|
customThemes: {},
|
||||||
|
mangaThumbnailBackdrop: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const convertSettingsToMetadata = (
|
export const convertSettingsToMetadata = (
|
||||||
|
|||||||
@@ -25,12 +25,19 @@ import { I18nResourceCode, i18nResources } from '@/i18n';
|
|||||||
import { langCodeToName } from '@/util/language.tsx';
|
import { langCodeToName } from '@/util/language.tsx';
|
||||||
import { getTheme } from '@/lib/ui/AppThemes.ts';
|
import { getTheme } from '@/lib/ui/AppThemes.ts';
|
||||||
import { ThemeList } from '@/screens/settings/appearance/theme/ThemeList.tsx';
|
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 = () => {
|
export const Appearance = () => {
|
||||||
const { t, i18n } = useTranslation();
|
const { t, i18n } = useTranslation();
|
||||||
const { themeMode, setThemeMode, pureBlackMode, setPureBlackMode, appTheme } = useContext(ThemeModeContext);
|
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);
|
const { setTitle, setAction } = useContext(NavBarContext);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -43,9 +50,34 @@ export const Appearance = () => {
|
|||||||
};
|
};
|
||||||
}, [t]);
|
}, [t]);
|
||||||
|
|
||||||
|
const {
|
||||||
|
settings,
|
||||||
|
request: { loading, error, refetch },
|
||||||
|
} = useMetadataServerSettings();
|
||||||
|
const updateMetadataSetting = createUpdateMetadataServerSettings<keyof MetadataThemeSettings>(() =>
|
||||||
|
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 DEFAULT_ITEM_WIDTH = 300;
|
||||||
const [itemWidth, setItemWidth] = useLocalStorage<number>('ItemWidth', DEFAULT_ITEM_WIDTH);
|
const [itemWidth, setItemWidth] = useLocalStorage<number>('ItemWidth', DEFAULT_ITEM_WIDTH);
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return <LoadingPlaceholder />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<EmptyViewAbsoluteCentered
|
||||||
|
message={t('global.error.label.failed_to_load_data')}
|
||||||
|
messageExtra={error.message}
|
||||||
|
retry={() => refetch().catch(defaultPromiseErrorHandler('Appearance::refetch'))}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<List
|
<List
|
||||||
subheader={
|
subheader={
|
||||||
@@ -121,6 +153,18 @@ export const Appearance = () => {
|
|||||||
showSlider
|
showSlider
|
||||||
handleUpdate={setItemWidth}
|
handleUpdate={setItemWidth}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<ListItem>
|
||||||
|
<ListItemText
|
||||||
|
primary={t('settings.appearance.manga_thumbnail_backdrop.title')}
|
||||||
|
secondary={t('settings.appearance.manga_thumbnail_backdrop.description')}
|
||||||
|
/>
|
||||||
|
<Switch
|
||||||
|
edge="end"
|
||||||
|
checked={settings.mangaThumbnailBackdrop}
|
||||||
|
onChange={(e) => updateMetadataSetting('mangaThumbnailBackdrop', e.target.checked)}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
</List>
|
</List>
|
||||||
</List>
|
</List>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -159,6 +159,7 @@ export type MetadataUpdateSettings = {
|
|||||||
|
|
||||||
export type MetadataThemeSettings = {
|
export type MetadataThemeSettings = {
|
||||||
customThemes: Record<string, AppTheme>;
|
customThemes: Record<string, AppTheme>;
|
||||||
|
mangaThumbnailBackdrop: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type MetadataServerSettings = MetadataDownloadSettings &
|
export type MetadataServerSettings = MetadataDownloadSettings &
|
||||||
|
|||||||
Reference in New Issue
Block a user