diff --git a/src/pages/confirm/main.tsx b/src/pages/confirm/main.tsx index c46c85b52..ccdd40f27 100644 --- a/src/pages/confirm/main.tsx +++ b/src/pages/confirm/main.tsx @@ -19,12 +19,14 @@ const loggerCore = new LoggerCore({ loggerCore.logger().debug("confirm page start"); +const Root = ( + + + + + +); + ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( - - - - - - - + process.env.NODE_ENV === "development" ? {Root} : Root ); diff --git a/src/pages/import/App.tsx b/src/pages/import/App.tsx index 57ea41c21..07f6c625e 100644 --- a/src/pages/import/App.tsx +++ b/src/pages/import/App.tsx @@ -1,102 +1,260 @@ -import React, { useEffect, useState } from "react"; +import React, { useEffect, useState, useCallback } from "react"; import { Button, Card, Checkbox, Divider, List, Message, Space, Switch, Typography } from "@arco-design/web-react"; import { useTranslation } from "react-i18next"; // 导入react-i18next的useTranslation钩子 import JSZip from "jszip"; -import { ScriptBackupData, ScriptOptions, SubscribeBackupData } from "@App/pkg/backup/struct"; +import { ScriptOptions, ScriptData, SubscribeData } from "@App/pkg/backup/struct"; import { prepareScriptByCode } from "@App/pkg/utils/script"; -import { Script, SCRIPT_STATUS_DISABLE, SCRIPT_STATUS_ENABLE, ScriptDAO } from "@App/app/repo/scripts"; -import { Subscribe } from "@App/app/repo/subscribe"; +import { SCRIPT_STATUS_DISABLE, SCRIPT_STATUS_ENABLE, ScriptDAO } from "@App/app/repo/scripts"; import Cache from "@App/app/cache"; import CacheKey from "@App/app/cache_key"; import { parseBackupZipFile } from "@App/pkg/backup/utils"; -import { resourceClient, scriptClient, synchronizeClient, valueClient } from "../store/features/script"; +import { scriptClient, synchronizeClient, valueClient } from "../store/features/script"; +import { sleep } from "@App/pkg/utils/utils"; -type ScriptData = ScriptBackupData & { - script?: { script: Script; oldScript?: Script }; - install: boolean; - error?: string; -}; - -type SubscribeData = SubscribeBackupData & { - subscribe?: Subscribe; - install: boolean; -}; +const ScriptListItem = React.memo( + ({ + item, + index, + t, + onToggle, + onStatusToggle, + }: { + item: ScriptData; + index: number; + t: (a: string) => string, + onToggle: (index: number) => () => void; + onStatusToggle: (index: number, checked: boolean) => void; + }) => { + return ( +
+ + + {item.script?.script?.name || item.error || t("unknown")} + + {t("author")}: {item.script?.script?.metadata.author?.[0]} + {t("description")}: {item.script?.script?.metadata.description?.[0]} + {t("source")}: {item.options?.meta.file_url || t("local_creation")} + + {t("operation")}: {(item.install && (item.script?.oldScript ? t("update") : t("add_new"))) || + (item.error ? `${t("error")}: ${item.options?.meta.name} - ${item.options?.meta.uuid}` : t("no_operation"))} + + +
+ {t("enable_script")} +
+ onStatusToggle(index, checked)} + /> +
+
+
+ ); + } +); function App() { const [scripts, setScripts] = useState([]); - const [subscribes, setSubscribe] = useState([]); + const [subscribes, setSubscribes] = useState([]); const [selectAll, setSelectAll] = useState([true, true]); const [installNum, setInstallNum] = useState([0, 0]); const [loading, setLoading] = useState(true); - const url = new URL(window.location.href); - const uuid = url.searchParams.get("uuid") || ""; const { t } = useTranslation(); // 使用useTranslation钩子获取翻译函数 - useEffect(() => { - Cache.getInstance() - .get(CacheKey.importFile(uuid)) - .then(async (resp: { filename: string; url: string }) => { - // 使用缓存优化脚本加载速度 - const scriptDAO = new ScriptDAO(); - scriptDAO.enableCache(); + const fetchData = async () => { + try { + + const url = new URL(window.location.href); + const uuid = url.searchParams.get("uuid") || ""; + const resp: { filename: string; url: string } = await Cache.getInstance().get(CacheKey.importFile(uuid)); + const filedata = await fetch(resp.url).then((resp) => resp.blob()); + const zip = await JSZip.loadAsync(filedata); + const backData = await parseBackupZipFile(zip); + const backDataScript = backData.script as ScriptData[]; - const filedata = await fetch(resp.url).then((resp) => resp.blob()); - const zip = await JSZip.loadAsync(filedata); - const backData = await parseBackupZipFile(zip); - const backDataScript = backData.script as ScriptData[]; - setScripts(backDataScript); - // 获取各个脚本现在已经存在的信息 - const result = await Promise.all( - backDataScript.map(async (item) => { - try { - const prepareScript = await prepareScriptByCode( - item.code, - item.options?.meta.file_url || "", - item.options?.meta.sc_uuid || undefined, - true, - scriptDAO - ); - item.script = prepareScript; - } catch (e: any) { - item.error = e.toString(); - return item; - } - if (!item.options) { - item.options = { - options: {} as ScriptOptions, - meta: { - name: item.script?.script.name, - // 此uuid是对tm的兼容处理 - uuid: item.script?.script.uuid, - sc_uuid: item.script?.script.uuid, - file_url: item.script?.script.downloadUrl || "", - modified: item.script?.script.createtime, - subscribe_url: item.script?.script.subscribeUrl, - }, - settings: { - enabled: - item.enabled === false - ? false - : !(item.script?.script.metadata.background || item.script?.script.metadata.crontab), - position: item.script?.script.sort, - }, - }; - } - item.script.script.sort = item.options.settings.position || 0; - item.script.script.status = - item.enabled !== false && item.options.settings.enabled ? SCRIPT_STATUS_ENABLE : SCRIPT_STATUS_DISABLE; - item.install = true; + // 使用缓存优化脚本加载速度 + const scriptDAO = new ScriptDAO(); + scriptDAO.enableCache(); + + // setScripts(backDataScript); + // 获取各个脚本现在已经存在的信息 + await Promise.all( + backDataScript.map(async (item) => { + try { + const prepareScript = await prepareScriptByCode( + item.code, + item.options?.meta.file_url || "", + item.options?.meta.sc_uuid || undefined, + true, + scriptDAO + ); + item.script = prepareScript; + } catch (e: any) { + item.error = e.toString(); return item; - }) - ); - setScripts(result); - setSelectAll([true, true]); - setLoading(false); - }) - .catch((e) => { - Message.error(`获取导入文件失败: ${e}`); + } + if (!item.options) { + item.options = { + options: {} as ScriptOptions, + meta: { + name: item.script?.script.name, + // 此uuid是对tm的兼容处理 + uuid: item.script?.script.uuid, + sc_uuid: item.script?.script.uuid, + file_url: item.script?.script.downloadUrl || "", + modified: item.script?.script.createtime, + subscribe_url: item.script?.script.subscribeUrl, + }, + settings: { + enabled: + item.enabled === false + ? false + : !(item.script?.script.metadata.background || item.script?.script.metadata.crontab), + position: item.script?.script.sort, + }, + }; + } + item.script.script.sort = item.options.settings.position || 0; + item.script.script.status = + item.enabled !== false && item.options.settings.enabled ? SCRIPT_STATUS_ENABLE : SCRIPT_STATUS_DISABLE; + item.install = true; + return item; + }) + ); + const results = backDataScript.slice().sort((a, b) => { + const aName = a.script?.script?.name || ''; + const bName = b.script?.script?.name || ''; + if (aName && bName) return aName.localeCompare(bName); + return 0; }); + setScripts(results); + setSelectAll([true, true]); + setLoading(false); + } catch (e) { + Message.error(`获取导入文件失败: ${e}`); + } + }; + + useEffect(() => { + fetchData(); + }, []); + + const scriptImportAsync = async (item: ScriptData) => { + try { + // await sleep(1); + await scriptClient.install(item.script?.script!, item.code); + await Promise.all([ + (async () => { // 导入资源 + if (!item.requires || !item.resources || !item.requiresCss) return; + if (!item.requires[0] && !item.resources[0] && !item.requiresCss[0]) return; + await sleep(((Math.random() * 600) | 0) + 200); + await synchronizeClient.importResources( + item.script?.script.uuid, + item.requires, + item.resources, + item.requiresCss + ); + })(), + (async () => { // 导入数据 + const { data } = item.storage; + const entries = Object.entries(data); + if (entries.length === 0) return; + await sleep(((Math.random() * 600) | 0) + 200); + for (const [key, value] of entries) { + await valueClient.setScriptValue(item.script?.script.uuid!, key, value); + } + })() + ]); + // await sleep(1); + setInstallNum((prev) => [prev[0] + 1, prev[1]]); + } catch (e) { + // 跳過失敗 + } + } + + const importScripts = useCallback(async (scripts: ScriptData[]) => { + const promises: Promise[] = []; + for (const item of scripts) { + if (item.install && !item.error) { + promises.push(scriptImportAsync(item)); + } + } + return Promise.all(promises); + }, []); + + const importButtonClick = useCallback((scripts: ScriptData[]) => async () => { + setInstallNum((prev) => [0, prev[1]]); + setLoading(true); + await importScripts(scripts); + setLoading(false); + Message.success(t("import_success")!); + }, []); + + const handleSelectAllScripts = useCallback(() => { + setSelectAll((prev) => { + const newValue = !prev[0]; + setScripts((prevScripts) => prevScripts.map((script) => ({ ...script, install: newValue }))); + return [newValue, prev[1]]; + }); }, []); + + const handleSelectAllSubscribes = useCallback(() => { + setSelectAll((prev) => { + const newValue = !prev[1]; + setSubscribes((prevSubscribes) => prevSubscribes.map((subscribe) => ({ ...subscribe, install: newValue }))); + return [prev[0], newValue]; + }); + }, []); + + const handleScriptToggle = useCallback((index: number) => { + setScripts((prevScripts) => { + const newScripts = [...prevScripts]; + newScripts[index] = { ...newScripts[index], install: !newScripts[index].install }; + setSelectAll((prev) => [newScripts.every((script) => script.install), prev[1]]); + return newScripts; + }); + }, []); + + const handleScriptToggleClick = useCallback((index: number) => () => { + handleScriptToggle(index); + }, [handleScriptToggle]); + + const handleSubscribeToggle = useCallback((index: number) => { + setSubscribes((prevSubscribes) => { + const newSubscribes = [...prevSubscribes]; + newSubscribes[index] = { ...newSubscribes[index], install: !newSubscribes[index].install }; + setSelectAll((prev) => [prev[0], newSubscribes.every((subscribe) => subscribe.install)]); + return newSubscribes; + }); + }, []); + + const handleScriptStatusToggle = useCallback((index: number, checked: boolean) => { + setScripts((prevScripts) => { + const newScripts = [...prevScripts]; + newScripts[index] = { + ...newScripts[index], + script: { + ...newScripts[index].script!, + script: { + ...newScripts[index].script!.script, + status: checked ? SCRIPT_STATUS_ENABLE : SCRIPT_STATUS_DISABLE, + }, + }, + }; + return newScripts; + }); + }, []); + return (
@@ -105,35 +263,7 @@ function App() { @@ -143,18 +273,7 @@ function App() { {t("select_scripts_to_import")}:{" "} - { - setScripts((prev) => { - setSelectAll([!selectAll[0], selectAll[1]]); - return prev.map((item) => { - item.install = !selectAll[0]; - return item; - }); - }); - }} - > + {t("select_all")} @@ -162,102 +281,30 @@ function App() { {t("select_subscribes_to_import")}:{" "} - { - setSubscribe((prev) => { - setSelectAll([selectAll[0], !selectAll[1]]); - return prev.map((item) => { - item.install = !selectAll[1]; - return item; - }); - }); - }} - > + {t("select_all")} {t("subscribe_import_progress")}: {installNum[1]}/{subscribes.length} - ( -
{ - const install = item.install; - setScripts((prev) => { - prev[index].install = !install; - return [...prev]; - }); - }} - > - - - {item.script?.script?.name || item.error || t("unknown")} - - - {t("author")}: {item.script?.script?.metadata.author && item.script?.script?.metadata.author[0]} - - - {t("description")}:{" "} - {item.script?.script?.metadata.description && item.script?.script?.metadata.description[0]} - - - {t("source")}: {item.options?.meta.file_url || t("local_creation")} - - - {t("operation")}:{" "} - {(item.install && (item.script?.oldScript ? t("update") : t("add_new"))) || - (item.error - ? `${t("error")}: ${item.options?.meta.name} - ${item.options?.meta.uuid}` - : t("no_operation"))} - - -
- {t("enable_script")} -
- { - setScripts((prev) => { - prev[index].script!.script.status = checked ? SCRIPT_STATUS_ENABLE : SCRIPT_STATUS_DISABLE; - return [...prev]; - }); - }} - /> -
-
-
- )} - /> + {scripts.length > 0 && ( + ( + + )} + /> + )}
diff --git a/src/pages/import/main.tsx b/src/pages/import/main.tsx index b32295bf7..0b6bf0ea3 100644 --- a/src/pages/import/main.tsx +++ b/src/pages/import/main.tsx @@ -19,12 +19,14 @@ const loggerCore = new LoggerCore({ loggerCore.logger().debug("import page start"); +const Root = ( + + + + + +); + ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( - - - - - - - + process.env.NODE_ENV === "development" ? {Root} : Root ); diff --git a/src/pages/install/main.tsx b/src/pages/install/main.tsx index e769ad3fd..51643e1a7 100644 --- a/src/pages/install/main.tsx +++ b/src/pages/install/main.tsx @@ -19,12 +19,14 @@ const loggerCore = new LoggerCore({ loggerCore.logger().debug("install page start"); +const Root = ( + + + + + +); + ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( - - - - - - - + process.env.NODE_ENV === "development" ? {Root} : Root ); diff --git a/src/pages/options/main.tsx b/src/pages/options/main.tsx index 487b5eab9..ff8669784 100644 --- a/src/pages/options/main.tsx +++ b/src/pages/options/main.tsx @@ -29,12 +29,14 @@ loggerCore.logger().debug("options page start"); storeSubscribe(); +const Root = ( + + + + + +); + ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( - - - - - - - + process.env.NODE_ENV === "development" ? {Root} : Root ); diff --git a/src/pages/popup/main.tsx b/src/pages/popup/main.tsx index 421b275bb..923faf695 100644 --- a/src/pages/popup/main.tsx +++ b/src/pages/popup/main.tsx @@ -19,16 +19,14 @@ const loggerCore = new LoggerCore({ loggerCore.logger().debug("popup page start"); +const Root = ( + +
+ +
+
+) + ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( - - -
- -
-
-
+ process.env.NODE_ENV === "development" ? {Root} : Root ); diff --git a/src/pkg/backup/import.ts b/src/pkg/backup/import.ts index ab237a436..b1609b223 100644 --- a/src/pkg/backup/import.ts +++ b/src/pkg/backup/import.ts @@ -12,6 +12,7 @@ import { SubscribeBackupData, SubscribeOptionsFile, ValueStorage, + ScriptData, SubscribeData, } from "./struct"; import FileSystem, { File } from "@Packages/filesystem/filesystem"; @@ -225,8 +226,8 @@ export default class BackupImport { // 将map转化为数组 return ({ - script: Array.from(map.values()), - subscribe: Array.from(subscribe.values()), + script: (Array.from(map.values())), + subscribe: (Array.from(subscribe.values())), }); } diff --git a/src/pkg/backup/struct.ts b/src/pkg/backup/struct.ts index ffec859df..5e34d3056 100644 --- a/src/pkg/backup/struct.ts +++ b/src/pkg/backup/struct.ts @@ -1,3 +1,6 @@ +import { Script } from "@App/app/repo/scripts"; +import { Subscribe } from "@App/app/repo/subscribe"; + /* eslint-disable camelcase */ export type ResourceMeta = { @@ -80,6 +83,17 @@ export type ScriptBackupData = { enabled?: boolean; }; +export type ScriptData = ScriptBackupData & { + script?: { script: Script; oldScript?: Script }; + install: boolean; + error?: string; +}; + +export type SubscribeData = SubscribeBackupData & { + subscribe?: Subscribe; + install: boolean; +}; + export type SubscribeScript = { uuid: string; url: string;