Add "images" settings page
This commit is contained in:
24
src/App.tsx
24
src/App.tsx
@@ -60,8 +60,9 @@ const { DownloadSettings } = loadable(
|
||||
() => import('@/features/downloads/screens/DownloadSettings.tsx'),
|
||||
lazyLoadFallback,
|
||||
);
|
||||
const { DownloadConversionSettings } = loadable(
|
||||
() => import('@/features/downloads/screens/DownloadConversionSettings.tsx'),
|
||||
const { ImagesSettings } = loadable(() => import('@/features/settings/screens/ImagesSettings.tsx'), lazyLoadFallback);
|
||||
const { ImageProcessingSetting } = loadable(
|
||||
() => import('@/features/settings/screens/ImageProcessingSetting.tsx'),
|
||||
lazyLoadFallback,
|
||||
);
|
||||
const { ServerSettings } = loadable(() => import('@/features/settings/screens/ServerSettings.tsx'), lazyLoadFallback);
|
||||
@@ -227,9 +228,25 @@ const MainApp = () => {
|
||||
</Route>
|
||||
<Route path={AppRoutes.settings.childRoutes.download.match}>
|
||||
<Route index element={<DownloadSettings />} />
|
||||
{/* TODO: deprecated - got moved to "settings/images/processing/downloads" */}
|
||||
<Route
|
||||
path={AppRoutes.settings.childRoutes.download.childRoutes.conversions.match}
|
||||
element={<DownloadConversionSettings />}
|
||||
element={
|
||||
<Navigate
|
||||
to={
|
||||
AppRoutes.settings.childRoutes.images.childRoutes.processingDownloads
|
||||
.path
|
||||
}
|
||||
replace
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Route>
|
||||
<Route path={AppRoutes.settings.childRoutes.images.match}>
|
||||
<Route index element={<ImagesSettings />} />
|
||||
<Route
|
||||
path={AppRoutes.settings.childRoutes.images.childRoutes.processingDownloads.match}
|
||||
element={<ImageProcessingSetting />}
|
||||
/>
|
||||
</Route>
|
||||
<Route path={AppRoutes.settings.childRoutes.backup.match} element={<Backup />} />
|
||||
@@ -255,6 +272,7 @@ const MainApp = () => {
|
||||
<Route path={AppRoutes.sources.childRoutes.searchAll.match} element={<SearchAll />} />
|
||||
</Route>
|
||||
<Route path={AppRoutes.extension.match}>
|
||||
{/* TODO: deprecated - "source" and "extension" page got merged into "browse" */}
|
||||
<Route
|
||||
index
|
||||
element={<Navigate to={AppRoutes.browse.path(BrowseTab.EXTENSIONS)} replace />}
|
||||
|
||||
@@ -70,12 +70,23 @@ export const AppRoutes = {
|
||||
match: 'download',
|
||||
path: '/settings/download',
|
||||
childRoutes: {
|
||||
// TODO: deprecated - got moved to "settings/images/processing/downloads"
|
||||
conversions: {
|
||||
match: 'conversions',
|
||||
path: '/settings/download/conversions',
|
||||
},
|
||||
},
|
||||
},
|
||||
images: {
|
||||
match: 'images',
|
||||
path: '/settings/images',
|
||||
childRoutes: {
|
||||
processingDownloads: {
|
||||
match: 'processing/downloads',
|
||||
path: '/settings/images/processing/downloads',
|
||||
},
|
||||
},
|
||||
},
|
||||
backup: {
|
||||
match: 'backup',
|
||||
path: '/settings/backup',
|
||||
|
||||
@@ -6,29 +6,9 @@
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { d } from 'koration';
|
||||
|
||||
export const DOWNLOAD_AHEAD = {
|
||||
min: 2,
|
||||
max: 10,
|
||||
default: 2,
|
||||
step: 1,
|
||||
};
|
||||
|
||||
export const DOWNLOAD_CONVERSION_COMPRESSION = {
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
};
|
||||
|
||||
export const IMAGE_CONVERSION_CALL_TIMEOUT = {
|
||||
min: d(10).seconds.inWholeSeconds,
|
||||
max: d(10).minutes.inWholeSeconds,
|
||||
step: d(10).seconds.inWholeSeconds,
|
||||
};
|
||||
|
||||
export const IMAGE_CONVERSION_CONNECT_TIMEOUT = {
|
||||
min: d(10).seconds.inWholeSeconds,
|
||||
max: d(10).minutes.inWholeSeconds,
|
||||
step: d(10).seconds.inWholeSeconds,
|
||||
};
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
* 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 { useTranslation } from 'react-i18next';
|
||||
import { useAppTitle } from '@/features/navigation-bar/hooks/useAppTitle.ts';
|
||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
|
||||
import { LoadingPlaceholder } from '@/base/components/feedback/LoadingPlaceholder.tsx';
|
||||
import { EmptyViewAbsoluteCentered } from '@/base/components/feedback/EmptyViewAbsoluteCentered.tsx';
|
||||
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
|
||||
import { DownloadConversionSetting } from '@/features/downloads/components/DownloadConversionSetting.tsx';
|
||||
import { makeToast } from '@/base/utils/Toast.ts';
|
||||
import { ServerSettings } from '@/features/settings/Settings.types.ts';
|
||||
|
||||
export const DownloadConversionSettings = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
useAppTitle(t('download.title.download'));
|
||||
|
||||
const { data, loading, error, refetch } = requestManager.useGetServerSettings({
|
||||
notifyOnNetworkStatusChange: true,
|
||||
});
|
||||
const [mutateSettings] = requestManager.useUpdateServerSettings();
|
||||
|
||||
const updateSetting = (value: ServerSettings['downloadConversions']): Promise<any> => {
|
||||
const mutation = mutateSettings({ variables: { input: { settings: { downloadConversions: value } } } });
|
||||
mutation.catch((e) => makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e)));
|
||||
|
||||
return mutation;
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return <LoadingPlaceholder />;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<EmptyViewAbsoluteCentered
|
||||
message={t('global.error.label.failed_to_load_data')}
|
||||
messageExtra={getErrorMessage(error)}
|
||||
retry={() => refetch().catch(defaultPromiseErrorHandler('DownloadConversionSetting::refetch'))}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return <DownloadConversionSetting conversions={data!.settings.downloadConversions} updateSetting={updateSetting} />;
|
||||
};
|
||||
@@ -129,7 +129,7 @@ export const DownloadSettings = () => {
|
||||
onChange={(e) => updateSetting('downloadAsCbz', e.target.checked)}
|
||||
/>
|
||||
</ListItem>
|
||||
<ListItemLink to={AppRoutes.settings.childRoutes.download.childRoutes.conversions.path}>
|
||||
<ListItemLink to={AppRoutes.settings.childRoutes.images.childRoutes.processingDownloads.path}>
|
||||
<ListItemText primary={t('download.settings.conversion.title')} />
|
||||
</ListItemLink>
|
||||
<List
|
||||
|
||||
@@ -256,3 +256,19 @@ export const KOREADER_SYNC_CHECKSUM_METHOD_SELECT_VALUES: SelectSettingValue<Kor
|
||||
method,
|
||||
KOREADER_SYNC_CHECKSUM_METHOD_TO_TRANSLATION_KEYS[method],
|
||||
]);
|
||||
|
||||
export const IMAGE_PROCESSING_COMPRESSION = {
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
};
|
||||
export const IMAGE_PROCESSING_CALL_TIMEOUT = {
|
||||
min: d(10).seconds.inWholeSeconds,
|
||||
max: d(10).minutes.inWholeSeconds,
|
||||
step: d(10).seconds.inWholeSeconds,
|
||||
};
|
||||
export const IMAGE_PROCESSING_CONNECT_TIMEOUT = {
|
||||
min: d(10).seconds.inWholeSeconds,
|
||||
max: d(10).minutes.inWholeSeconds,
|
||||
step: d(10).seconds.inWholeSeconds,
|
||||
};
|
||||
|
||||
@@ -24,11 +24,6 @@ import Collapse from '@mui/material/Collapse';
|
||||
import { useElementSize } from '@mantine/hooks';
|
||||
import { TypographyMaxLines } from '@/base/components/texts/TypographyMaxLines.tsx';
|
||||
import { CustomTooltip } from '@/base/components/CustomTooltip.tsx';
|
||||
import {
|
||||
DOWNLOAD_CONVERSION_COMPRESSION,
|
||||
IMAGE_CONVERSION_CALL_TIMEOUT,
|
||||
IMAGE_CONVERSION_CONNECT_TIMEOUT,
|
||||
} from '@/features/downloads/Downloads.constants.ts';
|
||||
import {
|
||||
Maybe,
|
||||
SettingsDownloadConversion,
|
||||
@@ -38,6 +33,20 @@ import {
|
||||
import { SelectSettingValue, SelectSettingValueDisplayInfo } from '@/base/components/settings/SelectSetting.tsx';
|
||||
import { Select } from '@/base/components/inputs/Select.tsx';
|
||||
import { TranslationKey } from '@/base/Base.types.ts';
|
||||
import {
|
||||
IMAGE_PROCESSING_CALL_TIMEOUT,
|
||||
IMAGE_PROCESSING_COMPRESSION,
|
||||
IMAGE_PROCESSING_CONNECT_TIMEOUT,
|
||||
} from '@/features/settings/Settings.constants.ts';
|
||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||
import { ServerSettings } from '@/features/settings/Settings.types.ts';
|
||||
import { makeToast } from '@/base/utils/Toast.ts';
|
||||
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
|
||||
import { LoadingPlaceholder } from '@/base/components/feedback/LoadingPlaceholder.tsx';
|
||||
import { EmptyViewAbsoluteCentered } from '@/base/components/feedback/EmptyViewAbsoluteCentered.tsx';
|
||||
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
|
||||
import { assertIsDefined } from '@/base/Asserts.ts';
|
||||
import { useAppTitle } from '@/features/navigation-bar/hooks/useAppTitle.ts';
|
||||
|
||||
export type TSettingsDownloadConversionHeader = SettingsDownloadConversionHeader & {
|
||||
/**
|
||||
@@ -108,19 +117,19 @@ const isValidNumberSetting = (value: number | null | undefined, min: number, max
|
||||
const isValidCallTimeoutSetting = (timeout: string | null | undefined): boolean =>
|
||||
isValidNumberSetting(
|
||||
timeout ? d(timeout).seconds.inWholeSeconds : null,
|
||||
IMAGE_CONVERSION_CALL_TIMEOUT.min,
|
||||
IMAGE_CONVERSION_CALL_TIMEOUT.max,
|
||||
IMAGE_PROCESSING_CALL_TIMEOUT.min,
|
||||
IMAGE_PROCESSING_CALL_TIMEOUT.max,
|
||||
);
|
||||
|
||||
const isValidConnectTimeoutSetting = (timeout: string | null | undefined): boolean =>
|
||||
isValidNumberSetting(
|
||||
timeout ? d(timeout).seconds.inWholeSeconds : null,
|
||||
IMAGE_CONVERSION_CONNECT_TIMEOUT.min,
|
||||
IMAGE_CONVERSION_CONNECT_TIMEOUT.max,
|
||||
IMAGE_PROCESSING_CONNECT_TIMEOUT.min,
|
||||
IMAGE_PROCESSING_CONNECT_TIMEOUT.max,
|
||||
);
|
||||
|
||||
const isValidCompressionLevel = (compression: number | null | undefined): boolean =>
|
||||
isValidNumberSetting(compression, DOWNLOAD_CONVERSION_COMPRESSION.min, DOWNLOAD_CONVERSION_COMPRESSION.max);
|
||||
isValidNumberSetting(compression, IMAGE_PROCESSING_COMPRESSION.min, IMAGE_PROCESSING_COMPRESSION.max);
|
||||
|
||||
const isUnsetConversion = (mimeType: string, target: string): boolean => mimeType === '' && target === '';
|
||||
|
||||
@@ -521,7 +530,7 @@ const Conversion = ({
|
||||
helperText={!isCompressionLevelValid ? t('global.error.label.invalid_input') : ''}
|
||||
slotProps={{
|
||||
input: {
|
||||
inputProps: DOWNLOAD_CONVERSION_COMPRESSION,
|
||||
inputProps: IMAGE_PROCESSING_COMPRESSION,
|
||||
},
|
||||
}}
|
||||
onChange={(e) => {
|
||||
@@ -542,7 +551,7 @@ const Conversion = ({
|
||||
helperText={!isCallTimeoutValid ? t('global.error.label.invalid_input') : ''}
|
||||
slotProps={{
|
||||
input: {
|
||||
inputProps: IMAGE_CONVERSION_CALL_TIMEOUT,
|
||||
inputProps: IMAGE_PROCESSING_CALL_TIMEOUT,
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
{t('global.date.label.second_other')}
|
||||
@@ -568,7 +577,7 @@ const Conversion = ({
|
||||
helperText={!isConnectTimeoutValid ? t('global.error.label.invalid_input') : ''}
|
||||
slotProps={{
|
||||
input: {
|
||||
inputProps: IMAGE_CONVERSION_CONNECT_TIMEOUT,
|
||||
inputProps: IMAGE_PROCESSING_CONNECT_TIMEOUT,
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
{t('global.date.label.second_other')}
|
||||
@@ -622,15 +631,34 @@ const Conversion = ({
|
||||
);
|
||||
};
|
||||
|
||||
export const DownloadConversionSetting = ({
|
||||
conversions,
|
||||
updateSetting,
|
||||
}: {
|
||||
conversions: SettingsDownloadConversion[];
|
||||
updateSetting: (conversions: SettingsDownloadConversion[]) => Promise<void>;
|
||||
}) => {
|
||||
export const ImageProcessingSetting = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
useAppTitle(t('download.settings.conversion.title'));
|
||||
|
||||
const { data, loading, error, refetch } = requestManager.useGetServerSettings({
|
||||
notifyOnNetworkStatusChange: true,
|
||||
});
|
||||
const [mutateSettings] = requestManager.useUpdateServerSettings();
|
||||
|
||||
if (loading) {
|
||||
return <LoadingPlaceholder />;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<EmptyViewAbsoluteCentered
|
||||
message={t('global.error.label.failed_to_load_data')}
|
||||
messageExtra={getErrorMessage(error)}
|
||||
retry={() => refetch().catch(defaultPromiseErrorHandler('DownloadConversionSetting::refetch'))}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
assertIsDefined(data?.settings?.downloadConversions);
|
||||
|
||||
const conversions = data?.settings?.downloadConversions;
|
||||
|
||||
const [tmpConversions, setTmpConversions] = useState(
|
||||
normalizeConversions(maybeAddDefault(addStableIdToConversions(conversions))),
|
||||
);
|
||||
@@ -641,6 +669,13 @@ export const DownloadConversionSetting = ({
|
||||
tmpConversions,
|
||||
);
|
||||
|
||||
const updateSetting = (value: ServerSettings['downloadConversions']): Promise<any> => {
|
||||
const mutation = mutateSettings({ variables: { input: { settings: { downloadConversions: value } } } });
|
||||
mutation.catch((e) => makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e)));
|
||||
|
||||
return mutation;
|
||||
};
|
||||
|
||||
const onSubmit = async () => {
|
||||
try {
|
||||
await updateSetting(toValidServerConversions(tmpConversions));
|
||||
36
src/features/settings/screens/ImagesSettings.tsx
Normal file
36
src/features/settings/screens/ImagesSettings.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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 { useTranslation } from 'react-i18next';
|
||||
import List from '@mui/material/List';
|
||||
import ListItemText from '@mui/material/ListItemText';
|
||||
import ListSubheader from '@mui/material/ListSubheader';
|
||||
import { useAppTitle } from '@/features/navigation-bar/hooks/useAppTitle.ts';
|
||||
import { ListItemLink } from '@/base/components/lists/ListItemLink.tsx';
|
||||
import { AppRoutes } from '@/base/AppRoute.constants.ts';
|
||||
|
||||
export const ImagesSettings = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
useAppTitle(t('settings.images.title'));
|
||||
|
||||
return (
|
||||
<List
|
||||
sx={{ pt: 0 }}
|
||||
subheader={
|
||||
<ListSubheader component="div" id="image-processing-settings">
|
||||
{t('settings.images.processing.title')}
|
||||
</ListSubheader>
|
||||
}
|
||||
>
|
||||
<ListItemLink to={AppRoutes.settings.childRoutes.images.childRoutes.processingDownloads.path}>
|
||||
<ListItemText primary={t('download.settings.conversion.title')} />
|
||||
</ListItemLink>
|
||||
</List>
|
||||
);
|
||||
};
|
||||
@@ -23,6 +23,7 @@ import DevicesIcon from '@mui/icons-material/Devices';
|
||||
import SyncIcon from '@mui/icons-material/Sync';
|
||||
import PaletteIcon from '@mui/icons-material/Palette';
|
||||
import HistoryIcon from '@mui/icons-material/History';
|
||||
import ImageIcon from '@mui/icons-material/Image';
|
||||
import { ListItemLink } from '@/base/components/lists/ListItemLink.tsx';
|
||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||
import { makeToast } from '@/base/utils/Toast.ts';
|
||||
@@ -73,6 +74,12 @@ export function Settings() {
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={t('download.title.download')} />
|
||||
</ListItemLink>
|
||||
<ListItemLink to={AppRoutes.settings.childRoutes.images.path}>
|
||||
<ListItemIcon>
|
||||
<ImageIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={t('settings.images.title')} />
|
||||
</ListItemLink>
|
||||
<ListItemLink to={AppRoutes.settings.childRoutes.tracking.path}>
|
||||
<ListItemIcon>
|
||||
<SyncIcon />
|
||||
|
||||
Reference in New Issue
Block a user