mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-04-13 13:30:31 +08:00
221 lines
7.1 KiB
TypeScript
221 lines
7.1 KiB
TypeScript
import { LoadingButton } from "@mui/lab";
|
|
import {
|
|
Button,
|
|
List,
|
|
ListItem,
|
|
ListItemText,
|
|
Stack,
|
|
Typography,
|
|
} from "@mui/material";
|
|
import { useLockFn } from "ahooks";
|
|
import type { ReactNode, Ref } from "react";
|
|
import { useCallback, useImperativeHandle, useState } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
|
|
import { BaseDialog, DialogRef } from "@/components/base";
|
|
import { createLocalBackup, createWebdavBackup } from "@/services/cmds";
|
|
import { showNotice } from "@/services/noticeService";
|
|
|
|
import { AutoBackupSettings } from "./auto-backup-settings";
|
|
import { BackupHistoryViewer } from "./backup-history-viewer";
|
|
import { BackupWebdavDialog } from "./backup-webdav-dialog";
|
|
|
|
type BackupSource = "local" | "webdav";
|
|
|
|
export function BackupViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
|
const { t } = useTranslation();
|
|
const [open, setOpen] = useState(false);
|
|
const [busyAction, setBusyAction] = useState<BackupSource | null>(null);
|
|
const [historyOpen, setHistoryOpen] = useState(false);
|
|
const [historySource, setHistorySource] = useState<BackupSource>("local");
|
|
const [historyPage, setHistoryPage] = useState(0);
|
|
const [webdavDialogOpen, setWebdavDialogOpen] = useState(false);
|
|
|
|
useImperativeHandle(ref, () => ({
|
|
open: () => setOpen(true),
|
|
close: () => setOpen(false),
|
|
}));
|
|
|
|
const openHistory = (target: BackupSource) => {
|
|
setHistorySource(target);
|
|
setHistoryPage(0);
|
|
setHistoryOpen(true);
|
|
};
|
|
|
|
const handleBackup = useLockFn(async (target: BackupSource) => {
|
|
try {
|
|
setBusyAction(target);
|
|
if (target === "local") {
|
|
await createLocalBackup();
|
|
showNotice.success(
|
|
"settings.modals.backup.messages.localBackupCreated",
|
|
);
|
|
} else {
|
|
await createWebdavBackup();
|
|
showNotice.success("settings.modals.backup.messages.backupCreated");
|
|
}
|
|
} catch (error) {
|
|
console.error(error);
|
|
showNotice.error(
|
|
target === "local"
|
|
? "settings.modals.backup.messages.localBackupFailed"
|
|
: "settings.modals.backup.messages.backupFailed",
|
|
target === "local" ? undefined : { error },
|
|
);
|
|
} finally {
|
|
setBusyAction(null);
|
|
}
|
|
});
|
|
|
|
const setWebdavBusy = useCallback(
|
|
(loading: boolean) => {
|
|
setBusyAction(loading ? "webdav" : null);
|
|
},
|
|
[setBusyAction],
|
|
);
|
|
|
|
return (
|
|
<BaseDialog
|
|
open={open}
|
|
title={t("settings.modals.backup.title")}
|
|
contentSx={{ width: { xs: 360, sm: 520 } }}
|
|
disableOk
|
|
cancelBtn={t("shared.actions.close")}
|
|
onCancel={() => setOpen(false)}
|
|
onClose={() => setOpen(false)}
|
|
>
|
|
<Stack spacing={2}>
|
|
<Stack
|
|
spacing={1}
|
|
sx={{
|
|
border: (theme) => `1px solid ${theme.palette.divider}`,
|
|
borderRadius: 2,
|
|
p: 2,
|
|
}}
|
|
>
|
|
<Typography variant="subtitle1">
|
|
{t("settings.modals.backup.auto.title")}
|
|
</Typography>
|
|
<List disablePadding sx={{ ".MuiListItem-root": { px: 0 } }}>
|
|
<AutoBackupSettings />
|
|
</List>
|
|
</Stack>
|
|
|
|
<Stack
|
|
spacing={1}
|
|
sx={{
|
|
border: (theme) => `1px solid ${theme.palette.divider}`,
|
|
borderRadius: 2,
|
|
p: 2,
|
|
}}
|
|
>
|
|
<Typography variant="subtitle1">
|
|
{t("settings.modals.backup.manual.title")}
|
|
</Typography>
|
|
<List disablePadding sx={{ ".MuiListItem-root": { px: 0 } }}>
|
|
{(
|
|
[
|
|
{
|
|
key: "local" as BackupSource,
|
|
title: t("settings.modals.backup.tabs.local"),
|
|
description: t("settings.modals.backup.manual.local"),
|
|
actions: [
|
|
<LoadingButton
|
|
key="backup"
|
|
variant="contained"
|
|
size="small"
|
|
loading={busyAction === "local"}
|
|
onClick={() => handleBackup("local")}
|
|
>
|
|
{t("settings.modals.backup.actions.backup")}
|
|
</LoadingButton>,
|
|
<Button
|
|
key="history"
|
|
variant="outlined"
|
|
size="small"
|
|
onClick={() => openHistory("local")}
|
|
>
|
|
{t("settings.modals.backup.actions.viewHistory")}
|
|
</Button>,
|
|
],
|
|
},
|
|
{
|
|
key: "webdav" as BackupSource,
|
|
title: t("settings.modals.backup.tabs.webdav"),
|
|
description: t("settings.modals.backup.manual.webdav"),
|
|
actions: [
|
|
<LoadingButton
|
|
key="backup"
|
|
variant="contained"
|
|
size="small"
|
|
loading={busyAction === "webdav"}
|
|
onClick={() => handleBackup("webdav")}
|
|
>
|
|
{t("settings.modals.backup.actions.backup")}
|
|
</LoadingButton>,
|
|
<Button
|
|
key="history"
|
|
variant="outlined"
|
|
size="small"
|
|
onClick={() => openHistory("webdav")}
|
|
>
|
|
{t("settings.modals.backup.actions.viewHistory")}
|
|
</Button>,
|
|
<Button
|
|
key="configure"
|
|
variant="text"
|
|
size="small"
|
|
onClick={() => setWebdavDialogOpen(true)}
|
|
>
|
|
{t("settings.modals.backup.manual.configureWebdav")}
|
|
</Button>,
|
|
],
|
|
},
|
|
] satisfies Array<{
|
|
key: BackupSource;
|
|
title: string;
|
|
description: string;
|
|
actions: ReactNode[];
|
|
}>
|
|
).map((item, idx) => (
|
|
<ListItem key={item.key} disableGutters divider={idx === 0}>
|
|
<Stack spacing={1} sx={{ width: "100%" }}>
|
|
<ListItemText
|
|
primary={item.title}
|
|
slotProps={{ secondary: { component: "span" } }}
|
|
secondary={item.description}
|
|
/>
|
|
<Stack
|
|
direction="row"
|
|
spacing={1}
|
|
useFlexGap
|
|
flexWrap="wrap"
|
|
alignItems="center"
|
|
>
|
|
{item.actions}
|
|
</Stack>
|
|
</Stack>
|
|
</ListItem>
|
|
))}
|
|
</List>
|
|
</Stack>
|
|
</Stack>
|
|
|
|
<BackupHistoryViewer
|
|
open={historyOpen}
|
|
source={historySource}
|
|
page={historyPage}
|
|
onSourceChange={setHistorySource}
|
|
onPageChange={setHistoryPage}
|
|
onClose={() => setHistoryOpen(false)}
|
|
/>
|
|
<BackupWebdavDialog
|
|
open={webdavDialogOpen}
|
|
onClose={() => setWebdavDialogOpen(false)}
|
|
onBackupSuccess={() => openHistory("webdav")}
|
|
setBusy={setWebdavBusy}
|
|
/>
|
|
</BaseDialog>
|
|
);
|
|
}
|