Use "GlobalDialogManager" for backup validation
This commit is contained in:
110
src/features/backup/component/BackupValidationDialog.tsx
Normal file
110
src/features/backup/component/BackupValidationDialog.tsx
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
/*
|
||||||
|
* 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 List from '@mui/material/List';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import Dialog from '@mui/material/Dialog';
|
||||||
|
import DialogTitle from '@mui/material/DialogTitle';
|
||||||
|
import DialogContent from '@mui/material/DialogContent';
|
||||||
|
import DialogActions from '@mui/material/DialogActions';
|
||||||
|
import Button from '@mui/material/Button';
|
||||||
|
import ListItem from '@mui/material/ListItem';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import Stack from '@mui/material/Stack';
|
||||||
|
import { BrowseTab } from '@/features/browse/Browse.types.ts';
|
||||||
|
import { AppRoutes } from '@/base/AppRoute.constants.ts';
|
||||||
|
import { DialogProps } from '@/base/global-dialog/GlobalDialogManager.tsx';
|
||||||
|
import { ValidateBackupResult } from '@/lib/graphql/generated/graphql.ts';
|
||||||
|
|
||||||
|
export const BackupValidationDialog = ({
|
||||||
|
validationResult,
|
||||||
|
onCancel,
|
||||||
|
onConfirm,
|
||||||
|
}: DialogProps & { validationResult: ValidateBackupResult }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open>
|
||||||
|
<DialogTitle>{t('settings.backup.action.validate.dialog.title')}</DialogTitle>
|
||||||
|
<DialogContent dividers>
|
||||||
|
{!!validationResult?.missingSources.length && (
|
||||||
|
<List
|
||||||
|
sx={{ listStyleType: 'initial', listStylePosition: 'inside' }}
|
||||||
|
subheader={t('settings.backup.action.validate.dialog.content.label.missing_sources')}
|
||||||
|
>
|
||||||
|
{validationResult?.missingSources.map(({ id, name }) => (
|
||||||
|
<ListItem sx={{ display: 'list-item' }} key={id}>
|
||||||
|
{`${name} (${id})`}
|
||||||
|
</ListItem>
|
||||||
|
))}
|
||||||
|
</List>
|
||||||
|
)}
|
||||||
|
{!!validationResult?.missingTrackers.length && (
|
||||||
|
<List
|
||||||
|
sx={{ listStyleType: 'initial', listStylePosition: 'inside' }}
|
||||||
|
subheader={t('settings.backup.action.validate.dialog.content.label.missing_trackers')}
|
||||||
|
>
|
||||||
|
{validationResult?.missingTrackers.map(({ name }) => (
|
||||||
|
<ListItem sx={{ display: 'list-item' }} key={name}>
|
||||||
|
{`${name}`}
|
||||||
|
</ListItem>
|
||||||
|
))}
|
||||||
|
</List>
|
||||||
|
)}
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Stack
|
||||||
|
direction="row"
|
||||||
|
sx={{
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
width: '100%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{!!validationResult?.missingSources.length && (
|
||||||
|
<Button
|
||||||
|
onClick={onCancel}
|
||||||
|
component={Link}
|
||||||
|
to={AppRoutes.browse.path(BrowseTab.EXTENSIONS)}
|
||||||
|
autoFocus={!!validationResult?.missingSources.length}
|
||||||
|
variant={validationResult?.missingSources.length ? 'contained' : 'text'}
|
||||||
|
>
|
||||||
|
{t('extension.action.label.install')}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{!!validationResult?.missingTrackers.length && (
|
||||||
|
<Button
|
||||||
|
onClick={onCancel}
|
||||||
|
component={Link}
|
||||||
|
to={AppRoutes.tracker.path}
|
||||||
|
autoFocus={!!validationResult?.missingTrackers.length}
|
||||||
|
variant={validationResult?.missingTrackers.length ? 'contained' : 'text'}
|
||||||
|
>
|
||||||
|
{t('global.button.log_in')}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<Stack direction="row">
|
||||||
|
<Button onClick={onCancel}>{t('global.button.cancel')}</Button>
|
||||||
|
<Button
|
||||||
|
onClick={onConfirm}
|
||||||
|
autoFocus={
|
||||||
|
!validationResult?.missingSources.length && !validationResult?.missingTrackers.length
|
||||||
|
}
|
||||||
|
variant={
|
||||||
|
!validationResult?.missingSources.length && !validationResult?.missingTrackers.length
|
||||||
|
? 'contained'
|
||||||
|
: 'text'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{t('global.button.restore')}
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -15,18 +15,10 @@ import ListItemButton from '@mui/material/ListItemButton';
|
|||||||
import ListItemIcon from '@mui/material/ListItemIcon';
|
import ListItemIcon from '@mui/material/ListItemIcon';
|
||||||
import ListSubheader from '@mui/material/ListSubheader';
|
import ListSubheader from '@mui/material/ListSubheader';
|
||||||
import { t as translate } from 'i18next';
|
import { t as translate } from 'i18next';
|
||||||
import Dialog from '@mui/material/Dialog';
|
|
||||||
import DialogTitle from '@mui/material/DialogTitle';
|
|
||||||
import DialogContent from '@mui/material/DialogContent';
|
|
||||||
import DialogActions from '@mui/material/DialogActions';
|
|
||||||
import Button from '@mui/material/Button';
|
|
||||||
import ListItem from '@mui/material/ListItem';
|
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
import Stack from '@mui/material/Stack';
|
|
||||||
import { useEventListener, useMergedRef, useWindowEvent } from '@mantine/hooks';
|
import { useEventListener, useMergedRef, useWindowEvent } from '@mantine/hooks';
|
||||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||||
import { makeToast } from '@/base/utils/Toast.ts';
|
import { makeToast } from '@/base/utils/Toast.ts';
|
||||||
import { BackupRestoreState, ValidateBackupQuery } from '@/lib/graphql/generated/graphql.ts';
|
import { BackupRestoreState } from '@/lib/graphql/generated/graphql.ts';
|
||||||
import { Progress } from '@/base/components/feedback/Progress.tsx';
|
import { Progress } from '@/base/components/feedback/Progress.tsx';
|
||||||
import { TextSetting } from '@/base/components/settings/text/TextSetting.tsx';
|
import { TextSetting } from '@/base/components/settings/text/TextSetting.tsx';
|
||||||
import { NumberSetting } from '@/base/components/settings/NumberSetting.tsx';
|
import { NumberSetting } from '@/base/components/settings/NumberSetting.tsx';
|
||||||
@@ -35,12 +27,11 @@ import { LoadingPlaceholder } from '@/base/components/feedback/LoadingPlaceholde
|
|||||||
import { EmptyViewAbsoluteCentered } from '@/base/components/feedback/EmptyViewAbsoluteCentered.tsx';
|
import { EmptyViewAbsoluteCentered } from '@/base/components/feedback/EmptyViewAbsoluteCentered.tsx';
|
||||||
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
|
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
|
||||||
import { ServerSettings } from '@/features/settings/Settings.types.ts';
|
import { ServerSettings } from '@/features/settings/Settings.types.ts';
|
||||||
import { AppRoutes } from '@/base/AppRoute.constants.ts';
|
|
||||||
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
|
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
|
||||||
import { useAppTitle } from '@/features/navigation-bar/hooks/useAppTitle.ts';
|
import { useAppTitle } from '@/features/navigation-bar/hooks/useAppTitle.ts';
|
||||||
import { BrowseTab } from '@/features/browse/Browse.types.ts';
|
|
||||||
import { GlobalDialogManager } from '@/base/global-dialog/GlobalDialogManager.tsx';
|
import { GlobalDialogManager } from '@/base/global-dialog/GlobalDialogManager.tsx';
|
||||||
import { BackupFlagInclusionDialog } from '@/features/backup/component/BackupFlagInclusionDialog.tsx';
|
import { BackupFlagInclusionDialog } from '@/features/backup/component/BackupFlagInclusionDialog.tsx';
|
||||||
|
import { BackupValidationDialog } from '@/features/backup/component/BackupValidationDialog.tsx';
|
||||||
|
|
||||||
type BackupSettingsType = Pick<ServerSettings, 'backupPath' | 'backupTime' | 'backupInterval' | 'backupTTL'>;
|
type BackupSettingsType = Pick<ServerSettings, 'backupPath' | 'backupTime' | 'backupInterval' | 'backupTTL'>;
|
||||||
|
|
||||||
@@ -72,10 +63,6 @@ export function Backup() {
|
|||||||
pollInterval: 1000,
|
pollInterval: 1000,
|
||||||
});
|
});
|
||||||
|
|
||||||
const [currentBackupFile, setCurrentBackupFile] = useState<File | null>(null);
|
|
||||||
const [isInvalidBackupDialogOpen, setIsInvalidBackupDialogOpen] = useState(false);
|
|
||||||
const [validationResult, setValidationResult] = useState<ValidateBackupQuery['validateBackup']>();
|
|
||||||
|
|
||||||
const [, setTriggerReRender] = useState(0);
|
const [, setTriggerReRender] = useState(0);
|
||||||
|
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
@@ -123,8 +110,6 @@ export function Backup() {
|
|||||||
}, [data?.restoreStatus?.state]);
|
}, [data?.restoreStatus?.state]);
|
||||||
|
|
||||||
const resetBackupState = () => {
|
const resetBackupState = () => {
|
||||||
setCurrentBackupFile(null);
|
|
||||||
|
|
||||||
const input = document.getElementById('backup-file') as HTMLInputElement;
|
const input = document.getElementById('backup-file') as HTMLInputElement;
|
||||||
if (input) {
|
if (input) {
|
||||||
input.value = '';
|
input.value = '';
|
||||||
@@ -167,14 +152,19 @@ export function Backup() {
|
|||||||
} = await requestManager.validateBackupFile(file, { fetchPolicy: 'network-only' }).response;
|
} = await requestManager.validateBackupFile(file, { fetchPolicy: 'network-only' }).response;
|
||||||
|
|
||||||
if (validateBackupData.missingSources.length || validateBackupData.missingTrackers.length) {
|
if (validateBackupData.missingSources.length || validateBackupData.missingTrackers.length) {
|
||||||
setValidationResult(validateBackupData);
|
try {
|
||||||
setIsInvalidBackupDialogOpen(true);
|
await GlobalDialogManager.show(`backup-validate-${file.name}`, BackupValidationDialog, {
|
||||||
|
validationResult: validateBackupData,
|
||||||
|
});
|
||||||
|
} catch (_) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
makeToast(t('settings.backup.action.validate.error.label.failure'), 'error', getErrorMessage(e));
|
makeToast(t('settings.backup.action.validate.error.label.failure'), 'error', getErrorMessage(e));
|
||||||
|
} finally {
|
||||||
resetBackupState();
|
resetBackupState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,18 +197,12 @@ export function Backup() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setCurrentBackupFile(file);
|
|
||||||
const isBackupValid = await validateBackup(file);
|
const isBackupValid = await validateBackup(file);
|
||||||
if (isBackupValid) {
|
if (isBackupValid) {
|
||||||
await restoreBackup(file);
|
await restoreBackup(file);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const closeInvalidBackupDialog = () => {
|
|
||||||
setIsInvalidBackupDialogOpen(false);
|
|
||||||
resetBackupState();
|
|
||||||
};
|
|
||||||
|
|
||||||
useWindowEvent('drop', async (e) => {
|
useWindowEvent('drop', async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const files = await fromEvent(e);
|
const files = await fromEvent(e);
|
||||||
@@ -322,88 +306,6 @@ export function Backup() {
|
|||||||
</List>
|
</List>
|
||||||
</List>
|
</List>
|
||||||
<input ref={mergedInputRef} type="file" style={{ display: 'none' }} />
|
<input ref={mergedInputRef} type="file" style={{ display: 'none' }} />
|
||||||
<Dialog open={isInvalidBackupDialogOpen}>
|
|
||||||
<DialogTitle>{t('settings.backup.action.validate.dialog.title')}</DialogTitle>
|
|
||||||
<DialogContent dividers>
|
|
||||||
{!!validationResult?.missingSources.length && (
|
|
||||||
<List
|
|
||||||
sx={{ listStyleType: 'initial', listStylePosition: 'inside' }}
|
|
||||||
subheader={t('settings.backup.action.validate.dialog.content.label.missing_sources')}
|
|
||||||
>
|
|
||||||
{validationResult?.missingSources.map(({ id, name }) => (
|
|
||||||
<ListItem sx={{ display: 'list-item' }} key={id}>
|
|
||||||
{`${name} (${id})`}
|
|
||||||
</ListItem>
|
|
||||||
))}
|
|
||||||
</List>
|
|
||||||
)}
|
|
||||||
{!!validationResult?.missingTrackers.length && (
|
|
||||||
<List
|
|
||||||
sx={{ listStyleType: 'initial', listStylePosition: 'inside' }}
|
|
||||||
subheader={t('settings.backup.action.validate.dialog.content.label.missing_trackers')}
|
|
||||||
>
|
|
||||||
{validationResult?.missingTrackers.map(({ name }) => (
|
|
||||||
<ListItem sx={{ display: 'list-item' }} key={name}>
|
|
||||||
{`${name}`}
|
|
||||||
</ListItem>
|
|
||||||
))}
|
|
||||||
</List>
|
|
||||||
)}
|
|
||||||
</DialogContent>
|
|
||||||
<DialogActions>
|
|
||||||
<Stack
|
|
||||||
direction="row"
|
|
||||||
sx={{
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
width: '100%',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{!!validationResult?.missingSources.length && (
|
|
||||||
<Button
|
|
||||||
onClick={closeInvalidBackupDialog}
|
|
||||||
component={Link}
|
|
||||||
to={AppRoutes.browse.path(BrowseTab.EXTENSIONS)}
|
|
||||||
autoFocus={!!validationResult?.missingSources.length}
|
|
||||||
variant={validationResult?.missingSources.length ? 'contained' : 'text'}
|
|
||||||
>
|
|
||||||
{t('extension.action.label.install')}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
{!!validationResult?.missingTrackers.length && (
|
|
||||||
<Button
|
|
||||||
onClick={closeInvalidBackupDialog}
|
|
||||||
component={Link}
|
|
||||||
to={AppRoutes.tracker.path}
|
|
||||||
autoFocus={!!validationResult?.missingTrackers.length}
|
|
||||||
variant={validationResult?.missingTrackers.length ? 'contained' : 'text'}
|
|
||||||
>
|
|
||||||
{t('global.button.log_in')}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
<Stack direction="row">
|
|
||||||
<Button onClick={closeInvalidBackupDialog}>{t('global.button.cancel')}</Button>
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
closeInvalidBackupDialog();
|
|
||||||
restoreBackup(currentBackupFile!);
|
|
||||||
}}
|
|
||||||
autoFocus={
|
|
||||||
!validationResult?.missingSources.length &&
|
|
||||||
!validationResult?.missingTrackers.length
|
|
||||||
}
|
|
||||||
variant={
|
|
||||||
!validationResult?.missingSources.length &&
|
|
||||||
!validationResult?.missingTrackers.length
|
|
||||||
? 'contained'
|
|
||||||
: 'text'
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{t('global.button.restore')}
|
|
||||||
</Button>
|
|
||||||
</Stack>
|
|
||||||
</Stack>
|
|
||||||
</DialogActions>
|
|
||||||
</Dialog>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user