Add "auto backup flags" settings
This commit is contained in:
@@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
import { PartialBackupFlagsInput } from '@/lib/graphql/generated/graphql.ts';
|
||||
import { ServerSettings } from '@/features/settings/Settings.types.ts';
|
||||
|
||||
export enum BackupFlagGroup {
|
||||
LIBRARY = 'library',
|
||||
@@ -16,3 +17,31 @@ export enum BackupFlagGroup {
|
||||
export type BackupFlag = keyof PartialBackupFlagsInput;
|
||||
|
||||
export type BackupFlagInclusionState = Record<BackupFlag, boolean>;
|
||||
|
||||
export type AutoBackupFlag = Pick<
|
||||
ServerSettings,
|
||||
| 'autoBackupIncludeCategories'
|
||||
| 'autoBackupIncludeChapters'
|
||||
| 'autoBackupIncludeClientData'
|
||||
| 'autoBackupIncludeHistory'
|
||||
| 'autoBackupIncludeManga'
|
||||
| 'autoBackupIncludeServerSettings'
|
||||
| 'autoBackupIncludeTracking'
|
||||
>;
|
||||
|
||||
export type AutoBackupFlagInclusionState = Record<keyof AutoBackupFlag, boolean>;
|
||||
|
||||
export type BackupSettingsType = Pick<
|
||||
ServerSettings,
|
||||
| 'backupPath'
|
||||
| 'backupTime'
|
||||
| 'backupInterval'
|
||||
| 'backupTTL'
|
||||
| 'autoBackupIncludeCategories'
|
||||
| 'autoBackupIncludeChapters'
|
||||
| 'autoBackupIncludeClientData'
|
||||
| 'autoBackupIncludeHistory'
|
||||
| 'autoBackupIncludeManga'
|
||||
| 'autoBackupIncludeServerSettings'
|
||||
| 'autoBackupIncludeTracking'
|
||||
>;
|
||||
|
||||
68
src/features/backup/Backup.utils.ts
Normal file
68
src/features/backup/Backup.utils.ts
Normal file
@@ -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 { t as translate } from 'i18next';
|
||||
import { AutoBackupFlagInclusionState, BackupFlag, BackupFlagInclusionState } from '@/features/backup/Backup.types.ts';
|
||||
import { BACKUP_FLAGS_TO_TRANSLATION } from '@/features/backup/Backup.constants.ts';
|
||||
|
||||
export const convertToAutoBackupFlags = (flags: BackupFlagInclusionState): AutoBackupFlagInclusionState => ({
|
||||
autoBackupIncludeCategories: flags.includeCategories,
|
||||
autoBackupIncludeChapters: flags.includeChapters,
|
||||
autoBackupIncludeClientData: flags.includeClientData,
|
||||
autoBackupIncludeHistory: flags.includeHistory,
|
||||
autoBackupIncludeManga: flags.includeManga,
|
||||
autoBackupIncludeServerSettings: flags.includeServerSettings,
|
||||
autoBackupIncludeTracking: flags.includeTracking,
|
||||
});
|
||||
|
||||
export const convertToBackupFlags = (flags: AutoBackupFlagInclusionState): BackupFlagInclusionState => ({
|
||||
includeCategories: flags.autoBackupIncludeCategories,
|
||||
includeChapters: flags.autoBackupIncludeChapters,
|
||||
includeClientData: flags.autoBackupIncludeClientData,
|
||||
includeHistory: flags.autoBackupIncludeHistory,
|
||||
includeManga: flags.autoBackupIncludeManga,
|
||||
includeServerSettings: flags.autoBackupIncludeServerSettings,
|
||||
includeTracking: flags.autoBackupIncludeTracking,
|
||||
});
|
||||
|
||||
const getIncludeExcludeText = (count: number, allCount: number, specificText: string): string => {
|
||||
if (count === 0) {
|
||||
return translate('global.label.none');
|
||||
}
|
||||
|
||||
if (count === allCount) {
|
||||
return translate('extension.language.all');
|
||||
}
|
||||
|
||||
return specificText;
|
||||
};
|
||||
|
||||
export const getAutoBackupFlagsInfo = (autoFlags: AutoBackupFlagInclusionState): Record<`${boolean}`, string> => {
|
||||
const flags = convertToBackupFlags(autoFlags);
|
||||
const totalFlags = Object.keys(flags).length;
|
||||
|
||||
const flagsByState = Object.groupBy(Object.entries(flags), ([, value]) => value.toString());
|
||||
|
||||
const includedFlagsString =
|
||||
flagsByState.true?.map(([key]) => translate(BACKUP_FLAGS_TO_TRANSLATION[key as BackupFlag])).join(', ') ?? '';
|
||||
const excludedFlagsString =
|
||||
flagsByState.false?.map(([key]) => translate(BACKUP_FLAGS_TO_TRANSLATION[key as BackupFlag])).join(', ') ?? '';
|
||||
|
||||
return {
|
||||
false: getIncludeExcludeText(flagsByState.false?.length ?? 0, totalFlags, excludedFlagsString),
|
||||
true: getIncludeExcludeText(flagsByState.true?.length ?? 0, totalFlags, includedFlagsString),
|
||||
};
|
||||
};
|
||||
|
||||
export const getBackupCleanupDisplayValue = (ttl: number): string => {
|
||||
if (ttl === 0) {
|
||||
return translate('global.label.never');
|
||||
}
|
||||
|
||||
return translate('settings.backup.automated.cleanup.label.value', { days: ttl, count: ttl });
|
||||
};
|
||||
@@ -32,11 +32,12 @@ export const BackupFlagInclusionDialog = ({
|
||||
isVisible,
|
||||
onExitComplete,
|
||||
title,
|
||||
}: AwaitableComponentProps<BackupFlagInclusionState> & { title: string }) => {
|
||||
flags,
|
||||
}: AwaitableComponentProps<BackupFlagInclusionState> & { title: string; flags?: BackupFlagInclusionState }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [includeStateByFlag, setIncludeStateByFlag] = useState(
|
||||
Object.fromEntries(BACKUP_FLAGS.map((flag) => [flag, true])) as BackupFlagInclusionState,
|
||||
Object.fromEntries(BACKUP_FLAGS.map((flag) => [flag, flags?.[flag] ?? true])) as BackupFlagInclusionState,
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -44,10 +45,10 @@ export const BackupFlagInclusionDialog = ({
|
||||
<DialogTitle>{title}</DialogTitle>
|
||||
<DialogContent>
|
||||
<FormGroup sx={{ gap: 2 }}>
|
||||
{Object.entries(BACKUP_FLAGS_BY_GROUP).map(([group, flags]) => (
|
||||
{Object.entries(BACKUP_FLAGS_BY_GROUP).map(([group, groupFlags]) => (
|
||||
<Stack key={group}>
|
||||
<Typography>{t(BACKUP_FLAG_GROUP_TO_TRANSLATION[group as BackupFlagGroup])}</Typography>
|
||||
{flags.map((flag) => (
|
||||
{groupFlags.map((flag) => (
|
||||
<CheckboxInput
|
||||
key={flag}
|
||||
label={t(BACKUP_FLAGS_TO_TRANSLATION[flag])}
|
||||
|
||||
@@ -14,7 +14,6 @@ import { useTranslation } from 'react-i18next';
|
||||
import ListItemButton from '@mui/material/ListItemButton';
|
||||
import ListItemIcon from '@mui/material/ListItemIcon';
|
||||
import ListSubheader from '@mui/material/ListSubheader';
|
||||
import { t as translate } from 'i18next';
|
||||
import { useEventListener, useMergedRef, useWindowEvent } from '@mantine/hooks';
|
||||
import { AwaitableComponent } from 'awaitable-component';
|
||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||
@@ -27,21 +26,17 @@ import { TimeSetting } from '@/base/components/settings/TimeSetting.tsx';
|
||||
import { LoadingPlaceholder } from '@/base/components/feedback/LoadingPlaceholder.tsx';
|
||||
import { EmptyViewAbsoluteCentered } from '@/base/components/feedback/EmptyViewAbsoluteCentered.tsx';
|
||||
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
|
||||
import { ServerSettings } from '@/features/settings/Settings.types.ts';
|
||||
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
|
||||
import { useAppTitle } from '@/features/navigation-bar/hooks/useAppTitle.ts';
|
||||
import { BackupFlagInclusionDialog } from '@/features/backup/component/BackupFlagInclusionDialog.tsx';
|
||||
import { BackupValidationDialog } from '@/features/backup/component/BackupValidationDialog.tsx';
|
||||
|
||||
type BackupSettingsType = Pick<ServerSettings, 'backupPath' | 'backupTime' | 'backupInterval' | 'backupTTL'>;
|
||||
|
||||
const getBackupCleanupDisplayValue = (ttl: number): string => {
|
||||
if (ttl === 0) {
|
||||
return translate('global.label.never');
|
||||
}
|
||||
|
||||
return translate('settings.backup.automated.cleanup.label.value', { days: ttl, count: ttl });
|
||||
};
|
||||
import {
|
||||
convertToAutoBackupFlags,
|
||||
convertToBackupFlags,
|
||||
getAutoBackupFlagsInfo,
|
||||
getBackupCleanupDisplayValue,
|
||||
} from '@/features/backup/Backup.utils.ts';
|
||||
import { BackupSettingsType } from '@/features/backup/Backup.types.ts';
|
||||
|
||||
let backupRestoreId: string | undefined;
|
||||
|
||||
@@ -85,6 +80,14 @@ export function Backup() {
|
||||
);
|
||||
};
|
||||
|
||||
const updateSettings = <Setting extends keyof BackupSettingsType>(
|
||||
settings: Record<Setting, BackupSettingsType[Setting]>,
|
||||
) => {
|
||||
mutateSettings({ variables: { input: { settings } } }).catch((e) =>
|
||||
makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e)),
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!data?.restoreStatus) {
|
||||
return;
|
||||
@@ -280,6 +283,39 @@ export function Backup() {
|
||||
}
|
||||
handleChange={(path) => updateSetting('backupPath', path)}
|
||||
/>
|
||||
<ListItemButton
|
||||
onClick={async () => {
|
||||
try {
|
||||
const flags = await AwaitableComponent.show(BackupFlagInclusionDialog, {
|
||||
title: t('settings.backup.automated.flags.title'),
|
||||
flags: convertToBackupFlags(backupSettings),
|
||||
});
|
||||
|
||||
updateSettings(convertToAutoBackupFlags(flags));
|
||||
} catch (e) {
|
||||
// Ignore
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ListItemText
|
||||
primary={t('settings.backup.automated.flags.title')}
|
||||
secondary={
|
||||
<>
|
||||
<span>
|
||||
{t('category.settings.inclusion.label.include', {
|
||||
includedCategoriesText: getAutoBackupFlagsInfo(backupSettings).true,
|
||||
})}
|
||||
</span>
|
||||
<span>
|
||||
{t('category.settings.inclusion.label.exclude', {
|
||||
excludedCategoriesText: getAutoBackupFlagsInfo(backupSettings).false,
|
||||
})}
|
||||
</span>
|
||||
</>
|
||||
}
|
||||
secondaryTypographyProps={{ style: { display: 'flex', flexDirection: 'column' } }}
|
||||
/>
|
||||
</ListItemButton>
|
||||
<TimeSetting
|
||||
settingName={t('settings.backup.automated.label.time')}
|
||||
value={backupSettings.backupTime}
|
||||
|
||||
Reference in New Issue
Block a user