diff --git a/README.md b/README.md index 80d31cb..b1f3c99 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@

-An open-source React.js package for easy integration of a file manager into applications. It provides a user-friendly interface for managing files and folders, including viewing, uploading, and deleting, with full UI and backend integration. +An open-source React.js package with TypeScript suppor for easy integration of a file manager into applications. It provides a user-friendly interface for managing files and folders, including viewing, uploading, and deleting, with full UI and backend integration.

## ✨ Features @@ -43,7 +43,47 @@ npm i @cubone/react-file-manager ## 💻 Usage -Here’s a basic example of how to use the File Manager Component in your React application: +**TypeScript:** Here’s a basic example of how to use the File Manager Component in your React application: + +```tsx +import { useState } from "react"; +import { FileItem, FileManager } from "@cubone/react-file-manager"; +import "@cubone/react-file-manager/dist/style.css"; + +function App() { + const [files, setFiles] = useState([ + { + name: "Documents", + isDirectory: true, // Folder + path: "/Documents", // Located in Root directory + updatedAt: "2024-09-09T10:30:00Z", // Last updated time + }, + { + name: "Pictures", + isDirectory: true, + path: "/Pictures", // Located in Root directory as well + updatedAt: "2024-09-09T11:00:00Z", + }, + { + name: "Pic.png", + isDirectory: false, // File + path: "/Pictures/Pic.png", // Located inside the "Pictures" folder + updatedAt: "2024-09-08T16:45:00Z", + size: 2048, // File size in bytes (example: 2 KB) + }, + ]); + + return ( + <> + + + ); +} + +export default App; +``` + +**JavaScript:** Here’s a basic example of how to use the File Manager Component in your React application: ```jsx import { useState } from "react"; @@ -83,6 +123,8 @@ function App() { export default App; ``` +More detailed samples can be found in the [samples folder](tree/main/frontend). + ## 📂 File Structure The `files` prop accepts an array of objects, where each object represents a file or folder. You can diff --git a/frontend/.eslintrc.cjs b/frontend/.eslintrc.cjs index 3e212e1..63f0ce0 100644 --- a/frontend/.eslintrc.cjs +++ b/frontend/.eslintrc.cjs @@ -6,11 +6,17 @@ module.exports = { 'plugin:react/recommended', 'plugin:react/jsx-runtime', 'plugin:react-hooks/recommended', + 'plugin:@typescript-eslint/recommended', ], ignorePatterns: ['dist', '.eslintrc.cjs'], - parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, + parser: '@typescript-eslint/parser', + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + project: './tsconfig.json' + }, settings: { react: { version: '18.2' } }, - plugins: ['react-refresh'], + plugins: ['react-refresh', "@typescript-eslint"], rules: { 'react/jsx-no-target-blank': 'off', 'react-refresh/only-export-components': [ diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 0c55cc1..1bd1884 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -14,8 +14,11 @@ "react-icons": "^5.4.0" }, "devDependencies": { - "@types/react": "^18.3.3", - "@types/react-dom": "^18.3.0", + "@types/node": "^24.10.0", + "@types/react": "^19.2.2", + "@types/react-dom": "^19.2.2", + "@typescript-eslint/eslint-plugin": "^8.46.4", + "@typescript-eslint/parser": "^8.46.4", "@vitejs/plugin-react-swc": "^3.5.0", "axios": "^1.7.7", "eslint": "^8.57.0", @@ -26,7 +29,9 @@ "react-dom": "^18.3.1", "sass": "^1.77.6", "semantic-release": "^24.1.0", - "vite": "^6.3.6" + "typescript": "^5.9.3", + "vite": "^6.3.6", + "vite-plugin-dts": "^4.5.4" }, "peerDependencies": { "react": ">=18", @@ -56,16 +61,42 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", - "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" } }, + "node_modules/@babel/parser": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.5" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@babel/runtime": { "version": "7.27.0", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz", @@ -78,6 +109,20 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/types": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@colors/colors": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", @@ -515,9 +560,9 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", - "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", "dev": true, "license": "MIT", "dependencies": { @@ -615,6 +660,219 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/@isaacs/balanced-match": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", + "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/brace-expansion": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", + "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@isaacs/balanced-match": "^4.0.1" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@microsoft/api-extractor": { + "version": "7.55.0", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.55.0.tgz", + "integrity": "sha512-TYc5OtAK/9E3HGgd2bIfSjQDYIwPc0dysf9rPiwXZGsq916I6W2oww9bhm1OxPOeg6rMfOX3PoroGd7oCryYog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@microsoft/api-extractor-model": "7.32.0", + "@microsoft/tsdoc": "~0.16.0", + "@microsoft/tsdoc-config": "~0.18.0", + "@rushstack/node-core-library": "5.18.0", + "@rushstack/rig-package": "0.6.0", + "@rushstack/terminal": "0.19.3", + "@rushstack/ts-command-line": "5.1.3", + "diff": "~8.0.2", + "lodash": "~4.17.15", + "minimatch": "10.0.3", + "resolve": "~1.22.1", + "semver": "~7.5.4", + "source-map": "~0.6.1", + "typescript": "5.8.2" + }, + "bin": { + "api-extractor": "bin/api-extractor" + } + }, + "node_modules/@microsoft/api-extractor-model": { + "version": "7.32.0", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.32.0.tgz", + "integrity": "sha512-QIVJSreb8fGGJy1Qx0yzGVXxvHJN1WXgkFNHFheVv1iBJNqgvp+xeT3ienJmRwXmPPc5Es/cxBrXtKZJR3i7iw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@microsoft/tsdoc": "~0.16.0", + "@microsoft/tsdoc-config": "~0.18.0", + "@rushstack/node-core-library": "5.18.0" + } + }, + "node_modules/@microsoft/api-extractor/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@microsoft/api-extractor/node_modules/minimatch": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", + "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@isaacs/brace-expansion": "^5.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@microsoft/api-extractor/node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@microsoft/api-extractor/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@microsoft/api-extractor/node_modules/typescript": { + "version": "5.8.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", + "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/@microsoft/tsdoc": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.16.0.tgz", + "integrity": "sha512-xgAyonlVVS+q7Vc7qLW0UrJU7rSFcETRWsqdXZtjzRU8dF+6CkozTK4V4y1LwOX7j8r/vHphjDeMeGI4tNGeGA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@microsoft/tsdoc-config": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc-config/-/tsdoc-config-0.18.0.tgz", + "integrity": "sha512-8N/vClYyfOH+l4fLkkr9+myAoR6M7akc8ntBJ4DJdWH2b09uVfr71+LTMpNyG19fNqWDg8KEDZhx5wxuqHyGjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@microsoft/tsdoc": "0.16.0", + "ajv": "~8.12.0", + "jju": "~1.4.0", + "resolve": "~1.22.2" + } + }, + "node_modules/@microsoft/tsdoc-config/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@microsoft/tsdoc-config/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/@microsoft/tsdoc-config/node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1215,6 +1473,42 @@ "node": ">=12" } }, + "node_modules/@rollup/pluginutils": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", + "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.40.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.0.tgz", @@ -1495,85 +1789,305 @@ "win32" ] }, - "node_modules/@sec-ant/readable-stream": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", - "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@semantic-release/commit-analyzer": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/@semantic-release/commit-analyzer/-/commit-analyzer-13.0.1.tgz", - "integrity": "sha512-wdnBPHKkr9HhNhXOhZD5a2LNl91+hs8CC2vsAVYxtZH3y0dV3wKn+uZSN61rdJQZ8EGxzWB3inWocBHV9+u/CQ==", + "node_modules/@rushstack/node-core-library": { + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-5.18.0.tgz", + "integrity": "sha512-XDebtBdw5S3SuZIt+Ra2NieT8kQ3D2Ow1HxhDQ/2soinswnOu9e7S69VSwTOLlQnx5mpWbONu+5JJjDxMAb6Fw==", "dev": true, "license": "MIT", "dependencies": { - "conventional-changelog-angular": "^8.0.0", - "conventional-changelog-writer": "^8.0.0", - "conventional-commits-filter": "^5.0.0", - "conventional-commits-parser": "^6.0.0", - "debug": "^4.0.0", - "import-from-esm": "^2.0.0", - "lodash-es": "^4.17.21", - "micromatch": "^4.0.2" - }, - "engines": { - "node": ">=20.8.1" + "ajv": "~8.13.0", + "ajv-draft-04": "~1.0.0", + "ajv-formats": "~3.0.1", + "fs-extra": "~11.3.0", + "import-lazy": "~4.0.0", + "jju": "~1.4.0", + "resolve": "~1.22.1", + "semver": "~7.5.4" }, "peerDependencies": { - "semantic-release": ">=20.1.0" + "@types/node": "*" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@semantic-release/error": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-4.0.0.tgz", - "integrity": "sha512-mgdxrHTLOjOddRVYIYDo0fR3/v61GNN1YGkfbrjuIKg/uMgCd+Qzo3UAXJ+woLQQpos4pl5Esuw5A7AoNlzjUQ==", + "node_modules/@rushstack/node-core-library/node_modules/ajv": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.13.0.tgz", + "integrity": "sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==", "dev": true, "license": "MIT", - "engines": { - "node": ">=18" + "dependencies": { + "fast-deep-equal": "^3.1.3", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.4.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@semantic-release/github": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-11.0.1.tgz", - "integrity": "sha512-Z9cr0LgU/zgucbT9cksH0/pX9zmVda9hkDPcgIE0uvjMQ8w/mElDivGjx1w1pEQ+MuQJ5CBq3VCF16S6G4VH3A==", + "node_modules/@rushstack/node-core-library/node_modules/ajv-draft-04": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz", + "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==", "dev": true, "license": "MIT", - "dependencies": { - "@octokit/core": "^6.0.0", - "@octokit/plugin-paginate-rest": "^11.0.0", - "@octokit/plugin-retry": "^7.0.0", - "@octokit/plugin-throttling": "^9.0.0", - "@semantic-release/error": "^4.0.0", - "aggregate-error": "^5.0.0", - "debug": "^4.3.4", - "dir-glob": "^3.0.1", - "globby": "^14.0.0", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.0", - "issue-parser": "^7.0.0", - "lodash-es": "^4.17.21", - "mime": "^4.0.0", - "p-filter": "^4.0.0", - "url-join": "^5.0.0" - }, - "engines": { - "node": ">=20.8.1" - }, "peerDependencies": { - "semantic-release": ">=24.1.0" + "ajv": "^8.5.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } } }, - "node_modules/@semantic-release/npm": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/@semantic-release/npm/-/npm-12.0.1.tgz", - "integrity": "sha512-/6nntGSUGK2aTOI0rHPwY3ZjgY9FkXmEHbW9Kr+62NVOsyqpKKeP0lrCH+tphv+EsNdJNmqqwijTEnVWUMQ2Nw==", + "node_modules/@rushstack/node-core-library/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true, - "license": "MIT", + "license": "MIT" + }, + "node_modules/@rushstack/node-core-library/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "license": "ISC", "dependencies": { - "@semantic-release/error": "^4.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@rushstack/node-core-library/node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@rushstack/node-core-library/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@rushstack/problem-matcher": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@rushstack/problem-matcher/-/problem-matcher-0.1.1.tgz", + "integrity": "sha512-Fm5XtS7+G8HLcJHCWpES5VmeMyjAKaWeyZU5qPzZC+22mPlJzAsOxymHiWIfuirtPckX3aptWws+K2d0BzniJA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/node": "*" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@rushstack/rig-package": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@rushstack/rig-package/-/rig-package-0.6.0.tgz", + "integrity": "sha512-ZQmfzsLE2+Y91GF15c65L/slMRVhF6Hycq04D4TwtdGaUAbIXXg9c5pKA5KFU7M4QMaihoobp9JJYpYcaY3zOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve": "~1.22.1", + "strip-json-comments": "~3.1.1" + } + }, + "node_modules/@rushstack/rig-package/node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@rushstack/terminal": { + "version": "0.19.3", + "resolved": "https://registry.npmjs.org/@rushstack/terminal/-/terminal-0.19.3.tgz", + "integrity": "sha512-0P8G18gK9STyO+CNBvkKPnWGMxESxecTYqOcikHOVIHXa9uAuTK+Fw8TJq2Gng1w7W6wTC9uPX6hGNvrMll2wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rushstack/node-core-library": "5.18.0", + "@rushstack/problem-matcher": "0.1.1", + "supports-color": "~8.1.1" + }, + "peerDependencies": { + "@types/node": "*" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@rushstack/terminal/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/@rushstack/ts-command-line": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-5.1.3.tgz", + "integrity": "sha512-Kdv0k/BnnxIYFlMVC1IxrIS0oGQd4T4b7vKfx52Y2+wk2WZSDFIvedr7JrhenzSlm3ou5KwtoTGTGd5nbODRug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rushstack/terminal": "0.19.3", + "@types/argparse": "1.0.38", + "argparse": "~1.0.9", + "string-argv": "~0.3.1" + } + }, + "node_modules/@rushstack/ts-command-line/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@sec-ant/readable-stream": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", + "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@semantic-release/commit-analyzer": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/@semantic-release/commit-analyzer/-/commit-analyzer-13.0.1.tgz", + "integrity": "sha512-wdnBPHKkr9HhNhXOhZD5a2LNl91+hs8CC2vsAVYxtZH3y0dV3wKn+uZSN61rdJQZ8EGxzWB3inWocBHV9+u/CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "conventional-changelog-angular": "^8.0.0", + "conventional-changelog-writer": "^8.0.0", + "conventional-commits-filter": "^5.0.0", + "conventional-commits-parser": "^6.0.0", + "debug": "^4.0.0", + "import-from-esm": "^2.0.0", + "lodash-es": "^4.17.21", + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">=20.8.1" + }, + "peerDependencies": { + "semantic-release": ">=20.1.0" + } + }, + "node_modules/@semantic-release/error": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-4.0.0.tgz", + "integrity": "sha512-mgdxrHTLOjOddRVYIYDo0fR3/v61GNN1YGkfbrjuIKg/uMgCd+Qzo3UAXJ+woLQQpos4pl5Esuw5A7AoNlzjUQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@semantic-release/github": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-11.0.1.tgz", + "integrity": "sha512-Z9cr0LgU/zgucbT9cksH0/pX9zmVda9hkDPcgIE0uvjMQ8w/mElDivGjx1w1pEQ+MuQJ5CBq3VCF16S6G4VH3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/core": "^6.0.0", + "@octokit/plugin-paginate-rest": "^11.0.0", + "@octokit/plugin-retry": "^7.0.0", + "@octokit/plugin-throttling": "^9.0.0", + "@semantic-release/error": "^4.0.0", + "aggregate-error": "^5.0.0", + "debug": "^4.3.4", + "dir-glob": "^3.0.1", + "globby": "^14.0.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "issue-parser": "^7.0.0", + "lodash-es": "^4.17.21", + "mime": "^4.0.0", + "p-filter": "^4.0.0", + "url-join": "^5.0.0" + }, + "engines": { + "node": ">=20.8.1" + }, + "peerDependencies": { + "semantic-release": ">=24.1.0" + } + }, + "node_modules/@semantic-release/npm": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/@semantic-release/npm/-/npm-12.0.1.tgz", + "integrity": "sha512-/6nntGSUGK2aTOI0rHPwY3ZjgY9FkXmEHbW9Kr+62NVOsyqpKKeP0lrCH+tphv+EsNdJNmqqwijTEnVWUMQ2Nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@semantic-release/error": "^4.0.0", "aggregate-error": "^5.0.0", "execa": "^9.0.0", "fs-extra": "^11.0.0", @@ -1897,6 +2411,13 @@ "@swc/counter": "^0.1.3" } }, + "node_modules/@types/argparse": { + "version": "1.0.38", + "resolved": "https://registry.npmjs.org/@types/argparse/-/argparse-1.0.38.tgz", + "integrity": "sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", @@ -1904,6 +2425,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/node": { + "version": "24.10.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.0.tgz", + "integrity": "sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, "node_modules/@types/normalize-package-data": { "version": "2.4.4", "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", @@ -1911,65 +2442,464 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/prop-types": { - "version": "15.7.14", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", - "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==", + "node_modules/@types/react": { + "version": "19.2.2", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz", + "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.2", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.2.tgz", + "integrity": "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@types/semver": { + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", + "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.46.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.4.tgz", + "integrity": "sha512-R48VhmTJqplNyDxCyqqVkFSZIx1qX6PzwqgcXn1olLrzxcSBDlOsbtcnQuQhNtnNiJ4Xe5gREI1foajYaYU2Vg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.46.4", + "@typescript-eslint/type-utils": "8.46.4", + "@typescript-eslint/utils": "8.46.4", + "@typescript-eslint/visitor-keys": "8.46.4", + "graphemer": "^1.4.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.46.4", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.46.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.46.4.tgz", + "integrity": "sha512-tK3GPFWbirvNgsNKto+UmB/cRtn6TZfyw0D6IKrW55n6Vbs7KJoZtI//kpTKzE/DUmmnAFD8/Ca46s7Obs92/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.46.4", + "@typescript-eslint/types": "8.46.4", + "@typescript-eslint/typescript-estree": "8.46.4", + "@typescript-eslint/visitor-keys": "8.46.4", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.46.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.46.4.tgz", + "integrity": "sha512-nPiRSKuvtTN+no/2N1kt2tUh/HoFzeEgOm9fQ6XQk4/ApGqjx0zFIIaLJ6wooR1HIoozvj2j6vTi/1fgAz7UYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.46.4", + "@typescript-eslint/types": "^8.46.4", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.46.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.46.4.tgz", + "integrity": "sha512-tMDbLGXb1wC+McN1M6QeDx7P7c0UWO5z9CXqp7J8E+xGcJuUuevWKxuG8j41FoweS3+L41SkyKKkia16jpX7CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.46.4", + "@typescript-eslint/visitor-keys": "8.46.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.46.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.46.4.tgz", + "integrity": "sha512-+/XqaZPIAk6Cjg7NWgSGe27X4zMGqrFqZ8atJsX3CWxH/jACqWnrWI68h7nHQld0y+k9eTTjb9r+KU4twLoo9A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.46.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.46.4.tgz", + "integrity": "sha512-V4QC8h3fdT5Wro6vANk6eojqfbv5bpwHuMsBcJUJkqs2z5XnYhJzyz9Y02eUmF9u3PgXEUiOt4w4KHR3P+z0PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.46.4", + "@typescript-eslint/typescript-estree": "8.46.4", + "@typescript-eslint/utils": "8.46.4", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.46.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.4.tgz", + "integrity": "sha512-USjyxm3gQEePdUwJBFjjGNG18xY9A2grDVGuk7/9AkjIF1L+ZrVnwR5VAU5JXtUnBL/Nwt3H31KlRDaksnM7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.46.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.4.tgz", + "integrity": "sha512-7oV2qEOr1d4NWNmpXLR35LvCfOkTNymY9oyW+lUHkmCno7aOmIf/hMaydnJBUTBMRCOGZh8YjkFOc8dadEoNGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.46.4", + "@typescript-eslint/tsconfig-utils": "8.46.4", + "@typescript-eslint/types": "8.46.4", + "@typescript-eslint/visitor-keys": "8.46.4", + "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" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.46.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.46.4.tgz", + "integrity": "sha512-AbSv11fklGXV6T28dp2Me04Uw90R2iJ30g2bgLz529Koehrmkbs1r7paFqr1vPCZi7hHwYxYtxfyQMRC8QaVSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.46.4", + "@typescript-eslint/types": "8.46.4", + "@typescript-eslint/typescript-estree": "8.46.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.46.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.4.tgz", + "integrity": "sha512-/++5CYLQqsO9HFGLI7APrxBJYo+5OCMpViuhV8q5/Qa3o5mMrF//eQHks+PXcsAVaLdn817fMuS7zqoXNNZGaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.46.4", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.1.tgz", + "integrity": "sha512-fEzPV3hSkSMltkw152tJKNARhOupqbH96MZWyRjNaYZOMIzbrTeQDG+MTc6Mr2pgzFQzFxAfmhGDNP5QK++2ZA==", + "dev": true, + "license": "ISC" + }, + "node_modules/@vitejs/plugin-react-swc": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.7.2.tgz", + "integrity": "sha512-y0byko2b2tSVVf5Gpng1eEhX1OvPC7x8yns1Fx8jDzlJp4LS6CMkCPfLw47cjyoMrshQDoQw4qcgjsU9VvlCew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@swc/core": "^1.7.26" + }, + "peerDependencies": { + "vite": "^4 || ^5 || ^6" + } + }, + "node_modules/@volar/language-core": { + "version": "2.4.23", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.23.tgz", + "integrity": "sha512-hEEd5ET/oSmBC6pi1j6NaNYRWoAiDhINbT8rmwtINugR39loROSlufGdYMF9TaKGfz+ViGs1Idi3mAhnuPcoGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/source-map": "2.4.23" + } + }, + "node_modules/@volar/source-map": { + "version": "2.4.23", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.23.tgz", + "integrity": "sha512-Z1Uc8IB57Lm6k7q6KIDu/p+JWtf3xsXJqAX/5r18hYOTpJyBn0KXUR8oTJ4WFYOcDzWC9n3IflGgHowx6U6z9Q==", "dev": true, "license": "MIT" }, - "node_modules/@types/react": { - "version": "18.3.18", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz", - "integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==", + "node_modules/@volar/typescript": { + "version": "2.4.23", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.23.tgz", + "integrity": "sha512-lAB5zJghWxVPqfcStmAP1ZqQacMpe90UrP5RJ3arDyrhy4aCUQqmxPPLB2PWDKugvylmO41ljK7vZ+t6INMTag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.23", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.24", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.24.tgz", + "integrity": "sha512-eDl5H57AOpNakGNAkFDH+y7kTqrQpJkZFXhWZQGyx/5Wh7B1uQYvcWkvZi11BDhscPgj8N7XV3oRwiPnx1Vrig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@vue/shared": "3.5.24", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.24", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.24.tgz", + "integrity": "sha512-1QHGAvs53gXkWdd3ZMGYuvQFXHW4ksKWPG8HP8/2BscrbZ0brw183q2oNWjMrSWImYLHxHrx1ItBQr50I/q2zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.24", + "@vue/shared": "3.5.24" + } + }, + "node_modules/@vue/compiler-vue2": { + "version": "2.7.16", + "resolved": "https://registry.npmjs.org/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz", + "integrity": "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==", + "dev": true, + "license": "MIT", + "dependencies": { + "de-indent": "^1.0.2", + "he": "^1.2.0" + } + }, + "node_modules/@vue/language-core": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.2.0.tgz", + "integrity": "sha512-O1ZZFaaBGkKbsRfnVH1ifOK1/1BUkyK+3SQsfnh6PmMmD4qJcTU8godCeA96jjDRTL6zgnK7YzCHfaUlH2r0Mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "~2.4.11", + "@vue/compiler-dom": "^3.5.0", + "@vue/compiler-vue2": "^2.7.16", + "@vue/shared": "^3.5.0", + "alien-signals": "^0.4.9", + "minimatch": "^9.0.3", + "muggle-string": "^0.4.1", + "path-browserify": "^1.0.1" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vue/language-core/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { - "@types/prop-types": "*", - "csstype": "^3.0.2" + "balanced-match": "^1.0.0" } }, - "node_modules/@types/react-dom": { - "version": "18.3.5", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.5.tgz", - "integrity": "sha512-P4t6saawp+b/dFrUr2cvkVsfvPguwsxtH6dNIYRllMsefqFzkZk5UIjzyDOv5g1dXIPdG4Sp1yCR4Z6RCUsG/Q==", + "node_modules/@vue/language-core/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, - "license": "MIT", - "peerDependencies": { - "@types/react": "^18.0.0" + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@types/semver": { - "version": "7.5.8", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", - "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", + "node_modules/@vue/shared": { + "version": "3.5.24", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.24.tgz", + "integrity": "sha512-9cwHL2EsJBdi8NY22pngYYWzkTDhld6fAD6jlaeloNGciNSJL6bLpbxVgXl96X00Jtc6YWQv96YA/0sxex/k1A==", "dev": true, "license": "MIT" }, - "node_modules/@ungap/structured-clone": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.1.tgz", - "integrity": "sha512-fEzPV3hSkSMltkw152tJKNARhOupqbH96MZWyRjNaYZOMIzbrTeQDG+MTc6Mr2pgzFQzFxAfmhGDNP5QK++2ZA==", - "dev": true, - "license": "ISC" - }, - "node_modules/@vitejs/plugin-react-swc": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.7.2.tgz", - "integrity": "sha512-y0byko2b2tSVVf5Gpng1eEhX1OvPC7x8yns1Fx8jDzlJp4LS6CMkCPfLw47cjyoMrshQDoQw4qcgjsU9VvlCew==", - "dev": true, - "license": "MIT", - "dependencies": { - "@swc/core": "^1.7.26" - }, - "peerDependencies": { - "vite": "^4 || ^5 || ^6" - } - }, "node_modules/acorn": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", - "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", "bin": { @@ -2033,6 +2963,55 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/alien-signals": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-0.4.14.tgz", + "integrity": "sha512-itUAVzhczTmP2U5yX67xVpsbbOiquusbWVyA9N+sy6+r6YVbFkahXvNCeEPWEOMhwDYwbVbGHFkVL03N9I5g+Q==", + "dev": true, + "license": "MIT" + }, "node_modules/ansi-escapes": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz", @@ -2592,6 +3571,13 @@ "dot-prop": "^5.1.0" } }, + "node_modules/compare-versions": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.1.tgz", + "integrity": "sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==", + "dev": true, + "license": "MIT" + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2599,6 +3585,13 @@ "dev": true, "license": "MIT" }, + "node_modules/confbox": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", + "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", + "dev": true, + "license": "MIT" + }, "node_modules/config-chain": { "version": "1.1.13", "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", @@ -2834,6 +3827,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/de-indent": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", + "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", + "dev": true, + "license": "MIT" + }, "node_modules/debug": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", @@ -2929,6 +3929,16 @@ "node": ">=0.10" } }, + "node_modules/diff": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.2.tgz", + "integrity": "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -3007,6 +4017,19 @@ "dev": true, "license": "MIT" }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/env-ci": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/env-ci/-/env-ci-11.1.0.tgz", @@ -3601,6 +4624,13 @@ "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, + "license": "MIT" + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -3655,6 +4685,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/exsolve": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz", + "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", + "dev": true, + "license": "MIT" + }, "node_modules/fast-content-type-parse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-2.0.1.tgz", @@ -3723,6 +4760,23 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/fastq": { "version": "1.18.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.18.0.tgz", @@ -4347,6 +5401,16 @@ "node": ">= 0.4" } }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, "node_modules/highlight.js": { "version": "10.7.3", "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", @@ -4500,6 +5564,16 @@ "node": ">=18.20" } }, + "node_modules/import-lazy": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz", + "integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/import-meta-resolve": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz", @@ -5117,6 +6191,13 @@ "node": ">= 0.6.0" } }, + "node_modules/jju": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz", + "integrity": "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==", + "dev": true, + "license": "MIT" + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -5210,6 +6291,13 @@ "json-buffer": "3.0.1" } }, + "node_modules/kolorist": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz", + "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==", + "dev": true, + "license": "MIT" + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -5261,6 +6349,24 @@ "node": ">=4" } }, + "node_modules/local-pkg": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.2.tgz", + "integrity": "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mlly": "^1.7.4", + "pkg-types": "^2.3.0", + "quansync": "^0.2.11" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -5277,6 +6383,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash-es": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", @@ -5345,6 +6458,16 @@ "dev": true, "license": "ISC" }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, "node_modules/marked": { "version": "12.0.2", "resolved": "https://registry.npmjs.org/marked/-/marked-12.0.2.tgz", @@ -5522,6 +6645,38 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/mlly": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz", + "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.15.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.1" + } + }, + "node_modules/mlly/node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/mlly/node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -5529,6 +6684,13 @@ "dev": true, "license": "MIT" }, + "node_modules/muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "dev": true, + "license": "MIT" + }, "node_modules/mz": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", @@ -8808,6 +9970,13 @@ "dev": true, "license": "MIT" }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, + "license": "MIT" + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -8855,6 +10024,13 @@ "node": ">=8" } }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -8962,6 +10138,18 @@ "node": ">=4" } }, + "node_modules/pkg-types": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", + "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.2.2", + "exsolve": "^1.0.7", + "pathe": "^2.0.3" + } + }, "node_modules/possible-typed-array-names": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", @@ -9070,6 +10258,23 @@ "node": ">=6" } }, + "node_modules/quansync": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", + "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], + "license": "MIT" + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -9363,6 +10568,16 @@ "node": ">=0.10.0" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve": { "version": "2.0.0-next.5", "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", @@ -10060,6 +11275,13 @@ "through2": "~2.0.0" } }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/stream-combiner2": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz", @@ -10081,6 +11303,16 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/string-argv": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6.19" + } + }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -10502,6 +11734,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -10606,6 +11851,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", + "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", + "dev": true, + "license": "MIT" + }, "node_modules/uglify-js": { "version": "3.19.3", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", @@ -10639,6 +11905,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + }, "node_modules/unicode-emoji-modifier-base": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz", @@ -10808,6 +12081,33 @@ } } }, + "node_modules/vite-plugin-dts": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/vite-plugin-dts/-/vite-plugin-dts-4.5.4.tgz", + "integrity": "sha512-d4sOM8M/8z7vRXHHq/ebbblfaxENjogAAekcfcDCCwAyvGqnPrc7f4NZbvItS+g4WTgerW0xDwSz5qz11JT3vg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@microsoft/api-extractor": "^7.50.1", + "@rollup/pluginutils": "^5.1.4", + "@volar/typescript": "^2.4.11", + "@vue/language-core": "2.2.0", + "compare-versions": "^6.1.1", + "debug": "^4.4.0", + "kolorist": "^1.8.0", + "local-pkg": "^1.0.0", + "magic-string": "^0.30.17" + }, + "peerDependencies": { + "typescript": "*", + "vite": "*" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, "node_modules/vite/node_modules/fdir": { "version": "6.4.4", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", @@ -10836,6 +12136,13 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/vscode-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", + "dev": true, + "license": "MIT" + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -11002,6 +12309,13 @@ "node": ">=10" } }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index 2f77316..9131c5f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -3,7 +3,19 @@ "private": false, "version": "1.10.5", "type": "module", - "module": "dist/react-file-manager.es.js", + "main": "./dist/react-file-manager.cjs.js", + "module": "./dist/react-file-manager.es.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/react-file-manager.es.js", + "require": "./dist/react-file-manager.cjs.js", + "default": "./dist/react-file-manager.es.js" + }, + "./style.css": "./dist/style.css", + "./dist/style.css": "./dist/style.css" + }, "files": [ "dist/", "README.md", @@ -15,7 +27,7 @@ "scripts": { "dev": "vite", "build": "vite build", - "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", + "lint": "eslint . --ext ts,tsx,js,jsx --report-unused-disable-directives --max-warnings 0", "preview": "vite preview", "semantic-release": "semantic-release" }, @@ -25,8 +37,11 @@ "react-icons": "^5.4.0" }, "devDependencies": { - "@types/react": "^18.3.3", - "@types/react-dom": "^18.3.0", + "@types/node": "^24.10.0", + "@types/react": "^19.2.2", + "@types/react-dom": "^19.2.2", + "@typescript-eslint/eslint-plugin": "^8.46.4", + "@typescript-eslint/parser": "^8.46.4", "@vitejs/plugin-react-swc": "^3.5.0", "axios": "^1.7.7", "eslint": "^8.57.0", @@ -37,7 +52,9 @@ "react-dom": "^18.3.1", "sass": "^1.77.6", "semantic-release": "^24.1.0", - "vite": "^6.3.6" + "typescript": "^5.9.3", + "vite": "^6.3.6", + "vite-plugin-dts": "^4.5.4" }, "peerDependencies": { "react": ">=18", @@ -52,7 +69,6 @@ } }, "description": "React File Manager is an open-source, user-friendly component designed to easily manage files and folders within applications. With smooth drag-and-drop functionality, responsive design, and efficient navigation, it simplifies file handling in any React project.", - "main": "src/index.js", "repository": { "type": "git", "url": "git+https://github.com/Saifullah-dev/react-file-manager.git" diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx new file mode 100644 index 0000000..cea398b --- /dev/null +++ b/frontend/src/App.tsx @@ -0,0 +1,194 @@ +import { useEffect, useRef, useState } from "react"; +import { createFolderAPI } from "./api/createFolderAPI"; +import { deleteAPI } from "./api/deleteAPI"; +import { downloadFile } from "./api/downloadFileAPI"; +import { copyItemAPI, moveItemAPI } from "./api/fileTransferAPI"; +import { getAllFilesAPI } from "./api/getAllFilesAPI"; +import { renameAPI } from "./api/renameAPI"; +import "./App.scss"; +import FileManager from "./FileManager/FileManager"; +import { Layout } from "./types/Layout"; +import { + OnCopy, + OnCreateFolder, + OnCut, + OnDelete, + OnDownload, + OnError, + OnFileOpen, + OnFileUploaded, + OnFileUploading, + OnLayoutChange, + OnPaste, + OnRefresh, + OnRename, + OnSelectionChange +} from "./types/FileManagerFunctions"; +import { FileItem } from "./types/File"; + +function App() { + const fileUploadConfig = { + url: import.meta.env.VITE_API_BASE_URL + "/upload", + }; + const [isLoading, setIsLoading] = useState(false); + const [files, setFiles] = useState([]); + const [currentPath, setCurrentPath] = useState(""); + const isMountRef = useRef(false); + + // Get Files + const getFiles = async () => { + setIsLoading(true); + const response = await getAllFilesAPI(); + + if (response.status === 200 && response.data) { + setFiles(response.data); + } else { + console.error(response); + setFiles([]); + } + setIsLoading(false); + }; + + useEffect(() => { + if (isMountRef.current) return; + isMountRef.current = true; + getFiles(); + }, []); + // + + // Create Folder + const handleCreateFolder : OnCreateFolder = async (name, parentFolder) => { + setIsLoading(true); + const response = await createFolderAPI(name, parentFolder?._id as string); + if (response.status === 200 || response.status === 201) { + setFiles((prev) => [...prev, response.data]); + } else { + console.error(response); + } + setIsLoading(false); + }; + // + + // File Upload Handlers + const handleFileUploading : OnFileUploading = (file, parentFolder) => { + console.log(`Uploading file with name ${file.name}`) + return { parentId: parentFolder?._id }; + }; + + const handleFileUploaded : OnFileUploaded = (response) => { + const uploadedFile = JSON.parse(response) as FileItem; + setFiles((prev) => [...prev, uploadedFile]); + }; + // + + // Rename File/Folder + const handleRename : OnRename = async (file, newName) => { + setIsLoading(true); + const response = await renameAPI(file._id as string, newName); + if (response.status === 200) { + getFiles(); + } else { + console.error(response); + } + setIsLoading(false); + }; + // + + // Delete File/Folder + const handleDelete : OnDelete = async (files) => { + setIsLoading(true); + const idsToDelete = files.map((file) => file._id as string); + const response = await deleteAPI(idsToDelete); + if (response.status === 200) { + getFiles(); + } else { + console.error(response); + setIsLoading(false); + } + }; + // + + // Paste File/Folder + const handlePaste : OnPaste = async (copiedItems, destinationFolder, operationType) => { + setIsLoading(true); + const copiedItemIds = copiedItems.map((item) => item._id as string); + if (operationType === "copy") { + await copyItemAPI(copiedItemIds, destinationFolder?._id as string); + } else { + await moveItemAPI(copiedItemIds, destinationFolder?._id as string); + } + await getFiles(); + }; + // + + const handleLayoutChange : OnLayoutChange = (layout : Layout) => { + console.log(layout); + }; + + // Refresh Files + const handleRefresh : OnRefresh = () => { + getFiles(); + }; + // + + const handleFileOpen : OnFileOpen = (file) => { + console.log(`Opening file: ${file.name} ${file.id}`); + }; + + const handleError : OnError = (error, file) => { + console.error(`An error occured for file ${file.name}`, error); + }; + + const handleDownload : OnDownload = async (files) => { + await downloadFile(files); + }; + + const handleCut : OnCut = (files) => { + console.log("Moving Files", files); + }; + + const handleCopy : OnCopy = (files) => { + console.log("Copied Files", files); + }; + + const handleSelectionChange : OnSelectionChange = (files) => { + console.log("Selected Files", files); + }; + + return ( +
+
+ +
+
+ ); +} + +export default App; diff --git a/frontend/src/FileManager/Actions/Actions.jsx b/frontend/src/FileManager/Actions/Actions.tsx similarity index 59% rename from frontend/src/FileManager/Actions/Actions.jsx rename to frontend/src/FileManager/Actions/Actions.tsx index 54a975b..d019c55 100644 --- a/frontend/src/FileManager/Actions/Actions.jsx +++ b/frontend/src/FileManager/Actions/Actions.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from "react"; +import { ReactNode, useEffect, useState } from "react"; import Modal from "../../components/Modal/Modal"; import DeleteAction from "./Delete/Delete.action"; import UploadFileAction from "./UploadFile/UploadFile.action"; @@ -6,6 +6,38 @@ import PreviewFileAction from "./PreviewFile/PreviewFile.action"; import { useSelection } from "../../contexts/SelectionContext"; import { useShortcutHandler } from "../../hooks/useShortcutHandler"; import { useTranslation } from "../../contexts/TranslationProvider"; +import { FileUploadConfiguration } from "../../types/FileUploadConfiguration"; +import { FilePreviewComponent, OnDelete, OnFileUploaded, OnFileUploading, OnRefresh } from "../../types/FileManagerFunctions"; +import { TriggerAction } from "../../types/TriggerAction"; +import { Permissions } from "../../types/Permissions"; + +interface ActionType { + title: string; + width: string; + component: ReactNode; +} + +interface ActionList { + uploadFile: ActionType; + delete: ActionType; + previewFile: ActionType; +} + +type ActionKey = keyof ActionList; + +export interface ActionsProps { + fileUploadConfig?: FileUploadConfiguration; + onFileUploading?: OnFileUploading; + onFileUploaded?: OnFileUploaded; + onDelete: OnDelete; + onRefresh?: OnRefresh; + maxFileSize?: number; + filePreviewPath?: string; + filePreviewComponent?: FilePreviewComponent; + acceptedFileTypes?: string; + triggerAction: TriggerAction; + permissions: Permissions; +} const Actions = ({ fileUploadConfig, @@ -19,15 +51,15 @@ const Actions = ({ acceptedFileTypes, triggerAction, permissions, -}) => { - const [activeAction, setActiveAction] = useState(null); +}: ActionsProps) => { + const [activeAction, setActiveAction] = useState(null); const { selectedFiles } = useSelection(); const t = useTranslation(); // Triggers all the keyboard shortcuts based actions useShortcutHandler(triggerAction, onRefresh, permissions); - const actionTypes = { + const actionTypes: ActionList = { uploadFile: { title: t("upload"), component: ( @@ -60,15 +92,17 @@ const Actions = ({ useEffect(() => { if (triggerAction.isActive) { - const actionType = triggerAction.actionType; + const actionType = triggerAction.actionType as ActionKey; + if (actionType === "previewFile") { - actionTypes[actionType].title = selectedFiles?.name ?? t("preview"); + actionTypes[actionType].title = selectedFiles?.at(0)?.name ?? t("preview"); } + setActiveAction(actionTypes[actionType]); } else { setActiveAction(null); } - }, [triggerAction.isActive]); + }, [triggerAction.isActive, selectedFiles, t]); if (activeAction) { return ( @@ -78,10 +112,12 @@ const Actions = ({ setShow={triggerAction.close} dialogWidth={activeAction.width} > - {activeAction?.component} + {activeAction.component} ); } + + return null; }; -export default Actions; +export default Actions; \ No newline at end of file diff --git a/frontend/src/FileManager/Actions/CreateFolder/CreateFolder.action.jsx b/frontend/src/FileManager/Actions/CreateFolder/CreateFolder.action.tsx similarity index 79% rename from frontend/src/FileManager/Actions/CreateFolder/CreateFolder.action.jsx rename to frontend/src/FileManager/Actions/CreateFolder/CreateFolder.action.tsx index 873b000..7008ba4 100644 --- a/frontend/src/FileManager/Actions/CreateFolder/CreateFolder.action.jsx +++ b/frontend/src/FileManager/Actions/CreateFolder/CreateFolder.action.tsx @@ -1,22 +1,31 @@ -import { useEffect, useState } from "react"; +import { ChangeEvent, KeyboardEvent, RefObject, useEffect, useState } from "react"; import { useDetectOutsideClick } from "../../../hooks/useDetectOutsideClick"; import { duplicateNameHandler } from "../../../utils/duplicateNameHandler"; import NameInput from "../../../components/NameInput/NameInput"; -import ErrorTooltip from "../../../components/ErrorTooltip/ErrorTooltip"; +import ErrorTooltip, { ErrorTooltipXPlacement, ErrorTooltipYPlacement } from "../../../components/ErrorTooltip/ErrorTooltip"; import { useFileNavigation } from "../../../contexts/FileNavigationContext"; import { useLayout } from "../../../contexts/LayoutContext"; -import { validateApiCallback } from "../../../utils/validateApiCallback"; import { useTranslation } from "../../../contexts/TranslationProvider"; +import { ExtendedFileItem } from "../../../types/File"; +import { OnCreateFolder } from "../../../types/FileManagerFunctions"; +import { TriggerAction } from "../../../types/TriggerAction"; + +export interface CreateFolderActionProps { + filesViewRef: RefObject; + file: ExtendedFileItem; + onCreateFolder?: OnCreateFolder; + triggerAction: TriggerAction; +} const maxNameLength = 220; -const CreateFolderAction = ({ filesViewRef, file, onCreateFolder, triggerAction }) => { +const CreateFolderAction = ({ filesViewRef, file, onCreateFolder, triggerAction } : CreateFolderActionProps) => { const [folderName, setFolderName] = useState(file.name); const [folderNameError, setFolderNameError] = useState(false); const [folderErrorMessage, setFolderErrorMessage] = useState(""); - const [errorXPlacement, setErrorXPlacement] = useState("right"); - const [errorYPlacement, setErrorYPlacement] = useState("bottom"); - const outsideClick = useDetectOutsideClick((e) => { + const [errorXPlacement, setErrorXPlacement] = useState("right"); + const [errorYPlacement, setErrorYPlacement] = useState("bottom"); + const outsideClick = useDetectOutsideClick((e) => { e.preventDefault(); e.stopPropagation(); }); @@ -25,14 +34,14 @@ const CreateFolderAction = ({ filesViewRef, file, onCreateFolder, triggerAction const t = useTranslation(); // Folder name change handler function - const handleFolderNameChange = (e) => { + const handleFolderNameChange = (e : ChangeEvent) => { setFolderName(e.target.value); setFolderNameError(false); }; // // Validate folder name and call "onCreateFolder" function - const handleValidateFolderName = (e) => { + const handleValidateFolderName = (e : KeyboardEvent) => { e.stopPropagation(); if (e.key === "Enter") { e.preventDefault(); @@ -68,6 +77,7 @@ const CreateFolderAction = ({ filesViewRef, file, onCreateFolder, triggerAction return () => clearTimeout(autoHideError); } + return; }, [folderNameError]); // @@ -92,7 +102,7 @@ const CreateFolderAction = ({ filesViewRef, file, onCreateFolder, triggerAction newFolderName = duplicateNameHandler("New Folder", true, syncedCurrPathFiles); } - validateApiCallback(onCreateFolder, "onCreateFolder", newFolderName, currentFolder); + onCreateFolder?.(newFolderName, currentFolder!); setCurrentPathFiles((prev) => prev.filter((f) => f.key !== file.key)); triggerAction.close(); } @@ -108,7 +118,7 @@ const CreateFolderAction = ({ filesViewRef, file, onCreateFolder, triggerAction const errorMessageWidth = 292 + 8 + 8 + 5; // 8px padding on left and right + additional 5px for gap const errorMessageHeight = 56 + 20 + 10 + 2; // 20px :before height const filesContainer = filesViewRef.current; - const filesContainerRect = filesContainer.getBoundingClientRect(); + const filesContainerRect = filesContainer!.getBoundingClientRect(); const nameInputContainer = outsideClick.ref.current; const nameInputContainerRect = nameInputContainer.getBoundingClientRect(); diff --git a/frontend/src/FileManager/Actions/Delete/Delete.action.jsx b/frontend/src/FileManager/Actions/Delete/Delete.action.tsx similarity index 72% rename from frontend/src/FileManager/Actions/Delete/Delete.action.jsx rename to frontend/src/FileManager/Actions/Delete/Delete.action.tsx index d3f8b5a..5b27687 100644 --- a/frontend/src/FileManager/Actions/Delete/Delete.action.jsx +++ b/frontend/src/FileManager/Actions/Delete/Delete.action.tsx @@ -1,11 +1,18 @@ -import React, { useEffect, useState } from "react"; +import { useEffect, useState } from "react"; import Button from "../../../components/Button/Button"; import { useSelection } from "../../../contexts/SelectionContext"; import { useTranslation } from "../../../contexts/TranslationProvider"; import "./Delete.action.scss"; +import { TriggerAction } from "../../../types/TriggerAction"; +import { OnDelete } from "../../../types/FileManagerFunctions"; -const DeleteAction = ({ triggerAction, onDelete }) => { - const [deleteMsg, setDeleteMsg] = useState(""); +export interface DeleteActionProps { + triggerAction: TriggerAction; + onDelete?: OnDelete; +} + +const DeleteAction = ({ triggerAction, onDelete } : DeleteActionProps) => { + const [deleteMsg, setDeleteMsg] = useState(""); const { selectedFiles, setSelectedFiles } = useSelection(); const t = useTranslation(); @@ -16,11 +23,12 @@ const DeleteAction = ({ triggerAction, onDelete }) => { } else if (selectedFiles.length > 1) { return t("deleteItemsConfirm", { count: selectedFiles.length }); } + return ""; }); }, [t]); const handleDeleting = () => { - onDelete(selectedFiles); + onDelete?.(selectedFiles); setSelectedFiles([]); triggerAction.close(); }; diff --git a/frontend/src/FileManager/Actions/PreviewFile/PreviewFile.action.jsx b/frontend/src/FileManager/Actions/PreviewFile/PreviewFile.action.tsx similarity index 90% rename from frontend/src/FileManager/Actions/PreviewFile/PreviewFile.action.jsx rename to frontend/src/FileManager/Actions/PreviewFile/PreviewFile.action.tsx index fea2ffd..e5ae0cb 100644 --- a/frontend/src/FileManager/Actions/PreviewFile/PreviewFile.action.jsx +++ b/frontend/src/FileManager/Actions/PreviewFile/PreviewFile.action.tsx @@ -9,13 +9,19 @@ import { useFileIcons } from "../../../hooks/useFileIcons"; import { FaRegFileAlt } from "react-icons/fa"; import { useTranslation } from "../../../contexts/TranslationProvider"; import "./PreviewFile.action.scss"; +import { FilePreviewComponent } from "../../../types/FileManagerFunctions"; + +export interface PreviewFileActionProps { + filePreviewPath?: string; + filePreviewComponent?: FilePreviewComponent; +} const imageExtensions = ["jpg", "jpeg", "png"]; const videoExtensions = ["mp4", "mov", "avi"]; const audioExtensions = ["mp3", "wav", "m4a"]; const iFrameExtensions = ["txt", "pdf"]; -const PreviewFileAction = ({ filePreviewPath, filePreviewComponent }) => { +const PreviewFileAction = ({ filePreviewPath, filePreviewComponent } : PreviewFileActionProps) => { const [isLoading, setIsLoading] = useState(true); const [hasError, setHasError] = useState(false); const { selectedFiles } = useSelection(); @@ -58,7 +64,7 @@ const PreviewFileAction = ({ filePreviewPath, filePreviewComponent }) => { ...iFrameExtensions, ].includes(extension) && (
- {fileIcons[extension] ?? } + {fileIcons[extension as keyof typeof fileIcons] ?? } {t("previewUnavailable")}
{selectedFiles[0].name} @@ -75,7 +81,7 @@ const PreviewFileAction = ({ filePreviewPath, filePreviewComponent }) => { ))} {imageExtensions.includes(extension) && ( <> - + Preview Unavailable; + file: ExtendedFileItem; + onRename?: OnRename; + triggerAction: TriggerAction; +} const maxNameLength = 220; -const RenameAction = ({ filesViewRef, file, onRename, triggerAction }) => { +const RenameAction = ({ filesViewRef, file, onRename, triggerAction } : RenameActionProps) => { const [renameFile, setRenameFile] = useState(file?.name); const [renameFileWarning, setRenameFileWarning] = useState(false); const [fileRenameError, setFileRenameError] = useState(false); const [renameErrorMessage, setRenameErrorMessage] = useState(""); - const [errorXPlacement, setErrorXPlacement] = useState("right"); - const [errorYPlacement, setErrorYPlacement] = useState("bottom"); + const [errorXPlacement, setErrorXPlacement] = useState("right"); + const [errorYPlacement, setErrorYPlacement] = useState("bottom"); const { currentPathFiles, setCurrentPathFiles } = useFileNavigation(); const { activeLayout } = useLayout(); const t = useTranslation(); - const warningModalRef = useRef(null); - const outsideClick = useDetectOutsideClick((e) => { - if (!warningModalRef.current?.contains(e.target)) { + const warningModalRef = useRef(null); + const outsideClick = useDetectOutsideClick((e) => { + if (!warningModalRef.current?.contains(e.target as Node)) { e.preventDefault(); e.stopPropagation(); } }); - const handleValidateFolderRename = (e) => { + const handleValidateFolderRename = (e : KeyboardEvent) => { e.stopPropagation(); if (e.key === "Enter") { e.preventDefault(); @@ -74,10 +83,11 @@ const RenameAction = ({ filesViewRef, file, onRename, triggerAction }) => { return () => clearTimeout(autoHideError); } + return; }, [fileRenameError]); // - function handleFileRenaming(isConfirmed) { + function handleFileRenaming(isConfirmed : boolean) { if (renameFile === "" || renameFile === file.name) { setCurrentPathFiles((prev) => prev.map((f) => { @@ -103,7 +113,7 @@ const RenameAction = ({ filesViewRef, file, onRename, triggerAction }) => { } } setFileRenameError(false); - validateApiCallback(onRename, "onRename", file, renameFile); + onRename?.(file, renameFile); setCurrentPathFiles((prev) => prev.filter((f) => f.key !== file.key)); // Todo: Should only filter on success API call triggerAction.close(); } @@ -124,7 +134,7 @@ const RenameAction = ({ filesViewRef, file, onRename, triggerAction }) => { focusName(); // Dynamic Error Message Placement based on available space - if (outsideClick.ref?.current) { + if (outsideClick.ref?.current && filesViewRef.current) { const errorMessageWidth = 292 + 8 + 8 + 5; // 8px padding on left and right + additional 5px for gap const errorMessageHeight = 56 + 20 + 10 + 2; // 20px :before height const filesContainer = filesViewRef.current; diff --git a/frontend/src/FileManager/Actions/UploadFile/UploadFile.action.jsx b/frontend/src/FileManager/Actions/UploadFile/UploadFile.action.tsx similarity index 78% rename from frontend/src/FileManager/Actions/UploadFile/UploadFile.action.jsx rename to frontend/src/FileManager/Actions/UploadFile/UploadFile.action.tsx index 465b256..ee8572c 100644 --- a/frontend/src/FileManager/Actions/UploadFile/UploadFile.action.jsx +++ b/frontend/src/FileManager/Actions/UploadFile/UploadFile.action.tsx @@ -1,7 +1,7 @@ -import { useRef, useState } from "react"; +import { ChangeEvent, DragEvent, KeyboardEvent, useRef, useState } from "react"; import Button from "../../../components/Button/Button"; import { AiOutlineCloudUpload } from "react-icons/ai"; -import UploadItem from "./UploadItem"; +import UploadItem, { UploadFileData } from "./UploadItem"; import Loader from "../../../components/Loader/Loader"; import { useFileNavigation } from "../../../contexts/FileNavigationContext"; import { getFileExtension } from "../../../utils/getFileExtension"; @@ -9,6 +9,16 @@ import { getDataSize } from "../../../utils/getDataSize"; import { useFiles } from "../../../contexts/FilesContext"; import { useTranslation } from "../../../contexts/TranslationProvider"; import "./UploadFile.action.scss"; +import { FileUploadConfiguration } from "../../../types/FileUploadConfiguration"; +import { OnFileUploaded, OnFileUploading } from "../../../types/FileManagerFunctions"; + +export interface UploadFileActionProps { + fileUploadConfig?: FileUploadConfiguration; + maxFileSize?: number; + acceptedFileTypes?: string; + onFileUploading?: OnFileUploading; + onFileUploaded?: OnFileUploaded; +} const UploadFileAction = ({ fileUploadConfig, @@ -16,28 +26,28 @@ const UploadFileAction = ({ acceptedFileTypes, onFileUploading, onFileUploaded, -}) => { - const [files, setFiles] = useState([]); +} : UploadFileActionProps) => { + const [files, setFiles] = useState([]); const [isDragging, setIsDragging] = useState(false); - const [isUploading, setIsUploading] = useState({}); + const [isUploading, setIsUploading] = useState>({}); const { currentFolder, currentPathFiles } = useFileNavigation(); const { onError } = useFiles(); - const fileInputRef = useRef(null); + const fileInputRef = useRef(null); const t = useTranslation(); // To open choose file if the "Choose File" button is focused and Enter key is pressed - const handleChooseFileKeyDown = (e) => { + const handleChooseFileKeyDown = (e : KeyboardEvent) => { if (e.key === "Enter") { - fileInputRef.current.click(); + fileInputRef.current?.click(); } }; - const checkFileError = (file) => { + const checkFileError = (file : File) => { if (acceptedFileTypes) { const extError = !acceptedFileTypes.includes(getFileExtension(file.name)); if (extError) return t("fileTypeNotAllowed"); } - + const fileExists = currentPathFiles.some( (item) => item.name.toLowerCase() === file.name.toLowerCase() && !item.isDirectory ); @@ -45,9 +55,11 @@ const UploadFileAction = ({ const sizeError = maxFileSize && file.size > maxFileSize; if (sizeError) return `${t("maxUploadSize")} ${getDataSize(maxFileSize, 0)}.`; + + return undefined; }; - const setSelectedFiles = (selectedFiles) => { + const setSelectedFiles = (selectedFiles : File[]) => { selectedFiles = selectedFiles.filter( (item) => !files.some((fileData) => fileData.file.name.toLowerCase() === item.name.toLowerCase()) @@ -55,7 +67,7 @@ const UploadFileAction = ({ if (selectedFiles.length > 0) { const newFiles = selectedFiles.map((file) => { - const appendData = onFileUploading(file, currentFolder); + const appendData = onFileUploading?.(file, currentFolder!); const error = checkFileError(file); error && onError({ type: "upload", message: error }, file); return { @@ -68,20 +80,19 @@ const UploadFileAction = ({ } }; - // Todo: Also validate allowed file extensions on drop - const handleDrop = (e) => { + const handleDrop = (e : DragEvent) => { e.preventDefault(); setIsDragging(false); const droppedFiles = Array.from(e.dataTransfer.files); setSelectedFiles(droppedFiles); }; - const handleChooseFile = (e) => { - const choosenFiles = Array.from(e.target.files); + const handleChooseFile = (e : ChangeEvent) => { + const choosenFiles = Array.from(e.target.files ?? []); setSelectedFiles(choosenFiles); }; - const handleFileRemove = (index) => { + const handleFileRemove = (index : number) => { setFiles((prev) => { const newFiles = prev.map((file, i) => { if (index === i) { diff --git a/frontend/src/FileManager/Actions/UploadFile/UploadItem.jsx b/frontend/src/FileManager/Actions/UploadFile/UploadItem.tsx similarity index 80% rename from frontend/src/FileManager/Actions/UploadFile/UploadItem.jsx rename to frontend/src/FileManager/Actions/UploadFile/UploadItem.tsx index e6dd77e..fbe3c7e 100644 --- a/frontend/src/FileManager/Actions/UploadFile/UploadItem.jsx +++ b/frontend/src/FileManager/Actions/UploadFile/UploadItem.tsx @@ -3,12 +3,32 @@ import Progress from "../../../components/Progress/Progress"; import { getFileExtension } from "../../../utils/getFileExtension"; import { useFileIcons } from "../../../hooks/useFileIcons"; import { FaRegFile } from "react-icons/fa6"; -import { useEffect, useRef, useState } from "react"; +import { Dispatch, SetStateAction, useEffect, useRef, useState } from "react"; import { getDataSize } from "../../../utils/getDataSize"; import { FaRegCheckCircle } from "react-icons/fa"; import { IoMdRefresh } from "react-icons/io"; import { useFiles } from "../../../contexts/FilesContext"; import { useTranslation } from "../../../contexts/TranslationProvider"; +import { FileUploadConfiguration } from "../../../types/FileUploadConfiguration"; +import { OnFileUploaded, OnFileUploading } from "../../../types/FileManagerFunctions"; + +export interface UploadFileData { + file: File; + appendData?: { [key: string]: any }; + error?: string; + removed?: boolean; +} + +export interface UploadItemProps { + index: number; + fileData: UploadFileData; + setFiles: Dispatch>; + fileUploadConfig?: FileUploadConfiguration; + setIsUploading: Dispatch>>; + onFileUploading?: OnFileUploading; + onFileUploaded?: OnFileUploaded; + handleFileRemove: (index : number) => void; +} const UploadItem = ({ index, @@ -18,17 +38,17 @@ const UploadItem = ({ fileUploadConfig, onFileUploaded, handleFileRemove, -}) => { +} : UploadItemProps) => { const [uploadProgress, setUploadProgress] = useState(0); const [isUploaded, setIsUploaded] = useState(false); const [isCanceled, setIsCanceled] = useState(false); const [uploadFailed, setUploadFailed] = useState(false); const fileIcons = useFileIcons(33); - const xhrRef = useRef(); + const xhrRef = useRef(null); const { onError } = useFiles(); const t = useTranslation(); - const handleUploadError = (xhr) => { + const handleUploadError = (xhr : XMLHttpRequest) => { setUploadProgress(0); setIsUploading((prev) => ({ ...prev, @@ -61,7 +81,7 @@ const UploadItem = ({ onError(error, fileData.file); }; - const fileUpload = (fileData) => { + const fileUpload = (fileData : UploadFileData) => { if (!!fileData.error) return; return new Promise((resolve, reject) => { @@ -86,7 +106,7 @@ const UploadItem = ({ })); if (xhr.status === 200 || xhr.status === 201) { setIsUploaded(true); - onFileUploaded(xhr.response); + onFileUploaded?.(xhr.response); resolve(xhr.response); } else { reject(xhr.statusText); @@ -100,7 +120,7 @@ const UploadItem = ({ }; const method = fileUploadConfig?.method || "POST"; - xhr.open(method, fileUploadConfig?.url, true); + xhr.open(method, fileUploadConfig?.url ?? "", true); const headers = fileUploadConfig?.headers; for (let key in headers) { xhr.setRequestHeader(key, headers[key]); @@ -143,13 +163,13 @@ const UploadItem = ({ if (index === i) { return { ...file, - error: false, + error: undefined, }; } return file; }) ); - fileUpload({ ...fileData, error: false }); + fileUpload({ ...fileData, error: undefined }); setIsCanceled(false); setUploadFailed(false); } @@ -164,7 +184,7 @@ const UploadItem = ({ return (
  • - {fileIcons[getFileExtension(fileData.file?.name)] ?? } + {fileIcons[getFileExtension(fileData.file?.name) as keyof typeof fileIcons] ?? }
    diff --git a/frontend/src/FileManager/BreadCrumb/BreadCrumb.jsx b/frontend/src/FileManager/BreadCrumb/BreadCrumb.tsx similarity index 67% rename from frontend/src/FileManager/BreadCrumb/BreadCrumb.jsx rename to frontend/src/FileManager/BreadCrumb/BreadCrumb.tsx index cd9a93b..4b0f155 100644 --- a/frontend/src/FileManager/BreadCrumb/BreadCrumb.jsx +++ b/frontend/src/FileManager/BreadCrumb/BreadCrumb.tsx @@ -1,5 +1,4 @@ -import { useEffect, useRef, useState } from "react"; -import PropTypes from "prop-types"; +import { Dispatch, SetStateAction, useCallback, useEffect, useRef, useState } from "react"; import { MdHome, MdMoreHoriz, MdOutlineNavigateNext } from "react-icons/md"; import { TbLayoutSidebarLeftExpand, TbLayoutSidebarLeftCollapseFilled } from "react-icons/tb"; import { useFileNavigation } from "../../contexts/FileNavigationContext"; @@ -7,21 +6,32 @@ import { useDetectOutsideClick } from "../../hooks/useDetectOutsideClick"; import { useTranslation } from "../../contexts/TranslationProvider"; import "./BreadCrumb.scss"; -const BreadCrumb = ({ collapsibleNav, isNavigationPaneOpen, setNavigationPaneOpen }) => { - const [folders, setFolders] = useState([]); - const [hiddenFolders, setHiddenFolders] = useState([]); - const [hiddenFoldersWidth, setHiddenFoldersWidth] = useState([]); +export interface FolderCrumb { + name: string; + path: string; +} + +export interface BreadCrumbProps { + collapsibleNav: boolean; + isNavigationPaneOpen: boolean; + setNavigationPaneOpen: Dispatch>; +} + +const BreadCrumb = ({ collapsibleNav, isNavigationPaneOpen, setNavigationPaneOpen }: BreadCrumbProps) => { + const [folders, setFolders] = useState([]); + const [hiddenFolders, setHiddenFolders] = useState([]); + const [hiddenFoldersWidth, setHiddenFoldersWidth] = useState([]); // Changed type const [showHiddenFolders, setShowHiddenFolders] = useState(false); const { currentPath, setCurrentPath, onFolderChange } = useFileNavigation(); - const breadCrumbRef = useRef(null); - const foldersRef = useRef([]); - const moreBtnRef = useRef(null); - const popoverRef = useDetectOutsideClick(() => { + const breadCrumbRef = useRef(null); + const foldersRef = useRef<(HTMLSpanElement | null)[]>([]); + const moreBtnRef = useRef(null); + const popoverRef = useDetectOutsideClick(() => { setShowHiddenFolders(false); }); const t = useTranslation(); - const navTogglerRef = useRef(null); + const navTogglerRef = useRef(null); useEffect(() => { setFolders(() => { @@ -31,25 +41,27 @@ const BreadCrumb = ({ collapsibleNav, isNavigationPaneOpen, setNavigationPaneOpe name: item || t("home"), path: item === "" ? item : (path += `/${item}`), }; - }); + }) ?? []; }); setHiddenFolders([]); setHiddenFoldersWidth([]); }, [currentPath, t]); - const switchPath = (path) => { - setCurrentPath(path); + const switchPath = (path: string) => { + setCurrentPath?.(path); onFolderChange?.(path); }; const getBreadCrumbWidth = () => { + if (!breadCrumbRef.current) return 0; + const containerWidth = breadCrumbRef.current.clientWidth; const containerStyles = getComputedStyle(breadCrumbRef.current); const paddingLeft = parseFloat(containerStyles.paddingLeft); const navTogglerGap = collapsibleNav ? 2 : 0; const navTogglerDividerWidth = 1; const navTogglerWidth = collapsibleNav - ? navTogglerRef.current?.clientWidth + navTogglerDividerWidth + ? (navTogglerRef.current?.clientWidth ?? 0) + navTogglerDividerWidth : 0; const moreBtnGap = hiddenFolders.length > 0 ? 1 : 0; const flexGap = parseFloat(containerStyles.gap) * (folders.length + moreBtnGap + navTogglerGap); @@ -66,24 +78,33 @@ const BreadCrumb = ({ collapsibleNav, isNavigationPaneOpen, setNavigationPaneOpe return availableSpace - (remainingFoldersWidth + moreBtnWidth); }; - const isBreadCrumbOverflowing = () => { + const isBreadCrumbOverflowing = useCallback(() => { + if (!breadCrumbRef.current) return false; return breadCrumbRef.current.scrollWidth > breadCrumbRef.current.clientWidth; - }; + }, []); useEffect(() => { if (isBreadCrumbOverflowing()) { const hiddenFolder = folders[1]; const hiddenFolderWidth = foldersRef.current[1]?.clientWidth; - setHiddenFoldersWidth((prev) => [...prev, hiddenFolderWidth]); - setHiddenFolders((prev) => [...prev, hiddenFolder]); - setFolders((prev) => prev.filter((_, index) => index !== 1)); - } else if (hiddenFolders.length > 0 && checkAvailableSpace() > hiddenFoldersWidth.at(-1)) { - const newFolders = [folders[0], hiddenFolders.at(-1), ...folders.slice(1)]; - setFolders(newFolders); - setHiddenFolders((prev) => prev.slice(0, -1)); - setHiddenFoldersWidth((prev) => prev.slice(0, -1)); + + if (hiddenFolder && hiddenFolderWidth) { + setHiddenFoldersWidth((prev) => [...prev, hiddenFolderWidth]); + setHiddenFolders((prev) => [...prev, hiddenFolder]); + setFolders((prev) => prev.filter((_, index) => index !== 1)); + } + } else if (hiddenFolders.length > 0) { + const lastHiddenWidth = hiddenFoldersWidth.at(-1); + const lastHiddenFolder = hiddenFolders.at(-1); + + if (lastHiddenWidth !== undefined && lastHiddenFolder && checkAvailableSpace() > lastHiddenWidth) { + const newFolders = [folders[0], lastHiddenFolder, ...folders.slice(1)]; + setFolders(newFolders); + setHiddenFolders((prev) => prev.slice(0, -1)); + setHiddenFoldersWidth((prev) => prev.slice(0, -1)); + } } - }, [isBreadCrumbOverflowing]); + }, [folders, hiddenFolders, hiddenFoldersWidth, isBreadCrumbOverflowing]); return (
    @@ -116,7 +137,9 @@ const BreadCrumb = ({ collapsibleNav, isNavigationPaneOpen, setNavigationPaneOpe switchPath(folder.path)} - ref={(el) => (foldersRef.current[index] = el)} + ref={(el) => { + foldersRef.current[index] = el; + }} > {index === 0 ? : } {folder.name} @@ -156,9 +179,4 @@ const BreadCrumb = ({ collapsibleNav, isNavigationPaneOpen, setNavigationPaneOpe BreadCrumb.displayName = "BreadCrumb"; -BreadCrumb.propTypes = { - isNavigationPaneOpen: PropTypes.bool.isRequired, - setNavigationPaneOpen: PropTypes.func.isRequired, -}; - -export default BreadCrumb; +export default BreadCrumb; \ No newline at end of file diff --git a/frontend/src/FileManager/FileList/FileList.jsx b/frontend/src/FileManager/FileList/FileList.tsx similarity index 73% rename from frontend/src/FileManager/FileList/FileList.jsx rename to frontend/src/FileManager/FileList/FileList.tsx index 2b59008..d19e69d 100644 --- a/frontend/src/FileManager/FileList/FileList.jsx +++ b/frontend/src/FileManager/FileList/FileList.tsx @@ -1,5 +1,4 @@ import { useRef } from "react"; -import FileItem from "./FileItem"; import { useFileNavigation } from "../../contexts/FileNavigationContext"; import { useLayout } from "../../contexts/LayoutContext"; import ContextMenu from "../../components/ContextMenu/ContextMenu"; @@ -8,6 +7,23 @@ import useFileList from "./useFileList"; import FilesHeader from "./FilesHeader"; import { useTranslation } from "../../contexts/TranslationProvider"; import "./FileList.scss"; +import { OnCreateFolder, OnFileOpen, OnRefresh, OnRename } from "../../types/FileManagerFunctions"; +import { Permissions } from "../../types/Permissions"; +import { TriggerAction } from "../../types/TriggerAction"; +import { SortDirection, SortKey } from "../../types/SortConfiguration"; +import { ExtendedFileItem } from "../../types/File"; +import FileListItem from "./FileListItem"; + +export interface FileListProps { + onCreateFolder?: OnCreateFolder; + onRename?: OnRename; + onFileOpen: OnFileOpen; + onRefresh?: OnRefresh; + enableFilePreview: boolean; + triggerAction: TriggerAction; + permissions: Permissions; + formatDate: (date: string) => string; +} const FileList = ({ onCreateFolder, @@ -18,9 +34,9 @@ const FileList = ({ triggerAction, permissions, formatDate, -}) => { +} : FileListProps) => { const { currentPathFiles, sortConfig, setSortConfig } = useFileNavigation(); - const filesViewRef = useRef(null); + const filesViewRef = useRef(null); const { activeLayout } = useLayout(); const t = useTranslation(); @@ -37,10 +53,10 @@ const FileList = ({ isSelectionCtx, } = useFileList(onRefresh, enableFilePreview, triggerAction, permissions, onFileOpen); - const contextMenuRef = useDetectOutsideClick(() => setVisible(false)); + const contextMenuRef = useDetectOutsideClick(() => setVisible(false)); - const handleSort = (key) => { - let direction = "asc"; + const handleSort = (key : SortKey) => { + let direction : SortDirection = "asc"; if (sortConfig.key === key && sortConfig.direction === "asc") { direction = "desc"; } @@ -61,10 +77,10 @@ const FileList = ({ {currentPathFiles?.length > 0 ? ( <> {currentPathFiles.map((file, index) => ( - >; + filesViewRef: RefObject; + selectedFileIndexes: number[]; + triggerAction: TriggerAction; + handleContextMenu: (e: MouseEvent, isSelection?: boolean) => void; + setLastSelectedFile: (file : FileItem) => void; + draggable?: boolean; + formatDate: (date : string) => string; +} const dragIconSize = 50; -const FileItem = ({ +const FileListItem = ({ index, file, onCreateFolder, @@ -26,12 +51,12 @@ const FileItem = ({ setLastSelectedFile, draggable, formatDate, -}) => { +} :FileListItemProps) => { const [fileSelected, setFileSelected] = useState(false); const [lastClickTime, setLastClickTime] = useState(0); const [checkboxClassName, setCheckboxClassName] = useState("hidden"); const [dropZoneClass, setDropZoneClass] = useState(""); - const [tooltipPosition, setTooltipPosition] = useState(null); + const [tooltipPosition, setTooltipPosition] = useState(null); const { activeLayout } = useLayout(); const iconSize = activeLayout === "grid" ? 48 : 20; @@ -39,7 +64,7 @@ const FileItem = ({ const { setCurrentPath, currentPathFiles, onFolderChange } = useFileNavigation(); const { setSelectedFiles } = useSelection(); const { clipBoard, handleCutCopy, setClipBoard, handlePasting } = useClipBoard(); - const dragIconRef = useRef(null); + const dragIconRef = useRef(null); const dragIcons = useFileIcons(dragIconSize); const isFileMoving = @@ -49,7 +74,7 @@ const FileItem = ({ const handleFileAccess = () => { onFileOpen(file); if (file.isDirectory) { - setCurrentPath(file.path); + setCurrentPath?.(file.path); onFolderChange?.(file.path); setSelectedFiles([]); } else { @@ -57,7 +82,7 @@ const FileItem = ({ } }; - const handleFileRangeSelection = (shiftKey, ctrlKey) => { + const handleFileRangeSelection = (shiftKey : boolean, ctrlKey : boolean) => { if (selectedFileIndexes.length > 0 && shiftKey) { let reverseSelection = false; let startRange = selectedFileIndexes[0]; @@ -87,7 +112,7 @@ const FileItem = ({ } }; - const handleFileSelection = (e) => { + const handleFileSelection = (e : MouseEvent) => { e.stopPropagation(); if (file.isEditing) return; @@ -101,7 +126,7 @@ const FileItem = ({ setLastClickTime(currentTime); }; - const handleOnKeyDown = (e) => { + const handleOnKeyDown = (e : KeyboardEvent) => { if (e.key === "Enter") { e.stopPropagation(); setSelectedFiles([file]); @@ -109,7 +134,7 @@ const FileItem = ({ } }; - const handleItemContextMenu = (e) => { + const handleItemContextMenu = (e : MouseEvent) => { e.stopPropagation(); e.preventDefault(); @@ -132,7 +157,7 @@ const FileItem = ({ !fileSelected && setCheckboxClassName("hidden"); }; - const handleCheckboxChange = (e) => { + const handleCheckboxChange = (e : ChangeEvent) => { if (e.target.checked) { setSelectedFiles((prev) => [...prev, file]); } else { @@ -143,15 +168,15 @@ const FileItem = ({ }; // - const handleDragStart = (e) => { - e.dataTransfer.setDragImage(dragIconRef.current, 30, 50); + const handleDragStart = (e : DragEvent) => { + e.dataTransfer.setDragImage(dragIconRef.current!, 30, 50); e.dataTransfer.effectAllowed = "copy"; handleCutCopy(true); }; const handleDragEnd = () => setClipBoard(null); - const handleDragEnterOver = (e) => { + const handleDragEnterOver = (e : DragEvent) => { e.preventDefault(); if (fileSelected || !file.isDirectory) { e.dataTransfer.dropEffect = "none"; @@ -162,15 +187,15 @@ const FileItem = ({ } }; - const handleDragLeave = (e) => { + const handleDragLeave = (e : DragEvent) => { // To stay in dragging state for the child elements of the target drop-zone - if (!e.currentTarget.contains(e.relatedTarget)) { + if (!e.currentTarget.contains(e.relatedTarget as Node)) { setDropZoneClass((prev) => (prev ? "" : prev)); setTooltipPosition(null); } }; - const handleDrop = (e) => { + const handleDrop = (e : DragEvent) => { e.preventDefault(); if (fileSelected || !file.isDirectory) return; @@ -219,7 +244,7 @@ const FileItem = ({ ) : ( <> - {fileIcons[file.name?.split(".").pop()?.toLowerCase()] ?? } + {fileIcons[file.name?.split(".").pop()?.toLowerCase() as keyof typeof fileIcons] ?? } )} @@ -248,8 +273,8 @@ const FileItem = ({ {activeLayout === "list" && ( <> -
    {formatDate(file.updatedAt)}
    -
    {file?.size > 0 ? getDataSize(file?.size) : ""}
    +
    {formatDate(file.updatedAt ?? "")}
    +
    {file?.size && file.size > 0 ? getDataSize(file.size) : ""}
    )} @@ -271,7 +296,7 @@ const FileItem = ({ ) : ( <> - {dragIcons[file.name?.split(".").pop()?.toLowerCase()] ?? ( + {dragIcons[file.name?.split(".").pop()?.toLowerCase() as keyof typeof dragIcons] ?? ( )} @@ -282,4 +307,4 @@ const FileItem = ({ ); }; -export default FileItem; +export default FileListItem; diff --git a/frontend/src/FileManager/FileList/FilesHeader.jsx b/frontend/src/FileManager/FileList/FilesHeader.tsx similarity index 83% rename from frontend/src/FileManager/FileList/FilesHeader.jsx rename to frontend/src/FileManager/FileList/FilesHeader.tsx index 3a419c7..23da443 100644 --- a/frontend/src/FileManager/FileList/FilesHeader.jsx +++ b/frontend/src/FileManager/FileList/FilesHeader.tsx @@ -1,10 +1,16 @@ -import { useMemo, useState } from "react"; +import { ChangeEvent, useMemo, useState } from "react"; import Checkbox from "../../components/Checkbox/Checkbox"; import { useFileNavigation } from "../../contexts/FileNavigationContext"; import { useSelection } from "../../contexts/SelectionContext"; import { useTranslation } from "../../contexts/TranslationProvider"; +import { SortConfiguration, SortKey } from "../../types/SortConfiguration"; -const FilesHeader = ({ unselectFiles, onSort, sortConfig }) => { +export interface FilesHeaderProps { + unselectFiles: () => void; + onSort: (key: SortKey) => void; + sortConfig: SortConfiguration; +} +const FilesHeader = ({ unselectFiles, onSort, sortConfig } : FilesHeaderProps) => { const t = useTranslation(); const [showSelectAll, setShowSelectAll] = useState(false); @@ -16,7 +22,7 @@ const FilesHeader = ({ unselectFiles, onSort, sortConfig }) => { return currentPathFiles.length > 0 && selectedFiles.length === currentPathFiles.length; }, [selectedFiles, currentPathFiles]); - const handleSelectAll = (e) => { + const handleSelectAll = (e : ChangeEvent) => { if (e.target.checked) { setSelectedFiles(currentPathFiles); setShowSelectAll(true); @@ -25,7 +31,7 @@ const FilesHeader = ({ unselectFiles, onSort, sortConfig }) => { } }; - const handleSort = (key) => { + const handleSort = (key : SortKey) => { if (onSort) { onSort(key); } diff --git a/frontend/src/FileManager/FileList/useFileList.jsx b/frontend/src/FileManager/FileList/useFileList.tsx similarity index 84% rename from frontend/src/FileManager/FileList/useFileList.jsx rename to frontend/src/FileManager/FileList/useFileList.tsx index dcb14f0..d774073 100644 --- a/frontend/src/FileManager/FileList/useFileList.jsx +++ b/frontend/src/FileManager/FileList/useFileList.tsx @@ -5,33 +5,41 @@ import { FiRefreshCw } from "react-icons/fi"; import { MdOutlineDelete, MdOutlineFileDownload, MdOutlineFileUpload } from "react-icons/md"; import { PiFolderOpen } from "react-icons/pi"; import { useClipBoard } from "../../contexts/ClipboardContext"; -import { useEffect, useState } from "react"; +import { MouseEvent, useEffect, useState } from "react"; import { useSelection } from "../../contexts/SelectionContext"; import { useLayout } from "../../contexts/LayoutContext"; import { useFileNavigation } from "../../contexts/FileNavigationContext"; import { duplicateNameHandler } from "../../utils/duplicateNameHandler"; -import { validateApiCallback } from "../../utils/validateApiCallback"; import { useTranslation } from "../../contexts/TranslationProvider"; +import { OnFileOpen, OnRefresh } from "../../types/FileManagerFunctions"; +import { TriggerAction } from "../../types/TriggerAction"; +import { Permissions } from "../../types/Permissions"; +import { ExtendedFileItem } from "../../types/File"; +import { MenuItem } from "../../components/ContextMenu/ContextMenu"; -const useFileList = (onRefresh, enableFilePreview, triggerAction, permissions, onFileOpen) => { - const [selectedFileIndexes, setSelectedFileIndexes] = useState([]); +export interface ClickPosition { + clickX: number; + clickY: number; +} + +const useFileList = (onRefresh: OnRefresh | undefined, enableFilePreview : boolean, triggerAction : TriggerAction, permissions : Permissions, onFileOpen : OnFileOpen) => { + const [selectedFileIndexes, setSelectedFileIndexes] = useState([]); const [visible, setVisible] = useState(false); const [isSelectionCtx, setIsSelectionCtx] = useState(false); const [clickPosition, setClickPosition] = useState({ clickX: 0, clickY: 0 }); - const [lastSelectedFile, setLastSelectedFile] = useState(null); + const [lastSelectedFile, setLastSelectedFile] = useState(null); const { clipBoard, setClipBoard, handleCutCopy, handlePasting } = useClipBoard(); const { selectedFiles, setSelectedFiles, handleDownload } = useSelection(); - const { currentPath, setCurrentPath, currentPathFiles, setCurrentPathFiles, onFolderChange } = - useFileNavigation(); + const { currentPath, setCurrentPath, currentPathFiles, setCurrentPathFiles, onFolderChange } = useFileNavigation(); const { activeLayout, setActiveLayout } = useLayout(); const t = useTranslation(); // Context Menu const handleFileOpen = () => { - onFileOpen(lastSelectedFile); - if (lastSelectedFile.isDirectory) { - setCurrentPath(lastSelectedFile.path); + onFileOpen(lastSelectedFile!); + if (lastSelectedFile?.isDirectory) { + setCurrentPath?.(lastSelectedFile.path); onFolderChange?.(lastSelectedFile.path); setSelectedFileIndexes([]); setSelectedFiles([]); @@ -41,13 +49,13 @@ const useFileList = (onRefresh, enableFilePreview, triggerAction, permissions, o setVisible(false); }; - const handleMoveOrCopyItems = (isMoving) => { + const handleMoveOrCopyItems = (isMoving : boolean) => { handleCutCopy(isMoving); setVisible(false); }; const handleFilePasting = () => { - handlePasting(lastSelectedFile); + handlePasting(lastSelectedFile!); setVisible(false); }; @@ -68,7 +76,7 @@ const useFileList = (onRefresh, enableFilePreview, triggerAction, permissions, o const handleRefresh = () => { setVisible(false); - validateApiCallback(onRefresh, "onRefresh"); + onRefresh?.(); setClipBoard(null); }; @@ -87,7 +95,7 @@ const useFileList = (onRefresh, enableFilePreview, triggerAction, permissions, o setVisible(false); }; - const emptySelecCtxItems = [ + const emptySelecCtxItems : MenuItem[] = [ { title: t("view"), icon: activeLayout === "grid" ? : , @@ -140,7 +148,7 @@ const useFileList = (onRefresh, enableFilePreview, triggerAction, permissions, o }, ]; - const selecCtxItems = [ + const selecCtxItems : MenuItem[] = [ { title: t("open"), icon: lastSelectedFile?.isDirectory ? : , @@ -173,8 +181,7 @@ const useFileList = (onRefresh, enableFilePreview, triggerAction, permissions, o title: t("rename"), icon: , onClick: handleRenaming, - hidden: selectedFiles.length > 1, - hidden: !permissions.rename, + hidden: selectedFiles.length > 1 || !permissions.rename, }, { title: t("download"), @@ -198,7 +205,7 @@ const useFileList = (onRefresh, enableFilePreview, triggerAction, permissions, o { name: duplicateNameHandler("New Folder", true, prev), isDirectory: true, - path: currentPath, + path: currentPath!, isEditing: true, key: new Date().valueOf(), }, @@ -208,7 +215,7 @@ const useFileList = (onRefresh, enableFilePreview, triggerAction, permissions, o const handleItemRenaming = () => { setCurrentPathFiles((prev) => { - const lastFileIndex = selectedFileIndexes.at(-1); + const lastFileIndex = selectedFileIndexes.at(-1)!; if (!prev[lastFileIndex]) { triggerAction.close(); @@ -236,7 +243,7 @@ const useFileList = (onRefresh, enableFilePreview, triggerAction, permissions, o setSelectedFiles((prev) => (prev.length > 0 ? [] : prev)); }; - const handleContextMenu = (e, isSelection = false) => { + const handleContextMenu = (e : MouseEvent, isSelection = false) => { e.preventDefault(); setClickPosition({ clickX: e.clientX, clickY: e.clientY }); setIsSelectionCtx(isSelection); diff --git a/frontend/src/FileManager/FileManager.jsx b/frontend/src/FileManager/FileManager.tsx similarity index 62% rename from frontend/src/FileManager/FileManager.jsx rename to frontend/src/FileManager/FileManager.tsx index e6cff0a..2cf6319 100644 --- a/frontend/src/FileManager/FileManager.jsx +++ b/frontend/src/FileManager/FileManager.tsx @@ -11,53 +11,116 @@ import { ClipBoardProvider } from "../contexts/ClipboardContext"; import { LayoutProvider } from "../contexts/LayoutContext"; import { useTriggerAction } from "../hooks/useTriggerAction"; import { useColumnResize } from "../hooks/useColumnResize"; -import PropTypes from "prop-types"; -import { dateStringValidator, urlValidator } from "../validators/propValidators"; import { TranslationProvider } from "../contexts/TranslationProvider"; import { useMemo, useState } from "react"; import { defaultPermissions } from "../constants"; import { formatDate as defaultFormatDate } from "../utils/formatDate"; import "./FileManager.scss"; +import { ExtendedFileItem, FileItem } from "../types/File"; +import { FileUploadConfiguration } from "../types/FileUploadConfiguration"; +import { Layout } from "../types/Layout"; +import { Language } from "../types/Language"; +import { + OnCreateFolder, + OnFileUploading, + OnFileUploaded, + OnCut, + OnCopy, + OnPaste, + OnRename, + OnDownload, + OnDelete, + OnFolderChange, + OnError, + OnFileOpen, + OnLayoutChange, + OnSelect, + OnSelectionChange, + OnSortChange, + FilePreviewComponent, + OnRefresh + } from "../types/FileManagerFunctions"; + +export interface FileManagerProps { + files?: FileItem[]; // x + fileUploadConfig?: FileUploadConfiguration; // x + isLoading?: boolean; // x + onCreateFolder?: OnCreateFolder; // x + onFileUploading?: OnFileUploading; // x + onFileUploaded?: OnFileUploaded; // x + onCut?: OnCut; // x + onCopy?: OnCopy; // x + onPaste?: OnPaste; // x + onRename?: OnRename; // x + onDownload?: OnDownload; // x + onDelete?: OnDelete // x + onLayoutChange?: OnLayoutChange // x + onRefresh?: OnRefresh; // x + onFileOpen?: OnFileOpen; // x + onFolderChange?: OnFolderChange; // x + onSelect?: OnSelect; // x (deprecated) + onSelectionChange?: OnSelectionChange; // x + onError?: OnError; // x + onSortChange?: OnSortChange; // x + layout?: Layout; // x + enableFilePreview?: boolean; // x + maxFileSize?: number; // x + filePreviewPath?: string; // x + acceptedFileTypes?: string; // x + height?: string | number; // x + width?: string | number; // x + initialPath?: string; // x + filePreviewComponent?: FilePreviewComponent; // x + primaryColor?: string; // x + fontFamily?: string; // x + language?: Language; // x + permissions?: Permissions; // x + collapsibleNav?: boolean; // x + defaultNavExpanded?: boolean; // x + className?: string; // x + style?: React.CSSProperties; // x + formatDate?: (date: string) => string; // x (Not in official docs but in your code) +} const FileManager = ({ - files, - fileUploadConfig, - isLoading, - onCreateFolder, - onFileUploading = () => {}, - onFileUploaded = () => {}, - onCut, - onCopy, - onPaste, - onRename, - onDownload, - onDelete = () => null, - onLayoutChange = () => {}, - onRefresh, - onFileOpen = () => {}, - onFolderChange = () => {}, - onSelect, - onSelectionChange, - onError = () => {}, - layout = "grid", - enableFilePreview = true, - maxFileSize, - filePreviewPath, - acceptedFileTypes, - height = "600px", - width = "100%", - initialPath = "", - filePreviewComponent, - primaryColor = "#6155b4", - fontFamily = "Nunito Sans, sans-serif", - language = "en-US", - permissions: userPermissions = {}, - collapsibleNav = false, - defaultNavExpanded = true, - className = "", - style = {}, - formatDate = defaultFormatDate, -}) => { + files = [], // x + fileUploadConfig, // x + isLoading, // x + onCreateFolder, // x + onFileUploading, + onFileUploaded, + onCut, // x + onCopy, // x + onPaste, // x + onRename, // x + onDownload, // x + onDelete = () => null, // x + onLayoutChange = () => {}, // x + onRefresh, // x + onFileOpen = () => {}, // x + onFolderChange = () => {}, // x + onSelect, // x + onSelectionChange, // x + onError = () => {}, // x + layout = "grid", // x + enableFilePreview = true, // x + maxFileSize, // x + filePreviewPath, // x + acceptedFileTypes, // x + height = "600px", // x + width = "100%", // x + initialPath = "", // x + filePreviewComponent, // x + primaryColor = "#6155b4", // x + fontFamily = "Nunito Sans, sans-serif", // x + language = "en-US", // x + permissions: userPermissions, // x + collapsibleNav = false, // x + defaultNavExpanded = true, // x + className = "", // x + style = {}, // x + formatDate = defaultFormatDate, // x +}: FileManagerProps) => { const [isNavigationPaneOpen, setNavigationPaneOpen] = useState(defaultNavExpanded); const triggerAction = useTriggerAction(); const { containerRef, colSizes, isDragging, handleMouseMove, handleMouseUp, handleMouseDown } = @@ -82,9 +145,9 @@ const FileManager = ({ > - + - { +export interface FolderTreeProps { + folder: ExtendedFileItem; + onFileOpen: OnFileOpen; +} + +const FolderTree = ({ folder, onFileOpen } : FolderTreeProps) => { const [isOpen, setIsOpen] = useState(false); const [isActive, setIsActive] = useState(false); const { currentPath, setCurrentPath, onFolderChange } = useFileNavigation(); @@ -12,11 +19,11 @@ const FolderTree = ({ folder, onFileOpen }) => { const handleFolderSwitch = () => { setIsActive(true); onFileOpen(folder); - setCurrentPath(folder.path); + setCurrentPath?.(folder.path); onFolderChange?.(folder.path); }; - const handleCollapseChange = (e) => { + const handleCollapseChange = (e : MouseEvent) => { e.stopPropagation(); setIsOpen((prev) => !prev); }; @@ -27,7 +34,7 @@ const FolderTree = ({ folder, onFileOpen }) => { // Auto expand parent folder if its child is accessed via file navigation // Explanation: Checks if the current folder's parent path matches with any folder path i.e. Folder's parent // then expand that parent. - const currentPathArray = currentPath.split("/"); + const currentPathArray = currentPath!.split("/"); currentPathArray.pop(); //splits with '/' and pops to remove last element to get current folder's parent path const currentFolderParentPath = currentPathArray.join("/"); if (currentFolderParentPath === folder.path) { @@ -36,7 +43,7 @@ const FolderTree = ({ folder, onFileOpen }) => { // }, [currentPath]); - if (folder.subDirectories.length > 0) { + if (folder.subDirectories && folder.subDirectories.length > 0) { return ( <>
    { - const [foldersTree, setFoldersTree] = useState([]); +export interface NavigationPaneProps { + onFileOpen: OnFileOpen; +} + +const NavigationPane = ({ onFileOpen } : NavigationPaneProps) => { + const [foldersTree, setFoldersTree] = useState([]); const { files } = useFiles(); const t = useTranslation(); - const createChildRecursive = (path, foldersStruct) => { + const createChildRecursive = ( + path: string, + foldersStruct: Partial> + ): ExtendedFileItem[] => { if (!foldersStruct[path]) return []; // No children for this path (folder) - return foldersStruct[path]?.map((folder) => { + return foldersStruct[path]!.map((folder) => { return { ...folder, subDirectories: createChildRecursive(folder.path, foldersStruct), @@ -28,7 +37,7 @@ const NavigationPane = ({ onFileOpen }) => { const foldersStruct = Object.groupBy(folders, ({ path }) => getParentPath(path)); setFoldersTree(() => { const rootPath = ""; - return createChildRecursive(rootPath, foldersStruct); + return createChildRecursive(rootPath, foldersStruct as Record); }); } }, [files]); diff --git a/frontend/src/FileManager/Toolbar/LayoutToggler.jsx b/frontend/src/FileManager/Toolbar/LayoutToggler.tsx similarity index 71% rename from frontend/src/FileManager/Toolbar/LayoutToggler.jsx rename to frontend/src/FileManager/Toolbar/LayoutToggler.tsx index aa5bb83..fa6bb40 100644 --- a/frontend/src/FileManager/Toolbar/LayoutToggler.jsx +++ b/frontend/src/FileManager/Toolbar/LayoutToggler.tsx @@ -3,9 +3,16 @@ import { FaCheck, FaListUl } from "react-icons/fa6"; import { useDetectOutsideClick } from "../../hooks/useDetectOutsideClick"; import { useLayout } from "../../contexts/LayoutContext"; import { useTranslation } from "../../contexts/TranslationProvider"; +import { OnLayoutChange } from "../../types/FileManagerFunctions"; +import { Layout } from "../../types/Layout"; -const LayoutToggler = ({ setShowToggleViewMenu, onLayoutChange }) => { - const toggleViewRef = useDetectOutsideClick(() => { +export interface LayoutTogglerProps { + setShowToggleViewMenu: (value : boolean) => void; + onLayoutChange: OnLayoutChange; +} + +const LayoutToggler = ({ setShowToggleViewMenu, onLayoutChange } : LayoutTogglerProps) => { + const toggleViewRef = useDetectOutsideClick(() => { setShowToggleViewMenu(false); }); const { activeLayout, setActiveLayout } = useLayout(); @@ -24,7 +31,7 @@ const LayoutToggler = ({ setShowToggleViewMenu, onLayoutChange }) => { }, ]; - const handleSelection = (key) => { + const handleSelection = (key : Layout) => { setActiveLayout(key); onLayoutChange(key); setShowToggleViewMenu(false); @@ -37,8 +44,8 @@ const LayoutToggler = ({ setShowToggleViewMenu, onLayoutChange }) => {
  • handleSelection(option.key)} - onKeyDown={() => handleSelection(option.key)} + onClick={() => handleSelection(option.key as Layout)} + onKeyDown={() => handleSelection(option.key as Layout)} > {option.key === activeLayout && } {option.icon} diff --git a/frontend/src/FileManager/Toolbar/Toolbar.jsx b/frontend/src/FileManager/Toolbar/Toolbar.tsx similarity index 91% rename from frontend/src/FileManager/Toolbar/Toolbar.jsx rename to frontend/src/FileManager/Toolbar/Toolbar.tsx index 4147578..9d57333 100644 --- a/frontend/src/FileManager/Toolbar/Toolbar.jsx +++ b/frontend/src/FileManager/Toolbar/Toolbar.tsx @@ -14,11 +14,20 @@ import { useFileNavigation } from "../../contexts/FileNavigationContext"; import { useSelection } from "../../contexts/SelectionContext"; import { useClipBoard } from "../../contexts/ClipboardContext"; import { useLayout } from "../../contexts/LayoutContext"; -import { validateApiCallback } from "../../utils/validateApiCallback"; import { useTranslation } from "../../contexts/TranslationProvider"; import "./Toolbar.scss"; +import { OnLayoutChange, OnRefresh } from "../../types/FileManagerFunctions"; +import { TriggerAction } from "../../types/TriggerAction"; +import { Permissions } from "../../types/Permissions"; -const Toolbar = ({ onLayoutChange, onRefresh, triggerAction, permissions }) => { +export interface ToolbarProps { + onLayoutChange: OnLayoutChange; + onRefresh?: OnRefresh; + triggerAction: TriggerAction; + permissions: Permissions; +} + +const Toolbar = ({ onLayoutChange, onRefresh, triggerAction, permissions } : ToolbarProps) => { const [showToggleViewMenu, setShowToggleViewMenu] = useState(false); const { currentFolder } = useFileNavigation(); const { selectedFiles, setSelectedFiles, handleDownload } = useSelection(); @@ -58,14 +67,14 @@ const Toolbar = ({ onLayoutChange, onRefresh, triggerAction, permissions }) => { icon: , title: t("refresh"), onClick: () => { - validateApiCallback(onRefresh, "onRefresh"); + onRefresh?.(); setClipBoard(null); }, }, ]; function handleFilePasting() { - handlePasting(currentFolder); + handlePasting(currentFolder!); } const handleDownloadItems = () => { @@ -91,7 +100,7 @@ const Toolbar = ({ onLayoutChange, onRefresh, triggerAction, permissions }) => { {t("copy")} )} - {clipBoard?.files?.length > 0 && ( + {clipBoard?.files?.length && clipBoard?.files?.length > 0 && ( - ); -}; - -export default Button; diff --git a/frontend/src/components/Button/Button.tsx b/frontend/src/components/Button/Button.tsx new file mode 100644 index 0000000..e3bb3f3 --- /dev/null +++ b/frontend/src/components/Button/Button.tsx @@ -0,0 +1,25 @@ +import { KeyboardEvent, MouseEvent, ReactNode } from "react"; +import "./Button.scss"; + +export interface ButtonProps { + onClick?: (event : MouseEvent) => void; + onKeyDown?: (event : KeyboardEvent) => void; + type?: "primary" | "secondary" | "danger"; + padding?: string; + children: ReactNode; +} + +const Button = ({ onClick, onKeyDown, type = "primary", padding = "0.4rem 0.8rem", children } : ButtonProps) => { + return ( + + ); +}; + +export default Button; diff --git a/frontend/src/components/Checkbox/Checkbox.jsx b/frontend/src/components/Checkbox/Checkbox.jsx deleted file mode 100644 index 7e804b6..0000000 --- a/frontend/src/components/Checkbox/Checkbox.jsx +++ /dev/null @@ -1,19 +0,0 @@ -import "./Checkbox.scss"; - -const Checkbox = ({ name, id, checked, onClick, onChange, className = "", title, disabled = false }) => { - return ( - - ); -}; - -export default Checkbox; diff --git a/frontend/src/components/Checkbox/Checkbox.tsx b/frontend/src/components/Checkbox/Checkbox.tsx new file mode 100644 index 0000000..64506a1 --- /dev/null +++ b/frontend/src/components/Checkbox/Checkbox.tsx @@ -0,0 +1,30 @@ +import { ChangeEvent, MouseEvent } from "react"; +import "./Checkbox.scss"; + +export interface CheckboxProps { + id?: string; + name?: string + checked?: boolean; + onClick?: (event : MouseEvent) => void; + onChange?: (event : ChangeEvent) => void; + className?: string; + title?: string; + disabled?: boolean; +} +const Checkbox = ({ name, id, checked, onClick, onChange, className = "", title, disabled = false } : CheckboxProps) => { + return ( + + ); +}; + +export default Checkbox; diff --git a/frontend/src/components/Collapse/Collapse.jsx b/frontend/src/components/Collapse/Collapse.tsx similarity index 66% rename from frontend/src/components/Collapse/Collapse.jsx rename to frontend/src/components/Collapse/Collapse.tsx index db4eab1..770fe00 100644 --- a/frontend/src/components/Collapse/Collapse.jsx +++ b/frontend/src/components/Collapse/Collapse.tsx @@ -1,7 +1,12 @@ -import { useState, useEffect } from "react"; +import { useState, useEffect, ReactNode } from "react"; import { useCollapse } from "react-collapsed"; -const Collapse = ({ open, children }) => { +export interface CollapseProps { + open: boolean; + children: ReactNode +} + +const Collapse = ({ open, children } : CollapseProps) => { const [isExpanded, setExpanded] = useState(open); const { getCollapseProps } = useCollapse({ isExpanded, diff --git a/frontend/src/components/ContextMenu/ContextMenu.jsx b/frontend/src/components/ContextMenu/ContextMenu.tsx similarity index 66% rename from frontend/src/components/ContextMenu/ContextMenu.jsx rename to frontend/src/components/ContextMenu/ContextMenu.tsx index 36b914f..ab2c909 100644 --- a/frontend/src/components/ContextMenu/ContextMenu.jsx +++ b/frontend/src/components/ContextMenu/ContextMenu.tsx @@ -1,25 +1,46 @@ -import { useEffect, useRef, useState } from "react"; +import { MouseEvent, ReactNode, RefObject, useEffect, useRef, useState } from "react"; import { FaChevronRight } from "react-icons/fa6"; -import SubMenu from "./SubMenu"; +import SubMenu, { SubMenuItem, SubMenuPosition } from "./SubMenu"; import "./ContextMenu.scss"; +import { ClickPosition } from "../../FileManager/FileList/useFileList"; -const ContextMenu = ({ filesViewRef, contextMenuRef, menuItems, visible, clickPosition }) => { - const [left, setLeft] = useState(0); - const [top, setTop] = useState(0); - const [activeSubMenuIndex, setActiveSubMenuIndex] = useState(null); - const [subMenuPosition, setSubMenuPosition] = useState("right"); +export interface MenuItem { + title?: string; + selected?: boolean; + icon?: ReactNode; + className?: string; + hidden?: boolean; + children?: SubMenuItem[]; + divider?: boolean; + onClick?: (event : MouseEvent) => void; +} - const subMenuRef = useRef(null); +export interface ContextMenuProps { + filesViewRef: RefObject; + contextMenuRef: RefObject; + menuItems: MenuItem[]; + visible: boolean; + setVisible?: (visible : boolean) => void; + clickPosition: ClickPosition +} + +const ContextMenu = ({ filesViewRef, contextMenuRef, menuItems, visible, clickPosition } : ContextMenuProps) => { + const [left, setLeft] = useState("0"); + const [top, setTop] = useState("0"); + const [activeSubMenuIndex, setActiveSubMenuIndex] = useState(null); + const [subMenuPosition, setSubMenuPosition] = useState("right"); + + const subMenuRef = useRef(null); const contextMenuPosition = () => { const { clickX, clickY } = clickPosition; const container = filesViewRef.current; - const containerRect = container.getBoundingClientRect(); - const scrollBarWidth = container.offsetWidth - container.clientWidth; + const containerRect = container!.getBoundingClientRect(); + const scrollBarWidth = container!.offsetWidth - container!.clientWidth; // Context menu size - const contextMenuContainer = contextMenuRef.current.getBoundingClientRect(); + const contextMenuContainer = contextMenuRef.current!.getBoundingClientRect(); const menuWidth = contextMenuContainer.width; const menuHeight = contextMenuContainer.height; @@ -42,18 +63,18 @@ const ContextMenu = ({ filesViewRef, contextMenuRef, menuItems, visible, clickPo } if (top) { - setTop(`${topToCursor + container.scrollTop}px`); + setTop(`${topToCursor + container!.scrollTop}px`); } else if (bottom) { - setTop(`${topToCursor + container.scrollTop - menuHeight}px`); + setTop(`${topToCursor + container!.scrollTop - menuHeight}px`); } }; - const handleContextMenu = (e) => { + const handleContextMenu = (e : MouseEvent) => { e.preventDefault(); e.stopPropagation(); }; - const handleMouseOver = (index) => { + const handleMouseOver = (index : number) => { setActiveSubMenuIndex(index); }; @@ -61,8 +82,8 @@ const ContextMenu = ({ filesViewRef, contextMenuRef, menuItems, visible, clickPo if (visible && contextMenuRef.current) { contextMenuPosition(); } else { - setTop(0); - setLeft(0); + setTop("0"); + setLeft("0"); setActiveSubMenuIndex(null); } }, [visible]); @@ -101,7 +122,7 @@ const ContextMenu = ({ filesViewRef, contextMenuRef, menuItems, visible, clickPo {activeSubMenu && ( )} @@ -120,6 +141,8 @@ const ContextMenu = ({ filesViewRef, contextMenuRef, menuItems, visible, clickPo
  • ); } + + return null; }; export default ContextMenu; diff --git a/frontend/src/components/ContextMenu/SubMenu.jsx b/frontend/src/components/ContextMenu/SubMenu.jsx deleted file mode 100644 index 6e9dc08..0000000 --- a/frontend/src/components/ContextMenu/SubMenu.jsx +++ /dev/null @@ -1,17 +0,0 @@ -import { FaCheck } from "react-icons/fa6"; - -const SubMenu = ({ subMenuRef, list, position = "right" }) => { - return ( -
      - {list?.map((item) => ( -
    • - {item.selected && } - {item.icon} - {item.title} -
    • - ))} -
    - ); -}; - -export default SubMenu; diff --git a/frontend/src/components/ContextMenu/SubMenu.tsx b/frontend/src/components/ContextMenu/SubMenu.tsx new file mode 100644 index 0000000..ea77278 --- /dev/null +++ b/frontend/src/components/ContextMenu/SubMenu.tsx @@ -0,0 +1,33 @@ +import { MouseEvent, ReactNode, RefObject } from "react"; +import { FaCheck } from "react-icons/fa6"; + +export type SubMenuPosition = "left" | "right"; + +export interface SubMenuItem { + title: string; + selected: boolean; + icon: ReactNode; + onClick: (event : MouseEvent) => void; +} + +export interface SubMenuProps { + subMenuRef: RefObject; + list: SubMenuItem[]; + position: SubMenuPosition +} + +const SubMenu = ({ subMenuRef, list, position = "right" } : SubMenuProps) => { + return ( +
      + {list?.map((item) => ( +
    • + {item.selected && } + {item.icon} + {item.title} +
    • + ))} +
    + ); +}; + +export default SubMenu; diff --git a/frontend/src/components/ErrorTooltip/ErrorTooltip.jsx b/frontend/src/components/ErrorTooltip/ErrorTooltip.jsx deleted file mode 100644 index 7c41408..0000000 --- a/frontend/src/components/ErrorTooltip/ErrorTooltip.jsx +++ /dev/null @@ -1,7 +0,0 @@ -import "./ErrorTooltip.scss"; - -const ErrorTooltip = ({ message, xPlacement, yPlacement }) => { - return

    {message}

    ; -}; - -export default ErrorTooltip; diff --git a/frontend/src/components/ErrorTooltip/ErrorTooltip.tsx b/frontend/src/components/ErrorTooltip/ErrorTooltip.tsx new file mode 100644 index 0000000..835ea6f --- /dev/null +++ b/frontend/src/components/ErrorTooltip/ErrorTooltip.tsx @@ -0,0 +1,16 @@ +import "./ErrorTooltip.scss"; + +export type ErrorTooltipXPlacement = "left" | "right"; +export type ErrorTooltipYPlacement = "top" | "bottom"; + +export interface ErrorTooltipProps { + message?: string; + xPlacement: ErrorTooltipXPlacement; + yPlacement: ErrorTooltipYPlacement; +} + +const ErrorTooltip = ({ message, xPlacement, yPlacement } : ErrorTooltipProps) => { + return

    {message}

    ; +}; + +export default ErrorTooltip; diff --git a/frontend/src/components/Loader/Loader.jsx b/frontend/src/components/Loader/Loader.tsx similarity index 64% rename from frontend/src/components/Loader/Loader.jsx rename to frontend/src/components/Loader/Loader.tsx index 1afcbf2..7bca4d4 100644 --- a/frontend/src/components/Loader/Loader.jsx +++ b/frontend/src/components/Loader/Loader.tsx @@ -1,7 +1,12 @@ import { ImSpinner2 } from "react-icons/im"; import "./Loader.scss"; -const Loader = ({ loading = false, className }) => { +interface LoaderProps { + loading?: boolean; + className?: string; +} + +const Loader = ({ loading = false, className } : LoaderProps) => { if (!loading) return null; return ( diff --git a/frontend/src/components/Modal/Modal.jsx b/frontend/src/components/Modal/Modal.tsx similarity index 67% rename from frontend/src/components/Modal/Modal.jsx rename to frontend/src/components/Modal/Modal.tsx index e612f5d..3fcb3de 100644 --- a/frontend/src/components/Modal/Modal.jsx +++ b/frontend/src/components/Modal/Modal.tsx @@ -1,21 +1,29 @@ import { MdClose } from "react-icons/md"; -import { useEffect, useRef } from "react"; +import { KeyboardEvent, ReactNode, useEffect, useRef } from "react"; import { useTranslation } from "../../contexts/TranslationProvider"; import "./Modal.scss"; +export interface ModalProps { + children: ReactNode; + show: boolean; + setShow: (show : boolean) => void; + heading: string; + dialogWidth?: string; + closeButton?: boolean; +} + const Modal = ({ children, show, setShow, heading, dialogWidth = "25%", - contentClassName = "", closeButton = true, -}) => { - const modalRef = useRef(null); +} : ModalProps) => { + const modalRef = useRef(null); const t = useTranslation(); - const handleKeyDown = (e) => { + const handleKeyDown = (e : KeyboardEvent) => { if (e.key === "Escape") { setShow(false); } @@ -23,9 +31,9 @@ const Modal = ({ useEffect(() => { if (show) { - modalRef.current.showModal(); + modalRef.current?.showModal(); } else { - modalRef.current.close(); + modalRef.current?.close(); } }, [show]); diff --git a/frontend/src/components/NameInput/NameInput.jsx b/frontend/src/components/NameInput/NameInput.jsx deleted file mode 100644 index c529359..0000000 --- a/frontend/src/components/NameInput/NameInput.jsx +++ /dev/null @@ -1,19 +0,0 @@ -import "./NameInput.scss"; - -const NameInput = ({ nameInputRef, id, maxLength, value, onChange, onKeyDown, onClick, rows }) => { - return ( -