feat: complete stage 3 settings refactor

This commit is contained in:
Jason
2025-10-16 11:40:02 +08:00
parent b88eb88608
commit 2b45af118f
17 changed files with 1828 additions and 1121 deletions

View File

@@ -0,0 +1,187 @@
import { useCallback, useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import { settingsApi } from "@/lib/api";
export type ImportStatus = "idle" | "importing" | "success" | "error";
export interface UseImportExportOptions {
onImportSuccess?: () => void | Promise<void>;
}
export interface UseImportExportResult {
selectedFile: string;
status: ImportStatus;
errorMessage: string | null;
backupId: string | null;
isImporting: boolean;
selectImportFile: () => Promise<void>;
clearSelection: () => void;
importConfig: () => Promise<void>;
exportConfig: () => Promise<void>;
resetStatus: () => void;
}
export function useImportExport(
options: UseImportExportOptions = {},
): UseImportExportResult {
const { t } = useTranslation();
const { onImportSuccess } = options;
const [selectedFile, setSelectedFile] = useState("");
const [status, setStatus] = useState<ImportStatus>("idle");
const [errorMessage, setErrorMessage] = useState<string | null>(null);
const [backupId, setBackupId] = useState<string | null>(null);
const [isImporting, setIsImporting] = useState(false);
const successTimerRef = useRef<number | null>(null);
useEffect(() => {
return () => {
if (successTimerRef.current) {
window.clearTimeout(successTimerRef.current);
}
};
}, []);
const clearSelection = useCallback(() => {
setSelectedFile("");
setStatus("idle");
setErrorMessage(null);
setBackupId(null);
}, []);
const selectImportFile = useCallback(async () => {
try {
const filePath = await settingsApi.openFileDialog();
if (filePath) {
setSelectedFile(filePath);
setStatus("idle");
setErrorMessage(null);
}
} catch (error) {
console.error("[useImportExport] Failed to open file dialog", error);
toast.error(
t("settings.selectFileFailed", {
defaultValue: "选择文件失败",
}),
);
}
}, [t]);
const importConfig = useCallback(async () => {
if (!selectedFile) {
toast.error(
t("settings.selectFileFailed", {
defaultValue: "请选择有效的配置文件",
}),
);
return;
}
if (isImporting) return;
setIsImporting(true);
setStatus("importing");
setErrorMessage(null);
try {
const result = await settingsApi.importConfigFromFile(selectedFile);
if (result.success) {
setBackupId(result.backupId ?? null);
setStatus("success");
toast.success(
t("settings.importSuccess", {
defaultValue: "配置导入成功",
}),
);
successTimerRef.current = window.setTimeout(() => {
void onImportSuccess?.();
}, 1500);
} else {
setStatus("error");
const message =
result.message ||
t("settings.configCorrupted", {
defaultValue: "配置文件已损坏或格式不正确",
});
setErrorMessage(message);
toast.error(message);
}
} catch (error) {
console.error("[useImportExport] Failed to import config", error);
setStatus("error");
const message =
error instanceof Error ? error.message : String(error ?? "");
setErrorMessage(message);
toast.error(
t("settings.importFailedError", {
defaultValue: "导入配置失败: {{message}}",
message,
}),
);
} finally {
setIsImporting(false);
}
}, [isImporting, onImportSuccess, selectedFile, t]);
const exportConfig = useCallback(async () => {
try {
const defaultName = `cc-switch-config-${
new Date().toISOString().split("T")[0]
}.json`;
const destination = await settingsApi.saveFileDialog(defaultName);
if (!destination) {
toast.error(
t("settings.selectFileFailed", {
defaultValue: "选择保存位置失败",
}),
);
return;
}
const result = await settingsApi.exportConfigToFile(destination);
if (result.success) {
const displayPath = result.filePath ?? destination;
toast.success(
t("settings.configExported", {
defaultValue: "配置已导出",
}) + `\n${displayPath}`,
);
} else {
toast.error(
t("settings.exportFailed", {
defaultValue: "导出配置失败",
}) + (result.message ? `: ${result.message}` : ""),
);
}
} catch (error) {
console.error("[useImportExport] Failed to export config", error);
toast.error(
t("settings.exportFailedError", {
defaultValue: "导出配置失败: {{message}}",
message: error instanceof Error ? error.message : String(error ?? ""),
}),
);
}
}, [t]);
const resetStatus = useCallback(() => {
setStatus("idle");
setErrorMessage(null);
setBackupId(null);
}, []);
return {
selectedFile,
status,
errorMessage,
backupId,
isImporting,
selectImportFile,
clearSelection,
importConfig,
exportConfig,
resetStatus,
};
}