diff --git a/.gitignore b/.gitignore index f00092f..67a96ce 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,5 @@ dist-ssr /bin /obj /Uploads -/View /appsettings.Development.json /Foxel.sln.DotSettings.user \ No newline at end of file diff --git a/View/bun.lock b/View/bun.lock new file mode 100644 index 0000000..ed9ab06 --- /dev/null +++ b/View/bun.lock @@ -0,0 +1,643 @@ +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "name": "foxel", + "dependencies": { + "@ant-design/v5-patch-for-react-19": "^1.0.3", + "@types/md5": "^2.3.5", + "antd": "^5.24.9", + "md5": "^2.3.0", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "react-router": "^7.5.3", + "react-zoom-pan-pinch": "^3.7.0", + "uuid": "^11.1.0", + }, + "devDependencies": { + "@eslint/js": "^9.25.0", + "@types/react": "^19.1.2", + "@types/react-dom": "^19.1.2", + "@vitejs/plugin-react": "^4.4.1", + "eslint": "^9.25.0", + "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-react-refresh": "^0.4.19", + "globals": "^16.0.0", + "typescript": "~5.8.3", + "typescript-eslint": "^8.30.1", + "vite": "^6.3.5", + }, + }, + }, + "packages": { + "@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="], + + "@ant-design/colors": ["@ant-design/colors@7.2.0", "", { "dependencies": { "@ant-design/fast-color": "^2.0.6" } }, "sha512-bjTObSnZ9C/O8MB/B4OUtd/q9COomuJAR2SYfhxLyHvCKn4EKwCN3e+fWGMo7H5InAyV0wL17jdE9ALrdOW/6A=="], + + "@ant-design/cssinjs": ["@ant-design/cssinjs@1.23.0", "", { "dependencies": { "@babel/runtime": "^7.11.1", "@emotion/hash": "^0.8.0", "@emotion/unitless": "^0.7.5", "classnames": "^2.3.1", "csstype": "^3.1.3", "rc-util": "^5.35.0", "stylis": "^4.3.4" }, "peerDependencies": { "react": ">=16.0.0", "react-dom": ">=16.0.0" } }, "sha512-7GAg9bD/iC9ikWatU9ym+P9ugJhi/WbsTWzcKN6T4gU0aehsprtke1UAaaSxxkjjmkJb3llet/rbUSLPgwlY4w=="], + + "@ant-design/cssinjs-utils": ["@ant-design/cssinjs-utils@1.1.3", "", { "dependencies": { "@ant-design/cssinjs": "^1.21.0", "@babel/runtime": "^7.23.2", "rc-util": "^5.38.0" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-nOoQMLW1l+xR1Co8NFVYiP8pZp3VjIIzqV6D6ShYF2ljtdwWJn5WSsH+7kvCktXL/yhEtWURKOfH5Xz/gzlwsg=="], + + "@ant-design/fast-color": ["@ant-design/fast-color@2.0.6", "", { "dependencies": { "@babel/runtime": "^7.24.7" } }, "sha512-y2217gk4NqL35giHl72o6Zzqji9O7vHh9YmhUVkPtAOpoTCH4uWxo/pr4VE8t0+ChEPs0qo4eJRC5Q1eXWo3vA=="], + + "@ant-design/icons": ["@ant-design/icons@5.6.1", "", { "dependencies": { "@ant-design/colors": "^7.0.0", "@ant-design/icons-svg": "^4.4.0", "@babel/runtime": "^7.24.8", "classnames": "^2.2.6", "rc-util": "^5.31.1" }, "peerDependencies": { "react": ">=16.0.0", "react-dom": ">=16.0.0" } }, "sha512-0/xS39c91WjPAZOWsvi1//zjx6kAp4kxWwctR6kuU6p133w8RU0D2dSCvZC19uQyharg/sAvYxGYWl01BbZZfg=="], + + "@ant-design/icons-svg": ["@ant-design/icons-svg@4.4.2", "", {}, "sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA=="], + + "@ant-design/react-slick": ["@ant-design/react-slick@1.1.2", "", { "dependencies": { "@babel/runtime": "^7.10.4", "classnames": "^2.2.5", "json2mq": "^0.2.0", "resize-observer-polyfill": "^1.5.1", "throttle-debounce": "^5.0.0" }, "peerDependencies": { "react": ">=16.9.0" } }, "sha512-EzlvzE6xQUBrZuuhSAFTdsr4P2bBBHGZwKFemEfq8gIGyIQCxalYfZW/T2ORbtQx5rU69o+WycP3exY/7T1hGA=="], + + "@ant-design/v5-patch-for-react-19": ["@ant-design/v5-patch-for-react-19@1.0.3", "", { "peerDependencies": { "antd": ">=5.22.6", "react": ">=19.0.0", "react-dom": ">=19.0.0" } }, "sha512-iWfZuSUl5kuhqLUw7jJXUQFMMkM7XpW7apmKzQBQHU0cpifYW4A79xIBt9YVO5IBajKpPG5UKP87Ft7Yrw1p/w=="], + + "@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], + + "@babel/compat-data": ["@babel/compat-data@7.27.2", "", {}, "sha512-TUtMJYRPyUb/9aU8f3K0mjmjf6M9N5Woshn2CS6nqJSeJtTtQcpLUXjGt9vbF8ZGff0El99sWkLgzwW3VXnxZQ=="], + + "@babel/core": ["@babel/core@7.27.1", "", { "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.27.1", "@babel/helper-compilation-targets": "^7.27.1", "@babel/helper-module-transforms": "^7.27.1", "@babel/helpers": "^7.27.1", "@babel/parser": "^7.27.1", "@babel/template": "^7.27.1", "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ=="], + + "@babel/generator": ["@babel/generator@7.27.1", "", { "dependencies": { "@babel/parser": "^7.27.1", "@babel/types": "^7.27.1", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" } }, "sha512-UnJfnIpc/+JO0/+KRVQNGU+y5taA5vCbwN8+azkX6beii/ZF+enZJSOKo11ZSzGJjlNfJHfQtmQT8H+9TXPG2w=="], + + "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.27.2", "", { "dependencies": { "@babel/compat-data": "^7.27.2", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ=="], + + "@babel/helper-module-imports": ["@babel/helper-module-imports@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w=="], + + "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.27.1", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", "@babel/traverse": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-9yHn519/8KvTU5BjTVEEeIM3w9/2yXNKoD82JifINImhpKkARMJKPP59kLo+BafpdN5zgNeIcS4jsGDmd3l58g=="], + + "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="], + + "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], + + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="], + + "@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="], + + "@babel/helpers": ["@babel/helpers@7.27.1", "", { "dependencies": { "@babel/template": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-FCvFTm0sWV8Fxhpp2McP5/W53GPllQ9QeQ7SiqGWjMf/LVG07lFa5+pgK05IRhVwtvafT22KF+ZSnM9I545CvQ=="], + + "@babel/parser": ["@babel/parser@7.27.2", "", { "dependencies": { "@babel/types": "^7.27.1" }, "bin": "./bin/babel-parser.js" }, "sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw=="], + + "@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw=="], + + "@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw=="], + + "@babel/runtime": ["@babel/runtime@7.27.1", "", {}, "sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog=="], + + "@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="], + + "@babel/traverse": ["@babel/traverse@7.27.1", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.27.1", "@babel/parser": "^7.27.1", "@babel/template": "^7.27.1", "@babel/types": "^7.27.1", "debug": "^4.3.1", "globals": "^11.1.0" } }, "sha512-ZCYtZciz1IWJB4U61UPu4KEaqyfj+r5T1Q5mqPo+IBpcG9kHv30Z0aD8LXPgC1trYa6rK0orRyAhqUgk4MjmEg=="], + + "@babel/types": ["@babel/types@7.27.1", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q=="], + + "@emotion/hash": ["@emotion/hash@0.8.0", "", {}, "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow=="], + + "@emotion/unitless": ["@emotion/unitless@0.7.5", "", {}, "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg=="], + + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.4", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.25.4", "", { "os": "android", "cpu": "arm" }, "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.4", "", { "os": "android", "cpu": "arm64" }, "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.25.4", "", { "os": "android", "cpu": "x64" }, "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.4", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.4", "", { "os": "linux", "cpu": "arm" }, "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.4", "", { "os": "linux", "cpu": "ia32" }, "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.4", "", { "os": "linux", "cpu": "none" }, "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.4", "", { "os": "linux", "cpu": "none" }, "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.4", "", { "os": "linux", "cpu": "none" }, "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.4", "", { "os": "linux", "cpu": "x64" }, "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.4", "", { "os": "none", "cpu": "arm64" }, "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.4", "", { "os": "none", "cpu": "x64" }, "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.4", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.4", "", { "os": "openbsd", "cpu": "x64" }, "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.4", "", { "os": "sunos", "cpu": "x64" }, "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.4", "", { "os": "win32", "cpu": "x64" }, "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ=="], + + "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.7.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw=="], + + "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.1", "", {}, "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ=="], + + "@eslint/config-array": ["@eslint/config-array@0.20.0", "", { "dependencies": { "@eslint/object-schema": "^2.1.6", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ=="], + + "@eslint/config-helpers": ["@eslint/config-helpers@0.2.2", "", {}, "sha512-+GPzk8PlG0sPpzdU5ZvIRMPidzAnZDl/s9L+y13iodqvb8leL53bTannOrQ/Im7UkpsmFU5Ily5U60LWixnmLg=="], + + "@eslint/core": ["@eslint/core@0.14.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg=="], + + "@eslint/eslintrc": ["@eslint/eslintrc@3.3.1", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ=="], + + "@eslint/js": ["@eslint/js@9.27.0", "", {}, "sha512-G5JD9Tu5HJEu4z2Uo4aHY2sLV64B7CDMXxFzqzjl3NKd6RVzSXNoE80jk7Y0lJkTTkjiIhBAqmlYwjuBY3tvpA=="], + + "@eslint/object-schema": ["@eslint/object-schema@2.1.6", "", {}, "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA=="], + + "@eslint/plugin-kit": ["@eslint/plugin-kit@0.3.1", "", { "dependencies": { "@eslint/core": "^0.14.0", "levn": "^0.4.1" } }, "sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w=="], + + "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], + + "@humanfs/node": ["@humanfs/node@0.16.6", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.3.0" } }, "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw=="], + + "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], + + "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="], + + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.8", "", { "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/set-array": ["@jridgewell/set-array@1.2.1", "", {}, "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.0", "", {}, "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="], + + "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], + + "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], + + "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], + + "@rc-component/async-validator": ["@rc-component/async-validator@5.0.4", "", { "dependencies": { "@babel/runtime": "^7.24.4" } }, "sha512-qgGdcVIF604M9EqjNF0hbUTz42bz/RDtxWdWuU5EQe3hi7M8ob54B6B35rOsvX5eSvIHIzT9iH1R3n+hk3CGfg=="], + + "@rc-component/color-picker": ["@rc-component/color-picker@2.0.1", "", { "dependencies": { "@ant-design/fast-color": "^2.0.6", "@babel/runtime": "^7.23.6", "classnames": "^2.2.6", "rc-util": "^5.38.1" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-WcZYwAThV/b2GISQ8F+7650r5ZZJ043E57aVBFkQ+kSY4C6wdofXgB0hBx+GPGpIU0Z81eETNoDUJMr7oy/P8Q=="], + + "@rc-component/context": ["@rc-component/context@1.4.0", "", { "dependencies": { "@babel/runtime": "^7.10.1", "rc-util": "^5.27.0" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-kFcNxg9oLRMoL3qki0OMxK+7g5mypjgaaJp/pkOis/6rVxma9nJBF/8kCIuTYHUQNr0ii7MxqE33wirPZLJQ2w=="], + + "@rc-component/mini-decimal": ["@rc-component/mini-decimal@1.1.0", "", { "dependencies": { "@babel/runtime": "^7.18.0" } }, "sha512-jS4E7T9Li2GuYwI6PyiVXmxTiM6b07rlD9Ge8uGZSCz3WlzcG5ZK7g5bbuKNeZ9pgUuPK/5guV781ujdVpm4HQ=="], + + "@rc-component/mutate-observer": ["@rc-component/mutate-observer@1.1.0", "", { "dependencies": { "@babel/runtime": "^7.18.0", "classnames": "^2.3.2", "rc-util": "^5.24.4" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-QjrOsDXQusNwGZPf4/qRQasg7UFEj06XiCJ8iuiq/Io7CrHrgVi6Uuetw60WAMG1799v+aM8kyc+1L/GBbHSlw=="], + + "@rc-component/portal": ["@rc-component/portal@1.1.2", "", { "dependencies": { "@babel/runtime": "^7.18.0", "classnames": "^2.3.2", "rc-util": "^5.24.4" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-6f813C0IsasTZms08kfA8kPAGxbbkYToa8ALaiDIGGECU4i9hj8Plgbx0sNJDrey3EtHO30hmdaxtT0138xZcg=="], + + "@rc-component/qrcode": ["@rc-component/qrcode@1.0.0", "", { "dependencies": { "@babel/runtime": "^7.24.7", "classnames": "^2.3.2", "rc-util": "^5.38.0" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-L+rZ4HXP2sJ1gHMGHjsg9jlYBX/SLN2D6OxP9Zn3qgtpMWtO2vUfxVFwiogHpAIqs54FnALxraUy/BCO1yRIgg=="], + + "@rc-component/tour": ["@rc-component/tour@1.15.1", "", { "dependencies": { "@babel/runtime": "^7.18.0", "@rc-component/portal": "^1.0.0-9", "@rc-component/trigger": "^2.0.0", "classnames": "^2.3.2", "rc-util": "^5.24.4" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-Tr2t7J1DKZUpfJuDZWHxyxWpfmj8EZrqSgyMZ+BCdvKZ6r1UDsfU46M/iWAAFBy961Ssfom2kv5f3UcjIL2CmQ=="], + + "@rc-component/trigger": ["@rc-component/trigger@2.2.6", "", { "dependencies": { "@babel/runtime": "^7.23.2", "@rc-component/portal": "^1.1.0", "classnames": "^2.3.2", "rc-motion": "^2.0.0", "rc-resize-observer": "^1.3.1", "rc-util": "^5.44.0" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-/9zuTnWwhQ3S3WT1T8BubuFTT46kvnXgaERR9f4BTKyn61/wpf/BvbImzYBubzJibU707FxwbKszLlHjcLiv1Q=="], + + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.40.2", "", { "os": "android", "cpu": "arm" }, "sha512-JkdNEq+DFxZfUwxvB58tHMHBHVgX23ew41g1OQinthJ+ryhdRk67O31S7sYw8u2lTjHUPFxwar07BBt1KHp/hg=="], + + "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.40.2", "", { "os": "android", "cpu": "arm64" }, "sha512-13unNoZ8NzUmnndhPTkWPWbX3vtHodYmy+I9kuLxN+F+l+x3LdVF7UCu8TWVMt1POHLh6oDHhnOA04n8oJZhBw=="], + + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.40.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Gzf1Hn2Aoe8VZzevHostPX23U7N5+4D36WJNHK88NZHCJr7aVMG4fadqkIf72eqVPGjGc0HJHNuUaUcxiR+N/w=="], + + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.40.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-47N4hxa01a4x6XnJoskMKTS8XZ0CZMd8YTbINbi+w03A2w4j1RTlnGHOz/P0+Bg1LaVL6ufZyNprSg+fW5nYQQ=="], + + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.40.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-8t6aL4MD+rXSHHZUR1z19+9OFJ2rl1wGKvckN47XFRVO+QL/dUSpKA2SLRo4vMg7ELA8pzGpC+W9OEd1Z/ZqoQ=="], + + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.40.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-C+AyHBzfpsOEYRFjztcYUFsH4S7UsE9cDtHCtma5BK8+ydOZYgMmWg1d/4KBytQspJCld8ZIujFMAdKG1xyr4Q=="], + + "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.40.2", "", { "os": "linux", "cpu": "arm" }, "sha512-de6TFZYIvJwRNjmW3+gaXiZ2DaWL5D5yGmSYzkdzjBDS3W+B9JQ48oZEsmMvemqjtAFzE16DIBLqd6IQQRuG9Q=="], + + "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.40.2", "", { "os": "linux", "cpu": "arm" }, "sha512-urjaEZubdIkacKc930hUDOfQPysezKla/O9qV+O89enqsqUmQm8Xj8O/vh0gHg4LYfv7Y7UsE3QjzLQzDYN1qg=="], + + "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.40.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-KlE8IC0HFOC33taNt1zR8qNlBYHj31qGT1UqWqtvR/+NuCVhfufAq9fxO8BMFC22Wu0rxOwGVWxtCMvZVLmhQg=="], + + "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.40.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-j8CgxvfM0kbnhu4XgjnCWJQyyBOeBI1Zq91Z850aUddUmPeQvuAy6OiMdPS46gNFgy8gN1xkYyLgwLYZG3rBOg=="], + + "@rollup/rollup-linux-loongarch64-gnu": ["@rollup/rollup-linux-loongarch64-gnu@4.40.2", "", { "os": "linux", "cpu": "none" }, "sha512-Ybc/1qUampKuRF4tQXc7G7QY9YRyeVSykfK36Y5Qc5dmrIxwFhrOzqaVTNoZygqZ1ZieSWTibfFhQ5qK8jpWxw=="], + + "@rollup/rollup-linux-powerpc64le-gnu": ["@rollup/rollup-linux-powerpc64le-gnu@4.40.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-3FCIrnrt03CCsZqSYAOW/k9n625pjpuMzVfeI+ZBUSDT3MVIFDSPfSUgIl9FqUftxcUXInvFah79hE1c9abD+Q=="], + + "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.40.2", "", { "os": "linux", "cpu": "none" }, "sha512-QNU7BFHEvHMp2ESSY3SozIkBPaPBDTsfVNGx3Xhv+TdvWXFGOSH2NJvhD1zKAT6AyuuErJgbdvaJhYVhVqrWTg=="], + + "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.40.2", "", { "os": "linux", "cpu": "none" }, "sha512-5W6vNYkhgfh7URiXTO1E9a0cy4fSgfE4+Hl5agb/U1sa0kjOLMLC1wObxwKxecE17j0URxuTrYZZME4/VH57Hg=="], + + "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.40.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-B7LKIz+0+p348JoAL4X/YxGx9zOx3sR+o6Hj15Y3aaApNfAshK8+mWZEf759DXfRLeL2vg5LYJBB7DdcleYCoQ=="], + + "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.40.2", "", { "os": "linux", "cpu": "x64" }, "sha512-lG7Xa+BmBNwpjmVUbmyKxdQJ3Q6whHjMjzQplOs5Z+Gj7mxPtWakGHqzMqNER68G67kmCX9qX57aRsW5V0VOng=="], + + "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.40.2", "", { "os": "linux", "cpu": "x64" }, "sha512-tD46wKHd+KJvsmije4bUskNuvWKFcTOIM9tZ/RrmIvcXnbi0YK/cKS9FzFtAm7Oxi2EhV5N2OpfFB348vSQRXA=="], + + "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.40.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-Bjv/HG8RRWLNkXwQQemdsWw4Mg+IJ29LK+bJPW2SCzPKOUaMmPEppQlu/Fqk1d7+DX3V7JbFdbkh/NMmurT6Pg=="], + + "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.40.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-dt1llVSGEsGKvzeIO76HToiYPNPYPkmjhMHhP00T9S4rDern8P2ZWvWAQUEJ+R1UdMWJ/42i/QqJ2WV765GZcA=="], + + "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.40.2", "", { "os": "win32", "cpu": "x64" }, "sha512-bwspbWB04XJpeElvsp+DCylKfF4trJDa2Y9Go8O6A7YLX2LIKGcNK/CYImJN6ZP4DcuOHB4Utl3iCbnR62DudA=="], + + "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="], + + "@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="], + + "@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="], + + "@types/babel__traverse": ["@types/babel__traverse@7.20.7", "", { "dependencies": { "@babel/types": "^7.20.7" } }, "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng=="], + + "@types/estree": ["@types/estree@1.0.7", "", {}, "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="], + + "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], + + "@types/md5": ["@types/md5@2.3.5", "", {}, "sha512-/i42wjYNgE6wf0j2bcTX6kuowmdL/6PE4IVitMpm2eYKBUuYCprdcWVK+xEF0gcV6ufMCRhtxmReGfc6hIK7Jw=="], + + "@types/react": ["@types/react@19.1.4", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-EB1yiiYdvySuIITtD5lhW4yPyJ31RkJkkDw794LaQYrxCSaQV/47y5o1FMC4zF9ZyjUjzJMZwbovEnT5yHTW6g=="], + + "@types/react-dom": ["@types/react-dom@19.1.5", "", { "peerDependencies": { "@types/react": "^19.0.0" } }, "sha512-CMCjrWucUBZvohgZxkjd6S9h0nZxXjzus6yDfUb+xLxYM7VvjKNH1tQrE9GWLql1XoOP4/Ds3bwFqShHUYraGg=="], + + "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.32.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.32.1", "@typescript-eslint/type-utils": "8.32.1", "@typescript-eslint/utils": "8.32.1", "@typescript-eslint/visitor-keys": "8.32.1", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-6u6Plg9nP/J1GRpe/vcjjabo6Uc5YQPAMxsgQyGC/I0RuukiG1wIe3+Vtg3IrSCVJDmqK3j8adrtzXSENRtFgg=="], + + "@typescript-eslint/parser": ["@typescript-eslint/parser@8.32.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.32.1", "@typescript-eslint/types": "8.32.1", "@typescript-eslint/typescript-estree": "8.32.1", "@typescript-eslint/visitor-keys": "8.32.1", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-LKMrmwCPoLhM45Z00O1ulb6jwyVr2kr3XJp+G+tSEZcbauNnScewcQwtJqXDhXeYPDEjZ8C1SjXm015CirEmGg=="], + + "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.32.1", "", { "dependencies": { "@typescript-eslint/types": "8.32.1", "@typescript-eslint/visitor-keys": "8.32.1" } }, "sha512-7IsIaIDeZn7kffk7qXC3o6Z4UblZJKV3UBpkvRNpr5NSyLji7tvTcvmnMNYuYLyh26mN8W723xpo3i4MlD33vA=="], + + "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.32.1", "", { "dependencies": { "@typescript-eslint/typescript-estree": "8.32.1", "@typescript-eslint/utils": "8.32.1", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-mv9YpQGA8iIsl5KyUPi+FGLm7+bA4fgXaeRcFKRDRwDMu4iwrSHeDPipwueNXhdIIZltwCJv+NkxftECbIZWfA=="], + + "@typescript-eslint/types": ["@typescript-eslint/types@8.32.1", "", {}, "sha512-YmybwXUJcgGqgAp6bEsgpPXEg6dcCyPyCSr0CAAueacR/CCBi25G3V8gGQ2kRzQRBNol7VQknxMs9HvVa9Rvfg=="], + + "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.32.1", "", { "dependencies": { "@typescript-eslint/types": "8.32.1", "@typescript-eslint/visitor-keys": "8.32.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-Y3AP9EIfYwBb4kWGb+simvPaqQoT5oJuzzj9m0i6FCY6SPvlomY2Ei4UEMm7+FXtlNJbor80ximyslzaQF6xhg=="], + + "@typescript-eslint/utils": ["@typescript-eslint/utils@8.32.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.32.1", "@typescript-eslint/types": "8.32.1", "@typescript-eslint/typescript-estree": "8.32.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-DsSFNIgLSrc89gpq1LJB7Hm1YpuhK086DRDJSNrewcGvYloWW1vZLHBTIvarKZDcAORIy/uWNx8Gad+4oMpkSA=="], + + "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.32.1", "", { "dependencies": { "@typescript-eslint/types": "8.32.1", "eslint-visitor-keys": "^4.2.0" } }, "sha512-ar0tjQfObzhSaW3C3QNmTc5ofj0hDoNQ5XWrCy6zDyabdr0TWhCkClp+rywGNj/odAFBVzzJrK4tEq5M4Hmu4w=="], + + "@vitejs/plugin-react": ["@vitejs/plugin-react@4.4.1", "", { "dependencies": { "@babel/core": "^7.26.10", "@babel/plugin-transform-react-jsx-self": "^7.25.9", "@babel/plugin-transform-react-jsx-source": "^7.25.9", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0" } }, "sha512-IpEm5ZmeXAP/osiBXVVP5KjFMzbWOonMs0NaQQl+xYnUAcq4oHUBsF2+p4MgKWG4YMmFYJU8A6sxRPuowllm6w=="], + + "acorn": ["acorn@8.14.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg=="], + + "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], + + "ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="], + + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "antd": ["antd@5.25.1", "", { "dependencies": { "@ant-design/colors": "^7.2.0", "@ant-design/cssinjs": "^1.23.0", "@ant-design/cssinjs-utils": "^1.1.3", "@ant-design/fast-color": "^2.0.6", "@ant-design/icons": "^5.6.1", "@ant-design/react-slick": "~1.1.2", "@babel/runtime": "^7.26.0", "@rc-component/color-picker": "~2.0.1", "@rc-component/mutate-observer": "^1.1.0", "@rc-component/qrcode": "~1.0.0", "@rc-component/tour": "~1.15.1", "@rc-component/trigger": "^2.2.6", "classnames": "^2.5.1", "copy-to-clipboard": "^3.3.3", "dayjs": "^1.11.11", "rc-cascader": "~3.34.0", "rc-checkbox": "~3.5.0", "rc-collapse": "~3.9.0", "rc-dialog": "~9.6.0", "rc-drawer": "~7.2.0", "rc-dropdown": "~4.2.1", "rc-field-form": "~2.7.0", "rc-image": "~7.12.0", "rc-input": "~1.8.0", "rc-input-number": "~9.5.0", "rc-mentions": "~2.20.0", "rc-menu": "~9.16.1", "rc-motion": "^2.9.5", "rc-notification": "~5.6.4", "rc-pagination": "~5.1.0", "rc-picker": "~4.11.3", "rc-progress": "~4.0.0", "rc-rate": "~2.13.1", "rc-resize-observer": "^1.4.3", "rc-segmented": "~2.7.0", "rc-select": "~14.16.7", "rc-slider": "~11.1.8", "rc-steps": "~6.0.1", "rc-switch": "~4.1.0", "rc-table": "~7.50.4", "rc-tabs": "~15.6.1", "rc-textarea": "~1.10.0", "rc-tooltip": "~6.4.0", "rc-tree": "~5.13.1", "rc-tree-select": "~5.27.0", "rc-upload": "~4.9.0", "rc-util": "^5.44.4", "scroll-into-view-if-needed": "^3.1.0", "throttle-debounce": "^5.0.2" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-4KC7KuPCjr0z3Vuw9DsF+ceqJaPLbuUI3lOX1sY8ix25ceamp+P8yxOmk3Y2JHCD2ZAhq+5IQ/DTJRN2adWYKQ=="], + + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="], + + "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], + + "browserslist": ["browserslist@4.24.5", "", { "dependencies": { "caniuse-lite": "^1.0.30001716", "electron-to-chromium": "^1.5.149", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw=="], + + "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], + + "caniuse-lite": ["caniuse-lite@1.0.30001718", "", {}, "sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw=="], + + "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + + "charenc": ["charenc@0.0.2", "", {}, "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA=="], + + "classnames": ["classnames@2.5.1", "", {}, "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow=="], + + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + + "compute-scroll-into-view": ["compute-scroll-into-view@3.1.1", "", {}, "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw=="], + + "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], + + "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], + + "cookie": ["cookie@1.0.2", "", {}, "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA=="], + + "copy-to-clipboard": ["copy-to-clipboard@3.3.3", "", { "dependencies": { "toggle-selection": "^1.0.6" } }, "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA=="], + + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + + "crypt": ["crypt@0.0.2", "", {}, "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow=="], + + "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], + + "dayjs": ["dayjs@1.11.13", "", {}, "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg=="], + + "debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="], + + "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], + + "electron-to-chromium": ["electron-to-chromium@1.5.155", "", {}, "sha512-ps5KcGGmwL8VaeJlvlDlu4fORQpv3+GIcF5I3f9tUKUlJ/wsysh6HU8P5L1XWRYeXfA0oJd4PyM8ds8zTFf6Ng=="], + + "esbuild": ["esbuild@0.25.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.4", "@esbuild/android-arm": "0.25.4", "@esbuild/android-arm64": "0.25.4", "@esbuild/android-x64": "0.25.4", "@esbuild/darwin-arm64": "0.25.4", "@esbuild/darwin-x64": "0.25.4", "@esbuild/freebsd-arm64": "0.25.4", "@esbuild/freebsd-x64": "0.25.4", "@esbuild/linux-arm": "0.25.4", "@esbuild/linux-arm64": "0.25.4", "@esbuild/linux-ia32": "0.25.4", "@esbuild/linux-loong64": "0.25.4", "@esbuild/linux-mips64el": "0.25.4", "@esbuild/linux-ppc64": "0.25.4", "@esbuild/linux-riscv64": "0.25.4", "@esbuild/linux-s390x": "0.25.4", "@esbuild/linux-x64": "0.25.4", "@esbuild/netbsd-arm64": "0.25.4", "@esbuild/netbsd-x64": "0.25.4", "@esbuild/openbsd-arm64": "0.25.4", "@esbuild/openbsd-x64": "0.25.4", "@esbuild/sunos-x64": "0.25.4", "@esbuild/win32-arm64": "0.25.4", "@esbuild/win32-ia32": "0.25.4", "@esbuild/win32-x64": "0.25.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q=="], + + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + + "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], + + "eslint": ["eslint@9.27.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.20.0", "@eslint/config-helpers": "^0.2.1", "@eslint/core": "^0.14.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.27.0", "@eslint/plugin-kit": "^0.3.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.3.0", "eslint-visitor-keys": "^4.2.0", "espree": "^10.3.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-ixRawFQuMB9DZ7fjU3iGGganFDp3+45bPOdaRurcFHSXO1e/sYwUX/FtQZpLZJR6SjMoJH8hR2pPEAfDyCoU2Q=="], + + "eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@5.2.0", "", { "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg=="], + + "eslint-plugin-react-refresh": ["eslint-plugin-react-refresh@0.4.20", "", { "peerDependencies": { "eslint": ">=8.40" } }, "sha512-XpbHQ2q5gUF8BGOX4dHe+71qoirYMhApEPZ7sfhF/dNnOF1UXnCMGZf79SFTBO7Bz5YEIT4TMieSlJBWhP9WBA=="], + + "eslint-scope": ["eslint-scope@8.3.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ=="], + + "eslint-visitor-keys": ["eslint-visitor-keys@4.2.0", "", {}, "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw=="], + + "espree": ["espree@10.3.0", "", { "dependencies": { "acorn": "^8.14.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.0" } }, "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg=="], + + "esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="], + + "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], + + "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], + + "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], + + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + + "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], + + "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], + + "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], + + "fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="], + + "fdir": ["fdir@6.4.4", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg=="], + + "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], + + "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], + + "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], + + "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], + + "flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="], + + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], + + "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], + + "globals": ["globals@16.1.0", "", {}, "sha512-aibexHNbb/jiUSObBgpHLj+sIuUmJnYcgXBlrfsiDZ9rt4aF2TFRbyLgZ2iFQuVZ1K5Mx3FVkbKRSgKrbK3K2g=="], + + "graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="], + + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + + "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], + + "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], + + "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], + + "is-buffer": ["is-buffer@1.1.6", "", {}, "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="], + + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], + + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], + + "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], + + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + + "js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="], + + "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], + + "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], + + "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], + + "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], + + "json2mq": ["json2mq@0.2.0", "", { "dependencies": { "string-convert": "^0.2.0" } }, "sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA=="], + + "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], + + "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], + + "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], + + "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], + + "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], + + "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], + + "md5": ["md5@2.3.0", "", { "dependencies": { "charenc": "0.0.2", "crypt": "0.0.2", "is-buffer": "~1.1.6" } }, "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g=="], + + "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], + + "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], + + "minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + + "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], + + "node-releases": ["node-releases@2.0.19", "", {}, "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw=="], + + "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], + + "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], + + "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], + + "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], + + "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], + + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "picomatch": ["picomatch@4.0.2", "", {}, "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="], + + "postcss": ["postcss@8.5.3", "", { "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A=="], + + "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], + + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], + + "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], + + "rc-cascader": ["rc-cascader@3.34.0", "", { "dependencies": { "@babel/runtime": "^7.25.7", "classnames": "^2.3.1", "rc-select": "~14.16.2", "rc-tree": "~5.13.0", "rc-util": "^5.43.0" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-KpXypcvju9ptjW9FaN2NFcA2QH9E9LHKq169Y0eWtH4e/wHQ5Wh5qZakAgvb8EKZ736WZ3B0zLLOBsrsja5Dag=="], + + "rc-checkbox": ["rc-checkbox@3.5.0", "", { "dependencies": { "@babel/runtime": "^7.10.1", "classnames": "^2.3.2", "rc-util": "^5.25.2" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-aOAQc3E98HteIIsSqm6Xk2FPKIER6+5vyEFMZfo73TqM+VVAIqOkHoPjgKLqSNtVLWScoaM7vY2ZrGEheI79yg=="], + + "rc-collapse": ["rc-collapse@3.9.0", "", { "dependencies": { "@babel/runtime": "^7.10.1", "classnames": "2.x", "rc-motion": "^2.3.4", "rc-util": "^5.27.0" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-swDdz4QZ4dFTo4RAUMLL50qP0EY62N2kvmk2We5xYdRwcRn8WcYtuetCJpwpaCbUfUt5+huLpVxhvmnK+PHrkA=="], + + "rc-dialog": ["rc-dialog@9.6.0", "", { "dependencies": { "@babel/runtime": "^7.10.1", "@rc-component/portal": "^1.0.0-8", "classnames": "^2.2.6", "rc-motion": "^2.3.0", "rc-util": "^5.21.0" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-ApoVi9Z8PaCQg6FsUzS8yvBEQy0ZL2PkuvAgrmohPkN3okps5WZ5WQWPc1RNuiOKaAYv8B97ACdsFU5LizzCqg=="], + + "rc-drawer": ["rc-drawer@7.2.0", "", { "dependencies": { "@babel/runtime": "^7.23.9", "@rc-component/portal": "^1.1.1", "classnames": "^2.2.6", "rc-motion": "^2.6.1", "rc-util": "^5.38.1" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-9lOQ7kBekEJRdEpScHvtmEtXnAsy+NGDXiRWc2ZVC7QXAazNVbeT4EraQKYwCME8BJLa8Bxqxvs5swwyOepRwg=="], + + "rc-dropdown": ["rc-dropdown@4.2.1", "", { "dependencies": { "@babel/runtime": "^7.18.3", "@rc-component/trigger": "^2.0.0", "classnames": "^2.2.6", "rc-util": "^5.44.1" }, "peerDependencies": { "react": ">=16.11.0", "react-dom": ">=16.11.0" } }, "sha512-YDAlXsPv3I1n42dv1JpdM7wJ+gSUBfeyPK59ZpBD9jQhK9jVuxpjj3NmWQHOBceA1zEPVX84T2wbdb2SD0UjmA=="], + + "rc-field-form": ["rc-field-form@2.7.0", "", { "dependencies": { "@babel/runtime": "^7.18.0", "@rc-component/async-validator": "^5.0.3", "rc-util": "^5.32.2" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-hgKsCay2taxzVnBPZl+1n4ZondsV78G++XVsMIJCAoioMjlMQR9YwAp7JZDIECzIu2Z66R+f4SFIRrO2DjDNAA=="], + + "rc-image": ["rc-image@7.12.0", "", { "dependencies": { "@babel/runtime": "^7.11.2", "@rc-component/portal": "^1.0.2", "classnames": "^2.2.6", "rc-dialog": "~9.6.0", "rc-motion": "^2.6.2", "rc-util": "^5.34.1" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-cZ3HTyyckPnNnUb9/DRqduqzLfrQRyi+CdHjdqgsyDpI3Ln5UX1kXnAhPBSJj9pVRzwRFgqkN7p9b6HBDjmu/Q=="], + + "rc-input": ["rc-input@1.8.0", "", { "dependencies": { "@babel/runtime": "^7.11.1", "classnames": "^2.2.1", "rc-util": "^5.18.1" }, "peerDependencies": { "react": ">=16.0.0", "react-dom": ">=16.0.0" } }, "sha512-KXvaTbX+7ha8a/k+eg6SYRVERK0NddX8QX7a7AnRvUa/rEH0CNMlpcBzBkhI0wp2C8C4HlMoYl8TImSN+fuHKA=="], + + "rc-input-number": ["rc-input-number@9.5.0", "", { "dependencies": { "@babel/runtime": "^7.10.1", "@rc-component/mini-decimal": "^1.0.1", "classnames": "^2.2.5", "rc-input": "~1.8.0", "rc-util": "^5.40.1" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-bKaEvB5tHebUURAEXw35LDcnRZLq3x1k7GxfAqBMzmpHkDGzjAtnUL8y4y5N15rIFIg5IJgwr211jInl3cipag=="], + + "rc-mentions": ["rc-mentions@2.20.0", "", { "dependencies": { "@babel/runtime": "^7.22.5", "@rc-component/trigger": "^2.0.0", "classnames": "^2.2.6", "rc-input": "~1.8.0", "rc-menu": "~9.16.0", "rc-textarea": "~1.10.0", "rc-util": "^5.34.1" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-w8HCMZEh3f0nR8ZEd466ATqmXFCMGMN5UFCzEUL0bM/nGw/wOS2GgRzKBcm19K++jDyuWCOJOdgcKGXU3fXfbQ=="], + + "rc-menu": ["rc-menu@9.16.1", "", { "dependencies": { "@babel/runtime": "^7.10.1", "@rc-component/trigger": "^2.0.0", "classnames": "2.x", "rc-motion": "^2.4.3", "rc-overflow": "^1.3.1", "rc-util": "^5.27.0" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-ghHx6/6Dvp+fw8CJhDUHFHDJ84hJE3BXNCzSgLdmNiFErWSOaZNsihDAsKq9ByTALo/xkNIwtDFGIl6r+RPXBg=="], + + "rc-motion": ["rc-motion@2.9.5", "", { "dependencies": { "@babel/runtime": "^7.11.1", "classnames": "^2.2.1", "rc-util": "^5.44.0" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-w+XTUrfh7ArbYEd2582uDrEhmBHwK1ZENJiSJVb7uRxdE7qJSYjbO2eksRXmndqyKqKoYPc9ClpPh5242mV1vA=="], + + "rc-notification": ["rc-notification@5.6.4", "", { "dependencies": { "@babel/runtime": "^7.10.1", "classnames": "2.x", "rc-motion": "^2.9.0", "rc-util": "^5.20.1" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-KcS4O6B4qzM3KH7lkwOB7ooLPZ4b6J+VMmQgT51VZCeEcmghdeR4IrMcFq0LG+RPdnbe/ArT086tGM8Snimgiw=="], + + "rc-overflow": ["rc-overflow@1.4.1", "", { "dependencies": { "@babel/runtime": "^7.11.1", "classnames": "^2.2.1", "rc-resize-observer": "^1.0.0", "rc-util": "^5.37.0" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-3MoPQQPV1uKyOMVNd6SZfONi+f3st0r8PksexIdBTeIYbMX0Jr+k7pHEDvsXtR4BpCv90/Pv2MovVNhktKrwvw=="], + + "rc-pagination": ["rc-pagination@5.1.0", "", { "dependencies": { "@babel/runtime": "^7.10.1", "classnames": "^2.3.2", "rc-util": "^5.38.0" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-8416Yip/+eclTFdHXLKTxZvn70duYVGTvUUWbckCCZoIl3jagqke3GLsFrMs0bsQBikiYpZLD9206Ej4SOdOXQ=="], + + "rc-picker": ["rc-picker@4.11.3", "", { "dependencies": { "@babel/runtime": "^7.24.7", "@rc-component/trigger": "^2.0.0", "classnames": "^2.2.1", "rc-overflow": "^1.3.2", "rc-resize-observer": "^1.4.0", "rc-util": "^5.43.0" }, "peerDependencies": { "date-fns": ">= 2.x", "dayjs": ">= 1.x", "luxon": ">= 3.x", "moment": ">= 2.x", "react": ">=16.9.0", "react-dom": ">=16.9.0" }, "optionalPeers": ["date-fns", "dayjs", "luxon", "moment"] }, "sha512-MJ5teb7FlNE0NFHTncxXQ62Y5lytq6sh5nUw0iH8OkHL/TjARSEvSHpr940pWgjGANpjCwyMdvsEV55l5tYNSg=="], + + "rc-progress": ["rc-progress@4.0.0", "", { "dependencies": { "@babel/runtime": "^7.10.1", "classnames": "^2.2.6", "rc-util": "^5.16.1" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-oofVMMafOCokIUIBnZLNcOZFsABaUw8PPrf1/y0ZBvKZNpOiu5h4AO9vv11Sw0p4Hb3D0yGWuEattcQGtNJ/aw=="], + + "rc-rate": ["rc-rate@2.13.1", "", { "dependencies": { "@babel/runtime": "^7.10.1", "classnames": "^2.2.5", "rc-util": "^5.0.1" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-QUhQ9ivQ8Gy7mtMZPAjLbxBt5y9GRp65VcUyGUMF3N3fhiftivPHdpuDIaWIMOTEprAjZPC08bls1dQB+I1F2Q=="], + + "rc-resize-observer": ["rc-resize-observer@1.4.3", "", { "dependencies": { "@babel/runtime": "^7.20.7", "classnames": "^2.2.1", "rc-util": "^5.44.1", "resize-observer-polyfill": "^1.5.1" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-YZLjUbyIWox8E9i9C3Tm7ia+W7euPItNWSPX5sCcQTYbnwDb5uNpnLHQCG1f22oZWUhLw4Mv2tFmeWe68CDQRQ=="], + + "rc-segmented": ["rc-segmented@2.7.0", "", { "dependencies": { "@babel/runtime": "^7.11.1", "classnames": "^2.2.1", "rc-motion": "^2.4.4", "rc-util": "^5.17.0" }, "peerDependencies": { "react": ">=16.0.0", "react-dom": ">=16.0.0" } }, "sha512-liijAjXz+KnTRVnxxXG2sYDGd6iLL7VpGGdR8gwoxAXy2KglviKCxLWZdjKYJzYzGSUwKDSTdYk8brj54Bn5BA=="], + + "rc-select": ["rc-select@14.16.8", "", { "dependencies": { "@babel/runtime": "^7.10.1", "@rc-component/trigger": "^2.1.1", "classnames": "2.x", "rc-motion": "^2.0.1", "rc-overflow": "^1.3.1", "rc-util": "^5.16.1", "rc-virtual-list": "^3.5.2" }, "peerDependencies": { "react": "*", "react-dom": "*" } }, "sha512-NOV5BZa1wZrsdkKaiK7LHRuo5ZjZYMDxPP6/1+09+FB4KoNi8jcG1ZqLE3AVCxEsYMBe65OBx71wFoHRTP3LRg=="], + + "rc-slider": ["rc-slider@11.1.8", "", { "dependencies": { "@babel/runtime": "^7.10.1", "classnames": "^2.2.5", "rc-util": "^5.36.0" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-2gg/72YFSpKP+Ja5AjC5DPL1YnV8DEITDQrcc1eASrUYjl0esptaBVJBh5nLTXCCp15eD8EuGjwezVGSHhs9tQ=="], + + "rc-steps": ["rc-steps@6.0.1", "", { "dependencies": { "@babel/runtime": "^7.16.7", "classnames": "^2.2.3", "rc-util": "^5.16.1" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-lKHL+Sny0SeHkQKKDJlAjV5oZ8DwCdS2hFhAkIjuQt1/pB81M0cA0ErVFdHq9+jmPmFw1vJB2F5NBzFXLJxV+g=="], + + "rc-switch": ["rc-switch@4.1.0", "", { "dependencies": { "@babel/runtime": "^7.21.0", "classnames": "^2.2.1", "rc-util": "^5.30.0" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-TI8ufP2Az9oEbvyCeVE4+90PDSljGyuwix3fV58p7HV2o4wBnVToEyomJRVyTaZeqNPAp+vqeo4Wnj5u0ZZQBg=="], + + "rc-table": ["rc-table@7.50.5", "", { "dependencies": { "@babel/runtime": "^7.10.1", "@rc-component/context": "^1.4.0", "classnames": "^2.2.5", "rc-resize-observer": "^1.1.0", "rc-util": "^5.44.3", "rc-virtual-list": "^3.14.2" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-FDZu8aolhSYd3v9KOc3lZOVAU77wmRRu44R0Wfb8Oj1dXRUsloFaXMSl6f7yuWZUxArJTli7k8TEOX2mvhDl4A=="], + + "rc-tabs": ["rc-tabs@15.6.1", "", { "dependencies": { "@babel/runtime": "^7.11.2", "classnames": "2.x", "rc-dropdown": "~4.2.0", "rc-menu": "~9.16.0", "rc-motion": "^2.6.2", "rc-resize-observer": "^1.0.0", "rc-util": "^5.34.1" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-/HzDV1VqOsUWyuC0c6AkxVYFjvx9+rFPKZ32ejxX0Uc7QCzcEjTA9/xMgv4HemPKwzBNX8KhGVbbumDjnj92aA=="], + + "rc-textarea": ["rc-textarea@1.10.0", "", { "dependencies": { "@babel/runtime": "^7.10.1", "classnames": "^2.2.1", "rc-input": "~1.8.0", "rc-resize-observer": "^1.0.0", "rc-util": "^5.27.0" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-ai9IkanNuyBS4x6sOL8qu/Ld40e6cEs6pgk93R+XLYg0mDSjNBGey6/ZpDs5+gNLD7urQ14po3V6Ck2dJLt9SA=="], + + "rc-tooltip": ["rc-tooltip@6.4.0", "", { "dependencies": { "@babel/runtime": "^7.11.2", "@rc-component/trigger": "^2.0.0", "classnames": "^2.3.1", "rc-util": "^5.44.3" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-kqyivim5cp8I5RkHmpsp1Nn/Wk+1oeloMv9c7LXNgDxUpGm+RbXJGL+OPvDlcRnx9DBeOe4wyOIl4OKUERyH1g=="], + + "rc-tree": ["rc-tree@5.13.1", "", { "dependencies": { "@babel/runtime": "^7.10.1", "classnames": "2.x", "rc-motion": "^2.0.1", "rc-util": "^5.16.1", "rc-virtual-list": "^3.5.1" }, "peerDependencies": { "react": "*", "react-dom": "*" } }, "sha512-FNhIefhftobCdUJshO7M8uZTA9F4OPGVXqGfZkkD/5soDeOhwO06T/aKTrg0WD8gRg/pyfq+ql3aMymLHCTC4A=="], + + "rc-tree-select": ["rc-tree-select@5.27.0", "", { "dependencies": { "@babel/runtime": "^7.25.7", "classnames": "2.x", "rc-select": "~14.16.2", "rc-tree": "~5.13.0", "rc-util": "^5.43.0" }, "peerDependencies": { "react": "*", "react-dom": "*" } }, "sha512-2qTBTzwIT7LRI1o7zLyrCzmo5tQanmyGbSaGTIf7sYimCklAToVVfpMC6OAldSKolcnjorBYPNSKQqJmN3TCww=="], + + "rc-upload": ["rc-upload@4.9.0", "", { "dependencies": { "@babel/runtime": "^7.18.3", "classnames": "^2.2.5", "rc-util": "^5.2.0" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-pAzlPnyiFn1GCtEybEG2m9nXNzQyWXqWV2xFYCmDxjN9HzyjS5Pz2F+pbNdYw8mMJsixLEKLG0wVy9vOGxJMJA=="], + + "rc-util": ["rc-util@5.44.4", "", { "dependencies": { "@babel/runtime": "^7.18.3", "react-is": "^18.2.0" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-resueRJzmHG9Q6rI/DfK6Kdv9/Lfls05vzMs1Sk3M2P+3cJa+MakaZyWY8IPfehVuhPJFKrIY1IK4GqbiaiY5w=="], + + "rc-virtual-list": ["rc-virtual-list@3.18.6", "", { "dependencies": { "@babel/runtime": "^7.20.0", "classnames": "^2.2.6", "rc-resize-observer": "^1.0.0", "rc-util": "^5.36.0" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-TQ5SsutL3McvWmmxqQtMIbfeoE3dGjJrRSfKekgby7WQMpPIFvv4ghytp5Z0s3D8Nik9i9YNOCqHBfk86AwgAA=="], + + "react": ["react@19.1.0", "", {}, "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg=="], + + "react-dom": ["react-dom@19.1.0", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.0" } }, "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g=="], + + "react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="], + + "react-refresh": ["react-refresh@0.17.0", "", {}, "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ=="], + + "react-router": ["react-router@7.6.0", "", { "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" }, "optionalPeers": ["react-dom"] }, "sha512-GGufuHIVCJDbnIAXP3P9Sxzq3UUsddG3rrI3ut1q6m0FI6vxVBF3JoPQ38+W/blslLH4a5Yutp8drkEpXoddGQ=="], + + "react-zoom-pan-pinch": ["react-zoom-pan-pinch@3.7.0", "", { "peerDependencies": { "react": "*", "react-dom": "*" } }, "sha512-UmReVZ0TxlKzxSbYiAj+LeGRW8s8LraAFTXRAxzMYnNRgGPsxCudwZKVkjvGmjtx7SW/hZamt69NUmGf4xrkXA=="], + + "resize-observer-polyfill": ["resize-observer-polyfill@1.5.1", "", {}, "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg=="], + + "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], + + "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], + + "rollup": ["rollup@4.40.2", "", { "dependencies": { "@types/estree": "1.0.7" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.40.2", "@rollup/rollup-android-arm64": "4.40.2", "@rollup/rollup-darwin-arm64": "4.40.2", "@rollup/rollup-darwin-x64": "4.40.2", "@rollup/rollup-freebsd-arm64": "4.40.2", "@rollup/rollup-freebsd-x64": "4.40.2", "@rollup/rollup-linux-arm-gnueabihf": "4.40.2", "@rollup/rollup-linux-arm-musleabihf": "4.40.2", "@rollup/rollup-linux-arm64-gnu": "4.40.2", "@rollup/rollup-linux-arm64-musl": "4.40.2", "@rollup/rollup-linux-loongarch64-gnu": "4.40.2", "@rollup/rollup-linux-powerpc64le-gnu": "4.40.2", "@rollup/rollup-linux-riscv64-gnu": "4.40.2", "@rollup/rollup-linux-riscv64-musl": "4.40.2", "@rollup/rollup-linux-s390x-gnu": "4.40.2", "@rollup/rollup-linux-x64-gnu": "4.40.2", "@rollup/rollup-linux-x64-musl": "4.40.2", "@rollup/rollup-win32-arm64-msvc": "4.40.2", "@rollup/rollup-win32-ia32-msvc": "4.40.2", "@rollup/rollup-win32-x64-msvc": "4.40.2", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-tfUOg6DTP4rhQ3VjOO6B4wyrJnGOX85requAXvqYTHsOgb2TFJdZ3aWpT8W2kPoypSGP7dZUyzxJ9ee4buM5Fg=="], + + "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], + + "scheduler": ["scheduler@0.26.0", "", {}, "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="], + + "scroll-into-view-if-needed": ["scroll-into-view-if-needed@3.1.0", "", { "dependencies": { "compute-scroll-into-view": "^3.0.2" } }, "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ=="], + + "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "set-cookie-parser": ["set-cookie-parser@2.7.1", "", {}, "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ=="], + + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], + + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + + "string-convert": ["string-convert@0.2.1", "", {}, "sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A=="], + + "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], + + "stylis": ["stylis@4.3.6", "", {}, "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ=="], + + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + + "throttle-debounce": ["throttle-debounce@5.0.2", "", {}, "sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A=="], + + "tinyglobby": ["tinyglobby@0.2.13", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw=="], + + "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], + + "toggle-selection": ["toggle-selection@1.0.6", "", {}, "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ=="], + + "ts-api-utils": ["ts-api-utils@2.1.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ=="], + + "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], + + "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], + + "typescript-eslint": ["typescript-eslint@8.32.1", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.32.1", "@typescript-eslint/parser": "8.32.1", "@typescript-eslint/utils": "8.32.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-D7el+eaDHAmXvrZBy1zpzSNIRqnCOrkwTgZxTu3MUqRWk8k0q9m9Ho4+vPf7iHtgUfrK/o8IZaEApsxPlHTFCg=="], + + "update-browserslist-db": ["update-browserslist-db@1.1.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw=="], + + "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], + + "uuid": ["uuid@11.1.0", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A=="], + + "vite": ["vite@6.3.5", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ=="], + + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + + "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], + + "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], + + "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], + + "@babel/traverse/globals": ["globals@11.12.0", "", {}, "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="], + + "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], + + "@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], + + "@humanfs/node/@humanwhocodes/retry": ["@humanwhocodes/retry@0.3.1", "", {}, "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA=="], + + "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.4", "", {}, "sha512-gJzzk+PQNznz8ysRrC0aOkBNVRBDtE1n53IqyqEf3PXrYwomFs5q4pGMizBMJF+ykh03insJ27hB8gSrD2Hn8A=="], + + "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + + "@typescript-eslint/typescript-estree/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="], + + "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], + + "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + + "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="], + } +} diff --git a/View/eslint.config.js b/View/eslint.config.js new file mode 100644 index 0000000..092408a --- /dev/null +++ b/View/eslint.config.js @@ -0,0 +1,28 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' + +export default tseslint.config( + { ignores: ['dist'] }, + { + extends: [js.configs.recommended, ...tseslint.configs.recommended], + files: ['**/*.{ts,tsx}'], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + }, + rules: { + ...reactHooks.configs.recommended.rules, + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, + }, +) diff --git a/View/index.html b/View/index.html new file mode 100644 index 0000000..6b46905 --- /dev/null +++ b/View/index.html @@ -0,0 +1,13 @@ + + + + + + + Foxel + + +
+ + + diff --git a/View/nginx.conf b/View/nginx.conf new file mode 100644 index 0000000..fba4868 --- /dev/null +++ b/View/nginx.conf @@ -0,0 +1,38 @@ +worker_processes 1; + +events { worker_connections 1024; } + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + sendfile on; + client_max_body_size 0; + + server { + listen 80; + root /var/www/html; + index index.html; + + location / { + try_files $uri $uri/ /index.html; + } + + location /api { + proxy_pass http://localhost:8080; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection keep-alive; + proxy_set_header Host $host; + proxy_cache_bypass $http_upgrade; + } + + location /uploads { + proxy_pass http://localhost:8080; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection keep-alive; + proxy_set_header Host $host; + proxy_cache_bypass $http_upgrade; + } + } +} \ No newline at end of file diff --git a/View/package.json b/View/package.json new file mode 100644 index 0000000..3ab1466 --- /dev/null +++ b/View/package.json @@ -0,0 +1,36 @@ +{ + "name": "foxel", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@ant-design/v5-patch-for-react-19": "^1.0.3", + "@types/md5": "^2.3.5", + "antd": "^5.24.9", + "md5": "^2.3.0", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "react-router": "^7.5.3", + "react-zoom-pan-pinch": "^3.7.0", + "uuid": "^11.1.0" + }, + "devDependencies": { + "@eslint/js": "^9.25.0", + "@types/react": "^19.1.2", + "@types/react-dom": "^19.1.2", + "@vitejs/plugin-react": "^4.4.1", + "eslint": "^9.25.0", + "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-react-refresh": "^0.4.19", + "globals": "^16.0.0", + "typescript": "~5.8.3", + "typescript-eslint": "^8.30.1", + "vite": "^6.3.5" + } +} diff --git a/View/public/favicon.ico b/View/public/favicon.ico new file mode 100644 index 0000000..53d9685 Binary files /dev/null and b/View/public/favicon.ico differ diff --git a/View/public/images/unavailable.gif b/View/public/images/unavailable.gif new file mode 100644 index 0000000..16aa259 Binary files /dev/null and b/View/public/images/unavailable.gif differ diff --git a/View/public/logo.png b/View/public/logo.png new file mode 100644 index 0000000..ac32700 Binary files /dev/null and b/View/public/logo.png differ diff --git a/View/src/App.css b/View/src/App.css new file mode 100644 index 0000000..a0de6c2 --- /dev/null +++ b/View/src/App.css @@ -0,0 +1,4 @@ +@import "https://chinese-fonts-cdn.deno.dev/packages/maple-mono-cn/dist/MapleMono-CN-Regular/result.css"; +body{ + margin: 0; +} \ No newline at end of file diff --git a/View/src/App.tsx b/View/src/App.tsx new file mode 100644 index 0000000..dab5430 --- /dev/null +++ b/View/src/App.tsx @@ -0,0 +1,73 @@ +import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router'; +import './App.css' +import MainLayout from './layouts/MainLayout'; +import Login from './pages/login/Index'; +import Register from './pages/register/Index'; +import { isAuthenticated } from './api'; +import type { JSX } from 'react'; +import { ConfigProvider } from 'antd'; +import routes from './config/routeConfig'; +import { AuthProvider } from './api/AuthContext'; // 导入 AuthProvider +import AnonymousPage from './pages/anonymous/Index'; + +const PrivateRoute = ({ children }: { children: JSX.Element }) => { + return isAuthenticated() ? children : ; +}; + +const customTheme = { + token: { + colorPrimary: '#18181b', + colorLink: '#444444', + colorBgContainer: '#ffffff', + borderRadius: 10, + fontFamily: '"SF Pro Display", "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif', + boxShadowTertiary: '0 4px 16px rgba(0,0,0,0.05)', + }, + components: { + Button: { + colorPrimary: '#18181b', + algorithm: true, + fontWeight: 500, + }, + Menu: { + itemBg: 'transparent', + colorActiveBarBorderSize: 0, + itemHeight: 46, + itemMarginInline: 12, + iconSize: 17, + fontSize: 15, + itemSelectedColor: '#ffffff', + itemSelectedBg: '#18181b', + itemHoverColor: '#333333', + itemBorderRadius: 8, + }, + } +}; + +function App() { + return ( + + + + + } /> + } /> + } /> + + + + + }> + {routes.map((route) => ( + + ))} + + + + + + ); +} + +export default App; \ No newline at end of file diff --git a/View/src/api/AuthContext.tsx b/View/src/api/AuthContext.tsx new file mode 100644 index 0000000..ab6d834 --- /dev/null +++ b/View/src/api/AuthContext.tsx @@ -0,0 +1,108 @@ +import React, { createContext, useState, useEffect, useContext, useCallback } from 'react'; +import { getCurrentUser, isAuthenticated, clearAuthData, getStoredUser } from './index'; +import type { UserProfile } from './types'; +import { UserRole } from './types'; + +interface AuthContextType { + user: UserProfile | null; + loading: boolean; + authenticated: boolean; + authError: string | null; + logout: () => void; + refreshUser: () => Promise; + hasRole: (requiredRole: UserRole) => boolean; +} + +const AuthContext = createContext({ + user: null, + loading: true, + authenticated: false, + authError: null, + logout: () => {}, + refreshUser: async () => {}, + hasRole: () => false +}); + +export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const [user, setUser] = useState(null); + const [loading, setLoading] = useState(true); + const [authError, setAuthError] = useState(null); + + const refreshUser = useCallback(async () => { + setLoading(true); + setAuthError(null); + + try { + const isLoggedIn = isAuthenticated(); + + if (isLoggedIn) { + const storedUser = getStoredUser(); + if (storedUser) { + setUser(storedUser); + } + + const response = await getCurrentUser(); + + if (response.success && response.data) { + setUser(response.data); + } else if (!storedUser) { + setAuthError(response.message || '获取用户信息失败'); + clearAuthData(); + setUser(null); + } + } else { + clearAuthData(); + setUser(null); + } + } catch (error: any) { + setAuthError(error.message || '获取用户信息时发生错误'); + if (!getStoredUser()) { + clearAuthData(); + setUser(null); + } + } finally { + setLoading(false); + } + }, []); + + const logout = useCallback(() => { + clearAuthData(); + setUser(null); + }, []); + + const hasRole = useCallback((requiredRole: UserRole): boolean => { + if (!user?.roleName) return false; + + // 管理员拥有所有权限 + if (user.roleName === UserRole.Administrator) { + return true; + } + + // 特定角色检查 + return user.roleName === requiredRole; + }, [user]); + + useEffect(() => { + refreshUser(); + }, [refreshUser]); + + const contextValue = { + user, + loading, + authenticated: !!user, + authError, + logout, + refreshUser, + hasRole + }; + + return ( + + {children} + + ); +}; + +export const useAuth = () => useContext(AuthContext); + +export default AuthContext; diff --git a/View/src/api/albumApi.ts b/View/src/api/albumApi.ts new file mode 100644 index 0000000..a0867e5 --- /dev/null +++ b/View/src/api/albumApi.ts @@ -0,0 +1,117 @@ +import type { + PaginatedResult, + AlbumResponse, + CreateAlbumRequest, + UpdateAlbumRequest, + AlbumPictureRequest, + AlbumPicturesRequest, + BaseResult +} from './types'; +import { fetchApi, BASE_URL } from './fetchClient'; + +// 获取相册列表 +export async function getAlbums( + page: number = 1, + pageSize: number = 10, + userId?: number +): Promise> { + try { + const token = localStorage.getItem('token'); + const headers: Record = { + 'Content-Type': 'application/json', + }; + if (token) { + headers['Authorization'] = `Bearer ${token}`; + } + + const queryParams = new URLSearchParams(); + queryParams.append('page', page.toString()); + queryParams.append('pageSize', pageSize.toString()); + if (userId) { + queryParams.append('userId', userId.toString()); + } + + const url = `${BASE_URL}/album/get_albums?${queryParams.toString()}`; + const response = await fetch(url, { headers }); + const data = await response.json(); + + return data as PaginatedResult; + } catch (error) { + console.error('获取相册列表失败:', error); + return { + success: false, + message: '网络请求失败,请检查您的网络连接', + data: [], + page: 1, + pageSize: 10, + totalCount: 0, + totalPages: 0, + code: 500, + }; + } +} + +// 获取单个相册详情 +export async function getAlbumById(id: number): Promise> { + return fetchApi(`/album/get_album/${id}`); +} + +// 创建相册 +export async function createAlbum(data: CreateAlbumRequest): Promise> { + return fetchApi('/album/create_album', { + method: 'POST', + body: JSON.stringify(data), + }); +} + +// 更新相册 +export async function updateAlbum(data: UpdateAlbumRequest): Promise> { + return fetchApi('/album/update_album', { + method: 'POST', + body: JSON.stringify(data), + }); +} + +// 删除相册 +export async function deleteAlbum(id: number): Promise> { + return fetchApi('/album/delete_album', { + method: 'POST', + body: JSON.stringify(id), + }); +} + +// 添加多张图片到相册 +export async function addPicturesToAlbum(albumId: number, pictureIds: number[]): Promise> { + const data: AlbumPicturesRequest = { + albumId, + pictureIds, + }; + return fetchApi('/album/add_pictures', { + method: 'POST', + body: JSON.stringify(data), + }); +} + +// 添加图片到相册 +export async function addPictureToAlbum(albumId: number, pictureId: number): Promise> { + const data: AlbumPictureRequest = { + albumId, + pictureId, + }; + return fetchApi('/album/add_picture', { + method: 'POST', + body: JSON.stringify(data), + }); +} + +// 从相册移除图片 +export async function removePictureFromAlbum(albumId: number, pictureId: number): Promise> { + const data: AlbumPictureRequest = { + albumId, + pictureId, + }; + return fetchApi('/album/remove_picture', { + method: 'POST', + body: JSON.stringify(data), + }); +} diff --git a/View/src/api/authApi.ts b/View/src/api/authApi.ts new file mode 100644 index 0000000..71fc60c --- /dev/null +++ b/View/src/api/authApi.ts @@ -0,0 +1,155 @@ +import { type BaseResult, type AuthResponse, type LoginRequest, type RegisterRequest, type UserProfile } from './types'; +import { fetchApi, BASE_URL } from './fetchClient'; + +// 认证数据本地存储键 +const TOKEN_KEY = 'token'; +const USER_KEY = 'user'; + +// 用户注册 +export async function register(data: RegisterRequest): Promise> { + return fetchApi('/auth/register', { + method: 'POST', + body: JSON.stringify(data), + }); +} + +// 用户登录 +export async function login(data: LoginRequest): Promise> { + const response = await fetchApi('/auth/login', { + method: 'POST', + body: JSON.stringify(data), + }); + + if (response.success && response.data) { + clearAuthData(); // 清除旧的认证数据 + console.log('登录成功,保存认证数据:', response.data); + saveAuthData(response.data); // 保存新的认证数据 + } + + return response; +} + +// 获取当前登录用户 +export async function getCurrentUser(): Promise> { + try { + const token = getToken(); + + if (!token) { + return { + success: false, + message: '用户未登录', + code: 401 + }; + } + + const response = await fetchApi('/auth/get_current_user'); + + // 如果成功获取到用户数据,更新本地存储 + if (response.success && response.data) { + localStorage.setItem(USER_KEY, JSON.stringify(response.data)); + } + + return response; + } catch (error: any) { + return { + success: false, + message: `获取用户信息失败: ${error.message}`, + code: 500 + }; + } +} + +// 保存认证数据到本地存储 +export const saveAuthData = (authData: AuthResponse): void => { + localStorage.setItem(TOKEN_KEY, authData.token); + if (authData.user) { + localStorage.setItem(USER_KEY, JSON.stringify(authData.user)); + } +}; + +// 清除认证数据 +export const clearAuthData = (): void => { + localStorage.removeItem(TOKEN_KEY); + localStorage.removeItem(USER_KEY); +}; + +// 检查是否已认证 +export const isAuthenticated = (): boolean => { + return !!getToken(); +}; + +// 获取存储的用户信息 +export const getStoredUser = (): UserProfile | null => { + try { + const userJson = localStorage.getItem(USER_KEY); + if (!userJson) return null; + + return JSON.parse(userJson) as UserProfile; + } catch (error) { + return null; + } +}; + +// 获取存储的令牌 +export const getToken = (): string | null => { + return localStorage.getItem(TOKEN_KEY); +}; + +// 处理GitHub OAuth回调,接收token并保存 +export function handleOAuthCallback(): boolean { + const urlParams = new URLSearchParams(window.location.search); + const token = urlParams.get('token'); + const error = urlParams.get('error'); + + if (error) return false; + + if (token) { + const githubUser = parseJwt(token); + if (githubUser) { + // 保存token + localStorage.setItem('token', token); + + // 保存用户信息 + if (githubUser.unique_name && githubUser.email) { + const user: UserProfile = { + id: parseInt(githubUser.nameid), + userName: githubUser.unique_name, + email: githubUser.email, + roleName: '' + }; + localStorage.setItem('user', JSON.stringify(user)); + } + + // 清除URL中的token参数 + const url = new URL(window.location.href); + url.searchParams.delete('token'); + window.history.replaceState({}, document.title, url.toString()); + + return true; + } + } + + return false; +} + +// 解析JWT获取用户信息 +function parseJwt(token: string) { + try { + const base64Url = token.split('.')[1]; + const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/'); + const jsonPayload = decodeURIComponent( + atob(base64) + .split('') + .map(c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)) + .join('') + ); + return JSON.parse(jsonPayload); + } catch { + return null; + } +} + +// 获取GitHub登录URL +export function getGitHubLoginUrl(): string { + return `${BASE_URL}/auth/github/login?returnUrl=${window.location.origin}/api/auth/github/callback`; +} diff --git a/View/src/api/backgroundTaskApi.ts b/View/src/api/backgroundTaskApi.ts new file mode 100644 index 0000000..ec4052f --- /dev/null +++ b/View/src/api/backgroundTaskApi.ts @@ -0,0 +1,17 @@ +import { fetchApi } from './fetchClient'; +import type { BaseResult, PictureProcessingTask } from './types'; + +/** + * 获取当前用户的所有处理任务 + */ +export const getUserTasks = async (): Promise> => { + return fetchApi('/background-tasks/user-tasks'); +}; + +/** + * 获取特定图片的处理状态 + * @param pictureId 图片ID + */ +export const getPictureProcessingStatus = async (pictureId: number): Promise> => { + return fetchApi(`/background-tasks/picture-status/${pictureId}`); +}; diff --git a/View/src/api/configApi.ts b/View/src/api/configApi.ts new file mode 100644 index 0000000..5c6de00 --- /dev/null +++ b/View/src/api/configApi.ts @@ -0,0 +1,74 @@ +import { UserRole, type BaseResult, type ConfigResponse, type SetConfigRequest } from './types'; +import { fetchApi } from './fetchClient'; + +// 获取所有配置 +export const getAllConfigs = async (): Promise> => { + try { + return await fetchApi('/config/get_configs'); + } catch (error: any) { + return { + success: false, + message: `获取配置失败: ${error.message}`, + code: 500 + }; + } +}; + +// 获取单个配置 +export const getConfig = async (key: string): Promise> => { + try { + const queryParams = new URLSearchParams(); + queryParams.append('key', key); + + return await fetchApi(`/config/get_config?${queryParams.toString()}`); + } catch (error: any) { + return { + success: false, + message: `获取配置失败: ${error.message}`, + code: 500 + }; + } +}; + +// 设置配置 +export const setConfig = async (config: SetConfigRequest): Promise> => { + try { + return await fetchApi('/config/set_config', { + method: 'POST', + body: JSON.stringify(config), + }); + } catch (error: any) { + return { + success: false, + message: `设置配置失败: ${error.message}`, + code: 500 + }; + } +}; + +// 删除配置 +export const deleteConfig = async (key: string): Promise> => { + try { + return await fetchApi('/config/delete_config', { + method: 'POST', + body: JSON.stringify(key), + }); + } catch (error: any) { + return { + success: false, + message: `删除配置失败: ${error.message}`, + code: 500 + }; + } +}; + +// 角色权限检查 +export const hasRole = (userRole: string | undefined, requiredRole: UserRole): boolean => { + if (!userRole) return false; + + // 如果是管理员,拥有所有权限 + if (userRole === UserRole.Administrator) return true; + + // 精确匹配角色 + return userRole === requiredRole; +}; diff --git a/View/src/api/fetchClient.ts b/View/src/api/fetchClient.ts new file mode 100644 index 0000000..9f38072 --- /dev/null +++ b/View/src/api/fetchClient.ts @@ -0,0 +1,31 @@ +import type { BaseResult } from './types'; +export const BASE_URL = import.meta.env.PROD ? '/api' : 'http://localhost:5153/api'; + +export async function fetchApi( + url: string, + options: RequestInit = {} +): Promise> { + try { + const token = localStorage.getItem('token'); + const headers: Record = { + 'Content-Type': 'application/json', + ...options.headers as Record, + }; + if (token) { + headers['Authorization'] = `Bearer ${token}`; + } + const response = await fetch(`${BASE_URL}${url}`, { + ...options, + headers, + }); + const data = await response.json(); + return data as BaseResult; + } catch (error) { + console.error('API请求错误:', error); + return { + success: false, + message: '网络请求失败,请检查您的网络连接', + code: 500, + }; + } +} diff --git a/View/src/api/index.ts b/View/src/api/index.ts new file mode 100644 index 0000000..c5ac312 --- /dev/null +++ b/View/src/api/index.ts @@ -0,0 +1,55 @@ +// 重新导出类型 +export * from './authApi'; +export * from './types'; + +// 导出fetch客户端 +export { fetchApi, BASE_URL } from './fetchClient'; + +// 导出Auth API +export { + register, + login, + getCurrentUser, + saveAuthData, + clearAuthData, + isAuthenticated, + getStoredUser +} from './authApi'; + +// 导出Picture API +export { + getPictures, + favoritePicture, + unfavoritePicture, + getUserFavorites, + uploadPicture, + deleteMultiplePictures, // 添加导出删除图片函数 +} from './pictureApi'; + +// 导出Album API +export { + getAlbums, + getAlbumById, + createAlbum, + updateAlbum, + deleteAlbum, + addPictureToAlbum, + addPicturesToAlbum, + removePictureFromAlbum +} from './albumApi'; + +// 导出BackgroundTask API +export { + getUserTasks, + getPictureProcessingStatus, +} from './backgroundTaskApi'; + +// 导出Config API +export { + getAllConfigs, + getConfig, + setConfig, + deleteConfig, + hasRole +} from './configApi'; + diff --git a/View/src/api/pictureApi.ts b/View/src/api/pictureApi.ts new file mode 100644 index 0000000..8ae62ed --- /dev/null +++ b/View/src/api/pictureApi.ts @@ -0,0 +1,198 @@ +import type { PaginatedResult, PictureResponse, FilteredPicturesRequest, BaseResult } from './types'; +import { fetchApi, BASE_URL } from './fetchClient'; + +// 获取图片列表 +export async function getPictures(params: FilteredPicturesRequest = {}): Promise> { + // 添加调试日志 + console.log("Search API 请求参数:", params); + + // 构建查询参数 + const queryParams = new URLSearchParams(); + + // 添加所有非空参数 + if (params.page) queryParams.append('page', params.page.toString()); + if (params.pageSize) queryParams.append('pageSize', params.pageSize.toString()); + if (params.searchQuery) queryParams.append('searchQuery', params.searchQuery); + if (params.tags) queryParams.append('tags', params.tags); + if (params.startDate) queryParams.append('startDate', params.startDate); + if (params.endDate) queryParams.append('endDate', params.endDate); + if (params.userId) queryParams.append('userId', params.userId.toString()); + if (params.sortBy) queryParams.append('sortBy', params.sortBy); + if (params.onlyWithGps !== undefined) queryParams.append('onlyWithGps', params.onlyWithGps.toString()); + if (params.useVectorSearch !== undefined) queryParams.append('useVectorSearch', params.useVectorSearch.toString()); + if (params.similarityThreshold) queryParams.append('similarityThreshold', params.similarityThreshold.toString()); + if (params.excludeAlbumId) queryParams.append('excludeAlbumId', params.excludeAlbumId.toString()); + if (params.albumId) queryParams.append('albumId', params.albumId.toString()); + if (params.onlyFavorites !== undefined) queryParams.append('onlyFavorites', params.onlyFavorites.toString()); + if (params.ownerId !== undefined) queryParams.append('ownerId', params.ownerId.toString()); + if (params.includeAllPublic !== undefined) queryParams.append('includeAllPublic', params.includeAllPublic.toString()); + + // 最终URL调试日志 + const url = `${BASE_URL}/picture/get_pictures?${queryParams.toString()}`; + console.log("发送API请求:", url); + + try { + const token = localStorage.getItem('token'); + const headers: Record = { + 'Content-Type': 'application/json', + }; + if (token) { + headers['Authorization'] = `Bearer ${token}`; + } + + const response = await fetch(url, { headers }); + const data = await response.json(); + + // 添加结果日志 + console.log("API 响应结果:", { + success: data.success, + totalCount: data.totalCount, + resultsCount: data.data?.length || 0 + }); + + return data as PaginatedResult; + } catch (error) { + console.error('获取图片列表失败:', error); + return { + success: false, + message: '网络请求失败,请检查您的网络连接', + data: [], + page: 1, + pageSize: 10, + totalCount: 0, + totalPages: 0, + code: 500, + }; + } +} + +// 收藏图片 +export async function favoritePicture(pictureId: number): Promise> { + return fetchApi('/picture/favorite', { + method: 'POST', + body: JSON.stringify({ pictureId }), + }); +} + +// 取消收藏图片 +export async function unfavoritePicture(pictureId: number): Promise> { + return fetchApi('/picture/unfavorite', { + method: 'POST', + body: JSON.stringify({ pictureId }), + }); +} + +// 获取用户收藏的图片 +export async function getUserFavorites(page: number = 1, pageSize: number = 8): Promise> { + try { + const token = localStorage.getItem('token'); + const headers: Record = { + 'Content-Type': 'application/json', + }; + if (token) { + headers['Authorization'] = `Bearer ${token}`; + } + + const url = `${BASE_URL}/picture/favorites?page=${page}&pageSize=${pageSize}`; + const response = await fetch(url, { headers }); + const data = await response.json(); + + return data as PaginatedResult; + } catch (error) { + console.error('获取收藏图片失败:', error); + return { + success: false, + message: '网络请求失败,请检查您的网络连接', + data: [], + page: 1, + pageSize: 10, + totalCount: 0, + totalPages: 0, + code: 500, + }; + } +} + +// 上传图片 +export async function uploadPicture( + file: File, + data: { + permission?: number; + albumId?: number; + onProgress?: (percent: number) => void + } = {} +): Promise> { + const formData = new FormData(); + formData.append('file', file); + + if (data.permission !== undefined) { + formData.append('permission', data.permission.toString()); + } + + if (data.albumId !== undefined) { + formData.append('albumId', data.albumId.toString()); + } + + try { + const token = localStorage.getItem('token'); + const headers: Record = {}; + if (token) { + headers['Authorization'] = `Bearer ${token}`; + } + + const xhr = new XMLHttpRequest(); + + // 返回一个Promise + return new Promise((resolve, reject) => { + xhr.open('POST', `${BASE_URL}/picture/upload_picture`); + + if (token) { + xhr.setRequestHeader('Authorization', `Bearer ${token}`); + } + + xhr.upload.onprogress = (event) => { + if (event.lengthComputable && data.onProgress) { + const percent = Math.round((event.loaded / event.total) * 100); + data.onProgress(percent); + } + }; + + xhr.onload = () => { + if (xhr.status >= 200 && xhr.status < 300) { + const response = JSON.parse(xhr.responseText); + resolve(response); + } else { + reject({ + status: xhr.status, + message: xhr.statusText || '上传失败', + }); + } + }; + + xhr.onerror = () => { + reject({ + status: xhr.status, + message: '网络错误,上传失败', + }); + }; + + xhr.send(formData); + }); + } catch (error) { + console.error('上传图片失败:', error); + return { + success: false, + message: '上传图片失败', + code: 500, + }; + } +} + +// 删除多张图片 +export async function deleteMultiplePictures(pictureIds: number[]): Promise> { + return fetchApi('/picture/delete_pictures', { + method: 'POST', + body: JSON.stringify({ pictureIds }), + }); +} + diff --git a/View/src/api/tagApi.ts b/View/src/api/tagApi.ts new file mode 100644 index 0000000..8b883c0 --- /dev/null +++ b/View/src/api/tagApi.ts @@ -0,0 +1,111 @@ +import type { BaseResult, PaginatedResult } from './types'; +import { fetchApi, BASE_URL } from './fetchClient'; + +// 标签响应类型 +export interface TagResponse { + id: number; + name: string; + description?: string; + pictureCount: number; + createdAt: Date; +} + +// 筛选标签请求参数 +export interface FilteredTagsRequest { + page?: number; + pageSize?: number; + searchQuery?: string; + sortBy?: string; + sortDirection?: string; +} + +// 创建标签请求 +export interface CreateTagRequest { + name: string; + description?: string; +} + +// 更新标签请求 +export interface UpdateTagRequest { + id: number; + name: string; + description?: string; +} + +// 获取所有标签 +export async function getAllTags(): Promise> { + return fetchApi('/tag/all', { + method: 'GET', + }); +} + +// 获取筛选后的标签 +export async function getFilteredTags(params: FilteredTagsRequest = {}): Promise> { + const queryParams = new URLSearchParams(); + + if (params.page) queryParams.append('page', params.page.toString()); + if (params.pageSize) queryParams.append('pageSize', params.pageSize.toString()); + if (params.searchQuery) queryParams.append('searchQuery', params.searchQuery); + if (params.sortBy) queryParams.append('sortBy', params.sortBy); + if (params.sortDirection) queryParams.append('sortDirection', params.sortDirection); + + try { + const token = localStorage.getItem('token'); + const headers: Record = { + 'Content-Type': 'application/json', + }; + + if (token) { + headers['Authorization'] = `Bearer ${token}`; + } + + const url = `${BASE_URL}/tag/get_tags?${queryParams.toString()}`; + const response = await fetch(url, { headers }); + const data = await response.json(); + + return data as PaginatedResult; + } catch (error) { + console.error('获取标签列表失败:', error); + return { + success: false, + message: '网络请求失败,请检查您的网络连接', + data: [], + page: 1, + pageSize: 10, + totalCount: 0, + totalPages: 0, + code: 500, + }; + } +} + +// 获取标签详情 +export async function getTagById(id: number): Promise> { + return fetchApi(`/tag/${id}`, { + method: 'GET', + }); +} + +// 创建标签 +export async function createTag(request: CreateTagRequest): Promise> { + return fetchApi('/tag/create_tag', { + method: 'POST', + body: JSON.stringify(request), + }); +} + +// 更新标签 +export async function updateTag(request: UpdateTagRequest): Promise> { + return fetchApi('/tag/update_tag', { + method: 'POST', + body: JSON.stringify(request), + }); +} + +// 删除标签 +export async function deleteTag(id: number): Promise> { + return fetchApi('/tag/delete_tag', { + method: 'POST', + body: JSON.stringify(id), + }); +} diff --git a/View/src/api/types.ts b/View/src/api/types.ts new file mode 100644 index 0000000..b5ab430 --- /dev/null +++ b/View/src/api/types.ts @@ -0,0 +1,200 @@ +// API响应的基础结构 +export interface BaseResult { + success: boolean; + message: string; + data?: T; + code: number; +} + +// 分页结果通用结构 +export interface PaginatedResult { + success: boolean; + message: string; + data: T[]; + page: number; + pageSize: number; + totalCount: number; + totalPages: number; + code: number; +} + +// 登录请求参数 +export interface LoginRequest { + email: string; + password: string; +} + +// 注册请求参数 +export interface RegisterRequest { + username: string; + email: string; + password: string; +} + +// 用户信息 +export interface UserProfile { + id: number; + userName: string; + email: string; + roleName: string; +} + +// 认证响应 +export interface AuthResponse { + token: string; + user: UserProfile; +} + +// 图片请求参数 +export interface FilteredPicturesRequest { + page?: number; + pageSize?: number; + searchQuery?: string; + tags?: string; + startDate?: string; + endDate?: string; + userId?: number; + sortBy?: string; + onlyWithGps?: boolean; + useVectorSearch?: boolean; + similarityThreshold?: number; + excludeAlbumId?: number; + albumId?: number; + onlyFavorites?: boolean; + ownerId?: number; + includeAllPublic?: boolean; +} + +// 图片响应数据 +export interface PictureResponse { + id: number; + name: string; + path: string; + thumbnailPath: string; + description: string; + takenAt?: Date; + createdAt: Date; + exifInfo?: any; + tags?: string[]; + userId: number; + username?: string; + isFavorited: boolean; + favoriteCount: number; + permission: number; + albumId?: number; + albumName?: string; + processingStatus: ProcessingStatus; + processingError?: string; + processingProgress: number; +} + +// 收藏请求 +export interface FavoriteRequest { + pictureId: number; +} + +// 上传队列中的文件项 +export interface UploadFile { + id: string; // 本地ID,用于跟踪状态 + file: File; // 原始文件 + status: 'pending' | 'uploading' | 'success' | 'error'; // 上传状态 + percent: number; // 上传进度百分比 0-100 + error?: string; // 错误信息 + response?: PictureResponse; // 上传成功后的响应 +} + +// 上传图片请求参数 +export interface UploadPictureParams { + permission?: number; // 权限设置,默认为0(公开) + albumId?: number; // 相册ID,可选 +} + +// 相册响应数据 +export interface AlbumResponse { + id: number; + name: string; + description: string; + coverImageUrl?: string; + pictureCount: number; + userId: number; + username?: string; + createdAt: Date; + updatedAt: Date; +} + +// 创建相册请求 +export interface CreateAlbumRequest { + name: string; + description: string; +} + +// 更新相册请求 +export interface UpdateAlbumRequest { + id: number; + name: string; + description: string; +} + +// 相册图片操作请求 +export interface AlbumPictureRequest { + albumId: number; + pictureId: number; +} + +// 批量添加图片到相册请求 +export interface AlbumPicturesRequest { + albumId: number; + pictureIds: number[]; +} + +// 删除多张图片请求 +export interface DeleteMultiplePicturesRequest { + pictureIds: number[]; +} + +// 将类型定义改为枚举,这样既可以作为类型也可以作为值使用 +export type ProcessingStatus = 'Pending' | 'Processing' | 'Completed' | 'Failed'; + +// 添加常量对象提供运行时值 +export const ProcessingStatus = { + Pending: 'Pending' as ProcessingStatus, + Processing: 'Processing' as ProcessingStatus, + Completed: 'Completed' as ProcessingStatus, + Failed: 'Failed' as ProcessingStatus +}; + +// 图片处理任务 +export interface PictureProcessingTask { + pictureId: number; + taskId: string; + pictureName: string; + status: ProcessingStatus; + progress: number; // 0-100 + error?: string; + createdAt: Date; + completedAt?: Date; +} + +// 配置响应数据 +export interface ConfigResponse { + id: number; + key: string; + value: string; + description: string; + createdAt: Date; + updatedAt?: Date; +} + +export interface SetConfigRequest { + key: string; + value: string; + description?: string; +} + +export type UserRole = "Administrator" | "User" | ""; + +export const UserRole = { + Administrator: "Administrator" as UserRole, + User: "User" as UserRole, + Guest: "" as UserRole +}; diff --git a/View/src/assets/react.svg b/View/src/assets/react.svg new file mode 100644 index 0000000..6c87de9 --- /dev/null +++ b/View/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/View/src/components/TaskProgressBar.tsx b/View/src/components/TaskProgressBar.tsx new file mode 100644 index 0000000..839b907 --- /dev/null +++ b/View/src/components/TaskProgressBar.tsx @@ -0,0 +1,91 @@ +import React from 'react'; +import { Progress, Tag, Tooltip } from 'antd'; +import { + ClockCircleOutlined, + SyncOutlined, + CheckCircleOutlined, + CloseCircleOutlined +} from '@ant-design/icons'; +import { ProcessingStatus } from '../api/types'; + +interface TaskProgressBarProps { + status: ProcessingStatus; + progress: number; + error?: string; + showLabel?: boolean; + size?: 'small' | 'default'; + className?: string; + style?: React.CSSProperties; +} + +const TaskProgressBar: React.FC = ({ + status, + progress, + error, + showLabel = true, + size = 'default', + className, + style +}) => { + let statusColor = ''; + let icon = null; + let statusText = ''; + let progressStatus: "success" | "exception" | "active" | "normal" | undefined; + + switch (status) { + case ProcessingStatus.Pending: + statusColor = 'orange'; + progressStatus = 'normal'; + icon = ; + statusText = '等待中'; + break; + case ProcessingStatus.Processing: + statusColor = 'processing'; + progressStatus = 'active'; + icon = ; + statusText = '处理中'; + break; + case ProcessingStatus.Completed: + statusColor = 'success'; + progressStatus = 'success'; + icon = ; + statusText = '已完成'; + break; + case ProcessingStatus.Failed: + statusColor = 'error'; + progressStatus = 'exception'; + icon = ; + statusText = '失败'; + break; + } + + return ( +
+ {showLabel && ( +
+ + {statusText} + + {status === ProcessingStatus.Failed && error && ( + + + 查看错误 + + + )} +
+ )} + + + +
+ ); +}; + +export default TaskProgressBar; diff --git a/View/src/components/UserAvatar.tsx b/View/src/components/UserAvatar.tsx new file mode 100644 index 0000000..be8d94e --- /dev/null +++ b/View/src/components/UserAvatar.tsx @@ -0,0 +1,57 @@ +import React from 'react'; +import { Avatar as AntAvatar, type AvatarProps as AntAvatarProps } from 'antd'; +import { UserOutlined } from '@ant-design/icons'; +import md5 from 'md5'; + +interface UserAvatarProps extends Omit { + email?: string; + url?: string; + text?: string; +} + +const UserAvatar: React.FC = ({ + email, + url, + text, + size = 46, + style, + ...restProps +}) => { + // 确定头像源 + const getAvatarSrc = () => { + if (url) { + return url; + } + + if (email) { + const hash = md5(email.toLowerCase().trim()); + return `https://cn.cravatar.com/avatar/${hash}?s=${typeof size === 'number' ? size * 2 : 96}&d=identicon`; + } + + return null; + }; + + const avatarSrc = getAvatarSrc(); + + // 默认样式 + const defaultStyle = { + backgroundColor: !avatarSrc && !text ? '#18181b' : undefined, + cursor: 'pointer', + boxShadow: '0 3px 10px rgba(0,0,0,0.1)', + ...style + }; + + return ( + : null} + {...restProps} + > + {!avatarSrc && text ? text.charAt(0).toUpperCase() : null} + + ); +}; + +export default UserAvatar; diff --git a/View/src/components/image/ImageGrid.css b/View/src/components/image/ImageGrid.css new file mode 100644 index 0000000..7794351 --- /dev/null +++ b/View/src/components/image/ImageGrid.css @@ -0,0 +1,270 @@ +.image-grid { + margin-bottom: 40px; + display: grid; + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); + grid-gap: 24px; +} + +/* 现代化卡片样式 */ +.custom-card { + position: relative; + border-radius: 12px; + overflow: hidden; + box-shadow: 0 8px 20px rgba(0,0,0,0.08); + transition: all 0.35s cubic-bezier(0.23, 1, 0.32, 1); + height: 100%; + background: #ffffff; + transform: translateY(0); + cursor: pointer; + aspect-ratio: 1 / 1; +} + +.custom-card:hover { + transform: translateY(-5px); + box-shadow: 0 14px 28px rgba(0,0,0,0.15); +} + +/* 图片占满卡片 */ +.custom-card-cover { + position: relative; + height: 100%; + width: 100%; + overflow: hidden; +} + +.custom-card-thumbnail { + height: 100%; + width: 100%; + object-fit: cover; + transition: transform 0.7s cubic-bezier(0.23, 1, 0.32, 1); + display: block; +} + +.custom-card:hover .custom-card-thumbnail { + transform: scale(1.05); +} + +/* 信息覆盖层 - 默认隐藏 */ +.custom-card-overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient(to top, rgba(0,0,0,0.85) 0%, rgba(0,0,0,0) 50%); + opacity: 0; + transition: opacity 0.35s ease; + display: flex; + flex-direction: column; + justify-content: flex-end; + padding: 20px; + color: white; +} + +.custom-card:hover .custom-card-overlay { + opacity: 1; +} + +/* 信息内容样式 */ +.custom-card-info { + transform: translateY(20px); + transition: transform 0.4s cubic-bezier(0.23, 1, 0.32, 1); + opacity: 0; +} + +.custom-card:hover .custom-card-info { + transform: translateY(0); + opacity: 1; +} + +.custom-card-title { + font-size: 18px; + font-weight: 600; + color: #ffffff; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + margin-bottom: 8px; + text-shadow: 0 1px 3px rgba(0,0,0,0.3); +} + +.custom-card-tags-container { + margin-top: 6px; + margin-bottom: 10px; + max-width: 100%; + overflow: hidden; +} + +.image-tag { + margin-right: 6px; + font-size: 11px !important; + background: rgba(255,255,255,0.2); + padding: 3px 8px; + border-radius: 4px; + color: #ffffff; + display: inline-block; + margin-bottom: 4px; + backdrop-filter: blur(4px); +} + +/* 权限和元数据指示器 */ +.custom-card-indicators { + position: absolute; + top: 12px; + left: 0; + right: 0; + display: flex; + justify-content: space-between; + padding: 0 12px; + opacity: 0; + transition: opacity 0.35s ease; + z-index: 2; +} + +.custom-card:hover .custom-card-indicators { + opacity: 1; +} + +.custom-card-permission { + background-color: rgba(0, 0, 0, 0.6); + color: white; + border-radius: 20px; + padding: 5px 10px; + font-size: 12px; + font-weight: 500; + display: flex; + align-items: center; + gap: 4px; + backdrop-filter: blur(4px); + box-shadow: 0 2px 8px rgba(0,0,0,0.2); +} + +.custom-card-metadata { + background-color: rgba(0, 0, 0, 0.6); + color: white; + border-radius: 20px; + padding: 5px 10px; + font-size: 12px; + font-weight: 500; + box-shadow: 0 2px 8px rgba(0,0,0,0.2); + backdrop-filter: blur(4px); +} + +/* 操作按钮容器 */ +.custom-card-actions { + display: flex; + justify-content: space-between; + margin-top: 12px; +} + +.custom-card-action-item { + background-color: rgba(255, 255, 255, 0.15); + border-radius: 50%; + width: 36px; + height: 36px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: all 0.3s; + backdrop-filter: blur(4px); +} + +.custom-card-action-item:hover { + background-color: rgba(255, 255, 255, 0.3); + transform: scale(1.1); +} + +/* 右键菜单样式 */ +.context-menu { + background-color: white; + border-radius: 8px; + box-shadow: 0 4px 20px rgba(0,0,0,0.2); + padding: 8px 0; + min-width: 160px; + z-index: 1000; +} + +.context-menu-item { + padding: 10px 16px; + cursor: pointer; + display: flex; + align-items: center; + gap: 10px; + transition: all 0.2s; +} + +.context-menu-item:hover { + background-color: rgba(0, 0, 0, 0.05); +} + +/* 选中状态样式 */ +.custom-card-selected { + box-shadow: 0 0 0 3px #1890ff, 0 14px 28px rgba(0,0,0,0.15) !important; +} + +.custom-card-selected::before { + content: ''; + position: absolute; + top: 10px; + right: 10px; + width: 22px; + height: 22px; + border-radius: 50%; + background-color: #1890ff; + z-index: 5; + display: flex; + align-items: center; + justify-content: center; + box-shadow: 0 2px 8px rgba(0,0,0,0.2); +} + +.custom-card-selected::after { + content: '✓'; + position: absolute; + top: 10px; + right: 10px; + width: 22px; + height: 22px; + z-index: 6; + color: white; + display: flex; + align-items: center; + justify-content: center; + font-size: 14px; +} + +.image-grid-pagination { + margin-top: 40px; + text-align: center; +} + +/* 移除旧的加载动画,不再需要 */ +.image-loading-effect { + position: relative; + overflow: hidden; +} + +.image-loading-effect::after { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent); + animation: loading 1.5s infinite; +} + +@keyframes loading { + 0% { transform: translateX(-100%); } + 100% { transform: translateX(100%); } +} + +/* 响应式调整 */ +@media (max-width: 768px) { + .image-grid { + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + grid-gap: 16px; + } +} diff --git a/View/src/components/image/ImageGrid.tsx b/View/src/components/image/ImageGrid.tsx new file mode 100644 index 0000000..ba9a3a0 --- /dev/null +++ b/View/src/components/image/ImageGrid.tsx @@ -0,0 +1,693 @@ +import React, { useState, useEffect, useCallback, useRef } from 'react'; +import { Typography, Empty, message, Pagination, Modal } from 'antd'; +import { + HeartOutlined, HeartFilled, LockOutlined, GlobalOutlined, TeamOutlined, + DeleteOutlined, EditOutlined, DownloadOutlined, ShareAltOutlined +} from '@ant-design/icons'; +import type { PictureResponse } from '../../api'; +import { favoritePicture, unfavoritePicture, getPictures, deleteMultiplePictures } from '../../api'; +import ImageViewer from './ImageViewer'; +import ShareImageDialog from './ShareImageDialog'; +import './ImageGrid.css'; +import { useAuth } from '../../api/AuthContext'; + +const { Text } = Typography; + +const permissionTypeMap: Record = { + 0: { label: '公开', icon: , color: '#52c41a' }, + 1: { label: '好友可见', icon: , color: '#1890ff' }, + 2: { label: '私人', icon: , color: '#ff4d4f' } +}; + +const formatDate = (dateString: string) => { + try { + if (!dateString) return '-'; + const date = new Date(dateString); + + if (isNaN(date.getTime())) return '-'; + + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + + return `${year}-${month}-${day}`; + } catch (error) { + console.error('日期格式化错误:', error); + return '-'; + } +}; + +// 简化API参数接口 +interface PaginationParams { + page: number; + pageSize: number; + albumId?: number; + excludeAlbumId?: number; + onlyFavorites?: boolean; + tags?: string; + searchQuery?: string; + sortBy?: string; + includeAllPublic?: boolean; + useVectorSearch?: boolean; + similarityThreshold?: number; +} + +// 右键菜单类型接口 +interface ContextMenuState { + visible: boolean; + x: number; + y: number; + imageId?: number; + image?: PictureResponse; +} + +// 简化Props接口,使用默认值 +interface ImageGridProps { + // 核心功能属性 + onToggleFavorite?: (image: PictureResponse) => void; + showFavoriteCount?: boolean; + emptyText?: string; + showPagination?: boolean; + + // 数据源属性集合 + dataSource?: PictureResponse[]; + totalImages?: number; + loading?: boolean; + + // 合并查询相关参数 + queryParams?: { + albumId?: number; + excludeAlbumId?: number; + onlyFavorites?: boolean; + tags?: string[]; + searchQuery?: string; + sortBy?: string; + includeAllPublic?: boolean; + useVectorSearch?: boolean; + similarityThreshold?: number; + _searchId?: number; // 添加搜索ID属性 + }; + + // 分页相关属性 + pageSize?: number; + defaultPage?: number; + onPageChange?: (page: number, pageSize: number) => void; + onImagesLoaded?: (images: PictureResponse[], totalCount: number) => void; + + // 选择模式相关属性 + selectedIds?: number[]; + selectable?: boolean; + onSelectionChange?: (selectedIds: number[]) => void; + + // 新增操作回调 + onDelete?: (image: PictureResponse) => void; + onEdit?: (image: PictureResponse) => void; + onDownload?: (image: PictureResponse) => void; + onShare?: (image: PictureResponse) => void; +} + + +const ImageGrid: React.FC = ({ + // 使用解构赋值时直接设置默认值 + onToggleFavorite, + showFavoriteCount = false, + emptyText = "暂无图片", + showPagination = true, + + dataSource, + totalImages: externalTotalImages, + loading: externalLoading, + + queryParams = {}, + + pageSize: externalPageSize = 20, + defaultPage = 1, + onPageChange, + onImagesLoaded, + + selectedIds = [], + selectable = false, + onSelectionChange, + + onDelete, + onEdit, + onDownload, + onShare, +}) => { + // 获取当前登录用户信息 + const { user, hasRole } = useAuth(); + + // 使用更紧凑的状态定义 + const [images, setImages] = useState([]); + const [loading, setLoading] = useState(true); + const [currentPage, setCurrentPage] = useState(defaultPage); + const [pageSize, setPageSize] = useState(externalPageSize); + const [totalImages, setTotalImages] = useState(0); + const [viewerState, setViewerState] = useState({ visible: false, index: 0 }); + const [shareDialogState, setShareDialogState] = useState<{ + visible: boolean; + image: PictureResponse | null; + }>({ + visible: false, + image: null + }); + + // 添加右键菜单状态 + const [contextMenu, setContextMenu] = useState({ + visible: false, + x: 0, + y: 0, + }); + + + // 简化标志变量 + const isUsingExternalData = !!dataSource; + const isLoading = isUsingExternalData ? externalLoading : loading; + + // 请求状态追踪 + const requestState = useRef({ + inProgress: false, + lastParams: '', + noResultsFor: '' // 新增:记录哪些查询参数没有返回结果 + }); + + // 优化构建查询参数函数 + const buildQueryParams = useCallback((): PaginationParams => { + const params: PaginationParams = { + page: currentPage, + pageSize, + ...queryParams, + searchQuery: queryParams.searchQuery, + tags: Array.isArray(queryParams.tags) ? queryParams.tags.join(',') : undefined, + useVectorSearch: queryParams.useVectorSearch, + similarityThreshold: queryParams.similarityThreshold + }; + return params; + }, [currentPage, pageSize, queryParams]); + + // 优化加载数据函数,减少依赖项 + const loadImages = useCallback(async () => { + if (isUsingExternalData || requestState.current.inProgress) return; + + const params = buildQueryParams(); + const paramsString = JSON.stringify(params); + + // 检查是否是已知没有结果的查询 + if (requestState.current.noResultsFor === paramsString) { + // 如果这个查询之前没有结果,且当前还是空结果状态,不再重复请求 + if (images.length === 0) { + return; + } + // 如果之前没结果但现在有图片显示,重置noResultsFor让新查询可以执行 + requestState.current.noResultsFor = ''; + } + + if (requestState.current.lastParams === paramsString) { + // 如果参数没变且已有数据或无数据状态已确认,跳过请求 + return; + } + + requestState.current = { + inProgress: true, + lastParams: paramsString, + noResultsFor: requestState.current.noResultsFor + }; + + setLoading(true); + + try { + const result = await getPictures(params); + + // 无论成功与否,都更新lastParams以避免相同参数的重复请求 + requestState.current.lastParams = paramsString; + + if (result.success) { + // 更新图片数据 + setImages(result.data || []); + setTotalImages(result.totalCount || 0); + onImagesLoaded?.(result.data || [], result.totalCount || 0); + + // 如果结果为空,记录到noResultsFor + if (!result.data || result.data.length === 0) { + requestState.current.noResultsFor = paramsString; + } + } else { + message.error(result.message || '获取图片失败'); + requestState.current.noResultsFor = paramsString; + } + } catch (error) { + message.error('加载图片列表出错'); + requestState.current.noResultsFor = paramsString; + } finally { + setLoading(false); + requestState.current.inProgress = false; + } + }, [buildQueryParams, isUsingExternalData, onImagesLoaded]); + + // 简化useEffect + useEffect(() => { + if (!isUsingExternalData) loadImages(); + }, [loadImages, isUsingExternalData]); + + // 同步外部数据 + useEffect(() => { + if (isUsingExternalData && dataSource) setImages(dataSource); + }, [dataSource, isUsingExternalData]); + + // 优化收藏/取消收藏逻辑 + const handleToggleFavorite = async (image: PictureResponse) => { + try { + const { id, isFavorited } = image; + const api = isFavorited ? unfavoritePicture : favoritePicture; + const result = await api(id); + + if (result.success) { + message.success(isFavorited ? '已取消收藏' : '已添加到收藏'); + + // 更新本地状态 + setImages(prevImages => + prevImages.map(img => + img.id === id ? { + ...img, + isFavorited: !isFavorited, + favoriteCount: isFavorited + ? Math.max(0, (img.favoriteCount || 0) - 1) + : (img.favoriteCount || 0) + 1 + } : img + ) + ); + + onToggleFavorite?.(image); + } else { + message.error(result.message || (isFavorited ? '取消收藏失败' : '收藏失败')); + } + } catch (error) { + message.error('操作失败,请重试'); + } + }; + + // 处理分页变化 + const handlePageChange = (page: number, size: number) => { + setCurrentPage(page); + if (size !== pageSize) setPageSize(size); + onPageChange?.(page, size); + }; + + // 优化图片点击处理逻辑 + const handleImageClick = (image: PictureResponse, index: number) => { + if (selectable && onSelectionChange) { + const isSelected = selectedIds.includes(image.id); + const newSelectedIds = isSelected + ? selectedIds.filter(id => id !== image.id) + : [...selectedIds, image.id]; + onSelectionChange(newSelectedIds); + } else { + setViewerState({ visible: true, index }); + } + }; + + // 处理右键菜单 + const handleContextMenu = (e: React.MouseEvent, image: PictureResponse) => { + e.preventDefault(); + setContextMenu({ + visible: true, + x: e.clientX, + y: e.clientY, + imageId: image.id, + image + }); + }; + + const closeContextMenu = () => { + setContextMenu(prev => ({ + ...prev, + visible: false + })); + }; + + useEffect(() => { + const handleDocumentClick = () => { + if (contextMenu.visible) { + closeContextMenu(); + } + }; + + document.addEventListener('click', handleDocumentClick); + return () => { + document.removeEventListener('click', handleDocumentClick); + }; + }, [contextMenu.visible]); + + // 处理图片分享 + const handleShareImage = (image: PictureResponse) => { + setShareDialogState({ + visible: true, + image + }); + }; + + // 关闭分享对话框 + const handleCloseShareDialog = () => { + setShareDialogState({ + ...shareDialogState, + visible: false + }); + }; + + // 修改handleMenuAction中的分享处理 + const handleMenuAction = (action: string) => { + if (!contextMenu.image) return; + + switch (action) { + case 'favorite': + handleToggleFavorite(contextMenu.image); + break; + case 'delete': + handleDeleteImage(contextMenu.image); + break; + case 'edit': + onEdit?.(contextMenu.image); + break; + case 'download': + onDownload?.(contextMenu.image); + break; + case 'share': + handleShareImage(contextMenu.image); + break; + default: + break; + } + + closeContextMenu(); + }; + + // 判断用户是否有权限编辑或删除图片 + const canEditImage = (image: PictureResponse): boolean => { + if (user && hasRole('Administrator')) { + return true; + } + return !!user && !!image.userId && user.id === image.userId; + }; + + // 优化渲染内容函数 + const renderContent = () => { + // 渲染加载状态 + if (isLoading) { + return ( +
+ {Array.from({ length: pageSize }).map((_, index) => ( +
+
+ {/* 简单的加载状态 */} +
+
+ ))} +
+ ); + } + + // 渲染空状态 + if (images.length === 0) { + return ( + + ); + } + + // 渲染图片网格 + return ( +
+ {images.map((image, index) => { + const isOwner = canEditImage(image); + + return ( +
handleImageClick(image, index)} + onContextMenu={(e) => handleContextMenu(e, image)} + > +
+ {image.name} + + {!selectable && ( + <> + {/* 顶部指示器 - 悬停时显示 */} +
+
+ {permissionTypeMap[image.permission]?.icon} {permissionTypeMap[image.permission]?.label || '公开'} +
+ +
+ {image.exifInfo && image.exifInfo.width && image.exifInfo.height + ? `${Math.round(image.exifInfo.width * image.exifInfo.height / 1000000)}MP` + : 'N/A'} + {' | '} + {formatDate( + typeof image.takenAt === 'string' + ? image.takenAt + : image.takenAt + ? image.takenAt.toISOString() + : typeof image.createdAt === 'string' + ? image.createdAt + : image.createdAt.toISOString() + )} +
+
+ + {/* 悬停时显示的信息覆盖层 */} +
+
+
{image.name}
+ + {image.tags && ( +
+ {image.tags.map(tag => ( + #{tag} + ))} +
+ )} + +
+
{ + e.stopPropagation(); + handleToggleFavorite(image); + }} + > + {image.isFavorited ? ( + + ) : ( + + )} +
+ + {isOwner && ( +
{ + e.stopPropagation(); + onEdit && onEdit(image); + }} + > + +
+ )} + +
{ + e.stopPropagation(); + onShare?.(image); + }} + > + +
+ +
{ + e.stopPropagation(); + onDownload?.(image); + }} + > + +
+
+
+
+ + )} +
+
+ ); + })} +
+ ); + }; + + // 渲染右键菜单 + const renderContextMenu = () => { + if (!contextMenu.visible) return null; + + const menuStyle = { + position: 'fixed' as const, + top: contextMenu.y, + left: contextMenu.x, + }; + + const currentImage = contextMenu.image; + if (!currentImage) return null; + + const isFavorited = currentImage.isFavorited; + const isOwner = canEditImage(currentImage); + + return ( +
+
handleMenuAction('favorite')} + > + {isFavorited ? ( + <> 取消收藏 + ) : ( + <> 收藏 + )} +
+ +
handleMenuAction('download')} + > + 下载 +
+ + {isOwner && ( +
handleMenuAction('edit')} + > + 编辑 +
+ )} + +
handleMenuAction('share')} + > + 分享 +
+ + {isOwner && ( +
handleMenuAction('delete')} + > + 删除 +
+ )} +
+ ); + }; + + // 处理删除图片 + const handleDeleteImage = async (image: PictureResponse) => { + Modal.confirm({ + title: '确认删除', + content: `确定要删除图片 "${image.name}" 吗?此操作不可恢复。`, + okText: '删除', + okType: 'danger', + cancelText: '取消', + onOk: async () => { + try { + const result = await deleteMultiplePictures( [image.id] ); + + if (result.success) { + message.success('图片已成功删除'); + + // 更新本地图片列表,移除被删除的图片 + setImages(prevImages => + prevImages.filter(img => img.id !== image.id) + ); + + onDelete?.(image); + + if (images.length === 1 && currentPage > 1) { + setCurrentPage(currentPage - 1); + } + } else { + message.error(result.message || '删除图片失败'); + } + } catch (error) { + message.error('删除图片失败,请重试'); + } + }, + }); + }; + + // 简化组件返回结构 + return ( + <> + {renderContent()} + {renderContextMenu()} + + {showPagination && images.length > 0 && ( +
+ `共 ${total} 张图片`} + size="default" + /> +
+ )} + + setViewerState({ ...viewerState, visible: false })} + images={images} + initialIndex={viewerState.index} + onFavorite={onToggleFavorite || handleToggleFavorite} + showFavoriteCount={showFavoriteCount} + onShare={handleShareImage} + /> + + + + ); +}; + +export default ImageGrid; diff --git a/View/src/components/image/ImageInfo.tsx b/View/src/components/image/ImageInfo.tsx new file mode 100644 index 0000000..39bdf8a --- /dev/null +++ b/View/src/components/image/ImageInfo.tsx @@ -0,0 +1,420 @@ +import React, { useState } from 'react'; +import { Divider } from 'antd'; +import { CloseOutlined, DownOutlined, UpOutlined } from '@ant-design/icons'; +import type { PictureResponse } from '../../api/types'; +import './ImageViewer.css'; + +interface ImageInfoProps { + image: PictureResponse; + onClose: () => void; + visible: boolean; +} + +const ImageInfo: React.FC = ({ + image, + onClose, + visible +}) => { + const [expandDescription, setExpandDescription] = useState(false); + + // 切换描述展开/折叠状态 + const toggleDescription = () => { + setExpandDescription(!expandDescription); + }; + + // 格式化EXIF数据 + const formatExifInfo = (exifInfo: any) => { + if (!exifInfo) return []; + + // 定义EXIF信息分类 + const categories = { + basic: { title: "基本信息", items: [] as any[] }, + camera: { title: "相机信息", items: [] as any[] }, + settings: { title: "拍摄参数", items: [] as any[] }, + time: { title: "时间信息", items: [] as any[] }, + location: { title: "位置信息", items: [] as any[] } + }; + + // 将EXIF信息映射到对应字段 + const exifMapping: Record string }> = { + // 基本信息 + width: { key: "width", category: "basic", formatter: (v) => `${v}px` }, + height: { key: "height", category: "basic", formatter: (v) => `${v}px` }, + + // 相机信息 + cameraMaker: { key: "make", category: "camera" }, + cameraModel: { key: "model", category: "camera" }, + software: { key: "software", category: "camera" }, + + // 拍摄参数 + exposureTime: { key: "exposureTime", category: "settings" }, + aperture: { key: "fNumber", category: "settings", formatter: (v) => `f/${v}` }, + isoSpeed: { key: "iso", category: "settings", formatter: (v) => `ISO ${v}` }, + focalLength: { key: "focalLength", category: "settings", formatter: (v) => `${v}mm` }, + flash: { key: "flash", category: "settings" }, + meteringMode: { key: "meteringMode", category: "settings" }, + whiteBalance: { key: "whiteBalance", category: "settings" }, + dateTimeOriginal: { + key: "dateTime", + category: "time", + formatter: (v) => { + if (typeof v === 'string' && v.match(/^\d{4}:\d{2}:\d{2} \d{2}:\d{2}:\d{2}$/)) { + const normalized = v.replace(/^(\d{4}):(\d{2}):(\d{2})/, '$1-$2-$3'); + const date = new Date(normalized); + if (!isNaN(date.getTime())) { + return date.toLocaleString(); + } + } + return v.toString(); + } + }, + + // 位置信息 + gpsLatitude: { key: "latitude", category: "location" }, + gpsLongitude: { key: "longitude", category: "location" } + }; + + // 处理每个EXIF字段 + Object.entries(exifInfo).forEach(([key, value]) => { + if (value === null || value === undefined || value === '') return; + + const mapping = exifMapping[key]; + if (mapping) { + const formattedValue = mapping.formatter ? mapping.formatter(value) : value.toString(); + const label = formatExifLabel(mapping.key); + + categories[mapping.category].items.push({ + key: mapping.key, + label, + value: formattedValue + }); + } + }); + + // 返回包含数据的分类 + return Object.values(categories).filter(category => category.items.length > 0); + }; + + // 格式化EXIF标签名称 + const formatExifLabel = (key: string): string => { + const labels: Record = { + // 基本信息 + width: "宽度", + height: "高度", + + // 相机信息 + make: "相机品牌", + model: "相机型号", + software: "软件", + + // 拍摄参数 + exposureTime: "曝光时间", + fNumber: "光圈值", + iso: "ISO感光度", + focalLength: "焦距", + flash: "闪光灯", + meteringMode: "测光模式", + whiteBalance: "白平衡", + + // 时间信息 + dateTime: "拍摄时间", + + // 位置信息 + latitude: "纬度", + longitude: "经度" + }; + + return labels[key] || key.charAt(0).toUpperCase() + key.slice(1).replace(/([A-Z])/g, ' $1'); + }; + + // 渲染EXIF信息 + const renderExifInfo = (styles: any) => { + if (!image?.exifInfo) return
无EXIF信息
; + + const formattedCategories = formatExifInfo(image.exifInfo); + + if (formattedCategories.length === 0) { + return
无EXIF信息
; + } + + return ( +
+ {formattedCategories.map(category => ( +
+ + {category.title} + +
+ {category.items.map(item => ( +
+
{item.label}
+
{item.value}
+
+ ))} +
+
+ ))} +
+ ); + }; + + // 定义内联样式对象 + const styles = { + // 抽屉基础样式 + drawer: { + position: 'fixed' as const, + top: 0, + right: 0, + width: '350px', + height: '100%', + zIndex: 1050, + backgroundColor: 'rgba(28, 30, 34, 0.5)', + backdropFilter: 'blur(24px) saturate(180%)', + WebkitBackdropFilter: 'blur(24px) saturate(180%)', + border: 'none', + boxShadow: '-10px 0 30px rgba(0, 0, 0, 0.2)', + transition: 'transform 0.3s ease', + transform: visible ? 'translateX(0)' : 'translateX(100%)', + overflowY: 'auto' as const + }, + header: { + backgroundColor: 'rgba(28, 30, 34, 0.6)', + backdropFilter: 'blur(24px) saturate(180%)', + WebkitBackdropFilter: 'blur(24px) saturate(180%)', + borderBottom: '1px solid rgba(255, 255, 255, 0.08)', + color: 'rgba(255, 255, 255, 0.95)', + padding: '16px 20px', + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center' + }, + headerTitle: { + color: 'rgba(255, 255, 255, 0.95)', + margin: 0, + fontSize: '16px', + fontWeight: 500 + }, + closeButton: { + background: 'transparent', + border: 'none', + color: 'rgba(255, 255, 255, 0.8)', + cursor: 'pointer', + padding: '4px', + display: 'flex', + alignItems: 'center', + justifyContent: 'center' + }, + body: { + padding: '24px 20px', + color: 'white' + }, + // 标题样式 + titleContainer: { + padding: '0 0 16px 0', + borderBottom: '1px solid rgba(255, 255, 255, 0.08)', + marginBottom: '20px' + }, + title: { + color: 'rgba(255, 255, 255, 0.95)', + margin: '0 0 4px 0', + fontSize: '18px', + fontWeight: 500, + textShadow: '0 1px 2px rgba(0, 0, 0, 0.1)' + }, + date: { + color: 'rgba(255, 255, 255, 0.6)', + fontSize: '13px' + }, + // 描述区域 + descSection: { + backgroundColor: 'rgba(255, 255, 255, 0.08)', + backdropFilter: 'blur(10px) saturate(180%)', + WebkitBackdropFilter: 'blur(10px) saturate(180%)', + padding: '16px', + borderRadius: '8px', + marginBottom: '20px', + border: '1px solid rgba(255, 255, 255, 0.1)', + boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)' + }, + descText: { + color: 'rgba(255, 255, 255, 0.9)', + lineHeight: '1.6', + display: '-webkit-box', + WebkitBoxOrient: 'vertical' as const, + overflow: 'hidden', + textOverflow: 'ellipsis', + WebkitLineClamp: expandDescription ? 'unset' : 8 + }, + expandButton: { + background: 'transparent', + border: 'none', + color: 'rgba(255, 255, 255, 0.7)', + cursor: 'pointer', + padding: '8px 0 0 0', + fontSize: '13px', + display: 'flex', + alignItems: 'center', + width: '100%', + justifyContent: 'center' + }, + // 标签区域 + tagsSection: { + marginBottom: '20px', + padding: '0 4px' + }, + tagTitle: { + color: 'rgba(255, 255, 255, 0.8)', + fontSize: '14px', + fontWeight: 500, + marginBottom: '12px' + }, + tagItem: { + backgroundColor: 'rgba(255, 255, 255, 0.15)', + borderRadius: '16px', + color: 'rgba(255, 255, 255, 0.9)', + border: 'none', + padding: '4px 12px', + margin: '0 8px 8px 0', + display: 'inline-block', + fontSize: '12px' + }, + // 规格信息区 + specsSection: { + backgroundColor: 'rgba(255, 255, 255, 0.08)', + backdropFilter: 'blur(10px) saturate(180%)', + WebkitBackdropFilter: 'blur(10px) saturate(180%)', + padding: '16px', + borderRadius: '8px', + margin: '16px 0 20px', + border: '1px solid rgba(255, 255, 255, 0.1)', + boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)' + }, + specsContainer: { + display: 'flex', + justifyContent: 'space-around', + textAlign: 'center' as const + }, + specItem: { + padding: '0 8px', + flex: 1 + }, + specValue: { + fontSize: '15px', + fontWeight: 500, + color: 'rgba(255, 255, 255, 0.95)', + marginBottom: '4px', + textShadow: '0 1px 2px rgba(0, 0, 0, 0.1)' + }, + specLabel: { + fontSize: '12px', + color: 'rgba(255, 255, 255, 0.6)' + }, + // EXIF信息区 + exifContainer: { + marginTop: '10px' + }, + exifCategory: { + marginBottom: '20px' + }, + divider: { + borderColor: 'rgba(255, 255, 255, 0.08)', + margin: '10px 0 16px', + fontSize: '14px', + color: 'rgba(255, 255, 255, 0.8)', + fontWeight: 500 + }, + exifTable: { + backgroundColor: 'rgba(255, 255, 255, 0.08)', + borderRadius: '8px', + overflow: 'hidden', + border: '1px solid rgba(255, 255, 255, 0.1)' + }, + exifLabel: { + color: 'rgba(255, 255, 255, 0.7)', + backgroundColor: 'rgba(0, 0, 0, 0.2)', + padding: '8px 12px', + width: '100px', + fontSize: '13px' + }, + exifValue: { + color: 'rgba(255, 255, 255, 0.9)', + backgroundColor: 'rgba(255, 255, 255, 0.05)', + padding: '8px 12px', + fontSize: '13px' + } + }; + + return ( +
+
+

图片信息

+ +
+
+
+

{image?.name}

+
上传于{new Date(image?.createdAt).toLocaleString()}
+
+ + {image?.description && ( +
+
{image.description}
+ {image.description.split('\n').length > 8 || image.description.length > 200 ? ( + + ) : null} +
+ )} + + {image?.tags && image.tags.length > 0 && ( +
+
标签
+
+ {image.tags.map(tag => ( + #{tag} + ))} +
+
+ )} + + {image?.exifInfo && ( +
+
+
+
{image.exifInfo.width}×{image.exifInfo.height}
+
分辨率
+
+ {image.exifInfo.cameraModel && ( +
+
{image.exifInfo.cameraModel}
+
相机
+
+ )} + {image.exifInfo.focalLength && ( +
+
{image.exifInfo.focalLength}
+
焦距
+
+ )} +
+
+ )} + + {/* 渲染EXIF信息 */} + {renderExifInfo(styles)} +
+
+ ); +}; + +export default ImageInfo; diff --git a/View/src/components/image/ImageViewer.css b/View/src/components/image/ImageViewer.css new file mode 100644 index 0000000..e4d237b --- /dev/null +++ b/View/src/components/image/ImageViewer.css @@ -0,0 +1,353 @@ +.image-viewer-container { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 1000; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + transition: opacity 0.3s ease; + opacity: 0; +} + +.image-viewer-container.visible { + opacity: 1; +} + +.viewer-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.85); + z-index: 1001; +} + +.viewer-content { + position: relative; + z-index: 1002; + width: 100%; + height: 100%; + display: flex; + flex-direction: column; +} + +.viewer-header { + position: absolute; + top: 0; + left: 0; + right: 0; + z-index: 1004; + display: flex; + justify-content: space-between; + align-items: center; + padding: 16px 24px; + background: linear-gradient(to bottom, rgba(0,0,0,0.7) 0%, rgba(0,0,0,0.3) 80%, rgba(0,0,0,0) 100%); + backdrop-filter: blur(8px); +} + +.image-counter { + color: white; + font-size: 16px; +} + +.header-actions { + display: flex; + gap: 8px; +} + +.header-btn { + color: white !important; + border: none; + background: transparent !important; + border-radius: 50%; + height: 36px; + width: 36px; + display: flex; + align-items: center; + justify-content: center; +} + +.header-btn:hover { + background: rgba(255, 255, 255, 0.2) !important; +} + +.image-container { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 1003; + overflow: visible; + display: flex; + align-items: center; + justify-content: center; +} + +.transform-wrapper { + width: 100%; + height: 100%; + position: relative; +} + +.transform-content { + display: flex; + justify-content: center; + align-items: center; + width: 100%; + height: 100%; +} + +.viewer-img { + max-width: 100%; + max-height: 100%; + transition: opacity 0.3s ease, transform 0.3s ease; +} + +.viewer-img.thumbnail { + filter: blur(1px); + transform-origin: center; +} + +.zoom-controls { + position: absolute; + bottom: 100px; + left: 50%; + transform: translateX(-50%); + z-index: 1005; + display: flex; + align-items: center; + background: rgba(0, 0, 0, 0.5); + backdrop-filter: blur(8px); + padding: 8px 12px; + border-radius: 24px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); +} + +.zoom-controls .ant-btn { + color: white !important; + border: none; + background: transparent !important; + border-radius: 50%; + margin: 0 2px; +} + +.zoom-controls .ant-btn:hover { + background: rgba(255, 255, 255, 0.2) !important; +} + +/* 添加原图模式指示样式 */ +.zoom-controls .ant-btn-primary { + background-color: #1890ff !important; +} + +.zoom-controls .ant-btn-primary:hover { + background-color: #40a9ff !important; +} + +.nav-button { + position: absolute; + top: 50%; + transform: translateY(-50%); + z-index: 1005; + background: rgba(0, 0, 0, 0.3) !important; + backdrop-filter: blur(4px); + color: white !important; + border: none !important; + width: 44px; + height: 44px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); + display: flex; + align-items: center; + justify-content: center; +} + +.prev-button { + left: 20px; +} + +.next-button { + right: 20px; +} + +.nav-button:hover { + background-color: rgba(0, 0, 0, 0.7) !important; +} + +.viewer-footer { + position: absolute; + bottom: 0; + left: 0; + right: 0; + z-index: 1004; + display: flex; + justify-content: space-between; + align-items: center; + padding: 16px 24px; + background: linear-gradient(to top, rgba(0,0,0,0.7) 0%, rgba(0,0,0,0.3) 80%, rgba(0,0,0,0) 100%); + backdrop-filter: blur(8px); +} + +.image-name { + color: white; + max-width: 70%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.footer-actions { + display: flex; + gap: 8px; +} + +.footer-btn { + color: white !important; + border: none; + background: transparent !important; + font-size: 18px; + border-radius: 50%; + height: 40px; + width: 40px; + display: flex; + align-items: center; + justify-content: center; +} + +.footer-btn:hover { + background: rgba(255, 255, 255, 0.2) !important; +} + +.footer-btn span { + margin-left: 4px; + font-size: 14px; +} + +.image-info-drawer { + position: absolute !important; + z-index: 1050 !important; +} + +.image-info-drawer .ant-drawer-mask { + background-color: transparent !important; +} + +.image-info-drawer .ant-drawer-header { + background-color: rgba(255, 255, 255, 0.75); + backdrop-filter: blur(15px); + border-bottom: 1px solid rgba(240, 240, 240, 0.6); +} + +.drawer-content { + padding: 16px 0; +} + +.image-title-section { + margin-bottom: 16px; +} + +.image-title-section h4 { + margin-bottom: 4px; +} + +.image-description-section { + margin-bottom: 16px; + padding: 16px; + background: rgba(249, 249, 249, 0.5) !important; + border-radius: 4px; + backdrop-filter: blur(10px) !important; +} + +.image-tags-section { + margin-bottom: 16px; +} + +.tag-title { + margin-bottom: 8px; + font-weight: 500; +} + +.tag-list { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.exif-description { + font-size: 13px; +} + +.exif-description .ant-descriptions-item-label { + width: 100px; +} + +.image-specs-section { + margin: 16px 0; + padding: 16px; + background: rgba(247, 249, 250, 0.5) !important; + border-radius: 8px; + backdrop-filter: blur(10px) !important; +} + +.specs-container { + display: flex; + justify-content: space-around; + text-align: center; +} + +.spec-item { + flex: 1; + padding: 0 8px; +} + +.spec-value { + font-size: 16px; + font-weight: 500; + color: #262626; + margin-bottom: 4px; +} + +.spec-label { + font-size: 12px; + color: #8c8c8c; +} + +.exif-sections { + margin-top: 20px; +} + +.exif-category { + margin-bottom: 24px; +} + +.exif-category .ant-divider { + margin: 16px 0; +} + +.exif-description { + font-size: 13px; +} + +.exif-description .ant-descriptions-item-label { + width: 90px; + font-weight: 500; +} + +.image-info-drawer .ant-drawer-body { + padding: 24px 20px; +} + +.exif-description .ant-descriptions-view { + background-color: rgba(255, 255, 255, 0.5) !important; + backdrop-filter: blur(10px) !important; +} + +.exif-description .ant-descriptions-row > th, +.exif-description .ant-descriptions-row > td { + padding: 12px 16px; +} diff --git a/View/src/components/image/ImageViewer.tsx b/View/src/components/image/ImageViewer.tsx new file mode 100644 index 0000000..4631af6 --- /dev/null +++ b/View/src/components/image/ImageViewer.tsx @@ -0,0 +1,473 @@ +import React, { useState, useEffect, useCallback, useRef } from 'react'; +import { Button, Space, Dropdown, message } from 'antd'; +import { + ZoomInOutlined, + ZoomOutOutlined, + ExpandOutlined, + InfoCircleOutlined, + CloseOutlined, + LeftOutlined, + RightOutlined, + RotateLeftOutlined, + RotateRightOutlined, + HeartOutlined, + HeartFilled, + DownloadOutlined, + ShareAltOutlined, + FolderAddOutlined, + EyeOutlined +} from '@ant-design/icons'; +import { TransformWrapper, TransformComponent } from 'react-zoom-pan-pinch'; +import type { PictureResponse, AlbumResponse } from '../../api/types'; +import { getAlbums, addPicturesToAlbum, favoritePicture, unfavoritePicture } from '../../api'; +import ImageInfo from './ImageInfo'; +import ShareImageDialog from './ShareImageDialog'; +import './ImageViewer.css'; + + +interface ImageViewerProps { + visible: boolean; + onClose: () => void; + images: PictureResponse[]; + initialIndex?: number; + onFavorite?: (image: PictureResponse) => void; + onNext?: () => void; + onPrevious?: () => void; + showFavoriteCount?: boolean; + onShare?: (image: PictureResponse) => void; +} + +const ImageViewer: React.FC = ({ + visible, + onClose, + images, + initialIndex = 0, + onFavorite, + onNext, + onPrevious, + showFavoriteCount = false, // 默认不显示收藏数量 + onShare, +}) => { + const wasVisible = useRef(visible); + const [currentIndex, setCurrentIndex] = useState(initialIndex); + const [isInfoDrawerOpen, setIsInfoDrawerOpen] = useState(false); + const [rotation, setRotation] = useState(0); + const [albums, setAlbums] = useState([]); + const [loadingAlbums, setLoadingAlbums] = useState(false); + const [localImages, setLocalImages] = useState(images); + const [shareDialogVisible, setShareDialogVisible] = useState(false); + + // 保留加载状态跟踪,但不再用于显示缩略图 + const [imageLoaded, setImageLoaded] = useState(false); + const [showOriginal, setShowOriginal] = useState(false); // 新增:控制是否显示原图 + + // 保留防缓存机制 + const [cacheKey, setCacheKey] = useState(Date.now()); + + // 当前显示的图片 + const currentImage = localImages[currentIndex]; + + // 重置查看器状态,包含图片加载状态 + const resetViewerState = useCallback(() => { + setRotation(0); + setIsInfoDrawerOpen(false); + setImageLoaded(false); // 重置图片加载状态 + setShowOriginal(false); // 重置为显示缩略图 + setCacheKey(Date.now()); // 每次重置时更新缓存键 + }, []); + + useEffect(() => { + setImageLoaded(false); + setShowOriginal(false); // 切换图片时重置为缩略图模式 + }, [currentIndex]); + + // 监听visible变化的处理 + useEffect(() => { + // 当查看器从不可见变为可见时 + if (visible && !wasVisible.current) { + if (initialIndex >= 0 && initialIndex < images.length) { + setCurrentIndex(initialIndex); + } + resetViewerState(); + } + // 当查看器从可见变为不可见时 + else if (!visible && wasVisible.current) { + // 关闭后等待一小段时间再更新缓存键,确保下次打开时强制刷新图片 + setTimeout(() => setCacheKey(Date.now()), 300); + } + + // 更新ref以跟踪visible的变化 + wasVisible.current = visible; + }, [visible, initialIndex, images.length, resetViewerState]); + + // 当currentIndex变化时,重置图片加载状态并更新缓存键 + useEffect(() => { + setImageLoaded(false); + setShowOriginal(false); // 重置为缩略图模式 + setCacheKey(Date.now()); + }, [currentIndex]); + + // 当外部传入的images发生变化时,更新本地缓存 + useEffect(() => { + setLocalImages(images); + }, [images]); + + // 处理键盘事件 + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if (!visible) return; + + switch (e.key) { + case 'ArrowLeft': + handlePrevious(); + break; + case 'ArrowRight': + handleNext(); + break; + case 'Escape': + onClose(); + break; + case 'i': + setIsInfoDrawerOpen(prev => !prev); + break; + } + }; + + window.addEventListener('keydown', handleKeyDown); + return () => window.removeEventListener('keydown', handleKeyDown); + }, [visible, currentIndex, images.length]); + + // 处理上一张图片 + const handlePrevious = useCallback(() => { + if (currentIndex > 0) { + setCurrentIndex(prevIndex => prevIndex - 1); + if (onPrevious) onPrevious(); + } + }, [currentIndex, onPrevious]); + + // 处理下一张图片 + const handleNext = useCallback(() => { + if (currentIndex < images.length - 1) { + setCurrentIndex(prevIndex => prevIndex + 1); + if (onNext) onNext(); + } + }, [currentIndex, images.length, onNext]); + + // 处理收藏按钮点击 + const handleFavoriteClick = useCallback(async () => { + if (!currentImage) return; + + try { + // 如果提供了onFavorite回调,直接使用它,不再重复发送请求 + if (onFavorite) { + onFavorite(currentImage); + return; + } + + // 只有在没有提供onFavorite回调时才直接发送网络请求 + let result; + if (currentImage.isFavorited) { + result = await unfavoritePicture(currentImage.id); + if (result.success) { + message.success('已取消收藏'); + } else { + message.error(result.message || '取消收藏失败'); + return; // 如果请求失败,不更新UI状态 + } + } else { + result = await favoritePicture(currentImage.id); + if (result.success) { + message.success('已添加到收藏'); + } else { + message.error(result.message || '收藏失败'); + return; // 如果请求失败,不更新UI状态 + } + } + + // 请求成功后,更新本地状态 + setLocalImages(prevImages => + prevImages.map(img => + img.id === currentImage.id ? { + ...img, + isFavorited: !img.isFavorited, + favoriteCount: img.isFavorited + ? Math.max(0, (img.favoriteCount || 0) - 1) + : (img.favoriteCount || 0) + 1 + } : img + ) + ); + } catch (error) { + console.error('收藏操作失败:', error); + message.error('操作失败,请重试'); + } + }, [currentImage, onFavorite]); + + // 处理旋转 + const handleRotateLeft = () => { + setRotation(prev => prev - 90); + }; + + const handleRotateRight = () => { + setRotation(prev => prev + 90); + }; + + // 加载相册 + useEffect(() => { + if (visible) { + loadAlbums(); + } + }, [visible]); + + const loadAlbums = async () => { + setLoadingAlbums(true); + try { + const result = await getAlbums(1, 100); + if (result.success && result.data) { + setAlbums(result.data); + } + } catch (error) { + console.error('加载相册失败:', error); + } finally { + setLoadingAlbums(false); + } + }; + + const handleAddToAlbum = async (albumId: number) => { + if (!currentImage) return; + + try { + // 使用新的批量添加方法,参数为数组 + const result = await addPicturesToAlbum(albumId, [currentImage.id]); + if (result.success) { + message.success('已添加到相册'); + } else { + message.error(result.message || '添加到相册失败'); + } + } catch (error) { + console.error('添加到相册失败:', error); + message.error('添加到相册失败,请重试'); + } + }; + + const albumItems = albums.map(album => ({ + key: album.id, + label: album.name, + onClick: () => handleAddToAlbum(album.id) + })); + + // 处理切换到原图 + const handleToggleOriginal = () => { + setShowOriginal(prev => !prev); + }; + + // 处理图片加载完成事件 + const handleImageLoaded = () => { + setImageLoaded(true); + }; + + // 新增:检测图片是否已经加载 + useEffect(() => { + // 如果当前图片已加载到缓存中 + if (currentImage && !imageLoaded) { + const img = new Image(); + img.onload = () => { + // 图片已在缓存中,立即设置为已加载 + setImageLoaded(true); + }; + + // 添加缓存键参数强制刷新 + const cacheBuster = `${currentImage.path}${currentImage.path.includes('?') ? '&' : '?'}_cb=${cacheKey}`; + img.src = cacheBuster; + } + }, [currentImage, imageLoaded, cacheKey]); + + // 处理分享按钮点击 + const handleShareClick = useCallback(() => { + if (!currentImage) return; + + if (onShare) { + onShare(currentImage); + } else { + setShareDialogVisible(true); + } + }, [currentImage, onShare]); + + // 当没有图片或当前图片不存在时,不渲染任何内容 + if (images.length === 0 || !currentImage) { + return null; + } + + return ( +
+
+ +
+
+ + {({ zoomIn, zoomOut, resetTransform }) => ( + <> + + {currentImage.name} + + +
+ +
+ + )} +
+ + {/* 图片导航按钮 */} + {currentIndex > 0 && ( +
+ + {/* 顶部操作栏 */} +
+
+ {currentIndex + 1} / {images.length} +
+
+
+
+ + {/* 底部操作栏 */} +
+
+ {currentImage.name} +
+ +
+ {onFavorite && ( + + )} + +
+
+
+ + {/* 图片信息 */} + {currentImage && ( + setIsInfoDrawerOpen(false)} + /> + )} + + {/* 添加分享对话框 */} + {!onShare && currentImage && ( + setShareDialogVisible(false)} + image={currentImage} + /> + )} +
+ ); +}; + +export default ImageViewer; diff --git a/View/src/components/image/ShareImageDialog.css b/View/src/components/image/ShareImageDialog.css new file mode 100644 index 0000000..6459438 --- /dev/null +++ b/View/src/components/image/ShareImageDialog.css @@ -0,0 +1,72 @@ +.share-image-dialog .ant-modal-body { + padding: 24px; +} + +.share-image-content { + display: flex; + flex-direction: column; +} + +.share-image-preview { + display: flex; + justify-content: center; + margin-bottom: 16px; + background-color: #f0f0f0; + border-radius: 8px; + padding: 16px; + overflow: hidden; +} + +.share-preview-img { + max-width: 100%; + max-height: 200px; + object-fit: contain; + border-radius: 4px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); +} + +.share-image-controls { + display: flex; + justify-content: center; + margin-bottom: 16px; +} + +.share-type-switch { + margin-bottom: 16px; +} + +.share-tabs { + margin-top: 8px; +} + +.share-tab-content { + width: 100%; +} + +.share-input-group { + display: flex; + width: 100%; + margin-top: 8px; +} + +.share-input { + flex: 1; +} + +.share-copy-btn { + width: 40px; + transition: all 0.3s; +} + +.share-copy-btn.copied { + background-color: #52c41a; + border-color: #52c41a; + color: white; +} + +/* 响应式调整 */ +@media (max-width: 576px) { + .share-image-dialog { + max-width: 90%; + } +} diff --git a/View/src/components/image/ShareImageDialog.tsx b/View/src/components/image/ShareImageDialog.tsx new file mode 100644 index 0000000..7396425 --- /dev/null +++ b/View/src/components/image/ShareImageDialog.tsx @@ -0,0 +1,168 @@ +import React, { useState, useRef } from 'react'; +import { Modal, Tabs, Input, Button, message, Radio, Space, Typography } from 'antd'; +import { CopyOutlined, CheckOutlined } from '@ant-design/icons'; +import type { PictureResponse } from '../../api/types'; +import './ShareImageDialog.css'; + +const { Text } = Typography; + +interface ShareImageDialogProps { + visible: boolean; + onClose: () => void; + image: PictureResponse | null; +} + +const ShareImageDialog: React.FC = ({ + visible, + onClose, + image +}) => { + const [imageType, setImageType] = useState<'original' | 'thumbnail'>('original'); + const [copied, setCopied] = useState>({}); + const timerRef = useRef>>({}); + + if (!image) return null; + + // 构建图片链接 + const imagePath = imageType === 'original' ? image.path : (image.thumbnailPath || image.path); + const imageUrl = new URL(imagePath, window.location.origin).href; + const imageName = image.name || 'image'; + + // 构建不同格式的链接 - 移到这里,确保随imageType变化而更新 + const linkFormats = { + direct: imageUrl, + markdown: `![${imageName}](${imageUrl})`, + html: `${imageName}` + }; + + const handleCopy = async (text: string, key: string) => { + try { + await navigator.clipboard.writeText(text); + + // 设置复制状态 + setCopied(prev => ({...prev, [key]: true})); + + // 清除现有定时器 + if (timerRef.current[key]) { + clearTimeout(timerRef.current[key]); + } + + // 设置新定时器 + timerRef.current[key] = setTimeout(() => { + setCopied(prev => ({...prev, [key]: false})); + }, 2000); + + message.success('已复制到剪贴板'); + } catch (error) { + message.error('复制失败,请手动复制'); + } + }; + + // 定义标签页内容 + const tabItems = [ + { + key: 'direct', + label: '直接链接', + children: ( + + 可直接访问的图片链接 + + + + + ); + } + + return ( +
+ {/* 移除原来的面包屑代码 */} + +
+
+ {album.name} + {album.description || "无描述"} + + 创建于 {getFormattedDate(album.createdAt)} · {album?.pictureCount || 0} 张照片 + +
+ +
+ + + + + +
+
+ + + + {/* 添加图片到相册的对话框 */} + setIsAddModalVisible(false)} + onOk={handleAddPictures} + width={1000} + > +
+ +
+ {selectedPictures.length} 张图片已选择 +
+
+
+ + {/* 编辑相册的对话框 */} + setIsEditModalVisible(false)} + footer={[ + , + , + ]} + > +
+ + + + +