From 9f0d3fb07ae01190544c862d2c370f60c38784a8 Mon Sep 17 00:00:00 2001 From: cyfung1031 <44498510+cyfung1031@users.noreply.github.com> Date: Sat, 7 Feb 2026 21:07:23 +0900 Subject: [PATCH 1/3] feat: ScriptEditor Edit Conflict Detection --- src/app/service/service_worker/client.ts | 18 ++-- src/app/service/service_worker/script.ts | 23 +++-- .../options/routes/script/ScriptEditor.tsx | 95 +++++++++++++------ 3 files changed, 91 insertions(+), 45 deletions(-) diff --git a/src/app/service/service_worker/client.ts b/src/app/service/service_worker/client.ts index 28e98d593..0f08d825c 100644 --- a/src/app/service/service_worker/client.ts +++ b/src/app/service/service_worker/client.ts @@ -11,7 +11,13 @@ import { type FileSystemType } from "@Packages/filesystem/factory"; import { type ResourceBackup } from "@App/pkg/backup/struct"; import { type VSCodeConnect } from "../offscreen/vscode-connect"; import { type ScriptInfo } from "@App/pkg/utils/scriptInstall"; -import type { ScriptService, TCheckScriptUpdateOption, TOpenBatchUpdatePageOption } from "./script"; +import type { + ScriptService, + TCheckScriptUpdateOption, + TOpenBatchUpdatePageOption, + TScriptInstallParam, + TScriptInstallReturn, +} from "./script"; import { encodeRValue, type TKeyValuePair } from "@App/pkg/utils/message_value"; import { type TSetValuesParams } from "./value"; @@ -40,15 +46,9 @@ export class ScriptClient extends Client { return this.do<[boolean, ScriptInfo, { byWebRequest?: boolean }]>("getInstallInfo", uuid); } - install(params: { - script: Script; - code: string; - upsertBy?: InstallSource; - createtime?: number; - updatetime?: number; - }): Promise<{ update: boolean }> { + install(params: TScriptInstallParam): Promise { if (!params.upsertBy) params.upsertBy = "user"; - return this.doThrow("install", { ...params }); + return this.doThrow("install", { ...params } satisfies TScriptInstallParam); } // delete(uuid: string) { diff --git a/src/app/service/service_worker/script.ts b/src/app/service/service_worker/script.ts index 59873e093..12f3e1a2b 100644 --- a/src/app/service/service_worker/script.ts +++ b/src/app/service/service_worker/script.ts @@ -55,6 +55,19 @@ export type TCheckScriptUpdateOption = Partial< export type TOpenBatchUpdatePageOption = { q: string; dontCheckNow: boolean }; +export type TScriptInstallParam = { + script: Script; + code: string; + upsertBy?: InstallSource; + createtime?: number; // Import 用 + updatetime?: number; // Import 用 +}; + +export type TScriptInstallReturn = { + update: boolean; + updatetime: number | undefined; +}; + export class ScriptService { logger: Logger; scriptCodeDAO: ScriptCodeDAO = new ScriptCodeDAO(); @@ -371,13 +384,7 @@ export class ScriptService { } // 安装脚本 / 更新腳本 - async installScript(param: { - script: Script; - code: string; - upsertBy?: InstallSource; - createtime?: number; - updatetime?: number; - }) { + async installScript(param: TScriptInstallParam): Promise { param.upsertBy = param.upsertBy || "user"; const { script, upsertBy, createtime, updatetime } = param; // 删 storage cache @@ -424,7 +431,7 @@ export class ScriptService { // Runtime 會負責更新 CompiledResource this.publishInstallScript(script, { update, upsertBy }); - return { update }; + return { update, updatetime: script.updatetime }; }) .catch((e: any) => { logger.error("install error", Logger.E(e)); diff --git a/src/pages/options/routes/script/ScriptEditor.tsx b/src/pages/options/routes/script/ScriptEditor.tsx index 08120b226..ba932b82a 100644 --- a/src/pages/options/routes/script/ScriptEditor.tsx +++ b/src/pages/options/routes/script/ScriptEditor.tsx @@ -1,7 +1,7 @@ import type { Script } from "@App/app/repo/scripts"; import { SCRIPT_TYPE_NORMAL, ScriptCodeDAO, ScriptDAO } from "@App/app/repo/scripts"; import CodeEditor from "@App/pages/components/CodeEditor"; -import React, { useCallback, useEffect, useMemo, useState } from "react"; +import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useParams, useSearchParams } from "react-router-dom"; import type { editor } from "monaco-editor"; import { KeyCode, KeyMod } from "monaco-editor"; @@ -31,18 +31,18 @@ type HotKey = { id: string; title: string; hotKey: number; - action: (script: Script, codeEditor: editor.IStandaloneCodeEditor) => void; + action: (script: Script, codeEditor: editor.ICodeEditor) => void; }; const Editor: React.FC<{ id: string; - script: Script; + getScript: (uuid: string) => Script | undefined; code: string; hotKeys: HotKey[]; - callbackEditor: (e: editor.IStandaloneCodeEditor) => void; + callbackEditor: (e: editor.ICodeEditor) => void; onChange: (code: string) => void; className: string; -}> = ({ id, script, code, hotKeys, callbackEditor, onChange, className }) => { +}> = ({ id, getScript, code, hotKeys, callbackEditor, onChange, className }) => { const [node, setNode] = useState<{ editor: editor.IStandaloneCodeEditor }>(); const ref = useCallback<(node: { editor: editor.IStandaloneCodeEditor }) => void>( (inlineNode) => { @@ -59,7 +59,7 @@ const Editor: React.FC<{ // @ts-ignore if (!node.editor.uuid) { // @ts-ignore - node.editor.uuid = script.uuid; + node.editor.uuid = id; } hotKeys.forEach((item) => { node.editor.addAction({ @@ -67,8 +67,10 @@ const Editor: React.FC<{ label: item.title, keybindings: [item.hotKey], run(editor) { - // @ts-ignore - item.action(script, editor); + const script = getScript(id); + if (script) { + item.action(script, editor); + } }, }); }); @@ -83,20 +85,20 @@ const Editor: React.FC<{ }; const WarpEditor = React.memo(Editor, (prev, next) => { - return prev.script.uuid === next.script.uuid; + return prev.id === next.id; }); type EditorMenu = { title: string; tooltip?: string; - action?: (script: Script, e: editor.IStandaloneCodeEditor) => void; + action?: (script: Script, e: editor.ICodeEditor) => void; items?: { id: string; title: string; tooltip?: string; hotKey?: number; hotKeyString?: string; - action: (script: Script, e: editor.IStandaloneCodeEditor) => void; + action: (script: Script, e: editor.ICodeEditor) => void; }[]; }; @@ -180,21 +182,30 @@ const popstate = () => { return false; }; +type EditorState = { + script: Script; + code: string; + active: boolean; + hotKeys: HotKey[]; + editor?: editor.ICodeEditor; + isChanged: boolean; +}; + function ScriptEditor() { const [visible, setVisible] = useState<{ [key: string]: boolean }>({}); const [searchKeyword, setSearchKeyword] = useState(""); const [showSearchInput, setShowSearchInput] = useState(false); const [modal, contextHolder] = Modal.useModal(); - const [editors, setEditors] = useState< - { - script: Script; - code: string; - active: boolean; - hotKeys: HotKey[]; - editor?: editor.IStandaloneCodeEditor; - isChanged: boolean; - }[] - >([]); + const [editors, setEditors] = useState([]); + const editorStateRef = useRef([]); // 取出资料用 + // getScript 是稳定不变的 state + const getScript = useCallback((uuid: string) => { + return editorStateRef.current!.find((e) => e.script.uuid === uuid)?.script; + }, []); + // 更新取出资料用的 editorStateRef + useEffect(() => { + editorStateRef.current = editors; + }, [editors]); const [scriptList, setScriptList] = useState([]); const [currentScript, setCurrentScript] = useState