Files
suwayomi-material-you-webui/src/features/downloads/screens/DownloadSettings.tsx

248 lines
12 KiB
TypeScript
Raw Normal View History

/*
* 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 ListItem from '@mui/material/ListItem';
import ListItemText from '@mui/material/ListItemText';
import Switch from '@mui/material/Switch';
import ListSubheader from '@mui/material/ListSubheader';
2025-08-15 22:02:58 +02:00
import { TextSetting } from '@/features/core/components/settings/text/TextSetting.tsx';
import { requestManager } from '@/lib/requests/RequestManager.ts';
2025-08-15 22:02:58 +02:00
import { DownloadAheadSetting } from '@/features/downloads/components/DownloadAheadSetting.tsx';
import {
createUpdateMetadataServerSettings,
useMetadataServerSettings,
2025-08-15 22:02:58 +02:00
} from '@/features/settings/services/ServerSettingsMetadata.ts';
import { makeToast } from '@/features/core/utils/Toast.ts';
import { DeleteChaptersWhileReadingSetting } from '@/features/downloads/components/DeleteChaptersWhileReadingSetting.tsx';
import { CategoriesInclusionSetting } from '@/features/category/components/CategoriesInclusionSetting.tsx';
import { NumberSetting } from '@/features/core/components/settings/NumberSetting.tsx';
import { LoadingPlaceholder } from '@/features/core/components/feedback/LoadingPlaceholder.tsx';
import { EmptyViewAbsoluteCentered } from '@/features/core/components/feedback/EmptyViewAbsoluteCentered.tsx';
2024-10-05 16:09:34 +02:00
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
import { GetCategoriesSettingsQuery, GetCategoriesSettingsQueryVariables } from '@/lib/graphql/generated/graphql.ts';
import { GET_CATEGORIES_SETTINGS } from '@/lib/graphql/queries/CategoryQuery.ts';
2025-08-15 22:02:58 +02:00
import { MetadataDownloadSettings } from '@/features/downloads/Downloads.types.ts';
import { ServerSettings } from '@/features/settings/Settings.types.ts';
2024-12-20 17:24:40 +01:00
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
2025-08-15 22:02:58 +02:00
import { useAppTitle } from '@/features/navigation-bar/hooks/useAppTitle.ts';
import { DownloadConversionSetting } from '@/features/downloads/components/DownloadConversionSetting.tsx';
type DownloadSettingsType = Pick<
ServerSettings,
| 'downloadAsCbz'
| 'downloadsPath'
| 'autoDownloadNewChapters'
| 'autoDownloadNewChaptersLimit'
| 'excludeEntryWithUnreadChapters'
| 'autoDownloadIgnoreReUploads'
| 'downloadConversions'
>;
const extractDownloadSettings = (settings: ServerSettings): DownloadSettingsType => ({
downloadAsCbz: settings.downloadAsCbz,
downloadsPath: settings.downloadsPath,
autoDownloadNewChapters: settings.autoDownloadNewChapters,
autoDownloadNewChaptersLimit: settings.autoDownloadNewChaptersLimit,
excludeEntryWithUnreadChapters: settings.excludeEntryWithUnreadChapters,
autoDownloadIgnoreReUploads: settings.autoDownloadIgnoreReUploads,
downloadConversions: settings.downloadConversions,
});
export const DownloadSettings = () => {
const { t } = useTranslation();
2025-05-03 13:00:03 +02:00
useAppTitle(t('download.title.download'));
const categories = requestManager.useGetCategories<GetCategoriesSettingsQuery, GetCategoriesSettingsQueryVariables>(
GET_CATEGORIES_SETTINGS,
);
const serverSettings = requestManager.useGetServerSettings({ notifyOnNetworkStatusChange: true });
const [mutateSettings] = requestManager.useUpdateServerSettings();
const {
settings: metadataSettings,
loading: areMetadataServerSettingsLoading,
request: { error: metadataServerSettingsError, refetch: refetchMetadataServerSettings },
} = useMetadataServerSettings();
const loading = serverSettings.loading || areMetadataServerSettingsLoading || categories.loading;
2024-04-27 16:24:55 +02:00
if (loading) {
return <LoadingPlaceholder />;
}
const error = serverSettings.error ?? metadataServerSettingsError ?? categories.error;
2024-04-27 22:28:55 +02:00
if (error) {
return (
2024-04-29 15:29:33 +02:00
<EmptyViewAbsoluteCentered
2024-04-27 22:28:55 +02:00
message={t('global.error.label.failed_to_load_data')}
2024-12-21 23:27:03 +01:00
messageExtra={getErrorMessage(error)}
retry={() => {
if (serverSettings.error) {
serverSettings
.refetch()
.catch(defaultPromiseErrorHandler('DownloadSettings::refetchServerSettings'));
}
if (metadataServerSettingsError) {
refetchMetadataServerSettings().catch(
defaultPromiseErrorHandler('refetchMetadataServerSettings::'),
);
}
if (categories.error) {
categories.refetch().catch(defaultPromiseErrorHandler('LibrarySettings::refetchCategories'));
}
}}
2024-04-27 22:28:55 +02:00
/>
);
}
const downloadSettings = extractDownloadSettings(serverSettings.data!.settings);
const updateSetting = <Setting extends keyof DownloadSettingsType>(
setting: Setting,
value: DownloadSettingsType[Setting],
): Promise<any> => {
const mutation = mutateSettings({ variables: { input: { settings: { [setting]: value } } } });
mutation.catch((e) => makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e)));
return mutation;
};
2024-12-20 17:24:40 +01:00
const updateMetadataSetting = createUpdateMetadataServerSettings<keyof MetadataDownloadSettings>((e) =>
makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e)),
);
return (
2024-09-05 00:34:13 +02:00
<List sx={{ pt: 0 }}>
<TextSetting
settingName={t('download.settings.download_path.label.title')}
dialogDescription={t('download.settings.download_path.label.description')}
value={downloadSettings?.downloadsPath}
settingDescription={
downloadSettings?.downloadsPath.length ? downloadSettings.downloadsPath : t('global.label.default')
}
handleChange={(path) => updateSetting('downloadsPath', path)}
/>
<ListItem>
<ListItemText primary={t('download.settings.file_type.label.cbz')} />
<Switch
edge="end"
checked={!!downloadSettings?.downloadAsCbz}
onChange={(e) => updateSetting('downloadAsCbz', e.target.checked)}
/>
</ListItem>
<DownloadConversionSetting
conversions={downloadSettings?.downloadConversions}
updateSetting={(conversions) => updateSetting('downloadConversions', conversions)}
/>
<List
subheader={
<ListSubheader component="div" id="download-settings-auto-delete-downloads">
{t('download.settings.delete_chapters.title')}
</ListSubheader>
}
>
<ListItem>
<ListItemText primary={t('download.settings.delete_chapters.label.manually_marked_as_read')} />
<Switch
edge="end"
checked={metadataSettings.deleteChaptersManuallyMarkedRead}
onChange={(e) => updateMetadataSetting('deleteChaptersManuallyMarkedRead', e.target.checked)}
/>
</ListItem>
<DeleteChaptersWhileReadingSetting
chapterToDelete={metadataSettings.deleteChaptersWhileReading}
handleChange={(chapterToDelete) =>
updateMetadataSetting('deleteChaptersWhileReading', chapterToDelete)
}
/>
<ListItem>
<ListItemText primary={t('download.settings.delete_chapters.label.allow_deletion_of_bookmarked')} />
<Switch
edge="end"
checked={metadataSettings.deleteChaptersWithBookmark}
onChange={(e) => updateMetadataSetting('deleteChaptersWithBookmark', e.target.checked)}
/>
</ListItem>
</List>
<List
subheader={
<ListSubheader component="div" id="download-settings-auto-download">
{t('download.settings.auto_download.title')}
</ListSubheader>
}
>
<ListItem>
<ListItemText primary={t('download.settings.auto_download.label.new_chapters')} />
<Switch
edge="end"
checked={!!downloadSettings?.autoDownloadNewChapters}
onChange={(e) => updateSetting('autoDownloadNewChapters', e.target.checked)}
/>
</ListItem>
<NumberSetting
disabled={!downloadSettings?.autoDownloadNewChapters}
settingTitle={t('download.settings.auto_download.download_limit.label.title')}
dialogDescription={t('download.settings.auto_download.download_limit.label.description')}
value={downloadSettings?.autoDownloadNewChaptersLimit ?? 0}
settingValue={
!downloadSettings.autoDownloadNewChaptersLimit
? t('global.label.none')
: t('download.settings.download_ahead.label.value', {
chapters: downloadSettings.autoDownloadNewChaptersLimit,
count: downloadSettings.autoDownloadNewChaptersLimit,
})
}
defaultValue={0}
minValue={0}
maxValue={20}
showSlider
2024-07-01 19:51:27 +02:00
valueUnit={t('chapter.title_one')}
handleUpdate={(autoDownloadNewChaptersLimit) =>
updateSetting('autoDownloadNewChaptersLimit', autoDownloadNewChaptersLimit)
}
/>
2024-09-03 17:15:47 +02:00
<ListItem>
2023-11-19 00:10:30 +01:00
<ListItemText primary={t('download.settings.auto_download.label.ignore_with_unread_chapters')} />
<Switch
edge="end"
checked={!!downloadSettings?.excludeEntryWithUnreadChapters}
onChange={(e) => updateSetting('excludeEntryWithUnreadChapters', e.target.checked)}
disabled={!downloadSettings?.autoDownloadNewChapters}
/>
2023-11-19 00:10:30 +01:00
</ListItem>
2024-09-03 17:15:47 +02:00
<ListItem>
<ListItemText primary={t('download.settings.auto_download.label.ignore_re_uploads')} />
<Switch
edge="end"
checked={!!downloadSettings?.autoDownloadIgnoreReUploads}
onChange={(e) => updateSetting('autoDownloadIgnoreReUploads', e.target.checked)}
disabled={!downloadSettings?.autoDownloadNewChapters}
/>
</ListItem>
<CategoriesInclusionSetting
categories={categories.data!.categories.nodes}
includeField="includeInDownload"
dialogText={t('download.settings.auto_download.categories.label.include_in_download')}
/>
</List>
<List
subheader={
<ListSubheader component="div" id="download-settings-download-ahead">
{t('download.settings.download_ahead.title')}
</ListSubheader>
}
>
<DownloadAheadSetting downloadAheadLimit={metadataSettings.downloadAheadLimit} />
</List>
</List>
);
};