options页面组件
This commit is contained in:
parent
78152222f3
commit
9876c1cbcb
@ -26,6 +26,7 @@ export default [
|
||||
rules: {
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"@typescript-eslint/no-unused-expressions": "off",
|
||||
...reactHooks.configs.recommended.rules,
|
||||
},
|
||||
},
|
||||
|
@ -17,6 +17,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@arco-design/web-react": "^2.64.1",
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
"@reduxjs/toolkit": "^2.3.0",
|
||||
"cron": "^3.2.1",
|
||||
"dayjs": "^1.11.13",
|
||||
@ -29,7 +32,9 @@
|
||||
"react-dom": "^18.2.0",
|
||||
"react-i18next": "^15.1.0",
|
||||
"react-icons": "^5.3.0",
|
||||
"react-joyride": "^2.9.3",
|
||||
"react-redux": "^9.1.2",
|
||||
"react-router-dom": "^7.1.1",
|
||||
"semver": "^7.6.3",
|
||||
"uuid": "^11.0.3",
|
||||
"yaml": "^2.6.1"
|
||||
|
@ -1,3 +1,4 @@
|
||||
import EventEmitter from "eventemitter3";
|
||||
import { connect } from "./client";
|
||||
import { ApiFunction, Server } from "./server";
|
||||
|
||||
@ -24,6 +25,8 @@ export class Broker {
|
||||
export class MessageQueue {
|
||||
topicConMap: Map<string, { name: string; con: chrome.runtime.Port }[]> = new Map();
|
||||
|
||||
private EE: EventEmitter = new EventEmitter();
|
||||
|
||||
constructor(api: Server) {
|
||||
api.on("messageQueue", this.handler());
|
||||
}
|
||||
@ -69,5 +72,16 @@ export class MessageQueue {
|
||||
list?.forEach((item) => {
|
||||
item.con.postMessage({ action: "message", topic, message });
|
||||
});
|
||||
this.EE.emit(topic, message);
|
||||
}
|
||||
|
||||
// 只发布给当前环境
|
||||
emit(topic: string, message: any) {
|
||||
this.EE.emit(topic, message);
|
||||
}
|
||||
|
||||
// 同环境下使用addListener
|
||||
addListener(topic: string, handler: (message: any) => void) {
|
||||
this.EE.on(topic, handler);
|
||||
}
|
||||
}
|
||||
|
238
pnpm-lock.yaml
generated
238
pnpm-lock.yaml
generated
@ -11,6 +11,15 @@ importers:
|
||||
'@arco-design/web-react':
|
||||
specifier: ^2.64.1
|
||||
version: 2.64.1(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@dnd-kit/core':
|
||||
specifier: ^6.3.1
|
||||
version: 6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@dnd-kit/sortable':
|
||||
specifier: ^10.0.0
|
||||
version: 10.0.0(@dnd-kit/core@6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
|
||||
'@dnd-kit/utilities':
|
||||
specifier: ^3.2.2
|
||||
version: 3.2.2(react@18.3.1)
|
||||
'@reduxjs/toolkit':
|
||||
specifier: ^2.3.0
|
||||
version: 2.3.0(react-redux@9.1.2(@types/react@18.3.12)(react@18.3.1)(redux@5.0.1))(react@18.3.1)
|
||||
@ -47,9 +56,15 @@ importers:
|
||||
react-icons:
|
||||
specifier: ^5.3.0
|
||||
version: 5.3.0(react@18.3.1)
|
||||
react-joyride:
|
||||
specifier: ^2.9.3
|
||||
version: 2.9.3(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
react-redux:
|
||||
specifier: ^9.1.2
|
||||
version: 9.1.2(@types/react@18.3.12)(react@18.3.1)(redux@5.0.1)
|
||||
react-router-dom:
|
||||
specifier: ^7.1.1
|
||||
version: 7.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
semver:
|
||||
specifier: ^7.6.3
|
||||
version: 7.6.3
|
||||
@ -309,6 +324,28 @@ packages:
|
||||
resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
|
||||
'@dnd-kit/accessibility@3.1.1':
|
||||
resolution: {integrity: sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==}
|
||||
peerDependencies:
|
||||
react: '>=16.8.0'
|
||||
|
||||
'@dnd-kit/core@6.3.1':
|
||||
resolution: {integrity: sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==}
|
||||
peerDependencies:
|
||||
react: '>=16.8.0'
|
||||
react-dom: '>=16.8.0'
|
||||
|
||||
'@dnd-kit/sortable@10.0.0':
|
||||
resolution: {integrity: sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==}
|
||||
peerDependencies:
|
||||
'@dnd-kit/core': ^6.3.0
|
||||
react: '>=16.8.0'
|
||||
|
||||
'@dnd-kit/utilities@3.2.2':
|
||||
resolution: {integrity: sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==}
|
||||
peerDependencies:
|
||||
react: '>=16.8.0'
|
||||
|
||||
'@esbuild/aix-ppc64@0.21.5':
|
||||
resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==}
|
||||
engines: {node: '>=12'}
|
||||
@ -634,6 +671,12 @@ packages:
|
||||
resolution: {integrity: sha512-HFZ4Mp26nbWk9d/BpvP0YNL6W4UoZF0VFcTw/aPPA8RpOxeFQgK+ClABGgAUXs9Y/RGX/l1vOmrqz1MQt9MNuw==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
|
||||
'@gilbarbara/deep-equal@0.1.2':
|
||||
resolution: {integrity: sha512-jk+qzItoEb0D0xSSmrKDDzf9sheQj/BAPxlgNxgmOaA3mxpUa6ndJLYGZKsJnIVEQSD8zcTbyILz7I0HcnBCRA==}
|
||||
|
||||
'@gilbarbara/deep-equal@0.3.1':
|
||||
resolution: {integrity: sha512-I7xWjLs2YSVMc5gGx1Z3ZG1lgFpITPndpi8Ku55GeEIKpACCPQNS/OTqQbxgTCfq0Ncvcc+CrFov96itVh6Qvw==}
|
||||
|
||||
'@humanfs/core@0.19.0':
|
||||
resolution: {integrity: sha512-2cbWIHbZVEweE853g8jymffCA+NCMiuqeECeBBLm8dg2oFdjuGJhgN4UAbI+6v0CKbbhvtXA4qV8YR5Ji86nmw==}
|
||||
engines: {node: '>=18.18.0'}
|
||||
@ -983,6 +1026,9 @@ packages:
|
||||
'@types/connect@3.4.38':
|
||||
resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==}
|
||||
|
||||
'@types/cookie@0.6.0':
|
||||
resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==}
|
||||
|
||||
'@types/eslint-scope@3.7.7':
|
||||
resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==}
|
||||
|
||||
@ -1644,6 +1690,10 @@ packages:
|
||||
resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
cookie@1.0.2:
|
||||
resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
core-js@3.38.1:
|
||||
resolution: {integrity: sha512-OP35aUorbU3Zvlx7pjsFdu1rGNnD4pgw/CWoYzRY3t2EzoVT7shKHY1dlAy3f41cGIO7ZDPQimhGFTlEYkG/Hw==}
|
||||
|
||||
@ -1724,6 +1774,9 @@ packages:
|
||||
decimal.js@10.4.3:
|
||||
resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==}
|
||||
|
||||
deep-diff@1.0.2:
|
||||
resolution: {integrity: sha512-aWS3UIVH+NPGCD1kki+DCU9Dua032iSsO43LqQpcs4R3+dVv7tX0qBGjiVHJHjplsoUM2XRO/KB92glqc68awg==}
|
||||
|
||||
deep-eql@5.0.2:
|
||||
resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==}
|
||||
engines: {node: '>=6'}
|
||||
@ -1731,6 +1784,10 @@ packages:
|
||||
deep-is@0.1.4:
|
||||
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
|
||||
|
||||
deepmerge@4.3.1:
|
||||
resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
default-browser-id@5.0.0:
|
||||
resolution: {integrity: sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==}
|
||||
engines: {node: '>=18'}
|
||||
@ -2367,6 +2424,12 @@ packages:
|
||||
engines: {node: '>=14.16'}
|
||||
hasBin: true
|
||||
|
||||
is-lite@0.8.2:
|
||||
resolution: {integrity: sha512-JZfH47qTsslwaAsqbMI3Q6HNNjUuq6Cmzzww50TdP5Esb6e1y2sK2UAaZZuzfAzpoI2AkxoPQapZdlDuP6Vlsw==}
|
||||
|
||||
is-lite@1.2.1:
|
||||
resolution: {integrity: sha512-pgF+L5bxC+10hLBgf6R2P4ZZUBOQIIacbdo8YvuCP8/JvsWxG7aZ9p10DYuLtifFci4l3VITphhMlMV4Y+urPw==}
|
||||
|
||||
is-map@2.0.3:
|
||||
resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@ -2852,6 +2915,10 @@ packages:
|
||||
pkg-types@1.2.1:
|
||||
resolution: {integrity: sha512-sQoqa8alT3nHjGuTjuKgOnvjo4cljkufdtLMnO2LBP/wRwuDlo1tkaEdMxCRhyGRPacv/ztlZgDPm2b7FAmEvw==}
|
||||
|
||||
popper.js@1.16.1:
|
||||
resolution: {integrity: sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==}
|
||||
deprecated: You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1
|
||||
|
||||
possible-typed-array-names@1.0.0:
|
||||
resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@ -2927,6 +2994,12 @@ packages:
|
||||
peerDependencies:
|
||||
react: ^18.3.1
|
||||
|
||||
react-floater@0.7.9:
|
||||
resolution: {integrity: sha512-NXqyp9o8FAXOATOEo0ZpyaQ2KPb4cmPMXGWkx377QtJkIXHlHRAGer7ai0r0C1kG5gf+KJ6Gy+gdNIiosvSicg==}
|
||||
peerDependencies:
|
||||
react: 15 - 18
|
||||
react-dom: 15 - 18
|
||||
|
||||
react-focus-lock@2.13.2:
|
||||
resolution: {integrity: sha512-T/7bsofxYqnod2xadvuwjGKHOoL5GH7/EIPI5UyEvaU/c2CcphvGI371opFtuY/SYdbMsNiuF4HsHQ50nA/TKQ==}
|
||||
peerDependencies:
|
||||
@ -2954,12 +3027,24 @@ packages:
|
||||
peerDependencies:
|
||||
react: '*'
|
||||
|
||||
react-innertext@1.1.5:
|
||||
resolution: {integrity: sha512-PWAqdqhxhHIv80dT9znP2KvS+hfkbRovFp4zFYHFFlOoQLRiawIic81gKb3U1wEyJZgMwgs3JoLtwryASRWP3Q==}
|
||||
peerDependencies:
|
||||
'@types/react': '>=0.0.0 <=99'
|
||||
react: '>=0.0.0 <=99'
|
||||
|
||||
react-is@16.13.1:
|
||||
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
|
||||
|
||||
react-is@18.3.1:
|
||||
resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==}
|
||||
|
||||
react-joyride@2.9.3:
|
||||
resolution: {integrity: sha512-1+Mg34XK5zaqJ63eeBhqdbk7dlGCFp36FXwsEvgpjqrtyywX2C6h9vr3jgxP0bGHCw8Ilsp/nRDzNVq6HJ3rNw==}
|
||||
peerDependencies:
|
||||
react: 15 - 18
|
||||
react-dom: 15 - 18
|
||||
|
||||
react-redux@9.1.2:
|
||||
resolution: {integrity: sha512-0OA4dhM1W48l3uzmv6B7TXPCGmokUU4p1M44DGN2/D9a1FjVPukVjER1PcPX97jIg6aUeLq1XJo1IpfbgULn0w==}
|
||||
peerDependencies:
|
||||
@ -2972,6 +3057,23 @@ packages:
|
||||
redux:
|
||||
optional: true
|
||||
|
||||
react-router-dom@7.1.1:
|
||||
resolution: {integrity: sha512-vSrQHWlJ5DCfyrhgo0k6zViOe9ToK8uT5XGSmnuC2R3/g261IdIMpZVqfjD6vWSXdnf5Czs4VA/V60oVR6/jnA==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
peerDependencies:
|
||||
react: '>=18'
|
||||
react-dom: '>=18'
|
||||
|
||||
react-router@7.1.1:
|
||||
resolution: {integrity: sha512-39sXJkftkKWRZ2oJtHhCxmoCrBCULr/HAH4IT5DHlgu/Q0FCPV0S4Lx+abjDTx/74xoZzNYDYbOZWlJjruyuDQ==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
peerDependencies:
|
||||
react: '>=18'
|
||||
react-dom: '>=18'
|
||||
peerDependenciesMeta:
|
||||
react-dom:
|
||||
optional: true
|
||||
|
||||
react-transition-group@4.4.5:
|
||||
resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==}
|
||||
peerDependencies:
|
||||
@ -3123,6 +3225,12 @@ packages:
|
||||
scroll-into-view-if-needed@2.2.31:
|
||||
resolution: {integrity: sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==}
|
||||
|
||||
scroll@3.0.1:
|
||||
resolution: {integrity: sha512-pz7y517OVls1maEzlirKO5nPYle9AXsFzTMNJrRGmT951mzpIBy7sNHOg5o/0MQd/NqliCiWnAi0kZneMPFLcg==}
|
||||
|
||||
scrollparent@2.1.0:
|
||||
resolution: {integrity: sha512-bnnvJL28/Rtz/kz2+4wpBjHzWoEzXhVg/TE8BeVGJHUqE8THNIRnDxDWMktwM+qahvlRdvlLdsQfYe+cuqfZeA==}
|
||||
|
||||
select-hose@2.0.0:
|
||||
resolution: {integrity: sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==}
|
||||
|
||||
@ -3154,6 +3262,9 @@ packages:
|
||||
resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
|
||||
set-cookie-parser@2.7.1:
|
||||
resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==}
|
||||
|
||||
set-function-length@1.2.2:
|
||||
resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@ -3399,6 +3510,12 @@ packages:
|
||||
resolution: {integrity: sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
tree-changes@0.11.2:
|
||||
resolution: {integrity: sha512-4gXlUthrl+RabZw6lLvcCDl6KfJOCmrC16BC5CRdut1EAH509Omgg0BfKLY+ViRlzrvYOTWR0FMS2SQTwzumrw==}
|
||||
|
||||
tree-changes@0.9.3:
|
||||
resolution: {integrity: sha512-vvvS+O6kEeGRzMglTKbc19ltLWNtmNt1cpBoSYLj/iEcPVvpJasemKOlxBrmZaCtDJoF+4bwv3m01UKYi8mukQ==}
|
||||
|
||||
tree-dump@1.0.2:
|
||||
resolution: {integrity: sha512-dpev9ABuLWdEubk+cIaI9cHwRNNDjkBBLXTwI4UCUFdQ5xXKqNXoK4FEciw/vxf+NQ7Cb7sGUyeUtORvHIdRXQ==}
|
||||
engines: {node: '>=10.0'}
|
||||
@ -3433,10 +3550,17 @@ packages:
|
||||
engines: {node: '>=18.0.0'}
|
||||
hasBin: true
|
||||
|
||||
turbo-stream@2.4.0:
|
||||
resolution: {integrity: sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==}
|
||||
|
||||
type-check@0.4.0:
|
||||
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
|
||||
type-fest@4.31.0:
|
||||
resolution: {integrity: sha512-yCxltHW07Nkhv/1F6wWBr8kz+5BGMfP+RbRSYFnegVb0qV/UMT0G0ElBloPVerqn4M2ZV80Ir1FtCcYv1cT6vQ==}
|
||||
engines: {node: '>=16'}
|
||||
|
||||
type-is@1.6.18:
|
||||
resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
|
||||
engines: {node: '>= 0.6'}
|
||||
@ -3882,6 +4006,31 @@ snapshots:
|
||||
|
||||
'@discoveryjs/json-ext@0.5.7': {}
|
||||
|
||||
'@dnd-kit/accessibility@3.1.1(react@18.3.1)':
|
||||
dependencies:
|
||||
react: 18.3.1
|
||||
tslib: 2.8.0
|
||||
|
||||
'@dnd-kit/core@6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@dnd-kit/accessibility': 3.1.1(react@18.3.1)
|
||||
'@dnd-kit/utilities': 3.2.2(react@18.3.1)
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
tslib: 2.8.0
|
||||
|
||||
'@dnd-kit/sortable@10.0.0(@dnd-kit/core@6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@dnd-kit/core': 6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@dnd-kit/utilities': 3.2.2(react@18.3.1)
|
||||
react: 18.3.1
|
||||
tslib: 2.8.0
|
||||
|
||||
'@dnd-kit/utilities@3.2.2(react@18.3.1)':
|
||||
dependencies:
|
||||
react: 18.3.1
|
||||
tslib: 2.8.0
|
||||
|
||||
'@esbuild/aix-ppc64@0.21.5':
|
||||
optional: true
|
||||
|
||||
@ -4075,6 +4224,10 @@ snapshots:
|
||||
dependencies:
|
||||
levn: 0.4.1
|
||||
|
||||
'@gilbarbara/deep-equal@0.1.2': {}
|
||||
|
||||
'@gilbarbara/deep-equal@0.3.1': {}
|
||||
|
||||
'@humanfs/core@0.19.0': {}
|
||||
|
||||
'@humanfs/node@0.16.5':
|
||||
@ -4448,6 +4601,8 @@ snapshots:
|
||||
dependencies:
|
||||
'@types/node': 22.10.2
|
||||
|
||||
'@types/cookie@0.6.0': {}
|
||||
|
||||
'@types/eslint-scope@3.7.7':
|
||||
dependencies:
|
||||
'@types/eslint': 9.6.1
|
||||
@ -5420,6 +5575,8 @@ snapshots:
|
||||
|
||||
cookie@0.7.1: {}
|
||||
|
||||
cookie@1.0.2: {}
|
||||
|
||||
core-js@3.38.1: {}
|
||||
|
||||
core-util-is@1.0.3: {}
|
||||
@ -5496,10 +5653,14 @@ snapshots:
|
||||
|
||||
decimal.js@10.4.3: {}
|
||||
|
||||
deep-diff@1.0.2: {}
|
||||
|
||||
deep-eql@5.0.2: {}
|
||||
|
||||
deep-is@0.1.4: {}
|
||||
|
||||
deepmerge@4.3.1: {}
|
||||
|
||||
default-browser-id@5.0.0: {}
|
||||
|
||||
default-browser@5.2.1:
|
||||
@ -6354,6 +6515,10 @@ snapshots:
|
||||
dependencies:
|
||||
is-docker: 3.0.0
|
||||
|
||||
is-lite@0.8.2: {}
|
||||
|
||||
is-lite@1.2.1: {}
|
||||
|
||||
is-map@2.0.3: {}
|
||||
|
||||
is-negative-zero@2.0.3: {}
|
||||
@ -6453,7 +6618,7 @@ snapshots:
|
||||
|
||||
jest-worker@27.5.1:
|
||||
dependencies:
|
||||
'@types/node': 22.8.1
|
||||
'@types/node': 22.10.2
|
||||
merge-stream: 2.0.0
|
||||
supports-color: 8.1.1
|
||||
optional: true
|
||||
@ -6813,6 +6978,8 @@ snapshots:
|
||||
mlly: 1.7.3
|
||||
pathe: 1.1.2
|
||||
|
||||
popper.js@1.16.1: {}
|
||||
|
||||
possible-typed-array-names@1.0.0: {}
|
||||
|
||||
postcss-loader@8.1.1(@rspack/core@1.0.14(@swc/helpers@0.5.13))(postcss@8.4.49)(typescript@5.6.3)(webpack@5.96.1(esbuild@0.23.1)):
|
||||
@ -6885,6 +7052,16 @@ snapshots:
|
||||
react: 18.3.1
|
||||
scheduler: 0.23.2
|
||||
|
||||
react-floater@0.7.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||
dependencies:
|
||||
deepmerge: 4.3.1
|
||||
is-lite: 0.8.2
|
||||
popper.js: 1.16.1
|
||||
prop-types: 15.8.1
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
tree-changes: 0.9.3
|
||||
|
||||
react-focus-lock@2.13.2(@types/react@18.3.12)(react@18.3.1):
|
||||
dependencies:
|
||||
'@babel/runtime': 7.26.0
|
||||
@ -6910,10 +7087,33 @@ snapshots:
|
||||
dependencies:
|
||||
react: 18.3.1
|
||||
|
||||
react-innertext@1.1.5(@types/react@18.3.12)(react@18.3.1):
|
||||
dependencies:
|
||||
'@types/react': 18.3.12
|
||||
react: 18.3.1
|
||||
|
||||
react-is@16.13.1: {}
|
||||
|
||||
react-is@18.3.1: {}
|
||||
|
||||
react-joyride@2.9.3(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||
dependencies:
|
||||
'@gilbarbara/deep-equal': 0.3.1
|
||||
deep-diff: 1.0.2
|
||||
deepmerge: 4.3.1
|
||||
is-lite: 1.2.1
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
react-floater: 0.7.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
react-innertext: 1.1.5(@types/react@18.3.12)(react@18.3.1)
|
||||
react-is: 16.13.1
|
||||
scroll: 3.0.1
|
||||
scrollparent: 2.1.0
|
||||
tree-changes: 0.11.2
|
||||
type-fest: 4.31.0
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
|
||||
react-redux@9.1.2(@types/react@18.3.12)(react@18.3.1)(redux@5.0.1):
|
||||
dependencies:
|
||||
'@types/use-sync-external-store': 0.0.3
|
||||
@ -6923,6 +7123,22 @@ snapshots:
|
||||
'@types/react': 18.3.12
|
||||
redux: 5.0.1
|
||||
|
||||
react-router-dom@7.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||
dependencies:
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
react-router: 7.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
|
||||
react-router@7.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||
dependencies:
|
||||
'@types/cookie': 0.6.0
|
||||
cookie: 1.0.2
|
||||
react: 18.3.1
|
||||
set-cookie-parser: 2.7.1
|
||||
turbo-stream: 2.4.0
|
||||
optionalDependencies:
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
|
||||
react-transition-group@4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||
dependencies:
|
||||
'@babel/runtime': 7.26.0
|
||||
@ -7105,6 +7321,10 @@ snapshots:
|
||||
dependencies:
|
||||
compute-scroll-into-view: 1.0.20
|
||||
|
||||
scroll@3.0.1: {}
|
||||
|
||||
scrollparent@2.1.0: {}
|
||||
|
||||
select-hose@2.0.0: {}
|
||||
|
||||
selfsigned@2.4.1:
|
||||
@ -7160,6 +7380,8 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
set-cookie-parser@2.7.1: {}
|
||||
|
||||
set-function-length@1.2.2:
|
||||
dependencies:
|
||||
define-data-property: 1.1.4
|
||||
@ -7425,6 +7647,16 @@ snapshots:
|
||||
dependencies:
|
||||
punycode: 2.3.1
|
||||
|
||||
tree-changes@0.11.2:
|
||||
dependencies:
|
||||
'@gilbarbara/deep-equal': 0.3.1
|
||||
is-lite: 1.2.1
|
||||
|
||||
tree-changes@0.9.3:
|
||||
dependencies:
|
||||
'@gilbarbara/deep-equal': 0.1.2
|
||||
is-lite: 0.8.2
|
||||
|
||||
tree-dump@1.0.2(tslib@2.8.0):
|
||||
dependencies:
|
||||
tslib: 2.8.0
|
||||
@ -7478,10 +7710,14 @@ snapshots:
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.3
|
||||
|
||||
turbo-stream@2.4.0: {}
|
||||
|
||||
type-check@0.4.0:
|
||||
dependencies:
|
||||
prelude-ls: 1.2.1
|
||||
|
||||
type-fest@4.31.0: {}
|
||||
|
||||
type-is@1.6.18:
|
||||
dependencies:
|
||||
media-typer: 0.3.0
|
||||
|
@ -139,7 +139,7 @@ export default defineConfig({
|
||||
}),
|
||||
new rspack.HtmlRspackPlugin({
|
||||
filename: `${dist}/ext/src/options.html`,
|
||||
template: `${src}/pages/template.html`,
|
||||
template: `${src}/pages/options.html`,
|
||||
inject: "head",
|
||||
title: "Home - ScriptCat",
|
||||
minify: true,
|
||||
|
@ -1,15 +1,15 @@
|
||||
export abstract class Repo<T> {
|
||||
constructor(private prefix: string) {
|
||||
if (!prefix.endsWith(":")) {
|
||||
prefix += ":";
|
||||
this.prefix += ":";
|
||||
}
|
||||
}
|
||||
|
||||
private joinKey(key: string) {
|
||||
protected joinKey(key: string) {
|
||||
return this.prefix + key;
|
||||
}
|
||||
|
||||
public async _save(key: string, val: T) {
|
||||
protected async _save(key: string, val: T) {
|
||||
return new Promise((resolve) => {
|
||||
const data = {
|
||||
[this.joinKey(key)]: val,
|
||||
@ -55,4 +55,39 @@ export abstract class Repo<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 interface Script {
|
||||
id: number; // 脚本id
|
||||
// id: number; // 脚本id mv3迁移为chrome.storage后舍弃
|
||||
uuid: string; // 脚本uuid,通过脚本uuid识别唯一脚本
|
||||
name: string; // 脚本名称
|
||||
code: string; // 脚本执行代码
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { Script } from "@App/app/repo/scripts";
|
||||
import { Client } from "@Packages/message/client";
|
||||
import { InstallSource } from ".";
|
||||
|
||||
export class ScriptClient extends Client {
|
||||
constructor() {
|
||||
@ -11,7 +12,7 @@ export class ScriptClient extends Client {
|
||||
return this.do("getInstallInfo", uuid);
|
||||
}
|
||||
|
||||
installScript(script: Script) {
|
||||
return this.do("installScript", script);
|
||||
installScript(script: Script, upsertBy: InstallSource = "user") {
|
||||
return this.do("installScript", { script, upsertBy });
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import CacheKey from "@App/app/cache_key";
|
||||
import { openInCurrentTab } from "@App/pkg/utils/utils";
|
||||
import { Script, ScriptDAO } from "@App/app/repo/scripts";
|
||||
import { MessageQueue } from "@Packages/message/message_queue";
|
||||
import { InstallSource } from ".";
|
||||
|
||||
export class ScriptService {
|
||||
logger: Logger;
|
||||
@ -135,17 +136,34 @@ export class ScriptService {
|
||||
}
|
||||
|
||||
// 安装脚本
|
||||
async installScript(script: Script) {
|
||||
async installScript(param: { script: Script; upsertBy: InstallSource }) {
|
||||
param.upsertBy = param.upsertBy || "user";
|
||||
const { script, upsertBy } = param;
|
||||
const logger = this.logger.with({
|
||||
name: script.name,
|
||||
uuid: script.uuid,
|
||||
version: script.metadata.version[0],
|
||||
upsertBy,
|
||||
});
|
||||
const dao = new ScriptDAO();
|
||||
// 判断是否已经安装
|
||||
const oldScript = await dao.findByUUID(script.uuid);
|
||||
if (!oldScript) {
|
||||
// 执行安装逻辑
|
||||
} else {
|
||||
if (oldScript) {
|
||||
// 执行更新逻辑
|
||||
script.selfMetadata = oldScript.selfMetadata;
|
||||
}
|
||||
return dao
|
||||
.save(script)
|
||||
.then(() => {
|
||||
logger.info("install success");
|
||||
// 广播一下
|
||||
this.mq.publish("installScript", script);
|
||||
return {};
|
||||
})
|
||||
.catch((e) => {
|
||||
logger.error("install error", Logger.E(e));
|
||||
throw e;
|
||||
});
|
||||
}
|
||||
|
||||
init() {
|
||||
|
@ -4,6 +4,10 @@
|
||||
"version": "0.17.0.1001",
|
||||
"author": "CodFrm",
|
||||
"description": "__MSG_scriptcat_description__",
|
||||
"options_ui": {
|
||||
"page": "src/options.html",
|
||||
"open_in_tab": true
|
||||
},
|
||||
"background": {
|
||||
"service_worker": "src/service_worker.js"
|
||||
},
|
||||
|
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 { useTranslation } from "react-i18next";
|
||||
import { prepareScriptByCode, prepareSubscribeByCode, ScriptInfo } from "@App/pkg/utils/script";
|
||||
import { isDebug, nextTime } from "@App/pkg/utils/utils";
|
||||
import { nextTime } from "@App/pkg/utils/utils";
|
||||
import { ScriptClient } from "@App/app/service/service_worker/client";
|
||||
|
||||
type Permission = { label: string; color?: string; value: string[] }[];
|
||||
@ -156,11 +156,13 @@ function App() {
|
||||
setScriptInfo(info);
|
||||
setEnable(action.status === SCRIPT_STATUS_ENABLE);
|
||||
setUpsertScript(action);
|
||||
// 修改网页显示title
|
||||
document.title = `${!isUpdate ? t("install_script") : t("update_script")} - ${i18nName(action)} - ScriptCat`;
|
||||
})
|
||||
.catch(() => {
|
||||
Message.error(t("script_info_load_failed"));
|
||||
});
|
||||
}, [t]);
|
||||
}, [isUpdate, t]);
|
||||
|
||||
return (
|
||||
<div className="h-full">
|
||||
@ -253,11 +255,9 @@ function App() {
|
||||
Message.success(t("install_success")!);
|
||||
setBtnText(t("install_success")!);
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
||||
!isDebug() &&
|
||||
setTimeout(() => {
|
||||
closeWindow();
|
||||
}, 200);
|
||||
}, 500);
|
||||
})
|
||||
.catch((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 "@App/locales/locales";
|
||||
import "@App/index.css";
|
||||
import "./index.css";
|
||||
import { Provider } from "react-redux";
|
||||
import { store } from "@App/store/store.ts";
|
||||
import { Broker } from "@Packages/message/message_queue.ts";
|
||||
import Sider from "../components/layout/Sider.tsx";
|
||||
|
||||
// 测试监听广播
|
||||
// // 测试监听广播
|
||||
|
||||
const border = new Broker();
|
||||
// const border = new Broker();
|
||||
|
||||
border.subscribe("installScript", (message) => {
|
||||
console.log(message);
|
||||
});
|
||||
// border.subscribe("installScript", (message) => {
|
||||
// console.log(message);
|
||||
// });
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
|
||||
<React.StrictMode>
|
||||
<Provider store={store}>
|
||||
<MainLayout className="!flex-col !px-4 box-border">p</MainLayout>
|
||||
<MainLayout className="!flex-row">
|
||||
<Sider />
|
||||
</MainLayout>
|
||||
</Provider>
|
||||
</React.StrictMode>
|
||||
);
|
||||
|
@ -1,14 +1,5 @@
|
||||
import React, { useEffect } from "react";
|
||||
import {
|
||||
BackTop,
|
||||
Button,
|
||||
Card,
|
||||
DatePicker,
|
||||
Input,
|
||||
List,
|
||||
Message,
|
||||
Space,
|
||||
} from "@arco-design/web-react";
|
||||
import { BackTop, Button, Card, DatePicker, Input, List, Message, Space } from "@arco-design/web-react";
|
||||
import dayjs from "dayjs";
|
||||
import Text from "@arco-design/web-react/es/Typography/text";
|
||||
import { Logger, LoggerDAO } from "@App/app/repo/logger";
|
||||
@ -16,8 +7,6 @@ import LogLabel, { Labels, Query } from "@App/pages/components/LogLabel";
|
||||
import { IconPlus } from "@arco-design/web-react/icon";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
import { formatUnixTime } from "@App/pkg/utils/utils";
|
||||
import { SystemConfig } from "@App/pkg/config/config";
|
||||
import IoC from "@App/app/ioc";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
function LoggerPage() {
|
||||
@ -28,12 +17,10 @@ function LoggerPage() {
|
||||
const [logs, setLogs] = React.useState<Logger[]>([]);
|
||||
const [queryLogs, setQueryLogs] = React.useState<Logger[]>([]);
|
||||
const [search, setSearch] = React.useState<string>("");
|
||||
const [startTime, setStartTime] = React.useState(
|
||||
dayjs().subtract(24, "hour").unix()
|
||||
);
|
||||
const [startTime, setStartTime] = React.useState(dayjs().subtract(24, "hour").unix());
|
||||
const [endTime, setEndTime] = React.useState(dayjs().unix());
|
||||
const loggerDAO = new LoggerDAO();
|
||||
const systemConfig = IoC.instance(SystemConfig) as SystemConfig;
|
||||
const systemConfig = { logCleanCycle: 1 };
|
||||
const { t } = useTranslation();
|
||||
|
||||
const onQueryLog = () => {
|
||||
@ -46,35 +33,27 @@ function LoggerPage() {
|
||||
const value = log.label[query.key];
|
||||
switch (query.condition) {
|
||||
case "=":
|
||||
// eslint-disable-next-line eqeqeq
|
||||
if (value != query.value) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case "=~":
|
||||
if (
|
||||
typeof value === "string" &&
|
||||
value.indexOf(query.value) === -1
|
||||
) {
|
||||
if (typeof value === "string" && value.indexOf(query.value) === -1) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case "!=":
|
||||
// eslint-disable-next-line eqeqeq
|
||||
if (value == query.value) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case "!~":
|
||||
if (
|
||||
typeof value === "string" &&
|
||||
value.indexOf(query.value) === -1
|
||||
) {
|
||||
if (typeof value === "string" && value.indexOf(query.value) === -1) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// eslint-disable-next-line eqeqeq
|
||||
|
||||
if (value != query.value) {
|
||||
return;
|
||||
}
|
||||
@ -129,11 +108,7 @@ function LoggerPage() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<BackTop
|
||||
visibleHeight={30}
|
||||
style={{ position: "absolute" }}
|
||||
target={() => document.getElementById("backtop")!}
|
||||
/>
|
||||
<BackTop visibleHeight={30} style={{ position: "absolute" }} target={() => document.getElementById("backtop")!} />
|
||||
<div
|
||||
id="backtop"
|
||||
style={{
|
||||
@ -316,8 +291,7 @@ function LoggerPage() {
|
||||
}}
|
||||
>
|
||||
<Text>
|
||||
{formatUnixTime(startTime)} {t("to")} {formatUnixTime(endTime)}{" "}
|
||||
{t("total_logs", { length: logs.length })}
|
||||
{formatUnixTime(startTime)} {t("to")} {formatUnixTime(endTime)} {t("total_logs", { length: logs.length })}
|
||||
{init === 4
|
||||
? `, ${t("filtered_logs", { length: queryLogs.length })}`
|
||||
: `, ${t("enter_filter_conditions")}`}
|
||||
@ -334,9 +308,9 @@ function LoggerPage() {
|
||||
key={index}
|
||||
style={{
|
||||
background:
|
||||
// eslint-disable-next-line no-nested-ternary
|
||||
|
||||
item.level === "error"
|
||||
? "var(--color-danger-light-2)" // eslint-disable-next-line no-nested-ternary
|
||||
? "var(--color-danger-light-2)"
|
||||
: item.level === "warn"
|
||||
? "var(--color-warning-light-2)"
|
||||
: item.level === "info"
|
||||
@ -345,9 +319,7 @@ function LoggerPage() {
|
||||
}}
|
||||
>
|
||||
{formatUnixTime(item.createtime / 1000)}{" "}
|
||||
{typeof item.message === "object"
|
||||
? JSON.stringify(item.message)
|
||||
: item.message}{" "}
|
||||
{typeof item.message === "object" ? JSON.stringify(item.message) : item.message}{" "}
|
||||
{JSON.stringify(item.label)}
|
||||
</List.Item>
|
||||
)}
|
||||
|
@ -25,7 +25,6 @@ import {
|
||||
SCRIPT_STATUS_ENABLE,
|
||||
SCRIPT_TYPE_BACKGROUND,
|
||||
SCRIPT_TYPE_NORMAL,
|
||||
ScriptDAO,
|
||||
UserConfig,
|
||||
} from "@App/app/repo/scripts";
|
||||
import {
|
||||
@ -46,7 +45,6 @@ import {
|
||||
RiUploadCloudFill,
|
||||
} from "react-icons/ri";
|
||||
import { Link, useNavigate, useSearchParams } from "react-router-dom";
|
||||
import ScriptController from "@App/app/service/script/controller";
|
||||
import { RefInputType } from "@arco-design/web-react/es/Input/interface";
|
||||
import Text from "@arco-design/web-react/es/Typography/text";
|
||||
import {
|
||||
@ -59,28 +57,21 @@ import {
|
||||
useSensors,
|
||||
} from "@dnd-kit/core";
|
||||
import {
|
||||
arrayMove,
|
||||
SortableContext,
|
||||
sortableKeyboardCoordinates,
|
||||
useSortable,
|
||||
verticalListSortingStrategy,
|
||||
} from "@dnd-kit/sortable";
|
||||
import { CSS } from "@dnd-kit/utilities";
|
||||
import IoC from "@App/app/ioc";
|
||||
import RuntimeController from "@App/runtime/content/runtime";
|
||||
import UserConfigPanel from "@App/pages/components/UserConfigPanel";
|
||||
import CloudScriptPlan from "@App/pages/components/CloudScriptPlan";
|
||||
import SynchronizeController from "@App/app/service/synchronize/controller";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { nextTime, semTime } from "@App/pkg/utils/utils";
|
||||
import { i18nName } from "@App/locales/locales";
|
||||
import { SystemConfig } from "@App/pkg/config/config";
|
||||
import {
|
||||
getValues,
|
||||
ListHomeRender,
|
||||
ScriptIcons,
|
||||
scriptListSort,
|
||||
} from "./utils";
|
||||
import { ListHomeRender, ScriptIcons } from "./utils";
|
||||
import { useAppDispatch, useAppSelector } from "@App/store/hooks";
|
||||
import { fetchScriptList, selectScripts } from "@App/store/features/script";
|
||||
import { selectScriptListColumnWidth } from "@App/store/features/setting";
|
||||
|
||||
type ListType = Script & { loading?: boolean };
|
||||
|
||||
@ -91,43 +82,38 @@ function ScriptList() {
|
||||
values: { [key: string]: any };
|
||||
}>();
|
||||
const [cloudScript, setCloudScript] = useState<Script>();
|
||||
const scriptCtrl = IoC.instance(ScriptController) as ScriptController;
|
||||
const synchronizeCtrl = IoC.instance(
|
||||
SynchronizeController
|
||||
) as SynchronizeController;
|
||||
const runtimeCtrl = IoC.instance(RuntimeController) as RuntimeController;
|
||||
const [scriptList, setScriptList] = useState<ListType[]>([]);
|
||||
const dispatch = useAppDispatch();
|
||||
const scriptList = useAppSelector(selectScripts);
|
||||
const scriptListColumnWidth = useAppSelector(selectScriptListColumnWidth);
|
||||
const inputRef = useRef<RefInputType>(null);
|
||||
const navigate = useNavigate();
|
||||
const openUserConfig = parseInt(
|
||||
useSearchParams()[0].get("userConfig") || "",
|
||||
10
|
||||
);
|
||||
const openUserConfig = parseInt(useSearchParams()[0].get("userConfig") || "", 10);
|
||||
const [showAction, setShowAction] = useState(false);
|
||||
const [action, setAction] = useState("");
|
||||
const [select, setSelect] = useState<Script[]>([]);
|
||||
const [selectColumn, setSelectColumn] = useState(0);
|
||||
const systemConfig = IoC.instance(SystemConfig) as SystemConfig;
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchScriptList());
|
||||
// 监听脚本安装/运行
|
||||
// Monitor script running status
|
||||
const channel = runtimeCtrl.watchRunStatus();
|
||||
channel.setHandler(([id, status]: any) => {
|
||||
setScriptList((list) => {
|
||||
return list.map((item) => {
|
||||
if (item.id === id) {
|
||||
item.runStatus = status;
|
||||
}
|
||||
return item;
|
||||
});
|
||||
});
|
||||
});
|
||||
return () => {
|
||||
channel.disChannel();
|
||||
};
|
||||
}, []);
|
||||
// const channel = runtimeCtrl.watchRunStatus();
|
||||
// channel.setHandler(([id, status]: any) => {
|
||||
// setScriptList((list) => {
|
||||
// return list.map((item) => {
|
||||
// if (item.id === id) {
|
||||
// item.runStatus = status;
|
||||
// }
|
||||
// return item;
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
// return () => {
|
||||
// channel.disChannel();
|
||||
// };
|
||||
}, [dispatch]);
|
||||
|
||||
const columns: ColumnProps[] = [
|
||||
{
|
||||
@ -167,27 +153,27 @@ function ScriptList() {
|
||||
loading={item.loading}
|
||||
disabled={item.loading}
|
||||
onChange={(checked) => {
|
||||
setScriptList((list) => {
|
||||
const index = list.findIndex((script) => script.id === item.id);
|
||||
list[index].loading = true;
|
||||
let p: Promise<any>;
|
||||
if (checked) {
|
||||
p = scriptCtrl.enable(item.id).then(() => {
|
||||
list[index].status = SCRIPT_STATUS_ENABLE;
|
||||
});
|
||||
} else {
|
||||
p = scriptCtrl.disable(item.id).then(() => {
|
||||
list[index].status = SCRIPT_STATUS_DISABLE;
|
||||
});
|
||||
}
|
||||
p.catch((err) => {
|
||||
Message.error(err);
|
||||
}).finally(() => {
|
||||
list[index].loading = false;
|
||||
setScriptList([...list]);
|
||||
});
|
||||
return list;
|
||||
});
|
||||
// setScriptList((list) => {
|
||||
// const index = list.findIndex((script) => script.id === item.id);
|
||||
// list[index].loading = true;
|
||||
// let p: Promise<any>;
|
||||
// if (checked) {
|
||||
// p = scriptCtrl.enable(item.id).then(() => {
|
||||
// list[index].status = SCRIPT_STATUS_ENABLE;
|
||||
// });
|
||||
// } else {
|
||||
// p = scriptCtrl.disable(item.id).then(() => {
|
||||
// list[index].status = SCRIPT_STATUS_DISABLE;
|
||||
// });
|
||||
// }
|
||||
// p.catch((err) => {
|
||||
// Message.error(err);
|
||||
// }).finally(() => {
|
||||
// list[index].loading = false;
|
||||
// setScriptList([...list]);
|
||||
// });
|
||||
// return list;
|
||||
// });
|
||||
}}
|
||||
/>
|
||||
);
|
||||
@ -199,7 +185,6 @@ function ScriptList() {
|
||||
dataIndex: "name",
|
||||
sorter: (a, b) => a.name.localeCompare(b.name),
|
||||
filterIcon: <IconSearch />,
|
||||
// eslint-disable-next-line react/no-unstable-nested-components
|
||||
filterDropdown: ({ filterKeys, setFilterKeys, confirm }: any) => {
|
||||
return (
|
||||
<div className="arco-table-custom-filter">
|
||||
@ -244,7 +229,7 @@ function ScriptList() {
|
||||
return (
|
||||
<Tooltip content={col} position="tl">
|
||||
<Link
|
||||
to={`/script/editor/${item.id}`}
|
||||
to={`/script/editor/${item.uuid}`}
|
||||
style={{
|
||||
textDecoration: "none",
|
||||
}}
|
||||
@ -287,7 +272,7 @@ function ScriptList() {
|
||||
pathname: "logger",
|
||||
search: `query=${encodeURIComponent(
|
||||
JSON.stringify([
|
||||
{ key: "scriptId", value: item.id },
|
||||
{ key: "scriptId", value: item.uuid },
|
||||
{
|
||||
key: "component",
|
||||
value: "GM_log",
|
||||
@ -317,9 +302,7 @@ function ScriptList() {
|
||||
if (item.type === SCRIPT_TYPE_BACKGROUND) {
|
||||
tooltip = t("background_script_tooltip");
|
||||
} else {
|
||||
tooltip = `${t("scheduled_script_tooltip")} ${nextTime(
|
||||
item.metadata.crontab[0]
|
||||
)}`;
|
||||
tooltip = `${t("scheduled_script_tooltip")} ${nextTime(item.metadata.crontab[0])}`;
|
||||
}
|
||||
return (
|
||||
<Tooltip content={tooltip}>
|
||||
@ -332,9 +315,7 @@ function ScriptList() {
|
||||
}}
|
||||
onClick={toLogger}
|
||||
>
|
||||
{item.runStatus === SCRIPT_RUN_STATUS_RUNNING
|
||||
? t("running")
|
||||
: t("completed")}
|
||||
{item.runStatus === SCRIPT_RUN_STATUS_RUNNING ? t("running") : t("completed")}
|
||||
</Tag>
|
||||
</Tooltip>
|
||||
);
|
||||
@ -351,8 +332,7 @@ function ScriptList() {
|
||||
<Tooltip
|
||||
content={
|
||||
<p style={{ margin: 0 }}>
|
||||
{t("subscription_link")}:{" "}
|
||||
{decodeURIComponent(item.subscribeUrl)}
|
||||
{t("subscription_link")}: {decodeURIComponent(item.subscribeUrl)}
|
||||
</p>
|
||||
}
|
||||
>
|
||||
@ -440,41 +420,40 @@ function ScriptList() {
|
||||
sorter: (a, b) => a.updatetime - b.updatetime,
|
||||
render(col, script: Script) {
|
||||
return (
|
||||
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
|
||||
<span
|
||||
style={{
|
||||
cursor: "pointer",
|
||||
}}
|
||||
onClick={() => {
|
||||
if (!script.checkUpdateUrl) {
|
||||
Message.warning(t("update_not_supported")!);
|
||||
return;
|
||||
}
|
||||
Message.info({
|
||||
id: "checkupdate",
|
||||
content: t("checking_for_updates"),
|
||||
});
|
||||
scriptCtrl
|
||||
.checkUpdate(script.id)
|
||||
.then((res) => {
|
||||
if (res) {
|
||||
Message.warning({
|
||||
id: "checkupdate",
|
||||
content: t("new_version_available"),
|
||||
});
|
||||
} else {
|
||||
Message.success({
|
||||
id: "checkupdate",
|
||||
content: t("latest_version"),
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
Message.error({
|
||||
id: "checkupdate",
|
||||
content: `${t("update_check_failed")}: ${e.message}`,
|
||||
});
|
||||
});
|
||||
// if (!script.checkUpdateUrl) {
|
||||
// Message.warning(t("update_not_supported")!);
|
||||
// return;
|
||||
// }
|
||||
// Message.info({
|
||||
// id: "checkupdate",
|
||||
// content: t("checking_for_updates"),
|
||||
// });
|
||||
// scriptCtrl
|
||||
// .checkUpdate(script.id)
|
||||
// .then((res) => {
|
||||
// if (res) {
|
||||
// Message.warning({
|
||||
// id: "checkupdate",
|
||||
// content: t("new_version_available"),
|
||||
// });
|
||||
// } else {
|
||||
// Message.success({
|
||||
// id: "checkupdate",
|
||||
// content: t("latest_version"),
|
||||
// });
|
||||
// }
|
||||
// })
|
||||
// .catch((e) => {
|
||||
// Message.error({
|
||||
// id: "checkupdate",
|
||||
// content: `${t("update_check_failed")}: ${e.message}`,
|
||||
// });
|
||||
// });
|
||||
}}
|
||||
>
|
||||
{semTime(new Date(col))}
|
||||
@ -490,7 +469,7 @@ function ScriptList() {
|
||||
render(col, item: Script) {
|
||||
return (
|
||||
<Button.Group>
|
||||
<Link to={`/script/editor/${item.id}`}>
|
||||
<Link to={`/script/editor/${item.uuid}`}>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<RiPencilFill />}
|
||||
@ -503,12 +482,12 @@ function ScriptList() {
|
||||
title={t("confirm_delete_script")}
|
||||
icon={<RiDeleteBin5Fill />}
|
||||
onOk={() => {
|
||||
setScriptList((list) => {
|
||||
return list.filter((i) => i.id !== item.id);
|
||||
});
|
||||
scriptCtrl.delete(item.id).catch((e) => {
|
||||
Message.error(`${t("delete_failed")}: ${e}`);
|
||||
});
|
||||
// setScriptList((list) => {
|
||||
// return list.filter((i) => i.id !== item.id);
|
||||
// });
|
||||
// scriptCtrl.delete(item.id).catch((e) => {
|
||||
// Message.error(`${t("delete_failed")}: ${e}`);
|
||||
// });
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
@ -526,13 +505,13 @@ function ScriptList() {
|
||||
icon={<RiSettings3Fill />}
|
||||
onClick={() => {
|
||||
// Get value
|
||||
getValues(item).then((newValues) => {
|
||||
setUserConfig({
|
||||
userConfig: { ...item.config! },
|
||||
script: item,
|
||||
values: newValues,
|
||||
});
|
||||
});
|
||||
// getValues(item).then((newValues) => {
|
||||
// setUserConfig({
|
||||
// userConfig: { ...item.config! },
|
||||
// script: item,
|
||||
// values: newValues,
|
||||
// });
|
||||
// });
|
||||
}}
|
||||
style={{
|
||||
color: "var(--color-text-2)",
|
||||
@ -546,16 +525,16 @@ function ScriptList() {
|
||||
icon={<RiStopFill />}
|
||||
onClick={async () => {
|
||||
// Stop script
|
||||
Message.loading({
|
||||
id: "script-stop",
|
||||
content: t("stopping_script"),
|
||||
});
|
||||
await runtimeCtrl.stopScript(item.id);
|
||||
Message.success({
|
||||
id: "script-stop",
|
||||
content: t("script_stopped"),
|
||||
duration: 3000,
|
||||
});
|
||||
// Message.loading({
|
||||
// id: "script-stop",
|
||||
// content: t("stopping_script"),
|
||||
// });
|
||||
// await runtimeCtrl.stopScript(item.id);
|
||||
// Message.success({
|
||||
// id: "script-stop",
|
||||
// content: t("script_stopped"),
|
||||
// duration: 3000,
|
||||
// });
|
||||
}}
|
||||
style={{
|
||||
color: "var(--color-text-2)",
|
||||
@ -567,25 +546,25 @@ function ScriptList() {
|
||||
icon={<RiPlayFill />}
|
||||
onClick={async () => {
|
||||
// Start script
|
||||
Message.loading({
|
||||
id: "script-run",
|
||||
content: t("starting_script"),
|
||||
});
|
||||
await runtimeCtrl.startScript(item.id);
|
||||
Message.success({
|
||||
id: "script-run",
|
||||
content: t("script_started"),
|
||||
duration: 3000,
|
||||
});
|
||||
setScriptList((list) => {
|
||||
for (let i = 0; i < list.length; i += 1) {
|
||||
if (list[i].id === item.id) {
|
||||
list[i].runStatus = SCRIPT_RUN_STATUS_RUNNING;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return [...list];
|
||||
});
|
||||
// Message.loading({
|
||||
// id: "script-run",
|
||||
// content: t("starting_script"),
|
||||
// });
|
||||
// await runtimeCtrl.startScript(item.id);
|
||||
// Message.success({
|
||||
// id: "script-run",
|
||||
// content: t("script_started"),
|
||||
// duration: 3000,
|
||||
// });
|
||||
// setScriptList((list) => {
|
||||
// for (let i = 0; i < list.length; i += 1) {
|
||||
// if (list[i].id === item.id) {
|
||||
// list[i].runStatus = SCRIPT_RUN_STATUS_RUNNING;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// return [...list];
|
||||
// });
|
||||
}}
|
||||
style={{
|
||||
color: "var(--color-text-2)",
|
||||
@ -612,32 +591,31 @@ function ScriptList() {
|
||||
|
||||
const [newColumns, setNewColumns] = useState<ColumnProps[]>([]);
|
||||
|
||||
// 设置列和排序
|
||||
useEffect(() => {
|
||||
const dao = new ScriptDAO();
|
||||
dao.table
|
||||
.orderBy("sort")
|
||||
.toArray()
|
||||
.then(async (scripts) => {
|
||||
// Sort when a new script is added (-1)
|
||||
scriptListSort(scripts);
|
||||
// Open user config panel
|
||||
if (openUserConfig) {
|
||||
const script = scripts.find((item) => item.id === openUserConfig);
|
||||
if (script && script.config) {
|
||||
setUserConfig({
|
||||
script,
|
||||
userConfig: script.config,
|
||||
values: await getValues(script),
|
||||
});
|
||||
}
|
||||
}
|
||||
setScriptList(scripts);
|
||||
});
|
||||
|
||||
// const dao = new ScriptDAO();
|
||||
// dao.table
|
||||
// .orderBy("sort")
|
||||
// .toArray()
|
||||
// .then(async (scripts) => {
|
||||
// // Sort when a new script is added (-1)
|
||||
// scriptListSort(scripts);
|
||||
// // Open user config panel
|
||||
// if (openUserConfig) {
|
||||
// const script = scripts.find((item) => item.id === openUserConfig);
|
||||
// if (script && script.config) {
|
||||
// setUserConfig({
|
||||
// script,
|
||||
// userConfig: script.config,
|
||||
// values: await getValues(script),
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
// setScriptList(scripts);
|
||||
// });
|
||||
setNewColumns(
|
||||
columns.map((item) => {
|
||||
item.width =
|
||||
systemConfig.scriptListColumnWidth[item.key!] ?? item.width;
|
||||
item.width = scriptListColumnWidth[item.key!] ?? item.width;
|
||||
return item;
|
||||
})
|
||||
);
|
||||
@ -651,7 +629,6 @@ function ScriptList() {
|
||||
})
|
||||
);
|
||||
|
||||
// eslint-disable-next-line react/no-unstable-nested-components
|
||||
const SortableWrapper = (props: any, ref: any) => {
|
||||
return (
|
||||
<DndContext
|
||||
@ -663,27 +640,24 @@ function ScriptList() {
|
||||
return;
|
||||
}
|
||||
if (active.id !== over.id) {
|
||||
setScriptList((items) => {
|
||||
let oldIndex = 0;
|
||||
let newIndex = 0;
|
||||
items.forEach((item, index) => {
|
||||
if (item.id === active.id) {
|
||||
oldIndex = index;
|
||||
} else if (item.id === over.id) {
|
||||
newIndex = index;
|
||||
}
|
||||
});
|
||||
const newItems = arrayMove(items, oldIndex, newIndex);
|
||||
scriptListSort(newItems);
|
||||
return newItems;
|
||||
});
|
||||
// setScriptList((items) => {
|
||||
// let oldIndex = 0;
|
||||
// let newIndex = 0;
|
||||
// items.forEach((item, index) => {
|
||||
// if (item.id === active.id) {
|
||||
// oldIndex = index;
|
||||
// } else if (item.id === over.id) {
|
||||
// newIndex = index;
|
||||
// }
|
||||
// });
|
||||
// const newItems = arrayMove(items, oldIndex, newIndex);
|
||||
// scriptListSort(newItems);
|
||||
// return newItems;
|
||||
// });
|
||||
}
|
||||
}}
|
||||
>
|
||||
<SortableContext
|
||||
items={scriptList}
|
||||
strategy={verticalListSortingStrategy}
|
||||
>
|
||||
<SortableContext items={scriptList} strategy={verticalListSortingStrategy}>
|
||||
<table ref={ref} {...props} />
|
||||
</SortableContext>
|
||||
</DndContext>
|
||||
@ -704,10 +678,8 @@ function ScriptList() {
|
||||
|
||||
const sortIndex = dealColumns.findIndex((item) => item.key === "sort");
|
||||
|
||||
// eslint-disable-next-line react/no-unstable-nested-components
|
||||
const SortableItem = (props: any) => {
|
||||
const { attributes, listeners, setNodeRef, transform, transition } =
|
||||
useSortable({ id: props!.record.id });
|
||||
const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ id: props!.record.uuid });
|
||||
|
||||
const style = {
|
||||
transform: CSS.Transform.toString(transform),
|
||||
@ -715,7 +687,6 @@ function ScriptList() {
|
||||
};
|
||||
|
||||
// 替换排序列,使其可以拖拽
|
||||
// eslint-disable-next-line react/destructuring-assignment
|
||||
props.children[sortIndex + 1] = (
|
||||
<td
|
||||
className="arco-table-td"
|
||||
@ -778,9 +749,7 @@ function ScriptList() {
|
||||
<Select.Option value="disable">{t("disable")}</Select.Option>
|
||||
<Select.Option value="export">{t("export")}</Select.Option>
|
||||
<Select.Option value="delete">{t("delete")}</Select.Option>
|
||||
<Select.Option value="check_update">
|
||||
{t("check_update")}
|
||||
</Select.Option>
|
||||
<Select.Option value="check_update">{t("check_update")}</Select.Option>
|
||||
</Select>
|
||||
<Button
|
||||
type="primary"
|
||||
@ -853,9 +822,7 @@ function ScriptList() {
|
||||
// 需要更新
|
||||
Message.warning({
|
||||
id: "checkupdate",
|
||||
content: `${i18nName(item)} ${t(
|
||||
"new_version_available"
|
||||
)}`,
|
||||
content: `${i18nName(item)} ${t("new_version_available")}`,
|
||||
});
|
||||
}
|
||||
if (index === array.length - 1) {
|
||||
@ -869,9 +836,7 @@ function ScriptList() {
|
||||
.catch((e) => {
|
||||
Message.error({
|
||||
id: "checkupdate",
|
||||
content: `${t("update_check_failed")}: ${
|
||||
e.message
|
||||
}`,
|
||||
content: `${t("update_check_failed")}: ${e.message}`,
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -944,12 +909,7 @@ function ScriptList() {
|
||||
position="bl"
|
||||
>
|
||||
<Input
|
||||
type={
|
||||
newColumns[selectColumn].width === 0 ||
|
||||
newColumns[selectColumn].width === -1
|
||||
? ""
|
||||
: "number"
|
||||
}
|
||||
type={newColumns[selectColumn].width === 0 || newColumns[selectColumn].width === -1 ? "" : "number"}
|
||||
style={{ width: "80px" }}
|
||||
size="mini"
|
||||
value={
|
||||
@ -1031,11 +991,7 @@ function ScriptList() {
|
||||
}}
|
||||
/>
|
||||
{userConfig && (
|
||||
<UserConfigPanel
|
||||
script={userConfig.script}
|
||||
userConfig={userConfig.userConfig}
|
||||
values={userConfig.values}
|
||||
/>
|
||||
<UserConfigPanel script={userConfig.script} userConfig={userConfig.userConfig} values={userConfig.values} />
|
||||
)}
|
||||
<CloudScriptPlan
|
||||
script={cloudScript}
|
||||
|
@ -8,15 +8,9 @@ import {
|
||||
Select,
|
||||
Space,
|
||||
} 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 { IconQuestionCircleFill } from "@arco-design/web-react/icon";
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies, import/no-import-module-exports
|
||||
import { format } from "prettier";
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies, import/no-import-module-exports
|
||||
import babel from "prettier/parser-babel";
|
||||
import GMApiSetting from "@App/pages/components/GMApiSetting";
|
||||
import i18n from "@App/locales/locales";
|
||||
|
@ -18,8 +18,6 @@ import {
|
||||
SubscribeDAO,
|
||||
} from "@App/app/repo/subscribe";
|
||||
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 { RefInputType } from "@arco-design/web-react/es/Input/interface";
|
||||
import { semTime } from "@App/pkg/utils/utils";
|
||||
|
@ -12,17 +12,11 @@ import {
|
||||
Space,
|
||||
} from "@arco-design/web-react";
|
||||
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 FileSystemParams from "@App/pages/components/FileSystemParams";
|
||||
import { IconQuestionCircleFill } from "@arco-design/web-react/icon";
|
||||
import { RefInputType } from "@arco-design/web-react/es/Input/interface";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import SystemController from "@App/app/service/system/controller";
|
||||
|
||||
function Tools() {
|
||||
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 { useNavigate, useParams, useSearchParams } from "react-router-dom";
|
||||
import { editor, KeyCode, KeyMod } from "monaco-editor";
|
||||
import {
|
||||
Button,
|
||||
Dropdown,
|
||||
Grid,
|
||||
Menu,
|
||||
Message,
|
||||
Tabs,
|
||||
Tooltip,
|
||||
} from "@arco-design/web-react";
|
||||
import { Button, Dropdown, Grid, Menu, Message, Tabs, Tooltip } from "@arco-design/web-react";
|
||||
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 crontabTpl from "@App/template/crontab.tpl";
|
||||
import backgroundTpl from "@App/template/background.tpl";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import "./index.css";
|
||||
import IoC from "@App/app/ioc";
|
||||
import LoggerCore from "@App/app/logger/core";
|
||||
import Logger from "@App/app/logger/logger";
|
||||
import { prepareScriptByCode } from "@App/pkg/utils/script";
|
||||
import RuntimeController from "@App/runtime/content/runtime";
|
||||
import ScriptStorage from "@App/pages/components/ScriptStorage";
|
||||
import ScriptResource from "@App/pages/components/ScriptResource";
|
||||
import ScriptSetting from "@App/pages/components/ScriptSetting";
|
||||
@ -49,7 +38,7 @@ const Editor: React.FC<{
|
||||
const [init, setInit] = useState(false);
|
||||
const codeEditor = useRef<{ editor: editor.IStandaloneCodeEditor }>(null);
|
||||
// Script.uuid为key,Script为value,储存Script
|
||||
ScriptMap.has(script.uuid) || ScriptMap.set(script.uuid, script);
|
||||
ScriptMap.set(script.uuid, script);
|
||||
useEffect(() => {
|
||||
if (!codeEditor.current || !codeEditor.current.editor) {
|
||||
setTimeout(() => {
|
||||
@ -69,13 +58,13 @@ const Editor: React.FC<{
|
||||
const activeEditor = editor
|
||||
.getEditors()
|
||||
// @ts-ignore
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
.find((i) => i._focusTracker._hasFocus);
|
||||
|
||||
// 仅在获取到激活的editor时,通过editor上绑定的uuid获取Script,并指定激活的editor执行快捷键action
|
||||
activeEditor &&
|
||||
if (activeEditor) {
|
||||
// @ts-ignore
|
||||
item.action(ScriptMap.get(activeEditor.uuid), activeEditor);
|
||||
}
|
||||
});
|
||||
});
|
||||
codeEditor.current.editor.onKeyUp(() => {
|
||||
@ -85,15 +74,7 @@ const Editor: React.FC<{
|
||||
return () => {};
|
||||
}, [init]);
|
||||
|
||||
return (
|
||||
<CodeEditor
|
||||
id={id}
|
||||
ref={codeEditor}
|
||||
code={script.code}
|
||||
diffCode=""
|
||||
editable
|
||||
/>
|
||||
);
|
||||
return <CodeEditor id={id} ref={codeEditor} code={script.code} diffCode="" editable />;
|
||||
};
|
||||
|
||||
type EditorMenu = {
|
||||
@ -180,8 +161,7 @@ function ScriptEditor() {
|
||||
>([]);
|
||||
const [scriptList, setScriptList] = useState<Script[]>([]);
|
||||
const [currentScript, setCurrentScript] = useState<Script>();
|
||||
const [selectSciptButtonAndTab, setSelectSciptButtonAndTab] =
|
||||
useState<string>("");
|
||||
const [selectSciptButtonAndTab, setSelectSciptButtonAndTab] = useState<string>("");
|
||||
const [rightOperationTab, setRightOperationTab] = useState<{
|
||||
key: string;
|
||||
uuid: string;
|
||||
@ -196,10 +176,7 @@ function ScriptEditor() {
|
||||
};
|
||||
|
||||
const { id } = useParams();
|
||||
const save = (
|
||||
script: Script,
|
||||
e: editor.IStandaloneCodeEditor
|
||||
): Promise<Script> => {
|
||||
const save = (script: Script, e: editor.IStandaloneCodeEditor): Promise<Script> => {
|
||||
// 解析code生成新的script并更新
|
||||
return new Promise((resolve) => {
|
||||
prepareScriptByCode(e.getValue(), script.origin || "", script.uuid)
|
||||
@ -257,9 +234,7 @@ function ScriptEditor() {
|
||||
return new Promise<void>((resolve) => {
|
||||
chrome.downloads.download(
|
||||
{
|
||||
url: URL.createObjectURL(
|
||||
new Blob([e.getValue()], { type: "text/javascript" })
|
||||
),
|
||||
url: URL.createObjectURL(new Blob([e.getValue()], { type: "text/javascript" })),
|
||||
saveAs: true, // true直接弹出对话框;false弹出下载选项
|
||||
filename: `${script.name}.user.js`,
|
||||
},
|
||||
@ -306,8 +281,7 @@ function ScriptEditor() {
|
||||
title: "调试",
|
||||
hotKey: KeyMod.CtrlCmd | KeyCode.F5,
|
||||
hotKeyString: "Ctrl+F5",
|
||||
tooltip:
|
||||
"只有后台脚本/定时脚本才能调试, 且调试模式下不对进行权限校验(例如@connect)",
|
||||
tooltip: "只有后台脚本/定时脚本才能调试, 且调试模式下不对进行权限校验(例如@connect)",
|
||||
action: async (script, e) => {
|
||||
// 保存更新代码之后再调试
|
||||
const newScript = await save(script, e);
|
||||
@ -457,44 +431,31 @@ function ScriptEditor() {
|
||||
// eslint-disable-next-line default-case
|
||||
switch (rightOperationTab.key) {
|
||||
case "1":
|
||||
newEditors = editors.filter(
|
||||
(item) => item.script.uuid !== rightOperationTab.uuid
|
||||
);
|
||||
newEditors = editors.filter((item) => item.script.uuid !== rightOperationTab.uuid);
|
||||
if (newEditors.length > 0) {
|
||||
// 还有的话,如果之前有选中的,那么我们还是选中之前的,如果没有选中的我们就选中第一个
|
||||
if (
|
||||
rightOperationTab.selectSciptButtonAndTab ===
|
||||
rightOperationTab.uuid
|
||||
) {
|
||||
if (rightOperationTab.selectSciptButtonAndTab === rightOperationTab.uuid) {
|
||||
if (newEditors.length > 0) {
|
||||
newEditors[0].active = true;
|
||||
setSelectSciptButtonAndTab(newEditors[0].script.uuid);
|
||||
}
|
||||
} else {
|
||||
setSelectSciptButtonAndTab(
|
||||
rightOperationTab.selectSciptButtonAndTab
|
||||
);
|
||||
setSelectSciptButtonAndTab(rightOperationTab.selectSciptButtonAndTab);
|
||||
// 之前选中的tab
|
||||
editors.filter((item) => {
|
||||
if (
|
||||
item.script.uuid === rightOperationTab.selectSciptButtonAndTab
|
||||
) {
|
||||
if (item.script.uuid === rightOperationTab.selectSciptButtonAndTab) {
|
||||
item.active = true;
|
||||
} else {
|
||||
item.active = false;
|
||||
}
|
||||
return (
|
||||
item.script.uuid === rightOperationTab.selectSciptButtonAndTab
|
||||
);
|
||||
return item.script.uuid === rightOperationTab.selectSciptButtonAndTab;
|
||||
});
|
||||
}
|
||||
}
|
||||
setEditors([...newEditors]);
|
||||
break;
|
||||
case "2":
|
||||
newEditors = editors.filter(
|
||||
(item) => item.script.uuid === rightOperationTab.uuid
|
||||
);
|
||||
newEditors = editors.filter((item) => item.script.uuid === rightOperationTab.uuid);
|
||||
setSelectSciptButtonAndTab(rightOperationTab.uuid);
|
||||
setEditors([...newEditors]);
|
||||
break;
|
||||
@ -657,11 +618,7 @@ function ScriptEditor() {
|
||||
}}
|
||||
>
|
||||
{menuItem.tooltip ? (
|
||||
<Tooltip
|
||||
key={`m${i.toString()}`}
|
||||
position="right"
|
||||
content={menuItem.tooltip}
|
||||
>
|
||||
<Tooltip key={`m${i.toString()}`} position="right" content={menuItem.tooltip}>
|
||||
{btn}
|
||||
</Tooltip>
|
||||
) : (
|
||||
@ -722,8 +679,7 @@ function ScriptEditor() {
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
whiteSpace: "nowrap",
|
||||
backgroundColor:
|
||||
selectSciptButtonAndTab === script.uuid ? "gray" : "",
|
||||
backgroundColor: selectSciptButtonAndTab === script.uuid ? "gray" : "",
|
||||
}}
|
||||
onClick={() => {
|
||||
setSelectSciptButtonAndTab(script.uuid);
|
||||
|
@ -1,15 +1,7 @@
|
||||
/* eslint-disable import/prefer-default-export */
|
||||
import React from "react";
|
||||
import IoC from "@App/app/ioc";
|
||||
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 {
|
||||
IconBug,
|
||||
IconCode,
|
||||
IconGithub,
|
||||
IconHome,
|
||||
} from "@arco-design/web-react/icon";
|
||||
import { IconBug, IconCode, IconGithub, IconHome } from "@arco-design/web-react/icon";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
// 较对脚本排序位置
|
||||
@ -17,7 +9,7 @@ export function scriptListSort(result: Script[]) {
|
||||
const dao = new ScriptDAO();
|
||||
for (let i = 0; i < result.length; i += 1) {
|
||||
if (result[i].sort !== i) {
|
||||
dao.update(result[i].id, { sort: i });
|
||||
dao.update(result[i].uuid, { sort: i });
|
||||
result[i].sort = i;
|
||||
}
|
||||
}
|
||||
@ -30,13 +22,7 @@ export function installUrlToHome(installUrl: string) {
|
||||
if (installUrl.indexOf("scriptcat.org") !== -1) {
|
||||
const id = installUrl.split("/")[5];
|
||||
return (
|
||||
<Button
|
||||
type="text"
|
||||
iconOnly
|
||||
size="small"
|
||||
target="_blank"
|
||||
href={`https://scriptcat.org/script-show-page/${id}`}
|
||||
>
|
||||
<Button 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="" />
|
||||
</Button>
|
||||
);
|
||||
@ -44,13 +30,7 @@ export function installUrlToHome(installUrl: string) {
|
||||
if (installUrl.indexOf("greasyfork.org") !== -1) {
|
||||
const id = installUrl.split("/")[4];
|
||||
return (
|
||||
<Button
|
||||
type="text"
|
||||
iconOnly
|
||||
size="small"
|
||||
target="_blank"
|
||||
href={`https://greasyfork.org/scripts/${id}`}
|
||||
>
|
||||
<Button type="text" iconOnly size="small" target="_blank" href={`https://greasyfork.org/scripts/${id}`}>
|
||||
<img width={16} height={16} src="/assets/logo/gf.png" alt="" />
|
||||
</Button>
|
||||
);
|
||||
@ -89,6 +69,7 @@ export function installUrlToHome(installUrl: string) {
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore error
|
||||
console.error(e);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
@ -167,28 +148,7 @@ export function ListHomeRender({ script }: { script: Script }) {
|
||||
}
|
||||
|
||||
export function getValues(script: Script) {
|
||||
const { config } = script;
|
||||
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;
|
||||
});
|
||||
return {};
|
||||
}
|
||||
|
||||
export type ScriptIconsProps = {
|
||||
@ -218,6 +178,5 @@ export function ScriptIcons({ script, size = 32, style }: ScriptIconsProps) {
|
||||
</Avatar>
|
||||
);
|
||||
}
|
||||
// eslint-disable-next-line react/jsx-no-useless-fragment
|
||||
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,
|
||||
config: "",
|
||||
},
|
||||
scriptListColumnWidth: {} as { [key: string]: number },
|
||||
},
|
||||
reducers: (create) => {
|
||||
// 初始化黑夜模式
|
||||
@ -45,9 +46,10 @@ export const settingSlice = createAppSlice({
|
||||
},
|
||||
selectors: {
|
||||
selectThemeMode: (state) => state.lightMode,
|
||||
selectScriptListColumnWidth: (state) => state.scriptListColumnWidth,
|
||||
},
|
||||
});
|
||||
|
||||
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 { setupListeners } from "@reduxjs/toolkit/query";
|
||||
import { settingSlice } from "./features/setting";
|
||||
import { scriptSlice } from "./features/script";
|
||||
|
||||
// `combineSlices` automatically combines the reducers using
|
||||
// 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
|
||||
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