options页面组件
This commit is contained in:
parent
78152222f3
commit
9876c1cbcb
@ -26,6 +26,7 @@ export default [
|
|||||||
rules: {
|
rules: {
|
||||||
"@typescript-eslint/no-explicit-any": "off",
|
"@typescript-eslint/no-explicit-any": "off",
|
||||||
"@typescript-eslint/ban-ts-comment": "off",
|
"@typescript-eslint/ban-ts-comment": "off",
|
||||||
|
"@typescript-eslint/no-unused-expressions": "off",
|
||||||
...reactHooks.configs.recommended.rules,
|
...reactHooks.configs.recommended.rules,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -17,6 +17,9 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@arco-design/web-react": "^2.64.1",
|
"@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",
|
"@reduxjs/toolkit": "^2.3.0",
|
||||||
"cron": "^3.2.1",
|
"cron": "^3.2.1",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
@ -29,7 +32,9 @@
|
|||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-i18next": "^15.1.0",
|
"react-i18next": "^15.1.0",
|
||||||
"react-icons": "^5.3.0",
|
"react-icons": "^5.3.0",
|
||||||
|
"react-joyride": "^2.9.3",
|
||||||
"react-redux": "^9.1.2",
|
"react-redux": "^9.1.2",
|
||||||
|
"react-router-dom": "^7.1.1",
|
||||||
"semver": "^7.6.3",
|
"semver": "^7.6.3",
|
||||||
"uuid": "^11.0.3",
|
"uuid": "^11.0.3",
|
||||||
"yaml": "^2.6.1"
|
"yaml": "^2.6.1"
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import EventEmitter from "eventemitter3";
|
||||||
import { connect } from "./client";
|
import { connect } from "./client";
|
||||||
import { ApiFunction, Server } from "./server";
|
import { ApiFunction, Server } from "./server";
|
||||||
|
|
||||||
@ -24,6 +25,8 @@ export class Broker {
|
|||||||
export class MessageQueue {
|
export class MessageQueue {
|
||||||
topicConMap: Map<string, { name: string; con: chrome.runtime.Port }[]> = new Map();
|
topicConMap: Map<string, { name: string; con: chrome.runtime.Port }[]> = new Map();
|
||||||
|
|
||||||
|
private EE: EventEmitter = new EventEmitter();
|
||||||
|
|
||||||
constructor(api: Server) {
|
constructor(api: Server) {
|
||||||
api.on("messageQueue", this.handler());
|
api.on("messageQueue", this.handler());
|
||||||
}
|
}
|
||||||
@ -69,5 +72,16 @@ export class MessageQueue {
|
|||||||
list?.forEach((item) => {
|
list?.forEach((item) => {
|
||||||
item.con.postMessage({ action: "message", topic, message });
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
238
pnpm-lock.yaml
generated
238
pnpm-lock.yaml
generated
@ -11,6 +11,15 @@ importers:
|
|||||||
'@arco-design/web-react':
|
'@arco-design/web-react':
|
||||||
specifier: ^2.64.1
|
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)
|
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':
|
'@reduxjs/toolkit':
|
||||||
specifier: ^2.3.0
|
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)
|
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:
|
react-icons:
|
||||||
specifier: ^5.3.0
|
specifier: ^5.3.0
|
||||||
version: 5.3.0(react@18.3.1)
|
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:
|
react-redux:
|
||||||
specifier: ^9.1.2
|
specifier: ^9.1.2
|
||||||
version: 9.1.2(@types/react@18.3.12)(react@18.3.1)(redux@5.0.1)
|
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:
|
semver:
|
||||||
specifier: ^7.6.3
|
specifier: ^7.6.3
|
||||||
version: 7.6.3
|
version: 7.6.3
|
||||||
@ -309,6 +324,28 @@ packages:
|
|||||||
resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==}
|
resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==}
|
||||||
engines: {node: '>=10.0.0'}
|
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':
|
'@esbuild/aix-ppc64@0.21.5':
|
||||||
resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==}
|
resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@ -634,6 +671,12 @@ packages:
|
|||||||
resolution: {integrity: sha512-HFZ4Mp26nbWk9d/BpvP0YNL6W4UoZF0VFcTw/aPPA8RpOxeFQgK+ClABGgAUXs9Y/RGX/l1vOmrqz1MQt9MNuw==}
|
resolution: {integrity: sha512-HFZ4Mp26nbWk9d/BpvP0YNL6W4UoZF0VFcTw/aPPA8RpOxeFQgK+ClABGgAUXs9Y/RGX/l1vOmrqz1MQt9MNuw==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
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':
|
'@humanfs/core@0.19.0':
|
||||||
resolution: {integrity: sha512-2cbWIHbZVEweE853g8jymffCA+NCMiuqeECeBBLm8dg2oFdjuGJhgN4UAbI+6v0CKbbhvtXA4qV8YR5Ji86nmw==}
|
resolution: {integrity: sha512-2cbWIHbZVEweE853g8jymffCA+NCMiuqeECeBBLm8dg2oFdjuGJhgN4UAbI+6v0CKbbhvtXA4qV8YR5Ji86nmw==}
|
||||||
engines: {node: '>=18.18.0'}
|
engines: {node: '>=18.18.0'}
|
||||||
@ -983,6 +1026,9 @@ packages:
|
|||||||
'@types/connect@3.4.38':
|
'@types/connect@3.4.38':
|
||||||
resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==}
|
resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==}
|
||||||
|
|
||||||
|
'@types/cookie@0.6.0':
|
||||||
|
resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==}
|
||||||
|
|
||||||
'@types/eslint-scope@3.7.7':
|
'@types/eslint-scope@3.7.7':
|
||||||
resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==}
|
resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==}
|
||||||
|
|
||||||
@ -1644,6 +1690,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==}
|
resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
|
cookie@1.0.2:
|
||||||
|
resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
core-js@3.38.1:
|
core-js@3.38.1:
|
||||||
resolution: {integrity: sha512-OP35aUorbU3Zvlx7pjsFdu1rGNnD4pgw/CWoYzRY3t2EzoVT7shKHY1dlAy3f41cGIO7ZDPQimhGFTlEYkG/Hw==}
|
resolution: {integrity: sha512-OP35aUorbU3Zvlx7pjsFdu1rGNnD4pgw/CWoYzRY3t2EzoVT7shKHY1dlAy3f41cGIO7ZDPQimhGFTlEYkG/Hw==}
|
||||||
|
|
||||||
@ -1724,6 +1774,9 @@ packages:
|
|||||||
decimal.js@10.4.3:
|
decimal.js@10.4.3:
|
||||||
resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==}
|
resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==}
|
||||||
|
|
||||||
|
deep-diff@1.0.2:
|
||||||
|
resolution: {integrity: sha512-aWS3UIVH+NPGCD1kki+DCU9Dua032iSsO43LqQpcs4R3+dVv7tX0qBGjiVHJHjplsoUM2XRO/KB92glqc68awg==}
|
||||||
|
|
||||||
deep-eql@5.0.2:
|
deep-eql@5.0.2:
|
||||||
resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==}
|
resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
@ -1731,6 +1784,10 @@ packages:
|
|||||||
deep-is@0.1.4:
|
deep-is@0.1.4:
|
||||||
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
|
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:
|
default-browser-id@5.0.0:
|
||||||
resolution: {integrity: sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==}
|
resolution: {integrity: sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
@ -2367,6 +2424,12 @@ packages:
|
|||||||
engines: {node: '>=14.16'}
|
engines: {node: '>=14.16'}
|
||||||
hasBin: true
|
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:
|
is-map@2.0.3:
|
||||||
resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==}
|
resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@ -2852,6 +2915,10 @@ packages:
|
|||||||
pkg-types@1.2.1:
|
pkg-types@1.2.1:
|
||||||
resolution: {integrity: sha512-sQoqa8alT3nHjGuTjuKgOnvjo4cljkufdtLMnO2LBP/wRwuDlo1tkaEdMxCRhyGRPacv/ztlZgDPm2b7FAmEvw==}
|
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:
|
possible-typed-array-names@1.0.0:
|
||||||
resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==}
|
resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@ -2927,6 +2994,12 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^18.3.1
|
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:
|
react-focus-lock@2.13.2:
|
||||||
resolution: {integrity: sha512-T/7bsofxYqnod2xadvuwjGKHOoL5GH7/EIPI5UyEvaU/c2CcphvGI371opFtuY/SYdbMsNiuF4HsHQ50nA/TKQ==}
|
resolution: {integrity: sha512-T/7bsofxYqnod2xadvuwjGKHOoL5GH7/EIPI5UyEvaU/c2CcphvGI371opFtuY/SYdbMsNiuF4HsHQ50nA/TKQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -2954,12 +3027,24 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: '*'
|
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:
|
react-is@16.13.1:
|
||||||
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
|
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
|
||||||
|
|
||||||
react-is@18.3.1:
|
react-is@18.3.1:
|
||||||
resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==}
|
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:
|
react-redux@9.1.2:
|
||||||
resolution: {integrity: sha512-0OA4dhM1W48l3uzmv6B7TXPCGmokUU4p1M44DGN2/D9a1FjVPukVjER1PcPX97jIg6aUeLq1XJo1IpfbgULn0w==}
|
resolution: {integrity: sha512-0OA4dhM1W48l3uzmv6B7TXPCGmokUU4p1M44DGN2/D9a1FjVPukVjER1PcPX97jIg6aUeLq1XJo1IpfbgULn0w==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -2972,6 +3057,23 @@ packages:
|
|||||||
redux:
|
redux:
|
||||||
optional: true
|
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:
|
react-transition-group@4.4.5:
|
||||||
resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==}
|
resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -3123,6 +3225,12 @@ packages:
|
|||||||
scroll-into-view-if-needed@2.2.31:
|
scroll-into-view-if-needed@2.2.31:
|
||||||
resolution: {integrity: sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==}
|
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:
|
select-hose@2.0.0:
|
||||||
resolution: {integrity: sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==}
|
resolution: {integrity: sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==}
|
||||||
|
|
||||||
@ -3154,6 +3262,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==}
|
resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==}
|
||||||
engines: {node: '>= 0.8.0'}
|
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:
|
set-function-length@1.2.2:
|
||||||
resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
|
resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@ -3399,6 +3510,12 @@ packages:
|
|||||||
resolution: {integrity: sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==}
|
resolution: {integrity: sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==}
|
||||||
engines: {node: '>=18'}
|
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:
|
tree-dump@1.0.2:
|
||||||
resolution: {integrity: sha512-dpev9ABuLWdEubk+cIaI9cHwRNNDjkBBLXTwI4UCUFdQ5xXKqNXoK4FEciw/vxf+NQ7Cb7sGUyeUtORvHIdRXQ==}
|
resolution: {integrity: sha512-dpev9ABuLWdEubk+cIaI9cHwRNNDjkBBLXTwI4UCUFdQ5xXKqNXoK4FEciw/vxf+NQ7Cb7sGUyeUtORvHIdRXQ==}
|
||||||
engines: {node: '>=10.0'}
|
engines: {node: '>=10.0'}
|
||||||
@ -3433,10 +3550,17 @@ packages:
|
|||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
turbo-stream@2.4.0:
|
||||||
|
resolution: {integrity: sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==}
|
||||||
|
|
||||||
type-check@0.4.0:
|
type-check@0.4.0:
|
||||||
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
|
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
|
||||||
engines: {node: '>= 0.8.0'}
|
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:
|
type-is@1.6.18:
|
||||||
resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
|
resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
@ -3882,6 +4006,31 @@ snapshots:
|
|||||||
|
|
||||||
'@discoveryjs/json-ext@0.5.7': {}
|
'@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':
|
'@esbuild/aix-ppc64@0.21.5':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@ -4075,6 +4224,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
levn: 0.4.1
|
levn: 0.4.1
|
||||||
|
|
||||||
|
'@gilbarbara/deep-equal@0.1.2': {}
|
||||||
|
|
||||||
|
'@gilbarbara/deep-equal@0.3.1': {}
|
||||||
|
|
||||||
'@humanfs/core@0.19.0': {}
|
'@humanfs/core@0.19.0': {}
|
||||||
|
|
||||||
'@humanfs/node@0.16.5':
|
'@humanfs/node@0.16.5':
|
||||||
@ -4448,6 +4601,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 22.10.2
|
'@types/node': 22.10.2
|
||||||
|
|
||||||
|
'@types/cookie@0.6.0': {}
|
||||||
|
|
||||||
'@types/eslint-scope@3.7.7':
|
'@types/eslint-scope@3.7.7':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/eslint': 9.6.1
|
'@types/eslint': 9.6.1
|
||||||
@ -5420,6 +5575,8 @@ snapshots:
|
|||||||
|
|
||||||
cookie@0.7.1: {}
|
cookie@0.7.1: {}
|
||||||
|
|
||||||
|
cookie@1.0.2: {}
|
||||||
|
|
||||||
core-js@3.38.1: {}
|
core-js@3.38.1: {}
|
||||||
|
|
||||||
core-util-is@1.0.3: {}
|
core-util-is@1.0.3: {}
|
||||||
@ -5496,10 +5653,14 @@ snapshots:
|
|||||||
|
|
||||||
decimal.js@10.4.3: {}
|
decimal.js@10.4.3: {}
|
||||||
|
|
||||||
|
deep-diff@1.0.2: {}
|
||||||
|
|
||||||
deep-eql@5.0.2: {}
|
deep-eql@5.0.2: {}
|
||||||
|
|
||||||
deep-is@0.1.4: {}
|
deep-is@0.1.4: {}
|
||||||
|
|
||||||
|
deepmerge@4.3.1: {}
|
||||||
|
|
||||||
default-browser-id@5.0.0: {}
|
default-browser-id@5.0.0: {}
|
||||||
|
|
||||||
default-browser@5.2.1:
|
default-browser@5.2.1:
|
||||||
@ -6354,6 +6515,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
is-docker: 3.0.0
|
is-docker: 3.0.0
|
||||||
|
|
||||||
|
is-lite@0.8.2: {}
|
||||||
|
|
||||||
|
is-lite@1.2.1: {}
|
||||||
|
|
||||||
is-map@2.0.3: {}
|
is-map@2.0.3: {}
|
||||||
|
|
||||||
is-negative-zero@2.0.3: {}
|
is-negative-zero@2.0.3: {}
|
||||||
@ -6453,7 +6618,7 @@ snapshots:
|
|||||||
|
|
||||||
jest-worker@27.5.1:
|
jest-worker@27.5.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 22.8.1
|
'@types/node': 22.10.2
|
||||||
merge-stream: 2.0.0
|
merge-stream: 2.0.0
|
||||||
supports-color: 8.1.1
|
supports-color: 8.1.1
|
||||||
optional: true
|
optional: true
|
||||||
@ -6813,6 +6978,8 @@ snapshots:
|
|||||||
mlly: 1.7.3
|
mlly: 1.7.3
|
||||||
pathe: 1.1.2
|
pathe: 1.1.2
|
||||||
|
|
||||||
|
popper.js@1.16.1: {}
|
||||||
|
|
||||||
possible-typed-array-names@1.0.0: {}
|
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)):
|
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
|
react: 18.3.1
|
||||||
scheduler: 0.23.2
|
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):
|
react-focus-lock@2.13.2(@types/react@18.3.12)(react@18.3.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.26.0
|
'@babel/runtime': 7.26.0
|
||||||
@ -6910,10 +7087,33 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
react: 18.3.1
|
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@16.13.1: {}
|
||||||
|
|
||||||
react-is@18.3.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):
|
react-redux@9.1.2(@types/react@18.3.12)(react@18.3.1)(redux@5.0.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/use-sync-external-store': 0.0.3
|
'@types/use-sync-external-store': 0.0.3
|
||||||
@ -6923,6 +7123,22 @@ snapshots:
|
|||||||
'@types/react': 18.3.12
|
'@types/react': 18.3.12
|
||||||
redux: 5.0.1
|
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):
|
react-transition-group@4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.26.0
|
'@babel/runtime': 7.26.0
|
||||||
@ -7105,6 +7321,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
compute-scroll-into-view: 1.0.20
|
compute-scroll-into-view: 1.0.20
|
||||||
|
|
||||||
|
scroll@3.0.1: {}
|
||||||
|
|
||||||
|
scrollparent@2.1.0: {}
|
||||||
|
|
||||||
select-hose@2.0.0: {}
|
select-hose@2.0.0: {}
|
||||||
|
|
||||||
selfsigned@2.4.1:
|
selfsigned@2.4.1:
|
||||||
@ -7160,6 +7380,8 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
set-cookie-parser@2.7.1: {}
|
||||||
|
|
||||||
set-function-length@1.2.2:
|
set-function-length@1.2.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
define-data-property: 1.1.4
|
define-data-property: 1.1.4
|
||||||
@ -7425,6 +7647,16 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
punycode: 2.3.1
|
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):
|
tree-dump@1.0.2(tslib@2.8.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib: 2.8.0
|
tslib: 2.8.0
|
||||||
@ -7478,10 +7710,14 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents: 2.3.3
|
fsevents: 2.3.3
|
||||||
|
|
||||||
|
turbo-stream@2.4.0: {}
|
||||||
|
|
||||||
type-check@0.4.0:
|
type-check@0.4.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
prelude-ls: 1.2.1
|
prelude-ls: 1.2.1
|
||||||
|
|
||||||
|
type-fest@4.31.0: {}
|
||||||
|
|
||||||
type-is@1.6.18:
|
type-is@1.6.18:
|
||||||
dependencies:
|
dependencies:
|
||||||
media-typer: 0.3.0
|
media-typer: 0.3.0
|
||||||
|
@ -139,7 +139,7 @@ export default defineConfig({
|
|||||||
}),
|
}),
|
||||||
new rspack.HtmlRspackPlugin({
|
new rspack.HtmlRspackPlugin({
|
||||||
filename: `${dist}/ext/src/options.html`,
|
filename: `${dist}/ext/src/options.html`,
|
||||||
template: `${src}/pages/template.html`,
|
template: `${src}/pages/options.html`,
|
||||||
inject: "head",
|
inject: "head",
|
||||||
title: "Home - ScriptCat",
|
title: "Home - ScriptCat",
|
||||||
minify: true,
|
minify: true,
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
export abstract class Repo<T> {
|
export abstract class Repo<T> {
|
||||||
constructor(private prefix: string) {
|
constructor(private prefix: string) {
|
||||||
if (!prefix.endsWith(":")) {
|
if (!prefix.endsWith(":")) {
|
||||||
prefix += ":";
|
this.prefix += ":";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private joinKey(key: string) {
|
protected joinKey(key: string) {
|
||||||
return this.prefix + key;
|
return this.prefix + key;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async _save(key: string, val: T) {
|
protected async _save(key: string, val: T) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const data = {
|
const data = {
|
||||||
[this.joinKey(key)]: val,
|
[this.joinKey(key)]: val,
|
||||||
@ -55,4 +55,39 @@ export abstract class Repo<T> {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public delete(key: string) {
|
||||||
|
return new Promise<void>((resolve) => {
|
||||||
|
chrome.storage.local.remove(this.joinKey(key), () => {
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
update(key: string, val: Partial<T>) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
this.get(key).then((result) => {
|
||||||
|
if (result) {
|
||||||
|
Object.assign(result, val);
|
||||||
|
this._save(key, result).then(() => {
|
||||||
|
resolve(result);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
all(): Promise<T[]> {
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,7 @@ export interface Config {
|
|||||||
export type UserConfig = { [key: string]: { [key: string]: Config } };
|
export type UserConfig = { [key: string]: { [key: string]: Config } };
|
||||||
|
|
||||||
export interface Script {
|
export interface Script {
|
||||||
id: number; // 脚本id
|
// id: number; // 脚本id mv3迁移为chrome.storage后舍弃
|
||||||
uuid: string; // 脚本uuid,通过脚本uuid识别唯一脚本
|
uuid: string; // 脚本uuid,通过脚本uuid识别唯一脚本
|
||||||
name: string; // 脚本名称
|
name: string; // 脚本名称
|
||||||
code: string; // 脚本执行代码
|
code: string; // 脚本执行代码
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { Script } from "@App/app/repo/scripts";
|
import { Script } from "@App/app/repo/scripts";
|
||||||
import { Client } from "@Packages/message/client";
|
import { Client } from "@Packages/message/client";
|
||||||
|
import { InstallSource } from ".";
|
||||||
|
|
||||||
export class ScriptClient extends Client {
|
export class ScriptClient extends Client {
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -11,7 +12,7 @@ export class ScriptClient extends Client {
|
|||||||
return this.do("getInstallInfo", uuid);
|
return this.do("getInstallInfo", uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
installScript(script: Script) {
|
installScript(script: Script, upsertBy: InstallSource = "user") {
|
||||||
return this.do("installScript", script);
|
return this.do("installScript", { script, upsertBy });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import CacheKey from "@App/app/cache_key";
|
|||||||
import { openInCurrentTab } from "@App/pkg/utils/utils";
|
import { openInCurrentTab } from "@App/pkg/utils/utils";
|
||||||
import { Script, ScriptDAO } from "@App/app/repo/scripts";
|
import { Script, ScriptDAO } from "@App/app/repo/scripts";
|
||||||
import { MessageQueue } from "@Packages/message/message_queue";
|
import { MessageQueue } from "@Packages/message/message_queue";
|
||||||
|
import { InstallSource } from ".";
|
||||||
|
|
||||||
export class ScriptService {
|
export class ScriptService {
|
||||||
logger: Logger;
|
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 dao = new ScriptDAO();
|
||||||
// 判断是否已经安装
|
// 判断是否已经安装
|
||||||
const oldScript = await dao.findByUUID(script.uuid);
|
const oldScript = await dao.findByUUID(script.uuid);
|
||||||
if (!oldScript) {
|
if (oldScript) {
|
||||||
// 执行安装逻辑
|
|
||||||
} else {
|
|
||||||
// 执行更新逻辑
|
// 执行更新逻辑
|
||||||
|
script.selfMetadata = oldScript.selfMetadata;
|
||||||
}
|
}
|
||||||
// 广播一下
|
return dao
|
||||||
this.mq.publish("installScript", script);
|
.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() {
|
init() {
|
||||||
|
@ -4,6 +4,10 @@
|
|||||||
"version": "0.17.0.1001",
|
"version": "0.17.0.1001",
|
||||||
"author": "CodFrm",
|
"author": "CodFrm",
|
||||||
"description": "__MSG_scriptcat_description__",
|
"description": "__MSG_scriptcat_description__",
|
||||||
|
"options_ui": {
|
||||||
|
"page": "src/options.html",
|
||||||
|
"open_in_tab": true
|
||||||
|
},
|
||||||
"background": {
|
"background": {
|
||||||
"service_worker": "src/service_worker.js"
|
"service_worker": "src/service_worker.js"
|
||||||
},
|
},
|
||||||
|
198
src/pages/components/CloudScriptPlan/index.tsx
Normal file
198
src/pages/components/CloudScriptPlan/index.tsx
Normal file
@ -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<ExportTarget>("local");
|
||||||
|
const [, setModel] = React.useState<Export>();
|
||||||
|
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 (
|
||||||
|
<Modal
|
||||||
|
title={
|
||||||
|
<div>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
height: "32px",
|
||||||
|
lineHeight: "32px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{script?.name} {t("upload_to_cloud")}
|
||||||
|
</span>
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
icon={
|
||||||
|
<IconQuestionCircleFill
|
||||||
|
style={{
|
||||||
|
margin: 0,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
href="https://docs.scriptcat.org/docs/dev/cloudcat/"
|
||||||
|
target="_blank"
|
||||||
|
iconOnly
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
okText={t("export")}
|
||||||
|
visible={visible}
|
||||||
|
onCancel={() => {
|
||||||
|
setVisible(false);
|
||||||
|
onClose();
|
||||||
|
}}
|
||||||
|
onConfirm={async () => {
|
||||||
|
// 保存并导出
|
||||||
|
const dao = new ExportDAO();
|
||||||
|
const params = form.getFieldsValue() as unknown as ExportParams;
|
||||||
|
if (!params || !script) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setModel((prevModel) => {
|
||||||
|
if (!prevModel) {
|
||||||
|
prevModel = {
|
||||||
|
id: 0,
|
||||||
|
scriptId: script!.id,
|
||||||
|
target: "local",
|
||||||
|
params: {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
prevModel.params[cloudScriptType] = params;
|
||||||
|
prevModel.target = cloudScriptType;
|
||||||
|
dao.save(prevModel).catch((err) => {
|
||||||
|
Message.error(`${t("save_failed")}: ${err}`);
|
||||||
|
});
|
||||||
|
return prevModel;
|
||||||
|
});
|
||||||
|
Message.info(t("exporting")!);
|
||||||
|
// 本地特殊处理
|
||||||
|
const values = await parseExportValue(script, params.exportValue);
|
||||||
|
const cookies = await parseExportCookie(params.exportCookie);
|
||||||
|
if (cloudScriptType === "local") {
|
||||||
|
const jszip = new JSZip();
|
||||||
|
const cloudScript = CloudScriptFactory.create("local", {
|
||||||
|
zip: jszip,
|
||||||
|
...params,
|
||||||
|
});
|
||||||
|
cloudScript.exportCloud(script, values, cookies);
|
||||||
|
// 生成文件,并下载
|
||||||
|
const files = await jszip.generateAsync({
|
||||||
|
type: "blob",
|
||||||
|
compression: "DEFLATE",
|
||||||
|
compressionOptions: {
|
||||||
|
level: 9,
|
||||||
|
},
|
||||||
|
comment: "Created by Scriptcat",
|
||||||
|
});
|
||||||
|
const url = URL.createObjectURL(files);
|
||||||
|
setTimeout(() => {
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
}, 60 * 1000);
|
||||||
|
chrome.downloads.download({
|
||||||
|
url,
|
||||||
|
saveAs: true,
|
||||||
|
filename: `${script.uuid}.zip`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Form
|
||||||
|
autoComplete="off"
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
}}
|
||||||
|
layout="vertical"
|
||||||
|
form={form}
|
||||||
|
>
|
||||||
|
<FormItem label={t("upload_to")}>
|
||||||
|
<Select
|
||||||
|
value={cloudScriptType}
|
||||||
|
onChange={(value) => {
|
||||||
|
setCloudScriptType(value);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{CloudScriptList.map((item) => (
|
||||||
|
<Select.Option key={item.key} value={item.key}>
|
||||||
|
{item.name}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormItem>
|
||||||
|
{/* {Object.keys(cloudScriptParams[cloudScriptType]).map((key) => {
|
||||||
|
const item = cloudScriptParams[cloudScriptType][key];
|
||||||
|
return (
|
||||||
|
<FormItem key={key} label={item.title}>
|
||||||
|
<Input />
|
||||||
|
</FormItem>
|
||||||
|
);
|
||||||
|
})} */}
|
||||||
|
<FormItem label={t("value_export_expression")} field="exportValue">
|
||||||
|
<Input.TextArea />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="" field="overwriteValue">
|
||||||
|
<Checkbox>{t("overwrite_original_value_on_import")}</Checkbox>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label={t("cookie_export_expression")} field="exportCookie">
|
||||||
|
<Input.TextArea />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="" field="overwriteCookie">
|
||||||
|
<Checkbox>{t("overwrite_original_cookie_on_import")}</Checkbox>
|
||||||
|
</FormItem>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={() => {
|
||||||
|
if (script) {
|
||||||
|
form.setFieldsValue(defaultParams(script));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("restore_default_values")}
|
||||||
|
</Button>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CloudScriptPlan;
|
35
src/pages/components/CustomLink/index.tsx
Normal file
35
src/pages/components/CustomLink/index.tsx
Normal file
@ -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 (
|
||||||
|
<div className={className} onClick={click}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CustomLink;
|
45
src/pages/components/CustomTrans/index.tsx
Normal file
45
src/pages/components/CustomTrans/index.tsx
Normal file
@ -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(`</${key}>`, end);
|
||||||
|
const element = content.substring(end + 1, content.indexOf(`</${key}>`, end));
|
||||||
|
switch (key) {
|
||||||
|
case "Link":
|
||||||
|
// eslint-disable-next-line no-case-declarations
|
||||||
|
const href = tag.match(/href="(.*)"/)![1];
|
||||||
|
children.push(
|
||||||
|
<Link key={`i${i}`} href={href} target="_black">
|
||||||
|
{element}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
children.push(element);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
content = content.substring(tagEnd + key.length + 3);
|
||||||
|
} else {
|
||||||
|
children.push(content);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div>{children}</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CustomTrans;
|
122
src/pages/components/FileSystemParams/index.tsx
Normal file
122
src/pages/components/FileSystemParams/index.tsx
Normal file
@ -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 (
|
||||||
|
<>
|
||||||
|
<Space>
|
||||||
|
{preNode}
|
||||||
|
<Select
|
||||||
|
value={fileSystemType}
|
||||||
|
style={{ width: 120 }}
|
||||||
|
onChange={(value) => {
|
||||||
|
onChangeFileSystemType(value as FileSystemType);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{fileSystemList.map((item) => (
|
||||||
|
<Select.Option key={item.key} value={item.key}>
|
||||||
|
{item.name}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
{actionButton.map((item) => item)}
|
||||||
|
</Space>
|
||||||
|
<Space
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
marginTop: 4,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Object.keys(fsParams[fileSystemType]).map((key) => (
|
||||||
|
<div key={key}>
|
||||||
|
{fsParams[fileSystemType][key].type === "select" && (
|
||||||
|
<>
|
||||||
|
<span>{fsParams[fileSystemType][key].title}</span>
|
||||||
|
<Select
|
||||||
|
value={
|
||||||
|
fileSystemParams[key] ||
|
||||||
|
fsParams[fileSystemType][key].options![0]
|
||||||
|
}
|
||||||
|
onChange={(value) => {
|
||||||
|
onChangeFileSystemParams({
|
||||||
|
...fileSystemParams,
|
||||||
|
[key]: value,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{fsParams[fileSystemType][key].options!.map((option) => (
|
||||||
|
<Select.Option value={option} key={option}>
|
||||||
|
{option}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{fsParams[fileSystemType][key].type === "password" && (
|
||||||
|
<>
|
||||||
|
<span>{fsParams[fileSystemType][key].title}</span>
|
||||||
|
<Input.Password
|
||||||
|
value={fileSystemParams[key]}
|
||||||
|
onChange={(value) => {
|
||||||
|
onChangeFileSystemParams({
|
||||||
|
...fileSystemParams,
|
||||||
|
[key]: value,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{!fsParams[fileSystemType][key].type && (
|
||||||
|
<>
|
||||||
|
<span>{fsParams[fileSystemType][key].title}</span>
|
||||||
|
<Input
|
||||||
|
value={fileSystemParams[key]}
|
||||||
|
onChange={(value) => {
|
||||||
|
onChangeFileSystemParams({
|
||||||
|
...fileSystemParams,
|
||||||
|
[key]: value,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</Space>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FileSystemParams;
|
131
src/pages/components/GMApiSetting/index.tsx
Normal file
131
src/pages/components/GMApiSetting/index.tsx
Normal file
@ -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<FileSystemType>(
|
||||||
|
systemConfig.catFileStorage.filesystem
|
||||||
|
);
|
||||||
|
const [fileSystemParams, setFilesystemParam] = useState<{
|
||||||
|
[key: string]: any;
|
||||||
|
}>(systemConfig.catFileStorage.params[fileSystemType] || {});
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card title={t("gm_api")} bordered={false}>
|
||||||
|
<Collapse bordered={false} defaultActiveKey={["storage"]}>
|
||||||
|
<CollapseItem header={t("storage_api")} name="storage">
|
||||||
|
<Space direction="vertical">
|
||||||
|
<FileSystemParams
|
||||||
|
preNode={
|
||||||
|
<Typography.Text>
|
||||||
|
{t("settings")}
|
||||||
|
<Link
|
||||||
|
target="_black"
|
||||||
|
href="https://github.com/scriptscat/scriptcat/blob/main/example/cat_file_storage.js"
|
||||||
|
>
|
||||||
|
CAT_fileStorage
|
||||||
|
</Link>
|
||||||
|
{t("use_file_system")}
|
||||||
|
</Typography.Text>
|
||||||
|
}
|
||||||
|
actionButton={[
|
||||||
|
<Button
|
||||||
|
key="save"
|
||||||
|
type="primary"
|
||||||
|
onClick={async () => {
|
||||||
|
try {
|
||||||
|
await FileSystemFactory.create(
|
||||||
|
fileSystemType,
|
||||||
|
fileSystemParams
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
Message.error(`${t("account_validation_failed")}: ${e}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const params = { ...systemConfig.catFileStorage.params };
|
||||||
|
params[fileSystemType] = fileSystemParams;
|
||||||
|
systemConfig.catFileStorage = {
|
||||||
|
status: "success",
|
||||||
|
filesystem: fileSystemType,
|
||||||
|
params,
|
||||||
|
};
|
||||||
|
setStatus("success");
|
||||||
|
Message.success(t("save_success")!);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("save")}
|
||||||
|
</Button>,
|
||||||
|
<Button
|
||||||
|
key="reset"
|
||||||
|
onClick={() => {
|
||||||
|
const config = systemConfig.catFileStorage;
|
||||||
|
config.status = "unset";
|
||||||
|
systemConfig.catFileStorage = config;
|
||||||
|
setStatus("unset");
|
||||||
|
}}
|
||||||
|
type="primary"
|
||||||
|
status="danger"
|
||||||
|
>
|
||||||
|
{t("reset")}
|
||||||
|
</Button>,
|
||||||
|
<Button
|
||||||
|
key="open"
|
||||||
|
type="secondary"
|
||||||
|
onClick={async () => {
|
||||||
|
try {
|
||||||
|
let fs = await FileSystemFactory.create(
|
||||||
|
fileSystemType,
|
||||||
|
fileSystemParams
|
||||||
|
);
|
||||||
|
fs = await fs.openDir("ScriptCat/app");
|
||||||
|
window.open(await fs.getDirUrl(), "_black");
|
||||||
|
} catch (e) {
|
||||||
|
Message.error(`${t("account_validation_failed")}: ${e}`);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("open_directory")}
|
||||||
|
</Button>,
|
||||||
|
]}
|
||||||
|
fileSystemType={fileSystemType}
|
||||||
|
fileSystemParams={fileSystemParams}
|
||||||
|
onChangeFileSystemType={(type) => {
|
||||||
|
setFilesystemType(type);
|
||||||
|
}}
|
||||||
|
onChangeFileSystemParams={(params) => {
|
||||||
|
setFilesystemParam(params);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{status === "unset" && (
|
||||||
|
<Typography.Text type="secondary">{t("not_set")}</Typography.Text>
|
||||||
|
)}
|
||||||
|
{status === "success" && (
|
||||||
|
<Typography.Text type="success">{t("in_use")}</Typography.Text>
|
||||||
|
)}
|
||||||
|
{status === "error" && (
|
||||||
|
<Typography.Text type="error">
|
||||||
|
{t("storage_error")}
|
||||||
|
</Typography.Text>
|
||||||
|
)}
|
||||||
|
</Space>
|
||||||
|
</CollapseItem>
|
||||||
|
</Collapse>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GMApiSetting;
|
26
src/pages/components/LogLabel/index.css
Normal file
26
src/pages/components/LogLabel/index.css
Normal file
@ -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);
|
||||||
|
}
|
80
src/pages/components/LogLabel/index.tsx
Normal file
80
src/pages/components/LogLabel/index.tsx
Normal file
@ -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 (
|
||||||
|
<div className="log-query-label">
|
||||||
|
<Select
|
||||||
|
showSearch
|
||||||
|
placeholder="key"
|
||||||
|
value={value.key || undefined}
|
||||||
|
onChange={(opt) => {
|
||||||
|
onChange({ ...value, key: opt });
|
||||||
|
}}
|
||||||
|
triggerProps={{
|
||||||
|
autoAlignPopupWidth: false,
|
||||||
|
autoAlignPopupMinWidth: true,
|
||||||
|
position: "bl",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Object.keys(labels).map((option) => (
|
||||||
|
<Select.Option key={option} value={option}>
|
||||||
|
{option}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
<Select
|
||||||
|
placeholder="condition"
|
||||||
|
value={value.condition || "="}
|
||||||
|
onChange={(opt) => {
|
||||||
|
onChange({ ...value, condition: opt });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Select.Option value="=">=</Select.Option>
|
||||||
|
<Select.Option value="=~">=~</Select.Option>
|
||||||
|
<Select.Option value="!=">!=</Select.Option>
|
||||||
|
<Select.Option value="!~">!~</Select.Option>
|
||||||
|
</Select>
|
||||||
|
<Select
|
||||||
|
showSearch
|
||||||
|
placeholder="value"
|
||||||
|
value={value.value || undefined}
|
||||||
|
onChange={(opt) => {
|
||||||
|
onChange({ ...value, value: opt });
|
||||||
|
}}
|
||||||
|
triggerProps={{
|
||||||
|
autoAlignPopupWidth: false,
|
||||||
|
autoAlignPopupMinWidth: true,
|
||||||
|
position: "bl",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Object.keys(values).map((option) => (
|
||||||
|
<Select.Option key={option} value={option}>
|
||||||
|
{option}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
<Button iconOnly icon={<IconClose />} onClick={onClose} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LogLabel;
|
334
src/pages/components/ScriptMenuList/index.tsx
Normal file
334
src/pages/components/ScriptMenuList/index.tsx
Normal file
@ -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 && <Empty />}
|
||||||
|
{list.map((item, index) => (
|
||||||
|
<Collapse bordered={false} expandIconPosition="right" key={item.id}>
|
||||||
|
<CollapseItem
|
||||||
|
header={
|
||||||
|
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
|
||||||
|
<div
|
||||||
|
onClick={(e) => {
|
||||||
|
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")!
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Space>
|
||||||
|
<Switch
|
||||||
|
size="small"
|
||||||
|
checked={item.enable}
|
||||||
|
onChange={(checked) => {
|
||||||
|
let p: Promise<any>;
|
||||||
|
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]);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
display: "block",
|
||||||
|
overflow: "hidden",
|
||||||
|
textOverflow: "ellipsis",
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
color: item.runNum === 0 ? "rgb(var(--gray-5))" : "",
|
||||||
|
lineHeight: "20px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ScriptIcons script={item} size={20} />
|
||||||
|
{item.name}
|
||||||
|
</span>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
name={item.id.toString()}
|
||||||
|
contentStyle={{ padding: "0 0 0 40px" }}
|
||||||
|
>
|
||||||
|
<div className="flex flex-col">
|
||||||
|
{isBackscript && (
|
||||||
|
<Button
|
||||||
|
className="text-left"
|
||||||
|
type="secondary"
|
||||||
|
icon={
|
||||||
|
item.runStatus !== SCRIPT_RUN_STATUS_RUNNING ? (
|
||||||
|
<RiPlayFill />
|
||||||
|
) : (
|
||||||
|
<RiStopFill />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
onClick={() => {
|
||||||
|
if (item.runStatus !== SCRIPT_RUN_STATUS_RUNNING) {
|
||||||
|
runtimeCtrl.startScript(item.id);
|
||||||
|
} else {
|
||||||
|
runtimeCtrl.stopScript(item.id);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item.runStatus !== SCRIPT_RUN_STATUS_RUNNING
|
||||||
|
? t("run_once")
|
||||||
|
: t("stop")}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
className="text-left"
|
||||||
|
type="secondary"
|
||||||
|
icon={<IconEdit />}
|
||||||
|
onClick={() => {
|
||||||
|
window.open(
|
||||||
|
`/src/options.html#/script/editor/${item.id}`,
|
||||||
|
"_blank"
|
||||||
|
);
|
||||||
|
window.close();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("edit")}
|
||||||
|
</Button>
|
||||||
|
{url && (
|
||||||
|
<Button
|
||||||
|
className="text-left"
|
||||||
|
status="warning"
|
||||||
|
type="secondary"
|
||||||
|
icon={<IconMinus />}
|
||||||
|
onClick={() => {
|
||||||
|
scriptCtrl
|
||||||
|
.exclude(
|
||||||
|
item.id,
|
||||||
|
`*://${url.host}*`,
|
||||||
|
isExclude(item, url.host)
|
||||||
|
)
|
||||||
|
.finally(() => {
|
||||||
|
window.close();
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isExclude(item, url.host)
|
||||||
|
? t("exclude_on")
|
||||||
|
: t("exclude_off")}
|
||||||
|
{` ${url.host} ${t("exclude_execution")}`}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<Popconfirm
|
||||||
|
title={t("confirm_delete_script")}
|
||||||
|
icon={<IconDelete />}
|
||||||
|
onOk={() => {
|
||||||
|
setList(list.filter((i) => i.id !== item.id));
|
||||||
|
scriptCtrl.delete(item.id).catch((e) => {
|
||||||
|
Message.error(`{t('delete_failed')}: ${e}`);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
className="text-left"
|
||||||
|
status="danger"
|
||||||
|
type="secondary"
|
||||||
|
icon={<IconDelete />}
|
||||||
|
>
|
||||||
|
{t("delete")}
|
||||||
|
</Button>
|
||||||
|
</Popconfirm>
|
||||||
|
</div>
|
||||||
|
</CollapseItem>
|
||||||
|
<div
|
||||||
|
className="arco-collapse-item-content-box flex flex-col"
|
||||||
|
style={{ padding: "0 0 0 40px" }}
|
||||||
|
>
|
||||||
|
{/* 判断菜单数量,再判断是否展开 */}
|
||||||
|
{(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 (
|
||||||
|
<Button
|
||||||
|
className="text-left"
|
||||||
|
key={menu.id}
|
||||||
|
type="secondary"
|
||||||
|
icon={<IconMenu />}
|
||||||
|
onClick={() => {
|
||||||
|
sendMenuAction(menu.sender, menu.channelFlag);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{menu.name}
|
||||||
|
{menu.accessKey && `(${menu.accessKey.toUpperCase()})`}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
{item.menus && item.menus?.length > systemConfig.menuExpandNum && (
|
||||||
|
<Button
|
||||||
|
className="text-left"
|
||||||
|
key="expand"
|
||||||
|
type="secondary"
|
||||||
|
icon={
|
||||||
|
expandMenuIndex[index] ? <IconCaretUp /> : <IconCaretDown />
|
||||||
|
}
|
||||||
|
onClick={() => {
|
||||||
|
setExpandMenuIndex({
|
||||||
|
...expandMenuIndex,
|
||||||
|
[index]: !expandMenuIndex[index],
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{expandMenuIndex[index] ? t("collapse") : t("expand")}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{item.hasUserConfig && (
|
||||||
|
<Button
|
||||||
|
className="text-left"
|
||||||
|
key="config"
|
||||||
|
type="secondary"
|
||||||
|
icon={<IconSettings />}
|
||||||
|
onClick={() => {
|
||||||
|
window.open(
|
||||||
|
`/src/options.html#/?userConfig=${item.id}`,
|
||||||
|
"_blank"
|
||||||
|
);
|
||||||
|
window.close();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("user_config")}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Collapse>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ScriptMenuList;
|
177
src/pages/components/ScriptResource/index.tsx
Normal file
177
src/pages/components/ScriptResource/index.tsx
Normal file
@ -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<ResourceListItem[]>([]);
|
||||||
|
const inputRef = useRef<RefInputType>(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: <IconSearch />,
|
||||||
|
// eslint-disable-next-line react/no-unstable-nested-components
|
||||||
|
filterDropdown: ({ filterKeys, setFilterKeys, confirm }: any) => {
|
||||||
|
return (
|
||||||
|
<div className="arco-table-custom-filter">
|
||||||
|
<Input.Search
|
||||||
|
ref={inputRef}
|
||||||
|
searchButton
|
||||||
|
placeholder={t("enter_key")!}
|
||||||
|
value={filterKeys[0] || ""}
|
||||||
|
onChange={(value) => {
|
||||||
|
setFilterKeys(value ? [value] : []);
|
||||||
|
}}
|
||||||
|
onSearch={() => {
|
||||||
|
confirm();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
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 (
|
||||||
|
<Space>
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
icon={<IconDownload />}
|
||||||
|
onClick={() => {
|
||||||
|
const url = URL.createObjectURL(base64ToBlob(value.base64));
|
||||||
|
setTimeout(() => {
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
}, 60 * 1000);
|
||||||
|
const filename = value.url.split("/").pop();
|
||||||
|
chrome.downloads.download({
|
||||||
|
url,
|
||||||
|
saveAs: true,
|
||||||
|
filename,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Popconfirm
|
||||||
|
focusLock
|
||||||
|
title={t("confirm_delete_resource")}
|
||||||
|
onOk={() => {
|
||||||
|
Message.info({
|
||||||
|
content: t("delete_success"),
|
||||||
|
});
|
||||||
|
resourceCtrl.deleteResource(value.id);
|
||||||
|
setData(data.filter((_, i) => i !== index));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button type="text" iconOnly icon={<IconDelete />} />
|
||||||
|
</Popconfirm>
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Drawer
|
||||||
|
width={600}
|
||||||
|
title={
|
||||||
|
<span>
|
||||||
|
{script?.name} {t("script_resource")}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
visible={visible}
|
||||||
|
onOk={onOk}
|
||||||
|
onCancel={onCancel}
|
||||||
|
>
|
||||||
|
<Space className="w-full" direction="vertical">
|
||||||
|
<Space className="!flex justify-end">
|
||||||
|
<Popconfirm
|
||||||
|
focusLock
|
||||||
|
title={t("confirm_clear_resource")}
|
||||||
|
onOk={() => {
|
||||||
|
setData((prev) => {
|
||||||
|
prev.forEach((v) => {
|
||||||
|
resourceCtrl.deleteResource(v.id);
|
||||||
|
});
|
||||||
|
Message.info({
|
||||||
|
content: t("clear_success"),
|
||||||
|
});
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button type="primary" status="warning">
|
||||||
|
{t("clear")}
|
||||||
|
</Button>
|
||||||
|
</Popconfirm>
|
||||||
|
</Space>
|
||||||
|
<Table columns={columns} data={data} rowKey="id" />
|
||||||
|
</Space>
|
||||||
|
</Drawer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ScriptResource;
|
325
src/pages/components/ScriptSetting/Match.tsx
Normal file
325
src/pages/components/ScriptSetting/Match.tsx
Normal file
@ -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<MatchItem[]>([]);
|
||||||
|
const [exclude, setExclude] = useState<MatchItem[]>([]);
|
||||||
|
const [matchValue, setMatchValue] = useState<string>("");
|
||||||
|
const [matchVisible, setMatchVisible] = useState<boolean>(false);
|
||||||
|
const [excludeValue, setExcludeValue] = useState<string>("");
|
||||||
|
const [excludeVisible, setExcludeVisible] = useState<boolean>(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<string, boolean>();
|
||||||
|
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<string, boolean>();
|
||||||
|
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 <span style={{ color: "#52c41a" }}>{t("yes")}</span>;
|
||||||
|
}
|
||||||
|
return <span style={{ color: "#c4751a" }}>{t("no")}</span>;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("action"),
|
||||||
|
render(_, item: MatchItem) {
|
||||||
|
if (item.isExclude) {
|
||||||
|
return (
|
||||||
|
<Space>
|
||||||
|
<Popconfirm
|
||||||
|
title={`${t("confirm_delete_exclude")}${
|
||||||
|
item.hasMatch ? ` ${t("after_deleting_match_item")}` : ""
|
||||||
|
}`}
|
||||||
|
onOk={() => {
|
||||||
|
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]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button type="text" iconOnly icon={<IconDelete />} />
|
||||||
|
</Popconfirm>
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Space>
|
||||||
|
<Popconfirm
|
||||||
|
title={`${t("confirm_delete_match")}${
|
||||||
|
item.self ? "" : ` ${t("after_deleting_exclude_item")}`
|
||||||
|
}`}
|
||||||
|
onOk={() => {
|
||||||
|
match.splice(match.indexOf(item), 1);
|
||||||
|
scriptCtrl
|
||||||
|
.resetMatch(
|
||||||
|
script.id,
|
||||||
|
match.map((m) => m.match)
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
|
setMatch([...match]);
|
||||||
|
// 添加到exclue
|
||||||
|
if (!item.self) {
|
||||||
|
exclude.push(item);
|
||||||
|
scriptCtrl
|
||||||
|
.resetExclude(
|
||||||
|
script.id,
|
||||||
|
exclude.map((m) => m.match)
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
|
setExclude([...exclude]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button type="text" iconOnly icon={<IconDelete />} />
|
||||||
|
</Popconfirm>
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Modal
|
||||||
|
title={t("add_match")}
|
||||||
|
visible={matchVisible}
|
||||||
|
onCancel={() => setMatchVisible(false)}
|
||||||
|
onOk={() => {
|
||||||
|
if (matchValue) {
|
||||||
|
match.push({
|
||||||
|
id: Math.random(),
|
||||||
|
match: matchValue,
|
||||||
|
self: true,
|
||||||
|
hasMatch: false,
|
||||||
|
isExclude: false,
|
||||||
|
});
|
||||||
|
scriptCtrl
|
||||||
|
.resetMatch(
|
||||||
|
script.id,
|
||||||
|
match.map((m) => m.match)
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
|
setMatch([...match]);
|
||||||
|
setMatchVisible(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
value={matchValue}
|
||||||
|
onChange={(e) => {
|
||||||
|
setMatchValue(e);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
<Modal
|
||||||
|
title={t("add_exclude")}
|
||||||
|
visible={excludeVisible}
|
||||||
|
onCancel={() => setExcludeVisible(false)}
|
||||||
|
onOk={() => {
|
||||||
|
if (excludeValue) {
|
||||||
|
exclude.push({
|
||||||
|
id: Math.random(),
|
||||||
|
match: excludeValue,
|
||||||
|
self: true,
|
||||||
|
hasMatch: false,
|
||||||
|
isExclude: true,
|
||||||
|
});
|
||||||
|
scriptCtrl
|
||||||
|
.resetExclude(
|
||||||
|
script.id,
|
||||||
|
exclude.map((m) => m.match)
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
|
setExclude([...exclude]);
|
||||||
|
setExcludeVisible(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
value={excludeValue}
|
||||||
|
onChange={(e) => {
|
||||||
|
setExcludeValue(e);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
<div className="flex flex-row justify-between pb-2">
|
||||||
|
<Typography.Title heading={6}>{t("website_match")}</Typography.Title>
|
||||||
|
<Space>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
size="small"
|
||||||
|
onClick={() => {
|
||||||
|
setMatchValue("");
|
||||||
|
setMatchVisible(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("add_match")}
|
||||||
|
</Button>
|
||||||
|
<Popconfirm
|
||||||
|
title={t("confirm_reset")}
|
||||||
|
onOk={() => {
|
||||||
|
scriptCtrl.resetMatch(script.id, undefined).then(() => {
|
||||||
|
setMatch([]);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button type="primary" size="small" status="warning">
|
||||||
|
{t("reset")}
|
||||||
|
</Button>
|
||||||
|
</Popconfirm>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
<Table columns={columns} data={match} rowKey="id" pagination={false} />
|
||||||
|
<Divider />
|
||||||
|
<div className="flex flex-row justify-between pb-2">
|
||||||
|
<Typography.Title heading={6}>{t("website_exclude")}</Typography.Title>
|
||||||
|
<Space>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
size="small"
|
||||||
|
onClick={() => {
|
||||||
|
setExcludeValue("");
|
||||||
|
setExcludeVisible(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("add_exclude")}
|
||||||
|
</Button>
|
||||||
|
<Popconfirm
|
||||||
|
title={t("confirm_reset")}
|
||||||
|
onOk={() => {
|
||||||
|
scriptCtrl.resetExclude(script.id, undefined).then(() => {
|
||||||
|
setExclude([]);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button type="primary" size="small" status="warning">
|
||||||
|
{t("reset")}
|
||||||
|
</Button>
|
||||||
|
</Popconfirm>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
<Table columns={columns} data={exclude} rowKey="id" pagination={false} />
|
||||||
|
<Divider />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Match;
|
196
src/pages/components/ScriptSetting/Permission.tsx
Normal file
196
src/pages/components/ScriptSetting/Permission.tsx
Normal file
@ -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<Permission[]>([]);
|
||||||
|
const [permissionVisible, setPermissionVisible] = useState<boolean>(false);
|
||||||
|
const [permissionValue, setPermissionValue] = useState<Permission>();
|
||||||
|
|
||||||
|
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 <span style={{ color: "#52c41a" }}>{t("yes")}</span>;
|
||||||
|
}
|
||||||
|
return <span style={{ color: "#f5222d" }}>{t("no")}</span>;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("action"),
|
||||||
|
render(_, item: Permission) {
|
||||||
|
return (
|
||||||
|
<Space>
|
||||||
|
<Popconfirm
|
||||||
|
title={t("confirm_delete_permission")}
|
||||||
|
onOk={() => {
|
||||||
|
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")!);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button type="text" iconOnly icon={<IconDelete />} />
|
||||||
|
</Popconfirm>
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (script) {
|
||||||
|
permissionCtrl.getPermissions(script.id).then((list) => {
|
||||||
|
setPermission(list);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [script]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Modal
|
||||||
|
title={t("add_permission")}
|
||||||
|
visible={permissionVisible}
|
||||||
|
onCancel={() => setPermissionVisible(false)}
|
||||||
|
onOk={() => {
|
||||||
|
if (permissionValue) {
|
||||||
|
permission.push({
|
||||||
|
id: 0,
|
||||||
|
scriptId: script.id,
|
||||||
|
permission: permissionValue.permission,
|
||||||
|
permissionValue: permissionValue.permissionValue,
|
||||||
|
allow: permissionValue.allow,
|
||||||
|
createtime: new Date().getTime(),
|
||||||
|
updatetime: 0,
|
||||||
|
});
|
||||||
|
permissionCtrl
|
||||||
|
.addPermission(script.id, permissionValue)
|
||||||
|
.then(() => {
|
||||||
|
setPermission([...permission]);
|
||||||
|
setPermissionVisible(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Space className="w-full" direction="vertical">
|
||||||
|
<Select
|
||||||
|
value={permissionValue?.permission}
|
||||||
|
onChange={(e) => {
|
||||||
|
permissionValue &&
|
||||||
|
setPermissionValue({ ...permissionValue, permission: e });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Select.Option value="cors">{t("permission_cors")}</Select.Option>
|
||||||
|
<Select.Option value="cookie">
|
||||||
|
{t("permission_cookie")}
|
||||||
|
</Select.Option>
|
||||||
|
</Select>
|
||||||
|
<Input
|
||||||
|
value={permissionValue?.permissionValue}
|
||||||
|
onChange={(e) => {
|
||||||
|
permissionValue &&
|
||||||
|
setPermissionValue({ ...permissionValue, permissionValue: e });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Checkbox
|
||||||
|
checked={permissionValue?.allow}
|
||||||
|
onChange={(e) => {
|
||||||
|
permissionValue &&
|
||||||
|
setPermissionValue({ ...permissionValue, allow: e });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("allow")}
|
||||||
|
</Checkbox>
|
||||||
|
</Space>
|
||||||
|
</Modal>
|
||||||
|
<div className="flex flex-row justify-between pb-2">
|
||||||
|
<Typography.Title heading={6}>
|
||||||
|
{t("permission_management")}
|
||||||
|
</Typography.Title>
|
||||||
|
<Space>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
size="small"
|
||||||
|
onClick={() => {
|
||||||
|
setPermissionValue({
|
||||||
|
id: 0,
|
||||||
|
scriptId: script.id,
|
||||||
|
permission: "cors",
|
||||||
|
permissionValue: "",
|
||||||
|
allow: true,
|
||||||
|
createtime: 0,
|
||||||
|
updatetime: 0,
|
||||||
|
});
|
||||||
|
setPermissionVisible(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("add_permission")}
|
||||||
|
</Button>
|
||||||
|
<Popconfirm
|
||||||
|
title={t("confirm_reset")}
|
||||||
|
onOk={() => {
|
||||||
|
permissionCtrl.resetPermission(script.id).then(() => {
|
||||||
|
setPermission([]);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button type="primary" size="small" status="warning">
|
||||||
|
{t("reset")}
|
||||||
|
</Button>
|
||||||
|
</Popconfirm>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
<Table
|
||||||
|
columns={columns}
|
||||||
|
data={permission}
|
||||||
|
rowKey="id"
|
||||||
|
pagination={false}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PermissionManager;
|
106
src/pages/components/ScriptSetting/index.tsx
Normal file
106
src/pages/components/ScriptSetting/index.tsx
Normal file
@ -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<string>("");
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (script) {
|
||||||
|
scriptCtrl.scriptDAO.findById(script.id).then((v) => {
|
||||||
|
setCheckUpdateUrl(v?.downloadUrl || "");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [script]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Drawer
|
||||||
|
width={600}
|
||||||
|
title={
|
||||||
|
<span>
|
||||||
|
{script?.name} {t("script_setting")}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
autoFocus={false}
|
||||||
|
focusLock={false}
|
||||||
|
visible={visible}
|
||||||
|
onOk={() => {
|
||||||
|
onOk();
|
||||||
|
}}
|
||||||
|
onCancel={() => {
|
||||||
|
onCancel();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Descriptions
|
||||||
|
column={1}
|
||||||
|
title={t("basic_info")}
|
||||||
|
data={[
|
||||||
|
{
|
||||||
|
label: t("last_updated"),
|
||||||
|
value: formatUnixTime(
|
||||||
|
(script?.updatetime || script?.createtime || 0) / 1000
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "UUID",
|
||||||
|
value: script?.uuid,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
style={{ marginBottom: 20 }}
|
||||||
|
labelStyle={{ paddingRight: 36 }}
|
||||||
|
/>
|
||||||
|
<Divider />
|
||||||
|
{script && <Match script={script} />}
|
||||||
|
<Descriptions
|
||||||
|
column={1}
|
||||||
|
title={t("update")}
|
||||||
|
data={[
|
||||||
|
{
|
||||||
|
label: t("update_url"),
|
||||||
|
value: (
|
||||||
|
<Input
|
||||||
|
value={checkUpdateUrl}
|
||||||
|
onChange={(e) => {
|
||||||
|
setCheckUpdateUrl(e);
|
||||||
|
}}
|
||||||
|
onBlur={() => {
|
||||||
|
scriptCtrl
|
||||||
|
.updateCheckUpdateUrl(script!.id, checkUpdateUrl)
|
||||||
|
.then(() => {
|
||||||
|
Message.success(t("update_success")!);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
style={{ marginBottom: 20 }}
|
||||||
|
labelStyle={{ paddingRight: 36 }}
|
||||||
|
/>
|
||||||
|
<Divider />
|
||||||
|
{script && <PermissionManager script={script} />}
|
||||||
|
<Empty description={t("under_construction")} />
|
||||||
|
</Drawer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ScriptSetting;
|
286
src/pages/components/ScriptStorage/index.tsx
Normal file
286
src/pages/components/ScriptStorage/index.tsx
Normal file
@ -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<Value[]>([]);
|
||||||
|
const inputRef = useRef<RefInputType>(null);
|
||||||
|
const [currentValue, setCurrentValue] = useState<Value>();
|
||||||
|
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: <IconSearch />,
|
||||||
|
width: 140,
|
||||||
|
// eslint-disable-next-line react/no-unstable-nested-components
|
||||||
|
filterDropdown: ({ filterKeys, setFilterKeys, confirm }: any) => {
|
||||||
|
return (
|
||||||
|
<div className="arco-table-custom-filter">
|
||||||
|
<Input.Search
|
||||||
|
ref={inputRef}
|
||||||
|
searchButton
|
||||||
|
placeholder={t("enter_key")!}
|
||||||
|
value={filterKeys[0] || ""}
|
||||||
|
onChange={(value) => {
|
||||||
|
setFilterKeys(value ? [value] : []);
|
||||||
|
}}
|
||||||
|
onSearch={() => {
|
||||||
|
confirm();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
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 (
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
whiteSpace: "break-spaces",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{JSON.stringify(col, null, 2)}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("type"),
|
||||||
|
dataIndex: "value",
|
||||||
|
width: 90,
|
||||||
|
key: "type",
|
||||||
|
render(col) {
|
||||||
|
return valueType(col);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("action"),
|
||||||
|
render(_col, value: Value, index) {
|
||||||
|
return (
|
||||||
|
<Space>
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
icon={<IconEdit />}
|
||||||
|
onClick={() => {
|
||||||
|
setCurrentValue(value);
|
||||||
|
setVisibleEdit(true);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
iconOnly
|
||||||
|
icon={<IconDelete />}
|
||||||
|
onClick={() => {
|
||||||
|
valueCtrl.setValue(script!.id, value.key, undefined);
|
||||||
|
Message.info({
|
||||||
|
content: t("delete_success"),
|
||||||
|
});
|
||||||
|
setData(data.filter((_, i) => i !== index));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Drawer
|
||||||
|
width={600}
|
||||||
|
title={
|
||||||
|
<span>
|
||||||
|
{script?.name} {t("script_storage")}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
visible={visible}
|
||||||
|
onOk={onOk}
|
||||||
|
onCancel={onCancel}
|
||||||
|
>
|
||||||
|
<Modal
|
||||||
|
title={currentValue ? t("edit_value") : t("add_value")}
|
||||||
|
visible={visibleEdit}
|
||||||
|
onOk={() => {
|
||||||
|
form.validate().then((value: { key: string; value: any; type: string }) => {
|
||||||
|
switch (value.type) {
|
||||||
|
case "number":
|
||||||
|
value.value = Number(value.value);
|
||||||
|
break;
|
||||||
|
case "boolean":
|
||||||
|
value.value = value.value === "true";
|
||||||
|
break;
|
||||||
|
case "object":
|
||||||
|
value.value = JSON.parse(value.value);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
valueCtrl.setValue(script!.id, value.key, value.value);
|
||||||
|
if (currentValue) {
|
||||||
|
Message.info({
|
||||||
|
content: t("update_success"),
|
||||||
|
});
|
||||||
|
setData(
|
||||||
|
data.map((v) => {
|
||||||
|
if (v.key === value.key) {
|
||||||
|
return {
|
||||||
|
...v,
|
||||||
|
value: value.value,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
Message.info({
|
||||||
|
content: t("add_success"),
|
||||||
|
});
|
||||||
|
setData([
|
||||||
|
{
|
||||||
|
id: 0,
|
||||||
|
scriptId: script!.id,
|
||||||
|
storageName: (script?.metadata.storagename && script?.metadata.storagename[0]) || "",
|
||||||
|
key: value.key,
|
||||||
|
value: value.value,
|
||||||
|
createtime: Date.now(),
|
||||||
|
updatetime: 0,
|
||||||
|
},
|
||||||
|
...data,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
setVisibleEdit(false);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
onCancel={() => setVisibleEdit(false)}
|
||||||
|
>
|
||||||
|
{visibleEdit && (
|
||||||
|
<Form
|
||||||
|
form={form}
|
||||||
|
initialValues={{
|
||||||
|
key: currentValue?.key,
|
||||||
|
value:
|
||||||
|
typeof currentValue?.value === "string"
|
||||||
|
? currentValue?.value
|
||||||
|
: JSON.stringify(currentValue?.value, null, 2),
|
||||||
|
type: valueType(currentValue?.value || "string"),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormItem label="Key" field="key" rules={[{ required: true }]}>
|
||||||
|
<Input placeholder={t("key_placeholder")!} disabled={!!currentValue} />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="Value" field="value" rules={[{ required: true }]}>
|
||||||
|
<Input.TextArea rows={6} placeholder={t("value_placeholder")!} />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label={t("type")} field="type" rules={[{ required: true }]}>
|
||||||
|
<Select>
|
||||||
|
<Select.Option value="string">{t("type_string")}</Select.Option>
|
||||||
|
<Select.Option value="number">{t("type_number")}</Select.Option>
|
||||||
|
<Select.Option value="boolean">{t("type_boolean")}</Select.Option>
|
||||||
|
<Select.Option value="object">{t("type_object")}</Select.Option>
|
||||||
|
</Select>
|
||||||
|
</FormItem>
|
||||||
|
</Form>
|
||||||
|
)}
|
||||||
|
</Modal>
|
||||||
|
<Space className="w-full" direction="vertical">
|
||||||
|
<Space className="!flex justify-end">
|
||||||
|
<Popconfirm
|
||||||
|
focusLock
|
||||||
|
title={t("confirm_clear")}
|
||||||
|
onOk={() => {
|
||||||
|
setData((prev) => {
|
||||||
|
prev.forEach((v) => {
|
||||||
|
valueCtrl.setValue(script!.id, v.key, undefined);
|
||||||
|
});
|
||||||
|
Message.info({
|
||||||
|
content: t("clear_success"),
|
||||||
|
});
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button type="primary" status="warning">
|
||||||
|
{t("clear")}
|
||||||
|
</Button>
|
||||||
|
</Popconfirm>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={() => {
|
||||||
|
setCurrentValue(undefined);
|
||||||
|
setVisibleEdit(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("add")}
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
<Table columns={columns} data={data} rowKey="id" />
|
||||||
|
</Space>
|
||||||
|
</Drawer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ScriptStorage;
|
200
src/pages/components/UserConfigPanel/index.tsx
Normal file
200
src/pages/components/UserConfigPanel/index.tsx
Normal file
@ -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 (
|
||||||
|
<Modal
|
||||||
|
visible={visible}
|
||||||
|
title={`${script.name} ${t("config")}`} // 替换为键值对应的英文文本
|
||||||
|
okText={t("save")} // 替换为键值对应的英文文本
|
||||||
|
cancelText={t("close")} // 替换为键值对应的英文文本
|
||||||
|
onOk={() => {
|
||||||
|
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);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Tabs
|
||||||
|
activeTab={tab}
|
||||||
|
onChange={(value) => {
|
||||||
|
setTab(value);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Object.keys(userConfig).map((itemKey) => {
|
||||||
|
const value = userConfig[itemKey];
|
||||||
|
return (
|
||||||
|
<TabPane key={itemKey} title={itemKey}>
|
||||||
|
<Form
|
||||||
|
key={script.id}
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
}}
|
||||||
|
autoComplete="off"
|
||||||
|
layout="vertical"
|
||||||
|
initialValues={values}
|
||||||
|
ref={(el: FormInstance) => {
|
||||||
|
formRefs.current[itemKey] = el;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Object.keys(value).map((key) => (
|
||||||
|
<FormItem
|
||||||
|
key={key}
|
||||||
|
label={value[key].title}
|
||||||
|
field={`${itemKey}.${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 (
|
||||||
|
<Input.Password
|
||||||
|
placeholder={item.description}
|
||||||
|
maxLength={item.max}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Input
|
||||||
|
placeholder={item.description}
|
||||||
|
maxLength={item.max}
|
||||||
|
showWordLimit
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
case "number":
|
||||||
|
return (
|
||||||
|
<InputNumber
|
||||||
|
placeholder={item.description}
|
||||||
|
min={item.min}
|
||||||
|
max={item.max}
|
||||||
|
suffix={item.unit}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
case "checkbox":
|
||||||
|
return (
|
||||||
|
<Checkbox
|
||||||
|
defaultChecked={values[`${itemKey}.${key}`]}
|
||||||
|
>
|
||||||
|
{item.description}
|
||||||
|
</Checkbox>
|
||||||
|
);
|
||||||
|
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 (
|
||||||
|
<Select
|
||||||
|
mode={
|
||||||
|
item.type === "mult-select"
|
||||||
|
? "multiple"
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
placeholder={item.description}
|
||||||
|
>
|
||||||
|
{options!.map((option) => (
|
||||||
|
<Select.Option key={option} value={option}>
|
||||||
|
{option}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
);
|
||||||
|
case "textarea":
|
||||||
|
return (
|
||||||
|
<Input.TextArea
|
||||||
|
placeholder={item.description}
|
||||||
|
maxLength={item.max}
|
||||||
|
rows={item.rows}
|
||||||
|
showWordLimit
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
</FormItem>
|
||||||
|
))}
|
||||||
|
</Form>
|
||||||
|
</TabPane>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Tabs>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default UserConfigPanel;
|
210
src/pages/components/layout/Sider.tsx
Normal file
210
src/pages/components/layout/Sider.tsx
Normal file
@ -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 (
|
||||||
|
<HashRouter>
|
||||||
|
<SiderGuide ref={guideRef} />
|
||||||
|
<Layout.Sider className="h-full" collapsed={collapsed} width={170}>
|
||||||
|
<div className="flex flex-col justify-between h-full">
|
||||||
|
<Menu
|
||||||
|
style={{ width: "100%" }}
|
||||||
|
selectedKeys={[menuSelect]}
|
||||||
|
selectable
|
||||||
|
onClickMenuItem={(key) => {
|
||||||
|
setMenuSelect(key);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CustomLink to="/">
|
||||||
|
<MenuItem key="/" className="menu-script">
|
||||||
|
<IconCode /> {t("installed_scripts")}
|
||||||
|
</MenuItem>
|
||||||
|
</CustomLink>
|
||||||
|
<CustomLink to="/subscribe">
|
||||||
|
<MenuItem key="/subscribe">
|
||||||
|
<IconSubscribe /> {t("subscribe")}
|
||||||
|
</MenuItem>
|
||||||
|
</CustomLink>
|
||||||
|
<CustomLink to="/logger">
|
||||||
|
<MenuItem key="/logger">
|
||||||
|
<IconFile /> {t("logs")}
|
||||||
|
</MenuItem>
|
||||||
|
</CustomLink>
|
||||||
|
<CustomLink to="/tools" className="menu-tools">
|
||||||
|
<MenuItem key="/tools">
|
||||||
|
<IconTool /> {t("tools")}
|
||||||
|
</MenuItem>
|
||||||
|
</CustomLink>
|
||||||
|
<CustomLink to="/setting" className="menu-setting">
|
||||||
|
<MenuItem key="/setting">
|
||||||
|
<IconSettings /> {t("settings")}
|
||||||
|
</MenuItem>
|
||||||
|
</CustomLink>
|
||||||
|
</Menu>
|
||||||
|
<Menu
|
||||||
|
style={{ width: "100%", borderTop: "1px solid var(--color-bg-5)" }}
|
||||||
|
selectedKeys={[]}
|
||||||
|
selectable
|
||||||
|
onClickMenuItem={(key) => {
|
||||||
|
setMenuSelect(key);
|
||||||
|
}}
|
||||||
|
mode="pop"
|
||||||
|
>
|
||||||
|
<Menu.SubMenu
|
||||||
|
key="/help"
|
||||||
|
title={
|
||||||
|
<>
|
||||||
|
<IconQuestion /> {t("helpcenter")}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
triggerProps={{
|
||||||
|
trigger: "hover",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Menu.SubMenu
|
||||||
|
key="/external_links"
|
||||||
|
title={
|
||||||
|
<>
|
||||||
|
<RiLinkM /> {t("external_links")}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Menu.Item key="scriptcat/docs/dev/">
|
||||||
|
<a
|
||||||
|
href="https://docs.scriptcat.org/docs/dev/"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
<RiFileCodeLine /> {t("api_docs")}
|
||||||
|
</a>
|
||||||
|
</Menu.Item>
|
||||||
|
<Menu.Item key="scriptcat/docs/learn/">
|
||||||
|
<a
|
||||||
|
href="https://learn.scriptcat.org/docs/%E7%AE%80%E4%BB%8B/"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
<RiFileCodeLine /> {t("development_guide")}
|
||||||
|
</a>
|
||||||
|
</Menu.Item>
|
||||||
|
<Menu.Item key="scriptcat/userscript">
|
||||||
|
<a
|
||||||
|
href="https://scriptcat.org/search"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
<IconLink /> {t("script_gallery")}
|
||||||
|
</a>
|
||||||
|
</Menu.Item>
|
||||||
|
<Menu.Item key="tampermonkey/bbs">
|
||||||
|
<a
|
||||||
|
href="https://bbs.tampermonkey.net.cn/"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
<IconLink /> {t("community_forum")}
|
||||||
|
</a>
|
||||||
|
</Menu.Item>
|
||||||
|
<Menu.Item key="GitHub">
|
||||||
|
<a
|
||||||
|
href="https://github.com/scriptscat/scriptcat"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
<IconGithub /> GitHub
|
||||||
|
</a>
|
||||||
|
</Menu.Item>
|
||||||
|
</Menu.SubMenu>
|
||||||
|
<Menu.Item
|
||||||
|
key="/guide"
|
||||||
|
onClick={() => {
|
||||||
|
guideRef.current?.open();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<RiGuideLine /> {t("guide")}
|
||||||
|
</Menu.Item>
|
||||||
|
<Menu.Item key="scriptcat/docs/use/">
|
||||||
|
<a
|
||||||
|
href="https://docs.scriptcat.org/docs/use/"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
<RiFileCodeLine /> {t("user_guide")}
|
||||||
|
</a>
|
||||||
|
</Menu.Item>
|
||||||
|
</Menu.SubMenu>
|
||||||
|
<MenuItem
|
||||||
|
key="/collapsible"
|
||||||
|
onClick={() => {
|
||||||
|
localStorage.collapsed = !collapsed;
|
||||||
|
setCollapsed(!collapsed);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{collapsed ? <IconRight /> : <IconLeft />} {t("collapsible")}
|
||||||
|
</MenuItem>
|
||||||
|
</Menu>
|
||||||
|
</div>
|
||||||
|
</Layout.Sider>
|
||||||
|
<Layout.Content
|
||||||
|
style={{
|
||||||
|
borderLeft: "1px solid var(--color-bg-5)",
|
||||||
|
overflow: "hidden",
|
||||||
|
padding: 10,
|
||||||
|
height: "100%",
|
||||||
|
boxSizing: "border-box",
|
||||||
|
position: "relative",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Routes>
|
||||||
|
<Route index element={<ScriptList />} />
|
||||||
|
<Route path="/script/editor">
|
||||||
|
<Route path=":id" element={<ScriptEditor />} />
|
||||||
|
<Route path="" element={<ScriptEditor />} />
|
||||||
|
</Route>
|
||||||
|
<Route path="/subscribe" element={<SubscribeList />} />
|
||||||
|
<Route path="/logger" element={<Logger />} />
|
||||||
|
<Route path="/tools" element={<Tools />} />
|
||||||
|
<Route path="/setting" element={<Setting />} />
|
||||||
|
</Routes>
|
||||||
|
</Layout.Content>
|
||||||
|
</HashRouter>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Sider;
|
157
src/pages/components/layout/SiderGuide.tsx
Normal file
157
src/pages/components/layout/SiderGuide.tsx
Normal file
@ -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<Partial<Path>>({ 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<Step> = [
|
||||||
|
{
|
||||||
|
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: <CustomTrans i18nKey="guide_script_list_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: <CustomTrans i18nKey="guide_script_list_sort_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<Path>) => {
|
||||||
|
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 (
|
||||||
|
<Joyride
|
||||||
|
callback={(data) => {
|
||||||
|
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);
|
@ -6,7 +6,7 @@ import { Subscribe } from "@App/app/repo/subscribe";
|
|||||||
import { i18nDescription, i18nName } from "@App/locales/locales";
|
import { i18nDescription, i18nName } from "@App/locales/locales";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { prepareScriptByCode, prepareSubscribeByCode, ScriptInfo } from "@App/pkg/utils/script";
|
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";
|
import { ScriptClient } from "@App/app/service/service_worker/client";
|
||||||
|
|
||||||
type Permission = { label: string; color?: string; value: string[] }[];
|
type Permission = { label: string; color?: string; value: string[] }[];
|
||||||
@ -156,11 +156,13 @@ function App() {
|
|||||||
setScriptInfo(info);
|
setScriptInfo(info);
|
||||||
setEnable(action.status === SCRIPT_STATUS_ENABLE);
|
setEnable(action.status === SCRIPT_STATUS_ENABLE);
|
||||||
setUpsertScript(action);
|
setUpsertScript(action);
|
||||||
|
// 修改网页显示title
|
||||||
|
document.title = `${!isUpdate ? t("install_script") : t("update_script")} - ${i18nName(action)} - ScriptCat`;
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
Message.error(t("script_info_load_failed"));
|
Message.error(t("script_info_load_failed"));
|
||||||
});
|
});
|
||||||
}, [t]);
|
}, [isUpdate, t]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full">
|
<div className="h-full">
|
||||||
@ -253,11 +255,9 @@ function App() {
|
|||||||
Message.success(t("install_success")!);
|
Message.success(t("install_success")!);
|
||||||
setBtnText(t("install_success")!);
|
setBtnText(t("install_success")!);
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
setTimeout(() => {
|
||||||
!isDebug() &&
|
closeWindow();
|
||||||
setTimeout(() => {
|
}, 500);
|
||||||
closeWindow();
|
|
||||||
}, 200);
|
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
Message.error(`${t("install_failed")}: ${e}`);
|
Message.error(`${t("install_failed")}: ${e}`);
|
||||||
|
24
src/pages/options.html
Normal file
24
src/pages/options.html
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title><%= htmlRspackPlugin.options.title %></title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<iframe src="/src/sandbox.html" name="sandbox" sandbox="allow-scripts" style="display: none"></iframe>
|
||||||
|
</body>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: var(--color-bg-2);
|
||||||
|
color: var(--color-text-1);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<% if rspackConfig.mode=="script" { %>
|
||||||
|
<script type="text/javascript" src="/_locales/i18n.js"></script>
|
||||||
|
<script type="text/javascript" src="https://cdn.crowdin.com/jipt/jipt.js"></script>
|
||||||
|
<% } %>
|
||||||
|
</html>
|
@ -4,22 +4,26 @@ import MainLayout from "../components/layout/MainLayout.tsx";
|
|||||||
import "@arco-design/web-react/dist/css/arco.css";
|
import "@arco-design/web-react/dist/css/arco.css";
|
||||||
import "@App/locales/locales";
|
import "@App/locales/locales";
|
||||||
import "@App/index.css";
|
import "@App/index.css";
|
||||||
|
import "./index.css";
|
||||||
import { Provider } from "react-redux";
|
import { Provider } from "react-redux";
|
||||||
import { store } from "@App/store/store.ts";
|
import { store } from "@App/store/store.ts";
|
||||||
import { Broker } from "@Packages/message/message_queue.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) => {
|
// border.subscribe("installScript", (message) => {
|
||||||
console.log(message);
|
// console.log(message);
|
||||||
});
|
// });
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
|
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<MainLayout className="!flex-col !px-4 box-border">p</MainLayout>
|
<MainLayout className="!flex-row">
|
||||||
|
<Sider />
|
||||||
|
</MainLayout>
|
||||||
</Provider>
|
</Provider>
|
||||||
</React.StrictMode>
|
</React.StrictMode>
|
||||||
);
|
);
|
||||||
|
@ -1,14 +1,5 @@
|
|||||||
import React, { useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
import {
|
import { BackTop, Button, Card, DatePicker, Input, List, Message, Space } from "@arco-design/web-react";
|
||||||
BackTop,
|
|
||||||
Button,
|
|
||||||
Card,
|
|
||||||
DatePicker,
|
|
||||||
Input,
|
|
||||||
List,
|
|
||||||
Message,
|
|
||||||
Space,
|
|
||||||
} from "@arco-design/web-react";
|
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import Text from "@arco-design/web-react/es/Typography/text";
|
import Text from "@arco-design/web-react/es/Typography/text";
|
||||||
import { Logger, LoggerDAO } from "@App/app/repo/logger";
|
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 { IconPlus } from "@arco-design/web-react/icon";
|
||||||
import { useSearchParams } from "react-router-dom";
|
import { useSearchParams } from "react-router-dom";
|
||||||
import { formatUnixTime } from "@App/pkg/utils/utils";
|
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";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
function LoggerPage() {
|
function LoggerPage() {
|
||||||
@ -28,12 +17,10 @@ function LoggerPage() {
|
|||||||
const [logs, setLogs] = React.useState<Logger[]>([]);
|
const [logs, setLogs] = React.useState<Logger[]>([]);
|
||||||
const [queryLogs, setQueryLogs] = React.useState<Logger[]>([]);
|
const [queryLogs, setQueryLogs] = React.useState<Logger[]>([]);
|
||||||
const [search, setSearch] = React.useState<string>("");
|
const [search, setSearch] = React.useState<string>("");
|
||||||
const [startTime, setStartTime] = React.useState(
|
const [startTime, setStartTime] = React.useState(dayjs().subtract(24, "hour").unix());
|
||||||
dayjs().subtract(24, "hour").unix()
|
|
||||||
);
|
|
||||||
const [endTime, setEndTime] = React.useState(dayjs().unix());
|
const [endTime, setEndTime] = React.useState(dayjs().unix());
|
||||||
const loggerDAO = new LoggerDAO();
|
const loggerDAO = new LoggerDAO();
|
||||||
const systemConfig = IoC.instance(SystemConfig) as SystemConfig;
|
const systemConfig = { logCleanCycle: 1 };
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const onQueryLog = () => {
|
const onQueryLog = () => {
|
||||||
@ -46,35 +33,27 @@ function LoggerPage() {
|
|||||||
const value = log.label[query.key];
|
const value = log.label[query.key];
|
||||||
switch (query.condition) {
|
switch (query.condition) {
|
||||||
case "=":
|
case "=":
|
||||||
// eslint-disable-next-line eqeqeq
|
|
||||||
if (value != query.value) {
|
if (value != query.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "=~":
|
case "=~":
|
||||||
if (
|
if (typeof value === "string" && value.indexOf(query.value) === -1) {
|
||||||
typeof value === "string" &&
|
|
||||||
value.indexOf(query.value) === -1
|
|
||||||
) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "!=":
|
case "!=":
|
||||||
// eslint-disable-next-line eqeqeq
|
|
||||||
if (value == query.value) {
|
if (value == query.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "!~":
|
case "!~":
|
||||||
if (
|
if (typeof value === "string" && value.indexOf(query.value) === -1) {
|
||||||
typeof value === "string" &&
|
|
||||||
value.indexOf(query.value) === -1
|
|
||||||
) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// eslint-disable-next-line eqeqeq
|
|
||||||
if (value != query.value) {
|
if (value != query.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -129,11 +108,7 @@ function LoggerPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<BackTop
|
<BackTop visibleHeight={30} style={{ position: "absolute" }} target={() => document.getElementById("backtop")!} />
|
||||||
visibleHeight={30}
|
|
||||||
style={{ position: "absolute" }}
|
|
||||||
target={() => document.getElementById("backtop")!}
|
|
||||||
/>
|
|
||||||
<div
|
<div
|
||||||
id="backtop"
|
id="backtop"
|
||||||
style={{
|
style={{
|
||||||
@ -316,8 +291,7 @@ function LoggerPage() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Text>
|
<Text>
|
||||||
{formatUnixTime(startTime)} {t("to")} {formatUnixTime(endTime)}{" "}
|
{formatUnixTime(startTime)} {t("to")} {formatUnixTime(endTime)} {t("total_logs", { length: logs.length })}
|
||||||
{t("total_logs", { length: logs.length })}
|
|
||||||
{init === 4
|
{init === 4
|
||||||
? `, ${t("filtered_logs", { length: queryLogs.length })}`
|
? `, ${t("filtered_logs", { length: queryLogs.length })}`
|
||||||
: `, ${t("enter_filter_conditions")}`}
|
: `, ${t("enter_filter_conditions")}`}
|
||||||
@ -334,20 +308,18 @@ function LoggerPage() {
|
|||||||
key={index}
|
key={index}
|
||||||
style={{
|
style={{
|
||||||
background:
|
background:
|
||||||
// eslint-disable-next-line no-nested-ternary
|
|
||||||
item.level === "error"
|
item.level === "error"
|
||||||
? "var(--color-danger-light-2)" // eslint-disable-next-line no-nested-ternary
|
? "var(--color-danger-light-2)"
|
||||||
: item.level === "warn"
|
: item.level === "warn"
|
||||||
? "var(--color-warning-light-2)"
|
? "var(--color-warning-light-2)"
|
||||||
: item.level === "info"
|
: item.level === "info"
|
||||||
? "var(--color-success-light-2)"
|
? "var(--color-success-light-2)"
|
||||||
: "var(--color-primary-light-1)",
|
: "var(--color-primary-light-1)",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{formatUnixTime(item.createtime / 1000)}{" "}
|
{formatUnixTime(item.createtime / 1000)}{" "}
|
||||||
{typeof item.message === "object"
|
{typeof item.message === "object" ? JSON.stringify(item.message) : item.message}{" "}
|
||||||
? JSON.stringify(item.message)
|
|
||||||
: item.message}{" "}
|
|
||||||
{JSON.stringify(item.label)}
|
{JSON.stringify(item.label)}
|
||||||
</List.Item>
|
</List.Item>
|
||||||
)}
|
)}
|
||||||
|
@ -25,7 +25,6 @@ import {
|
|||||||
SCRIPT_STATUS_ENABLE,
|
SCRIPT_STATUS_ENABLE,
|
||||||
SCRIPT_TYPE_BACKGROUND,
|
SCRIPT_TYPE_BACKGROUND,
|
||||||
SCRIPT_TYPE_NORMAL,
|
SCRIPT_TYPE_NORMAL,
|
||||||
ScriptDAO,
|
|
||||||
UserConfig,
|
UserConfig,
|
||||||
} from "@App/app/repo/scripts";
|
} from "@App/app/repo/scripts";
|
||||||
import {
|
import {
|
||||||
@ -46,7 +45,6 @@ import {
|
|||||||
RiUploadCloudFill,
|
RiUploadCloudFill,
|
||||||
} from "react-icons/ri";
|
} from "react-icons/ri";
|
||||||
import { Link, useNavigate, useSearchParams } from "react-router-dom";
|
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 { RefInputType } from "@arco-design/web-react/es/Input/interface";
|
||||||
import Text from "@arco-design/web-react/es/Typography/text";
|
import Text from "@arco-design/web-react/es/Typography/text";
|
||||||
import {
|
import {
|
||||||
@ -59,28 +57,21 @@ import {
|
|||||||
useSensors,
|
useSensors,
|
||||||
} from "@dnd-kit/core";
|
} from "@dnd-kit/core";
|
||||||
import {
|
import {
|
||||||
arrayMove,
|
|
||||||
SortableContext,
|
SortableContext,
|
||||||
sortableKeyboardCoordinates,
|
sortableKeyboardCoordinates,
|
||||||
useSortable,
|
useSortable,
|
||||||
verticalListSortingStrategy,
|
verticalListSortingStrategy,
|
||||||
} from "@dnd-kit/sortable";
|
} from "@dnd-kit/sortable";
|
||||||
import { CSS } from "@dnd-kit/utilities";
|
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 UserConfigPanel from "@App/pages/components/UserConfigPanel";
|
||||||
import CloudScriptPlan from "@App/pages/components/CloudScriptPlan";
|
import CloudScriptPlan from "@App/pages/components/CloudScriptPlan";
|
||||||
import SynchronizeController from "@App/app/service/synchronize/controller";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { nextTime, semTime } from "@App/pkg/utils/utils";
|
import { nextTime, semTime } from "@App/pkg/utils/utils";
|
||||||
import { i18nName } from "@App/locales/locales";
|
import { i18nName } from "@App/locales/locales";
|
||||||
import { SystemConfig } from "@App/pkg/config/config";
|
import { ListHomeRender, ScriptIcons } from "./utils";
|
||||||
import {
|
import { useAppDispatch, useAppSelector } from "@App/store/hooks";
|
||||||
getValues,
|
import { fetchScriptList, selectScripts } from "@App/store/features/script";
|
||||||
ListHomeRender,
|
import { selectScriptListColumnWidth } from "@App/store/features/setting";
|
||||||
ScriptIcons,
|
|
||||||
scriptListSort,
|
|
||||||
} from "./utils";
|
|
||||||
|
|
||||||
type ListType = Script & { loading?: boolean };
|
type ListType = Script & { loading?: boolean };
|
||||||
|
|
||||||
@ -91,43 +82,38 @@ function ScriptList() {
|
|||||||
values: { [key: string]: any };
|
values: { [key: string]: any };
|
||||||
}>();
|
}>();
|
||||||
const [cloudScript, setCloudScript] = useState<Script>();
|
const [cloudScript, setCloudScript] = useState<Script>();
|
||||||
const scriptCtrl = IoC.instance(ScriptController) as ScriptController;
|
const dispatch = useAppDispatch();
|
||||||
const synchronizeCtrl = IoC.instance(
|
const scriptList = useAppSelector(selectScripts);
|
||||||
SynchronizeController
|
const scriptListColumnWidth = useAppSelector(selectScriptListColumnWidth);
|
||||||
) as SynchronizeController;
|
|
||||||
const runtimeCtrl = IoC.instance(RuntimeController) as RuntimeController;
|
|
||||||
const [scriptList, setScriptList] = useState<ListType[]>([]);
|
|
||||||
const inputRef = useRef<RefInputType>(null);
|
const inputRef = useRef<RefInputType>(null);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const openUserConfig = parseInt(
|
const openUserConfig = parseInt(useSearchParams()[0].get("userConfig") || "", 10);
|
||||||
useSearchParams()[0].get("userConfig") || "",
|
|
||||||
10
|
|
||||||
);
|
|
||||||
const [showAction, setShowAction] = useState(false);
|
const [showAction, setShowAction] = useState(false);
|
||||||
const [action, setAction] = useState("");
|
const [action, setAction] = useState("");
|
||||||
const [select, setSelect] = useState<Script[]>([]);
|
const [select, setSelect] = useState<Script[]>([]);
|
||||||
const [selectColumn, setSelectColumn] = useState(0);
|
const [selectColumn, setSelectColumn] = useState(0);
|
||||||
const systemConfig = IoC.instance(SystemConfig) as SystemConfig;
|
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
dispatch(fetchScriptList());
|
||||||
|
// 监听脚本安装/运行
|
||||||
// Monitor script running status
|
// Monitor script running status
|
||||||
const channel = runtimeCtrl.watchRunStatus();
|
// const channel = runtimeCtrl.watchRunStatus();
|
||||||
channel.setHandler(([id, status]: any) => {
|
// channel.setHandler(([id, status]: any) => {
|
||||||
setScriptList((list) => {
|
// setScriptList((list) => {
|
||||||
return list.map((item) => {
|
// return list.map((item) => {
|
||||||
if (item.id === id) {
|
// if (item.id === id) {
|
||||||
item.runStatus = status;
|
// item.runStatus = status;
|
||||||
}
|
// }
|
||||||
return item;
|
// return item;
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
return () => {
|
// return () => {
|
||||||
channel.disChannel();
|
// channel.disChannel();
|
||||||
};
|
// };
|
||||||
}, []);
|
}, [dispatch]);
|
||||||
|
|
||||||
const columns: ColumnProps[] = [
|
const columns: ColumnProps[] = [
|
||||||
{
|
{
|
||||||
@ -167,27 +153,27 @@ function ScriptList() {
|
|||||||
loading={item.loading}
|
loading={item.loading}
|
||||||
disabled={item.loading}
|
disabled={item.loading}
|
||||||
onChange={(checked) => {
|
onChange={(checked) => {
|
||||||
setScriptList((list) => {
|
// setScriptList((list) => {
|
||||||
const index = list.findIndex((script) => script.id === item.id);
|
// const index = list.findIndex((script) => script.id === item.id);
|
||||||
list[index].loading = true;
|
// list[index].loading = true;
|
||||||
let p: Promise<any>;
|
// let p: Promise<any>;
|
||||||
if (checked) {
|
// if (checked) {
|
||||||
p = scriptCtrl.enable(item.id).then(() => {
|
// p = scriptCtrl.enable(item.id).then(() => {
|
||||||
list[index].status = SCRIPT_STATUS_ENABLE;
|
// list[index].status = SCRIPT_STATUS_ENABLE;
|
||||||
});
|
// });
|
||||||
} else {
|
// } else {
|
||||||
p = scriptCtrl.disable(item.id).then(() => {
|
// p = scriptCtrl.disable(item.id).then(() => {
|
||||||
list[index].status = SCRIPT_STATUS_DISABLE;
|
// list[index].status = SCRIPT_STATUS_DISABLE;
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
p.catch((err) => {
|
// p.catch((err) => {
|
||||||
Message.error(err);
|
// Message.error(err);
|
||||||
}).finally(() => {
|
// }).finally(() => {
|
||||||
list[index].loading = false;
|
// list[index].loading = false;
|
||||||
setScriptList([...list]);
|
// setScriptList([...list]);
|
||||||
});
|
// });
|
||||||
return list;
|
// return list;
|
||||||
});
|
// });
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@ -199,7 +185,6 @@ function ScriptList() {
|
|||||||
dataIndex: "name",
|
dataIndex: "name",
|
||||||
sorter: (a, b) => a.name.localeCompare(b.name),
|
sorter: (a, b) => a.name.localeCompare(b.name),
|
||||||
filterIcon: <IconSearch />,
|
filterIcon: <IconSearch />,
|
||||||
// eslint-disable-next-line react/no-unstable-nested-components
|
|
||||||
filterDropdown: ({ filterKeys, setFilterKeys, confirm }: any) => {
|
filterDropdown: ({ filterKeys, setFilterKeys, confirm }: any) => {
|
||||||
return (
|
return (
|
||||||
<div className="arco-table-custom-filter">
|
<div className="arco-table-custom-filter">
|
||||||
@ -244,7 +229,7 @@ function ScriptList() {
|
|||||||
return (
|
return (
|
||||||
<Tooltip content={col} position="tl">
|
<Tooltip content={col} position="tl">
|
||||||
<Link
|
<Link
|
||||||
to={`/script/editor/${item.id}`}
|
to={`/script/editor/${item.uuid}`}
|
||||||
style={{
|
style={{
|
||||||
textDecoration: "none",
|
textDecoration: "none",
|
||||||
}}
|
}}
|
||||||
@ -287,7 +272,7 @@ function ScriptList() {
|
|||||||
pathname: "logger",
|
pathname: "logger",
|
||||||
search: `query=${encodeURIComponent(
|
search: `query=${encodeURIComponent(
|
||||||
JSON.stringify([
|
JSON.stringify([
|
||||||
{ key: "scriptId", value: item.id },
|
{ key: "scriptId", value: item.uuid },
|
||||||
{
|
{
|
||||||
key: "component",
|
key: "component",
|
||||||
value: "GM_log",
|
value: "GM_log",
|
||||||
@ -317,9 +302,7 @@ function ScriptList() {
|
|||||||
if (item.type === SCRIPT_TYPE_BACKGROUND) {
|
if (item.type === SCRIPT_TYPE_BACKGROUND) {
|
||||||
tooltip = t("background_script_tooltip");
|
tooltip = t("background_script_tooltip");
|
||||||
} else {
|
} else {
|
||||||
tooltip = `${t("scheduled_script_tooltip")} ${nextTime(
|
tooltip = `${t("scheduled_script_tooltip")} ${nextTime(item.metadata.crontab[0])}`;
|
||||||
item.metadata.crontab[0]
|
|
||||||
)}`;
|
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Tooltip content={tooltip}>
|
<Tooltip content={tooltip}>
|
||||||
@ -332,9 +315,7 @@ function ScriptList() {
|
|||||||
}}
|
}}
|
||||||
onClick={toLogger}
|
onClick={toLogger}
|
||||||
>
|
>
|
||||||
{item.runStatus === SCRIPT_RUN_STATUS_RUNNING
|
{item.runStatus === SCRIPT_RUN_STATUS_RUNNING ? t("running") : t("completed")}
|
||||||
? t("running")
|
|
||||||
: t("completed")}
|
|
||||||
</Tag>
|
</Tag>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
@ -351,8 +332,7 @@ function ScriptList() {
|
|||||||
<Tooltip
|
<Tooltip
|
||||||
content={
|
content={
|
||||||
<p style={{ margin: 0 }}>
|
<p style={{ margin: 0 }}>
|
||||||
{t("subscription_link")}:{" "}
|
{t("subscription_link")}: {decodeURIComponent(item.subscribeUrl)}
|
||||||
{decodeURIComponent(item.subscribeUrl)}
|
|
||||||
</p>
|
</p>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@ -440,41 +420,40 @@ function ScriptList() {
|
|||||||
sorter: (a, b) => a.updatetime - b.updatetime,
|
sorter: (a, b) => a.updatetime - b.updatetime,
|
||||||
render(col, script: Script) {
|
render(col, script: Script) {
|
||||||
return (
|
return (
|
||||||
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
|
|
||||||
<span
|
<span
|
||||||
style={{
|
style={{
|
||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
}}
|
}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!script.checkUpdateUrl) {
|
// if (!script.checkUpdateUrl) {
|
||||||
Message.warning(t("update_not_supported")!);
|
// Message.warning(t("update_not_supported")!);
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
Message.info({
|
// Message.info({
|
||||||
id: "checkupdate",
|
// id: "checkupdate",
|
||||||
content: t("checking_for_updates"),
|
// content: t("checking_for_updates"),
|
||||||
});
|
// });
|
||||||
scriptCtrl
|
// scriptCtrl
|
||||||
.checkUpdate(script.id)
|
// .checkUpdate(script.id)
|
||||||
.then((res) => {
|
// .then((res) => {
|
||||||
if (res) {
|
// if (res) {
|
||||||
Message.warning({
|
// Message.warning({
|
||||||
id: "checkupdate",
|
// id: "checkupdate",
|
||||||
content: t("new_version_available"),
|
// content: t("new_version_available"),
|
||||||
});
|
// });
|
||||||
} else {
|
// } else {
|
||||||
Message.success({
|
// Message.success({
|
||||||
id: "checkupdate",
|
// id: "checkupdate",
|
||||||
content: t("latest_version"),
|
// content: t("latest_version"),
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
})
|
// })
|
||||||
.catch((e) => {
|
// .catch((e) => {
|
||||||
Message.error({
|
// Message.error({
|
||||||
id: "checkupdate",
|
// id: "checkupdate",
|
||||||
content: `${t("update_check_failed")}: ${e.message}`,
|
// content: `${t("update_check_failed")}: ${e.message}`,
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{semTime(new Date(col))}
|
{semTime(new Date(col))}
|
||||||
@ -490,7 +469,7 @@ function ScriptList() {
|
|||||||
render(col, item: Script) {
|
render(col, item: Script) {
|
||||||
return (
|
return (
|
||||||
<Button.Group>
|
<Button.Group>
|
||||||
<Link to={`/script/editor/${item.id}`}>
|
<Link to={`/script/editor/${item.uuid}`}>
|
||||||
<Button
|
<Button
|
||||||
type="text"
|
type="text"
|
||||||
icon={<RiPencilFill />}
|
icon={<RiPencilFill />}
|
||||||
@ -503,12 +482,12 @@ function ScriptList() {
|
|||||||
title={t("confirm_delete_script")}
|
title={t("confirm_delete_script")}
|
||||||
icon={<RiDeleteBin5Fill />}
|
icon={<RiDeleteBin5Fill />}
|
||||||
onOk={() => {
|
onOk={() => {
|
||||||
setScriptList((list) => {
|
// setScriptList((list) => {
|
||||||
return list.filter((i) => i.id !== item.id);
|
// return list.filter((i) => i.id !== item.id);
|
||||||
});
|
// });
|
||||||
scriptCtrl.delete(item.id).catch((e) => {
|
// scriptCtrl.delete(item.id).catch((e) => {
|
||||||
Message.error(`${t("delete_failed")}: ${e}`);
|
// Message.error(`${t("delete_failed")}: ${e}`);
|
||||||
});
|
// });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
@ -526,13 +505,13 @@ function ScriptList() {
|
|||||||
icon={<RiSettings3Fill />}
|
icon={<RiSettings3Fill />}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
// Get value
|
// Get value
|
||||||
getValues(item).then((newValues) => {
|
// getValues(item).then((newValues) => {
|
||||||
setUserConfig({
|
// setUserConfig({
|
||||||
userConfig: { ...item.config! },
|
// userConfig: { ...item.config! },
|
||||||
script: item,
|
// script: item,
|
||||||
values: newValues,
|
// values: newValues,
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
color: "var(--color-text-2)",
|
color: "var(--color-text-2)",
|
||||||
@ -546,16 +525,16 @@ function ScriptList() {
|
|||||||
icon={<RiStopFill />}
|
icon={<RiStopFill />}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
// Stop script
|
// Stop script
|
||||||
Message.loading({
|
// Message.loading({
|
||||||
id: "script-stop",
|
// id: "script-stop",
|
||||||
content: t("stopping_script"),
|
// content: t("stopping_script"),
|
||||||
});
|
// });
|
||||||
await runtimeCtrl.stopScript(item.id);
|
// await runtimeCtrl.stopScript(item.id);
|
||||||
Message.success({
|
// Message.success({
|
||||||
id: "script-stop",
|
// id: "script-stop",
|
||||||
content: t("script_stopped"),
|
// content: t("script_stopped"),
|
||||||
duration: 3000,
|
// duration: 3000,
|
||||||
});
|
// });
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
color: "var(--color-text-2)",
|
color: "var(--color-text-2)",
|
||||||
@ -567,25 +546,25 @@ function ScriptList() {
|
|||||||
icon={<RiPlayFill />}
|
icon={<RiPlayFill />}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
// Start script
|
// Start script
|
||||||
Message.loading({
|
// Message.loading({
|
||||||
id: "script-run",
|
// id: "script-run",
|
||||||
content: t("starting_script"),
|
// content: t("starting_script"),
|
||||||
});
|
// });
|
||||||
await runtimeCtrl.startScript(item.id);
|
// await runtimeCtrl.startScript(item.id);
|
||||||
Message.success({
|
// Message.success({
|
||||||
id: "script-run",
|
// id: "script-run",
|
||||||
content: t("script_started"),
|
// content: t("script_started"),
|
||||||
duration: 3000,
|
// duration: 3000,
|
||||||
});
|
// });
|
||||||
setScriptList((list) => {
|
// setScriptList((list) => {
|
||||||
for (let i = 0; i < list.length; i += 1) {
|
// for (let i = 0; i < list.length; i += 1) {
|
||||||
if (list[i].id === item.id) {
|
// if (list[i].id === item.id) {
|
||||||
list[i].runStatus = SCRIPT_RUN_STATUS_RUNNING;
|
// list[i].runStatus = SCRIPT_RUN_STATUS_RUNNING;
|
||||||
break;
|
// break;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
return [...list];
|
// return [...list];
|
||||||
});
|
// });
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
color: "var(--color-text-2)",
|
color: "var(--color-text-2)",
|
||||||
@ -612,32 +591,31 @@ function ScriptList() {
|
|||||||
|
|
||||||
const [newColumns, setNewColumns] = useState<ColumnProps[]>([]);
|
const [newColumns, setNewColumns] = useState<ColumnProps[]>([]);
|
||||||
|
|
||||||
|
// 设置列和排序
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const dao = new ScriptDAO();
|
// const dao = new ScriptDAO();
|
||||||
dao.table
|
// dao.table
|
||||||
.orderBy("sort")
|
// .orderBy("sort")
|
||||||
.toArray()
|
// .toArray()
|
||||||
.then(async (scripts) => {
|
// .then(async (scripts) => {
|
||||||
// Sort when a new script is added (-1)
|
// // Sort when a new script is added (-1)
|
||||||
scriptListSort(scripts);
|
// scriptListSort(scripts);
|
||||||
// Open user config panel
|
// // Open user config panel
|
||||||
if (openUserConfig) {
|
// if (openUserConfig) {
|
||||||
const script = scripts.find((item) => item.id === openUserConfig);
|
// const script = scripts.find((item) => item.id === openUserConfig);
|
||||||
if (script && script.config) {
|
// if (script && script.config) {
|
||||||
setUserConfig({
|
// setUserConfig({
|
||||||
script,
|
// script,
|
||||||
userConfig: script.config,
|
// userConfig: script.config,
|
||||||
values: await getValues(script),
|
// values: await getValues(script),
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
setScriptList(scripts);
|
// setScriptList(scripts);
|
||||||
});
|
// });
|
||||||
|
|
||||||
setNewColumns(
|
setNewColumns(
|
||||||
columns.map((item) => {
|
columns.map((item) => {
|
||||||
item.width =
|
item.width = scriptListColumnWidth[item.key!] ?? item.width;
|
||||||
systemConfig.scriptListColumnWidth[item.key!] ?? item.width;
|
|
||||||
return item;
|
return item;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@ -651,7 +629,6 @@ function ScriptList() {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
// eslint-disable-next-line react/no-unstable-nested-components
|
|
||||||
const SortableWrapper = (props: any, ref: any) => {
|
const SortableWrapper = (props: any, ref: any) => {
|
||||||
return (
|
return (
|
||||||
<DndContext
|
<DndContext
|
||||||
@ -663,27 +640,24 @@ function ScriptList() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (active.id !== over.id) {
|
if (active.id !== over.id) {
|
||||||
setScriptList((items) => {
|
// setScriptList((items) => {
|
||||||
let oldIndex = 0;
|
// let oldIndex = 0;
|
||||||
let newIndex = 0;
|
// let newIndex = 0;
|
||||||
items.forEach((item, index) => {
|
// items.forEach((item, index) => {
|
||||||
if (item.id === active.id) {
|
// if (item.id === active.id) {
|
||||||
oldIndex = index;
|
// oldIndex = index;
|
||||||
} else if (item.id === over.id) {
|
// } else if (item.id === over.id) {
|
||||||
newIndex = index;
|
// newIndex = index;
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
const newItems = arrayMove(items, oldIndex, newIndex);
|
// const newItems = arrayMove(items, oldIndex, newIndex);
|
||||||
scriptListSort(newItems);
|
// scriptListSort(newItems);
|
||||||
return newItems;
|
// return newItems;
|
||||||
});
|
// });
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SortableContext
|
<SortableContext items={scriptList} strategy={verticalListSortingStrategy}>
|
||||||
items={scriptList}
|
|
||||||
strategy={verticalListSortingStrategy}
|
|
||||||
>
|
|
||||||
<table ref={ref} {...props} />
|
<table ref={ref} {...props} />
|
||||||
</SortableContext>
|
</SortableContext>
|
||||||
</DndContext>
|
</DndContext>
|
||||||
@ -704,10 +678,8 @@ function ScriptList() {
|
|||||||
|
|
||||||
const sortIndex = dealColumns.findIndex((item) => item.key === "sort");
|
const sortIndex = dealColumns.findIndex((item) => item.key === "sort");
|
||||||
|
|
||||||
// eslint-disable-next-line react/no-unstable-nested-components
|
|
||||||
const SortableItem = (props: any) => {
|
const SortableItem = (props: any) => {
|
||||||
const { attributes, listeners, setNodeRef, transform, transition } =
|
const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ id: props!.record.uuid });
|
||||||
useSortable({ id: props!.record.id });
|
|
||||||
|
|
||||||
const style = {
|
const style = {
|
||||||
transform: CSS.Transform.toString(transform),
|
transform: CSS.Transform.toString(transform),
|
||||||
@ -715,7 +687,6 @@ function ScriptList() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 替换排序列,使其可以拖拽
|
// 替换排序列,使其可以拖拽
|
||||||
// eslint-disable-next-line react/destructuring-assignment
|
|
||||||
props.children[sortIndex + 1] = (
|
props.children[sortIndex + 1] = (
|
||||||
<td
|
<td
|
||||||
className="arco-table-td"
|
className="arco-table-td"
|
||||||
@ -778,9 +749,7 @@ function ScriptList() {
|
|||||||
<Select.Option value="disable">{t("disable")}</Select.Option>
|
<Select.Option value="disable">{t("disable")}</Select.Option>
|
||||||
<Select.Option value="export">{t("export")}</Select.Option>
|
<Select.Option value="export">{t("export")}</Select.Option>
|
||||||
<Select.Option value="delete">{t("delete")}</Select.Option>
|
<Select.Option value="delete">{t("delete")}</Select.Option>
|
||||||
<Select.Option value="check_update">
|
<Select.Option value="check_update">{t("check_update")}</Select.Option>
|
||||||
{t("check_update")}
|
|
||||||
</Select.Option>
|
|
||||||
</Select>
|
</Select>
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
@ -853,9 +822,7 @@ function ScriptList() {
|
|||||||
// 需要更新
|
// 需要更新
|
||||||
Message.warning({
|
Message.warning({
|
||||||
id: "checkupdate",
|
id: "checkupdate",
|
||||||
content: `${i18nName(item)} ${t(
|
content: `${i18nName(item)} ${t("new_version_available")}`,
|
||||||
"new_version_available"
|
|
||||||
)}`,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (index === array.length - 1) {
|
if (index === array.length - 1) {
|
||||||
@ -869,9 +836,7 @@ function ScriptList() {
|
|||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
Message.error({
|
Message.error({
|
||||||
id: "checkupdate",
|
id: "checkupdate",
|
||||||
content: `${t("update_check_failed")}: ${
|
content: `${t("update_check_failed")}: ${e.message}`,
|
||||||
e.message
|
|
||||||
}`,
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -944,12 +909,7 @@ function ScriptList() {
|
|||||||
position="bl"
|
position="bl"
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
type={
|
type={newColumns[selectColumn].width === 0 || newColumns[selectColumn].width === -1 ? "" : "number"}
|
||||||
newColumns[selectColumn].width === 0 ||
|
|
||||||
newColumns[selectColumn].width === -1
|
|
||||||
? ""
|
|
||||||
: "number"
|
|
||||||
}
|
|
||||||
style={{ width: "80px" }}
|
style={{ width: "80px" }}
|
||||||
size="mini"
|
size="mini"
|
||||||
value={
|
value={
|
||||||
@ -957,8 +917,8 @@ function ScriptList() {
|
|||||||
newColumns[selectColumn].width === 0
|
newColumns[selectColumn].width === 0
|
||||||
? t("auto")
|
? t("auto")
|
||||||
: newColumns[selectColumn].width === -1
|
: newColumns[selectColumn].width === -1
|
||||||
? t("hide")
|
? t("hide")
|
||||||
: newColumns[selectColumn].width?.toString()
|
: newColumns[selectColumn].width?.toString()
|
||||||
}
|
}
|
||||||
onChange={(val) => {
|
onChange={(val) => {
|
||||||
setNewColumns((cols) => {
|
setNewColumns((cols) => {
|
||||||
@ -1031,11 +991,7 @@ function ScriptList() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{userConfig && (
|
{userConfig && (
|
||||||
<UserConfigPanel
|
<UserConfigPanel script={userConfig.script} userConfig={userConfig.userConfig} values={userConfig.values} />
|
||||||
script={userConfig.script}
|
|
||||||
userConfig={userConfig.userConfig}
|
|
||||||
values={userConfig.values}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
<CloudScriptPlan
|
<CloudScriptPlan
|
||||||
script={cloudScript}
|
script={cloudScript}
|
||||||
|
@ -8,15 +8,9 @@ import {
|
|||||||
Select,
|
Select,
|
||||||
Space,
|
Space,
|
||||||
} from "@arco-design/web-react";
|
} from "@arco-design/web-react";
|
||||||
import FileSystemParams from "@App/pages/components/FileSystemParams";
|
|
||||||
import { SystemConfig } from "@App/pkg/config/config";
|
|
||||||
import IoC from "@App/app/ioc";
|
|
||||||
import FileSystemFactory, { FileSystemType } from "@Pkg/filesystem/factory";
|
|
||||||
import Title from "@arco-design/web-react/es/Typography/title";
|
import Title from "@arco-design/web-react/es/Typography/title";
|
||||||
import { IconQuestionCircleFill } from "@arco-design/web-react/icon";
|
import { IconQuestionCircleFill } from "@arco-design/web-react/icon";
|
||||||
// eslint-disable-next-line import/no-extraneous-dependencies, import/no-import-module-exports
|
|
||||||
import { format } from "prettier";
|
import { format } from "prettier";
|
||||||
// eslint-disable-next-line import/no-extraneous-dependencies, import/no-import-module-exports
|
|
||||||
import babel from "prettier/parser-babel";
|
import babel from "prettier/parser-babel";
|
||||||
import GMApiSetting from "@App/pages/components/GMApiSetting";
|
import GMApiSetting from "@App/pages/components/GMApiSetting";
|
||||||
import i18n from "@App/locales/locales";
|
import i18n from "@App/locales/locales";
|
||||||
|
@ -18,8 +18,6 @@ import {
|
|||||||
SubscribeDAO,
|
SubscribeDAO,
|
||||||
} from "@App/app/repo/subscribe";
|
} from "@App/app/repo/subscribe";
|
||||||
import { ColumnProps } from "@arco-design/web-react/es/Table";
|
import { ColumnProps } from "@arco-design/web-react/es/Table";
|
||||||
import IoC from "@App/app/ioc";
|
|
||||||
import SubscribeController from "@App/app/service/subscribe/controller";
|
|
||||||
import { IconSearch, IconUserAdd } from "@arco-design/web-react/icon";
|
import { IconSearch, IconUserAdd } from "@arco-design/web-react/icon";
|
||||||
import { RefInputType } from "@arco-design/web-react/es/Input/interface";
|
import { RefInputType } from "@arco-design/web-react/es/Input/interface";
|
||||||
import { semTime } from "@App/pkg/utils/utils";
|
import { semTime } from "@App/pkg/utils/utils";
|
||||||
|
@ -12,17 +12,11 @@ import {
|
|||||||
Space,
|
Space,
|
||||||
} from "@arco-design/web-react";
|
} from "@arco-design/web-react";
|
||||||
import Title from "@arco-design/web-react/es/Typography/title";
|
import Title from "@arco-design/web-react/es/Typography/title";
|
||||||
import IoC from "@App/app/ioc";
|
|
||||||
import SynchronizeController from "@App/app/service/synchronize/controller";
|
|
||||||
import FileSystemFactory, { FileSystemType } from "@Pkg/filesystem/factory";
|
|
||||||
import { SystemConfig } from "@App/pkg/config/config";
|
|
||||||
import { File, FileReader } from "@Pkg/filesystem/filesystem";
|
|
||||||
import { formatUnixTime } from "@App/pkg/utils/utils";
|
import { formatUnixTime } from "@App/pkg/utils/utils";
|
||||||
import FileSystemParams from "@App/pages/components/FileSystemParams";
|
import FileSystemParams from "@App/pages/components/FileSystemParams";
|
||||||
import { IconQuestionCircleFill } from "@arco-design/web-react/icon";
|
import { IconQuestionCircleFill } from "@arco-design/web-react/icon";
|
||||||
import { RefInputType } from "@arco-design/web-react/es/Input/interface";
|
import { RefInputType } from "@arco-design/web-react/es/Input/interface";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import SystemController from "@App/app/service/system/controller";
|
|
||||||
|
|
||||||
function Tools() {
|
function Tools() {
|
||||||
const [loading, setLoading] = useState<{ [key: string]: boolean }>({});
|
const [loading, setLoading] = useState<{ [key: string]: boolean }>({});
|
||||||
|
@ -3,27 +3,16 @@ import CodeEditor from "@App/pages/components/CodeEditor";
|
|||||||
import React, { useEffect, useRef, useState } from "react";
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
import { useNavigate, useParams, useSearchParams } from "react-router-dom";
|
import { useNavigate, useParams, useSearchParams } from "react-router-dom";
|
||||||
import { editor, KeyCode, KeyMod } from "monaco-editor";
|
import { editor, KeyCode, KeyMod } from "monaco-editor";
|
||||||
import {
|
import { Button, Dropdown, Grid, Menu, Message, Tabs, Tooltip } from "@arco-design/web-react";
|
||||||
Button,
|
|
||||||
Dropdown,
|
|
||||||
Grid,
|
|
||||||
Menu,
|
|
||||||
Message,
|
|
||||||
Tabs,
|
|
||||||
Tooltip,
|
|
||||||
} from "@arco-design/web-react";
|
|
||||||
import TabPane from "@arco-design/web-react/es/Tabs/tab-pane";
|
import TabPane from "@arco-design/web-react/es/Tabs/tab-pane";
|
||||||
import ScriptController from "@App/app/service/script/controller";
|
|
||||||
import normalTpl from "@App/template/normal.tpl";
|
import normalTpl from "@App/template/normal.tpl";
|
||||||
import crontabTpl from "@App/template/crontab.tpl";
|
import crontabTpl from "@App/template/crontab.tpl";
|
||||||
import backgroundTpl from "@App/template/background.tpl";
|
import backgroundTpl from "@App/template/background.tpl";
|
||||||
import { v4 as uuidv4 } from "uuid";
|
import { v4 as uuidv4 } from "uuid";
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
import IoC from "@App/app/ioc";
|
|
||||||
import LoggerCore from "@App/app/logger/core";
|
import LoggerCore from "@App/app/logger/core";
|
||||||
import Logger from "@App/app/logger/logger";
|
import Logger from "@App/app/logger/logger";
|
||||||
import { prepareScriptByCode } from "@App/pkg/utils/script";
|
import { prepareScriptByCode } from "@App/pkg/utils/script";
|
||||||
import RuntimeController from "@App/runtime/content/runtime";
|
|
||||||
import ScriptStorage from "@App/pages/components/ScriptStorage";
|
import ScriptStorage from "@App/pages/components/ScriptStorage";
|
||||||
import ScriptResource from "@App/pages/components/ScriptResource";
|
import ScriptResource from "@App/pages/components/ScriptResource";
|
||||||
import ScriptSetting from "@App/pages/components/ScriptSetting";
|
import ScriptSetting from "@App/pages/components/ScriptSetting";
|
||||||
@ -49,7 +38,7 @@ const Editor: React.FC<{
|
|||||||
const [init, setInit] = useState(false);
|
const [init, setInit] = useState(false);
|
||||||
const codeEditor = useRef<{ editor: editor.IStandaloneCodeEditor }>(null);
|
const codeEditor = useRef<{ editor: editor.IStandaloneCodeEditor }>(null);
|
||||||
// Script.uuid为key,Script为value,储存Script
|
// Script.uuid为key,Script为value,储存Script
|
||||||
ScriptMap.has(script.uuid) || ScriptMap.set(script.uuid, script);
|
ScriptMap.set(script.uuid, script);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!codeEditor.current || !codeEditor.current.editor) {
|
if (!codeEditor.current || !codeEditor.current.editor) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@ -69,13 +58,13 @@ const Editor: React.FC<{
|
|||||||
const activeEditor = editor
|
const activeEditor = editor
|
||||||
.getEditors()
|
.getEditors()
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
// eslint-disable-next-line no-underscore-dangle
|
|
||||||
.find((i) => i._focusTracker._hasFocus);
|
.find((i) => i._focusTracker._hasFocus);
|
||||||
|
|
||||||
// 仅在获取到激活的editor时,通过editor上绑定的uuid获取Script,并指定激活的editor执行快捷键action
|
// 仅在获取到激活的editor时,通过editor上绑定的uuid获取Script,并指定激活的editor执行快捷键action
|
||||||
activeEditor &&
|
if (activeEditor) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
item.action(ScriptMap.get(activeEditor.uuid), activeEditor);
|
item.action(ScriptMap.get(activeEditor.uuid), activeEditor);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
codeEditor.current.editor.onKeyUp(() => {
|
codeEditor.current.editor.onKeyUp(() => {
|
||||||
@ -85,15 +74,7 @@ const Editor: React.FC<{
|
|||||||
return () => {};
|
return () => {};
|
||||||
}, [init]);
|
}, [init]);
|
||||||
|
|
||||||
return (
|
return <CodeEditor id={id} ref={codeEditor} code={script.code} diffCode="" editable />;
|
||||||
<CodeEditor
|
|
||||||
id={id}
|
|
||||||
ref={codeEditor}
|
|
||||||
code={script.code}
|
|
||||||
diffCode=""
|
|
||||||
editable
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type EditorMenu = {
|
type EditorMenu = {
|
||||||
@ -180,8 +161,7 @@ function ScriptEditor() {
|
|||||||
>([]);
|
>([]);
|
||||||
const [scriptList, setScriptList] = useState<Script[]>([]);
|
const [scriptList, setScriptList] = useState<Script[]>([]);
|
||||||
const [currentScript, setCurrentScript] = useState<Script>();
|
const [currentScript, setCurrentScript] = useState<Script>();
|
||||||
const [selectSciptButtonAndTab, setSelectSciptButtonAndTab] =
|
const [selectSciptButtonAndTab, setSelectSciptButtonAndTab] = useState<string>("");
|
||||||
useState<string>("");
|
|
||||||
const [rightOperationTab, setRightOperationTab] = useState<{
|
const [rightOperationTab, setRightOperationTab] = useState<{
|
||||||
key: string;
|
key: string;
|
||||||
uuid: string;
|
uuid: string;
|
||||||
@ -196,10 +176,7 @@ function ScriptEditor() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
const save = (
|
const save = (script: Script, e: editor.IStandaloneCodeEditor): Promise<Script> => {
|
||||||
script: Script,
|
|
||||||
e: editor.IStandaloneCodeEditor
|
|
||||||
): Promise<Script> => {
|
|
||||||
// 解析code生成新的script并更新
|
// 解析code生成新的script并更新
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
prepareScriptByCode(e.getValue(), script.origin || "", script.uuid)
|
prepareScriptByCode(e.getValue(), script.origin || "", script.uuid)
|
||||||
@ -257,9 +234,7 @@ function ScriptEditor() {
|
|||||||
return new Promise<void>((resolve) => {
|
return new Promise<void>((resolve) => {
|
||||||
chrome.downloads.download(
|
chrome.downloads.download(
|
||||||
{
|
{
|
||||||
url: URL.createObjectURL(
|
url: URL.createObjectURL(new Blob([e.getValue()], { type: "text/javascript" })),
|
||||||
new Blob([e.getValue()], { type: "text/javascript" })
|
|
||||||
),
|
|
||||||
saveAs: true, // true直接弹出对话框;false弹出下载选项
|
saveAs: true, // true直接弹出对话框;false弹出下载选项
|
||||||
filename: `${script.name}.user.js`,
|
filename: `${script.name}.user.js`,
|
||||||
},
|
},
|
||||||
@ -306,8 +281,7 @@ function ScriptEditor() {
|
|||||||
title: "调试",
|
title: "调试",
|
||||||
hotKey: KeyMod.CtrlCmd | KeyCode.F5,
|
hotKey: KeyMod.CtrlCmd | KeyCode.F5,
|
||||||
hotKeyString: "Ctrl+F5",
|
hotKeyString: "Ctrl+F5",
|
||||||
tooltip:
|
tooltip: "只有后台脚本/定时脚本才能调试, 且调试模式下不对进行权限校验(例如@connect)",
|
||||||
"只有后台脚本/定时脚本才能调试, 且调试模式下不对进行权限校验(例如@connect)",
|
|
||||||
action: async (script, e) => {
|
action: async (script, e) => {
|
||||||
// 保存更新代码之后再调试
|
// 保存更新代码之后再调试
|
||||||
const newScript = await save(script, e);
|
const newScript = await save(script, e);
|
||||||
@ -457,44 +431,31 @@ function ScriptEditor() {
|
|||||||
// eslint-disable-next-line default-case
|
// eslint-disable-next-line default-case
|
||||||
switch (rightOperationTab.key) {
|
switch (rightOperationTab.key) {
|
||||||
case "1":
|
case "1":
|
||||||
newEditors = editors.filter(
|
newEditors = editors.filter((item) => item.script.uuid !== rightOperationTab.uuid);
|
||||||
(item) => item.script.uuid !== rightOperationTab.uuid
|
|
||||||
);
|
|
||||||
if (newEditors.length > 0) {
|
if (newEditors.length > 0) {
|
||||||
// 还有的话,如果之前有选中的,那么我们还是选中之前的,如果没有选中的我们就选中第一个
|
// 还有的话,如果之前有选中的,那么我们还是选中之前的,如果没有选中的我们就选中第一个
|
||||||
if (
|
if (rightOperationTab.selectSciptButtonAndTab === rightOperationTab.uuid) {
|
||||||
rightOperationTab.selectSciptButtonAndTab ===
|
|
||||||
rightOperationTab.uuid
|
|
||||||
) {
|
|
||||||
if (newEditors.length > 0) {
|
if (newEditors.length > 0) {
|
||||||
newEditors[0].active = true;
|
newEditors[0].active = true;
|
||||||
setSelectSciptButtonAndTab(newEditors[0].script.uuid);
|
setSelectSciptButtonAndTab(newEditors[0].script.uuid);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
setSelectSciptButtonAndTab(
|
setSelectSciptButtonAndTab(rightOperationTab.selectSciptButtonAndTab);
|
||||||
rightOperationTab.selectSciptButtonAndTab
|
|
||||||
);
|
|
||||||
// 之前选中的tab
|
// 之前选中的tab
|
||||||
editors.filter((item) => {
|
editors.filter((item) => {
|
||||||
if (
|
if (item.script.uuid === rightOperationTab.selectSciptButtonAndTab) {
|
||||||
item.script.uuid === rightOperationTab.selectSciptButtonAndTab
|
|
||||||
) {
|
|
||||||
item.active = true;
|
item.active = true;
|
||||||
} else {
|
} else {
|
||||||
item.active = false;
|
item.active = false;
|
||||||
}
|
}
|
||||||
return (
|
return item.script.uuid === rightOperationTab.selectSciptButtonAndTab;
|
||||||
item.script.uuid === rightOperationTab.selectSciptButtonAndTab
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setEditors([...newEditors]);
|
setEditors([...newEditors]);
|
||||||
break;
|
break;
|
||||||
case "2":
|
case "2":
|
||||||
newEditors = editors.filter(
|
newEditors = editors.filter((item) => item.script.uuid === rightOperationTab.uuid);
|
||||||
(item) => item.script.uuid === rightOperationTab.uuid
|
|
||||||
);
|
|
||||||
setSelectSciptButtonAndTab(rightOperationTab.uuid);
|
setSelectSciptButtonAndTab(rightOperationTab.uuid);
|
||||||
setEditors([...newEditors]);
|
setEditors([...newEditors]);
|
||||||
break;
|
break;
|
||||||
@ -657,11 +618,7 @@ function ScriptEditor() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{menuItem.tooltip ? (
|
{menuItem.tooltip ? (
|
||||||
<Tooltip
|
<Tooltip key={`m${i.toString()}`} position="right" content={menuItem.tooltip}>
|
||||||
key={`m${i.toString()}`}
|
|
||||||
position="right"
|
|
||||||
content={menuItem.tooltip}
|
|
||||||
>
|
|
||||||
{btn}
|
{btn}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
) : (
|
) : (
|
||||||
@ -722,8 +679,7 @@ function ScriptEditor() {
|
|||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
textOverflow: "ellipsis",
|
textOverflow: "ellipsis",
|
||||||
whiteSpace: "nowrap",
|
whiteSpace: "nowrap",
|
||||||
backgroundColor:
|
backgroundColor: selectSciptButtonAndTab === script.uuid ? "gray" : "",
|
||||||
selectSciptButtonAndTab === script.uuid ? "gray" : "",
|
|
||||||
}}
|
}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSelectSciptButtonAndTab(script.uuid);
|
setSelectSciptButtonAndTab(script.uuid);
|
||||||
@ -857,10 +813,10 @@ function ScriptEditor() {
|
|||||||
color: e.isChanged
|
color: e.isChanged
|
||||||
? "rgb(var(--orange-5))" // eslint-disable-next-line no-nested-ternary
|
? "rgb(var(--orange-5))" // eslint-disable-next-line no-nested-ternary
|
||||||
: e.script.uuid === selectSciptButtonAndTab
|
: e.script.uuid === selectSciptButtonAndTab
|
||||||
? "rgb(var(--green-7))"
|
? "rgb(var(--green-7))"
|
||||||
: e.active
|
: e.active
|
||||||
? "rgb(var(--green-7))"
|
? "rgb(var(--green-7))"
|
||||||
: "var(--color-text-1)",
|
: "var(--color-text-1)",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{e.script.name}
|
{e.script.name}
|
||||||
|
@ -1,15 +1,7 @@
|
|||||||
/* eslint-disable import/prefer-default-export */
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import IoC from "@App/app/ioc";
|
|
||||||
import { Metadata, Script, ScriptDAO } from "@App/app/repo/scripts";
|
import { Metadata, Script, ScriptDAO } from "@App/app/repo/scripts";
|
||||||
import ValueManager from "@App/app/service/value/manager";
|
|
||||||
import { Avatar, Button, Space, Tooltip } from "@arco-design/web-react";
|
import { Avatar, Button, Space, Tooltip } from "@arco-design/web-react";
|
||||||
import {
|
import { IconBug, IconCode, IconGithub, IconHome } from "@arco-design/web-react/icon";
|
||||||
IconBug,
|
|
||||||
IconCode,
|
|
||||||
IconGithub,
|
|
||||||
IconHome,
|
|
||||||
} from "@arco-design/web-react/icon";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
// 较对脚本排序位置
|
// 较对脚本排序位置
|
||||||
@ -17,7 +9,7 @@ export function scriptListSort(result: Script[]) {
|
|||||||
const dao = new ScriptDAO();
|
const dao = new ScriptDAO();
|
||||||
for (let i = 0; i < result.length; i += 1) {
|
for (let i = 0; i < result.length; i += 1) {
|
||||||
if (result[i].sort !== i) {
|
if (result[i].sort !== i) {
|
||||||
dao.update(result[i].id, { sort: i });
|
dao.update(result[i].uuid, { sort: i });
|
||||||
result[i].sort = i;
|
result[i].sort = i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -30,13 +22,7 @@ export function installUrlToHome(installUrl: string) {
|
|||||||
if (installUrl.indexOf("scriptcat.org") !== -1) {
|
if (installUrl.indexOf("scriptcat.org") !== -1) {
|
||||||
const id = installUrl.split("/")[5];
|
const id = installUrl.split("/")[5];
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button type="text" iconOnly size="small" target="_blank" href={`https://scriptcat.org/script-show-page/${id}`}>
|
||||||
type="text"
|
|
||||||
iconOnly
|
|
||||||
size="small"
|
|
||||||
target="_blank"
|
|
||||||
href={`https://scriptcat.org/script-show-page/${id}`}
|
|
||||||
>
|
|
||||||
<img width={16} height={16} src="/assets/logo.png" alt="" />
|
<img width={16} height={16} src="/assets/logo.png" alt="" />
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
@ -44,13 +30,7 @@ export function installUrlToHome(installUrl: string) {
|
|||||||
if (installUrl.indexOf("greasyfork.org") !== -1) {
|
if (installUrl.indexOf("greasyfork.org") !== -1) {
|
||||||
const id = installUrl.split("/")[4];
|
const id = installUrl.split("/")[4];
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button type="text" iconOnly size="small" target="_blank" href={`https://greasyfork.org/scripts/${id}`}>
|
||||||
type="text"
|
|
||||||
iconOnly
|
|
||||||
size="small"
|
|
||||||
target="_blank"
|
|
||||||
href={`https://greasyfork.org/scripts/${id}`}
|
|
||||||
>
|
|
||||||
<img width={16} height={16} src="/assets/logo/gf.png" alt="" />
|
<img width={16} height={16} src="/assets/logo/gf.png" alt="" />
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
@ -89,6 +69,7 @@ export function installUrlToHome(installUrl: string) {
|
|||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// ignore error
|
// ignore error
|
||||||
|
console.error(e);
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@ -167,28 +148,7 @@ export function ListHomeRender({ script }: { script: Script }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getValues(script: Script) {
|
export function getValues(script: Script) {
|
||||||
const { config } = script;
|
return {};
|
||||||
return (IoC.instance(ValueManager) as ValueManager)
|
|
||||||
.getValues(script)
|
|
||||||
.then((data) => {
|
|
||||||
const newValues: { [key: string]: any } = {};
|
|
||||||
Object.keys(config!).forEach((tabKey) => {
|
|
||||||
const tab = config![tabKey];
|
|
||||||
Object.keys(tab).forEach((key) => {
|
|
||||||
// 动态变量
|
|
||||||
if (tab[key].bind) {
|
|
||||||
const bindKey = tab[key].bind!.substring(1);
|
|
||||||
newValues[bindKey] =
|
|
||||||
data[bindKey] === undefined ? undefined : data[bindKey].value;
|
|
||||||
}
|
|
||||||
newValues[`${tabKey}.${key}`] =
|
|
||||||
data[`${tabKey}.${key}`] === undefined
|
|
||||||
? config![tabKey][key].default
|
|
||||||
: data[`${tabKey}.${key}`].value;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return newValues;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ScriptIconsProps = {
|
export type ScriptIconsProps = {
|
||||||
@ -218,6 +178,5 @@ export function ScriptIcons({ script, size = 32, style }: ScriptIconsProps) {
|
|||||||
</Avatar>
|
</Avatar>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react/jsx-no-useless-fragment
|
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
|
31
src/store/features/script.ts
Normal file
31
src/store/features/script.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { createAsyncThunk } from "@reduxjs/toolkit";
|
||||||
|
import { createAppSlice } from "../hooks";
|
||||||
|
import { Script, ScriptDAO } from "@App/app/repo/scripts";
|
||||||
|
|
||||||
|
export const fetchScriptList = createAsyncThunk("script/fetchScriptList", () => {
|
||||||
|
return new ScriptDAO().all();
|
||||||
|
});
|
||||||
|
|
||||||
|
export const scriptSlice = createAppSlice({
|
||||||
|
name: "script",
|
||||||
|
initialState: {
|
||||||
|
scripts: [] as Script[],
|
||||||
|
},
|
||||||
|
reducers: {},
|
||||||
|
extraReducers: (builder) => {
|
||||||
|
builder.addCase(fetchScriptList.fulfilled, (state, action) => {
|
||||||
|
const newScripts: Script[] = [];
|
||||||
|
action.payload.forEach((item) => {
|
||||||
|
newScripts.push(item);
|
||||||
|
});
|
||||||
|
state.scripts = newScripts;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
selectors: {
|
||||||
|
selectScripts: (state) => state.scripts,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// export const {} = scriptSlice.actions;
|
||||||
|
|
||||||
|
export const { selectScripts } = scriptSlice.selectors;
|
@ -10,6 +10,7 @@ export const settingSlice = createAppSlice({
|
|||||||
enable: true,
|
enable: true,
|
||||||
config: "",
|
config: "",
|
||||||
},
|
},
|
||||||
|
scriptListColumnWidth: {} as { [key: string]: number },
|
||||||
},
|
},
|
||||||
reducers: (create) => {
|
reducers: (create) => {
|
||||||
// 初始化黑夜模式
|
// 初始化黑夜模式
|
||||||
@ -45,9 +46,10 @@ export const settingSlice = createAppSlice({
|
|||||||
},
|
},
|
||||||
selectors: {
|
selectors: {
|
||||||
selectThemeMode: (state) => state.lightMode,
|
selectThemeMode: (state) => state.lightMode,
|
||||||
|
selectScriptListColumnWidth: (state) => state.scriptListColumnWidth,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const { setDarkMode } = settingSlice.actions;
|
export const { setDarkMode } = settingSlice.actions;
|
||||||
|
|
||||||
export const { selectThemeMode } = settingSlice.selectors;
|
export const { selectThemeMode, selectScriptListColumnWidth } = settingSlice.selectors;
|
||||||
|
@ -2,10 +2,11 @@ import type { Action, ThunkAction } from "@reduxjs/toolkit";
|
|||||||
import { combineSlices, configureStore } from "@reduxjs/toolkit";
|
import { combineSlices, configureStore } from "@reduxjs/toolkit";
|
||||||
import { setupListeners } from "@reduxjs/toolkit/query";
|
import { setupListeners } from "@reduxjs/toolkit/query";
|
||||||
import { settingSlice } from "./features/setting";
|
import { settingSlice } from "./features/setting";
|
||||||
|
import { scriptSlice } from "./features/script";
|
||||||
|
|
||||||
// `combineSlices` automatically combines the reducers using
|
// `combineSlices` automatically combines the reducers using
|
||||||
// their `reducerPath`s, therefore we no longer need to call `combineReducers`.
|
// their `reducerPath`s, therefore we no longer need to call `combineReducers`.
|
||||||
const rootReducer = combineSlices(settingSlice);
|
const rootReducer = combineSlices(settingSlice, scriptSlice);
|
||||||
// Infer the `RootState` type from the root reducer
|
// Infer the `RootState` type from the root reducer
|
||||||
export type RootState = ReturnType<typeof rootReducer>;
|
export type RootState = ReturnType<typeof rootReducer>;
|
||||||
|
|
||||||
|
13
src/template/background.tpl
Normal file
13
src/template/background.tpl
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// ==UserScript==
|
||||||
|
// @name New Userscript
|
||||||
|
// @namespace https://bbs.tampermonkey.net.cn/
|
||||||
|
// @version 0.1.0
|
||||||
|
// @description try to take over the world!
|
||||||
|
// @author You
|
||||||
|
// @background
|
||||||
|
// ==/UserScript==
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
// Your code here...
|
||||||
|
resolve();
|
||||||
|
});
|
3
src/template/cloudcat-package/index.tpl
Normal file
3
src/template/cloudcat-package/index.tpl
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
const utils = require('./utils');
|
||||||
|
|
||||||
|
utils.run();
|
15
src/template/cloudcat-package/package.tpl
Normal file
15
src/template/cloudcat-package/package.tpl
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"name": "cloudcat-package",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "scriptcat后台脚本打包项目",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"run": "node index.js",
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"author": "CodFrm",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"scriptcat-nodejs": "^0.1.7"
|
||||||
|
}
|
||||||
|
}
|
17
src/template/cloudcat-package/utils.tpl
Normal file
17
src/template/cloudcat-package/utils.tpl
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const { ScriptCat } = require("scriptcat-nodejs/dist/src/scriptcat");
|
||||||
|
const { ModelValues } = require("scriptcat-nodejs/dist/src/storage/values");
|
||||||
|
const { cookies } = require('./cookies');
|
||||||
|
const { values } = require('./values');
|
||||||
|
|
||||||
|
exports.run = function () {
|
||||||
|
const code = fs.readFileSync('userScript.js', 'utf8');
|
||||||
|
|
||||||
|
const run = new ScriptCat();
|
||||||
|
run.RunOnce(code, {
|
||||||
|
cookies: cookies,
|
||||||
|
values: new ModelValues(values),
|
||||||
|
}).then((res) => {
|
||||||
|
console.log(res);
|
||||||
|
});
|
||||||
|
}
|
13
src/template/crontab.tpl
Normal file
13
src/template/crontab.tpl
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// ==UserScript==
|
||||||
|
// @name New Userscript
|
||||||
|
// @namespace https://bbs.tampermonkey.net.cn/
|
||||||
|
// @version 0.1.0
|
||||||
|
// @description try to take over the world!
|
||||||
|
// @author You
|
||||||
|
// @crontab * * once * *
|
||||||
|
// ==/UserScript==
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
// Your code here...
|
||||||
|
resolve();
|
||||||
|
});
|
14
src/template/normal.tpl
Normal file
14
src/template/normal.tpl
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
// ==UserScript==
|
||||||
|
// @name New Userscript
|
||||||
|
// @namespace https://bbs.tampermonkey.net.cn/
|
||||||
|
// @version 0.1.0
|
||||||
|
// @description try to take over the world!
|
||||||
|
// @author You
|
||||||
|
// @match {{match}}
|
||||||
|
// ==/UserScript==
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// Your code here...
|
||||||
|
})();
|
Loading…
x
Reference in New Issue
Block a user