refactor: batch 1

This commit is contained in:
鲁树人 2025-05-17 05:59:39 +09:00
parent 089d66cbf4
commit 13c669b4ea
23 changed files with 828 additions and 533 deletions

View File

@ -1,7 +1,8 @@
{
"recommendations": [
"editorconfig.editorconfig",
"bradlc.vscode-tailwindcss",
"dbaeumer.vscode-eslint",
"editorconfig.editorconfig",
"esbenp.prettier-vscode",
"foxundermoon.shell-format"
]

View File

@ -24,6 +24,7 @@
"@emotion/styled": "^11.14.0",
"@reduxjs/toolkit": "^2.8.2",
"@unlock-music/crypto": "0.1.10",
"classnames": "^2.5.1",
"framer-motion": "^12.12.1",
"nanoid": "^5.1.5",
"next-themes": "^0.4.6",
@ -32,8 +33,8 @@
"react-dom": "^19.1.0",
"react-dropzone": "^14.3.8",
"react-icons": "^5.5.0",
"react-promise-suspense": "^0.3.4",
"react-redux": "^9.2.0",
"react-router": "^7.6.0",
"react-syntax-highlighter": "^15.6.1",
"sass": "^1.89.0",
"sql.js": "^1.13.0",
@ -42,6 +43,7 @@
"devDependencies": {
"@eslint/js": "^9.26.0",
"@rollup/plugin-replace": "^6.0.2",
"@tailwindcss/vite": "^4.1.7",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^14.6.1",
@ -67,6 +69,7 @@
"prettier": "^3.5.3",
"rollup": "^4.40.2",
"simple-git-hooks": "^2.13.0",
"tailwindcss": "^4.1.7",
"terser": "^5.39.2",
"typescript": "^5.8.3",
"typescript-eslint": "^8.32.1",

535
pnpm-lock.yaml generated
View File

@ -41,6 +41,9 @@ importers:
'@unlock-music/crypto':
specifier: 0.1.10
version: 0.1.10
classnames:
specifier: ^2.5.1
version: 2.5.1
framer-motion:
specifier: ^12.12.1
version: 12.12.1(@emotion/is-prop-valid@1.3.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
@ -65,12 +68,12 @@ importers:
react-icons:
specifier: ^5.5.0
version: 5.5.0(react@19.1.0)
react-promise-suspense:
specifier: ^0.3.4
version: 0.3.4
react-redux:
specifier: ^9.2.0
version: 9.2.0(@types/react@19.1.4)(react@19.1.0)(redux@5.0.1)
react-router:
specifier: ^7.6.0
version: 7.6.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
react-syntax-highlighter:
specifier: ^15.6.1
version: 15.6.1(react@19.1.0)
@ -90,6 +93,9 @@ importers:
'@rollup/plugin-replace':
specifier: ^6.0.2
version: 6.0.2(rollup@4.40.2)
'@tailwindcss/vite':
specifier: ^4.1.7
version: 4.1.7(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0))
'@testing-library/jest-dom':
specifier: ^6.6.3
version: 6.6.3
@ -116,13 +122,13 @@ importers:
version: 1.4.9
'@typescript-eslint/eslint-plugin':
specifier: ^8.32.1
version: 8.32.1(@typescript-eslint/parser@8.32.1(eslint@9.26.0)(typescript@5.8.3))(eslint@9.26.0)(typescript@5.8.3)
version: 8.32.1(@typescript-eslint/parser@8.32.1(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3)
'@typescript-eslint/parser':
specifier: ^8.32.1
version: 8.32.1(eslint@9.26.0)(typescript@5.8.3)
version: 8.32.1(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3)
'@vitejs/plugin-react':
specifier: ^4.4.1
version: 4.4.1(vite@6.3.5(@types/node@22.15.18)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0))
version: 4.4.1(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0))
'@vitest/coverage-v8':
specifier: ^3.1.3
version: 3.1.3(vitest@3.1.3)
@ -134,16 +140,16 @@ importers:
version: 5.0.35
eslint:
specifier: ^9.26.0
version: 9.26.0
version: 9.26.0(jiti@2.4.2)
eslint-config-prettier:
specifier: ^10.1.5
version: 10.1.5(eslint@9.26.0)
version: 10.1.5(eslint@9.26.0(jiti@2.4.2))
eslint-plugin-react-hooks:
specifier: ^5.2.0
version: 5.2.0(eslint@9.26.0)
version: 5.2.0(eslint@9.26.0(jiti@2.4.2))
eslint-plugin-react-refresh:
specifier: ^0.4.20
version: 0.4.20(eslint@9.26.0)
version: 0.4.20(eslint@9.26.0(jiti@2.4.2))
globals:
specifier: ^16.1.0
version: 16.1.0
@ -165,6 +171,9 @@ importers:
simple-git-hooks:
specifier: ^2.13.0
version: 2.13.0
tailwindcss:
specifier: ^4.1.7
version: 4.1.7
terser:
specifier: ^5.39.2
version: 5.39.2
@ -173,22 +182,22 @@ importers:
version: 5.8.3
typescript-eslint:
specifier: ^8.32.1
version: 8.32.1(eslint@9.26.0)(typescript@5.8.3)
version: 8.32.1(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3)
vite:
specifier: ^6.3.5
version: 6.3.5(@types/node@22.15.18)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0)
version: 6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0)
vite-plugin-pwa:
specifier: ^1.0.0
version: 1.0.0(vite@6.3.5(@types/node@22.15.18)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0))(workbox-build@7.3.0(@types/babel__core@7.20.5))(workbox-window@7.3.0)
version: 1.0.0(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0))(workbox-build@7.3.0(@types/babel__core@7.20.5))(workbox-window@7.3.0)
vite-plugin-top-level-await:
specifier: ^1.5.0
version: 1.5.0(@swc/helpers@0.5.17)(rollup@4.40.2)(vite@6.3.5(@types/node@22.15.18)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0))
version: 1.5.0(@swc/helpers@0.5.17)(rollup@4.40.2)(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0))
vite-plugin-wasm:
specifier: ^3.4.1
version: 3.4.1(vite@6.3.5(@types/node@22.15.18)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0))
version: 3.4.1(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0))
vitest:
specifier: ^3.1.3
version: 3.1.3(@types/node@22.15.18)(@vitest/ui@3.1.3)(jsdom@26.1.0)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0)
version: 3.1.3(@types/node@22.15.18)(@vitest/ui@3.1.3)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0)
workbox-window:
specifier: ^7.3.0
version: 7.3.0
@ -1050,6 +1059,10 @@ packages:
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
engines: {node: '>=12'}
'@isaacs/fs-minipass@4.0.1':
resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==}
engines: {node: '>=18.0.0'}
'@istanbuljs/schema@0.1.3':
resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==}
engines: {node: '>=8'}
@ -1451,6 +1464,96 @@ packages:
'@swc/types@0.1.21':
resolution: {integrity: sha512-2YEtj5HJVbKivud9N4bpPBAyZhj4S2Ipe5LkUG94alTpr7in/GU/EARgPAd3BwU+YOmFVJC2+kjqhGRi3r0ZpQ==}
'@tailwindcss/node@4.1.7':
resolution: {integrity: sha512-9rsOpdY9idRI2NH6CL4wORFY0+Q6fnx9XP9Ju+iq/0wJwGD5IByIgFmwVbyy4ymuyprj8Qh4ErxMKTUL4uNh3g==}
'@tailwindcss/oxide-android-arm64@4.1.7':
resolution: {integrity: sha512-IWA410JZ8fF7kACus6BrUwY2Z1t1hm0+ZWNEzykKmMNM09wQooOcN/VXr0p/WJdtHZ90PvJf2AIBS/Ceqx1emg==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [android]
'@tailwindcss/oxide-darwin-arm64@4.1.7':
resolution: {integrity: sha512-81jUw9To7fimGGkuJ2W5h3/oGonTOZKZ8C2ghm/TTxbwvfSiFSDPd6/A/KE2N7Jp4mv3Ps9OFqg2fEKgZFfsvg==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [darwin]
'@tailwindcss/oxide-darwin-x64@4.1.7':
resolution: {integrity: sha512-q77rWjEyGHV4PdDBtrzO0tgBBPlQWKY7wZK0cUok/HaGgbNKecegNxCGikuPJn5wFAlIywC3v+WMBt0PEBtwGw==}
engines: {node: '>= 10'}
cpu: [x64]
os: [darwin]
'@tailwindcss/oxide-freebsd-x64@4.1.7':
resolution: {integrity: sha512-RfmdbbK6G6ptgF4qqbzoxmH+PKfP4KSVs7SRlTwcbRgBwezJkAO3Qta/7gDy10Q2DcUVkKxFLXUQO6J3CRvBGw==}
engines: {node: '>= 10'}
cpu: [x64]
os: [freebsd]
'@tailwindcss/oxide-linux-arm-gnueabihf@4.1.7':
resolution: {integrity: sha512-OZqsGvpwOa13lVd1z6JVwQXadEobmesxQ4AxhrwRiPuE04quvZHWn/LnihMg7/XkN+dTioXp/VMu/p6A5eZP3g==}
engines: {node: '>= 10'}
cpu: [arm]
os: [linux]
'@tailwindcss/oxide-linux-arm64-gnu@4.1.7':
resolution: {integrity: sha512-voMvBTnJSfKecJxGkoeAyW/2XRToLZ227LxswLAwKY7YslG/Xkw9/tJNH+3IVh5bdYzYE7DfiaPbRkSHFxY1xA==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
'@tailwindcss/oxide-linux-arm64-musl@4.1.7':
resolution: {integrity: sha512-PjGuNNmJeKHnP58M7XyjJyla8LPo+RmwHQpBI+W/OxqrwojyuCQ+GUtygu7jUqTEexejZHr/z3nBc/gTiXBj4A==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
'@tailwindcss/oxide-linux-x64-gnu@4.1.7':
resolution: {integrity: sha512-HMs+Va+ZR3gC3mLZE00gXxtBo3JoSQxtu9lobbZd+DmfkIxR54NO7Z+UQNPsa0P/ITn1TevtFxXTpsRU7qEvWg==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
'@tailwindcss/oxide-linux-x64-musl@4.1.7':
resolution: {integrity: sha512-MHZ6jyNlutdHH8rd+YTdr3QbXrHXqwIhHw9e7yXEBcQdluGwhpQY2Eku8UZK6ReLaWtQ4gijIv5QoM5eE+qlsA==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
'@tailwindcss/oxide-wasm32-wasi@4.1.7':
resolution: {integrity: sha512-ANaSKt74ZRzE2TvJmUcbFQ8zS201cIPxUDm5qez5rLEwWkie2SkGtA4P+GPTj+u8N6JbPrC8MtY8RmJA35Oo+A==}
engines: {node: '>=14.0.0'}
cpu: [wasm32]
bundledDependencies:
- '@napi-rs/wasm-runtime'
- '@emnapi/core'
- '@emnapi/runtime'
- '@tybys/wasm-util'
- '@emnapi/wasi-threads'
- tslib
'@tailwindcss/oxide-win32-arm64-msvc@4.1.7':
resolution: {integrity: sha512-HUiSiXQ9gLJBAPCMVRk2RT1ZrBjto7WvqsPBwUrNK2BcdSxMnk19h4pjZjI7zgPhDxlAbJSumTC4ljeA9y0tEw==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [win32]
'@tailwindcss/oxide-win32-x64-msvc@4.1.7':
resolution: {integrity: sha512-rYHGmvoHiLJ8hWucSfSOEmdCBIGZIq7SpkPRSqLsH2Ab2YUNgKeAPT1Fi2cx3+hnYOrAb0jp9cRyode3bBW4mQ==}
engines: {node: '>= 10'}
cpu: [x64]
os: [win32]
'@tailwindcss/oxide@4.1.7':
resolution: {integrity: sha512-5SF95Ctm9DFiUyjUPnDGkoKItPX/k+xifcQhcqX5RA85m50jw1pT/KzjdvlqxRja45Y52nR4MR9fD1JYd7f8NQ==}
engines: {node: '>= 10'}
'@tailwindcss/vite@4.1.7':
resolution: {integrity: sha512-tYa2fO3zDe41I7WqijyVbRd8oWT0aEID1Eokz5hMT6wShLIHj3yvwj9XbfuloHP9glZ6H+aG2AN/+ZrxJ1Y5RQ==}
peerDependencies:
vite: ^5.2.0 || ^6
'@testing-library/dom@10.4.0':
resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==}
engines: {node: '>=18'}
@ -1853,6 +1956,13 @@ packages:
resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
engines: {node: '>= 14.16.0'}
chownr@3.0.0:
resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==}
engines: {node: '>=18'}
classnames@2.5.1:
resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==}
cli-cursor@5.0.0:
resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==}
engines: {node: '>=18'}
@ -1913,6 +2023,10 @@ packages:
resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==}
engines: {node: '>= 0.6'}
cookie@1.0.2:
resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==}
engines: {node: '>=18'}
copy-to-clipboard@3.3.3:
resolution: {integrity: sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==}
@ -2008,6 +2122,10 @@ packages:
engines: {node: '>=0.10'}
hasBin: true
detect-libc@2.0.4:
resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==}
engines: {node: '>=8'}
detect-node-es@1.1.0:
resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==}
@ -2048,6 +2166,10 @@ packages:
resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==}
engines: {node: '>= 0.8'}
enhanced-resolve@5.18.1:
resolution: {integrity: sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==}
engines: {node: '>=10.13.0'}
entities@6.0.0:
resolution: {integrity: sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw==}
engines: {node: '>=0.12'}
@ -2199,9 +2321,6 @@ packages:
resolution: {integrity: sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==}
engines: {node: '>= 18'}
fast-deep-equal@2.0.1:
resolution: {integrity: sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==}
fast-deep-equal@3.1.3:
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
@ -2688,6 +2807,10 @@ packages:
engines: {node: '>=10'}
hasBin: true
jiti@2.4.2:
resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==}
hasBin: true
js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
@ -2755,6 +2878,70 @@ packages:
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
engines: {node: '>= 0.8.0'}
lightningcss-darwin-arm64@1.30.1:
resolution: {integrity: sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [darwin]
lightningcss-darwin-x64@1.30.1:
resolution: {integrity: sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [darwin]
lightningcss-freebsd-x64@1.30.1:
resolution: {integrity: sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [freebsd]
lightningcss-linux-arm-gnueabihf@1.30.1:
resolution: {integrity: sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==}
engines: {node: '>= 12.0.0'}
cpu: [arm]
os: [linux]
lightningcss-linux-arm64-gnu@1.30.1:
resolution: {integrity: sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [linux]
lightningcss-linux-arm64-musl@1.30.1:
resolution: {integrity: sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [linux]
lightningcss-linux-x64-gnu@1.30.1:
resolution: {integrity: sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [linux]
lightningcss-linux-x64-musl@1.30.1:
resolution: {integrity: sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [linux]
lightningcss-win32-arm64-msvc@1.30.1:
resolution: {integrity: sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [win32]
lightningcss-win32-x64-msvc@1.30.1:
resolution: {integrity: sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [win32]
lightningcss@1.30.1:
resolution: {integrity: sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==}
engines: {node: '>= 12.0.0'}
lilconfig@3.1.3:
resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==}
engines: {node: '>=14'}
@ -2878,6 +3065,15 @@ packages:
resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
engines: {node: '>=16 || 14 >=14.17'}
minizlib@3.0.2:
resolution: {integrity: sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==}
engines: {node: '>= 18'}
mkdirp@3.0.1:
resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==}
engines: {node: '>=10'}
hasBin: true
motion-dom@12.12.1:
resolution: {integrity: sha512-GXq/uUbZBEiFFE+K1Z/sxdPdadMdfJ/jmBALDfIuHGi0NmtealLOfH9FqT+6aNPgVx8ilq0DtYmyQlo6Uj9LKQ==}
@ -3157,9 +3353,6 @@ packages:
react-is@17.0.2:
resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==}
react-promise-suspense@0.3.4:
resolution: {integrity: sha512-I42jl7L3Ze6kZaq+7zXWSunBa3b1on5yfvUW6Eo/3fFOj6dZ5Bqmcd264nJbTK/gn1HjjILAjSwnZbV4RpSaNQ==}
react-redux@9.2.0:
resolution: {integrity: sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==}
peerDependencies:
@ -3196,6 +3389,16 @@ packages:
'@types/react':
optional: true
react-router@7.6.0:
resolution: {integrity: sha512-GGufuHIVCJDbnIAXP3P9Sxzq3UUsddG3rrI3ut1q6m0FI6vxVBF3JoPQ38+W/blslLH4a5Yutp8drkEpXoddGQ==}
engines: {node: '>=20.0.0'}
peerDependencies:
react: '>=18'
react-dom: '>=18'
peerDependenciesMeta:
react-dom:
optional: true
react-style-singleton@2.2.3:
resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==}
engines: {node: '>=10'}
@ -3357,6 +3560,9 @@ packages:
resolution: {integrity: sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==}
engines: {node: '>= 18'}
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'}
@ -3527,6 +3733,17 @@ packages:
symbol-tree@3.2.4:
resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
tailwindcss@4.1.7:
resolution: {integrity: sha512-kr1o/ErIdNhTz8uzAYL7TpaUuzKIE6QPQ4qmSdxnoX/lo+5wmUHQA6h3L5yIqEImSRnAAURDirLu/BgiXGPAhg==}
tapable@2.2.1:
resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==}
engines: {node: '>=6'}
tar@7.4.3:
resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==}
engines: {node: '>=18'}
temp-dir@2.0.0:
resolution: {integrity: sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==}
engines: {node: '>=8'}
@ -3973,6 +4190,10 @@ packages:
yallist@3.1.1:
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
yallist@5.0.0:
resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==}
engines: {node: '>=18'}
yaml@1.10.2:
resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==}
engines: {node: '>= 6'}
@ -4913,9 +5134,9 @@ snapshots:
'@esbuild/win32-x64@0.25.4':
optional: true
'@eslint-community/eslint-utils@4.7.0(eslint@9.26.0)':
'@eslint-community/eslint-utils@4.7.0(eslint@9.26.0(jiti@2.4.2))':
dependencies:
eslint: 9.26.0
eslint: 9.26.0(jiti@2.4.2)
eslint-visitor-keys: 3.4.3
'@eslint-community/regexpp@4.12.1': {}
@ -4979,6 +5200,10 @@ snapshots:
wrap-ansi: 8.1.0
wrap-ansi-cjs: wrap-ansi@7.0.0
'@isaacs/fs-minipass@4.0.1':
dependencies:
minipass: 7.1.2
'@istanbuljs/schema@0.1.3': {}
'@jridgewell/gen-mapping@0.3.8':
@ -5310,6 +5535,77 @@ snapshots:
dependencies:
'@swc/counter': 0.1.3
'@tailwindcss/node@4.1.7':
dependencies:
'@ampproject/remapping': 2.3.0
enhanced-resolve: 5.18.1
jiti: 2.4.2
lightningcss: 1.30.1
magic-string: 0.30.17
source-map-js: 1.2.1
tailwindcss: 4.1.7
'@tailwindcss/oxide-android-arm64@4.1.7':
optional: true
'@tailwindcss/oxide-darwin-arm64@4.1.7':
optional: true
'@tailwindcss/oxide-darwin-x64@4.1.7':
optional: true
'@tailwindcss/oxide-freebsd-x64@4.1.7':
optional: true
'@tailwindcss/oxide-linux-arm-gnueabihf@4.1.7':
optional: true
'@tailwindcss/oxide-linux-arm64-gnu@4.1.7':
optional: true
'@tailwindcss/oxide-linux-arm64-musl@4.1.7':
optional: true
'@tailwindcss/oxide-linux-x64-gnu@4.1.7':
optional: true
'@tailwindcss/oxide-linux-x64-musl@4.1.7':
optional: true
'@tailwindcss/oxide-wasm32-wasi@4.1.7':
optional: true
'@tailwindcss/oxide-win32-arm64-msvc@4.1.7':
optional: true
'@tailwindcss/oxide-win32-x64-msvc@4.1.7':
optional: true
'@tailwindcss/oxide@4.1.7':
dependencies:
detect-libc: 2.0.4
tar: 7.4.3
optionalDependencies:
'@tailwindcss/oxide-android-arm64': 4.1.7
'@tailwindcss/oxide-darwin-arm64': 4.1.7
'@tailwindcss/oxide-darwin-x64': 4.1.7
'@tailwindcss/oxide-freebsd-x64': 4.1.7
'@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.7
'@tailwindcss/oxide-linux-arm64-gnu': 4.1.7
'@tailwindcss/oxide-linux-arm64-musl': 4.1.7
'@tailwindcss/oxide-linux-x64-gnu': 4.1.7
'@tailwindcss/oxide-linux-x64-musl': 4.1.7
'@tailwindcss/oxide-wasm32-wasi': 4.1.7
'@tailwindcss/oxide-win32-arm64-msvc': 4.1.7
'@tailwindcss/oxide-win32-x64-msvc': 4.1.7
'@tailwindcss/vite@4.1.7(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0))':
dependencies:
'@tailwindcss/node': 4.1.7
'@tailwindcss/oxide': 4.1.7
tailwindcss: 4.1.7
vite: 6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0)
'@testing-library/dom@10.4.0':
dependencies:
'@babel/code-frame': 7.27.1
@ -5417,15 +5713,15 @@ snapshots:
'@types/use-sync-external-store@0.0.6': {}
'@typescript-eslint/eslint-plugin@8.32.1(@typescript-eslint/parser@8.32.1(eslint@9.26.0)(typescript@5.8.3))(eslint@9.26.0)(typescript@5.8.3)':
'@typescript-eslint/eslint-plugin@8.32.1(@typescript-eslint/parser@8.32.1(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3)':
dependencies:
'@eslint-community/regexpp': 4.12.1
'@typescript-eslint/parser': 8.32.1(eslint@9.26.0)(typescript@5.8.3)
'@typescript-eslint/parser': 8.32.1(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3)
'@typescript-eslint/scope-manager': 8.32.1
'@typescript-eslint/type-utils': 8.32.1(eslint@9.26.0)(typescript@5.8.3)
'@typescript-eslint/utils': 8.32.1(eslint@9.26.0)(typescript@5.8.3)
'@typescript-eslint/type-utils': 8.32.1(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3)
'@typescript-eslint/utils': 8.32.1(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3)
'@typescript-eslint/visitor-keys': 8.32.1
eslint: 9.26.0
eslint: 9.26.0(jiti@2.4.2)
graphemer: 1.4.0
ignore: 7.0.4
natural-compare: 1.4.0
@ -5434,14 +5730,14 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/parser@8.32.1(eslint@9.26.0)(typescript@5.8.3)':
'@typescript-eslint/parser@8.32.1(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3)':
dependencies:
'@typescript-eslint/scope-manager': 8.32.1
'@typescript-eslint/types': 8.32.1
'@typescript-eslint/typescript-estree': 8.32.1(typescript@5.8.3)
'@typescript-eslint/visitor-keys': 8.32.1
debug: 4.4.1
eslint: 9.26.0
eslint: 9.26.0(jiti@2.4.2)
typescript: 5.8.3
transitivePeerDependencies:
- supports-color
@ -5451,12 +5747,12 @@ snapshots:
'@typescript-eslint/types': 8.32.1
'@typescript-eslint/visitor-keys': 8.32.1
'@typescript-eslint/type-utils@8.32.1(eslint@9.26.0)(typescript@5.8.3)':
'@typescript-eslint/type-utils@8.32.1(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3)':
dependencies:
'@typescript-eslint/typescript-estree': 8.32.1(typescript@5.8.3)
'@typescript-eslint/utils': 8.32.1(eslint@9.26.0)(typescript@5.8.3)
'@typescript-eslint/utils': 8.32.1(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3)
debug: 4.4.1
eslint: 9.26.0
eslint: 9.26.0(jiti@2.4.2)
ts-api-utils: 2.1.0(typescript@5.8.3)
typescript: 5.8.3
transitivePeerDependencies:
@ -5478,13 +5774,13 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/utils@8.32.1(eslint@9.26.0)(typescript@5.8.3)':
'@typescript-eslint/utils@8.32.1(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3)':
dependencies:
'@eslint-community/eslint-utils': 4.7.0(eslint@9.26.0)
'@eslint-community/eslint-utils': 4.7.0(eslint@9.26.0(jiti@2.4.2))
'@typescript-eslint/scope-manager': 8.32.1
'@typescript-eslint/types': 8.32.1
'@typescript-eslint/typescript-estree': 8.32.1(typescript@5.8.3)
eslint: 9.26.0
eslint: 9.26.0(jiti@2.4.2)
typescript: 5.8.3
transitivePeerDependencies:
- supports-color
@ -5496,14 +5792,14 @@ snapshots:
'@unlock-music/crypto@0.1.10': {}
'@vitejs/plugin-react@4.4.1(vite@6.3.5(@types/node@22.15.18)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0))':
'@vitejs/plugin-react@4.4.1(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0))':
dependencies:
'@babel/core': 7.27.1
'@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.27.1)
'@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.27.1)
'@types/babel__core': 7.20.5
react-refresh: 0.17.0
vite: 6.3.5(@types/node@22.15.18)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0)
vite: 6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0)
transitivePeerDependencies:
- supports-color
@ -5521,7 +5817,7 @@ snapshots:
std-env: 3.9.0
test-exclude: 7.0.1
tinyrainbow: 2.0.0
vitest: 3.1.3(@types/node@22.15.18)(@vitest/ui@3.1.3)(jsdom@26.1.0)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0)
vitest: 3.1.3(@types/node@22.15.18)(@vitest/ui@3.1.3)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0)
transitivePeerDependencies:
- supports-color
@ -5532,13 +5828,13 @@ snapshots:
chai: 5.2.0
tinyrainbow: 2.0.0
'@vitest/mocker@3.1.3(vite@6.3.5(@types/node@22.15.18)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0))':
'@vitest/mocker@3.1.3(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0))':
dependencies:
'@vitest/spy': 3.1.3
estree-walker: 3.0.3
magic-string: 0.30.17
optionalDependencies:
vite: 6.3.5(@types/node@22.15.18)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0)
vite: 6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0)
'@vitest/pretty-format@3.1.3':
dependencies:
@ -5568,7 +5864,7 @@ snapshots:
sirv: 3.0.1
tinyglobby: 0.2.13
tinyrainbow: 2.0.0
vitest: 3.1.3(@types/node@22.15.18)(@vitest/ui@3.1.3)(jsdom@26.1.0)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0)
vitest: 3.1.3(@types/node@22.15.18)(@vitest/ui@3.1.3)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0)
'@vitest/utils@3.1.3':
dependencies:
@ -5793,6 +6089,10 @@ snapshots:
dependencies:
readdirp: 4.1.2
chownr@3.0.0: {}
classnames@2.5.1: {}
cli-cursor@5.0.0:
dependencies:
restore-cursor: 5.1.0
@ -5836,6 +6136,8 @@ snapshots:
cookie@0.7.2: {}
cookie@1.0.2: {}
copy-to-clipboard@3.3.3:
dependencies:
toggle-selection: 1.0.6
@ -5930,6 +6232,8 @@ snapshots:
detect-libc@1.0.3:
optional: true
detect-libc@2.0.4: {}
detect-node-es@1.1.0: {}
dom-accessibility-api@0.5.16: {}
@ -5960,6 +6264,11 @@ snapshots:
encodeurl@2.0.0: {}
enhanced-resolve@5.18.1:
dependencies:
graceful-fs: 4.2.11
tapable: 2.2.1
entities@6.0.0: {}
environment@1.1.0: {}
@ -6079,17 +6388,17 @@ snapshots:
escape-string-regexp@4.0.0: {}
eslint-config-prettier@10.1.5(eslint@9.26.0):
eslint-config-prettier@10.1.5(eslint@9.26.0(jiti@2.4.2)):
dependencies:
eslint: 9.26.0
eslint: 9.26.0(jiti@2.4.2)
eslint-plugin-react-hooks@5.2.0(eslint@9.26.0):
eslint-plugin-react-hooks@5.2.0(eslint@9.26.0(jiti@2.4.2)):
dependencies:
eslint: 9.26.0
eslint: 9.26.0(jiti@2.4.2)
eslint-plugin-react-refresh@0.4.20(eslint@9.26.0):
eslint-plugin-react-refresh@0.4.20(eslint@9.26.0(jiti@2.4.2)):
dependencies:
eslint: 9.26.0
eslint: 9.26.0(jiti@2.4.2)
eslint-scope@8.3.0:
dependencies:
@ -6100,9 +6409,9 @@ snapshots:
eslint-visitor-keys@4.2.0: {}
eslint@9.26.0:
eslint@9.26.0(jiti@2.4.2):
dependencies:
'@eslint-community/eslint-utils': 4.7.0(eslint@9.26.0)
'@eslint-community/eslint-utils': 4.7.0(eslint@9.26.0(jiti@2.4.2))
'@eslint-community/regexpp': 4.12.1
'@eslint/config-array': 0.20.0
'@eslint/config-helpers': 0.2.2
@ -6139,6 +6448,8 @@ snapshots:
natural-compare: 1.4.0
optionator: 0.9.4
zod: 3.24.4
optionalDependencies:
jiti: 2.4.2
transitivePeerDependencies:
- supports-color
@ -6216,8 +6527,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
fast-deep-equal@2.0.1: {}
fast-deep-equal@3.1.3: {}
fast-glob@3.3.3:
@ -6706,6 +7015,8 @@ snapshots:
filelist: 1.0.4
minimatch: 3.1.2
jiti@2.4.2: {}
js-tokens@4.0.0: {}
js-yaml@4.1.0:
@ -6776,6 +7087,51 @@ snapshots:
prelude-ls: 1.2.1
type-check: 0.4.0
lightningcss-darwin-arm64@1.30.1:
optional: true
lightningcss-darwin-x64@1.30.1:
optional: true
lightningcss-freebsd-x64@1.30.1:
optional: true
lightningcss-linux-arm-gnueabihf@1.30.1:
optional: true
lightningcss-linux-arm64-gnu@1.30.1:
optional: true
lightningcss-linux-arm64-musl@1.30.1:
optional: true
lightningcss-linux-x64-gnu@1.30.1:
optional: true
lightningcss-linux-x64-musl@1.30.1:
optional: true
lightningcss-win32-arm64-msvc@1.30.1:
optional: true
lightningcss-win32-x64-msvc@1.30.1:
optional: true
lightningcss@1.30.1:
dependencies:
detect-libc: 2.0.4
optionalDependencies:
lightningcss-darwin-arm64: 1.30.1
lightningcss-darwin-x64: 1.30.1
lightningcss-freebsd-x64: 1.30.1
lightningcss-linux-arm-gnueabihf: 1.30.1
lightningcss-linux-arm64-gnu: 1.30.1
lightningcss-linux-arm64-musl: 1.30.1
lightningcss-linux-x64-gnu: 1.30.1
lightningcss-linux-x64-musl: 1.30.1
lightningcss-win32-arm64-msvc: 1.30.1
lightningcss-win32-x64-msvc: 1.30.1
lilconfig@3.1.3: {}
lines-and-columns@1.2.4: {}
@ -6900,6 +7256,12 @@ snapshots:
minipass@7.1.2: {}
minizlib@3.0.2:
dependencies:
minipass: 7.1.2
mkdirp@3.0.1: {}
motion-dom@12.12.1:
dependencies:
motion-utils: 12.12.1
@ -7144,10 +7506,6 @@ snapshots:
react-is@17.0.2: {}
react-promise-suspense@0.3.4:
dependencies:
fast-deep-equal: 2.0.1
react-redux@9.2.0(@types/react@19.1.4)(react@19.1.0)(redux@5.0.1):
dependencies:
'@types/use-sync-external-store': 0.0.6
@ -7178,6 +7536,14 @@ snapshots:
optionalDependencies:
'@types/react': 19.1.4
react-router@7.6.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
dependencies:
cookie: 1.0.2
react: 19.1.0
set-cookie-parser: 2.7.1
optionalDependencies:
react-dom: 19.1.0(react@19.1.0)
react-style-singleton@2.2.3(@types/react@19.1.4)(react@19.1.0):
dependencies:
get-nonce: 1.0.1
@ -7395,6 +7761,8 @@ snapshots:
transitivePeerDependencies:
- supports-color
set-cookie-parser@2.7.1: {}
set-function-length@1.2.2:
dependencies:
define-data-property: 1.1.4
@ -7593,6 +7961,19 @@ snapshots:
symbol-tree@3.2.4: {}
tailwindcss@4.1.7: {}
tapable@2.2.1: {}
tar@7.4.3:
dependencies:
'@isaacs/fs-minipass': 4.0.1
chownr: 3.0.0
minipass: 7.1.2
minizlib: 3.0.2
mkdirp: 3.0.1
yallist: 5.0.0
temp-dir@2.0.0: {}
tempy@0.6.0:
@ -7711,12 +8092,12 @@ snapshots:
possible-typed-array-names: 1.1.0
reflect.getprototypeof: 1.0.10
typescript-eslint@8.32.1(eslint@9.26.0)(typescript@5.8.3):
typescript-eslint@8.32.1(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3):
dependencies:
'@typescript-eslint/eslint-plugin': 8.32.1(@typescript-eslint/parser@8.32.1(eslint@9.26.0)(typescript@5.8.3))(eslint@9.26.0)(typescript@5.8.3)
'@typescript-eslint/parser': 8.32.1(eslint@9.26.0)(typescript@5.8.3)
'@typescript-eslint/utils': 8.32.1(eslint@9.26.0)(typescript@5.8.3)
eslint: 9.26.0
'@typescript-eslint/eslint-plugin': 8.32.1(@typescript-eslint/parser@8.32.1(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3)
'@typescript-eslint/parser': 8.32.1(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3)
'@typescript-eslint/utils': 8.32.1(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3)
eslint: 9.26.0(jiti@2.4.2)
typescript: 5.8.3
transitivePeerDependencies:
- supports-color
@ -7786,13 +8167,13 @@ snapshots:
vary@1.1.2: {}
vite-node@3.1.3(@types/node@22.15.18)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0):
vite-node@3.1.3(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0):
dependencies:
cac: 6.7.14
debug: 4.4.1
es-module-lexer: 1.7.0
pathe: 2.0.3
vite: 6.3.5(@types/node@22.15.18)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0)
vite: 6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0)
transitivePeerDependencies:
- '@types/node'
- jiti
@ -7807,32 +8188,32 @@ snapshots:
- tsx
- yaml
vite-plugin-pwa@1.0.0(vite@6.3.5(@types/node@22.15.18)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0))(workbox-build@7.3.0(@types/babel__core@7.20.5))(workbox-window@7.3.0):
vite-plugin-pwa@1.0.0(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0))(workbox-build@7.3.0(@types/babel__core@7.20.5))(workbox-window@7.3.0):
dependencies:
debug: 4.4.1
pretty-bytes: 6.1.1
tinyglobby: 0.2.13
vite: 6.3.5(@types/node@22.15.18)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0)
vite: 6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0)
workbox-build: 7.3.0(@types/babel__core@7.20.5)
workbox-window: 7.3.0
transitivePeerDependencies:
- supports-color
vite-plugin-top-level-await@1.5.0(@swc/helpers@0.5.17)(rollup@4.40.2)(vite@6.3.5(@types/node@22.15.18)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0)):
vite-plugin-top-level-await@1.5.0(@swc/helpers@0.5.17)(rollup@4.40.2)(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0)):
dependencies:
'@rollup/plugin-virtual': 3.0.2(rollup@4.40.2)
'@swc/core': 1.11.24(@swc/helpers@0.5.17)
uuid: 10.0.0
vite: 6.3.5(@types/node@22.15.18)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0)
vite: 6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0)
transitivePeerDependencies:
- '@swc/helpers'
- rollup
vite-plugin-wasm@3.4.1(vite@6.3.5(@types/node@22.15.18)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0)):
vite-plugin-wasm@3.4.1(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0)):
dependencies:
vite: 6.3.5(@types/node@22.15.18)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0)
vite: 6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0)
vite@6.3.5(@types/node@22.15.18)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0):
vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0):
dependencies:
esbuild: 0.25.4
fdir: 6.4.4(picomatch@4.0.2)
@ -7843,14 +8224,16 @@ snapshots:
optionalDependencies:
'@types/node': 22.15.18
fsevents: 2.3.3
jiti: 2.4.2
lightningcss: 1.30.1
sass: 1.89.0
terser: 5.39.2
yaml: 2.8.0
vitest@3.1.3(@types/node@22.15.18)(@vitest/ui@3.1.3)(jsdom@26.1.0)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0):
vitest@3.1.3(@types/node@22.15.18)(@vitest/ui@3.1.3)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0):
dependencies:
'@vitest/expect': 3.1.3
'@vitest/mocker': 3.1.3(vite@6.3.5(@types/node@22.15.18)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0))
'@vitest/mocker': 3.1.3(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0))
'@vitest/pretty-format': 3.1.3
'@vitest/runner': 3.1.3
'@vitest/snapshot': 3.1.3
@ -7867,8 +8250,8 @@ snapshots:
tinyglobby: 0.2.13
tinypool: 1.0.2
tinyrainbow: 2.0.0
vite: 6.3.5(@types/node@22.15.18)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0)
vite-node: 3.1.3(@types/node@22.15.18)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0)
vite: 6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0)
vite-node: 3.1.3(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.89.0)(terser@5.39.2)(yaml@2.8.0)
why-is-node-running: 2.3.0
optionalDependencies:
'@types/node': 22.15.18
@ -8108,6 +8491,8 @@ snapshots:
yallist@3.1.1: {}
yallist@5.0.0: {}
yaml@1.10.2: {}
yaml@2.8.0: {}

17
src/App.css Normal file
View File

@ -0,0 +1,17 @@
@import 'tailwindcss';
@plugin "daisyui";
@theme {
--font-display:
system-ui, Sarasa UI SC, Source Han Sans CN, Noto Sans CJK SC, sans-serif, Apple Color Emoji, Segoe UI Emoji;
--font-mono:
ui-monospace, Consolas, Sarasa Mono CJK SC, Sarasa UI SC, Source Han Sans CN, Noto Sans CJK SC, Microsoft YaHei UI,
monospace, Apple Color Emoji, Segoe UI Emoji;
}
#root {
display: flex;
flex-direction: column;
height: 100vh;
display: flex;
flex-direction: column;
}

View File

@ -1,16 +1,4 @@
import {
Button,
ButtonGroup,
HStack,
Icon,
IconButton,
Menu,
MenuButton,
MenuDivider,
MenuItem,
MenuList,
} from '@chakra-ui/react';
import { MdAdd, MdDeleteForever, MdExpandMore, MdFileUpload } from 'react-icons/md';
import { MdAdd, MdDeleteForever, MdFileUpload } from 'react-icons/md';
export interface AddKeyProps {
addKey: () => void;
@ -20,28 +8,20 @@ export interface AddKeyProps {
export function AddKey({ addKey, importKeyFromFile, clearKeys }: AddKeyProps) {
return (
<HStack pb={2} pt={2}>
<ButtonGroup isAttached colorScheme="purple" variant="outline">
<Button onClick={addKey} leftIcon={<Icon as={MdAdd} />}>
</Button>
<Menu>
<MenuButton as={IconButton} icon={<MdExpandMore />}></MenuButton>
<MenuList>
{importKeyFromFile && (
<MenuItem onClick={importKeyFromFile} icon={<Icon as={MdFileUpload} boxSize={5} />}>
</MenuItem>
)}
{importKeyFromFile && clearKeys && <MenuDivider />}
{clearKeys && (
<MenuItem color="red" onClick={clearKeys} icon={<Icon as={MdDeleteForever} boxSize={5} />}>
</MenuItem>
)}
</MenuList>
</Menu>
</ButtonGroup>
</HStack>
<div className="flex flex-row justify-between items-center">
<div className="join">
<button className="btn join-item" onClick={addKey}>
<MdAdd />
</button>
<button className="btn join-item" onClick={importKeyFromFile}>
<MdFileUpload />
</button>
<button className="btn btn-error join-item" onClick={clearKeys}>
<MdDeleteForever />
</button>
</div>
</div>
);
}

View File

@ -1,12 +1,11 @@
import { useEffect } from 'react';
import { BrowserRouter, NavLink, Route, Routes } from 'react-router';
import { MdSettings, MdHome, MdQuestionAnswer } from 'react-icons/md';
import { ChakraProvider, Tabs, TabList, TabPanels, Tab, TabPanel, Icon, chakra } from '@chakra-ui/react';
import { MainTab } from '~/tabs/MainTab';
import { SettingsTab } from '~/tabs/SettingsTab';
import { Provider } from 'react-redux';
import { theme } from '~/theme';
import { persistSettings } from '~/features/settings/persistSettings';
import { setupStore } from '~/store';
import { Footer } from '~/components/Footer';
@ -15,43 +14,39 @@ import { FaqTab } from '~/tabs/FaqTab';
// Private to this file only.
const store = setupStore();
const tabClassNames = ({ isActive }: { isActive: boolean }) => `tab ${isActive ? 'tab-active' : ''}`;
export function AppRoot() {
useEffect(() => persistSettings(store), []);
return (
<ChakraProvider theme={theme}>
<BrowserRouter>
<Provider store={store}>
<Tabs flex={1} minH={0} display="flex" flexDir="column">
<TabList justifyContent="center">
<Tab>
<Icon as={MdHome} mr="1" />
<chakra.span></chakra.span>
</Tab>
<Tab>
<Icon as={MdSettings} mr="1" />
<chakra.span></chakra.span>
</Tab>
<Tab>
<Icon as={MdQuestionAnswer} mr="1" />
<chakra.span></chakra.span>
</Tab>
</TabList>
<div role="tablist" className="tabs tabs-border w-full justify-center">
<NavLink to="/" role="tab" className={tabClassNames}>
<MdHome />
</NavLink>
<NavLink to="/settings" role="tab" className={tabClassNames}>
<MdSettings />
</NavLink>
<NavLink to="/questions" role="tab" className={tabClassNames}>
<MdQuestionAnswer />
</NavLink>
</div>
<TabPanels overflow="auto" minW={0} flexDir="column" flex={1} display="flex">
<TabPanel>
<MainTab />
</TabPanel>
<TabPanel flex={1} display="flex">
<SettingsTab />
</TabPanel>
<TabPanel>
<FaqTab />
</TabPanel>
</TabPanels>
</Tabs>
<main className="flex-1 flex justify-center">
<Routes>
<Route path="/" Component={MainTab} />
<Route path="/settings" Component={SettingsTab} />
<Route path="/questions" Component={FaqTab} />
</Routes>
</main>
<Footer />
</Provider>
</ChakraProvider>
</BrowserRouter>
);
}

40
src/components/Dialog.tsx Normal file
View File

@ -0,0 +1,40 @@
import { useEffect, useRef } from 'react';
export interface DialogProps {
closeButton?: boolean;
backdropClose?: boolean;
title?: React.ReactNode;
children: React.ReactNode;
show: boolean;
onClose: () => void;
}
export function Dialog({ closeButton, backdropClose, title, children, show, onClose }: DialogProps) {
const refModel = useRef<HTMLDialogElement>(null);
useEffect(() => {
if (show) {
refModel.current?.showModal();
} else {
refModel.current?.close();
}
}, [show]);
return (
<dialog ref={refModel} className="modal">
<div className="modal-box">
{closeButton && (
<form method="dialog" onSubmit={onClose}>
<button className="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"></button>
</form>
)}
<h3 className="font-bold text-lg">{title}</h3>
{children}
</div>
{backdropClose && (
<form method="dialog" className="modal-backdrop" onSubmit={onClose}>
<button>close</button>
</form>
)}
</dialog>
);
}

View File

@ -1,12 +1,15 @@
import type { AnchorHTMLAttributes } from 'react';
import { ExternalLinkIcon } from '@chakra-ui/icons';
import { Link } from '@chakra-ui/react';
import { FiExternalLink } from 'react-icons/fi';
export function ExtLink({ children, ...props }: AnchorHTMLAttributes<HTMLAnchorElement>) {
export type ExtLinkProps = AnchorHTMLAttributes<HTMLAnchorElement> & {
icon?: boolean;
};
export function ExtLink({ className, icon = true, children, ...props }: ExtLinkProps) {
return (
<Link isExternal {...props} rel="noreferrer noopener nofollow">
<a rel="noreferrer noopener nofollow" className={`link ${className}`} {...props}>
{children}
<ExternalLinkIcon />
</Link>
{icon && <FiExternalLink />}
</a>
);
}

View File

@ -1,5 +1,5 @@
import classnames from 'classnames';
import { useDropzone } from 'react-dropzone';
import { Box } from '@chakra-ui/react';
export interface FileInputProps {
onReceiveFiles: (files: File[]) => void;
@ -14,30 +14,19 @@ export function FileInput({ children, onReceiveFiles }: FileInputProps) {
});
return (
<Box
<div
{...getRootProps()}
w="100%"
maxW={480}
borderWidth="1px"
borderRadius="lg"
transitionDuration="0.5s"
p="6"
cursor="pointer"
display="flex"
flexDir="column"
alignItems="center"
_hover={{
borderColor: 'gray.400',
bg: 'gray.50',
}}
{...(isDragActive && {
bg: 'blue.50',
borderColor: 'blue.700',
})}
className={classnames(
'w-full max-w-xl border rounded-lg transition duration-500 p-6 border-base-300 mx-auto',
'cursor-pointer flex flex-col items-center hover:border-gray-400 hover:bg-gray-50',
{
'bg-blue-50 border-blue-700': isDragActive,
},
)}
tabIndex={0}
>
<input {...getInputProps()} />
{children}
</Box>
</div>
);
}

View File

@ -1,10 +1,9 @@
import { Code, Text } from '@chakra-ui/react';
import React from 'react';
export function FilePathBlock({ children }: { children: React.ReactNode }) {
return (
<Text as="pre" whiteSpace="pre-wrap" wordBreak="break-all">
<Code>{children}</Code>
</Text>
<pre className="whitespace-pre-wrap break-all">
<code>{children}</code>
</pre>
);
}

View File

@ -1,45 +1,28 @@
import { Center, Flex, Link, Text } from '@chakra-ui/react';
import { Suspense } from 'react';
import { SDKVersion } from './SDKVersion';
import { CurrentYear } from './CurrentYear';
export function Footer() {
const appVersionShort = '__APP_VERSION_SHORT__';
return (
<Center
fontSize="sm"
textAlign="center"
bottom="0"
w="full"
pt="3"
pb="3"
borderTop="1px solid"
borderColor="gray.300"
bg="gray.100"
color="gray.800"
flexDir="column"
flexShrink={0}
>
<Flex as={Text}>
<Link href="https://git.unlock-music.dev/um/um-react" isExternal>
<footer className="flex flex-col text-center p-4 bg-base-200">
<p className="flex flex-row justify-center items-center h-[1em]">
<a className="link link-info mr-1" href="https://git.unlock-music.dev/um/um-react">
</Link>
{' (__APP_VERSION_SHORT__'}
<Suspense>
<SDKVersion />
</Suspense>
{') - 移除已购音乐的加密保护。'}
</Flex>
<Text>
</a>
(v{appVersionShort}
<SDKVersion />) -
</p>
<p>
{'© 2019 - '}
<CurrentYear />{' '}
<Link href="https://git.unlock-music.dev/um" isExternal>
<a className="link link-info" href="https://git.unlock-music.dev/um">
UnlockMusic
</Link>
</a>
{' | '}
<Link href="https://git.unlock-music.dev/um/um-react/src/branch/main/LICENSE" isExternal>
<a className="link link-info" href="https://git.unlock-music.dev/um/um-react/src/branch/main/LICENSE">
使 MIT
</Link>
</Text>
</Center>
</a>
</p>
</footer>
);
}

View File

@ -1,15 +1,4 @@
import {
Center,
Flex,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalHeader,
ModalOverlay,
Tabs,
Text,
} from '@chakra-ui/react';
import { useEffect, useRef } from 'react';
import { FileInput } from '~/components/FileInput';
@ -18,39 +7,43 @@ export interface ImportSecretModalProps {
children: React.ReactNode;
show: boolean;
onClose: () => void;
onImport: (file: File) => void|Promise<void>;
onImport: (file: File) => void | Promise<void>;
}
export function ImportSecretModal({ clientName, children, show, onClose, onImport }: ImportSecretModalProps) {
const handleFileReceived = (files: File[]) => {
const promise = onImport(files[0]);
if (promise instanceof Promise) {
promise.catch(err => {
promise.catch((err) => {
console.error('could not import: ', err);
});
}
return promise;
};
return (
<Modal isOpen={show} onClose={onClose} closeOnOverlayClick={false} scrollBehavior="inside" size="xl">
<ModalOverlay />
<ModalContent>
<ModalHeader></ModalHeader>
<ModalCloseButton />
<Flex as={ModalBody} gap={2} flexDir="column" flex={1}>
<Center>
<FileInput onReceiveFiles={handleFileReceived}></FileInput>
</Center>
const refModel = useRef<HTMLDialogElement>(null);
useEffect(() => {
if (show) {
refModel.current?.showModal();
} else {
refModel.current?.close();
}
}, [show]);
<Text as="div" mt={2}>
{clientName && <>{clientName}</>}
</Text>
<Flex as={Tabs} variant="enclosed" flexDir="column" flex={1} minH={0}>
{children}
</Flex>
</Flex>
</ModalContent>
</Modal>
return (
<dialog ref={refModel} className="modal">
<div className="modal-box">
<form method="dialog" onSubmit={() => onClose()}>
<button className="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"></button>
</form>
<h3 className="font-bold text-lg"></h3>
<div className="py-4 flex flex-col gap-2 flex-1">
<FileInput onReceiveFiles={handleFileReceived}></FileInput>
<div className="mt-2">{clientName && <>{clientName}</>}</div>
<div>{children}</div>
</div>
</div>
</dialog>
);
}

View File

@ -1,33 +1,39 @@
import { InfoOutlineIcon } from '@chakra-ui/icons';
import { Tooltip, VStack, Text, Flex } from '@chakra-ui/react';
import { MdInfoOutline } from 'react-icons/md';
import { workerClientBus } from '~/decrypt-worker/client';
import { DECRYPTION_WORKER_ACTION_NAME } from '~/decrypt-worker/constants';
import usePromise from 'react-promise-suspense';
import { useEffect, useRef, useState } from 'react';
const getSDKVersion = async (): Promise<string> => {
return workerClientBus.request(DECRYPTION_WORKER_ACTION_NAME.VERSION, null);
};
export function SDKVersion() {
const sdkVersion = usePromise(getSDKVersion, []);
const refDialog = useRef<HTMLDialogElement>(null);
const [sdkVersion, setSdkVersion] = useState('...');
useEffect(() => {
getSDKVersion().then(setSdkVersion);
}, []);
return (
<Flex as="span" pl="1" alignItems="center" data-testid="sdk-version">
<Tooltip
hasArrow
placement="top"
label={
<VStack>
<Text>App: __APP_VERSION__</Text>
<Text>SDK: {sdkVersion}</Text>
</VStack>
}
bg="gray.300"
color="black"
>
<InfoOutlineIcon />
</Tooltip>
</Flex>
<>
<span className="btn btn-ghost inline-flex p-0 h-[1em]" onClick={() => refDialog.current?.showModal()}>
<MdInfoOutline />
</span>
<dialog ref={refDialog} className="modal text-left">
<div className="modal-box">
<form method="dialog">
<button className="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"></button>
</form>
<h3 className="font-bold text-lg"></h3>
<p>App: __APP_VERSION__</p>
<p>SDK: {sdkVersion}</p>
</div>
<form method="dialog" className="modal-backdrop">
<button></button>
</form>
</dialog>
</>
);
}

View File

@ -1,5 +1,4 @@
import { Box, Text } from '@chakra-ui/react';
import { UnlockIcon } from '@chakra-ui/icons';
import { FiUnlock } from 'react-icons/fi';
import { useAppDispatch } from '~/hooks';
import { addNewFile, processFile } from '~/features/file-listing/fileListingSlice';
@ -12,7 +11,7 @@ export function SelectFile() {
console.debug(
'react-dropzone/onDropAccepted(%o, %o)',
files.length,
files.map((x) => x.name)
files.map((x) => x.name),
);
for (const file of files) {
@ -26,7 +25,7 @@ export function SelectFile() {
id: fileId,
blobURI,
fileName,
})
}),
);
dispatch(processFile({ fileId }));
}
@ -34,19 +33,13 @@ export function SelectFile() {
return (
<FileInput multiple onReceiveFiles={handleFileReceived}>
<Box pb={3}>
<UnlockIcon boxSize={8} />
</Box>
<Text as="div" textAlign="center">
<FiUnlock className="size-8 mb-4" />
<p className="text-center">
<Text as="span" color="teal.400">
</Text>
<span className="text-teal-700 font-semibold"></span>
<Text fontSize="sm" opacity="50%">
</Text>
</Text>
</p>
<p className="text-sm opacity-50 m-0"></p>
</FileInput>
);
}

View File

@ -1,5 +1,3 @@
import { VStack } from '@chakra-ui/react';
import { selectFiles } from './fileListingSlice';
import { useAppSelector } from '~/hooks';
import { FileRow } from './FileRow';
@ -8,10 +6,10 @@ export function FileListing() {
const files = useAppSelector(selectFiles);
return (
<VStack>
<div className="flex flex-row flex-wrap gap-8">
{Object.entries(files).map(([id, file]) => (
<FileRow key={id} id={id} file={file} />
))}
</VStack>
</div>
);
}

View File

@ -1,24 +1,9 @@
import { useRef } from 'react';
import {
Box,
Button,
Card,
CardBody,
Collapse,
GridItem,
Link,
VStack,
Wrap,
WrapItem,
useDisclosure,
} from '@chakra-ui/react';
import { FileRowResponsiveGrid } from './FileRowResponsiveGrid';
import { DecryptedAudioFile, deleteFile, ProcessState } from './fileListingSlice';
import { useAppDispatch } from '~/hooks';
import { AnimationDefinition } from 'framer-motion';
import { AlbumImage } from './AlbumImage';
import { SongMetadata } from './SongMetadata';
import { FileError } from './FileError';
import classNames from 'classnames';
interface FileRowProps {
id: string;
@ -26,7 +11,7 @@ interface FileRowProps {
}
export function FileRow({ id, file }: FileRowProps) {
const { isOpen, onClose } = useDisclosure({ defaultIsOpen: true });
// const { isOpen, onClose } = useDisclosure({ defaultIsOpen: true });
const dispatch = useAppDispatch();
const isDecrypted = file.state === ProcessState.COMPLETE;
const metadata = file.metadata;
@ -35,81 +20,54 @@ export function FileRow({ id, file }: FileRowProps) {
const decryptedName = nameWithoutExt + '.' + file.ext;
const audioPlayerRef = useRef<HTMLAudioElement>(null);
const togglePlay = () => {
const player = audioPlayerRef.current;
if (!player) {
return;
}
if (player.paused) {
player.play();
} else {
player.pause();
}
};
const onCollapseAnimationComplete = (definition: AnimationDefinition) => {
const _onCollapseAnimationComplete = (definition: AnimationDefinition) => {
if (definition === 'exit') {
dispatch(deleteFile({ id }));
}
};
return (
<Collapse
in={isOpen}
animateOpacity
unmountOnExit
startingHeight={0}
onAnimationComplete={onCollapseAnimationComplete}
style={{ width: '100%', padding: '0.25rem' }}
>
<Card w="full" data-testid="file-row">
<CardBody>
<FileRowResponsiveGrid>
<GridItem area="cover">
<AlbumImage name={metadata?.album} url={metadata?.cover} />
</GridItem>
<GridItem area="title">
<Box w="full" as="h4" fontWeight="semibold" mt="1" textAlign={{ base: 'center', md: 'left' }}>
<span data-testid="audio-meta-song-name">{metadata?.name ?? nameWithoutExt}</span>
</Box>
</GridItem>
<GridItem area="meta">
{isDecrypted && metadata && <SongMetadata metadata={metadata} />}
{file.state === ProcessState.ERROR && <FileError error={file.errorMessage} code={file.errorCode} />}
</GridItem>
<GridItem area="action" alignSelf="center">
<VStack>
{file.decrypted && <audio controls autoPlay={false} src={file.decrypted} ref={audioPlayerRef} />}
<div className="card bg-base-100 shadow-sm w-full md:w-[30%] " data-testid="file-row">
<div className="card-body items-center text-center px-2">
<h2
className="card-title overflow-hidden text-ellipsis block max-w-full whitespace-nowrap"
data-testid="audio-meta-song-name"
>
{metadata?.name ?? nameWithoutExt}
</h2>
<Wrap>
{isDecrypted && (
<>
<WrapItem>
<Button type="button" onClick={togglePlay}>
/
</Button>
</WrapItem>
<WrapItem>
{file.decrypted && (
<Link href={file.decrypted} download={decryptedName}>
<Button as="span"></Button>
</Link>
)}
</WrapItem>
</>
)}
<WrapItem>
<Button type="button" onClick={onClose}>
</Button>
</WrapItem>
</Wrap>
</VStack>
</GridItem>
</FileRowResponsiveGrid>
</CardBody>
</Card>
</Collapse>
<div>
{file.state === ProcessState.ERROR && <FileError error={file.errorMessage} code={file.errorCode} />}
{isDecrypted && (
<audio
className="max-w-[100%]"
aria-disabled={!file.decrypted}
controls
autoPlay={false}
src={file.decrypted}
ref={audioPlayerRef}
/>
)}
</div>
<div className="card-actions justify-end">
<a
href={file.decrypted}
download={decryptedName}
title={`下载: ${decryptedName}`}
className={classNames('btn', {
'btn-primary': file.decrypted,
'cursor-not-allowed pointer-events-none': !file.decrypted,
})}
data-testid="audio-download"
>
</a>
<button type="button" className="btn btn-error" onClick={() => dispatch(deleteFile({ id }))}>
</button>
</div>
</div>
</div>
);
}

View File

@ -19,7 +19,6 @@ import {
TabPanels,
Tabs,
Text,
useBreakpointValue,
useToast,
VStack,
} from '@chakra-ui/react';
@ -47,11 +46,7 @@ const TABS: { name: string; Tab: FC }[] = [
export function Settings() {
const toast = useToast();
const dispatch = useAppDispatch();
const isLargeWidthDevice =
useBreakpointValue({
base: false,
lg: true,
}) ?? false;
const isLargeWidthDevice = false;
const [tabIndex, setTabIndex] = useState(0);
const handleTabChange = (idx: number) => {

View File

@ -1,31 +1,9 @@
import {
Box,
Button,
ButtonGroup,
Checkbox,
Flex,
Heading,
HStack,
Icon,
IconButton,
List,
Menu,
MenuButton,
MenuDivider,
MenuItem,
MenuList,
Select,
Text,
Tooltip,
useToast,
} from '@chakra-ui/react';
import { Select, useToast } from '@chakra-ui/react';
import { useDispatch, useSelector } from 'react-redux';
import { qmc2AddKey, qmc2AllowFuzzyNameSearch, qmc2ClearKeys, qmc2ImportKeys } from '../settingsSlice';
import { selectStagingQMCv2Settings } from '../settingsSelector';
import React, { useState } from 'react';
import { MdAdd, MdDeleteForever, MdExpandMore, MdFileUpload } from 'react-icons/md';
import { QMCv2EKeyItem } from './QMCv2/QMCv2EKeyItem';
import { InfoOutlineIcon } from '@chakra-ui/icons';
import { ImportSecretModal } from '~/components/ImportSecretModal';
import { StagingQMCv2Key } from '../keyFormats';
import { DatabaseKeyExtractor } from '~/util/DatabaseKeyExtractor';
@ -33,8 +11,11 @@ import { parseAndroidQmEKey } from '~/util/mmkv/qm';
import { getFileName } from '~/util/pathHelper';
import { QMCv2QQMusicAllInstructions } from './QMCv2/QMCv2QQMusicAllInstructions';
import { QMCv2DoubanAllInstructions } from './QMCv2/QMCv2DoubanAllInstructions';
import { AddKey } from '~/components/AddKey';
import { Dialog } from '~/components/Dialog';
export function PanelQMCv2Key() {
const [showFuzzyNameSearchInfo, setShowFuzzyNameSearchInfo] = useState(false);
const toast = useToast();
const dispatch = useDispatch();
const { keys: qmc2Keys, allowFuzzyNameSearch } = useSelector(selectStagingQMCv2Settings);
@ -93,73 +74,64 @@ export function PanelQMCv2Key() {
};
return (
<Flex minH={0} flexDir="column" flex={1}>
<Heading as="h2" size="lg">
QMCv2
</Heading>
<div className="flex min-h-0 flex-col flex-1">
<h2 className="text-2xl font-bold">QMCv2 </h2>
<Text>
QQ FM QMCv2使QQ Mac iOS 使
FM线
</Text>
<p>
<span>QQ FM QMCv2</span>
<span>
使QQ Mac iOS 使 FM
线
</span>
</p>
<HStack pb={2} pt={2}>
<ButtonGroup isAttached colorScheme="purple" variant="outline">
<Button onClick={addKey} leftIcon={<Icon as={MdAdd} />}>
</Button>
<Menu>
<MenuButton as={IconButton} icon={<MdExpandMore />}></MenuButton>
<MenuList>
<MenuItem onClick={() => setShowImportModal(true)} icon={<Icon as={MdFileUpload} boxSize={5} />}>
</MenuItem>
<MenuDivider />
<MenuItem color="red" onClick={clearAll} icon={<Icon as={MdDeleteForever} boxSize={5} />}>
</MenuItem>
</MenuList>
</Menu>
</ButtonGroup>
<div className="flex flex-row gap-2 items-center">
<label className="label">
<input
className="checkbox"
type="checkbox"
checked={allowFuzzyNameSearch}
onChange={handleAllowFuzzyNameSearchCheckbox}
/>
</label>
<button className="btn btn-info btn-sm" type="button" onClick={() => setShowFuzzyNameSearchInfo(true)}>
?
</button>
<Dialog
closeButton
backdropClose
show={showFuzzyNameSearchInfo}
onClose={() => setShowFuzzyNameSearchInfo(false)}
title="莱文斯坦距离"
>
<p>使</p>
<p>
使
<ruby>
<rp> (</rp>
<rt>Levenshtein distance</rt>
<rp>)</rp>
</ruby>
</p>
<p></p>
<p></p>
</Dialog>
</div>
<HStack>
<Checkbox isChecked={allowFuzzyNameSearch} onChange={handleAllowFuzzyNameSearchCheckbox}>
<Text></Text>
</Checkbox>
<Tooltip
hasArrow
closeOnClick={false}
label={
<Box>
<Text>使</Text>
<Text>
使
<ruby>
<rp> (</rp>
<rt>Levenshtein distance</rt>
<rp>)</rp>
</ruby>
</Text>
<Text></Text>
<Text></Text>
</Box>
}
>
<InfoOutlineIcon />
</Tooltip>
</HStack>
</HStack>
<h3 className="mt-2 text-1xl font-bold"></h3>
<AddKey addKey={addKey} importKeyFromFile={() => setShowImportModal(true)} clearKeys={clearAll} />
<Box flex={1} minH={0} overflow="auto" pr="4">
<List spacing={3}>
<div className="flex-1 min-h-0 overflow-auto pr-4">
<ul className="list bg-base-100 rounded-box shadow-md">
{qmc2Keys.map(({ id, ekey, name }, i) => (
<QMCv2EKeyItem key={id} id={id} ekey={ekey} name={name} i={i} />
))}
</List>
{qmc2Keys.length === 0 && <Text></Text>}
</Box>
</ul>
{qmc2Keys.length === 0 && <p className="p-4 pb-2 text-xs tracking-wide"></p>}
</div>
<ImportSecretModal
clientName={
@ -181,6 +153,6 @@ export function PanelQMCv2Key() {
{secretType === 'qm' && <QMCv2QQMusicAllInstructions />}
{secretType === 'douban' && <QMCv2DoubanAllInstructions />}
</ImportSecretModal>
</Flex>
</div>
);
}

View File

@ -1,15 +1,3 @@
import {
HStack,
Icon,
IconButton,
Input,
InputGroup,
InputLeftElement,
InputRightElement,
ListItem,
Text,
VStack,
} from '@chakra-ui/react';
import { MdDelete, MdVpnKey } from 'react-icons/md';
import { qmc2DeleteKey, qmc2UpdateKey } from '../../settingsSlice';
import { useAppDispatch } from '~/hooks';
@ -22,48 +10,45 @@ export const QMCv2EKeyItem = memo(({ id, name, ekey, i }: { id: string; name: st
dispatch(qmc2UpdateKey({ id, field: prop, value: e.target.value }));
const deleteKey = () => dispatch(qmc2DeleteKey({ id }));
return (
<ListItem mt={0} pt={2} pb={2} _even={{ bg: 'gray.50' }}>
<HStack>
<Text w="2em" textAlign="center">
{i + 1}
</Text>
const isValidEKey = [364, 704].includes(ekey.length);
<VStack flex={1}>
<Input
variant="flushed"
return (
<li className="list-row items-center">
<div className="flex items-center justify-center w-8 h-8 text-sm font-bold text-gray-500 bg-gray-200 rounded-full">
{i + 1}
</div>
<div className="join join-vertical flex-1">
<label className="input w-full rounded-tl-md rounded-tr-md">
<span className="cursor-default select-none"></span>
<input
type="text"
className="font-mono"
placeholder="文件名,包括后缀名。如 “AAA - BBB.mflac”"
value={name}
onChange={(e) => updateKey('name', e)}
/>
</label>
<label className="input w-full rounded-bl-md rounded-br-md mt-[-1px]">
<span className="cursor-default inline-flex items-center gap-1 select-none">
<MdVpnKey />
</span>
<input
type="text"
className="font-mono"
placeholder="密钥,通常包含 364 或 704 位字符,没有空格。"
value={ekey}
onChange={(e) => updateKey('ekey', e)}
/>
<span className={isValidEKey ? 'text-green-600' : 'text-red-600'}>
<code>{ekey.length || '?'}</code>
</span>
</label>
</div>
<InputGroup size="xs">
<InputLeftElement pr="2">
<Icon as={MdVpnKey} />
</InputLeftElement>
<Input
variant="flushed"
placeholder="密钥,通常包含 364 或 704 位字符,没有空格。"
value={ekey}
onChange={(e) => updateKey('ekey', e)}
/>
<InputRightElement>
<Text pl="2" color={ekey.length ? 'green.500' : 'red.500'}>
<code>{ekey.length || '?'}</code>
</Text>
</InputRightElement>
</InputGroup>
</VStack>
<IconButton
aria-label="删除该密钥"
icon={<Icon as={MdDelete} boxSize={6} />}
variant="ghost"
colorScheme="red"
type="button"
onClick={deleteKey}
/>
</HStack>
</ListItem>
<button type="button" className="btn btn-error btn-sm px-1 btn-outline" onClick={deleteKey}>
<MdDelete className="size-6" />
</button>
</li>
);
});

View File

@ -1,4 +1,5 @@
import './pwa';
import './App.css';
import React from 'react';
import ReactDOM from 'react-dom/client';

View File

@ -1,4 +1,4 @@
import { Alert, AlertIcon, Box, Button, Flex, Text, VStack } from '@chakra-ui/react';
import { RiErrorWarningLine } from 'react-icons/ri';
import { SelectFile } from '../components/SelectFile';
import { FileListing } from '~/features/file-listing/FileListing';
@ -14,29 +14,32 @@ export function MainTab() {
};
return (
<Box h="full" w="full" pt="4">
<VStack gap="3">
<div className="size-full max-w-[80%] self-center pt-4">
<div className="gap-3 flex flex-col">
{isSettingsNotSaved && (
<Alert borderRadius={7} maxW={400} status="warning">
<AlertIcon />
<Flex flexDir="row" alignItems="center" flexGrow={1} justifyContent="space-between">
<Text m={0}>
<br />
</Text>
<Button type="button" ml={3} size="md" onClick={onClickSaveSettings}>
<div role="alert" className="alert alert-warning gap-2">
<span className="md:flex flex-row items-center gap-1">
<RiErrorWarningLine className="size-6" />
<span className="font-bold hidden md:inline"></span>
</span>
<div>
<span className="block font-bold md:hidden"></span>
<span></span>
<span className="inline-block"></span>
</div>
<div>
<button type="button" className="btn btn-primary btn-sm" onClick={onClickSaveSettings}>
</Button>
</Flex>
</Alert>
</button>
</div>
</div>
)}
<SelectFile />
<Box w="full">
<div className="w-full mt-4">
<FileListing />
</Box>
</VStack>
</Box>
</div>
</div>
</div>
);
}

View File

@ -1,15 +1,9 @@
import { Container, Flex, useBreakpointValue } from '@chakra-ui/react';
import { Settings } from '~/features/settings/Settings';
export function SettingsTab() {
const containerProps = useBreakpointValue({
base: { p: '0' },
lg: { p: undefined },
});
return (
<Container as={Flex} maxW="container.lg" {...containerProps}>
<div className="flex p-0">
<Settings />
</Container>
</div>
);
}

View File

@ -8,6 +8,7 @@ import wasm from 'vite-plugin-wasm';
import replace from '@rollup/plugin-replace';
import topLevelAwait from 'vite-plugin-top-level-await';
import { VitePWA } from 'vite-plugin-pwa';
import tailwindcss from '@tailwindcss/vite';
import { tryCommand } from './support/command';
@ -43,6 +44,7 @@ export default defineConfig({
exclude: ['@unlock-music/crypto', 'sql.js'],
},
plugins: [
tailwindcss(),
replace({
preventAssignment: true,
values: {