diff --git a/src/components/globalUpdate/CheckboxContainer.ts b/src/components/settings/globalUpdate/CheckboxContainer.ts similarity index 100% rename from src/components/globalUpdate/CheckboxContainer.ts rename to src/components/settings/globalUpdate/CheckboxContainer.ts diff --git a/src/components/globalUpdate/GlobalUpdateSettings.tsx b/src/components/settings/globalUpdate/GlobalUpdateSettings.tsx similarity index 68% rename from src/components/globalUpdate/GlobalUpdateSettings.tsx rename to src/components/settings/globalUpdate/GlobalUpdateSettings.tsx index e31ea581..aeade011 100644 --- a/src/components/globalUpdate/GlobalUpdateSettings.tsx +++ b/src/components/settings/globalUpdate/GlobalUpdateSettings.tsx @@ -9,8 +9,9 @@ import List from '@mui/material/List'; import ListSubheader from '@mui/material/ListSubheader'; import { useTranslation } from 'react-i18next'; -import { GlobalUpdateSettingsCategories } from '@/components/globalUpdate/GlobalUpdateSettingsCategories.tsx'; -import { GlobalUpdateSettingsEntries } from '@/components/globalUpdate/GlobalUpdateSettingsEntries.tsx'; +import { GlobalUpdateSettingsCategories } from '@/components/settings/globalUpdate/GlobalUpdateSettingsCategories.tsx'; +import { GlobalUpdateSettingsEntries } from '@/components/settings/globalUpdate/GlobalUpdateSettingsEntries.tsx'; +import { GlobalUpdateSettingsInterval } from '@/components/settings/globalUpdate/GlobalUpdateSettingsInterval.tsx'; export const GlobalUpdateSettings = () => { const { t } = useTranslation(); @@ -23,6 +24,7 @@ export const GlobalUpdateSettings = () => { } > + diff --git a/src/components/globalUpdate/GlobalUpdateSettingsCategories.tsx b/src/components/settings/globalUpdate/GlobalUpdateSettingsCategories.tsx similarity index 98% rename from src/components/globalUpdate/GlobalUpdateSettingsCategories.tsx rename to src/components/settings/globalUpdate/GlobalUpdateSettingsCategories.tsx index b66ba5c3..c10af04c 100644 --- a/src/components/globalUpdate/GlobalUpdateSettingsCategories.tsx +++ b/src/components/settings/globalUpdate/GlobalUpdateSettingsCategories.tsx @@ -22,7 +22,7 @@ import { makeToast } from '@/components/util/Toast.tsx'; import { IncludeInUpdate } from '@/lib/graphql/generated/graphql.ts'; import { TCategory } from '@/typings.ts'; import { requestManager } from '@/lib/requests/RequestManager.ts'; -import { CheckboxContainer } from '@/components/globalUpdate/CheckboxContainer.ts'; +import { CheckboxContainer } from '@/components/settings/globalUpdate/CheckboxContainer.ts'; const booleanToIncludeInStatus = (status: boolean | null | undefined): IncludeInUpdate => { switch (status) { diff --git a/src/components/globalUpdate/GlobalUpdateSettingsEntries.tsx b/src/components/settings/globalUpdate/GlobalUpdateSettingsEntries.tsx similarity index 93% rename from src/components/globalUpdate/GlobalUpdateSettingsEntries.tsx rename to src/components/settings/globalUpdate/GlobalUpdateSettingsEntries.tsx index 968f9ab1..93e8e6c1 100644 --- a/src/components/globalUpdate/GlobalUpdateSettingsEntries.tsx +++ b/src/components/settings/globalUpdate/GlobalUpdateSettingsEntries.tsx @@ -12,15 +12,14 @@ import { useEffect, useState } from 'react'; import ListItemButton from '@mui/material/ListItemButton'; import ListItemText from '@mui/material/ListItemText'; import { Button, Dialog, DialogActions, DialogContent, DialogTitle } from '@mui/material'; -import { GetServerSettingsQuery } from '@/lib/graphql/generated/graphql.ts'; -import { TranslationKey } from '@/typings.ts'; +import { ServerSettings, TranslationKey } from '@/typings.ts'; import { requestManager } from '@/lib/requests/RequestManager.ts'; import { makeToast } from '@/components/util/Toast.tsx'; -import { CheckboxContainer } from '@/components/globalUpdate/CheckboxContainer'; -import { CheckboxInput } from '@/components/atoms/CheckboxInput'; +import { CheckboxContainer } from '@/components/settings/globalUpdate/CheckboxContainer.ts'; +import { CheckboxInput } from '@/components/atoms/CheckboxInput.tsx'; type GlobalUpdateSkipEntriesSettings = Pick< - GetServerSettingsQuery['settings'], + ServerSettings, 'excludeUnreadChapters' | 'excludeNotStarted' | 'excludeCompleted' >; @@ -61,9 +60,7 @@ const getSkipMangasText = (settings: GlobalUpdateSkipEntriesSettings | undefined return skipSettings.join(', '); }; -const extractSkipEntriesSettings = ( - serverSettings: GetServerSettingsQuery['settings'], -): GlobalUpdateSkipEntriesSettings => ({ +const extractSkipEntriesSettings = (serverSettings: ServerSettings): GlobalUpdateSkipEntriesSettings => ({ excludeCompleted: serverSettings.excludeCompleted, excludeNotStarted: serverSettings.excludeNotStarted, excludeUnreadChapters: serverSettings.excludeUnreadChapters, diff --git a/src/components/settings/globalUpdate/GlobalUpdateSettingsInterval.tsx b/src/components/settings/globalUpdate/GlobalUpdateSettingsInterval.tsx new file mode 100644 index 00000000..f2f50ad4 --- /dev/null +++ b/src/components/settings/globalUpdate/GlobalUpdateSettingsInterval.tsx @@ -0,0 +1,120 @@ +/* + * 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 { InputAdornment, 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 { requestManager } from '@/lib/requests/RequestManager.ts'; + +const DEFAULT_INTERVAL_HOURS = 12; +const MIN_INTERVAL_HOURS = 6; + +export const GlobalUpdateSettingsInterval = () => { + const { t } = useTranslation(); + + const { data } = requestManager.useGetServerSettings(); + const autoUpdateIntervalHours = data?.settings.globalUpdateInterval ?? 0; + 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; + } + + mutateSettings({ variables: { input: { settings: { globalUpdateInterval } } } }); + }; + + const setDoAutoUpdates = (enable: boolean) => { + const globalUpdateInterval = enable ? DEFAULT_INTERVAL_HOURS : 0; + updateSetting(globalUpdateInterval); + }; + + useEffect(() => { + setDialogUpdateIntervalHours(autoUpdateIntervalHours); + }, [autoUpdateIntervalHours]); + + return ( + + + + + setDoAutoUpdates(e.target.checked)} /> + + + {doAutoUpdates ? ( + <> + setIsDialogOpen(true)}> + + + + + + + {t('library.settings.global_update.auto_update.interval.label.title')} + + {t('global.time.hour_short')} + ), + }} + autoFocus + value={dialogUpdateIntervalHours} + type="number" + onChange={(e) => setDialogUpdateIntervalHours(Number(e.target.value))} + /> + + + + + + + + ) : null} + + ); +}; diff --git a/src/i18n/i18next.d.ts b/src/i18n/i18next.d.ts index 157d37be..14933e37 100644 --- a/src/i18n/i18next.d.ts +++ b/src/i18n/i18next.d.ts @@ -7,7 +7,7 @@ */ import 'i18next'; -import resources from '@/i18n/translations'; +import { resources } from '@/i18n/translations'; declare module 'i18next' { interface CustomTypeOptions { diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json index 6ba186ec..5579872d 100644 --- a/src/i18n/locale/en.json +++ b/src/i18n/locale/en.json @@ -206,6 +206,9 @@ "start": "Start", "submit": "Submit" }, + "time": { + "hour_short": "h" + }, "date": { "label": { "today": "Today", @@ -321,6 +324,17 @@ "unread_chapters": "With unread chapter(s)" } }, + "auto_update": { + "interval": { + "label": { + "value": "{{hours}}$t(global.time.hour_short)", + "title": "Automatic update interval" + } + }, + "label": { + "title": "Automatic updates" + } + }, "title": "Global update" }, "title": "Library Settings" diff --git a/src/screens/settings/LibrarySettings.tsx b/src/screens/settings/LibrarySettings.tsx index 0acc6233..1986b1b6 100644 --- a/src/screens/settings/LibrarySettings.tsx +++ b/src/screens/settings/LibrarySettings.tsx @@ -9,7 +9,7 @@ import { useTranslation } from 'react-i18next'; import { useContext, useEffect } from 'react'; import { NavBarContext, useSetDefaultBackTo } from '@/components/context/NavbarContext.tsx'; -import { GlobalUpdateSettings } from '@/components/globalUpdate/GlobalUpdateSettings.tsx'; +import { GlobalUpdateSettings } from '@/components/settings/globalUpdate/GlobalUpdateSettings.tsx'; import { SearchSettings } from '@/screens/settings/SearchSettings.tsx'; export function LibrarySettings() { diff --git a/src/typings.ts b/src/typings.ts index 589a4b50..1639edd8 100644 --- a/src/typings.ts +++ b/src/typings.ts @@ -15,6 +15,7 @@ import { GetChapterQuery, GetExtensionQuery, GetMangaQuery, + GetServerSettingsQuery, GetSourceQuery, MetaType, SourcePreferenceChangeInput, @@ -376,3 +377,5 @@ export type BackupValidationResult = { missingTrackers: string[]; mangasMissingSources: string[]; }; + +export type ServerSettings = GetServerSettingsQuery['settings'];