From 5575a35626107b98ea79dfb0084452913773e2df Mon Sep 17 00:00:00 2001 From: InkerBot Date: Sat, 6 Jun 2026 00:45:56 +0800 Subject: [PATCH] refactor: enhance UI components with toggle switches and loading screen --- package-lock.json | 403 ------------- src/Layer.css | 606 ++++++++++++++++++-- src/Layer.tsx | 123 +++- src/components/LoadingScreen.tsx | 72 +++ src/components/ModuleHeader.tsx | 8 +- src/components/ModuleList.tsx | 111 +++- src/components/SettingsPanel.tsx | 212 +++++++ src/components/tabs.css | 34 +- src/index.css | 529 ++++++++++++++--- src/main.tsx | 1 + src/modules/chat_export/page.tsx | 9 +- src/modules/history/HistoryEntryList.tsx | 85 +-- src/modules/privacy/page.tsx | 152 +++-- src/modules/translation/bio-style.css | 67 ++- src/modules/translation/page.css | 70 ++- src/modules/translation/page.tsx | 182 +++--- src/modules/translation/style.css | 17 +- src/modules/ungarbled_messages/page.tsx | 10 +- src/modules/util_editor/page.tsx | 74 ++- src/modules/util_lock/page.tsx | 8 +- src/modules/util_remove_limit/page.tsx | 18 +- src/modules/util_remove_submissive/page.tsx | 12 +- src/modules/util_trap/TrapMain.tsx | 43 +- src/modules/util_unlock/page.tsx | 8 +- src/settings.ts | 176 ++++++ src/theme.css | 94 +++ vite.config.ts | 1 + 27 files changed, 2233 insertions(+), 892 deletions(-) create mode 100644 src/components/LoadingScreen.tsx create mode 100644 src/components/SettingsPanel.tsx create mode 100644 src/settings.ts create mode 100644 src/theme.css diff --git a/package-lock.json b/package-lock.json index 78744fe..4b3edc7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,7 +25,6 @@ "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.20", "globals": "^16.3.0", - "rollup-plugin-visualizer": "^7.0.0", "socket.io-client": "^4.8.1", "typescript": "~5.8.3", "typescript-eslint": "^8.35.1", @@ -1823,19 +1822,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -2022,22 +2008,6 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/bundle-name": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", - "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "run-applescript": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -2086,21 +2056,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/cliui": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", - "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^7.2.0", - "strip-ansi": "^7.1.0", - "wrap-ansi": "^9.0.0" - }, - "engines": { - "node": ">=20" - } - }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -2182,49 +2137,6 @@ "dev": true, "license": "MIT" }, - "node_modules/default-browser": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz", - "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==", - "dev": true, - "license": "MIT", - "dependencies": { - "bundle-name": "^4.1.0", - "default-browser-id": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/default-browser-id": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", - "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/define-lazy-prop": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", - "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/electron-to-chromium": { "version": "1.5.182", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.182.tgz", @@ -2232,13 +2144,6 @@ "dev": true, "license": "ISC" }, - "node_modules/emoji-regex": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", - "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", - "dev": true, - "license": "MIT" - }, "node_modules/engine.io-client": { "version": "6.6.3", "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.3.tgz", @@ -2674,29 +2579,6 @@ "node": ">=6.9.0" } }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-east-asian-width": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", - "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -2777,22 +2659,6 @@ "node": ">=0.8.19" } }, - "node_modules/is-docker": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", - "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", - "dev": true, - "license": "MIT", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -2816,38 +2682,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-in-ssh": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-in-ssh/-/is-in-ssh-1.0.0.tgz", - "integrity": "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-inside-container": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", - "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-docker": "^3.0.0" - }, - "bin": { - "is-inside-container": "cli.js" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -2858,22 +2692,6 @@ "node": ">=0.12.0" } }, - "node_modules/is-wsl": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", - "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-inside-container": "^1.0.0" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -3089,27 +2907,6 @@ "dev": true, "license": "MIT" }, - "node_modules/open": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/open/-/open-11.0.0.tgz", - "integrity": "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==", - "dev": true, - "license": "MIT", - "dependencies": { - "default-browser": "^5.4.0", - "define-lazy-prop": "^3.0.0", - "is-in-ssh": "^1.0.0", - "is-inside-container": "^1.0.0", - "powershell-utils": "^0.1.0", - "wsl-utils": "^0.3.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -3242,19 +3039,6 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/powershell-utils": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.1.0.tgz", - "integrity": "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -3397,63 +3181,6 @@ "fsevents": "~2.3.2" } }, - "node_modules/rollup-plugin-visualizer": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/rollup-plugin-visualizer/-/rollup-plugin-visualizer-7.0.0.tgz", - "integrity": "sha512-loo4kmhTg7GMO0hqaUv/azvLPUT2B4jXU3gNMG35gm1mWKpOzhV6rspb/Mqmsfg7oOTdkzdmOckCIwGB5Ca1CA==", - "dev": true, - "license": "MIT", - "dependencies": { - "open": "^11.0.0", - "picomatch": "^4.0.2", - "source-map": "^0.7.4", - "yargs": "^18.0.0" - }, - "bin": { - "rollup-plugin-visualizer": "dist/bin/cli.js" - }, - "engines": { - "node": ">=22" - }, - "peerDependencies": { - "rolldown": "1.x || ^1.0.0-beta || ^1.0.0-rc", - "rollup": "2.x || 3.x || 4.x" - }, - "peerDependenciesMeta": { - "rolldown": { - "optional": true - }, - "rollup": { - "optional": true - } - } - }, - "node_modules/rollup-plugin-visualizer/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/run-applescript": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", - "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -3583,16 +3310,6 @@ } } }, - "node_modules/source-map": { - "version": "0.7.6", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", - "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">= 12" - } - }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -3609,40 +3326,6 @@ "integrity": "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==", "license": "MIT" }, - "node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -3973,37 +3656,6 @@ "node": ">=0.10.0" } }, - "node_modules/wrap-ansi": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", - "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/ws": { "version": "8.17.1", "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", @@ -4026,23 +3678,6 @@ } } }, - "node_modules/wsl-utils": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.3.1.tgz", - "integrity": "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-wsl": "^3.1.0", - "powershell-utils": "^0.1.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/xmlhttprequest-ssl": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", @@ -4052,16 +3687,6 @@ "node": ">=0.4.0" } }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", @@ -4069,34 +3694,6 @@ "dev": true, "license": "ISC" }, - "node_modules/yargs": { - "version": "18.0.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz", - "integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^9.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "string-width": "^7.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^22.0.0" - }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=23" - } - }, - "node_modules/yargs-parser": { - "version": "22.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", - "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=23" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/src/Layer.css b/src/Layer.css index 853bc39..6c7437a 100644 --- a/src/Layer.css +++ b/src/Layer.css @@ -1,24 +1,55 @@ .floating-window { - z-index: 1; + z-index: var(--rw-z-window); position: fixed; user-select: none; - - line-height: 1.2; - min-height: 1.2em; - font-size: 12px; /* Fixed font size - scaling handled by transform */ + line-height: var(--rw-line-height-normal); + font-family: var(--rw-font-family); + font-size: var(--rw-font-size-base); } .floating-window.expanded { - background: rgba(0, 0, 0, 0.45); - border: 1px solid rgba(30, 64, 175, 0.6); - color: white; - opacity: 0.85; + background: var(--rw-surface-overlay); + backdrop-filter: var(--rw-backdrop-blur); + -webkit-backdrop-filter: var(--rw-backdrop-blur); + border: 1px solid var(--rw-border-color-strong); + border-radius: var(--rw-radius-md); + color: var(--rw-text-primary); + box-shadow: var(--rw-shadow-lg), var(--rw-glow-cyan); + overflow: hidden; + position: relative; +} + +.floating-window.expanded .window-content::after { + content: ''; + position: absolute; + inset: 0; + pointer-events: none; + z-index: 9999; + background: repeating-linear-gradient( + 0deg, + transparent, + transparent 2px, + var(--rw-scanline-color) 2px, + var(--rw-scanline-color) 4px + ); + display: var(--rw-scanline-display, block); +} + +.floating-window.expanded::before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 20px; + height: 2px; + background: var(--rw-neon-cyan); + box-shadow: 0 0 8px var(--rw-neon-cyan-glow-strong); + z-index: 2; } .floating-window.collapsed { - background: rgba(0, 0, 0, 0.8); - border: 1px solid rgba(30, 64, 175, 0.5); - color: white; + background: transparent; + border: none; } .window-content { @@ -27,46 +58,95 @@ display: flex; flex-direction: column; height: 100%; + border-radius: var(--rw-radius-md); } .window-header { - background: linear-gradient(90deg, #1e40af, #1e3a8a); - padding: 0 4px; - color: white; + background: linear-gradient(180deg, rgba(0, 229, 255, 0.08) 0%, rgba(0, 0, 0, 0.3) 100%); + padding: var(--rw-space-2) var(--rw-space-3); + color: var(--rw-neon-cyan); display: flex; justify-content: space-between; align-items: center; - border-bottom: 1px solid rgba(30, 64, 175, 0.3); + border-bottom: 1px solid var(--rw-border-color); flex-shrink: 0; + min-height: 34px; + text-shadow: 0 0 6px var(--rw-neon-cyan-glow); } .window-header h3 { margin: 0; - font-weight: 600; + font-weight: var(--rw-font-weight-semibold); + font-size: var(--rw-font-size-sm); + letter-spacing: 1.5px; + text-transform: uppercase; pointer-events: none; + color: var(--rw-neon-cyan); } .collapse-btn { - background: rgba(255, 255, 255, 0.15); - border: none; - color: white; - width: 16px; - height: 16px; + background: rgba(0, 229, 255, 0.08); + border: 1px solid var(--rw-border-color); + border-radius: var(--rw-radius-sm); + color: var(--rw-neon-cyan); + width: 22px; + height: 22px; display: flex; align-items: center; justify-content: center; - font-weight: bold; + font-size: var(--rw-font-size-xs); + font-weight: var(--rw-font-weight-bold); + cursor: pointer; + transition: all var(--rw-transition-fast); + padding: 0; } - .collapse-btn:hover { - background: rgba(30, 64, 175, 0.4); + background: rgba(0, 229, 255, 0.18); + box-shadow: var(--rw-glow-cyan); +} + +.settings-btn { + background: transparent; + border: 1px solid transparent; + border-radius: var(--rw-radius-sm); + color: var(--rw-text-secondary); + width: 22px; + height: 22px; + display: flex; + align-items: center; + justify-content: center; + font-size: 13px; + cursor: pointer; + transition: all var(--rw-transition-fast); + padding: 0; +} + +.settings-btn:hover { + color: var(--rw-neon-cyan); + background: rgba(0, 229, 255, 0.1); + text-shadow: 0 0 6px var(--rw-neon-cyan-glow); +} + +.window-header-actions { + display: flex; + align-items: center; + gap: var(--rw-space-2); +} + +.window-footer { + flex-shrink: 0; + padding: var(--rw-space-2) var(--rw-space-3); + border-top: 1px solid var(--rw-border-color-subtle); + background: rgba(0, 0, 0, 0.3); + font-size: var(--rw-font-size-xs); + color: var(--rw-text-muted); } .window-body { - padding: 2px 2px; + padding: var(--rw-space-3); background: transparent; - color: white; - line-height: 1.2; + color: var(--rw-text-primary); + line-height: var(--rw-line-height-normal); flex: 1; display: flex; flex-direction: column; @@ -74,7 +154,7 @@ } .window-body p { - margin: 0 0 1px 0; + margin: 0 0 var(--rw-space-1) 0; } .window-body p:last-child { @@ -82,36 +162,146 @@ } .floating-button { - background: linear-gradient(135deg, #1e40af, #1e3a8a); - border: none; - width: 22px; - height: 22px; - color: white; - font-size: 20px; + background: var(--rw-surface-header); + border: 1px solid var(--rw-border-color-strong); + border-radius: var(--rw-radius-full); + box-shadow: var(--rw-shadow-md), var(--rw-glow-cyan); + width: 32px; + height: 32px; + color: var(--rw-neon-cyan); + font-size: var(--rw-font-size-md); + font-weight: var(--rw-font-weight-bold); display: flex; align-items: center; justify-content: center; + cursor: pointer; + transition: transform var(--rw-transition-fast), box-shadow var(--rw-transition-fast); + text-shadow: 0 0 6px var(--rw-neon-cyan-glow); } - .floating-button:hover { - background: linear-gradient(135deg, #1e3a8a, #1e40af); + transform: scale(1.15); + box-shadow: var(--rw-shadow-lg), var(--rw-glow-cyan-strong); } .module-header { flex-shrink: 0; - padding: 4px 0; + padding: var(--rw-space-2) 0; + display: flex; + align-items: center; + gap: var(--rw-space-2); + min-height: 28px; +} + +.module-header .back-btn { + background: rgba(0, 229, 255, 0.08); + border: 1px solid var(--rw-border-color); + border-radius: var(--rw-radius-sm); + color: var(--rw-text-secondary); + font-size: var(--rw-font-size-xs); + padding: 2px var(--rw-space-2); + cursor: pointer; + transition: all var(--rw-transition-fast); + line-height: 1.2; +} + +.module-header .back-btn:hover { + color: var(--rw-neon-cyan); + background: rgba(0, 229, 255, 0.15); + text-shadow: 0 0 4px var(--rw-neon-cyan-glow); +} + +.module-header .location-info { + font-size: var(--rw-font-size-xs); + color: var(--rw-text-muted); + letter-spacing: 0.5px; } .module-list { margin: 0; padding: 0; list-style: none; - flex: 1; display: flex; flex-direction: column; overflow-x: hidden; overflow-y: auto; + gap: 1px; +} + +.module-list li { + display: flex; + align-items: center; + gap: var(--rw-space-2); + padding: var(--rw-space-2) var(--rw-space-3); + border-radius: var(--rw-radius-sm); + cursor: pointer; + color: var(--rw-text-primary); + font-size: var(--rw-font-size-sm); + font-weight: var(--rw-font-weight-normal); + transition: all var(--rw-transition-fast); + position: relative; + border-left: 2px solid transparent; +} + +.module-list li:hover { + background: var(--rw-surface-hover); + color: var(--rw-neon-cyan); + border-left-color: var(--rw-neon-cyan); + text-shadow: 0 0 4px var(--rw-neon-cyan-glow); +} + +.module-list li:active { + background: var(--rw-surface-active); + border-left-color: var(--rw-neon-cyan); +} + +.module-list .module-category { + padding: var(--rw-space-2) var(--rw-space-3) var(--rw-space-1); + font-size: var(--rw-font-size-xs); + font-weight: var(--rw-font-weight-semibold); + color: var(--rw-neon-cyan); + text-transform: uppercase; + letter-spacing: 1.5px; + cursor: default; + pointer-events: none; + border-bottom: 1px solid var(--rw-border-color); + border-left: none; + margin-bottom: 2px; + text-shadow: 0 0 4px var(--rw-neon-cyan-glow); +} + +.module-list .module-category:hover { + text-shadow: 0 0 4px var(--rw-neon-cyan-glow); + color: var(--rw-neon-cyan); + background: transparent; + border-left: none; +} + +.module-list .module-category:not(:first-child) { + margin-top: var(--rw-space-2); +} + +.module-list li .module-icon { + width: 18px; + height: 18px; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + opacity: 0.5; + transition: opacity var(--rw-transition-fast); +} + +.module-list li:hover .module-icon { + opacity: 0.85; +} + +.module-list li .module-icon svg { + width: 16px; + height: 16px; + stroke: currentColor; + fill: none; + stroke-width: 1.5; } .module-content { @@ -120,4 +310,342 @@ flex-direction: column; overflow-x: hidden; overflow-y: auto; + padding: var(--rw-space-1) 0 0 0; +} + +.module-list-empty { + display: flex; + align-items: center; + justify-content: center; + height: 100%; + color: var(--rw-text-muted); + font-size: var(--rw-font-size-sm); + text-align: center; + padding: var(--rw-space-6); +} + +.settings-overlay { + position: absolute; + inset: 0; + z-index: var(--rw-z-settings); + background: var(--rw-surface-overlay); + backdrop-filter: blur(6px); + -webkit-backdrop-filter: blur(6px); + display: flex; + flex-direction: column; + border-radius: var(--rw-radius-md); + overflow: hidden; +} + +.settings-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: var(--rw-space-3); + border-bottom: 1px solid var(--rw-border-color); + background: linear-gradient(180deg, rgba(0, 229, 255, 0.06) 0%, transparent 100%); +} + +.settings-header h3 { + font-size: var(--rw-font-size-sm); + letter-spacing: 1.5px; + text-transform: uppercase; + color: var(--rw-neon-cyan); + text-shadow: 0 0 6px var(--rw-neon-cyan-glow); +} + +.settings-close-btn { + background: transparent; + border: 1px solid var(--rw-border-color); + border-radius: var(--rw-radius-sm); + color: var(--rw-text-secondary); + width: 22px; + height: 22px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + font-size: 14px; + transition: all var(--rw-transition-fast); + padding: 0; +} + +.settings-close-btn:hover { + color: var(--rw-neon-magenta); + border-color: var(--rw-neon-magenta); + box-shadow: var(--rw-glow-magenta); +} + +.settings-back-btn { + background: transparent; + border: 1px solid var(--rw-border-color); + border-radius: var(--rw-radius-sm); + color: var(--rw-text-secondary); + width: 22px; + height: 22px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + font-size: 14px; + transition: all var(--rw-transition-fast); + padding: 0; +} + +.settings-back-btn:hover { + color: var(--rw-neon-cyan); + border-color: var(--rw-neon-cyan); + box-shadow: var(--rw-glow-cyan); +} + +.settings-body { + flex: 1; + overflow-y: auto; + padding: var(--rw-space-3); +} + +.settings-shortcut { + font-size: var(--rw-font-size-xs); + color: var(--rw-text-muted); + letter-spacing: 0.5px; +} + +.loading-screen { + position: absolute; + inset: 0; + z-index: var(--rw-z-modal); + background: var(--rw-bg-deep); + display: flex; + align-items: center; + justify-content: center; + border-radius: var(--rw-radius-md); + overflow: hidden; +} + +.loading-screen-inner { + position: relative; + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; +} + +.loading-grid { + position: absolute; + inset: 0; + background-image: linear-gradient(rgba(0, 229, 255, 0.06) 1px, transparent 1px), + linear-gradient(90deg, rgba(0, 229, 255, 0.06) 1px, transparent 1px); + background-size: 24px 24px; + animation: gridShift 3s linear infinite; +} + +@keyframes gridShift { + 0% { + background-position: 0 0, 0 0; + } + 100% { + background-position: 24px 24px, 24px 24px; + } +} + +.loading-center { + display: flex; + flex-direction: column; + align-items: center; + gap: 14px; + z-index: 1; +} + +.loading-logo { + font-family: var(--rw-font-family); + font-size: 28px; + font-weight: var(--rw-font-weight-bold); + color: var(--rw-neon-cyan); + text-shadow: 0 0 16px var(--rw-neon-cyan-glow-strong), 0 0 32px var(--rw-neon-cyan-glow); + animation: logoPulse 1.2s ease-in-out infinite; + letter-spacing: 3px; +} + +@keyframes logoPulse { + 0%, 100% { + opacity: 0.85; + text-shadow: 0 0 16px var(--rw-neon-cyan-glow-strong); + } + 50% { + opacity: 1; + text-shadow: 0 0 24px var(--rw-neon-cyan-glow-strong), 0 0 48px var(--rw-neon-cyan-glow); + } +} + +.loading-bracket { + opacity: 0.5; +} + +.loading-rw { + margin: 0 2px; +} + +.loading-bar-track { + width: 180px; + height: 2px; + background: var(--rw-border-color); + border-radius: var(--rw-radius-full); + overflow: hidden; + position: relative; +} + +.loading-bar-fill { + height: 100%; + background: var(--rw-neon-cyan); + border-radius: var(--rw-radius-full); + transition: width 120ms ease-out; + box-shadow: 0 0 8px var(--rw-neon-cyan-glow-strong); +} + +.loading-status { + font-family: var(--rw-font-family); + font-size: var(--rw-font-size-xs); + color: var(--rw-text-secondary); + letter-spacing: 1px; + text-transform: uppercase; + display: flex; + align-items: center; + gap: 6px; +} + +.loading-cursor { + color: var(--rw-neon-cyan); + animation: cursorBlink 0.7s step-end infinite; +} + +@keyframes cursorBlink { + 0%, 100% { + opacity: 1; + } + 50% { + opacity: 0; + } +} + +.loading-corner { + position: absolute; + width: 20px; + height: 20px; + border-color: var(--rw-neon-cyan); + border-style: solid; + border-width: 0; + opacity: 0.3; + animation: cornerFade 1.5s ease-in-out infinite; +} + +.loading-corner.tl { + top: 8px; + left: 8px; + border-top-width: 1px; + border-left-width: 1px; +} + +.loading-corner.tr { + top: 8px; + right: 8px; + border-top-width: 1px; + border-right-width: 1px; + animation-delay: 0.3s; +} + +.loading-corner.bl { + bottom: 8px; + left: 8px; + border-bottom-width: 1px; + border-left-width: 1px; + animation-delay: 0.6s; +} + +.loading-corner.br { + bottom: 8px; + right: 8px; + border-bottom-width: 1px; + border-right-width: 1px; + animation-delay: 0.9s; +} + +@keyframes cornerFade { + 0%, 100% { + opacity: 0.2; + } + 50% { + opacity: 0.7; + } +} + +.view-transition-wrap { + flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; + position: relative; + min-height: 0; +} + +.view-panel { + flex: 1; + display: flex; + flex-direction: column; + overflow-x: hidden; + overflow-y: auto; + transition: opacity var(--rw-anim-duration, 220ms) ease, + transform var(--rw-anim-duration, 220ms) cubic-bezier(0.22, 0.61, 0.36, 1); +} + +.view-panel.view-enter-forward { + animation: slideFromRight var(--rw-anim-duration, 220ms) cubic-bezier(0.22, 0.61, 0.36, 1) forwards; +} + +@keyframes slideFromRight { + 0% { + opacity: 0; + transform: translateX(30px); + } + 100% { + opacity: 1; + transform: translateX(0); + } +} + +.view-panel.view-enter-backward { + animation: slideFromLeft var(--rw-anim-duration, 220ms) cubic-bezier(0.22, 0.61, 0.36, 1) forwards; +} + +@keyframes slideFromLeft { + 0% { + opacity: 0; + transform: translateX(-30px); + } + 100% { + opacity: 1; + transform: translateX(0); + } +} + +.view-panel.view-enter-fade { + animation: fadeScaleIn var(--rw-anim-duration, 220ms) ease forwards; +} + +@keyframes fadeScaleIn { + 0% { + opacity: 0; + transform: scale(0.96); + } + 100% { + opacity: 1; + transform: scale(1); + } +} + +@keyframes instant { + 0%, 100% { + opacity: 1; + transform: none; + } } diff --git a/src/Layer.tsx b/src/Layer.tsx index e45c56c..6ea43bf 100644 --- a/src/Layer.tsx +++ b/src/Layer.tsx @@ -1,29 +1,57 @@ -import React, { useState, useEffect } from 'react'; +import React, {useEffect, useState} from 'react'; import FloatingWindow from './components/FloatingWindow'; import LoadingProgress from './components/LoadingProgress'; +import LoadingScreen from './components/LoadingScreen'; import ModuleHeader from './components/ModuleHeader'; import ModuleList from './components/ModuleList'; import ModuleContent from './components/ModuleContent'; -import { razorIsPro } from "./util/pro.ts"; +import SettingsPanel from './components/SettingsPanel'; +import {applySettings, loadSettings} from './settings'; +import {razorIsPro} from "./util/pro.ts"; +import type {ModuleConfig} from "./modules.ts"; import razorModSdk from "./razor-wings"; import main from "./main.tsx"; -import { useGameState } from './hooks/useGameState'; -import { useModuleManager } from './hooks/useModuleManager'; +import {useGameState} from './hooks/useGameState'; +import {useModuleManager} from './hooks/useModuleManager'; const Layer: React.FC = () => { const [isExpanded, setIsExpanded] = useState(false); + const [showSettings, setShowSettings] = useState(false); + const [showLoading, setShowLoading] = useState(false); + + type NavDir = 'forward' | 'backward' | null; + const [navDir, setNavDir] = useState(null); + + // Track animation setting from saved config + const [enableAnimations, setEnableAnimations] = useState(() => loadSettings().enableAnimations); - // Use custom hooks for game state and module management const gameState = useGameState(); const { availableModules, loadingState, currentOpenModule, - openModule, - closeModule + openModule: _openModule, + closeModule: _closeModule } = useModuleManager(gameState); - // Setup keyboard hook on mount + // Wrap open/close to inject direction tracking (only when animations enabled) + const handleOpenModule = (m: ModuleConfig) => { + if (enableAnimations) setNavDir('forward'); + _openModule(m); + }; + const handleCloseModule = () => { + if (enableAnimations) setNavDir('backward'); + _closeModule(); + }; + // Clear direction after animation completes + useEffect(() => { + if (navDir) { + const t = setTimeout(() => setNavDir(null), 250); + return () => clearTimeout(t); + } + }, [navDir, currentOpenModule]); + + // Setup keyboard hook useEffect(() => { razorModSdk.hookFunction('ChatRoomKeyDown', 10, (args, next) => { if (document.activeElement && main.overlay && main.overlay.contains(document.activeElement)) { @@ -33,38 +61,93 @@ const Layer: React.FC = () => { }); }, []); - // Toggle expand/collapse state + // Apply saved settings on mount (target the shadow host so CSS vars reach inside Shadow DOM) + useEffect(() => { + const settings = loadSettings(); + applySettings(settings, main.overlay!); + }, []); + const toggleExpanded = () => { + if (!isExpanded) { + setShowLoading(true); + } setIsExpanded(!isExpanded); }; + const toggleSettings = () => { + if (enableAnimations) setNavDir('forward'); + setShowSettings(!showSettings); + }; + const closeSettings = () => { + if (enableAnimations) setNavDir('backward'); + setShowSettings(false); + // Refresh animation setting after settings panel closes + const s = loadSettings(); + setEnableAnimations(s.enableAnimations); + }; + const finishLoading = () => setShowLoading(false); + + // Derive animation classes — only when animations are enabled + const animForward = enableAnimations && navDir === 'forward' ? 'view-enter-forward' : ''; + const animBackward = enableAnimations && navDir === 'backward' ? 'view-enter-backward' : ''; + const animFade = enableAnimations ? 'view-enter-fade' : ''; + + const headerConfig = { + title: "Razor Wings" + (razorIsPro() ? ' Pro' : ''), + actions: ( + + ), + }; return ( + {/* Sci-fi boot animation */} + {showLoading && } + - {currentOpenModule === null ? ( - - ) : ( - + {/* ── Animated content area ── */} +
+ {currentOpenModule === null ? ( +
+ +
+ ) : ( +
+ +
+ )} +
+ + {/* Settings overlay with fade animation */} + {showSettings && ( +
+ +
)}
); diff --git a/src/components/LoadingScreen.tsx b/src/components/LoadingScreen.tsx new file mode 100644 index 0000000..7dad0c2 --- /dev/null +++ b/src/components/LoadingScreen.tsx @@ -0,0 +1,72 @@ +import React, { useState, useEffect, useRef } from 'react'; + +interface Props { + onComplete: () => void; + duration?: number; +} + +/** Cyber sci-fi boot animation — shows once per expand cycle */ +const LoadingScreen: React.FC = ({ onComplete, duration = 700 }) => { + const [phase, setPhase] = useState(0); + const timerRef = useRef | undefined>(undefined); + + useEffect(() => { + const step = duration / 5; + let i = 0; + timerRef.current = setInterval(() => { + i++; + setPhase(i); + if (i >= 5) { + clearInterval(timerRef.current); + setTimeout(onComplete, 150); + } + }, step); + return () => { if (timerRef.current !== undefined) clearInterval(timerRef.current); }; + }, []); + + const phases = [ + 'INITIALIZING_KERNEL', + 'LOADING_MODULES', + 'ESTABLISHING_HANDSHAKE', + 'CALIBRATING_RENDERER', + 'SYSTEM_READY', + ]; + + return ( +
+
+ {/* Decorative cyber grid */} +
+ + {/* Central content */} +
+
+ {'['} + RW + {']'} +
+ +
+
+
+ +
+ + {phase < 5 ? phases[phase] : phases[4]} +
+
+ + {/* Corner decorations */} +
+
+
+
+
+
+ ); +}; + +export default LoadingScreen; diff --git a/src/components/ModuleHeader.tsx b/src/components/ModuleHeader.tsx index 4cdb280..eb43ecf 100644 --- a/src/components/ModuleHeader.tsx +++ b/src/components/ModuleHeader.tsx @@ -15,10 +15,12 @@ const ModuleHeader: React.FC = ({ }) => { return (
- Current location: {currentModule ?? 'unknown'} - {currentScreen ?? 'unknown'} + + Current location: {currentModule ?? 'unknown'} - {currentScreen ?? 'unknown'} + {showBackButton && ( - )}
diff --git a/src/components/ModuleList.tsx b/src/components/ModuleList.tsx index ba05894..3413872 100644 --- a/src/components/ModuleList.tsx +++ b/src/components/ModuleList.tsx @@ -1,26 +1,117 @@ import React from 'react'; -import type { ModuleConfig } from '../modules.ts'; +import type {ModuleConfig} from '../modules.ts'; interface ModuleListProps { modules: ModuleConfig[]; onModuleClick: (module: ModuleConfig) => void; } +/* ── Sci-fi linear SVG icons (16x16 viewBox, stroke-based) ── */ +const SVGIcon: React.FC<{ d: string }> = ({d}) => ( + + + +); + +const ICONS: Record = { + '翻译': 'M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20zM2 12h20M12 2a15 15 0 0 1 4 10 15 15 0 0 1-4 10M12 2a15 15 0 0 0-4 10 15 15 0 0 0 4 10', + '隐私设置': 'M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z', + '辅助_退出房间': 'M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4m7 14 5-5-5-5m5 5H9', + '辅助_解锁': 'M19 11H5a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7a2 2 0 0 0-2-2zM7 11V7a5 5 0 0 1 10 0', + '辅助_上锁': 'M19 11H5a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7a2 2 0 0 0-2-2zM7 11V7a5 5 0 0 1 9.9-1', + '辅助_外观编辑器': 'M17 3a2.85 2.85 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z', + '辅助_解除限制': 'M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09zM12 15l-3-3m0 0a22 22 0 0 1 4-4m-4 4 4 4', + '辅助_移除奴隶': 'M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2m16 0v-2M22 11l-4-4m0 0-4 4m4-4v13m-14-1a4 4 0 0 1 0-8', + '工具_陷阱': 'M22 2 11 13m11-11-7 7M22 2l-4.5 16.5L14 14 2 8.5Z', + '聊天导出': 'M22 12h-4l-3 9L9 3l-3 9H2', + '地图脚本': 'M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z', + '外观记录': 'M12 8v4l3 3m6-3a9 9 0 1 1-18 0 9 9 0 0 1 18 0z', + '郊狼': 'M13 2 3 14h9l-1 8 10-12h-9l1-8z', + '作弊_获得所有物品': 'M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z', + '强制开启绒语翻译器': 'M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z', +}; + +function getIconId(displayName: string): string | null { + if (ICONS[displayName]) return displayName; + for (const prefix of Object.keys(ICONS)) { + if (displayName.startsWith(prefix)) return prefix; + } + return null; +} + +const CATEGORIES: Record = { + '辅助_': 'AUX', + '工具_': 'TOOLS', + '作弊_': 'CHEAT', +}; + +function getCategory(displayName: string): string | null { + for (const [prefix, label] of Object.entries(CATEGORIES)) { + if (displayName.startsWith(prefix)) return label; + } + return null; +} + +function getCleanName(displayName: string): string { + for (const prefix of Object.keys(CATEGORIES)) { + if (displayName.startsWith(prefix)) return displayName.slice(prefix.length); + } + return displayName; +} + +interface GroupedModules { + category: string | null; + items: ModuleConfig[]; +} + +function groupModules(modules: ModuleConfig[]): GroupedModules[] { + const groups: GroupedModules[] = []; + let currentCategory: string | null = null; + let currentItems: ModuleConfig[] = []; + + for (const mod of modules) { + const cat = getCategory(mod.displayName); + if (cat !== currentCategory) { + if (currentItems.length > 0) groups.push({category: currentCategory, items: currentItems}); + currentCategory = cat; + currentItems = []; + } + currentItems.push(mod); + } + if (currentItems.length > 0) groups.push({category: currentCategory, items: currentItems}); + return groups; +} + const ModuleList: React.FC = ({ modules, onModuleClick }) => { if (modules.length === 0) { - return ( -
- No modules available in current context -
- ); + return
NO MODULES AVAILABLE IN CURRENT CONTEXT
; } + const groups = groupModules(modules); + const hasMultipleGroups = groups.length > 1; + return (
    - {modules.map((module, index) => ( -
  • onModuleClick(module)}> - {module.displayName} -
  • + {groups.map((group, groupIndex) => ( + + {hasMultipleGroups && group.category && ( +
  • + [{group.category}] +
  • + )} + {group.items.map((module, index) => { + const iconId = getIconId(module.displayName); + return ( +
  • onModuleClick(module)}> + + {iconId && } + + {getCleanName(module.displayName)} +
  • + ); + })} +
    ))}
); diff --git a/src/components/SettingsPanel.tsx b/src/components/SettingsPanel.tsx new file mode 100644 index 0000000..e0d02be --- /dev/null +++ b/src/components/SettingsPanel.tsx @@ -0,0 +1,212 @@ +import React, { useState, useEffect, useCallback } from 'react'; +import { loadSettings, saveSettings, applySettings, type UserSettings } from '../settings'; + +interface Props { + onClose: () => void; + /** The shadow root's host or document element to apply CSS vars to */ + styleRoot: HTMLElement; +} + +const ACCENT_OPTIONS: { value: string; label: string; color: string }[] = [ + { value: 'cyan', label: 'Cyan', color: '#00e5ff' }, + { value: 'blue', label: 'Blue', color: '#448aff' }, + { value: 'purple', label: 'Purple', color: '#7c4dff' }, + { value: 'green', label: 'Green', color: '#00e676' }, + { value: 'red', label: 'Red', color: '#ff4444' }, + { value: 'pink', label: 'Pink', color: '#ff80ab' }, +]; + +const THEME_OPTIONS: { value: 'dark' | 'light'; label: string; icon: string }[] = [ + { value: 'dark', label: '深色', icon: '🌙' }, + { value: 'light', label: '浅色', icon: '☀️' }, +]; + +const SettingsPanel: React.FC = ({ onClose, styleRoot }) => { + const [settings, setSettings] = useState(() => loadSettings()); + + const update = useCallback((patch: Partial) => { + setSettings(prev => { + const next = { ...prev, ...patch }; + saveSettings(next); + applySettings(next, styleRoot); + return next; + }); + }, [styleRoot]); + + const handleReset = () => { + const def: UserSettings = { + fontSize: 14, + bgOpacity: 0.94, + accentColor: 'cyan', + glowIntensity: 1, + showScanlines: true, + backdropBlur: 16, + enableAnimations: true, + themeMode: 'dark', + }; + setSettings(def); + saveSettings(def); + applySettings(def, styleRoot); + }; + + // Apply on mount + useEffect(() => { + applySettings(settings, styleRoot); + }, []); + + return ( +
+
+
+ +

Settings

+
+ +
+
+ + {/* Font Size */} +
Typography
+
+ +
+ update({ fontSize: Number(e.target.value) })} + style={{ flex: 1 }} + /> + + {settings.fontSize}px + +
+
+ + {/* Theme Mode — Dark / Light */} +
Theme
+
+ +
+ {THEME_OPTIONS.map(opt => ( + + ))} +
+
+ + {/* Accent Color */} +
+ +
+ {ACCENT_OPTIONS.map(opt => ( + + ))} +
+
+ + {/* Background Opacity */} +
Appearance
+
+ +
+ update({ bgOpacity: Number(e.target.value) / 100 })} + style={{ flex: 1 }} + /> + + {Math.round(settings.bgOpacity * 100)}% + +
+
+ + {/* Backdrop Blur */} +
+ +
+ update({ backdropBlur: Number(e.target.value) })} + style={{ flex: 1 }} + /> + + {settings.backdropBlur}px + +
+
+ + {/* Glow Intensity */} +
+ +
+ update({ glowIntensity: Number(e.target.value) })} + style={{ flex: 1 }} + /> + + {settings.glowIntensity}x + +
+
+ + {/* Animations Toggle */} +
Effects
+
+ +
+ +
+
+ + {/* Reset button */} +
+ +
+
+
+ ); +}; + +export default SettingsPanel; diff --git a/src/components/tabs.css b/src/components/tabs.css index 19a33d4..1b8e13b 100644 --- a/src/components/tabs.css +++ b/src/components/tabs.css @@ -1,29 +1,43 @@ .rw-tabs { display: flex; - gap: 4px; - border-bottom: 1px solid rgba(255, 255, 255, 0.2); - margin-bottom: 8px; + gap: 1px; + border-bottom: 1px solid var(--rw-border-color); + margin-bottom: var(--rw-space-3); } .rw-tab { - padding: 6px 12px; + padding: var(--rw-space-2) var(--rw-space-4); background: none; border: none; border-bottom: 2px solid transparent; - color: inherit; + border-radius: var(--rw-radius-sm) var(--rw-radius-sm) 0 0; + color: var(--rw-text-muted); + font-family: var(--rw-font-family); + font-size: var(--rw-font-size-sm); + font-weight: var(--rw-font-weight-medium); cursor: pointer; - opacity: 0.6; + transition: all var(--rw-transition-fast); + margin-bottom: -1px; + letter-spacing: 0.3px; } .rw-tab:hover { - opacity: 0.8; + color: var(--rw-text-primary); + background: var(--rw-surface-hover); } .rw-tab-active { - opacity: 1; - border-bottom-color: #49bbcf; + color: var(--rw-neon-cyan); + border-bottom-color: var(--rw-neon-cyan); + font-weight: var(--rw-font-weight-semibold); + text-shadow: 0 0 6px var(--rw-neon-cyan-glow); +} + +.rw-tab-active:hover { + color: var(--rw-neon-cyan); + background: rgba(0, 229, 255, 0.06); } .rw-tab-content { - padding-top: 4px; + padding-top: var(--rw-space-1); } diff --git a/src/index.css b/src/index.css index b4057de..78906a7 100644 --- a/src/index.css +++ b/src/index.css @@ -1,20 +1,77 @@ +*, *::before, *::after { + box-sizing: border-box; +} + +body { + margin: 0; + padding: 0; + font-family: var(--rw-font-family); + font-size: var(--rw-font-size-base); + line-height: var(--rw-line-height-normal); + color: var(--rw-text-primary); + -webkit-font-smoothing: antialiased; +} + h1, h2, h3, h4, h5, h6 { - color: white; + color: var(--rw-neon-cyan); margin: 0; + line-height: var(--rw-line-height-tight); + font-weight: var(--rw-font-weight-semibold); + text-shadow: 0 0 6px var(--rw-neon-cyan-glow); +} + +h1 { + font-size: var(--rw-font-size-2xl); +} + +h2 { + font-size: var(--rw-font-size-xl); +} + +h3 { + font-size: var(--rw-font-size-lg); +} + +h4 { + font-size: var(--rw-font-size-md); +} + +h5, h6 { + font-size: var(--rw-font-size-base); +} + +p { + margin: 0 0 var(--rw-space-1) 0; + line-height: var(--rw-line-height-normal); + color: var(--rw-text-secondary); + font-size: var(--rw-font-size-sm); } -ul { +p:last-child { + margin-bottom: 0; +} + +ul, ol { margin: 0; padding: 0; + list-style: none; } -li { - cursor: pointer; - margin: 1px 0; +a { + color: var(--rw-text-link); + text-decoration: none; + transition: color var(--rw-transition-fast), text-shadow var(--rw-transition-fast); } -li:hover { - background: rgba(59, 130, 246, 0.5); +a:hover { + color: var(--rw-text-link-hover); + text-shadow: 0 0 6px var(--rw-neon-cyan-glow); +} + +hr { + border: none; + border-top: var(--rw-border-width) solid var(--rw-border-color); + margin: var(--rw-space-3) 0; } input[type="text"], @@ -23,131 +80,455 @@ input[type="email"], input[type="number"], input[type="search"], textarea { - background: rgba(0, 0, 0, 0.5); - border: 1px solid rgba(59, 130, 246, 0.7); - color: white; - font-size: 13px; + background: var(--rw-surface-input); + border: var(--rw-border-width) solid var(--rw-border-color); + border-radius: var(--rw-radius-sm); + color: var(--rw-text-primary); + font-family: var(--rw-font-family); + font-size: var(--rw-font-size-sm); + padding: 6px var(--rw-space-2); outline: none; + transition: border-color var(--rw-transition-fast), box-shadow var(--rw-transition-fast); + line-height: var(--rw-line-height-normal); } -input[type="text"]:focus, -input[type="password"]:focus, -input[type="email"]:focus, -input[type="number"]:focus, -input[type="search"]:focus, -textarea:focus { - border-color: rgba(96, 165, 250, 0.9); - background: rgba(0, 0, 0, 0.4); +input:focus, textarea:focus { + border-color: var(--rw-neon-cyan); + box-shadow: 0 0 6px var(--rw-neon-cyan-glow); } -input[type="text"]::placeholder, -input[type="password"]::placeholder, -input[type="email"]::placeholder, -input[type="number"]::placeholder, -input[type="search"]::placeholder, -textarea::placeholder { - color: rgba(255, 255, 255, 0.5); +input::placeholder, textarea::placeholder { + color: var(--rw-text-muted); } +/* ── Select ── */ select { - background: rgba(0, 0, 0, 0.5); - border: 1px solid rgba(59, 130, 246, 0.7); - color: white; - font-size: 13px; + background: var(--rw-surface-input); + border: var(--rw-border-width) solid var(--rw-border-color); + border-radius: var(--rw-radius-sm); + color: var(--rw-text-primary); + font-family: var(--rw-font-family); + font-size: var(--rw-font-size-sm); + padding: 6px var(--rw-space-2); outline: none; cursor: pointer; + transition: border-color var(--rw-transition-fast), box-shadow var(--rw-transition-fast); } - select:focus { - border-color: rgba(96, 165, 250, 0.5); - background: rgba(0, 0, 0, 0.8); + border-color: var(--rw-neon-cyan); + box-shadow: 0 0 6px var(--rw-neon-cyan-glow); } select option { - background: rgba(0, 0, 0, 0.5); - color: white; + background: var(--rw-bg-panel); + color: var(--rw-text-primary); } button { - background: #3b82f6; - border: 1px solid rgba(59, 130, 246, 0.5); - color: white; - font-size: 13px; + display: inline-flex; + align-items: center; + justify-content: center; + gap: var(--rw-space-1); + background: rgba(0, 229, 255, 0.12); + border: 1px solid var(--rw-border-color-strong); + border-radius: var(--rw-radius-sm); + color: var(--rw-neon-cyan); + font-family: var(--rw-font-family); + font-size: var(--rw-font-size-sm); + font-weight: var(--rw-font-weight-medium); + padding: 6px var(--rw-space-3); cursor: pointer; outline: none; + transition: all var(--rw-transition-fast); + line-height: var(--rw-line-height-normal); + white-space: nowrap; + text-shadow: 0 0 4px var(--rw-neon-cyan-glow); } - button:hover { - background: #2563eb; + background: rgba(0, 229, 255, 0.2); + border-color: var(--rw-neon-cyan); + box-shadow: var(--rw-glow-cyan); } button:active { - background: #2563eb; + background: rgba(0, 229, 255, 0.28); } +button:focus-visible { + box-shadow: 0 0 0 2px var(--rw-neon-cyan-glow-strong); +} button:disabled { - background: rgba(0, 0, 0, 0.5); - border-color: rgba(255, 255, 255, 0.2); - color: rgba(255, 255, 255, 0.5); + background: rgba(255, 255, 255, 0.04); + border-color: var(--rw-border-color-subtle); + color: var(--rw-text-muted); cursor: not-allowed; + text-shadow: none; } button.primary { - background: #3b82f6; - font-weight: 600; + background: rgba(0, 229, 255, 0.2); + font-weight: var(--rw-font-weight-semibold); + border-color: var(--rw-neon-cyan); +} + +button.primary:hover { + background: rgba(0, 229, 255, 0.3); + box-shadow: var(--rw-glow-cyan-strong); } button.secondary { - background: rgba(0, 0, 0, 0.7); - border: 1px solid rgba(59, 130, 246, 0.7); + background: var(--rw-surface-input); + border-color: var(--rw-border-color); + color: var(--rw-text-secondary); + text-shadow: none; } button.secondary:hover { - background: rgba(59, 130, 246, 0.4); + background: var(--rw-bg-hover); + border-color: var(--rw-border-color-strong); } -input[type="checkbox"] { - accent-color: #3b82f6; - width: 14px; - height: 14px; +button.danger { + background: rgba(255, 64, 129, 0.12); + border-color: rgba(255, 64, 129, 0.35); + color: var(--rw-neon-magenta); + text-shadow: 0 0 4px rgba(255, 64, 129, 0.3); +} + +button.danger:hover { + background: rgba(255, 64, 129, 0.22); + box-shadow: var(--rw-glow-magenta); +} + +/* ── Accent Color Grid (Settings Panel) ── */ +.accent-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 6px; + width: 100%; +} + +.accent-btn { + display: flex; + align-items: center; + gap: 5px; + padding: 5px 8px; + background: var(--rw-surface-input); + border: 1px solid var(--rw-border-color); + border-radius: var(--rw-radius-sm); cursor: pointer; + color: var(--rw-text-secondary); + font-size: 11px; + transition: all var(--rw-transition-fast); + text-shadow: none; } -input[type="radio"] { - accent-color: #3b82f6; - width: 14px; - height: 14px; +.accent-btn:hover { + border-color: var(--rw-border-color-strong); + color: var(--rw-text-primary); +} + +.accent-btn.active { + background: rgba(0, 229, 255, 0.12); + border-color: var(--rw-neon-cyan); + color: var(--rw-neon-cyan); + box-shadow: 0 0 8px var(--rw-neon-cyan-glow); +} + +.accent-dot { + display: inline-block; + width: 10px; + height: 10px; + border-radius: 50%; + flex-shrink: 0; + box-shadow: 0 0 4px currentColor; +} + +.accent-btn.active .accent-dot { + box-shadow: 0 0 8px currentColor; +} + +.accent-label { + font-size: 11px; + font-weight: var(--rw-font-weight-medium); +} + +button.small { + font-size: var(--rw-font-size-xs); + padding: 3px var(--rw-space-2); +} + +/* ── Toggle Switch — cyber neon toggle for all menus ── */ +.toggle-row { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + cursor: pointer; + font-size: var(--rw-font-size-sm); + color: var(--rw-text-secondary); + transition: all var(--rw-transition-fast); + gap: var(--rw-space-3); + padding: 5px 0; +} + +.toggle-row:hover { + color: var(--rw-neon-cyan); +} + +.toggle-row:has(input:checked) { + color: var(--rw-text-primary); +} + +.toggle-switch { + position: relative; + display: inline-block; + width: 40px; + height: 22px; + flex-shrink: 0; +} + +.toggle-switch input { + opacity: 0; + width: 0; + height: 0; + position: absolute; +} + +.toggle-slider { + position: absolute; cursor: pointer; + inset: 0; + background: rgba(255, 255, 255, 0.06); + border: 1px solid var(--rw-border-color-strong); + border-radius: 11px; + transition: all 0.25s ease; +} + +.toggle-slider::before { + content: ''; + position: absolute; + height: 16px; + width: 16px; + left: 2px; + bottom: 2px; + background: var(--rw-text-muted); + border-radius: 50%; + transition: all 0.25s cubic-bezier(0.22, 0.61, 0.36, 1); +} + +.toggle-switch input:checked + .toggle-slider { + background: rgba(0, 229, 255, 0.15); + border-color: var(--rw-neon-cyan); + box-shadow: 0 0 10px var(--rw-neon-cyan-glow), inset 0 0 8px rgba(0, 229, 255, 0.08); +} + +.toggle-switch input:checked + .toggle-slider::before { + background: var(--rw-neon-cyan); + transform: translateX(18px); + box-shadow: 0 0 8px var(--rw-neon-cyan-glow); } +.toggle-switch:hover input:not(:checked) + .toggle-slider { + border-color: var(--rw-neon-cyan); + box-shadow: 0 0 6px rgba(0, 229, 255, 0.15); +} + +/* ── Toggle row group (for dense checkbox lists) ── */ +.toggle-row-group { + display: flex; + flex-direction: column; + margin-bottom: var(--rw-space-3); +} + +.toggle-row-group .toggle-row { + padding: 6px var(--rw-space-2); + border-radius: var(--rw-radius-sm); + border: 1px solid transparent; +} + +.toggle-row-group .toggle-row:hover { + background: var(--rw-bg-hover); + border-color: var(--rw-border-color); +} + +.toggle-row-group .toggle-row:has(input:checked) { + background: rgba(0, 229, 255, 0.04); + border-color: rgba(0, 229, 255, 0.08); +} + +/* ── Labels ── */ label { - color: white; - font-size: 13px; - margin-right: 6px; + color: var(--rw-text-primary); + font-size: var(--rw-font-size-sm); cursor: pointer; + display: inline-flex; + align-items: center; + gap: var(--rw-space-2); } +/* ── Form Groups (grid-based for alignment) ── */ .form-group { - margin-bottom: 8px; + display: grid; + grid-template-columns: 140px 1fr; + align-items: center; + gap: var(--rw-space-2) var(--rw-space-3); + margin-bottom: var(--rw-space-2); + min-height: 30px; } -.form-group label { - display: block; - margin-bottom: 2px; +.form-group > label:first-child { + font-weight: var(--rw-font-weight-medium); + color: var(--rw-text-secondary); + justify-content: flex-end; + text-align: right; + font-size: var(--rw-font-size-sm); } -a { - color: #60a5fa; - text-decoration: none; +.form-group > .form-control { + display: flex; + align-items: center; + gap: var(--rw-space-2); } -a:hover { - color: #93c5fd; - text-decoration: underline; +/* ── Form Section ── */ +.form-section { + margin-bottom: var(--rw-space-4); } -hr { +.form-section-title { + font-size: var(--rw-font-size-xs); + font-weight: var(--rw-font-weight-semibold); + color: var(--rw-neon-cyan); + text-transform: uppercase; + letter-spacing: 1px; + padding-bottom: var(--rw-space-1); + margin-bottom: var(--rw-space-2); + border-bottom: 1px solid var(--rw-border-color); + text-shadow: 0 0 4px var(--rw-neon-cyan-glow); +} + +/* ── Slider (range input) ── */ +input[type="range"] { + -webkit-appearance: none; + appearance: none; + width: 100%; + height: 4px; + background: var(--rw-border-color); + border-radius: var(--rw-radius-full); + outline: none; + cursor: pointer; +} + +input[type="range"]::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 14px; + height: 14px; + border-radius: 50%; + background: var(--rw-neon-cyan); + box-shadow: 0 0 8px var(--rw-neon-cyan-glow); + cursor: pointer; border: none; - border-top: 1px solid rgba(59, 130, 246, 0.5); - margin: 4px 0; + transition: box-shadow var(--rw-transition-fast); +} + +input[type="range"]::-webkit-slider-thumb:hover { + box-shadow: 0 0 14px var(--rw-neon-cyan-glow-strong); +} + +/* ── Scrollbar ── */ +::-webkit-scrollbar { + width: 5px; + height: 5px; +} + +::-webkit-scrollbar-track { + background: transparent; +} + +::-webkit-scrollbar-thumb { + background: rgba(0, 229, 255, 0.18); + border-radius: var(--rw-radius-full); +} + +::-webkit-scrollbar-thumb:hover { + background: rgba(0, 229, 255, 0.35); +} + +/* ── Utility Classes ── */ +.rw-text-muted { + color: var(--rw-text-muted); +} + +.rw-text-secondary { + color: var(--rw-text-secondary); +} + +.rw-text-success { + color: var(--rw-color-success); +} + +.rw-text-error { + color: var(--rw-color-error); +} + +.rw-text-warning { + color: var(--rw-color-warning); +} + +.rw-text-cyan { + color: var(--rw-neon-cyan); +} + +.rw-mt-1 { + margin-top: var(--rw-space-1); +} + +.rw-mt-2 { + margin-top: var(--rw-space-2); +} + +.rw-mt-3 { + margin-top: var(--rw-space-3); +} + +.rw-mb-1 { + margin-bottom: var(--rw-space-1); +} + +.rw-mb-2 { + margin-bottom: var(--rw-space-2); +} + +.rw-mb-3 { + margin-bottom: var(--rw-space-3); +} + +.rw-flex { + display: flex; +} + +.rw-flex-col { + flex-direction: column; +} + +.rw-items-center { + align-items: center; +} + +.rw-gap-1 { + gap: var(--rw-space-1); +} + +.rw-gap-2 { + gap: var(--rw-space-2); +} + +.rw-gap-3 { + gap: var(--rw-space-3); +} + +.rw-full-width { + width: 100%; } diff --git a/src/main.tsx b/src/main.tsx index 82b24db..a20211d 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,5 +1,6 @@ import React, {StrictMode} from 'react' import {createRoot} from 'react-dom/client' +import './theme.css' // Design tokens — MUST load before all other styles import './index.css' import './Layer.css' import {setShadowRoot} from './shadow-style.ts' diff --git a/src/modules/chat_export/page.tsx b/src/modules/chat_export/page.tsx index 832a4f0..1af1335 100644 --- a/src/modules/chat_export/page.tsx +++ b/src/modules/chat_export/page.tsx @@ -4,9 +4,12 @@ import {extract} from "./extractor.ts"; export default function ChatExportPage() { const [includePrivate, setIncludePrivate] = useState(false); return (<> -
) + } + + private historyUpdateListener = () => { + this.forceUpdate() + } + private reverntToHere(entry: HistoryEntry) { if (!entry.fully) { console.warn("Cannot revert to an entry without full appearance data"); @@ -106,38 +149,4 @@ export default class HistoryEntryList extends React.Component -

history

-
-
    - {module.history.map((entry, index) => ( -
  • this.setState({previewEntry: entry})} - onMouseLeave={() => this.state.previewEntry === entry && this.setState({previewEntry: undefined})} - > - {entry.reason.length > 0 ? entry.reason[0].text : 'unknown reason'} {new Date(entry.timestamp).toLocaleString()} -
    - {Object.entries(entry.added).length > 0 && {Object.entries(entry.added).length}+} - {Object.entries(entry.removed).length > 0 && {Object.entries(entry.removed).length}-} - {Object.entries(entry.changed).length > 0 && {Object.entries(entry.changed).length}±} -
    - - - -
  • - ))} -
-
- - {this.state.previewEntry && <> - {Object.entries(this.state.previewEntry.added).length > 0 &&

Added: {Object.entries(this.state.previewEntry.added).map(([, v]) => v.Name).join(", ")}

} - {Object.entries(this.state.previewEntry.removed).length > 0 &&

Removed: {Object.entries(this.state.previewEntry.removed).map(([, v]) => v.Name).join(", ")}

} - {Object.entries(this.state.previewEntry.changed).length > 0 &&

Changed: {Object.entries(this.state.previewEntry.changed).map(([, {old, new: _new}]) => `${old.Name} => ${_new.Name}`).join(", ")}

} - } -
-
-
) - } } diff --git a/src/modules/privacy/page.tsx b/src/modules/privacy/page.tsx index 3040449..a1470ef 100644 --- a/src/modules/privacy/page.tsx +++ b/src/modules/privacy/page.tsx @@ -49,7 +49,7 @@ export default function PrivacyPage() { setHiddenMods(newHiddenMods); module.hiddenMods = newHiddenMods; module.saveConfig(); - setNewModName(""); // 清空输入框 + setNewModName(""); } }; @@ -61,106 +61,96 @@ export default function PrivacyPage() { }; return (<> - WCE -
-