diff --git a/src/App.tsx b/src/App.tsx
index 0c6cecd3..62b198d2 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -32,6 +32,7 @@ import { Updates } from '@/screens/Updates';
import '@/i18n';
import { LibrarySettings } from '@/screens/settings/LibrarySettings';
import { DefaultNavBar } from '@/components/navbar/DefaultNavBar';
+import { DownloadSettings } from '@/screens/settings/DownloadSettings.tsx';
if (__DEV__) {
// Adds messages only in a dev environment
@@ -63,6 +64,7 @@ export const App: React.FC = () => (
} />
} />
} />
+ } />
} />
diff --git a/src/components/settings/NumberSetting.tsx b/src/components/settings/NumberSetting.tsx
new file mode 100644
index 00000000..96248e30
--- /dev/null
+++ b/src/components/settings/NumberSetting.tsx
@@ -0,0 +1,162 @@
+/*
+ * 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 Dialog from '@mui/material/Dialog';
+import DialogContent from '@mui/material/DialogContent';
+import DialogTitle from '@mui/material/DialogTitle';
+import TextField from '@mui/material/TextField';
+import { InputAdornment, ListItemText } from '@mui/material';
+import DialogActions from '@mui/material/DialogActions';
+import Button from '@mui/material/Button';
+import { useCallback, useEffect, useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import ListItemButton from '@mui/material/ListItemButton';
+import * as React from 'react';
+import ListItemIcon from '@mui/material/ListItemIcon';
+import Slider from '@mui/material/Slider';
+
+type BaseProps = {
+ settingTitle: string;
+ settingValue: string;
+ settingIcon?: React.ReactNode;
+ value: number;
+ defaultValue?: number;
+ minValue?: number;
+ maxValue?: number;
+ stepSize?: number;
+ dialogTitle: string;
+ valueUnit: string;
+ handleUpdate: (value: number) => void;
+ showSlider?: never;
+};
+
+type PropsWithSlider = Omit &
+ Required> & { showSlider: true };
+
+type Props = BaseProps | PropsWithSlider;
+
+export const NumberSetting = ({
+ settingTitle,
+ settingValue,
+ settingIcon,
+ value,
+ defaultValue,
+ minValue,
+ maxValue,
+ stepSize,
+ dialogTitle,
+ valueUnit,
+ handleUpdate,
+ showSlider,
+}: Props) => {
+ const { t } = useTranslation();
+
+ const [isDialogOpen, setIsDialogOpen] = useState(false);
+ const [dialogValue, setDialogValue] = useState(value);
+
+ const closeDialog = useCallback(
+ (resetValue: boolean) => {
+ setIsDialogOpen(false);
+
+ if (resetValue) {
+ setDialogValue(value);
+ }
+ },
+ [value],
+ );
+
+ const closeDialogWithReset = useCallback(() => closeDialog(true), [closeDialog]);
+
+ const updateSetting = useCallback(
+ (newValue: number, shouldCloseDialog: boolean = true) => {
+ if (shouldCloseDialog) {
+ closeDialog(false);
+ }
+
+ const didValueChange = value !== newValue;
+ if (!didValueChange) {
+ return;
+ }
+
+ handleUpdate(newValue);
+ },
+ [value, handleUpdate, closeDialog],
+ );
+
+ useEffect(() => {
+ setDialogValue(value);
+ }, [value]);
+
+ return (
+ <>
+ setIsDialogOpen(true)}>
+ {settingIcon ? {settingIcon} : null}
+
+
+
+
+ >
+ );
+};
diff --git a/src/components/settings/ServerDirSetting.tsx b/src/components/settings/ServerDirSetting.tsx
new file mode 100644
index 00000000..00afa44b
--- /dev/null
+++ b/src/components/settings/ServerDirSetting.tsx
@@ -0,0 +1,87 @@
+/*
+ * 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 { Button, Dialog, DialogTitle, ListItemText } from '@mui/material';
+import DialogContent from '@mui/material/DialogContent';
+import TextField from '@mui/material/TextField';
+import DialogActions from '@mui/material/DialogActions';
+import ListItemButton from '@mui/material/ListItemButton';
+import { useEffect, useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import DialogContentText from '@mui/material/DialogContentText';
+
+export const ServerDirSetting = ({
+ settingName,
+ dialogDescription,
+ dirPath,
+ handlePathChange,
+}: {
+ settingName: string;
+ dialogDescription: string;
+ dirPath?: string;
+ handlePathChange: (path: string) => void;
+}) => {
+ const { t } = useTranslation();
+
+ const [isDialogOpen, setIsDialogOpen] = useState(false);
+ const [dialogDirPath, setDialogDirPath] = useState(dirPath ?? '');
+
+ useEffect(() => {
+ if (!dirPath) {
+ return;
+ }
+
+ setDialogDirPath(dirPath);
+ }, [dirPath]);
+
+ const closeDialog = () => {
+ setIsDialogOpen(false);
+ };
+
+ const updateSetting = () => {
+ closeDialog();
+ handlePathChange(dialogDirPath);
+ };
+
+ return (
+ <>
+ setIsDialogOpen(true)}>
+
+
+
+
+ >
+ );
+};
diff --git a/src/components/settings/downloads/DownloadAheadSetting.tsx b/src/components/settings/downloads/DownloadAheadSetting.tsx
new file mode 100644
index 00000000..0bb460aa
--- /dev/null
+++ b/src/components/settings/downloads/DownloadAheadSetting.tsx
@@ -0,0 +1,68 @@
+/*
+ * 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, ListItem, ListItemText, Switch } from '@mui/material';
+import ListItemSecondaryAction from '@mui/material/ListItemSecondaryAction';
+import { useCallback } from 'react';
+import { requestManager } from '@/lib/requests/RequestManager.ts';
+import { NumberSetting } from '@/components/settings/NumberSetting.tsx';
+
+const DEFAULT_LIMIT = 5;
+const MIN_LIMIT = 2;
+const MAX_LIMIT = 10;
+
+export const DownloadAheadSetting = () => {
+ const { t } = useTranslation();
+
+ const { data } = requestManager.useGetServerSettings();
+ const downloadAheadLimit = data?.settings.autoDownloadAheadLimit ?? 0;
+ const shouldDownloadAhead = !!downloadAheadLimit;
+ const [mutateSettings] = requestManager.useUpdateServerSettings();
+
+ const updateSetting = useCallback((autoDownloadAheadLimit: number) => {
+ mutateSettings({ variables: { input: { settings: { autoDownloadAheadLimit } } } });
+ }, []);
+
+ const setDoAutoUpdates = (enable: boolean) => {
+ const globalUpdateInterval = enable ? DEFAULT_LIMIT : 0;
+ updateSetting(globalUpdateInterval);
+ };
+
+ return (
+
+
+
+
+ setDoAutoUpdates(e.target.checked)}
+ />
+
+
+ {shouldDownloadAhead ? (
+
+ ) : null}
+
+ );
+};
diff --git a/src/components/settings/globalUpdate/GlobalUpdateSettingsInterval.tsx b/src/components/settings/globalUpdate/GlobalUpdateSettingsInterval.tsx
index f2f50ad4..2d3e3dbd 100644
--- a/src/components/settings/globalUpdate/GlobalUpdateSettingsInterval.tsx
+++ b/src/components/settings/globalUpdate/GlobalUpdateSettingsInterval.tsx
@@ -7,20 +7,15 @@
*/
import { useTranslation } from 'react-i18next';
-import { InputAdornment, List, ListItem, ListItemText, Switch } from '@mui/material';
+import { List, ListItem, ListItemText, Switch } from '@mui/material';
import ListItemSecondaryAction from '@mui/material/ListItemSecondaryAction';
-import ListItemButton from '@mui/material/ListItemButton';
-import { useEffect, useState } from 'react';
-import DialogContent from '@mui/material/DialogContent';
-import DialogTitle from '@mui/material/DialogTitle';
-import DialogActions from '@mui/material/DialogActions';
-import Button from '@mui/material/Button';
-import Dialog from '@mui/material/Dialog';
-import TextField from '@mui/material/TextField';
+import { useCallback } from 'react';
import { requestManager } from '@/lib/requests/RequestManager.ts';
+import { NumberSetting } from '@/components/settings/NumberSetting.tsx';
const DEFAULT_INTERVAL_HOURS = 12;
const MIN_INTERVAL_HOURS = 6;
+const MAX_INTERVAL_HOURS = 24 * 7 * 4; // 1 month
export const GlobalUpdateSettingsInterval = () => {
const { t } = useTranslation();
@@ -30,33 +25,15 @@ export const GlobalUpdateSettingsInterval = () => {
const doAutoUpdates = !!autoUpdateIntervalHours;
const [mutateSettings] = requestManager.useUpdateServerSettings();
- const [isDialogOpen, setIsDialogOpen] = useState(false);
- const [dialogUpdateIntervalHours, setDialogUpdateIntervalHours] = useState(autoUpdateIntervalHours);
-
- const closeDialog = () => {
- setIsDialogOpen(false);
- };
-
- const updateSetting = (globalUpdateInterval: number) => {
- closeDialog();
-
- const didIntervalChange = autoUpdateIntervalHours !== globalUpdateInterval;
- if (!didIntervalChange) {
- return;
- }
-
+ const updateSetting = useCallback((globalUpdateInterval: number) => {
mutateSettings({ variables: { input: { settings: { globalUpdateInterval } } } });
- };
+ }, []);
const setDoAutoUpdates = (enable: boolean) => {
const globalUpdateInterval = enable ? DEFAULT_INTERVAL_HOURS : 0;
updateSetting(globalUpdateInterval);
};
- useEffect(() => {
- setDialogUpdateIntervalHours(autoUpdateIntervalHours);
- }, [autoUpdateIntervalHours]);
-
return (
@@ -66,54 +43,20 @@ export const GlobalUpdateSettingsInterval = () => {
{doAutoUpdates ? (
- <>
- setIsDialogOpen(true)}>
-
-
-
-
- >
+
) : null}
);
diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json
index fc2cd10e..95c859a6 100644
--- a/src/i18n/locale/en.json
+++ b/src/i18n/locale/en.json
@@ -157,6 +157,35 @@
"queued": "Queued"
}
},
+ "settings": {
+ "file_type": {
+ "label": {
+ "cbz": "Save as CBZ archive"
+ }
+ },
+ "download_path": {
+ "label": {
+ "description": "The path to the directory on the server where downloaded files should get saved in",
+ "title": "Download location"
+ }
+ },
+ "auto_download": {
+ "label": {
+ "new_chapters": "Download new chapters",
+ "ignore_with_unread_chapters": "Ignore automatic chapter downloads for entries with unread chapters"
+ },
+ "title": "Auto-download"
+ },
+ "download_ahead": {
+ "label": {
+ "while_reading": "Auto download while reading",
+ "unread_chapters_to_download": "Number of unread chapters to download",
+ "value": "{{chapters}} $t(chapter.title)"
+ },
+ "title": "Download ahead"
+ },
+ "title": "Download settings"
+ },
"title": "Downloads"
},
"extension": {
diff --git a/src/lib/graphql/Fragments.ts b/src/lib/graphql/Fragments.ts
index e801d2da..03589f48 100644
--- a/src/lib/graphql/Fragments.ts
+++ b/src/lib/graphql/Fragments.ts
@@ -406,34 +406,54 @@ export const WEBUI_UPDATE_STATUS = gql`
export const SERVER_SETTINGS = gql`
fragment SERVER_SETTINGS on SettingsType {
- autoDownloadNewChapters
- backupInterval
- backupPath
- backupTTL
- backupTime
- basicAuthEnabled
- basicAuthPassword
- basicAuthUsername
- debugLogsEnabled
- downloadAsCbz
- downloadsPath
- electronPath
- excludeCompleted
- excludeNotStarted
- excludeUnreadChapters
- globalUpdateInterval
- initialOpenInBrowserEnabled
+ # Server ip and port bindings
ip
- localSourcePath
- maxSourcesInParallel
port
+
+ # Socks proxy
socksProxyEnabled
socksProxyHost
socksProxyPort
- systemTrayEnabled
- webUIChannel
+
+ # webUI
webUIFlavor
+ initialOpenInBrowserEnabled
webUIInterface
+ electronPath
+ webUIChannel
webUIUpdateCheckInterval
+
+ # downloader
+ downloadAsCbz
+ downloadsPath
+ autoDownloadNewChapters
+ excludeEntryWithUnreadChapters
+ autoDownloadAheadLimit
+
+ # requests
+ maxSourcesInParallel
+
+ # updater
+ excludeUnreadChapters
+ excludeNotStarted
+ excludeCompleted
+ globalUpdateInterval
+ updateMangas
+
+ # Authentication
+ basicAuthEnabled
+ basicAuthUsername
+ basicAuthPassword
+
+ # misc
+ debugLogsEnabled
+ gqlDebugLogsEnabled
+ systemTrayEnabled
+
+ # backup
+ backupPath
+ backupTime
+ backupInterval
+ backupTTL
}
`;
diff --git a/src/lib/graphql/generated/graphql.ts b/src/lib/graphql/generated/graphql.ts
index 5f843c95..e1ac25dc 100644
--- a/src/lib/graphql/generated/graphql.ts
+++ b/src/lib/graphql/generated/graphql.ts
@@ -2108,7 +2108,7 @@ export type WebuiUpdateInfoFragment = { __typename?: 'WebUIUpdateInfo', channel:
export type WebuiUpdateStatusFragment = { __typename?: 'WebUIUpdateStatus', progress: number, state: UpdateState, info: { __typename?: 'WebUIUpdateInfo', channel: string, tag: string, updateAvailable: boolean } };
-export type ServerSettingsFragment = { __typename?: 'SettingsType', autoDownloadNewChapters: boolean, backupInterval: number, backupPath: string, backupTTL: number, backupTime: string, basicAuthEnabled: boolean, basicAuthPassword: string, basicAuthUsername: string, debugLogsEnabled: boolean, downloadAsCbz: boolean, downloadsPath: string, electronPath: string, excludeCompleted: boolean, excludeNotStarted: boolean, excludeUnreadChapters: boolean, globalUpdateInterval: number, initialOpenInBrowserEnabled: boolean, ip: string, localSourcePath: string, maxSourcesInParallel: number, port: number, socksProxyEnabled: boolean, socksProxyHost: string, socksProxyPort: string, systemTrayEnabled: boolean, webUIChannel: WebUiChannel, webUIFlavor: WebUiFlavor, webUIInterface: WebUiInterface, webUIUpdateCheckInterval: number };
+export type ServerSettingsFragment = { __typename?: 'SettingsType', ip: string, port: number, socksProxyEnabled: boolean, socksProxyHost: string, socksProxyPort: string, webUIFlavor: WebUiFlavor, initialOpenInBrowserEnabled: boolean, webUIInterface: WebUiInterface, electronPath: string, webUIChannel: WebUiChannel, webUIUpdateCheckInterval: number, downloadAsCbz: boolean, downloadsPath: string, autoDownloadNewChapters: boolean, excludeEntryWithUnreadChapters: boolean, autoDownloadAheadLimit: number, maxSourcesInParallel: number, excludeUnreadChapters: boolean, excludeNotStarted: boolean, excludeCompleted: boolean, globalUpdateInterval: number, updateMangas: boolean, basicAuthEnabled: boolean, basicAuthUsername: string, basicAuthPassword: string, debugLogsEnabled: boolean, gqlDebugLogsEnabled: boolean, systemTrayEnabled: boolean, backupPath: string, backupTime: string, backupInterval: number, backupTTL: number };
export type CreateBackupMutationVariables = Exact<{
input: CreateBackupInput;
@@ -2388,14 +2388,14 @@ export type ResetServerSettingsMutationVariables = Exact<{
}>;
-export type ResetServerSettingsMutation = { __typename?: 'Mutation', resetSettings: { __typename?: 'ResetSettingsPayload', clientMutationId?: string | null, settings: { __typename?: 'SettingsType', autoDownloadNewChapters: boolean, backupInterval: number, backupPath: string, backupTTL: number, backupTime: string, basicAuthEnabled: boolean, basicAuthPassword: string, basicAuthUsername: string, debugLogsEnabled: boolean, downloadAsCbz: boolean, downloadsPath: string, electronPath: string, excludeCompleted: boolean, excludeNotStarted: boolean, excludeUnreadChapters: boolean, globalUpdateInterval: number, initialOpenInBrowserEnabled: boolean, ip: string, localSourcePath: string, maxSourcesInParallel: number, port: number, socksProxyEnabled: boolean, socksProxyHost: string, socksProxyPort: string, systemTrayEnabled: boolean, webUIChannel: WebUiChannel, webUIFlavor: WebUiFlavor, webUIInterface: WebUiInterface, webUIUpdateCheckInterval: number } } };
+export type ResetServerSettingsMutation = { __typename?: 'Mutation', resetSettings: { __typename?: 'ResetSettingsPayload', clientMutationId?: string | null, settings: { __typename?: 'SettingsType', ip: string, port: number, socksProxyEnabled: boolean, socksProxyHost: string, socksProxyPort: string, webUIFlavor: WebUiFlavor, initialOpenInBrowserEnabled: boolean, webUIInterface: WebUiInterface, electronPath: string, webUIChannel: WebUiChannel, webUIUpdateCheckInterval: number, downloadAsCbz: boolean, downloadsPath: string, autoDownloadNewChapters: boolean, excludeEntryWithUnreadChapters: boolean, autoDownloadAheadLimit: number, maxSourcesInParallel: number, excludeUnreadChapters: boolean, excludeNotStarted: boolean, excludeCompleted: boolean, globalUpdateInterval: number, updateMangas: boolean, basicAuthEnabled: boolean, basicAuthUsername: string, basicAuthPassword: string, debugLogsEnabled: boolean, gqlDebugLogsEnabled: boolean, systemTrayEnabled: boolean, backupPath: string, backupTime: string, backupInterval: number, backupTTL: number } } };
export type UpdateServerSettingsMutationVariables = Exact<{
input: SetSettingsInput;
}>;
-export type UpdateServerSettingsMutation = { __typename?: 'Mutation', setSettings: { __typename?: 'SetSettingsPayload', clientMutationId?: string | null, settings: { __typename?: 'SettingsType', autoDownloadNewChapters: boolean, backupInterval: number, backupPath: string, backupTTL: number, backupTime: string, basicAuthEnabled: boolean, basicAuthPassword: string, basicAuthUsername: string, debugLogsEnabled: boolean, downloadAsCbz: boolean, downloadsPath: string, electronPath: string, excludeCompleted: boolean, excludeNotStarted: boolean, excludeUnreadChapters: boolean, globalUpdateInterval: number, initialOpenInBrowserEnabled: boolean, ip: string, localSourcePath: string, maxSourcesInParallel: number, port: number, socksProxyEnabled: boolean, socksProxyHost: string, socksProxyPort: string, systemTrayEnabled: boolean, webUIChannel: WebUiChannel, webUIFlavor: WebUiFlavor, webUIInterface: WebUiInterface, webUIUpdateCheckInterval: number } } };
+export type UpdateServerSettingsMutation = { __typename?: 'Mutation', setSettings: { __typename?: 'SetSettingsPayload', clientMutationId?: string | null, settings: { __typename?: 'SettingsType', ip: string, port: number, socksProxyEnabled: boolean, socksProxyHost: string, socksProxyPort: string, webUIFlavor: WebUiFlavor, initialOpenInBrowserEnabled: boolean, webUIInterface: WebUiInterface, electronPath: string, webUIChannel: WebUiChannel, webUIUpdateCheckInterval: number, downloadAsCbz: boolean, downloadsPath: string, autoDownloadNewChapters: boolean, excludeEntryWithUnreadChapters: boolean, autoDownloadAheadLimit: number, maxSourcesInParallel: number, excludeUnreadChapters: boolean, excludeNotStarted: boolean, excludeCompleted: boolean, globalUpdateInterval: number, updateMangas: boolean, basicAuthEnabled: boolean, basicAuthUsername: string, basicAuthPassword: string, debugLogsEnabled: boolean, gqlDebugLogsEnabled: boolean, systemTrayEnabled: boolean, backupPath: string, backupTime: string, backupInterval: number, backupTTL: number } } };
export type GetSourceMangasFetchMutationVariables = Exact<{
input: FetchSourceMangaInput;
@@ -2591,7 +2591,7 @@ export type GetWebuiUpdateStatusQuery = { __typename?: 'Query', getWebUIUpdateSt
export type GetServerSettingsQueryVariables = Exact<{ [key: string]: never; }>;
-export type GetServerSettingsQuery = { __typename?: 'Query', settings: { __typename?: 'SettingsType', autoDownloadNewChapters: boolean, backupInterval: number, backupPath: string, backupTTL: number, backupTime: string, basicAuthEnabled: boolean, basicAuthPassword: string, basicAuthUsername: string, debugLogsEnabled: boolean, downloadAsCbz: boolean, downloadsPath: string, electronPath: string, excludeCompleted: boolean, excludeNotStarted: boolean, excludeUnreadChapters: boolean, globalUpdateInterval: number, initialOpenInBrowserEnabled: boolean, ip: string, localSourcePath: string, maxSourcesInParallel: number, port: number, socksProxyEnabled: boolean, socksProxyHost: string, socksProxyPort: string, systemTrayEnabled: boolean, webUIChannel: WebUiChannel, webUIFlavor: WebUiFlavor, webUIInterface: WebUiInterface, webUIUpdateCheckInterval: number } };
+export type GetServerSettingsQuery = { __typename?: 'Query', settings: { __typename?: 'SettingsType', ip: string, port: number, socksProxyEnabled: boolean, socksProxyHost: string, socksProxyPort: string, webUIFlavor: WebUiFlavor, initialOpenInBrowserEnabled: boolean, webUIInterface: WebUiInterface, electronPath: string, webUIChannel: WebUiChannel, webUIUpdateCheckInterval: number, downloadAsCbz: boolean, downloadsPath: string, autoDownloadNewChapters: boolean, excludeEntryWithUnreadChapters: boolean, autoDownloadAheadLimit: number, maxSourcesInParallel: number, excludeUnreadChapters: boolean, excludeNotStarted: boolean, excludeCompleted: boolean, globalUpdateInterval: number, updateMangas: boolean, basicAuthEnabled: boolean, basicAuthUsername: string, basicAuthPassword: string, debugLogsEnabled: boolean, gqlDebugLogsEnabled: boolean, systemTrayEnabled: boolean, backupPath: string, backupTime: string, backupInterval: number, backupTTL: number } };
export type GetSourceQueryVariables = Exact<{
id: Scalars['LongString']['input'];
diff --git a/src/screens/Settings.tsx b/src/screens/Settings.tsx
index 256d74e8..aca083fb 100644
--- a/src/screens/Settings.tsx
+++ b/src/screens/Settings.tsx
@@ -6,7 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
-import { useContext, useEffect, useState } from 'react';
+import { useContext, useEffect, useMemo, useState } from 'react';
import AutoStoriesIcon from '@mui/icons-material/AutoStories';
import List from '@mui/material/List';
import ListAltIcon from '@mui/icons-material/ListAlt';
@@ -29,18 +29,19 @@ import DialogContent from '@mui/material/DialogContent';
import DialogContentText from '@mui/material/DialogContentText';
import TextField from '@mui/material/TextField';
import FavoriteIcon from '@mui/icons-material/Favorite';
-import Slider from '@mui/material/Slider';
-import { DialogTitle, Link, ListItemButton, MenuItem, Select } from '@mui/material';
-import ViewModuleIcon from '@mui/icons-material/ViewModule';
+import { Link, MenuItem, Select } from '@mui/material';
import { useTranslation } from 'react-i18next';
import LanguageIcon from '@mui/icons-material/Language';
import CollectionsOutlinedBookmarkIcon from '@mui/icons-material/CollectionsBookmarkOutlined';
+import GetAppOutlinedIcon from '@mui/icons-material/GetAppOutlined';
+import ViewModuleIcon from '@mui/icons-material/ViewModule';
import { requestManager } from '@/lib/requests/RequestManager.ts';
import { langCodeToName } from '@/util/language';
import { useLocalStorage } from '@/util/useLocalStorage';
import { ListItemLink } from '@/components/util/ListItemLink';
import { DarkTheme } from '@/components/context/DarkTheme';
import { NavBarContext } from '@/components/context/NavbarContext.tsx';
+import { NumberSetting } from '@/components/settings/NumberSetting.tsx';
export function Settings() {
const { t, i18n } = useTranslation();
@@ -58,9 +59,9 @@ export function Settings() {
const [dialogOpen, setDialogOpen] = useState(false);
const [dialogValue, setDialogValue] = useState(serverAddress);
- const [dialogOpenItemWidth, setDialogOpenItemWidth] = useState(false);
- const [ItemWidth, setItemWidth] = useLocalStorage('ItemWidth', 300);
- const [DialogItemWidth, setDialogItemWidth] = useState(ItemWidth);
+ const DEFAULT_ITEM_WIDTH = 300;
+ const itemWidthIcon = useMemo(() => , []);
+ const [itemWidth, setItemWidth] = useLocalStorage('ItemWidth', DEFAULT_ITEM_WIDTH);
const handleDialogOpen = () => {
setDialogValue(serverAddress);
@@ -78,29 +79,6 @@ export function Settings() {
requestManager.updateClient({ baseURL: serverBaseUrl });
};
- const handleDialogOpenItemWidth = () => {
- setDialogItemWidth(ItemWidth);
- setDialogOpenItemWidth(true);
- };
-
- const handleDialogCancelItemWidth = () => {
- setDialogOpenItemWidth(false);
- };
-
- const handleDialogSubmitItemWidth = () => {
- setDialogOpenItemWidth(false);
- setItemWidth(DialogItemWidth);
- };
-
- const handleDialogResetItemWidth = () => {
- setDialogOpenItemWidth(false);
- setItemWidth(300);
- };
-
- const handleChange = (event: Event, newValue: number | number[]) => {
- setDialogItemWidth(newValue as number);
- };
-
return (
<>
@@ -122,6 +100,12 @@ export function Settings() {
+
+
+
+
+
+
@@ -137,16 +121,20 @@ export function Settings() {
setDarkTheme(!darkTheme)} />
- {
- handleDialogOpenItemWidth();
- }}
- >
-
-
-
-
-
+
@@ -248,47 +236,6 @@ export function Settings() {
-
-
>
);
}
diff --git a/src/screens/settings/DownloadSettings.tsx b/src/screens/settings/DownloadSettings.tsx
new file mode 100644
index 00000000..f9857768
--- /dev/null
+++ b/src/screens/settings/DownloadSettings.tsx
@@ -0,0 +1,121 @@
+/*
+ * 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 { useContext, useEffect } from 'react';
+import List from '@mui/material/List';
+import { ListItem, ListItemText, Switch } from '@mui/material';
+import ListItemSecondaryAction from '@mui/material/ListItemSecondaryAction';
+import ListSubheader from '@mui/material/ListSubheader';
+import { ServerDirSetting } from '@/components/settings/ServerDirSetting';
+import { NavBarContext, useSetDefaultBackTo } from '@/components/context/NavbarContext.tsx';
+import { ServerSettings } from '@/typings.ts';
+import { requestManager } from '@/lib/requests/RequestManager.ts';
+import { DownloadAheadSetting } from '@/components/settings/downloads/DownloadAheadSetting.tsx';
+
+type DownloadSettingsType = Pick<
+ ServerSettings,
+ | 'downloadAsCbz'
+ | 'downloadsPath'
+ | 'autoDownloadNewChapters'
+ | 'autoDownloadAheadLimit'
+ | 'excludeEntryWithUnreadChapters'
+>;
+
+const extractDownloadSettings = (settings: ServerSettings): DownloadSettingsType => ({
+ downloadAsCbz: settings.downloadAsCbz,
+ downloadsPath: settings.downloadsPath,
+ autoDownloadNewChapters: settings.autoDownloadNewChapters,
+ autoDownloadAheadLimit: settings.autoDownloadAheadLimit,
+ excludeEntryWithUnreadChapters: settings.excludeEntryWithUnreadChapters,
+});
+
+export const DownloadSettings = () => {
+ const { t } = useTranslation();
+ const { setTitle, setAction } = useContext(NavBarContext);
+
+ useSetDefaultBackTo('settings');
+
+ useEffect(() => {
+ setTitle(t('download.settings.title'));
+ setAction(null);
+ }, [t]);
+
+ const { data } = requestManager.useGetServerSettings();
+ const downloadSettings = data ? extractDownloadSettings(data.settings) : undefined;
+ const [mutateSettings] = requestManager.useUpdateServerSettings();
+
+ const updateSetting = (
+ setting: Setting,
+ value: DownloadSettingsType[Setting],
+ ) => {
+ mutateSettings({ variables: { input: { settings: { [setting]: value } } } });
+ };
+
+ return (
+
+ updateSetting('downloadsPath', path)}
+ />
+
+
+
+ updateSetting('downloadAsCbz', e.target.checked)}
+ />
+
+
+
+ {t('download.settings.auto_download.title')}
+
+ }
+ >
+
+
+
+ updateSetting('autoDownloadNewChapters', e.target.checked)}
+ />
+
+
+ {downloadSettings?.autoDownloadNewChapters ? (
+
+
+
+ updateSetting('excludeEntryWithUnreadChapters', e.target.checked)}
+ />
+
+
+ ) : null}
+
+
+ {t('download.settings.download_ahead.title')}
+
+ }
+ >
+
+
+
+ );
+};