From 9876c1cbcb7e79873f863bf4f35b9dc7c4414bbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E4=B8=80=E4=B9=8B?= Date: Mon, 30 Dec 2024 18:06:53 +0800 Subject: [PATCH] =?UTF-8?q?options=E9=A1=B5=E9=9D=A2=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- eslint.config.mjs | 1 + package.json | 5 + packages/message/message_queue.ts | 14 + pnpm-lock.yaml | 238 ++++++++++- rspack.config.ts | 2 +- src/app/repo/repo.ts | 41 +- src/app/repo/scripts.ts | 2 +- src/app/service/service_worker/client.ts | 5 +- src/app/service/service_worker/script.ts | 30 +- src/manifest.json | 4 + .../components/CloudScriptPlan/index.tsx | 198 +++++++++ src/pages/components/CustomLink/index.tsx | 35 ++ src/pages/components/CustomTrans/index.tsx | 45 +++ .../components/FileSystemParams/index.tsx | 122 ++++++ src/pages/components/GMApiSetting/index.tsx | 131 ++++++ src/pages/components/LogLabel/index.css | 26 ++ src/pages/components/LogLabel/index.tsx | 80 ++++ src/pages/components/ScriptMenuList/index.tsx | 334 +++++++++++++++ src/pages/components/ScriptResource/index.tsx | 177 ++++++++ src/pages/components/ScriptSetting/Match.tsx | 325 +++++++++++++++ .../components/ScriptSetting/Permission.tsx | 196 +++++++++ src/pages/components/ScriptSetting/index.tsx | 106 +++++ src/pages/components/ScriptStorage/index.tsx | 286 +++++++++++++ .../components/UserConfigPanel/index.tsx | 200 +++++++++ src/pages/components/layout/Sider.tsx | 210 ++++++++++ src/pages/components/layout/SiderGuide.tsx | 157 ++++++++ src/pages/install/App.tsx | 14 +- src/pages/options.html | 24 ++ src/pages/options/main.tsx | 16 +- src/pages/options/routes/Logger.tsx | 58 +-- src/pages/options/routes/ScriptList.tsx | 380 ++++++++---------- src/pages/options/routes/Setting.tsx | 6 - src/pages/options/routes/SubscribeList.tsx | 2 - src/pages/options/routes/Tools.tsx | 6 - .../options/routes/script/ScriptEditor.tsx | 86 +--- src/pages/options/routes/utils.tsx | 53 +-- src/store/features/script.ts | 31 ++ src/store/features/setting.ts | 4 +- src/store/store.ts | 3 +- src/template/background.tpl | 13 + src/template/cloudcat-package/index.tpl | 3 + src/template/cloudcat-package/package.tpl | 15 + src/template/cloudcat-package/utils.tpl | 17 + src/template/crontab.tpl | 13 + src/template/normal.tpl | 14 + 45 files changed, 3318 insertions(+), 410 deletions(-) create mode 100644 src/pages/components/CloudScriptPlan/index.tsx create mode 100644 src/pages/components/CustomLink/index.tsx create mode 100644 src/pages/components/CustomTrans/index.tsx create mode 100644 src/pages/components/FileSystemParams/index.tsx create mode 100644 src/pages/components/GMApiSetting/index.tsx create mode 100644 src/pages/components/LogLabel/index.css create mode 100644 src/pages/components/LogLabel/index.tsx create mode 100644 src/pages/components/ScriptMenuList/index.tsx create mode 100644 src/pages/components/ScriptResource/index.tsx create mode 100644 src/pages/components/ScriptSetting/Match.tsx create mode 100644 src/pages/components/ScriptSetting/Permission.tsx create mode 100644 src/pages/components/ScriptSetting/index.tsx create mode 100644 src/pages/components/ScriptStorage/index.tsx create mode 100644 src/pages/components/UserConfigPanel/index.tsx create mode 100644 src/pages/components/layout/Sider.tsx create mode 100644 src/pages/components/layout/SiderGuide.tsx create mode 100644 src/pages/options.html create mode 100644 src/store/features/script.ts create mode 100644 src/template/background.tpl create mode 100644 src/template/cloudcat-package/index.tpl create mode 100644 src/template/cloudcat-package/package.tpl create mode 100644 src/template/cloudcat-package/utils.tpl create mode 100644 src/template/crontab.tpl create mode 100644 src/template/normal.tpl diff --git a/eslint.config.mjs b/eslint.config.mjs index 02d56ae..8f5435f 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -26,6 +26,7 @@ export default [ rules: { "@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/ban-ts-comment": "off", + "@typescript-eslint/no-unused-expressions": "off", ...reactHooks.configs.recommended.rules, }, }, diff --git a/package.json b/package.json index 6ee5df5..a249be3 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,9 @@ }, "dependencies": { "@arco-design/web-react": "^2.64.1", + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/sortable": "^10.0.0", + "@dnd-kit/utilities": "^3.2.2", "@reduxjs/toolkit": "^2.3.0", "cron": "^3.2.1", "dayjs": "^1.11.13", @@ -29,7 +32,9 @@ "react-dom": "^18.2.0", "react-i18next": "^15.1.0", "react-icons": "^5.3.0", + "react-joyride": "^2.9.3", "react-redux": "^9.1.2", + "react-router-dom": "^7.1.1", "semver": "^7.6.3", "uuid": "^11.0.3", "yaml": "^2.6.1" diff --git a/packages/message/message_queue.ts b/packages/message/message_queue.ts index cf537d4..b023310 100644 --- a/packages/message/message_queue.ts +++ b/packages/message/message_queue.ts @@ -1,3 +1,4 @@ +import EventEmitter from "eventemitter3"; import { connect } from "./client"; import { ApiFunction, Server } from "./server"; @@ -24,6 +25,8 @@ export class Broker { export class MessageQueue { topicConMap: Map = new Map(); + private EE: EventEmitter = new EventEmitter(); + constructor(api: Server) { api.on("messageQueue", this.handler()); } @@ -69,5 +72,16 @@ export class MessageQueue { list?.forEach((item) => { item.con.postMessage({ action: "message", topic, message }); }); + this.EE.emit(topic, message); + } + + // 只发布给当前环境 + emit(topic: string, message: any) { + this.EE.emit(topic, message); + } + + // 同环境下使用addListener + addListener(topic: string, handler: (message: any) => void) { + this.EE.on(topic, handler); } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 61f7ebf..3e82191 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,15 @@ importers: '@arco-design/web-react': specifier: ^2.64.1 version: 2.64.1(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@dnd-kit/core': + specifier: ^6.3.1 + version: 6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@dnd-kit/sortable': + specifier: ^10.0.0 + version: 10.0.0(@dnd-kit/core@6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) + '@dnd-kit/utilities': + specifier: ^3.2.2 + version: 3.2.2(react@18.3.1) '@reduxjs/toolkit': specifier: ^2.3.0 version: 2.3.0(react-redux@9.1.2(@types/react@18.3.12)(react@18.3.1)(redux@5.0.1))(react@18.3.1) @@ -47,9 +56,15 @@ importers: react-icons: specifier: ^5.3.0 version: 5.3.0(react@18.3.1) + react-joyride: + specifier: ^2.9.3 + version: 2.9.3(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-redux: specifier: ^9.1.2 version: 9.1.2(@types/react@18.3.12)(react@18.3.1)(redux@5.0.1) + react-router-dom: + specifier: ^7.1.1 + version: 7.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) semver: specifier: ^7.6.3 version: 7.6.3 @@ -309,6 +324,28 @@ packages: resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==} engines: {node: '>=10.0.0'} + '@dnd-kit/accessibility@3.1.1': + resolution: {integrity: sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==} + peerDependencies: + react: '>=16.8.0' + + '@dnd-kit/core@6.3.1': + resolution: {integrity: sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@dnd-kit/sortable@10.0.0': + resolution: {integrity: sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==} + peerDependencies: + '@dnd-kit/core': ^6.3.0 + react: '>=16.8.0' + + '@dnd-kit/utilities@3.2.2': + resolution: {integrity: sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==} + peerDependencies: + react: '>=16.8.0' + '@esbuild/aix-ppc64@0.21.5': resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} engines: {node: '>=12'} @@ -634,6 +671,12 @@ packages: resolution: {integrity: sha512-HFZ4Mp26nbWk9d/BpvP0YNL6W4UoZF0VFcTw/aPPA8RpOxeFQgK+ClABGgAUXs9Y/RGX/l1vOmrqz1MQt9MNuw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@gilbarbara/deep-equal@0.1.2': + resolution: {integrity: sha512-jk+qzItoEb0D0xSSmrKDDzf9sheQj/BAPxlgNxgmOaA3mxpUa6ndJLYGZKsJnIVEQSD8zcTbyILz7I0HcnBCRA==} + + '@gilbarbara/deep-equal@0.3.1': + resolution: {integrity: sha512-I7xWjLs2YSVMc5gGx1Z3ZG1lgFpITPndpi8Ku55GeEIKpACCPQNS/OTqQbxgTCfq0Ncvcc+CrFov96itVh6Qvw==} + '@humanfs/core@0.19.0': resolution: {integrity: sha512-2cbWIHbZVEweE853g8jymffCA+NCMiuqeECeBBLm8dg2oFdjuGJhgN4UAbI+6v0CKbbhvtXA4qV8YR5Ji86nmw==} engines: {node: '>=18.18.0'} @@ -983,6 +1026,9 @@ packages: '@types/connect@3.4.38': resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + '@types/cookie@0.6.0': + resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} + '@types/eslint-scope@3.7.7': resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} @@ -1644,6 +1690,10 @@ packages: resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==} engines: {node: '>= 0.6'} + cookie@1.0.2: + resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==} + engines: {node: '>=18'} + core-js@3.38.1: resolution: {integrity: sha512-OP35aUorbU3Zvlx7pjsFdu1rGNnD4pgw/CWoYzRY3t2EzoVT7shKHY1dlAy3f41cGIO7ZDPQimhGFTlEYkG/Hw==} @@ -1724,6 +1774,9 @@ packages: decimal.js@10.4.3: resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==} + deep-diff@1.0.2: + resolution: {integrity: sha512-aWS3UIVH+NPGCD1kki+DCU9Dua032iSsO43LqQpcs4R3+dVv7tX0qBGjiVHJHjplsoUM2XRO/KB92glqc68awg==} + deep-eql@5.0.2: resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} engines: {node: '>=6'} @@ -1731,6 +1784,10 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + default-browser-id@5.0.0: resolution: {integrity: sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==} engines: {node: '>=18'} @@ -2367,6 +2424,12 @@ packages: engines: {node: '>=14.16'} hasBin: true + is-lite@0.8.2: + resolution: {integrity: sha512-JZfH47qTsslwaAsqbMI3Q6HNNjUuq6Cmzzww50TdP5Esb6e1y2sK2UAaZZuzfAzpoI2AkxoPQapZdlDuP6Vlsw==} + + is-lite@1.2.1: + resolution: {integrity: sha512-pgF+L5bxC+10hLBgf6R2P4ZZUBOQIIacbdo8YvuCP8/JvsWxG7aZ9p10DYuLtifFci4l3VITphhMlMV4Y+urPw==} + is-map@2.0.3: resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} engines: {node: '>= 0.4'} @@ -2852,6 +2915,10 @@ packages: pkg-types@1.2.1: resolution: {integrity: sha512-sQoqa8alT3nHjGuTjuKgOnvjo4cljkufdtLMnO2LBP/wRwuDlo1tkaEdMxCRhyGRPacv/ztlZgDPm2b7FAmEvw==} + popper.js@1.16.1: + resolution: {integrity: sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==} + deprecated: You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1 + possible-typed-array-names@1.0.0: resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} engines: {node: '>= 0.4'} @@ -2927,6 +2994,12 @@ packages: peerDependencies: react: ^18.3.1 + react-floater@0.7.9: + resolution: {integrity: sha512-NXqyp9o8FAXOATOEo0ZpyaQ2KPb4cmPMXGWkx377QtJkIXHlHRAGer7ai0r0C1kG5gf+KJ6Gy+gdNIiosvSicg==} + peerDependencies: + react: 15 - 18 + react-dom: 15 - 18 + react-focus-lock@2.13.2: resolution: {integrity: sha512-T/7bsofxYqnod2xadvuwjGKHOoL5GH7/EIPI5UyEvaU/c2CcphvGI371opFtuY/SYdbMsNiuF4HsHQ50nA/TKQ==} peerDependencies: @@ -2954,12 +3027,24 @@ packages: peerDependencies: react: '*' + react-innertext@1.1.5: + resolution: {integrity: sha512-PWAqdqhxhHIv80dT9znP2KvS+hfkbRovFp4zFYHFFlOoQLRiawIic81gKb3U1wEyJZgMwgs3JoLtwryASRWP3Q==} + peerDependencies: + '@types/react': '>=0.0.0 <=99' + react: '>=0.0.0 <=99' + react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} react-is@18.3.1: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + react-joyride@2.9.3: + resolution: {integrity: sha512-1+Mg34XK5zaqJ63eeBhqdbk7dlGCFp36FXwsEvgpjqrtyywX2C6h9vr3jgxP0bGHCw8Ilsp/nRDzNVq6HJ3rNw==} + peerDependencies: + react: 15 - 18 + react-dom: 15 - 18 + react-redux@9.1.2: resolution: {integrity: sha512-0OA4dhM1W48l3uzmv6B7TXPCGmokUU4p1M44DGN2/D9a1FjVPukVjER1PcPX97jIg6aUeLq1XJo1IpfbgULn0w==} peerDependencies: @@ -2972,6 +3057,23 @@ packages: redux: optional: true + react-router-dom@7.1.1: + resolution: {integrity: sha512-vSrQHWlJ5DCfyrhgo0k6zViOe9ToK8uT5XGSmnuC2R3/g261IdIMpZVqfjD6vWSXdnf5Czs4VA/V60oVR6/jnA==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + + react-router@7.1.1: + resolution: {integrity: sha512-39sXJkftkKWRZ2oJtHhCxmoCrBCULr/HAH4IT5DHlgu/Q0FCPV0S4Lx+abjDTx/74xoZzNYDYbOZWlJjruyuDQ==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + peerDependenciesMeta: + react-dom: + optional: true + react-transition-group@4.4.5: resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==} peerDependencies: @@ -3123,6 +3225,12 @@ packages: scroll-into-view-if-needed@2.2.31: resolution: {integrity: sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==} + scroll@3.0.1: + resolution: {integrity: sha512-pz7y517OVls1maEzlirKO5nPYle9AXsFzTMNJrRGmT951mzpIBy7sNHOg5o/0MQd/NqliCiWnAi0kZneMPFLcg==} + + scrollparent@2.1.0: + resolution: {integrity: sha512-bnnvJL28/Rtz/kz2+4wpBjHzWoEzXhVg/TE8BeVGJHUqE8THNIRnDxDWMktwM+qahvlRdvlLdsQfYe+cuqfZeA==} + select-hose@2.0.0: resolution: {integrity: sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==} @@ -3154,6 +3262,9 @@ packages: resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==} engines: {node: '>= 0.8.0'} + set-cookie-parser@2.7.1: + resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==} + set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} @@ -3399,6 +3510,12 @@ packages: resolution: {integrity: sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==} engines: {node: '>=18'} + tree-changes@0.11.2: + resolution: {integrity: sha512-4gXlUthrl+RabZw6lLvcCDl6KfJOCmrC16BC5CRdut1EAH509Omgg0BfKLY+ViRlzrvYOTWR0FMS2SQTwzumrw==} + + tree-changes@0.9.3: + resolution: {integrity: sha512-vvvS+O6kEeGRzMglTKbc19ltLWNtmNt1cpBoSYLj/iEcPVvpJasemKOlxBrmZaCtDJoF+4bwv3m01UKYi8mukQ==} + tree-dump@1.0.2: resolution: {integrity: sha512-dpev9ABuLWdEubk+cIaI9cHwRNNDjkBBLXTwI4UCUFdQ5xXKqNXoK4FEciw/vxf+NQ7Cb7sGUyeUtORvHIdRXQ==} engines: {node: '>=10.0'} @@ -3433,10 +3550,17 @@ packages: engines: {node: '>=18.0.0'} hasBin: true + turbo-stream@2.4.0: + resolution: {integrity: sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==} + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} + type-fest@4.31.0: + resolution: {integrity: sha512-yCxltHW07Nkhv/1F6wWBr8kz+5BGMfP+RbRSYFnegVb0qV/UMT0G0ElBloPVerqn4M2ZV80Ir1FtCcYv1cT6vQ==} + engines: {node: '>=16'} + type-is@1.6.18: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} @@ -3882,6 +4006,31 @@ snapshots: '@discoveryjs/json-ext@0.5.7': {} + '@dnd-kit/accessibility@3.1.1(react@18.3.1)': + dependencies: + react: 18.3.1 + tslib: 2.8.0 + + '@dnd-kit/core@6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@dnd-kit/accessibility': 3.1.1(react@18.3.1) + '@dnd-kit/utilities': 3.2.2(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + tslib: 2.8.0 + + '@dnd-kit/sortable@10.0.0(@dnd-kit/core@6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)': + dependencies: + '@dnd-kit/core': 6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@dnd-kit/utilities': 3.2.2(react@18.3.1) + react: 18.3.1 + tslib: 2.8.0 + + '@dnd-kit/utilities@3.2.2(react@18.3.1)': + dependencies: + react: 18.3.1 + tslib: 2.8.0 + '@esbuild/aix-ppc64@0.21.5': optional: true @@ -4075,6 +4224,10 @@ snapshots: dependencies: levn: 0.4.1 + '@gilbarbara/deep-equal@0.1.2': {} + + '@gilbarbara/deep-equal@0.3.1': {} + '@humanfs/core@0.19.0': {} '@humanfs/node@0.16.5': @@ -4448,6 +4601,8 @@ snapshots: dependencies: '@types/node': 22.10.2 + '@types/cookie@0.6.0': {} + '@types/eslint-scope@3.7.7': dependencies: '@types/eslint': 9.6.1 @@ -5420,6 +5575,8 @@ snapshots: cookie@0.7.1: {} + cookie@1.0.2: {} + core-js@3.38.1: {} core-util-is@1.0.3: {} @@ -5496,10 +5653,14 @@ snapshots: decimal.js@10.4.3: {} + deep-diff@1.0.2: {} + deep-eql@5.0.2: {} deep-is@0.1.4: {} + deepmerge@4.3.1: {} + default-browser-id@5.0.0: {} default-browser@5.2.1: @@ -6354,6 +6515,10 @@ snapshots: dependencies: is-docker: 3.0.0 + is-lite@0.8.2: {} + + is-lite@1.2.1: {} + is-map@2.0.3: {} is-negative-zero@2.0.3: {} @@ -6453,7 +6618,7 @@ snapshots: jest-worker@27.5.1: dependencies: - '@types/node': 22.8.1 + '@types/node': 22.10.2 merge-stream: 2.0.0 supports-color: 8.1.1 optional: true @@ -6813,6 +6978,8 @@ snapshots: mlly: 1.7.3 pathe: 1.1.2 + popper.js@1.16.1: {} + possible-typed-array-names@1.0.0: {} postcss-loader@8.1.1(@rspack/core@1.0.14(@swc/helpers@0.5.13))(postcss@8.4.49)(typescript@5.6.3)(webpack@5.96.1(esbuild@0.23.1)): @@ -6885,6 +7052,16 @@ snapshots: react: 18.3.1 scheduler: 0.23.2 + react-floater@0.7.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + deepmerge: 4.3.1 + is-lite: 0.8.2 + popper.js: 1.16.1 + prop-types: 15.8.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + tree-changes: 0.9.3 + react-focus-lock@2.13.2(@types/react@18.3.12)(react@18.3.1): dependencies: '@babel/runtime': 7.26.0 @@ -6910,10 +7087,33 @@ snapshots: dependencies: react: 18.3.1 + react-innertext@1.1.5(@types/react@18.3.12)(react@18.3.1): + dependencies: + '@types/react': 18.3.12 + react: 18.3.1 + react-is@16.13.1: {} react-is@18.3.1: {} + react-joyride@2.9.3(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@gilbarbara/deep-equal': 0.3.1 + deep-diff: 1.0.2 + deepmerge: 4.3.1 + is-lite: 1.2.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-floater: 0.7.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react-innertext: 1.1.5(@types/react@18.3.12)(react@18.3.1) + react-is: 16.13.1 + scroll: 3.0.1 + scrollparent: 2.1.0 + tree-changes: 0.11.2 + type-fest: 4.31.0 + transitivePeerDependencies: + - '@types/react' + react-redux@9.1.2(@types/react@18.3.12)(react@18.3.1)(redux@5.0.1): dependencies: '@types/use-sync-external-store': 0.0.3 @@ -6923,6 +7123,22 @@ snapshots: '@types/react': 18.3.12 redux: 5.0.1 + react-router-dom@7.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-router: 7.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + + react-router@7.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@types/cookie': 0.6.0 + cookie: 1.0.2 + react: 18.3.1 + set-cookie-parser: 2.7.1 + turbo-stream: 2.4.0 + optionalDependencies: + react-dom: 18.3.1(react@18.3.1) + react-transition-group@4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@babel/runtime': 7.26.0 @@ -7105,6 +7321,10 @@ snapshots: dependencies: compute-scroll-into-view: 1.0.20 + scroll@3.0.1: {} + + scrollparent@2.1.0: {} + select-hose@2.0.0: {} selfsigned@2.4.1: @@ -7160,6 +7380,8 @@ snapshots: transitivePeerDependencies: - supports-color + set-cookie-parser@2.7.1: {} + set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 @@ -7425,6 +7647,16 @@ snapshots: dependencies: punycode: 2.3.1 + tree-changes@0.11.2: + dependencies: + '@gilbarbara/deep-equal': 0.3.1 + is-lite: 1.2.1 + + tree-changes@0.9.3: + dependencies: + '@gilbarbara/deep-equal': 0.1.2 + is-lite: 0.8.2 + tree-dump@1.0.2(tslib@2.8.0): dependencies: tslib: 2.8.0 @@ -7478,10 +7710,14 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + turbo-stream@2.4.0: {} + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 + type-fest@4.31.0: {} + type-is@1.6.18: dependencies: media-typer: 0.3.0 diff --git a/rspack.config.ts b/rspack.config.ts index 4a8b5ad..36452ef 100644 --- a/rspack.config.ts +++ b/rspack.config.ts @@ -139,7 +139,7 @@ export default defineConfig({ }), new rspack.HtmlRspackPlugin({ filename: `${dist}/ext/src/options.html`, - template: `${src}/pages/template.html`, + template: `${src}/pages/options.html`, inject: "head", title: "Home - ScriptCat", minify: true, diff --git a/src/app/repo/repo.ts b/src/app/repo/repo.ts index 2e5e3f3..5b3cf42 100644 --- a/src/app/repo/repo.ts +++ b/src/app/repo/repo.ts @@ -1,15 +1,15 @@ export abstract class Repo { constructor(private prefix: string) { if (!prefix.endsWith(":")) { - prefix += ":"; + this.prefix += ":"; } } - private joinKey(key: string) { + protected joinKey(key: string) { return this.prefix + key; } - public async _save(key: string, val: T) { + protected async _save(key: string, val: T) { return new Promise((resolve) => { const data = { [this.joinKey(key)]: val, @@ -55,4 +55,39 @@ export abstract class Repo { }); }); } + + public delete(key: string) { + return new Promise((resolve) => { + chrome.storage.local.remove(this.joinKey(key), () => { + resolve(); + }); + }); + } + + update(key: string, val: Partial) { + return new Promise((resolve) => { + this.get(key).then((result) => { + if (result) { + Object.assign(result, val); + this._save(key, result).then(() => { + resolve(result); + }); + } + }); + }); + } + + all(): Promise { + return new Promise((resolve) => { + chrome.storage.local.get((result) => { + const ret = []; + for (const key in result) { + if (key.startsWith(this.prefix)) { + ret.push(result[key]); + } + } + resolve(ret); + }); + }); + } } diff --git a/src/app/repo/scripts.ts b/src/app/repo/scripts.ts index c57bc4e..383cd59 100644 --- a/src/app/repo/scripts.ts +++ b/src/app/repo/scripts.ts @@ -46,7 +46,7 @@ export interface Config { export type UserConfig = { [key: string]: { [key: string]: Config } }; export interface Script { - id: number; // 脚本id + // id: number; // 脚本id mv3迁移为chrome.storage后舍弃 uuid: string; // 脚本uuid,通过脚本uuid识别唯一脚本 name: string; // 脚本名称 code: string; // 脚本执行代码 diff --git a/src/app/service/service_worker/client.ts b/src/app/service/service_worker/client.ts index 91a4ae3..7eaecbb 100644 --- a/src/app/service/service_worker/client.ts +++ b/src/app/service/service_worker/client.ts @@ -1,5 +1,6 @@ import { Script } from "@App/app/repo/scripts"; import { Client } from "@Packages/message/client"; +import { InstallSource } from "."; export class ScriptClient extends Client { constructor() { @@ -11,7 +12,7 @@ export class ScriptClient extends Client { return this.do("getInstallInfo", uuid); } - installScript(script: Script) { - return this.do("installScript", script); + installScript(script: Script, upsertBy: InstallSource = "user") { + return this.do("installScript", { script, upsertBy }); } } diff --git a/src/app/service/service_worker/script.ts b/src/app/service/service_worker/script.ts index f3f3536..a0fa90e 100644 --- a/src/app/service/service_worker/script.ts +++ b/src/app/service/service_worker/script.ts @@ -8,6 +8,7 @@ import CacheKey from "@App/app/cache_key"; import { openInCurrentTab } from "@App/pkg/utils/utils"; import { Script, ScriptDAO } from "@App/app/repo/scripts"; import { MessageQueue } from "@Packages/message/message_queue"; +import { InstallSource } from "."; export class ScriptService { logger: Logger; @@ -135,17 +136,34 @@ export class ScriptService { } // 安装脚本 - async installScript(script: Script) { + async installScript(param: { script: Script; upsertBy: InstallSource }) { + param.upsertBy = param.upsertBy || "user"; + const { script, upsertBy } = param; + const logger = this.logger.with({ + name: script.name, + uuid: script.uuid, + version: script.metadata.version[0], + upsertBy, + }); const dao = new ScriptDAO(); // 判断是否已经安装 const oldScript = await dao.findByUUID(script.uuid); - if (!oldScript) { - // 执行安装逻辑 - } else { + if (oldScript) { // 执行更新逻辑 + script.selfMetadata = oldScript.selfMetadata; } - // 广播一下 - this.mq.publish("installScript", script); + return dao + .save(script) + .then(() => { + logger.info("install success"); + // 广播一下 + this.mq.publish("installScript", script); + return {}; + }) + .catch((e) => { + logger.error("install error", Logger.E(e)); + throw e; + }); } init() { diff --git a/src/manifest.json b/src/manifest.json index dad5de5..a4ad442 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -4,6 +4,10 @@ "version": "0.17.0.1001", "author": "CodFrm", "description": "__MSG_scriptcat_description__", + "options_ui": { + "page": "src/options.html", + "open_in_tab": true + }, "background": { "service_worker": "src/service_worker.js" }, diff --git a/src/pages/components/CloudScriptPlan/index.tsx b/src/pages/components/CloudScriptPlan/index.tsx new file mode 100644 index 0000000..d914e6b --- /dev/null +++ b/src/pages/components/CloudScriptPlan/index.tsx @@ -0,0 +1,198 @@ +import { Export, ExportDAO, ExportTarget } from "@App/app/repo/export"; +import { Script } from "@App/app/repo/scripts"; +import { Button, Checkbox, Form, Input, Message, Modal, Select } from "@arco-design/web-react"; +import { IconQuestionCircleFill } from "@arco-design/web-react/icon"; +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; + +const FormItem = Form.Item; + +function defaultParams(script: Script) { + return { + exportValue: script.metadata.exportvalue && script.metadata.exportvalue[0], + exportCookie: script.metadata.exportcookie && script.metadata.exportcookie[0], + }; +} + +const CloudScriptPlan: React.FC<{ + // eslint-disable-next-line react/require-default-props + script?: Script; + onClose: () => void; +}> = ({ script, onClose }) => { + const [form] = Form.useForm(); + const [visible, setVisible] = React.useState(false); + const [cloudScriptType, setCloudScriptType] = React.useState("local"); + const [, setModel] = React.useState(); + const { t } = useTranslation(); + + const CloudScriptList = [ + { + key: "local", + name: t("local"), + }, + ]; + + useEffect(() => { + if (script) { + setVisible(true); + // 设置默认值 + // 从数据库中获取导出数据 + const dao = new ExportDAO(); + dao.findByScriptID(script.uuid).then((data) => { + setModel(data); + if (data && data.params[data.target]) { + setCloudScriptType(data.target); + form.setFieldsValue(data.params[data.target]); + } else { + setCloudScriptType("local"); + form.setFieldsValue(defaultParams(script)); + } + }); + } + }, [script]); + return ( + + + {script?.name} {t("upload_to_cloud")} + + + + + ); +}; + +export default CloudScriptPlan; diff --git a/src/pages/components/CustomLink/index.tsx b/src/pages/components/CustomLink/index.tsx new file mode 100644 index 0000000..029dfc6 --- /dev/null +++ b/src/pages/components/CustomLink/index.tsx @@ -0,0 +1,35 @@ +import React, { ReactNode } from "react"; +import { useNavigate } from "react-router-dom"; + +const CustomLink: React.FC<{ + children: ReactNode; + to: string; + className?: string; + search?: string; +}> = ({ children, to, search, className }) => { + const nav = useNavigate(); + + const click = () => { + if (window.onbeforeunload) { + if (confirm("当前正在编辑状态,跳转其它页面将会丢失当前内容,是否跳转?")) { + nav({ + pathname: to, + search, + }); + } + } else { + nav({ + pathname: to, + search, + }); + } + }; + + return ( +
+ {children} +
+ ); +}; + +export default CustomLink; diff --git a/src/pages/components/CustomTrans/index.tsx b/src/pages/components/CustomTrans/index.tsx new file mode 100644 index 0000000..53c73f6 --- /dev/null +++ b/src/pages/components/CustomTrans/index.tsx @@ -0,0 +1,45 @@ +import { Link } from "@arco-design/web-react"; +import React from "react"; +import { useTranslation } from "react-i18next"; + +// 因为i18n的Trans组件打包后出现问题,所以自己实现一个 +export const CustomTrans: React.FC<{ + i18nKey: string; +}> = ({ i18nKey }) => { + const { t } = useTranslation(); + const children: (JSX.Element | string)[] = []; + let content = t(i18nKey); + for (;;) { + const i = content.indexOf("<"); + if (i !== -1) { + children.push(content.substring(0, i)); + const end = content.indexOf(">", i); + const key = content.substring(i + 1, end).split(" ")[0]; + const tag = content.substring(i, end + 1); + const tagEnd = content.indexOf(``, end); + const element = content.substring(end + 1, content.indexOf(``, end)); + switch (key) { + case "Link": + // eslint-disable-next-line no-case-declarations + const href = tag.match(/href="(.*)"/)![1]; + children.push( + + {element} + + ); + break; + default: + children.push(element); + break; + } + content = content.substring(tagEnd + key.length + 3); + } else { + children.push(content); + break; + } + } + + return
{children}
; +}; + +export default CustomTrans; diff --git a/src/pages/components/FileSystemParams/index.tsx b/src/pages/components/FileSystemParams/index.tsx new file mode 100644 index 0000000..703621e --- /dev/null +++ b/src/pages/components/FileSystemParams/index.tsx @@ -0,0 +1,122 @@ +import React from "react"; +import { Input, Select, Space } from "@arco-design/web-react"; + + +const fileSystemList: { + key: FileSystemType; + name: string; +}[] = [ + { + key: "webdav", + name: "WebDAV", + }, + { + key: "baidu-netdsik", + name: "百度网盘", + }, + { + key: "onedrive", + name: "OneDrive", + }, +]; +const FileSystemParams: React.FC<{ + preNode: React.ReactNode | string; + onChangeFileSystemType: (type: FileSystemType) => void; + onChangeFileSystemParams: (params: any) => void; + actionButton: React.ReactNode[]; + fileSystemType: FileSystemType; + fileSystemParams: any; +}> = ({ + onChangeFileSystemType, + onChangeFileSystemParams, + preNode, + actionButton, + fileSystemType, + fileSystemParams, +}) => { + return ( + <> + + {preNode} + + {actionButton.map((item) => item)} + + + {Object.keys(fsParams[fileSystemType]).map((key) => ( +
+ {fsParams[fileSystemType][key].type === "select" && ( + <> + {fsParams[fileSystemType][key].title} + + + )} + {fsParams[fileSystemType][key].type === "password" && ( + <> + {fsParams[fileSystemType][key].title} + { + onChangeFileSystemParams({ + ...fileSystemParams, + [key]: value, + }); + }} + /> + + )} + {!fsParams[fileSystemType][key].type && ( + <> + {fsParams[fileSystemType][key].title} + { + onChangeFileSystemParams({ + ...fileSystemParams, + [key]: value, + }); + }} + /> + + )} +
+ ))} +
+ + ); +}; + +export default FileSystemParams; diff --git a/src/pages/components/GMApiSetting/index.tsx b/src/pages/components/GMApiSetting/index.tsx new file mode 100644 index 0000000..5e2e824 --- /dev/null +++ b/src/pages/components/GMApiSetting/index.tsx @@ -0,0 +1,131 @@ +import React, { useState } from "react"; +import { + Button, + Card, + Collapse, + Link, + Message, + Space, + Typography, +} from "@arco-design/web-react"; +import { useTranslation } from "react-i18next"; +import FileSystemParams from "../FileSystemParams"; + +const CollapseItem = Collapse.Item; + +const GMApiSetting: React.FC = () => { + const systemConfig = IoC.instance(SystemConfig) as SystemConfig; + const [status, setStatus] = useState(systemConfig.catFileStorage.status); + const [fileSystemType, setFilesystemType] = useState( + systemConfig.catFileStorage.filesystem + ); + const [fileSystemParams, setFilesystemParam] = useState<{ + [key: string]: any; + }>(systemConfig.catFileStorage.params[fileSystemType] || {}); + const { t } = useTranslation(); + + return ( + + + + + + {t("settings")} + + CAT_fileStorage + + {t("use_file_system")} + + } + actionButton={[ + , + , + , + ]} + fileSystemType={fileSystemType} + fileSystemParams={fileSystemParams} + onChangeFileSystemType={(type) => { + setFilesystemType(type); + }} + onChangeFileSystemParams={(params) => { + setFilesystemParam(params); + }} + /> + {status === "unset" && ( + {t("not_set")} + )} + {status === "success" && ( + {t("in_use")} + )} + {status === "error" && ( + + {t("storage_error")} + + )} + + + + + ); +}; + +export default GMApiSetting; diff --git a/src/pages/components/LogLabel/index.css b/src/pages/components/LogLabel/index.css new file mode 100644 index 0000000..4fbd7a5 --- /dev/null +++ b/src/pages/components/LogLabel/index.css @@ -0,0 +1,26 @@ +.log-query-label .arco-select-view { + border-radius: 0; +} + +.log-query-label .arco-select:first-child { + border-left: 1px solid var(--color-neutral-3); +} + +.log-query-label .arco-select { + width: auto; + border-top: 1px solid var(--color-neutral-3); + border-bottom: 1px solid var(--color-neutral-3); +} + +.log-query-label .arco-btn { + height: 34px; + border-left: 0; + border-radius: 0; + border-top: 1px solid var(--color-neutral-3); + border-bottom: 1px solid var(--color-neutral-3); + border-right: 1px solid var(--color-neutral-3); +} + +.log-query-label .arco-select { + border-right: 1px solid var(--color-neutral-3); +} diff --git a/src/pages/components/LogLabel/index.tsx b/src/pages/components/LogLabel/index.tsx new file mode 100644 index 0000000..10dedef --- /dev/null +++ b/src/pages/components/LogLabel/index.tsx @@ -0,0 +1,80 @@ +import { Button, Select } from "@arco-design/web-react"; +import { IconClose } from "@arco-design/web-react/icon"; +import React from "react"; +import "./index.css"; + +export type Query = { + key: string; + condition: "=" | "=~" | "!=" | "!~"; + value: string; +}; + +export type Labels = { + [key: string]: { [key: string | number]: boolean }; +}; + +const LogLabel: React.FC<{ + value: Query; + labels: Labels; + onChange: (value: Query) => void; + onClose: () => void; +}> = ({ value, labels, onChange, onClose }) => { + const values = labels[value.key] || {}; + return ( +
+ + + +
+ ); +}; + +export default LogLabel; diff --git a/src/pages/components/ScriptMenuList/index.tsx b/src/pages/components/ScriptMenuList/index.tsx new file mode 100644 index 0000000..04831ac --- /dev/null +++ b/src/pages/components/ScriptMenuList/index.tsx @@ -0,0 +1,334 @@ +/* eslint-disable no-nested-ternary */ +import React, { useEffect, useState } from "react"; +import MessageInternal from "@App/app/message/internal"; +import { MessageSender } from "@App/app/message/message"; +import { ScriptMenu } from "@App/runtime/background/runtime"; +import { + Button, + Collapse, + Empty, + Message, + Popconfirm, + Space, + Switch, +} from "@arco-design/web-react"; +import { + IconCaretDown, + IconCaretUp, + IconDelete, + IconEdit, + IconMenu, + IconMinus, + IconSettings, +} from "@arco-design/web-react/icon"; +import IoC from "@App/app/ioc"; +import ScriptController from "@App/app/service/script/controller"; +import { SCRIPT_RUN_STATUS_RUNNING } from "@App/app/repo/scripts"; +import { RiPlayFill, RiStopFill } from "react-icons/ri"; +import RuntimeController from "@App/runtime/content/runtime"; +import { useTranslation } from "react-i18next"; +import { SystemConfig } from "@App/pkg/config/config"; +import { ScriptIcons } from "@App/pages/options/routes/utils"; + +const CollapseItem = Collapse.Item; + +function isExclude(script: ScriptMenu, host: string) { + if (!script.customExclude) { + return false; + } + for (let i = 0; i < script.customExclude.length; i += 1) { + if (script.customExclude[i] === `*://${host}*`) { + return true; + } + } + return false; +} + +// 用于popup页的脚本操作列表 +const ScriptMenuList: React.FC<{ + script: ScriptMenu[]; + isBackscript: boolean; + currentUrl: string; +}> = ({ script, isBackscript, currentUrl }) => { + const [list, setList] = useState([] as ScriptMenu[]); + const message = IoC.instance(MessageInternal) as MessageInternal; + const scriptCtrl = IoC.instance(ScriptController) as ScriptController; + const runtimeCtrl = IoC.instance(RuntimeController) as RuntimeController; + const systemConfig = IoC.instance(SystemConfig) as SystemConfig; + const [expandMenuIndex, setExpandMenuIndex] = useState<{ + [key: string]: boolean; + }>({}); + const { t } = useTranslation(); + + let url: URL; + try { + url = new URL(currentUrl); + } catch (e) { + // ignore error + } + useEffect(() => { + setList(script); + }, [script]); + + useEffect(() => { + // 监听脚本运行状态 + const channel = runtimeCtrl.watchRunStatus(); + channel.setHandler(([id, status]: any) => { + setList((prev) => { + const newList = [...prev]; + const index = newList.findIndex((item) => item.id === id); + if (index !== -1) { + newList[index].runStatus = status; + } + return newList; + }); + }); + return () => { + channel.disChannel(); + }; + }, []); + + const sendMenuAction = (sender: MessageSender, channelFlag: string) => { + let id = sender.tabId; + if (sender.frameId) { + id = sender.frameId; + } + message.broadcastChannel( + { + tag: sender.targetTag, + id: [id!], + }, + channelFlag, + "click" + ); + window.close(); + }; + // 监听菜单按键 + + // 菜单展开 + + return ( + <> + {list.length === 0 && } + {list.map((item, index) => ( + + { + e.stopPropagation(); + }} + title={ + // eslint-disable-next-line no-nested-ternary + item.enable + ? item.runNumByIframe + ? t("script_total_runs", { + runNum: item.runNum, + runNumByIframe: item.runNumByIframe, + })! + : t("script_total_runs_single", { runNum: item.runNum })! + : t("script_disabled")! + } + > + + { + let p: Promise; + if (checked) { + p = scriptCtrl.enable(item.id).then(() => { + item.enable = true; + }); + } else { + p = scriptCtrl.disable(item.id).then(() => { + item.enable = false; + }); + } + p.catch((err) => { + Message.error(err); + }).finally(() => { + setList([...list]); + }); + }} + /> + + + {item.name} + + + + } + name={item.id.toString()} + contentStyle={{ padding: "0 0 0 40px" }} + > +
+ {isBackscript && ( + + )} + + {url && ( + + )} + } + onOk={() => { + setList(list.filter((i) => i.id !== item.id)); + scriptCtrl.delete(item.id).catch((e) => { + Message.error(`{t('delete_failed')}: ${e}`); + }); + }} + > + + +
+
+
+ {/* 判断菜单数量,再判断是否展开 */} + {(item.menus && item.menus?.length > systemConfig.menuExpandNum + ? expandMenuIndex[index] + ? item.menus + : item.menus?.slice(0, systemConfig.menuExpandNum) + : item.menus + )?.map((menu) => { + if (menu.accessKey) { + document.addEventListener("keypress", (e) => { + if (e.key.toUpperCase() === menu.accessKey!.toUpperCase()) { + sendMenuAction(menu.sender, menu.channelFlag); + } + }); + } + return ( + + ); + })} + {item.menus && item.menus?.length > systemConfig.menuExpandNum && ( + + )} + {item.hasUserConfig && ( + + )} +
+
+ ))} + + ); +}; + +export default ScriptMenuList; diff --git a/src/pages/components/ScriptResource/index.tsx b/src/pages/components/ScriptResource/index.tsx new file mode 100644 index 0000000..7db0355 --- /dev/null +++ b/src/pages/components/ScriptResource/index.tsx @@ -0,0 +1,177 @@ +import { Resource } from "@App/app/repo/resource"; +import { Script } from "@App/app/repo/scripts"; +import { base64ToBlob } from "@App/pkg/utils/script"; +import { + Button, + Drawer, + Input, + Message, + Popconfirm, + Space, + Table, +} from "@arco-design/web-react"; +import { RefInputType } from "@arco-design/web-react/es/Input/interface"; +import { ColumnProps } from "@arco-design/web-react/es/Table"; +import { + IconDelete, + IconDownload, + IconSearch, +} from "@arco-design/web-react/icon"; +import React, { useEffect, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; + +type ResourceListItem = { + key: string; +} & Resource; + +const ScriptResource: React.FC<{ + // eslint-disable-next-line react/require-default-props + script?: Script; + visible: boolean; + onOk: () => void; + onCancel: () => void; +}> = ({ script, visible, onCancel, onOk }) => { + const [data, setData] = useState([]); + const inputRef = useRef(null); + // const resourceCtrl = IoC.instance(ResourceController) as ResourceController; + const { t } = useTranslation(); + + useEffect(() => { + if (!script) { + return () => {}; + } + resourceCtrl.getResource(script).then((res) => { + const arr: ResourceListItem[] = []; + Object.keys(res).forEach((key) => { + // @ts-ignore + const item: ResourceListItem = res[key]; + item.key = key; + arr.push(item); + }); + setData(arr); + }); + return () => {}; + }, [script]); + + const columns: ColumnProps[] = [ + { + title: t("key"), + dataIndex: "key", + key: "key", + filterIcon: , + // eslint-disable-next-line react/no-unstable-nested-components + filterDropdown: ({ filterKeys, setFilterKeys, confirm }: any) => { + return ( +
+ { + setFilterKeys(value ? [value] : []); + }} + onSearch={() => { + confirm(); + }} + /> +
+ ); + }, + onFilter: (value, row) => (value ? row.key.indexOf(value) !== -1 : true), + onFilterDropdownVisibleChange: (v) => { + if (v) { + setTimeout(() => inputRef.current!.focus(), 150); + } + }, + }, + { + title: t("type"), + dataIndex: "contentType", + width: 140, + key: "type", + render(col, res: Resource) { + return `${res.type}/${col}`; + }, + }, + { + title: t("action"), + render(_col, value: Resource, index) { + return ( + + + + + + + + ); +}; + +export default ScriptResource; diff --git a/src/pages/components/ScriptSetting/Match.tsx b/src/pages/components/ScriptSetting/Match.tsx new file mode 100644 index 0000000..82fff0b --- /dev/null +++ b/src/pages/components/ScriptSetting/Match.tsx @@ -0,0 +1,325 @@ +import React, { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { Script } from "@App/app/repo/scripts"; +import { + Space, + Popconfirm, + Button, + Divider, + Typography, + Modal, + Input, +} from "@arco-design/web-react"; +import Table, { ColumnProps } from "@arco-design/web-react/es/Table"; +import { IconDelete } from "@arco-design/web-react/icon"; + +type MatchItem = { + // id是为了避免match重复 + id: number; + match: string; + self: boolean; + hasMatch: boolean; + isExclude: boolean; +}; + +const Match: React.FC<{ + script: Script; +}> = ({ script }) => { + // const scriptCtrl = IoC.instance(ScriptController) as ScriptController; + const [match, setMatch] = useState([]); + const [exclude, setExclude] = useState([]); + const [matchValue, setMatchValue] = useState(""); + const [matchVisible, setMatchVisible] = useState(false); + const [excludeValue, setExcludeValue] = useState(""); + const [excludeVisible, setExcludeVisible] = useState(false); + const { t } = useTranslation(); // 使用 react-i18next 的 useTranslation 钩子函数获取翻译函数 + + useEffect(() => { + if (script) { + // 从数据库中获取是简单处理数据一致性的问题 + scriptCtrl.scriptDAO.findById(script.id).then((res) => { + if (!res) { + return; + } + const matchArr = res.selfMetadata?.match || res.metadata.match || []; + const matchMap = new Map(); + res.metadata.match?.forEach((m) => { + matchMap.set(m, true); + }); + const v: MatchItem[] = []; + matchArr.forEach((value, index) => { + if (matchMap.has(value)) { + v.push({ + id: index, + match: value, + self: false, + hasMatch: false, + isExclude: false, + }); + } else { + v.push({ + id: index, + match: value, + self: true, + hasMatch: false, + isExclude: false, + }); + } + }); + setMatch(v); + + const excludeArr = + res.selfMetadata?.exclude || res.metadata.exclude || []; + const excludeMap = new Map(); + res.metadata.exclude?.forEach((m) => { + excludeMap.set(m, true); + }); + const e: MatchItem[] = []; + excludeArr.forEach((value, index) => { + const hasMatch = matchMap.has(value); + if (excludeMap.has(value)) { + e.push({ + id: index, + match: value, + self: false, + hasMatch, + isExclude: true, + }); + } else { + e.push({ + id: index, + match: value, + self: true, + hasMatch, + isExclude: true, + }); + } + }); + setExclude(e); + }); + } + }, [script, exclude, match]); + + const columns: ColumnProps[] = [ + { + title: t("match"), + dataIndex: "match", + key: "match", + }, + { + title: t("user_setting"), + dataIndex: "self", + key: "self", + width: 100, + render(col) { + if (col) { + return {t("yes")}; + } + return {t("no")}; + }, + }, + { + title: t("action"), + render(_, item: MatchItem) { + if (item.isExclude) { + return ( + + { + exclude.splice(exclude.indexOf(item), 1); + scriptCtrl + .resetExclude( + script.id, + exclude.map((m) => m.match) + ) + .then(() => { + setExclude([...exclude]); + if (item.hasMatch) { + match.push(item); + scriptCtrl + .resetMatch( + script.id, + match.map((m) => m.match) + ) + .then(() => { + setMatch([...match]); + }); + } + }); + }} + > + + { + scriptCtrl.resetMatch(script.id, undefined).then(() => { + setMatch([]); + }); + }} + > + + + + +
+ +
+ {t("website_exclude")} + + + { + scriptCtrl.resetExclude(script.id, undefined).then(() => { + setExclude([]); + }); + }} + > + + + +
+
+ + + ); +}; + +export default Match; diff --git a/src/pages/components/ScriptSetting/Permission.tsx b/src/pages/components/ScriptSetting/Permission.tsx new file mode 100644 index 0000000..1ee0614 --- /dev/null +++ b/src/pages/components/ScriptSetting/Permission.tsx @@ -0,0 +1,196 @@ +import React, { useEffect, useState } from "react"; +import { Permission } from "@App/app/repo/permission"; +import { Script } from "@App/app/repo/scripts"; +import { useTranslation } from "react-i18next"; +import { + Space, + Popconfirm, + Message, + Button, + Checkbox, + Input, + Modal, + Select, + Typography, +} from "@arco-design/web-react"; +import Table, { ColumnProps } from "@arco-design/web-react/es/Table"; +import { IconDelete } from "@arco-design/web-react/icon"; + +const PermissionManager: React.FC<{ + script: Script; +}> = ({ script }) => { + // const permissionCtrl = IoC.instance( + // PermissionController + // ) as PermissionController; + const [permission, setPermission] = useState([]); + const [permissionVisible, setPermissionVisible] = useState(false); + const [permissionValue, setPermissionValue] = useState(); + + const { t } = useTranslation(); + + const columns: ColumnProps[] = [ + { + title: t("type"), + dataIndex: "permission", + key: "permission", + width: 100, + }, + { + title: t("permission_value"), + dataIndex: "permissionValue", + key: "permissionValue", + }, + { + title: t("allow"), + dataIndex: "allow", + key: "allow", + render(col) { + if (col) { + return {t("yes")}; + } + return {t("no")}; + }, + }, + { + title: t("action"), + render(_, item: Permission) { + return ( + + { + permissionCtrl + .deletePermission(script!.id, { + permission: item.permission, + permissionValue: item.permissionValue, + }) + .then(() => { + Message.success(t("delete_success")!); + setPermission(permission.filter((i) => i.id !== item.id)); + }) + .catch(() => { + Message.error(t("delete_failed")!); + }); + }} + > + + { + permissionCtrl.resetPermission(script.id).then(() => { + setPermission([]); + }); + }} + > + + + + +
+ + ); +}; + +export default PermissionManager; diff --git a/src/pages/components/ScriptSetting/index.tsx b/src/pages/components/ScriptSetting/index.tsx new file mode 100644 index 0000000..2be2d44 --- /dev/null +++ b/src/pages/components/ScriptSetting/index.tsx @@ -0,0 +1,106 @@ +import { Script } from "@App/app/repo/scripts"; +import { formatUnixTime } from "@App/pkg/utils/utils"; +import { + Descriptions, + Divider, + Drawer, + Empty, + Input, + Message, +} from "@arco-design/web-react"; +import React, { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import Match from "./Match"; +import PermissionManager from "./Permission"; + +const ScriptSetting: React.FC<{ + script: Script; + visible: boolean; + onOk: () => void; + onCancel: () => void; +}> = ({ script, visible, onCancel, onOk }) => { + // const scriptCtrl = IoC.instance(ScriptController) as ScriptController; + const [checkUpdateUrl, setCheckUpdateUrl] = useState(""); + + const { t } = useTranslation(); + + useEffect(() => { + if (script) { + scriptCtrl.scriptDAO.findById(script.id).then((v) => { + setCheckUpdateUrl(v?.downloadUrl || ""); + }); + } + }, [script]); + + return ( + + {script?.name} {t("script_setting")} + + } + autoFocus={false} + focusLock={false} + visible={visible} + onOk={() => { + onOk(); + }} + onCancel={() => { + onCancel(); + }} + > + + + {script && } + { + setCheckUpdateUrl(e); + }} + onBlur={() => { + scriptCtrl + .updateCheckUpdateUrl(script!.id, checkUpdateUrl) + .then(() => { + Message.success(t("update_success")!); + }); + }} + /> + ), + }, + ]} + style={{ marginBottom: 20 }} + labelStyle={{ paddingRight: 36 }} + /> + + {script && } + + + ); +}; + +export default ScriptSetting; diff --git a/src/pages/components/ScriptStorage/index.tsx b/src/pages/components/ScriptStorage/index.tsx new file mode 100644 index 0000000..c62f3c7 --- /dev/null +++ b/src/pages/components/ScriptStorage/index.tsx @@ -0,0 +1,286 @@ +import { Script } from "@App/app/repo/scripts"; +import { Value } from "@App/app/repo/value"; +import { valueType } from "@App/pkg/utils/utils"; +import { Button, Drawer, Form, Input, Message, Modal, Popconfirm, Select, Space, Table } from "@arco-design/web-react"; +import { RefInputType } from "@arco-design/web-react/es/Input/interface"; +import { ColumnProps } from "@arco-design/web-react/es/Table"; +import { IconDelete, IconEdit, IconSearch } from "@arco-design/web-react/icon"; +import React, { useEffect, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; + +const FormItem = Form.Item; + +const ScriptStorage: React.FC<{ + // eslint-disable-next-line react/require-default-props + script?: Script; + visible: boolean; + onOk: () => void; + onCancel: () => void; +}> = ({ script, visible, onCancel, onOk }) => { + const [data, setData] = useState([]); + const inputRef = useRef(null); + const [currentValue, setCurrentValue] = useState(); + const [visibleEdit, setVisibleEdit] = useState(false); + const [form] = Form.useForm(); + const { t } = useTranslation(); + + useEffect(() => { + if (!script) { + return () => {}; + } + // valueCtrl.getValues(script).then((values) => { + // setData(values); + // }); + // Monitor value changes + // const channel = valueCtrl.watchValue(script); + // channel.setHandler((value: Value) => { + // setData((prev) => { + // const index = prev.findIndex((item) => item.key === value.key); + // if (index === -1) { + // if (value.value === undefined) { + // return prev; + // } + // return [value, ...prev]; + // } + // if (value.value === undefined) { + // prev.splice(index, 1); + // return [...prev]; + // } + // prev[index] = value; + // return [...prev]; + // }); + // }); + return () => { + // channel.disChannel(); + }; + }, [script]); + const columns: ColumnProps[] = [ + { + title: t("key"), + dataIndex: "key", + key: "key", + filterIcon: , + width: 140, + // eslint-disable-next-line react/no-unstable-nested-components + filterDropdown: ({ filterKeys, setFilterKeys, confirm }: any) => { + return ( +
+ { + setFilterKeys(value ? [value] : []); + }} + onSearch={() => { + confirm(); + }} + /> +
+ ); + }, + onFilter: (value, row) => (value ? row.key.indexOf(value) !== -1 : true), + onFilterDropdownVisibleChange: (v) => { + if (v) { + setTimeout(() => inputRef.current!.focus(), 150); + } + }, + }, + { + title: t("value"), + dataIndex: "value", + key: "value", + className: "max-table-cell", + render(col) { + switch (typeof col) { + case "string": + return col; + default: + return ( + + {JSON.stringify(col, null, 2)} + + ); + } + }, + }, + { + title: t("type"), + dataIndex: "value", + width: 90, + key: "type", + render(col) { + return valueType(col); + }, + }, + { + title: t("action"), + render(_col, value: Value, index) { + return ( + + + + + +
+ + + ); +}; + +export default ScriptStorage; diff --git a/src/pages/components/UserConfigPanel/index.tsx b/src/pages/components/UserConfigPanel/index.tsx new file mode 100644 index 0000000..8ba6106 --- /dev/null +++ b/src/pages/components/UserConfigPanel/index.tsx @@ -0,0 +1,200 @@ +import React, { useEffect, useRef } from "react"; +import { useTranslation } from "react-i18next"; // 添加这行导入语句 +import { Script, UserConfig } from "@App/app/repo/scripts"; +import { + Checkbox, + Form, + FormInstance, + Input, + InputNumber, + Message, + Modal, + Select, + Tabs, +} from "@arco-design/web-react"; +import TabPane from "@arco-design/web-react/es/Tabs/tab-pane"; + +const FormItem = Form.Item; + +const UserConfigPanel: React.FC<{ + script: Script; + userConfig: UserConfig; + values: { [key: string]: any }; +}> = ({ script, userConfig, values }) => { + const formRefs = useRef<{ [key: string]: FormInstance }>({}); + const [visible, setVisible] = React.useState(true); + const [tab, setTab] = React.useState(Object.keys(userConfig)[0]); + useEffect(() => { + setTab(Object.keys(userConfig)[0]); + setVisible(true); + }, [script, userConfig]); + + const { t } = useTranslation(); + + return ( + { + if (formRefs.current[tab]) { + const saveValues = formRefs.current[tab].getFieldsValue(); + // 更新value + const valueCtrl = IoC.instance(ValueController) as ValueController; + Object.keys(saveValues).forEach((key) => { + Object.keys(saveValues[key]).forEach((valueKey) => { + if (saveValues[key][valueKey] === undefined) { + return; + } + valueCtrl.setValue( + script.id, + `${key}.${valueKey}`, + saveValues[key][valueKey] + ); + }); + }); + Message.success(t("save_success")!); // 替换为键值对应的英文文本 + setVisible(false); + } + }} + onCancel={() => { + setVisible(false); + }} + > + { + setTab(value); + }} + > + {Object.keys(userConfig).map((itemKey) => { + const value = userConfig[itemKey]; + return ( + +
{ + formRefs.current[itemKey] = el; + }} + > + {Object.keys(value).map((key) => ( + + {() => { + const item = value[key]; + let { type } = item; + if (!type) { + // 根据其他值判断类型 + if (typeof item.default === "boolean") { + type = "checkbox"; + } else if (item.values) { + if (typeof item.values === "object") { + type = "mult-select"; + } else { + type = "select"; + } + } else if (typeof item.default === "number") { + type = "number"; + } else { + type = "text"; + } + } + switch (type) { + case "text": + if (item.password) { + return ( + + ); + } + return ( + + ); + case "number": + return ( + + ); + case "checkbox": + return ( + + {item.description} + + ); + case "select": + case "mult-select": + // eslint-disable-next-line no-case-declarations + let options: any[]; + if (item.bind) { + const bindKey = item.bind.substring(1); + if (values[bindKey]) { + options = values[bindKey]!; + } else { + options = []; + } + } else { + options = item.values!; + } + return ( + + ); + case "textarea": + return ( + + ); + default: + return null; + } + }} + + ))} + +
+ ); + })} +
+
+ ); +}; + +export default UserConfigPanel; diff --git a/src/pages/components/layout/Sider.tsx b/src/pages/components/layout/Sider.tsx new file mode 100644 index 0000000..0fd94c1 --- /dev/null +++ b/src/pages/components/layout/Sider.tsx @@ -0,0 +1,210 @@ +import Logger from "@App/pages/options/routes/Logger"; +import ScriptEditor from "@App/pages/options/routes/script/ScriptEditor"; +import ScriptList from "@App/pages/options/routes/ScriptList"; +import Setting from "@App/pages/options/routes/Setting"; +import SubscribeList from "@App/pages/options/routes/SubscribeList"; +import Tools from "@App/pages/options/routes/Tools"; +import { Layout, Menu } from "@arco-design/web-react"; +import { + IconCode, + IconFile, + IconGithub, + IconLeft, + IconLink, + IconQuestion, + IconRight, + IconSettings, + IconSubscribe, + IconTool, +} from "@arco-design/web-react/icon"; +import React, { useRef, useState } from "react"; +import { HashRouter, Route, Routes } from "react-router-dom"; +import { useTranslation } from "react-i18next"; +import { RiFileCodeLine, RiGuideLine, RiLinkM } from "react-icons/ri"; +import SiderGuide from "./SiderGuide"; +import CustomLink from "../CustomLink"; + +const MenuItem = Menu.Item; +let { hash } = window.location; +if (!hash.length) { + hash = "/"; +} else { + hash = hash.substring(1); +} + +const Sider: React.FC = () => { + const [menuSelect, setMenuSelect] = useState(hash); + const [collapsed, setCollapsed] = useState(localStorage.collapsed === "true"); + const { t } = useTranslation(); + const guideRef = useRef<{ open: () => void }>(null); + + return ( + + + +
+ { + setMenuSelect(key); + }} + > + + + {t("installed_scripts")} + + + + + {t("subscribe")} + + + + + {t("logs")} + + + + + {t("tools")} + + + + + {t("settings")} + + + + { + setMenuSelect(key); + }} + mode="pop" + > + + {t("helpcenter")} + + } + triggerProps={{ + trigger: "hover", + }} + > + + {t("external_links")} + + } + > + + + {t("api_docs")} + + + + + {t("development_guide")} + + + + + {t("script_gallery")} + + + + + {t("community_forum")} + + + + + GitHub + + + + { + guideRef.current?.open(); + }} + > + {t("guide")} + + + + {t("user_guide")} + + + + { + localStorage.collapsed = !collapsed; + setCollapsed(!collapsed); + }} + > + {collapsed ? : } {t("collapsible")} + + +
+
+ + + } /> + + } /> + } /> + + } /> + } /> + } /> + } /> + + +
+ ); +}; + +export default Sider; diff --git a/src/pages/components/layout/SiderGuide.tsx b/src/pages/components/layout/SiderGuide.tsx new file mode 100644 index 0000000..e429d2d --- /dev/null +++ b/src/pages/components/layout/SiderGuide.tsx @@ -0,0 +1,157 @@ +import React, { useEffect, useImperativeHandle, useState } from "react"; +import { useTranslation } from "react-i18next"; +import Joyride, { Step } from "react-joyride"; +import { Path, useLocation, useNavigate } from "react-router-dom"; +import CustomTrans from "../CustomTrans"; + +const SiderGuide: React.ForwardRefRenderFunction<{ open: () => void }, object> = ( + _props, + ref +) => { + const { t } = useTranslation(); + const [stepIndex, setStepIndex] = useState(0); + const [initRoute, setInitRoute] = useState>({ pathname: "/" }); + const [run, setRun] = useState(false); + const navigate = useNavigate(); + const location = useLocation(); + useImperativeHandle(ref, () => ({ + open: () => setRun(true), + })); + useEffect(() => { + // 首次使用时,打开引导 + if (localStorage.getItem("firstUse") === null) { + localStorage.setItem("firstUse", "false"); + setRun(true); + } + }, []); + + const steps: Array = [ + { + title: t("start_guide_title"), + content: t("start_guide_content"), + target: "body", + placement: "center", + }, + { + title: t("installed_scripts"), + content: t("guide_installed_scripts"), + target: ".menu-script", + }, + { + content: , + target: "#script-list", + title: t("guide_script_list_title"), + placement: "auto", + }, + { + content: t("guide_script_list_enable_content"), + target: ".script-enable", + title: t("guide_script_list_enable_title"), + }, + { + content: t("guide_script_list_apply_to_run_status_content"), + target: ".apply_to_run_status", + title: t("guide_script_list_apply_to_run_status_title"), + }, + { + target: ".script-sort", + title: t("guide_script_list_sort_title"), + content: , + }, + { + target: ".menu-tools", + title: t("guide_tools_title"), + content: t("guide_tools_content"), + placement: "auto", + }, + { + target: ".tools .backup", + title: t("guide_tools_backup_title"), + content: t("guide_tools_backup_content"), + }, + { + target: ".menu-setting", + title: t("guide_setting_title"), + content: t("guide_setting_content"), + placement: "auto", + }, + { + target: ".setting .sync", + title: t("guide_setting_sync_title"), + content: t("guide_setting_sync_content"), + }, + ]; + + const gotoNavigate = (go: Partial) => { + if (go.pathname !== location.pathname) { + return navigate(go); + } + if (go.search !== location.search) { + return navigate(go); + } + if (go.hash !== location.hash) { + return navigate(go); + } + return true; + }; + + return ( + { + if ( + data.action === "stop" || + data.action === "close" || + data.status === "finished" + ) { + setRun(false); + setStepIndex(0); + gotoNavigate(initRoute); + } else if (data.action === "next" && data.lifecycle === "complete") { + switch (data.index) { + case 5: + gotoNavigate({ pathname: "/tools" }); + break; + case 7: + gotoNavigate({ pathname: "/setting" }); + break; + default: + break; + } + setStepIndex(data.index + 1); + } else if (data.action === "prev" && data.lifecycle === "complete") { + setStepIndex(data.index - 1); + } else if (data.action === "start" && data.lifecycle === "init") { + gotoNavigate({ pathname: "/" }); + setInitRoute({ + pathname: location.pathname, + search: location.search, + hash: location.hash, + }); + } + }} + locale={{ + next: t("next"), + skip: t("skip"), + back: t("back"), + last: t("last"), + }} + continuous + run={run} + scrollToFirstStep + showProgress + showSkipButton + stepIndex={stepIndex} + steps={steps} + disableOverlayClose + disableScrolling + spotlightPadding={0} + styles={{ + options: { + zIndex: 10000, + }, + }} + /> + ); +}; + +export default React.forwardRef(SiderGuide); diff --git a/src/pages/install/App.tsx b/src/pages/install/App.tsx index 7ead88d..bbe91b9 100644 --- a/src/pages/install/App.tsx +++ b/src/pages/install/App.tsx @@ -6,7 +6,7 @@ import { Subscribe } from "@App/app/repo/subscribe"; import { i18nDescription, i18nName } from "@App/locales/locales"; import { useTranslation } from "react-i18next"; import { prepareScriptByCode, prepareSubscribeByCode, ScriptInfo } from "@App/pkg/utils/script"; -import { isDebug, nextTime } from "@App/pkg/utils/utils"; +import { nextTime } from "@App/pkg/utils/utils"; import { ScriptClient } from "@App/app/service/service_worker/client"; type Permission = { label: string; color?: string; value: string[] }[]; @@ -156,11 +156,13 @@ function App() { setScriptInfo(info); setEnable(action.status === SCRIPT_STATUS_ENABLE); setUpsertScript(action); + // 修改网页显示title + document.title = `${!isUpdate ? t("install_script") : t("update_script")} - ${i18nName(action)} - ScriptCat`; }) .catch(() => { Message.error(t("script_info_load_failed")); }); - }, [t]); + }, [isUpdate, t]); return (
@@ -253,11 +255,9 @@ function App() { Message.success(t("install_success")!); setBtnText(t("install_success")!); } - // eslint-disable-next-line @typescript-eslint/no-unused-expressions - !isDebug() && - setTimeout(() => { - closeWindow(); - }, 200); + setTimeout(() => { + closeWindow(); + }, 500); }) .catch((e) => { Message.error(`${t("install_failed")}: ${e}`); diff --git a/src/pages/options.html b/src/pages/options.html new file mode 100644 index 0000000..948ae74 --- /dev/null +++ b/src/pages/options.html @@ -0,0 +1,24 @@ + + + + + + <%= htmlRspackPlugin.options.title %> + + +
+ + + + <% if rspackConfig.mode=="script" { %> + + + <% } %> + diff --git a/src/pages/options/main.tsx b/src/pages/options/main.tsx index 234ae2a..536b8ba 100644 --- a/src/pages/options/main.tsx +++ b/src/pages/options/main.tsx @@ -4,22 +4,26 @@ import MainLayout from "../components/layout/MainLayout.tsx"; import "@arco-design/web-react/dist/css/arco.css"; import "@App/locales/locales"; import "@App/index.css"; +import "./index.css"; import { Provider } from "react-redux"; import { store } from "@App/store/store.ts"; import { Broker } from "@Packages/message/message_queue.ts"; +import Sider from "../components/layout/Sider.tsx"; -// 测试监听广播 +// // 测试监听广播 -const border = new Broker(); +// const border = new Broker(); -border.subscribe("installScript", (message) => { - console.log(message); -}); +// border.subscribe("installScript", (message) => { +// console.log(message); +// }); ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( - p + + + ); diff --git a/src/pages/options/routes/Logger.tsx b/src/pages/options/routes/Logger.tsx index 9ee75bc..1c11675 100644 --- a/src/pages/options/routes/Logger.tsx +++ b/src/pages/options/routes/Logger.tsx @@ -1,14 +1,5 @@ import React, { useEffect } from "react"; -import { - BackTop, - Button, - Card, - DatePicker, - Input, - List, - Message, - Space, -} from "@arco-design/web-react"; +import { BackTop, Button, Card, DatePicker, Input, List, Message, Space } from "@arco-design/web-react"; import dayjs from "dayjs"; import Text from "@arco-design/web-react/es/Typography/text"; import { Logger, LoggerDAO } from "@App/app/repo/logger"; @@ -16,8 +7,6 @@ import LogLabel, { Labels, Query } from "@App/pages/components/LogLabel"; import { IconPlus } from "@arco-design/web-react/icon"; import { useSearchParams } from "react-router-dom"; import { formatUnixTime } from "@App/pkg/utils/utils"; -import { SystemConfig } from "@App/pkg/config/config"; -import IoC from "@App/app/ioc"; import { useTranslation } from "react-i18next"; function LoggerPage() { @@ -28,12 +17,10 @@ function LoggerPage() { const [logs, setLogs] = React.useState([]); const [queryLogs, setQueryLogs] = React.useState([]); const [search, setSearch] = React.useState(""); - const [startTime, setStartTime] = React.useState( - dayjs().subtract(24, "hour").unix() - ); + const [startTime, setStartTime] = React.useState(dayjs().subtract(24, "hour").unix()); const [endTime, setEndTime] = React.useState(dayjs().unix()); const loggerDAO = new LoggerDAO(); - const systemConfig = IoC.instance(SystemConfig) as SystemConfig; + const systemConfig = { logCleanCycle: 1 }; const { t } = useTranslation(); const onQueryLog = () => { @@ -46,35 +33,27 @@ function LoggerPage() { const value = log.label[query.key]; switch (query.condition) { case "=": - // eslint-disable-next-line eqeqeq if (value != query.value) { return; } break; case "=~": - if ( - typeof value === "string" && - value.indexOf(query.value) === -1 - ) { + if (typeof value === "string" && value.indexOf(query.value) === -1) { return; } break; case "!=": - // eslint-disable-next-line eqeqeq if (value == query.value) { return; } break; case "!~": - if ( - typeof value === "string" && - value.indexOf(query.value) === -1 - ) { + if (typeof value === "string" && value.indexOf(query.value) === -1) { return; } break; default: - // eslint-disable-next-line eqeqeq + if (value != query.value) { return; } @@ -129,11 +108,7 @@ function LoggerPage() { return ( <> - document.getElementById("backtop")!} - /> + document.getElementById("backtop")!} />
- {formatUnixTime(startTime)} {t("to")} {formatUnixTime(endTime)}{" "} - {t("total_logs", { length: logs.length })} + {formatUnixTime(startTime)} {t("to")} {formatUnixTime(endTime)} {t("total_logs", { length: logs.length })} {init === 4 ? `, ${t("filtered_logs", { length: queryLogs.length })}` : `, ${t("enter_filter_conditions")}`} @@ -334,20 +308,18 @@ function LoggerPage() { key={index} style={{ background: - // eslint-disable-next-line no-nested-ternary + item.level === "error" - ? "var(--color-danger-light-2)" // eslint-disable-next-line no-nested-ternary + ? "var(--color-danger-light-2)" : item.level === "warn" - ? "var(--color-warning-light-2)" - : item.level === "info" - ? "var(--color-success-light-2)" - : "var(--color-primary-light-1)", + ? "var(--color-warning-light-2)" + : item.level === "info" + ? "var(--color-success-light-2)" + : "var(--color-primary-light-1)", }} > {formatUnixTime(item.createtime / 1000)}{" "} - {typeof item.message === "object" - ? JSON.stringify(item.message) - : item.message}{" "} + {typeof item.message === "object" ? JSON.stringify(item.message) : item.message}{" "} {JSON.stringify(item.label)} )} diff --git a/src/pages/options/routes/ScriptList.tsx b/src/pages/options/routes/ScriptList.tsx index af90d5f..f028795 100644 --- a/src/pages/options/routes/ScriptList.tsx +++ b/src/pages/options/routes/ScriptList.tsx @@ -25,7 +25,6 @@ import { SCRIPT_STATUS_ENABLE, SCRIPT_TYPE_BACKGROUND, SCRIPT_TYPE_NORMAL, - ScriptDAO, UserConfig, } from "@App/app/repo/scripts"; import { @@ -46,7 +45,6 @@ import { RiUploadCloudFill, } from "react-icons/ri"; import { Link, useNavigate, useSearchParams } from "react-router-dom"; -import ScriptController from "@App/app/service/script/controller"; import { RefInputType } from "@arco-design/web-react/es/Input/interface"; import Text from "@arco-design/web-react/es/Typography/text"; import { @@ -59,28 +57,21 @@ import { useSensors, } from "@dnd-kit/core"; import { - arrayMove, SortableContext, sortableKeyboardCoordinates, useSortable, verticalListSortingStrategy, } from "@dnd-kit/sortable"; import { CSS } from "@dnd-kit/utilities"; -import IoC from "@App/app/ioc"; -import RuntimeController from "@App/runtime/content/runtime"; import UserConfigPanel from "@App/pages/components/UserConfigPanel"; import CloudScriptPlan from "@App/pages/components/CloudScriptPlan"; -import SynchronizeController from "@App/app/service/synchronize/controller"; import { useTranslation } from "react-i18next"; import { nextTime, semTime } from "@App/pkg/utils/utils"; import { i18nName } from "@App/locales/locales"; -import { SystemConfig } from "@App/pkg/config/config"; -import { - getValues, - ListHomeRender, - ScriptIcons, - scriptListSort, -} from "./utils"; +import { ListHomeRender, ScriptIcons } from "./utils"; +import { useAppDispatch, useAppSelector } from "@App/store/hooks"; +import { fetchScriptList, selectScripts } from "@App/store/features/script"; +import { selectScriptListColumnWidth } from "@App/store/features/setting"; type ListType = Script & { loading?: boolean }; @@ -91,43 +82,38 @@ function ScriptList() { values: { [key: string]: any }; }>(); const [cloudScript, setCloudScript] = useState