From e8862d1a908be4e7861d440e3360b16c8bf5c949 Mon Sep 17 00:00:00 2001 From: Wibus <62133302+wibus-wee@users.noreply.github.com> Date: Sat, 27 May 2023 15:49:16 +0800 Subject: [PATCH] feat: store manager page (#123) --- package.json | 1 + pnpm-lock.yaml | 433 +++++++++++++++++- src/App.tsx | 1 - src/components/universal/Editable/index.tsx | 52 +++ .../FileContextMenu/index.module.css | 46 ++ .../universal/FileContextMenu/index.tsx | 133 ++++++ .../widgets/ActionButtons/index.tsx | 3 +- src/components/widgets/Sidebar/index.tsx | 2 + src/hooks/useAppCheck.ts | 5 + src/libs/dialogs.ts | 13 + src/main.tsx | 21 +- src/pages/Files/components.tsx | 169 +++++++ src/pages/Files/index.module.css | 83 ++++ src/pages/Files/index.tsx | 113 +++++ src/router/router.tsx | 2 + src/states/app.ts | 1 + src/utils/mouse.ts | 7 + tsconfig.json | 1 + vite.config.ts | 1 + 19 files changed, 1077 insertions(+), 10 deletions(-) create mode 100644 src/components/universal/Editable/index.tsx create mode 100644 src/components/universal/FileContextMenu/index.module.css create mode 100644 src/components/universal/FileContextMenu/index.tsx create mode 100644 src/libs/dialogs.ts create mode 100644 src/pages/Files/components.tsx create mode 100644 src/pages/Files/index.module.css create mode 100644 src/pages/Files/index.tsx create mode 100644 src/utils/mouse.ts diff --git a/package.json b/package.json index 1962cbb6..cfd83714 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "ofetch": "^1.0.1", "react": "18.2.0", "react-dom": "18.2.0", + "react-hook-dialog": "^1.2.1", "react-message-popup": "1.1.1", "react-router-dom": "6.9.0", "react-use": "17.4.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0c621d7e..f5b5b9fe 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -28,6 +28,9 @@ dependencies: react-dom: specifier: 18.2.0 version: 18.2.0(react@18.2.0) + react-hook-dialog: + specifier: ^1.2.1 + version: 1.2.1(react@18.2.0) react-message-popup: specifier: 1.1.1 version: 1.1.1(react-dom@18.2.0)(react@18.2.0) @@ -556,6 +559,21 @@ packages: dev: true optional: true + /@eslint-community/eslint-utils@4.4.0(eslint@8.41.0): + resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + dependencies: + eslint: 8.41.0 + eslint-visitor-keys: 3.4.1 + dev: true + + /@eslint-community/regexpp@4.5.1: + resolution: {integrity: sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + dev: true + /@eslint/eslintrc@1.4.1: resolution: {integrity: sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -573,6 +591,28 @@ packages: - supports-color dev: true + /@eslint/eslintrc@2.0.3: + resolution: {integrity: sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + ajv: 6.12.6 + debug: 4.3.4 + espree: 9.5.2 + globals: 13.19.0 + ignore: 5.2.0 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + dev: true + + /@eslint/js@8.41.0: + resolution: {integrity: sha512-LxcyMGxwmTh2lY9FwHPGWOHmYFCZvbrFCBZL4FzSSsxsRPuhrYUg/49/0KDfW8tnIEaEHtfmn6+NPN+1DqaNmA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true + /@headlessui/react@1.7.13(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-9n+EQKRtD9266xIHXdY5MfiXPDfYwl7zBM7KOx2Ae3Gdgxy8QML1FkCMjq6AsOf0l6N9uvI4HcFtuFlenaldKg==} engines: {node: '>=10'} @@ -648,7 +688,7 @@ packages: peerDependencies: typescript: '>=4' dependencies: - '@innei/eslint-config-ts': 0.9.8(typescript@5.0.2) + '@innei/eslint-config-ts': 0.10.0(typescript@5.0.2) eslint: 8.31.0 eslint-plugin-react: 7.32.0(eslint@8.31.0) eslint-plugin-react-hooks: 4.6.0(eslint@8.31.0) @@ -659,6 +699,24 @@ packages: - supports-color dev: true + /@innei/eslint-config-ts@0.10.0(typescript@5.0.2): + resolution: {integrity: sha512-nMA/8sqALgqVPiaz4OvWzJDzEWRr2BWZ45cT3r9otXFduhtegyR9/AMg1hcrsIqRq0OM0L0bGXDANc9TGQ/fIA==} + peerDependencies: + typescript: '>=4' + dependencies: + '@typescript-eslint/eslint-plugin': 5.59.6(@typescript-eslint/parser@5.59.7)(eslint@8.41.0)(typescript@5.0.2) + '@typescript-eslint/parser': 5.59.7(eslint@8.41.0)(typescript@5.0.2) + eslint: 8.41.0 + eslint-config-prettier: 8.8.0(eslint@8.41.0) + eslint-plugin-import: 2.27.5(@typescript-eslint/parser@5.59.7)(eslint@8.41.0) + eslint-plugin-unused-imports: 2.0.0(@typescript-eslint/eslint-plugin@5.59.6)(eslint@8.41.0) + typescript: 5.0.2 + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + dev: true + /@innei/eslint-config-ts@0.9.8(typescript@5.0.2): resolution: {integrity: sha512-q2QHigFXgea4M4c6J5pAV4OioobE7EuJ/XRkI9645woj86rwWqjxJqYBR7HIOMFXoQd0aLZ2wtmepCofsPIjHQ==} peerDependencies: @@ -833,6 +891,34 @@ packages: - supports-color dev: true + /@typescript-eslint/eslint-plugin@5.59.6(@typescript-eslint/parser@5.59.7)(eslint@8.41.0)(typescript@5.0.2): + resolution: {integrity: sha512-sXtOgJNEuRU5RLwPUb1jxtToZbgvq3M6FPpY4QENxoOggK+UpTxUBpj6tD8+Qh2g46Pi9We87E+eHnUw8YcGsw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + '@typescript-eslint/parser': ^5.0.0 + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@eslint-community/regexpp': 4.5.1 + '@typescript-eslint/parser': 5.59.7(eslint@8.41.0)(typescript@5.0.2) + '@typescript-eslint/scope-manager': 5.59.6 + '@typescript-eslint/type-utils': 5.59.6(eslint@8.41.0)(typescript@5.0.2) + '@typescript-eslint/utils': 5.59.6(eslint@8.41.0)(typescript@5.0.2) + debug: 4.3.4 + eslint: 8.41.0 + grapheme-splitter: 1.0.4 + ignore: 5.2.0 + natural-compare-lite: 1.4.0 + semver: 7.3.7 + tsutils: 3.21.0(typescript@5.0.2) + typescript: 5.0.2 + transitivePeerDependencies: + - supports-color + dev: true + /@typescript-eslint/parser@5.48.1(eslint@8.31.0)(typescript@5.0.2): resolution: {integrity: sha512-4yg+FJR/V1M9Xoq56SF9Iygqm+r5LMXvheo6DQ7/yUWynQ4YfCRnsKuRgqH4EQ5Ya76rVwlEpw4Xu+TgWQUcdA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -853,6 +939,26 @@ packages: - supports-color dev: true + /@typescript-eslint/parser@5.59.7(eslint@8.41.0)(typescript@5.0.2): + resolution: {integrity: sha512-VhpsIEuq/8i5SF+mPg9jSdIwgMBBp0z9XqjiEay+81PYLJuroN+ET1hM5IhkiYMJd9MkTz8iJLt7aaGAgzWUbQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/scope-manager': 5.59.7 + '@typescript-eslint/types': 5.59.7 + '@typescript-eslint/typescript-estree': 5.59.7(typescript@5.0.2) + debug: 4.3.4 + eslint: 8.41.0 + typescript: 5.0.2 + transitivePeerDependencies: + - supports-color + dev: true + /@typescript-eslint/scope-manager@5.48.1: resolution: {integrity: sha512-S035ueRrbxRMKvSTv9vJKIWgr86BD8s3RqoRZmsSh/s8HhIs90g6UlK8ZabUSjUZQkhVxt7nmZ63VJ9dcZhtDQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -869,6 +975,22 @@ packages: '@typescript-eslint/visitor-keys': 5.54.1 dev: true + /@typescript-eslint/scope-manager@5.59.6: + resolution: {integrity: sha512-gLbY3Le9Dxcb8KdpF0+SJr6EQ+hFGYFl6tVY8VxLPFDfUZC7BHFw+Vq7bM5lE9DwWPfx4vMWWTLGXgpc0mAYyQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + '@typescript-eslint/types': 5.59.6 + '@typescript-eslint/visitor-keys': 5.59.6 + dev: true + + /@typescript-eslint/scope-manager@5.59.7: + resolution: {integrity: sha512-FL6hkYWK9zBGdxT2wWEd2W8ocXMu3K94i3gvMrjXpx+koFYdYV7KprKfirpgY34vTGzEPPuKoERpP8kD5h7vZQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + '@typescript-eslint/types': 5.59.7 + '@typescript-eslint/visitor-keys': 5.59.7 + dev: true + /@typescript-eslint/type-utils@5.54.1(eslint@8.31.0)(typescript@5.0.2): resolution: {integrity: sha512-WREHsTz0GqVYLIbzIZYbmUUr95DKEKIXZNH57W3s+4bVnuF1TKe2jH8ZNH8rO1CeMY3U4j4UQeqPNkHMiGem3g==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -889,6 +1011,26 @@ packages: - supports-color dev: true + /@typescript-eslint/type-utils@5.59.6(eslint@8.41.0)(typescript@5.0.2): + resolution: {integrity: sha512-A4tms2Mp5yNvLDlySF+kAThV9VTBPCvGf0Rp8nl/eoDX9Okun8byTKoj3fJ52IJitjWOk0fKPNQhXEB++eNozQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: '*' + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/typescript-estree': 5.59.6(typescript@5.0.2) + '@typescript-eslint/utils': 5.59.6(eslint@8.41.0)(typescript@5.0.2) + debug: 4.3.4 + eslint: 8.41.0 + tsutils: 3.21.0(typescript@5.0.2) + typescript: 5.0.2 + transitivePeerDependencies: + - supports-color + dev: true + /@typescript-eslint/types@5.48.1: resolution: {integrity: sha512-xHyDLU6MSuEEdIlzrrAerCGS3T7AA/L8Hggd0RCYBi0w3JMvGYxlLlXHeg50JI9Tfg5MrtsfuNxbS/3zF1/ATg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -899,6 +1041,16 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true + /@typescript-eslint/types@5.59.6: + resolution: {integrity: sha512-tH5lBXZI7T2MOUgOWFdVNUILsI02shyQvfzG9EJkoONWugCG77NDDa1EeDGw7oJ5IvsTAAGVV8I3Tk2PNu9QfA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true + + /@typescript-eslint/types@5.59.7: + resolution: {integrity: sha512-UnVS2MRRg6p7xOSATscWkKjlf/NDKuqo5TdbWck6rIRZbmKpVNTLALzNvcjIfHBE7736kZOFc/4Z3VcZwuOM/A==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true + /@typescript-eslint/typescript-estree@5.48.1(typescript@5.0.2): resolution: {integrity: sha512-Hut+Osk5FYr+sgFh8J/FHjqX6HFcDzTlWLrFqGoK5kVUN3VBHF/QzZmAsIXCQ8T/W9nQNBTqalxi1P3LSqWnRA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -941,6 +1093,48 @@ packages: - supports-color dev: true + /@typescript-eslint/typescript-estree@5.59.6(typescript@5.0.2): + resolution: {integrity: sha512-vW6JP3lMAs/Tq4KjdI/RiHaaJSO7IUsbkz17it/Rl9Q+WkQ77EOuOnlbaU8kKfVIOJxMhnRiBG+olE7f3M16DA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/types': 5.59.6 + '@typescript-eslint/visitor-keys': 5.59.6 + debug: 4.3.4 + globby: 11.1.0 + is-glob: 4.0.3 + semver: 7.3.7 + tsutils: 3.21.0(typescript@5.0.2) + typescript: 5.0.2 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/typescript-estree@5.59.7(typescript@5.0.2): + resolution: {integrity: sha512-4A1NtZ1I3wMN2UGDkU9HMBL+TIQfbrh4uS0WDMMpf3xMRursDbqEf1ahh6vAAe3mObt8k3ZATnezwG4pdtWuUQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/types': 5.59.7 + '@typescript-eslint/visitor-keys': 5.59.7 + debug: 4.3.4 + globby: 11.1.0 + is-glob: 4.0.3 + semver: 7.3.7 + tsutils: 3.21.0(typescript@5.0.2) + typescript: 5.0.2 + transitivePeerDependencies: + - supports-color + dev: true + /@typescript-eslint/utils@5.54.1(eslint@8.31.0)(typescript@5.0.2): resolution: {integrity: sha512-IY5dyQM8XD1zfDe5X8jegX6r2EVU5o/WJnLu/znLPWCBF7KNGC+adacXnt5jEYS9JixDcoccI6CvE4RCjHMzCQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -961,6 +1155,26 @@ packages: - typescript dev: true + /@typescript-eslint/utils@5.59.6(eslint@8.41.0)(typescript@5.0.2): + resolution: {integrity: sha512-vzaaD6EXbTS29cVH0JjXBdzMt6VBlv+hE31XktDRMX1j3462wZCJa7VzO2AxXEXcIl8GQqZPcOPuW/Z1tZVogg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.41.0) + '@types/json-schema': 7.0.11 + '@types/semver': 7.3.13 + '@typescript-eslint/scope-manager': 5.59.6 + '@typescript-eslint/types': 5.59.6 + '@typescript-eslint/typescript-estree': 5.59.6(typescript@5.0.2) + eslint: 8.41.0 + eslint-scope: 5.1.1 + semver: 7.3.7 + transitivePeerDependencies: + - supports-color + - typescript + dev: true + /@typescript-eslint/visitor-keys@5.48.1: resolution: {integrity: sha512-Ns0XBwmfuX7ZknznfXozgnydyR8F6ev/KEGePP4i74uL3ArsKbEhJ7raeKr1JSa997DBDwol/4a0Y+At82c9dA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -977,6 +1191,22 @@ packages: eslint-visitor-keys: 3.3.0 dev: true + /@typescript-eslint/visitor-keys@5.59.6: + resolution: {integrity: sha512-zEfbFLzB9ETcEJ4HZEEsCR9HHeNku5/Qw1jSS5McYJv5BR+ftYXwFFAH5Al+xkGaZEqowMwl7uoJjQb1YSPF8Q==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + '@typescript-eslint/types': 5.59.6 + eslint-visitor-keys: 3.4.1 + dev: true + + /@typescript-eslint/visitor-keys@5.59.7: + resolution: {integrity: sha512-tyN+X2jvMslUszIiYbF0ZleP+RqQsFVpGrKI6e0Eet1w8WmhsAtmzaqm8oM8WJQ1ysLwhnsK/4hYHJjOgJVfQQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + '@typescript-eslint/types': 5.59.7 + eslint-visitor-keys: 3.4.1 + dev: true + /@vitejs/plugin-react@3.1.0(vite@4.2.1): resolution: {integrity: sha512-AfgcRL8ZBhAlc3BFdigClmTUMISmmzHn7sB2h9U1odvc5U/MjWXsAaz18b/WoppUTDBzxOJwo2VdClfUcItu9g==} engines: {node: ^14.18.0 || >=16.0.0} @@ -1099,6 +1329,16 @@ packages: es-shim-unscopables: 1.0.0 dev: true + /array.prototype.flat@1.3.1: + resolution: {integrity: sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + es-abstract: 1.21.1 + es-shim-unscopables: 1.0.0 + dev: true + /array.prototype.flatmap@1.3.1: resolution: {integrity: sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==} engines: {node: '>= 0.4'} @@ -1795,6 +2035,15 @@ packages: eslint: 8.31.0 dev: true + /eslint-config-prettier@8.8.0(eslint@8.41.0): + resolution: {integrity: sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + dependencies: + eslint: 8.41.0 + dev: true + /eslint-import-resolver-node@0.3.6: resolution: {integrity: sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==} dependencies: @@ -1804,6 +2053,16 @@ packages: - supports-color dev: true + /eslint-import-resolver-node@0.3.7: + resolution: {integrity: sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==} + dependencies: + debug: 3.2.7 + is-core-module: 2.12.1 + resolve: 1.22.1 + transitivePeerDependencies: + - supports-color + dev: true + /eslint-module-utils@2.7.3(@typescript-eslint/parser@5.48.1)(eslint-import-resolver-node@0.3.6): resolution: {integrity: sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ==} engines: {node: '>=4'} @@ -1830,6 +2089,35 @@ packages: - supports-color dev: true + /eslint-module-utils@2.8.0(@typescript-eslint/parser@5.59.7)(eslint-import-resolver-node@0.3.7)(eslint@8.41.0): + resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: '*' + eslint-import-resolver-node: '*' + eslint-import-resolver-typescript: '*' + eslint-import-resolver-webpack: '*' + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + eslint: + optional: true + eslint-import-resolver-node: + optional: true + eslint-import-resolver-typescript: + optional: true + eslint-import-resolver-webpack: + optional: true + dependencies: + '@typescript-eslint/parser': 5.59.7(eslint@8.41.0)(typescript@5.0.2) + debug: 3.2.7 + eslint: 8.41.0 + eslint-import-resolver-node: 0.3.7 + transitivePeerDependencies: + - supports-color + dev: true + /eslint-plugin-import@2.26.0(@typescript-eslint/parser@5.48.1)(eslint@8.31.0): resolution: {integrity: sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==} engines: {node: '>=4'} @@ -1861,6 +2149,39 @@ packages: - supports-color dev: true + /eslint-plugin-import@2.27.5(@typescript-eslint/parser@5.59.7)(eslint@8.41.0): + resolution: {integrity: sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + dependencies: + '@typescript-eslint/parser': 5.59.7(eslint@8.41.0)(typescript@5.0.2) + array-includes: 3.1.6 + array.prototype.flat: 1.3.1 + array.prototype.flatmap: 1.3.1 + debug: 3.2.7 + doctrine: 2.1.0 + eslint: 8.41.0 + eslint-import-resolver-node: 0.3.7 + eslint-module-utils: 2.8.0(@typescript-eslint/parser@5.59.7)(eslint-import-resolver-node@0.3.7)(eslint@8.41.0) + has: 1.0.3 + is-core-module: 2.12.1 + is-glob: 4.0.3 + minimatch: 3.1.2 + object.values: 1.1.6 + resolve: 1.22.1 + semver: 6.3.0 + tsconfig-paths: 3.14.1 + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + dev: true + /eslint-plugin-react-hooks@4.6.0(eslint@8.31.0): resolution: {integrity: sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==} engines: {node: '>=10'} @@ -1909,6 +2230,21 @@ packages: eslint-rule-composer: 0.3.0 dev: true + /eslint-plugin-unused-imports@2.0.0(@typescript-eslint/eslint-plugin@5.59.6)(eslint@8.41.0): + resolution: {integrity: sha512-3APeS/tQlTrFa167ThtP0Zm0vctjr4M44HMpeg1P4bK6wItarumq0Ma82xorMKdFsWpphQBlRPzw/pxiVELX1A==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + '@typescript-eslint/eslint-plugin': ^5.0.0 + eslint: ^8.0.0 + peerDependenciesMeta: + '@typescript-eslint/eslint-plugin': + optional: true + dependencies: + '@typescript-eslint/eslint-plugin': 5.59.6(@typescript-eslint/parser@5.59.7)(eslint@8.41.0)(typescript@5.0.2) + eslint: 8.41.0 + eslint-rule-composer: 0.3.0 + dev: true + /eslint-rule-composer@0.3.0: resolution: {integrity: sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg==} engines: {node: '>=4.0.0'} @@ -1930,6 +2266,14 @@ packages: estraverse: 5.3.0 dev: true + /eslint-scope@7.2.0: + resolution: {integrity: sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + dev: true + /eslint-utils@3.0.0(eslint@8.31.0): resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==} engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} @@ -1950,6 +2294,11 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true + /eslint-visitor-keys@3.4.1: + resolution: {integrity: sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true + /eslint@8.31.0: resolution: {integrity: sha512-0tQQEVdmPZ1UtUKXjX7EMm9BlgJ08G90IhWh0PKDCb3ZLsgAOHI8fYSIzYVZej92zsgq+ft0FGsxhJ3xo2tbuA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -1998,6 +2347,54 @@ packages: - supports-color dev: true + /eslint@8.41.0: + resolution: {integrity: sha512-WQDQpzGBOP5IrXPo4Hc0814r4/v2rrIsB0rhT7jtunIalgg6gYXWhRMOejVO8yH21T/FGaxjmFjBMNqcIlmH1Q==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + hasBin: true + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.41.0) + '@eslint-community/regexpp': 4.5.1 + '@eslint/eslintrc': 2.0.3 + '@eslint/js': 8.41.0 + '@humanwhocodes/config-array': 0.11.8 + '@humanwhocodes/module-importer': 1.0.1 + '@nodelib/fs.walk': 1.2.8 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.3 + debug: 4.3.4 + doctrine: 3.0.0 + escape-string-regexp: 4.0.0 + eslint-scope: 7.2.0 + eslint-visitor-keys: 3.4.1 + espree: 9.5.2 + esquery: 1.5.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 6.0.1 + find-up: 5.0.0 + glob-parent: 6.0.2 + globals: 13.19.0 + graphemer: 1.4.0 + ignore: 5.2.0 + import-fresh: 3.3.0 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + is-path-inside: 3.0.3 + js-yaml: 4.1.0 + json-stable-stringify-without-jsonify: 1.0.1 + levn: 0.4.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.1 + strip-ansi: 6.0.1 + strip-json-comments: 3.1.1 + text-table: 0.2.0 + transitivePeerDependencies: + - supports-color + dev: true + /espree@9.4.1: resolution: {integrity: sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -2007,6 +2404,15 @@ packages: eslint-visitor-keys: 3.3.0 dev: true + /espree@9.5.2: + resolution: {integrity: sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + acorn: 8.8.0 + acorn-jsx: 5.3.2(acorn@8.8.0) + eslint-visitor-keys: 3.4.1 + dev: true + /esquery@1.4.0: resolution: {integrity: sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==} engines: {node: '>=0.10'} @@ -2014,6 +2420,13 @@ packages: estraverse: 5.3.0 dev: true + /esquery@1.5.0: + resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} + engines: {node: '>=0.10'} + dependencies: + estraverse: 5.3.0 + dev: true + /esrecurse@4.3.0: resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} engines: {node: '>=4.0'} @@ -2272,6 +2685,10 @@ packages: resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} dev: true + /graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + dev: true + /has-bigints@1.0.2: resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} dev: true @@ -2422,6 +2839,12 @@ packages: engines: {node: '>= 0.4'} dev: true + /is-core-module@2.12.1: + resolution: {integrity: sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==} + dependencies: + has: 1.0.3 + dev: true + /is-core-module@2.9.0: resolution: {integrity: sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==} dependencies: @@ -3159,6 +3582,14 @@ packages: scheduler: 0.23.0 dev: false + /react-hook-dialog@1.2.1(react@18.2.0): + resolution: {integrity: sha512-6M1aGYumxi++nSUg7A7MWjOWt5lvIDGeOeOTBCxQM/v6p4VT3ZXor7uBmOSKkhnbd4SKKJXnOyhPwzI3PQXsNQ==} + peerDependencies: + react: ^16.8.0 || ^17 || ^18 + dependencies: + react: 18.2.0 + dev: false + /react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} dev: true diff --git a/src/App.tsx b/src/App.tsx index bbfc4682..03d1c87c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -12,7 +12,6 @@ import { Toaster } from "sonner"; function App() { const appSnapshot = useSnapshot(app); - const { error: gatewayError } = useSWR("/ping"); const [theme, setTheme] = useState("light"); diff --git a/src/components/universal/Editable/index.tsx b/src/components/universal/Editable/index.tsx new file mode 100644 index 00000000..461b7926 --- /dev/null +++ b/src/components/universal/Editable/index.tsx @@ -0,0 +1,52 @@ +import { useState, useRef, useEffect } from "react"; + +export const Editable = (props: { + onChange: (value: string) => void; + enable: boolean; + value: string; +}) => { + const [value, setValue] = useState(props.value); + const inputRef = useRef(null); + + useEffect(() => { + setValue(props.value); + }, [props.value]); + + useEffect(() => { + if (props.enable) { + inputRef.current?.focus(); + } + }, [props.enable]) + + const handleBlur = () => { + props.onChange(value); + }; + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === "Enter") { + props.onChange(value); + } + }; + + return ( +
+ {props.enable ? ( + setValue(e.target.value)} + onBlur={handleBlur} + onKeyDown={handleKeyDown} + /> + ) : ( + + {props.value} + + )} +
+ ); +}; diff --git a/src/components/universal/FileContextMenu/index.module.css b/src/components/universal/FileContextMenu/index.module.css new file mode 100644 index 00000000..d0225cc2 --- /dev/null +++ b/src/components/universal/FileContextMenu/index.module.css @@ -0,0 +1,46 @@ +.contextMenu { + position: absolute; + /* padding: 10px 30px; */ + background-color: var(--background-color); + /* box-shadow: 0 0 2px 0 rgba(0, 0, 0, 0.2); */ + border: 1px solid var(--background-color-secondary); + border-radius: 15px; + flex-direction: column; + animation: fadeIn 0.1s ease-in-out; +} + +.action { + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; + padding: 10px 30px; + transition: background-color 0.1s ease-in-out; +} + +.action:first-child { + border-top-left-radius: 13px; + border-top-right-radius: 13px; +} + +.action:last-child { + border-bottom-left-radius: 13px; + border-bottom-right-radius: 13px; +} + +.action:hover { + background-color: var(--background-color-secondary); +} + +.icon { + margin-right: 15px; +} + +@keyframes fadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } +} \ No newline at end of file diff --git a/src/components/universal/FileContextMenu/index.tsx b/src/components/universal/FileContextMenu/index.tsx new file mode 100644 index 00000000..7c6aa679 --- /dev/null +++ b/src/components/universal/FileContextMenu/index.tsx @@ -0,0 +1,133 @@ +import { dialog } from "@libs/dialogs"; +import styles from "./index.module.css"; +import clsx from "clsx"; +import { useEffect, useRef } from "react"; +import { + Delete, + Download, + LinkOne, + MoveOne, + Pencil, + Share, +} from "@icon-park/react"; +import { API, apiClient } from "@utils/request"; +import path from "path"; +import { toast } from "sonner"; +import { ofetch } from "ofetch"; +import { useNavigate } from "react-router-dom"; + +export const FileContextMenu = () => { + const navgative = useNavigate(); + const { isOpen, handleClose, props } = + dialog.useDialogController("fileContextMenu"); + const ref = useRef(null); + + useEffect(() => { + const handleClick = (e: MouseEvent) => { + if (!ref.current?.contains(e.target as Node)) { + handleClose(); + } + }; + window.addEventListener("click", handleClick); + return () => { + window.removeEventListener("click", handleClick); + }; + }, []); + + const handleOpen = () => { + if (props.isFile) { + window.open(`${API}/store/raw${props.path}/${props.name}`); + } else { + navgative(`/files?path=${props.path}/${props.name}`); + } + }; + + const handleDownload = async () => { + const url = `${API}/store/raw${props.path}`; + // blob + const blob = await ofetch(`${url}/${props.name}`, { + method: "GET", + responseType: "blob", + }); + const a = document.createElement("a"); + a.href = window.URL.createObjectURL(blob); + a.download = props.name; + a.click(); + }; + + const handleRename = () => { + props.onRename?.(); + }; + + const handleDelete = () => { + toast.promise( + apiClient(`/store/delete?path=${props.path}/${props.name}`, { + method: "POST", + body: JSON.stringify({ + path: `${path}/${props.name}`, + }), + }), + { + loading: "正在删除", + success: "删除成功", + error: "删除失败", + } + ); + }; + + const handleMove = () => { + toast.error("Not implemented"); + }; + + const handleCopy = () => { + toast.promise( + Promise.all([ + navigator.clipboard.writeText( + `${API}/store/raw${props.path}/${props.name}` + ), + ]), + { + loading: "正在复制", + success: "复制成功", + error: "复制失败", + } + ); + }; + + return ( +
+ + {props.isFile && ( + + )} + {props.isFile && ( + + )} + + + +
+ ); +}; diff --git a/src/components/widgets/ActionButtons/index.tsx b/src/components/widgets/ActionButtons/index.tsx index 51f04965..6ae77dec 100644 --- a/src/components/widgets/ActionButtons/index.tsx +++ b/src/components/widgets/ActionButtons/index.tsx @@ -6,7 +6,7 @@ interface ActionButtonsProps { selectedClassName: string setSelect: (value: React.SetStateAction) => void selected: any[] - editAction: (event: React.MouseEvent) => void + editAction?: (event: React.MouseEvent) => void deleteFunction: (e) => void } @@ -51,6 +51,7 @@ export const ActionButtons = (props: ActionButtonsProps) => { } const EditButton = () => { + if (!editAction) return null return ( } diff --git a/src/components/widgets/Sidebar/index.tsx b/src/components/widgets/Sidebar/index.tsx index 15518ab3..fdfc3e9f 100644 --- a/src/components/widgets/Sidebar/index.tsx +++ b/src/components/widgets/Sidebar/index.tsx @@ -3,6 +3,7 @@ import { Comment, Dashboard, Editor, + FolderFocusOne, FriendsCircle, HomeTwo, Login, @@ -76,6 +77,7 @@ const Links = () => { + {}, + isFile: true, + }, +}); + +export const dialog = createDialogHooks(dialogs); diff --git a/src/main.tsx b/src/main.tsx index 1a24c465..0bb2a52c 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -6,17 +6,24 @@ import "@icon-park/react/styles/index.css"; import "./index.css"; import { SWRConfig } from "swr"; import { fetch } from "@utils/request"; +import { DialogProvider } from "react-hook-dialog"; +import { dialogs } from "./libs/dialogs"; +import { FileContextMenu } from "@components/universal/FileContextMenu"; ReactDOM.createRoot(document.getElementById("root")!).render( - - - + + + + + + + ); diff --git a/src/pages/Files/components.tsx b/src/pages/Files/components.tsx new file mode 100644 index 00000000..280da6f1 --- /dev/null +++ b/src/pages/Files/components.tsx @@ -0,0 +1,169 @@ +import { useEffect, useState } from "react"; +import styles from "./index.module.css"; +import { API, apiClient } from "@utils/request"; +import { FileMusic, FileText, FolderOpen, Notes } from "@icon-park/react"; +import { useNavigate } from "react-router-dom"; +import clsx from "clsx"; +import { dialog } from "@libs/dialogs"; +import { calculateMousePosition } from "@utils/mouse"; +import { Editable } from "@components/universal/Editable"; +import { toast } from "sonner"; + +export interface FileItemProps { + name: string; + type: "image" | "directory" | "audio" | "text" | "other"; + size: string; +} + +export const Item = ( + props: FileItemProps & { + path: string; + onClick?: () => void; + className?: string; + } +) => { + const [icon, setIcon] = useState(<>); + const [rename, setRename] = useState(false); + const navgative = useNavigate(); + const { open } = dialog.useDialog('fileContextMenu', {}) + + useEffect(() => { + if (props.type === "image") { + setIcon( + {props.name} + ); + } else if (props.type === "directory") { + setIcon(); + } else if (props.type === "audio") { + setIcon(); + } else if (props.type === "text") { + setIcon(); + } else { + setIcon(); + } + }, [props]); + + const [clickCount, setClickCount] = useState(1); + const doubleOrOneClick = (callback: () => void) => { + return () => { + setClickCount(clickCount + 1); + + const timer = setTimeout(() => { + if (clickCount === 1) { + props.onClick?.(); + } else if (clickCount > 1) { + callback(); + } + setClickCount(1); + }, 200); + + return () => clearTimeout(timer); + }; + }; + + const onRename = () => { + setRename(true); + } + + const contextMenu = (e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + const { x, y } = calculateMousePosition(e) + open({ + position: { + x, + y, + }, + path: `${props.path}`, + name: `${props.name}`, + onRename, + isFile: props.type !== "directory" + }) + } + + const handleOnClick = () => { + if (props.type === "directory") { + // navgative(`/files?path=${props.path}/${props.name}`); + // props.onClick?.(); + doubleOrOneClick(() => { + navgative(`/files?path=${props.path}/${props.name}`); + })(); + } else { + // window.open(`${API}/store/raw/${props.path}/${props.name}`); + doubleOrOneClick(() => { + window.open(`${API}/store/raw${props.path}/${props.name}`); + })(); + } + }; + + return ( +
+
+
{icon}
+
+ { + setRename(false) + console.log(value); + toast.promise(apiClient("/store/rename", { + method: "PATCH", + body: { + oldPath: `${props.path}/${props.name}`, + newPath: `${props.path}/${value}`, + } + }), { + loading: "正在重命名", + success: "重命名成功", + error: "重命名失败", + }) + }} /> + {props.size} +
+
+
+ ); +}; + +export const FilesBreadcrumb = (props: { path: string }) => { + const [paths, setPaths] = useState(<>); + const navgative = useNavigate(); + + useEffect(() => { + const paths = props.path.split("/"); + const pathsList = paths.map((path, index) => { + path = decodeURIComponent(path); + return ( + { + navgative(`/files?path=${paths.slice(0, index + 1).join("/")}`); + }} + > + {path} + + ); + }); + pathsList.unshift( + { + navgative(`/files`); + }} + > + / + + ); + setPaths(pathsList); + }, [props.path]); + + return
{paths}
; +}; diff --git a/src/pages/Files/index.module.css b/src/pages/Files/index.module.css new file mode 100644 index 00000000..74019f8e --- /dev/null +++ b/src/pages/Files/index.module.css @@ -0,0 +1,83 @@ +.container { + margin-top: 30px; + display: flex; +} + +.itemContainer { + cursor: pointer; + display: grid; + justify-items: center; + align-items: center; + margin: 0 10px; + height: 200px; + width: 200px; + background-color: var(--background-color-primary); + border-radius: 10px; + transition: background-color .2s ease-in-out; + padding: 10px; + +} + + +@media screen and (max-width: 768px) { + .container { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 20px 0px; + margin-left: -14px; + } +} + +.itemContainer:hover { + background-color: var(--background-color-secondary); +} + +.itemIcon { + height: 130px; + width: 130px; + font-size: 50px; + color: var(--text-color-primary); + background-color: var(--background-color-secondary); + border-radius: 10px; + padding: 13px; + display: flex; + justify-content: center; + align-items: center; + margin: auto; +} + +.itemInfo { + display: flex; + flex-direction: column; + align-items: center; + margin-top: 10px; + font-size: 0.8rem; + font-weight: 500; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + width: 150px; + text-align: left; +} + +.itemSize { + font-size: 0.7rem; + font-weight: 400; + color: var(--text-color-primary); +} + +.breadcrumb { + display: inline-block; + margin: 0 10px; + font-size: 0.85rem; +} + +.breadcrumbItem { + color: var(--text-color-primary); + cursor: pointer; + margin-right: 2px; +} + +.selected { + background-color: var(--background-color-quaternary) !important; +} \ No newline at end of file diff --git a/src/pages/Files/index.tsx b/src/pages/Files/index.tsx new file mode 100644 index 00000000..25e6e193 --- /dev/null +++ b/src/pages/Files/index.tsx @@ -0,0 +1,113 @@ +import type { BasicPage } from "@type/basic"; +import styles from "./index.module.css"; +import { Title } from "@components/universal/Title"; +import { useSeo } from "@hooks/useSeo"; +import useSWR from "swr"; +import type { FileItemProps } from "./components"; +import { FilesBreadcrumb, Item } from "./components"; +import { getQueryVariable } from "@utils/url"; +import { useEffect, useState } from "react"; +import clsx from "clsx"; +import { ActionButton, ActionButtons } from "@components/widgets/ActionButtons"; +import { apiClient } from "@utils/request"; +import { toast } from "sonner"; +import { AddOne, Upload } from "@icon-park/react"; + +export const FilesPage: BasicPage = () => { + useSeo("文件 · 管理"); + const path = getQueryVariable("path") || ""; + const { data, mutate } = useSWR(`/store/list${path}`); + const [select, setSelect] = useState([]); + + useEffect(() => { + mutate(); + setSelect([]); + }, [path]); + + return ( + <> + + <div className={clsx("flex", "justify-between")}> + <div> + 文件 · 管理 + <FilesBreadcrumb path={path} /> + </div> + <div> + <ActionButton + label="上传文件" + icon={<Upload />} + action={() => { + const input = document.createElement("input"); + input.type = "file"; + input.onchange = async (e) => { + const file = (e.target as HTMLInputElement).files?.[0]; + if (!file) return; + const formData = new FormData(); + formData.append("file", file); + formData.append("path", path); + toast.promise( + apiClient(`/store/upload?path=${path}`, { + method: "POST", + body: formData, + }).then(() => { + mutate(); + }), + { + loading: "正在上传", + success: "上传成功", + error: "上传失败", + } + ); + }; + input.click(); + }} + /> + <ActionButtons + selectedClassName={styles.selected} + selected={select} + setSelect={setSelect} + deleteFunction={async () => { + toast.promise( + Promise.all( + select.map(async (item) => { + await apiClient(`/store/delete?path=${item}`, { + method: "POST", + body: JSON.stringify({ + path: `${path}/${item}`, + }), + }); + }) + ), + { + loading: "正在删除", + success: "删除成功", + error: "删除失败", + } + ); + }} + /> + </div> + </div> + +
+ {data?.data.map((item: FileItemProps) => { + return ( + { + if (select.includes(item.name)) { + setSelect(select.filter((i) => i !== item.name)); + } else { + setSelect([...select, item.name]); + } + }} + className={clsx(select.includes(item.name) && styles.selected)} + {...item} + key={item.name} + path={path} + /> + ); + })} +
+ + ); +}; diff --git a/src/router/router.tsx b/src/router/router.tsx index 796ea8d4..59d128ad 100644 --- a/src/router/router.tsx +++ b/src/router/router.tsx @@ -22,6 +22,7 @@ import { PostsIndex } from "../pages/Posts/Index"; import { SettingsPage } from "../pages/Settings"; import { StatusPage } from "../pages/Status"; import { EditorPage } from "../pages/Write"; +import { FilesPage } from "@pages/Files"; export const AppRouter = () => { return ( @@ -45,6 +46,7 @@ export const AppRouter = () => { } /> } /> + } /> } /> } /> diff --git a/src/states/app.ts b/src/states/app.ts index d69112f4..b6f18bee 100644 --- a/src/states/app.ts +++ b/src/states/app.ts @@ -9,6 +9,7 @@ export const app = proxy({ // example: ["user", "comments"] -- means there is an error in user and comments service // Gateway's error will not be stored here, it will turn to Internal Server Error Page directly error: [] as any[], + gatewayError: false, }); subscribe(app.error, (state) => { diff --git a/src/utils/mouse.ts b/src/utils/mouse.ts new file mode 100644 index 00000000..af955d1d --- /dev/null +++ b/src/utils/mouse.ts @@ -0,0 +1,7 @@ +export function calculateMousePosition(e: React.MouseEvent) { + const { clientX, clientY } = e; + return { + x: clientX, + y: clientY, + }; +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index d7aef9e6..245f989b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -27,6 +27,7 @@ "@states/*": ["src/states/*"], "@hooks/*": ["src/hooks/*"], "@constants/*": ["src/constants/*"], + "@libs/*": ["src/libs/*"], } }, "include": ["src"], diff --git a/vite.config.ts b/vite.config.ts index ae2690c4..cf6d51d2 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -16,6 +16,7 @@ export default defineConfig({ "@states": path.resolve(__dirname, "src/states"), "@hooks": path.resolve(__dirname, "src/hooks"), "@constants": path.resolve(__dirname, "src/constants"), + "@libs": path.resolve(__dirname, "src/libs"), }, }, });