feat: complete stage 3 settings refactor
This commit is contained in:
187
src/hooks/useImportExport.ts
Normal file
187
src/hooks/useImportExport.ts
Normal 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,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user