From ca97e3a3e69e77206b35312f8968b3945586a8ca Mon Sep 17 00:00:00 2001 From: huzibaca Date: Sat, 9 Nov 2024 06:56:58 +0800 Subject: [PATCH] chore: update --- src/components/setting/mods/backup-viewer.tsx | 607 ++++++++++-------- src/locales/en.json | 21 +- src/locales/fa.json | 25 +- src/locales/ru.json | 14 +- src/locales/zh.json | 32 +- 5 files changed, 429 insertions(+), 270 deletions(-) diff --git a/src/components/setting/mods/backup-viewer.tsx b/src/components/setting/mods/backup-viewer.tsx index 5a65594..b410406 100644 --- a/src/components/setting/mods/backup-viewer.tsx +++ b/src/components/setting/mods/backup-viewer.tsx @@ -4,11 +4,14 @@ import { useState, useRef, SVGProps, + useCallback, + useMemo, + memo, } from "react"; import { useTranslation } from "react-i18next"; import { useLockFn } from "ahooks"; import { Typography } from "@mui/material"; -import { useForm } from "react-hook-form"; +import { useForm, UseFormRegister } from "react-hook-form"; import { useVerge } from "@/hooks/use-verge"; import { BaseDialog, DialogRef, Notice } from "@/components/base"; import { isValidUrl } from "@/utils/helper"; @@ -53,6 +56,36 @@ type BackupFile = IWebDavFile & { allow_apply: boolean; }; +interface BackupTableProps { + datasource: BackupFile[]; + page: number; + rowsPerPage: number; + onPageChange: (event: any, newPage: number) => void; + onRowsPerPageChange: (event: React.ChangeEvent) => void; + totalCount: number; +} + +interface WebDAVConfigFormProps { + onSubmit: (e: React.FormEvent) => void; + initialValues: Partial; + urlRef: React.RefObject; + usernameRef: React.RefObject; + passwordRef: React.RefObject; + showPassword: boolean; + onShowPasswordClick: () => void; + webdavChanged: boolean; + webdavUrl: string | undefined | null; + webdavUsername: string | undefined | null; + webdavPassword: string | undefined | null; + handleBackup: () => Promise; + register: UseFormRegister; +} + +// 将魔法数字和配置提取为常量 +const DEFAULT_ROWS_PER_PAGE = 5; +const DATE_FORMAT = "YYYY-MM-DD_HH-mm-ss"; +const FILENAME_PATTERN = /\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}/; + export const BackupViewer = forwardRef((props, ref) => { const { t } = useTranslation(); const [open, setOpen] = useState(false); @@ -62,13 +95,16 @@ export const BackupViewer = forwardRef((props, ref) => { const [showPassword, setShowPassword] = useState(false); const usernameRef = useRef(null); const passwordRef = useRef(null); - const [backupFiles, setBackupFiles] = useState([]); - const [page, setPage] = useState(0); - const [rowsPerPage, setRowsPerPage] = useState(5); + const [backupState, setBackupState] = useState({ + files: [] as BackupFile[], + page: 0, + rowsPerPage: DEFAULT_ROWS_PER_PAGE, + isLoading: false, + }); const OS = getSystem(); const urlRef = useRef(null); - const [isLoading, setIsLoading] = useState(false); + const { register, handleSubmit, watch } = useForm({ defaultValues: { url: webdav_url, @@ -96,34 +132,36 @@ export const BackupViewer = forwardRef((props, ref) => { })); // Handle page change - const handleChangePage = ( - _: React.MouseEvent | null, - page: number - ) => { - console.log(page); - setPage(page); - }; + const handleChangePage = useCallback( + (_: React.MouseEvent | null, page: number) => { + setBackupState((prev) => ({ ...prev, page })); + }, + [] + ); // Handle rows per page change - const handleChangeRowsPerPage = (event: any) => { - setRowsPerPage(parseInt(event.target.value, 10)); - setPage(0); // Reset to the first page - }; + const handleChangeRowsPerPage = useCallback( + (event: React.ChangeEvent) => { + setBackupState((prev) => ({ + ...prev, + rowsPerPage: parseInt(event.target.value, 10), + page: 0, + })); + }, + [] + ); - const fetchAndSetBackupFiles = () => { - setIsLoading(true); // Assuming setIsLoading is defined in your component or context to manage loading state - - getAllBackupFiles() - .then((files: BackupFile[]) => { - console.log(files); - setBackupFiles(files); // Assuming setBackupFiles is a state setter function in your component or context - }) - .catch((e) => { - console.error(e); - }) - .finally(() => { - setIsLoading(false); - }); + const fetchAndSetBackupFiles = async () => { + try { + setBackupState((prev) => ({ ...prev, isLoading: true })); + const files = await getAllBackupFiles(); + setBackupState((prev) => ({ ...prev, files })); + } catch (error) { + console.error("Failed to fetch backup files:", error); + Notice.error(t("Failed to fetch backup files")); + } finally { + setBackupState((prev) => ({ ...prev, isLoading: false })); + } }; const checkForm = () => { @@ -132,21 +170,21 @@ export const BackupViewer = forwardRef((props, ref) => { const url = urlRef.current?.value; if (!url) { - Notice.error(t("Webdav url cannot be empty")); + Notice.error(t("WebDAV URL Required")); urlRef.current?.focus(); return; } else if (!isValidUrl(url)) { - Notice.error(t("Webdav address must be url")); + Notice.error(t("Invalid WebDAV URL")); urlRef.current?.focus(); return; } if (!username) { - Notice.error(t("Username cannot be empty")); + Notice.error(t("Username Required")); usernameRef.current?.focus(); return; } if (!password) { - Notice.error(t("Password cannot be empty")); + Notice.error(t("Password Required")); passwordRef.current?.focus(); return; } @@ -154,7 +192,7 @@ export const BackupViewer = forwardRef((props, ref) => { const submit = async (data: IWebDavConfig) => { checkForm(); - setIsLoading(true); + setBackupState((prev) => ({ ...prev, isLoading: true })); await saveWebdavConfig(data.url, data.username, data.password) .then(() => { mutateVerge( @@ -165,36 +203,34 @@ export const BackupViewer = forwardRef((props, ref) => { }, false ); - Notice.success(t("Webdav Config Saved Successfully"), 1500); + Notice.success(t("WebDAV Config Saved")); }) .catch((e) => { - Notice.error(t("Webdav Config Save Failed", { error: e }), 3000); + Notice.error(t("WebDAV Config Save Failed", { error: e }), 3000); }) .finally(() => { - setIsLoading(false); + setBackupState((prev) => ({ ...prev, isLoading: false })); fetchAndSetBackupFiles(); }); }; - const handleClickShowPassword = () => { - setShowPassword(!showPassword); - }; + const handleClickShowPassword = useCallback(() => { + setShowPassword((prev) => !prev); + }, []); const handleBackup = useLockFn(async () => { - checkForm(); - setIsLoading(true); - await createWebdavBackup() - .then(() => { - Notice.success(t("Backup Successfully"), 1500); - }) - .finally(() => { - setIsLoading(false); - fetchAndSetBackupFiles(); - }) - .catch((e) => { - console.log(e, "backup failed"); - Notice.error(t("Backup Failed", { error: e }), 3000); - }); + try { + checkForm(); + setBackupState((prev) => ({ ...prev, isLoading: true })); + await createWebdavBackup(); + Notice.success(t("Backup Created")); + await fetchAndSetBackupFiles(); + } catch (error) { + console.error("Backup failed:", error); + Notice.error(t("Backup Failed", { error })); + } finally { + setBackupState((prev) => ({ ...prev, isLoading: false })); + } }); const getAllBackupFiles = async () => { @@ -202,10 +238,8 @@ export const BackupViewer = forwardRef((props, ref) => { return files .map((file) => { const platform = file.filename.split("-")[0]; - const fileBackupTimeStr = file.filename.match( - /\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}/ - )!; - const backupTime = dayjs(fileBackupTimeStr[0], "YYYY-MM-DD_HH-mm-ss"); + const fileBackupTimeStr = file.filename.match(FILENAME_PATTERN)!; + const backupTime = dayjs(fileBackupTimeStr[0], DATE_FORMAT); const allowApply = OS === platform; return { ...file, @@ -217,10 +251,17 @@ export const BackupViewer = forwardRef((props, ref) => { .sort((a, b) => (a.backup_time.isAfter(b.backup_time) ? -1 : 1)); }; - const datasource = backupFiles.slice( - page * rowsPerPage, - page * rowsPerPage + rowsPerPage - ); + const datasource = useMemo(() => { + return backupState.files.slice( + backupState.page * backupState.rowsPerPage, + backupState.page * backupState.rowsPerPage + backupState.rowsPerPage + ); + }, [backupState.files, backupState.page, backupState.rowsPerPage]); + + const onFormSubmit = (e: React.FormEvent) => { + e.preventDefault(); + handleSubmit(submit)(e); + }; return ( ((props, ref) => { onCancel={() => setOpen(false)} > - + -
- - - - {/* WebDAV Server Address */} - - - - - {/* Username and Password */} - - - - - - - {showPassword ? ( - - ) : ( - - )} - - - ), - }} - /> - - - - - - - {webdavChanged || - webdav_url === null || - webdav_username == null || - webdav_password == null ? ( - - ) : ( - - )} - - - -
+ - - - - - 文件名称 - 时间 - 操作 - - - - {datasource.length > 0 ? ( - datasource?.map((file, index) => ( - - - {file.platform === "windows" ? ( - - ) : file.platform === "linux" ? ( - - ) : ( - - )} - {file.filename} - - - {file.backup_time.fromNow()} - - - - - - - - - - - - - - - )) - ) : ( - - - - - 暂无备份 - - - - - )} - -
- -
+ handleChangePage(null, page)} + onRowsPerPageChange={handleChangeRowsPerPage} + totalCount={backupState.files.length} + />
); }); +const BackupTable = memo( + ({ + datasource, + page, + rowsPerPage, + onPageChange, + onRowsPerPageChange, + totalCount, + }: BackupTableProps) => { + const { t } = useTranslation(); + return ( + + + + + {t("Filename")} + {t("Time")} + {t("Actions")} + + + + {datasource.length > 0 ? ( + datasource?.map((file, index) => ( + + + {file.platform === "windows" ? ( + + ) : file.platform === "linux" ? ( + + ) : ( + + )} + {file.filename} + + + {file.backup_time.fromNow()} + + + + + + + + + + + + + + )) + ) : ( + + + + + {t("No Backups")} + + + + + )} + +
+ +
+ ); + } +); + +const WebDAVConfigForm = memo( + ({ + onSubmit, + initialValues, + urlRef, + usernameRef, + passwordRef, + showPassword, + onShowPasswordClick, + webdavChanged, + webdavUrl, + webdavUsername, + webdavPassword, + handleBackup, + register, + }: WebDAVConfigFormProps) => { + const { t } = useTranslation(); + return ( +
+ + + + + + + + + + + + + {showPassword ? : } + + + ), + }} + /> + + + + + + {webdavChanged || + webdavUrl === null || + webdavUsername == null || + webdavPassword == null ? ( + + ) : ( + + )} + + + +
+ ); + } +); + export function LinuxIcon(props: SVGProps) { return (