Skip to content

Commit

Permalink
Feature/settings backup (#453)
Browse files Browse the repository at this point in the history
* Show loading indicator for "NumberSetting"

* [i18n] Add lowercase interpolation format

* Add required dependencies for time settings

* Dynamically import dayjs locales for languages

* Add backup settings
  • Loading branch information
schroda authored Nov 12, 2023
1 parent b080286 commit 9b96560
Show file tree
Hide file tree
Showing 10 changed files with 419 additions and 70 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,11 @@
"@fontsource/roboto": "^5.0.8",
"@mui/icons-material": "^5.14.16",
"@mui/material": "^5.14.16",
"@mui/x-date-pickers": "^6.18.0",
"@vitejs/plugin-react-swc": "^3.4.1",
"apollo-upload-client": "^17.0.0",
"axios": "^1.6.0",
"dayjs": "^1.11.10",
"file-selector": "^0.6.0",
"graphql-tag": "^2.12.6",
"graphql-ws": "^5.14.2",
Expand Down
4 changes: 2 additions & 2 deletions src/components/settings/NumberSetting.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import Slider from '@mui/material/Slider';

type BaseProps = {
settingTitle: string;
settingValue: string;
settingValue?: string;
settingIcon?: React.ReactNode;
value: number;
defaultValue?: number;
Expand Down Expand Up @@ -98,7 +98,7 @@ export const NumberSetting = ({
{settingIcon ? <ListItemIcon>{settingIcon}</ListItemIcon> : null}
<ListItemText
primary={settingTitle}
secondary={settingValue}
secondary={settingValue ?? t('global.label.loading')}
secondaryTypographyProps={{ style: { display: 'flex', flexDirection: 'column' } }}
/>
</ListItemButton>
Expand Down
133 changes: 133 additions & 0 deletions src/components/settings/TimeSetting.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/*
* 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 DialogActions from '@mui/material/DialogActions';
import ListItemButton from '@mui/material/ListItemButton';
import { useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
import { TimePicker } from '@mui/x-date-pickers/TimePicker';
import dayjs from 'dayjs';
import { loadDayJsLocale } from '@/util/language.tsx';

export const TimeSetting = ({
settingName,
value,
defaultValue,
handleChange,
}: {
settingName: string;
value?: string;
defaultValue: string;
handleChange: (path: string) => void;
}) => {
const { t, i18n } = useTranslation();

const [isDialogOpen, setIsDialogOpen] = useState(false);
const [dialogValue, setDialogValue] = useState(value ?? defaultValue);

const [locale, setLocale] = useState('en');

const currentLocale = i18n.language;

useEffect(() => {
loadDayJsLocale(currentLocale).then((wasLoaded) => setLocale(wasLoaded ? currentLocale : 'en'));
}, [currentLocale]);

useEffect(() => {
if (!value) {
return;
}

setDialogValue(value);
}, [value]);

const closeDialog = useCallback(
(resetValue: boolean) => {
setIsDialogOpen(false);

if (resetValue) {
setDialogValue(value ?? defaultValue);
}
},
[value],
);

const closeDialogWithReset = useCallback(() => closeDialog(true), [closeDialog]);

const updateSetting = useCallback(
(newValue: string, shouldCloseDialog: boolean = true) => {
if (shouldCloseDialog) {
closeDialog(false);
}

const didValueChange = value !== newValue;
if (!didValueChange) {
return;
}

handleChange(newValue);
},
[value, handleChange, closeDialog],
);

return (
<>
<ListItemButton onClick={() => setIsDialogOpen(true)}>
<ListItemText
primary={settingName}
secondary={
value ? dayjs(value, 'HH:mm').locale(currentLocale).format('LT') : t('global.label.loading')
}
secondaryTypographyProps={{ style: { display: 'flex', flexDirection: 'column' } }}
/>
</ListItemButton>

<Dialog open={isDialogOpen} onClose={closeDialog}>
<DialogContent>
<DialogTitle sx={{ paddingLeft: 0 }}>{settingName}</DialogTitle>
<LocalizationProvider dateAdapter={AdapterDayjs} adapterLocale={locale}>
<TimePicker
value={dayjs(dialogValue, 'HH:mm')}
defaultValue={dayjs(defaultValue, 'HH:mm')}
format="LT"
onChange={(time) => setDialogValue(time?.format('HH:mm') ?? '00:00')}
/>
</LocalizationProvider>
</DialogContent>
<DialogActions>
{defaultValue !== undefined ? (
<Button
onClick={() => {
setDialogValue(defaultValue);
updateSetting(defaultValue, false);
}}
color="primary"
>
{t('global.button.reset_to_default')}
</Button>
) : null}
<Button onClick={closeDialogWithReset} color="primary">
{t('global.button.cancel')}
</Button>
<Button
onClick={() => {
updateSetting(dialogValue);
}}
color="primary"
>
{t('global.button.ok')}
</Button>
</DialogActions>
</Dialog>
</>
);
};
14 changes: 9 additions & 5 deletions src/components/settings/downloads/DownloadAheadSetting.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export const DownloadAheadSetting = () => {
const { t } = useTranslation();

const { data } = requestManager.useGetServerSettings();
const downloadAheadLimit = data?.settings.autoDownloadAheadLimit ?? 0;
const downloadAheadLimit = data?.settings.autoDownloadAheadLimit;
const shouldDownloadAhead = !!downloadAheadLimit;
const [mutateSettings] = requestManager.useUpdateServerSettings();

Expand Down Expand Up @@ -49,10 +49,14 @@ export const DownloadAheadSetting = () => {
{shouldDownloadAhead ? (
<NumberSetting
settingTitle={t('download.settings.download_ahead.label.unread_chapters_to_download')}
settingValue={t('download.settings.download_ahead.label.value', {
chapters: downloadAheadLimit,
count: downloadAheadLimit,
})}
settingValue={
downloadAheadLimit
? t('download.settings.download_ahead.label.value', {
chapters: downloadAheadLimit,
count: downloadAheadLimit,
})
: undefined
}
value={downloadAheadLimit}
minValue={MIN_LIMIT}
maxValue={MAX_LIMIT}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export const GlobalUpdateSettingsInterval = () => {
const { t } = useTranslation();

const { data } = requestManager.useGetServerSettings();
const autoUpdateIntervalHours = data?.settings.globalUpdateInterval ?? 0;
const autoUpdateIntervalHours = data?.settings.globalUpdateInterval;
const doAutoUpdates = !!autoUpdateIntervalHours;
const [mutateSettings] = requestManager.useUpdateServerSettings();

Expand All @@ -45,9 +45,13 @@ export const GlobalUpdateSettingsInterval = () => {
{doAutoUpdates ? (
<NumberSetting
settingTitle={t('library.settings.global_update.auto_update.interval.label.title')}
settingValue={t('library.settings.global_update.auto_update.interval.label.value', {
hours: autoUpdateIntervalHours,
})}
settingValue={
autoUpdateIntervalHours
? t('library.settings.global_update.auto_update.interval.label.value', {
hours: autoUpdateIntervalHours,
})
: undefined
}
value={autoUpdateIntervalHours}
minValue={MIN_INTERVAL_HOURS}
maxValue={MAX_INTERVAL_HOURS}
Expand Down
8 changes: 8 additions & 0 deletions src/i18n/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ export const i18n = use(initReactI18next)
fallbackLng: 'en',
interpolation: {
escapeValue: false,
format: (value, format) => {
switch (format) {
case 'lowercase':
return value.toLowerCase();
default:
return value;
}
},
},
returnNull: false,
debug: process.env.NODE_ENV !== 'production',
Expand Down
27 changes: 27 additions & 0 deletions src/i18n/locale/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -243,10 +243,18 @@
},
"date": {
"label": {
"day": "Day",
"day_one": "Day",
"day_other": "Days",
"today": "Today",
"today_at": "Today at {{timeString}}",
"yesterday": "Yesterday",
"yesterday_at": "Yesterday at {{timeString}}"
},
"value": {
"label": {
"day": "{{days}} $t(global.date.label.day, lowercase)"
}
}
},
"error": {
Expand Down Expand Up @@ -278,6 +286,7 @@
"display": "Display",
"filter": "Filter",
"loading": "Loading…",
"never": "Never",
"none": "None",
"sort": "Sort",
"unknown": "Unknown"
Expand Down Expand Up @@ -486,6 +495,24 @@
"title": "About"
},
"backup": {
"automated": {
"cleanup": {
"label": {
"title": "Backup cleanup",
"value": "Delete backups that are older than $t(global.date.value.label.day)"
}
},
"label": {
"interval": "Backup interval",
"time": "Backup time"
},
"location": {
"label": {
"description": "The path to the directory on the server where automated backups should get saved in",
"title": "Backup location"
}
}
},
"label": {
"backup_restore_failed": "Could not restore backup",
"create_backup": "Create backup",
Expand Down
Loading

0 comments on commit 9b96560

Please sign in to comment.