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/locales/ach-UG/translation.json b/src/locales/ach-UG/translation.json index 25b2dbed3..980f4a4e9 100644 --- a/src/locales/ach-UG/translation.json +++ b/src/locales/ach-UG/translation.json @@ -410,6 +410,9 @@ "menu_expand_num_before": "crwdns8608:0crwdne8608:0", "menu_expand_num_after": "crwdns8610:0crwdne8610:0", "script_name_cannot_be_set_to_empty": "crwdns8612:0crwdne8612:0", + "edit_conflict": "Edit Conflict", + "confirm_override_when_edit_conflict": "This script was edited in another instance. Replacing it will overwrite those changes. Would you like to keep this version instead?", + "save_abort_when_edit_conflict": "The script was edited in another instance. Save aborted.", "eslint_config_format_error": "crwdns8614:0crwdne8614:0", "export_success": "crwdns8616:0crwdne8616:0", "get_backup_dir_url_failed": "crwdns8618:0crwdne8618:0", diff --git a/src/locales/de-DE/translation.json b/src/locales/de-DE/translation.json index 421eff062..4a3fec5ef 100644 --- a/src/locales/de-DE/translation.json +++ b/src/locales/de-DE/translation.json @@ -419,6 +419,9 @@ "menu_expand_num_before": "Wenn Menüelemente mehr als", "menu_expand_num_after": "sind, automatisch verbergen", "script_name_cannot_be_set_to_empty": "Skriptname kann nicht leer gesetzt werden", + "edit_conflict": "Bearbeitungskonflikt", + "confirm_override_when_edit_conflict": "Dieses Skript wurde in einer anderen Instanz bearbeitet. Beim Ersetzen werden diese Änderungen überschrieben. Möchten Sie stattdessen diese Version behalten?", + "save_abort_when_edit_conflict": "Dieses Skript wurde in einer anderen Instanz bearbeitet. Speichern abgebrochen.", "eslint_config_format_error": "eslint-Konfigurationsformat-Fehler", "export_success": "Export erfolgreich", "get_backup_dir_url_failed": "Backup-Verzeichnis-Adresse abrufen fehlgeschlagen", diff --git a/src/locales/en-US/translation.json b/src/locales/en-US/translation.json index 47d5e1b2f..1420a0701 100644 --- a/src/locales/en-US/translation.json +++ b/src/locales/en-US/translation.json @@ -419,6 +419,9 @@ "menu_expand_num_before": "Menu items more than", "menu_expand_num_after": "will be hidden.", "script_name_cannot_be_set_to_empty": "Script name cannot be empty", + "edit_conflict": "Edit Conflict", + "confirm_override_when_edit_conflict": "This script was edited in another instance. Replacing it will overwrite those changes. Would you like to keep this version instead?", + "save_abort_when_edit_conflict": "The script was edited in another instance. Save aborted.", "eslint_config_format_error": "eslint configuration format error", "export_success": "Dump success saved", "get_backup_dir_url_failed": "Failed to get backup directory address", diff --git a/src/locales/ja-JP/translation.json b/src/locales/ja-JP/translation.json index ab7c3cce7..64861f93e 100644 --- a/src/locales/ja-JP/translation.json +++ b/src/locales/ja-JP/translation.json @@ -419,6 +419,9 @@ "menu_expand_num_before": "メニュー項目が", "menu_expand_num_after": "個を超えると自動的に非表示", "script_name_cannot_be_set_to_empty": "スクリプト名を空に設定することはできません", + "edit_conflict": "編集の競合", + "confirm_override_when_edit_conflict": "このスクリプトは別のインスタンスで編集されています。置き換えるとその変更は上書きされます。このバージョンを保持しますか?", + "save_abort_when_edit_conflict": "このスクリプトは別のインスタンスで編集されています。保存は中止されました。", "eslint_config_format_error": "ESLint設定フォーマットエラー", "export_success": "エクスポートに成功しました", "get_backup_dir_url_failed": "バックアップディレクトリアドレスの取得に失敗しました", diff --git a/src/locales/ru-RU/translation.json b/src/locales/ru-RU/translation.json index 5d400d000..0650cf11b 100644 --- a/src/locales/ru-RU/translation.json +++ b/src/locales/ru-RU/translation.json @@ -419,6 +419,9 @@ "menu_expand_num_before": "Когда пунктов меню больше", "menu_expand_num_after": ", автоматически скрывать", "script_name_cannot_be_set_to_empty": "Имя скрипта не может быть пустым", + "edit_conflict": "Конфликт редактирования", + "confirm_override_when_edit_conflict": "Этот скрипт был изменён в другом экземпляре. Замена приведёт к перезаписи этих изменений. Хотите сохранить эту версию?", + "save_abort_when_edit_conflict": "Скрипт был изменён в другом экземпляре. Сохранение отменено.", "eslint_config_format_error": "Ошибка формата конфигурации ESLint", "export_success": "Экспорт успешен", "get_backup_dir_url_failed": "Ошибка получения адреса папки резервных копий", diff --git a/src/locales/vi-VN/translation.json b/src/locales/vi-VN/translation.json index 84fa78748..aa1a6d4c8 100644 --- a/src/locales/vi-VN/translation.json +++ b/src/locales/vi-VN/translation.json @@ -419,6 +419,9 @@ "menu_expand_num_before": "Các mục menu nhiều hơn", "menu_expand_num_after": "sẽ bị ẩn.", "script_name_cannot_be_set_to_empty": "Tên script không được để trống", + "edit_conflict": "Xung đột chỉnh sửa", + "confirm_override_when_edit_conflict": "Tập lệnh này đã được chỉnh sửa ở một phiên bản khác. Việc thay thế sẽ ghi đè các thay đổi đó. Bạn có muốn giữ phiên bản này không?", + "save_abort_when_edit_conflict": "Tập lệnh đã được chỉnh sửa ở một phiên bản khác. Đã hủy lưu.", "eslint_config_format_error": "Lỗi định dạng cấu hình eslint", "export_success": "Đổ dữ liệu thành công đã lưu", "get_backup_dir_url_failed": "Không thể lấy địa chỉ thư mục sao lưu", diff --git a/src/locales/zh-CN/translation.json b/src/locales/zh-CN/translation.json index b5200ab34..5f55f5df1 100644 --- a/src/locales/zh-CN/translation.json +++ b/src/locales/zh-CN/translation.json @@ -419,6 +419,9 @@ "menu_expand_num_before": "菜单项超过", "menu_expand_num_after": "个时,自动隐藏", "script_name_cannot_be_set_to_empty": "脚本name不可以设置为空", + "edit_conflict": "编辑冲突", + "confirm_override_when_edit_conflict": "此脚本已在其他实例中被修改。替换将覆盖这些更改。是否要保留此版本?", + "save_abort_when_edit_conflict": "该脚本已在其他实例中被修改,保存已中止。", "eslint_config_format_error": "ESLint配置格式错误", "export_success": "导出成功", "get_backup_dir_url_failed": "获取备份目录地址失败", diff --git a/src/locales/zh-TW/translation.json b/src/locales/zh-TW/translation.json index b375955e7..9cce86015 100644 --- a/src/locales/zh-TW/translation.json +++ b/src/locales/zh-TW/translation.json @@ -419,6 +419,9 @@ "menu_expand_num_before": "選單項目超過", "menu_expand_num_after": "個時,自動隱藏", "script_name_cannot_be_set_to_empty": "腳本名稱不可設定為空", + "edit_conflict": "編輯衝突", + "confirm_override_when_edit_conflict": "此腳本已在其他實例中被修改。替換將覆蓋這些變更。是否要保留此版本?", + "save_abort_when_edit_conflict": "此腳本已在其他實例中被修改,已中止儲存。", "eslint_config_format_error": "ESLint設定格式錯誤", "export_success": "匯出成功", "get_backup_dir_url_failed": "取得備份目錄網址失敗", diff --git a/src/pages/options/routes/script/ScriptEditor.tsx b/src/pages/options/routes/script/ScriptEditor.tsx index 08120b226..22af41b19 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