2025-04-18 18:01:05 +08:00

333 lines
12 KiB
TypeScript

import { useEffect, useRef, useState } from "react";
import { Button, Card, Checkbox, Drawer, Empty, Input, List, Message, Modal, Space } from "@arco-design/web-react";
import Title from "@arco-design/web-react/es/Typography/title";
import { formatUnixTime } from "@App/pkg/utils/utils";
import FileSystemParams from "@App/pages/components/FileSystemParams";
import { IconQuestionCircleFill } from "@arco-design/web-react/icon";
import { RefInputType } from "@arco-design/web-react/es/Input/interface";
import { useTranslation } from "react-i18next";
import FileSystemFactory, { FileSystemType } from "@Packages/filesystem/factory";
import { File, FileReader } from "@Packages/filesystem/filesystem";
import { message, systemConfig } from "@App/pages/store/global";
import { SynchronizeClient } from "@App/app/service/service_worker/client";
import { set } from "node_modules/yaml/dist/schema/yaml-1.1/set";
const synchronizeClient = new SynchronizeClient(message);
function Tools() {
const [loading, setLoading] = useState<{ [key: string]: boolean }>({});
const fileRef = useRef<HTMLInputElement>(null);
const [fileSystemType, setFilesystemType] = useState<FileSystemType>("webdav");
const [fileSystemParams, setFilesystemParam] = useState<{
[key: string]: any;
}>({});
const [vscodeUrl, setVscodeUrl] = useState<string>("");
const [vscodeReconnect, setVscodeReconnect] = useState<boolean>(false);
const [backupFileList, setBackupFileList] = useState<File[]>([]);
const vscodeRef = useRef<RefInputType>(null);
const { t } = useTranslation();
useEffect(() => {
// 获取配置
const loadConfig = async () => {
const [backup, vscodeUrl] = await Promise.all([
systemConfig.getBackup(),
systemConfig.getVscodeUrl(),
systemConfig.getVscodeReconnect(),
]);
setFilesystemType(backup.filesystem);
setFilesystemParam(backup.params[backup.filesystem] || {});
setVscodeUrl(vscodeUrl);
setVscodeReconnect(systemConfig.vscodeReconnect);
};
loadConfig();
}, []);
return (
<Space
className="tools"
direction="vertical"
style={{
width: "100%",
height: "100%",
overflow: "auto",
position: "relative",
}}
>
<Card className="backup" title={t("backup")} bordered={false}>
<Space direction="vertical">
<Title heading={6}>{t("local")}</Title>
<Space>
<input type="file" ref={fileRef} style={{ display: "none" }} accept=".zip" />
<Button
type="primary"
loading={loading.local}
onClick={async () => {
setLoading((prev) => ({ ...prev, local: true }));
await synchronizeClient.export();
setLoading((prev) => ({ ...prev, local: false }));
}}
>
{t("export_file")}
</Button>
<Button
type="primary"
onClick={() => {
const el = fileRef.current!;
el.onchange = async () => {
const { files } = el;
if (!files) {
return;
}
const file = files[0];
if (!file) {
return;
}
try {
await synchronizeClient.openImportWindow(file.name, file);
Message.success(t("select_import_script")!);
} catch (e) {
Message.error(`${t("import_error")}: ${e}`);
}
};
el.click();
}}
>
{t("import_file")}
</Button>
</Space>
<Title heading={6}>{t("cloud")}</Title>
<FileSystemParams
preNode={t("backup_to")}
onChangeFileSystemType={(type) => {
setFilesystemType(type);
}}
onChangeFileSystemParams={(params) => {
setFilesystemParam(params);
}}
actionButton={[
<Button
key="backup"
type="primary"
loading={loading.cloud}
onClick={() => {
// Store parameters
const params = { ...fileSystemParams };
params[fileSystemType] = fileSystemParams;
systemConfig.setBackup({
filesystem: fileSystemType,
params,
});
setLoading((prev) => ({ ...prev, cloud: true }));
Message.info(t("preparing_backup")!);
synchronizeClient
.backupToCloud(fileSystemType, fileSystemParams)
.then(() => {
Message.success(t("backup_success")!);
})
.catch((e) => {
Message.error(`${t("backup_failed")}: ${e}`);
})
.finally(() => {
setLoading((prev) => ({ ...prev, cloud: false }));
});
}}
>
{t("backup")}
</Button>,
<Button
key="list"
type="primary"
onClick={async () => {
let fs = await FileSystemFactory.create(fileSystemType, fileSystemParams);
try {
fs = await fs.openDir("ScriptCat");
let list = await fs.list();
list.sort((a, b) => b.updatetime - a.updatetime);
// Filter non-zip files
list = list.filter((file) => file.name.endsWith(".zip"));
if (list.length === 0) {
Message.info(t("no_backup_files")!);
return;
}
setBackupFileList(list);
} catch (e) {
Message.error(`${t("get_backup_files_failed")}: ${e}`);
}
}}
>
{t("backup_list")}
</Button>,
]}
fileSystemType={fileSystemType}
fileSystemParams={fileSystemParams}
/>
<Drawer
width={400}
title={
<div className="flex flex-row justify-between w-full gap-10">
<span>{t("backup_list")}</span>
<Button
type="secondary"
size="mini"
onClick={async () => {
let fs = await FileSystemFactory.create(fileSystemType, fileSystemParams);
try {
fs = await fs.openDir("ScriptCat");
const url = await fs.getDirUrl();
if (url) {
window.open(url, "_black");
}
} catch (e) {
Message.error(`${t("get_backup_dir_url_failed")}: ${e}`);
}
}}
>
{t("open_backup_dir")}
</Button>
</div>
}
visible={backupFileList.length !== 0}
onOk={() => {
setBackupFileList([]);
}}
onCancel={() => {
setBackupFileList([]);
}}
>
<List
bordered={false}
dataSource={backupFileList}
render={(item: File) => (
<List.Item key={item.name}>
<List.Item.Meta title={item.name} description={formatUnixTime(item.updatetime / 1000)} />
<Space className="w-full justify-end">
<Button
type="primary"
size="small"
onClick={async () => {
Message.info(t("pulling_data_from_cloud")!);
let fs = await FileSystemFactory.create(fileSystemType, fileSystemParams);
let file: FileReader;
let data: Blob;
try {
fs = await fs.openDir("ScriptCat");
file = await fs.open(item);
data = (await file.read("blob")) as Blob;
} catch (e) {
Message.error(`${t("pull_failed")}: ${e}`);
return;
}
synchronizeClient
.openImportWindow(item.name, data)
.then(() => {
Message.success(t("select_import_script")!);
})
.then((e) => {
Message.error(`${t("import_error")}${e}`);
});
}}
>
{t("restore")}
</Button>
<Button
type="primary"
status="danger"
size="small"
onClick={() => {
Modal.confirm({
title: t("confirm_delete"),
content: `${t("confirm_delete_backup_file")}${item.name}?`,
onOk: async () => {
let fs = await FileSystemFactory.create(fileSystemType, fileSystemParams);
try {
fs = await fs.openDir("ScriptCat");
await fs.delete(item.name);
setBackupFileList(backupFileList.filter((i) => i.name !== item.name));
Message.success(t("delete_success")!);
} catch (e) {
Message.error(`${t("delete_failed")}${e}`);
}
},
});
}}
>
{t("delete")}
</Button>
</Space>
</List.Item>
)}
/>
</Drawer>
<Title heading={6}>{t("backup_strategy")}</Title>
<Empty description={t("under_construction")} />
</Space>
</Card>
<Card
title={
<>
<span>{t("development_debugging")}</span>
<Button
type="text"
style={{
height: 24,
}}
icon={
<IconQuestionCircleFill
style={{
margin: 0,
}}
/>
}
href="https://www.bilibili.com/video/BV16q4y157CP"
target="_blank"
iconOnly
/>
</>
}
bordered={false}
>
<Space direction="vertical">
<Title heading={6}>{t("vscode_url")}</Title>
<Input
ref={vscodeRef}
value={vscodeUrl}
onChange={(value) => {
setVscodeUrl(value);
}}
/>
<Checkbox
checked={vscodeReconnect}
onChange={(checked) => {
setVscodeReconnect(checked);
}}
>
{t("auto_connect_vscode_service")}
</Checkbox>
<Button
type="primary"
onClick={() => {
systemConfig.setVscodeUrl(vscodeUrl);
systemConfig.setVscodeReconnect(vscodeReconnect);
const ctrl = IoC.instance(SystemController) as SystemController;
ctrl
.connectVSCode()
.then(() => {
Message.success(t("connection_success")!);
})
.catch((e) => {
Message.error(`${t("connection_failed")}: ${e}`);
});
}}
>
{t("connect")}
</Button>
</Space>
</Card>
</Space>
);
}
export default Tools;