diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index f9ec204a6..1438432a5 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -48,6 +48,7 @@
"@vitest/coverage-v8": "^1.0.0",
"jsdom": "^23.0.0",
"license-checker": "^25.0.1",
+ "madge": "^8.0.0",
"postcss": "^8.5.3",
"postcss-cli": "^11.0.1",
"postcss-preset-mantine": "^1.17.0",
@@ -331,12 +332,12 @@
}
},
"node_modules/@babel/parser": {
- "version": "7.27.3",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.3.tgz",
- "integrity": "sha512-xyYxRj6+tLNDTWi0KCBcZ9V7yg3/lwL9DWh9Uwh/RIVlIfFidggcgxKX3GCXwCiswwcGRawBKbEg2LG/Y8eJhw==",
+ "version": "7.28.3",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz",
+ "integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==",
"license": "MIT",
"dependencies": {
- "@babel/types": "^7.27.3"
+ "@babel/types": "^7.28.2"
},
"bin": {
"parser": "bin/babel-parser.js"
@@ -419,9 +420,9 @@
}
},
"node_modules/@babel/types": {
- "version": "7.27.3",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.3.tgz",
- "integrity": "sha512-Y1GkI4ktrtvmawoSq+4FCVHNryea6uR+qUQy0AGxLSsjCX0nVmkYQMBLHDkXZuo5hGx7eYdnIaslsdBFm7zbUw==",
+ "version": "7.28.2",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz",
+ "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==",
"license": "MIT",
"dependencies": {
"@babel/helper-string-parser": "^7.27.1",
@@ -553,6 +554,20 @@
"node": ">=18"
}
},
+ "node_modules/@dependents/detective-less": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/@dependents/detective-less/-/detective-less-5.0.1.tgz",
+ "integrity": "sha512-Y6+WUMsTFWE5jb20IFP4YGa5IrGY/+a/FbOSjDF/wz9gepU2hwCYSXRHP/vPwBvwcY3SVMASt4yXxbXNXigmZQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "gonzales-pe": "^4.3.0",
+ "node-source-walk": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/@emotion/babel-plugin": {
"version": "11.13.5",
"resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz",
@@ -2367,6 +2382,96 @@
"@testing-library/dom": ">=7.21.4"
}
},
+ "node_modules/@ts-graphviz/adapter": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/@ts-graphviz/adapter/-/adapter-2.0.6.tgz",
+ "integrity": "sha512-kJ10lIMSWMJkLkkCG5gt927SnGZcBuG0s0HHswGzcHTgvtUe7yk5/3zTEr0bafzsodsOq5Gi6FhQeV775nC35Q==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ts-graphviz"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/ts-graphviz"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ts-graphviz/common": "^2.1.5"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@ts-graphviz/ast": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/@ts-graphviz/ast/-/ast-2.0.7.tgz",
+ "integrity": "sha512-e6+2qtNV99UT6DJSoLbHfkzfyqY84aIuoV8Xlb9+hZAjgpum8iVHprGeAMQ4rF6sKUAxrmY8rfF/vgAwoPc3gw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ts-graphviz"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/ts-graphviz"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ts-graphviz/common": "^2.1.5"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@ts-graphviz/common": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@ts-graphviz/common/-/common-2.1.5.tgz",
+ "integrity": "sha512-S6/9+T6x8j6cr/gNhp+U2olwo1n0jKj/682QVqsh7yXWV6ednHYqxFw0ZsY3LyzT0N8jaZ6jQY9YD99le3cmvg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ts-graphviz"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/ts-graphviz"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@ts-graphviz/core": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/@ts-graphviz/core/-/core-2.0.7.tgz",
+ "integrity": "sha512-w071DSzP94YfN6XiWhOxnLpYT3uqtxJBDYdh6Jdjzt+Ce6DNspJsPQgpC7rbts/B8tEkq0LHoYuIF/O5Jh5rPg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ts-graphviz"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/ts-graphviz"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ts-graphviz/ast": "^2.0.7",
+ "@ts-graphviz/common": "^2.1.5"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/@types/aria-query": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
@@ -2475,6 +2580,132 @@
"@types/react": "*"
}
},
+ "node_modules/@typescript-eslint/project-service": {
+ "version": "8.40.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.40.0.tgz",
+ "integrity": "sha512-/A89vz7Wf5DEXsGVvcGdYKbVM9F7DyFXj52lNYUDS1L9yJfqjW/fIp5PgMuEJL/KeqVTe2QSbXAGUZljDUpArw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/tsconfig-utils": "^8.40.0",
+ "@typescript-eslint/types": "^8.40.0",
+ "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/tsconfig-utils": {
+ "version": "8.40.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.40.0.tgz",
+ "integrity": "sha512-jtMytmUaG9d/9kqSl/W3E3xaWESo4hFDxAIHGVW/WKKtQhesnRIJSAJO6XckluuJ6KDB5woD1EiqknriCtAmcw==",
+ "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/types": {
+ "version": "8.40.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.40.0.tgz",
+ "integrity": "sha512-ETdbFlgbAmXHyFPwqUIYrfc12ArvpBhEVgGAxVYSwli26dn8Ko+lIo4Su9vI9ykTZdJn+vJprs/0eZU0YMAEQg==",
+ "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.40.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.40.0.tgz",
+ "integrity": "sha512-k1z9+GJReVVOkc1WfVKs1vBrR5MIKKbdAjDTPvIK3L8De6KbFfPFt6BKpdkdk7rZS2GtC/m6yI5MYX+UsuvVYQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/project-service": "8.40.0",
+ "@typescript-eslint/tsconfig-utils": "8.40.0",
+ "@typescript-eslint/types": "8.40.0",
+ "@typescript-eslint/visitor-keys": "8.40.0",
+ "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/visitor-keys": {
+ "version": "8.40.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.40.0.tgz",
+ "integrity": "sha512-8CZ47QwalyRjsypfwnbI3hKy5gJDPmrkLjkgMxhi0+DZZ2QNx2naS6/hWoVYUHU7LU2zleF68V9miaVZvhFfTA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.40.0",
+ "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/@vitejs/plugin-react": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.5.0.tgz",
@@ -2668,6 +2899,94 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@vue/compiler-core": {
+ "version": "3.5.19",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.19.tgz",
+ "integrity": "sha512-/afpyvlkrSNYbPo94Qu8GtIOWS+g5TRdOvs6XZNw6pWQQmj5pBgSZvEPOIZlqWq0YvoUhDDQaQ2TnzuJdOV4hA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.28.3",
+ "@vue/shared": "3.5.19",
+ "entities": "^4.5.0",
+ "estree-walker": "^2.0.2",
+ "source-map-js": "^1.2.1"
+ }
+ },
+ "node_modules/@vue/compiler-core/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/@vue/compiler-core/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/@vue/compiler-dom": {
+ "version": "3.5.19",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.19.tgz",
+ "integrity": "sha512-Drs6rPHQZx/pN9S6ml3Z3K/TWCIRPvzG2B/o5kFK9X0MNHt8/E+38tiRfojufrYBfA6FQUFB2qBBRXlcSXWtOA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vue/compiler-core": "3.5.19",
+ "@vue/shared": "3.5.19"
+ }
+ },
+ "node_modules/@vue/compiler-sfc": {
+ "version": "3.5.19",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.19.tgz",
+ "integrity": "sha512-YWCm1CYaJ+2RvNmhCwI7t3I3nU+hOrWGWMsn+Z/kmm1jy5iinnVtlmkiZwbLlbV1SRizX7vHsc0/bG5dj0zRTg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.28.3",
+ "@vue/compiler-core": "3.5.19",
+ "@vue/compiler-dom": "3.5.19",
+ "@vue/compiler-ssr": "3.5.19",
+ "@vue/shared": "3.5.19",
+ "estree-walker": "^2.0.2",
+ "magic-string": "^0.30.17",
+ "postcss": "^8.5.6",
+ "source-map-js": "^1.2.1"
+ }
+ },
+ "node_modules/@vue/compiler-sfc/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/@vue/compiler-ssr": {
+ "version": "3.5.19",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.19.tgz",
+ "integrity": "sha512-/wx0VZtkWOPdiQLWPeQeqpHWR/LuNC7bHfSX7OayBTtUy8wur6vT6EQIX6Et86aED6J+y8tTw43qo2uoqGg5sw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vue/compiler-dom": "3.5.19",
+ "@vue/shared": "3.5.19"
+ }
+ },
+ "node_modules/@vue/shared": {
+ "version": "3.5.19",
+ "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.19.tgz",
+ "integrity": "sha512-IhXCOn08wgKrLQxRFKKlSacWg4Goi1BolrdEeLYn6tgHjJNXVrWJ5nzoxZqNwl5p88aLlQ8LOaoMa3AYvaKJ/Q==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/abbrev": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
@@ -2738,6 +3057,13 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
+ "node_modules/any-promise": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
+ "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/anymatch": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
@@ -2752,6 +3078,13 @@
"node": ">= 8"
}
},
+ "node_modules/app-module-path": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/app-module-path/-/app-module-path-2.2.0.tgz",
+ "integrity": "sha512-gkco+qxENJV+8vFcDiiFhuoSvRXb2a/QPqpSoWhVz829VNJfOTnELbBmPmNKFxf3xdNnw4DWCkzkDaavcX/1YQ==",
+ "dev": true,
+ "license": "BSD-2-Clause"
+ },
"node_modules/aproba": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz",
@@ -2808,6 +3141,16 @@
"node": "*"
}
},
+ "node_modules/ast-module-types": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/ast-module-types/-/ast-module-types-6.0.1.tgz",
+ "integrity": "sha512-WHw67kLXYbZuHTmcdbIrVArCq5wxo6NEuj3hiYAWr8mwJeC+C2mMCIBIWCiDoCye/OF/xelc+teJ1ERoWmnEIA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@@ -2893,6 +3236,27 @@
"devOptional": true,
"license": "MIT"
},
+ "node_modules/base64-js": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
"node_modules/bidi-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz",
@@ -2922,6 +3286,18 @@
"integrity": "sha512-PJvH288AWQhKs2v9zyfYdPzlPqf5bXbGMmhmUIY9x4dAUGIWgomO771oBQNwJnMQSnUIXhKu6sgzpBRXTlvb8Q==",
"license": "MIT"
},
+ "node_modules/bl": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
+ "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "buffer": "^5.5.0",
+ "inherits": "^2.0.4",
+ "readable-stream": "^3.4.0"
+ }
+ },
"node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -2978,6 +3354,31 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
+ "node_modules/buffer": {
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
+ "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "base64-js": "^1.3.1",
+ "ieee754": "^1.1.13"
+ }
+ },
"node_modules/cac": {
"version": "6.7.14",
"resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
@@ -3152,6 +3553,42 @@
"node": ">=10"
}
},
+ "node_modules/cli-cursor": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
+ "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "restore-cursor": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cli-spinners": {
+ "version": "2.9.2",
+ "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz",
+ "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/clone": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz",
+ "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
"node_modules/clsx": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
@@ -3201,6 +3638,23 @@
"node": ">= 0.8"
}
},
+ "node_modules/commander": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
+ "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/commondir": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
+ "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==",
+ "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",
@@ -3424,6 +3878,29 @@
"node": ">=6"
}
},
+ "node_modules/deep-extend": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
+ "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "node_modules/defaults": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz",
+ "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "clone": "^1.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
@@ -3450,6 +3927,35 @@
"node": ">=4"
}
},
+ "node_modules/dependency-tree": {
+ "version": "11.2.0",
+ "resolved": "https://registry.npmjs.org/dependency-tree/-/dependency-tree-11.2.0.tgz",
+ "integrity": "sha512-+C1H3mXhcvMCeu5i2Jpg9dc0N29TWTuT6vJD7mHLAfVmAbo9zW8NlkvQ1tYd3PDMab0IRQM0ccoyX68EZtx9xw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "commander": "^12.1.0",
+ "filing-cabinet": "^5.0.3",
+ "precinct": "^12.2.0",
+ "typescript": "^5.8.3"
+ },
+ "bin": {
+ "dependency-tree": "bin/cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/dependency-tree/node_modules/commander": {
+ "version": "12.1.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
+ "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/dequal": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
@@ -3474,6 +3980,147 @@
"integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==",
"license": "MIT"
},
+ "node_modules/detective-amd": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/detective-amd/-/detective-amd-6.0.1.tgz",
+ "integrity": "sha512-TtyZ3OhwUoEEIhTFoc1C9IyJIud3y+xYkSRjmvCt65+ycQuc3VcBrPRTMWoO/AnuCyOB8T5gky+xf7Igxtjd3g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ast-module-types": "^6.0.1",
+ "escodegen": "^2.1.0",
+ "get-amd-module-type": "^6.0.1",
+ "node-source-walk": "^7.0.1"
+ },
+ "bin": {
+ "detective-amd": "bin/cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/detective-cjs": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/detective-cjs/-/detective-cjs-6.0.1.tgz",
+ "integrity": "sha512-tLTQsWvd2WMcmn/60T2inEJNhJoi7a//PQ7DwRKEj1yEeiQs4mrONgsUtEJKnZmrGWBBmE0kJ1vqOG/NAxwaJw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ast-module-types": "^6.0.1",
+ "node-source-walk": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/detective-es6": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/detective-es6/-/detective-es6-5.0.1.tgz",
+ "integrity": "sha512-XusTPuewnSUdoxRSx8OOI6xIA/uld/wMQwYsouvFN2LAg7HgP06NF1lHRV3x6BZxyL2Kkoih4ewcq8hcbGtwew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "node-source-walk": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/detective-postcss": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/detective-postcss/-/detective-postcss-7.0.1.tgz",
+ "integrity": "sha512-bEOVpHU9picRZux5XnwGsmCN4+8oZo7vSW0O0/Enq/TO5R2pIAP2279NsszpJR7ocnQt4WXU0+nnh/0JuK4KHQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-url": "^1.2.4",
+ "postcss-values-parser": "^6.0.2"
+ },
+ "engines": {
+ "node": "^14.0.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.47"
+ }
+ },
+ "node_modules/detective-sass": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/detective-sass/-/detective-sass-6.0.1.tgz",
+ "integrity": "sha512-jSGPO8QDy7K7pztUmGC6aiHkexBQT4GIH+mBAL9ZyBmnUIOFbkfZnO8wPRRJFP/QP83irObgsZHCoDHZ173tRw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "gonzales-pe": "^4.3.0",
+ "node-source-walk": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/detective-scss": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/detective-scss/-/detective-scss-5.0.1.tgz",
+ "integrity": "sha512-MAyPYRgS6DCiS6n6AoSBJXLGVOydsr9huwXORUlJ37K3YLyiN0vYHpzs3AdJOgHobBfispokoqrEon9rbmKacg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "gonzales-pe": "^4.3.0",
+ "node-source-walk": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/detective-stylus": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/detective-stylus/-/detective-stylus-5.0.1.tgz",
+ "integrity": "sha512-Dgn0bUqdGbE3oZJ+WCKf8Dmu7VWLcmRJGc6RCzBgG31DLIyai9WAoEhYRgIHpt/BCRMrnXLbGWGPQuBUrnF0TA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/detective-typescript": {
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/detective-typescript/-/detective-typescript-14.0.0.tgz",
+ "integrity": "sha512-pgN43/80MmWVSEi5LUuiVvO/0a9ss5V7fwVfrJ4QzAQRd3cwqU1SfWGXJFcNKUqoD5cS+uIovhw5t/0rSeC5Mw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/typescript-estree": "^8.23.0",
+ "ast-module-types": "^6.0.1",
+ "node-source-walk": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "typescript": "^5.4.4"
+ }
+ },
+ "node_modules/detective-vue2": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/detective-vue2/-/detective-vue2-2.2.0.tgz",
+ "integrity": "sha512-sVg/t6O2z1zna8a/UIV6xL5KUa2cMTQbdTIIvqNM0NIPswp52fe43Nwmbahzj3ww4D844u/vC2PYfiGLvD3zFA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@dependents/detective-less": "^5.0.1",
+ "@vue/compiler-sfc": "^3.5.13",
+ "detective-es6": "^5.0.1",
+ "detective-sass": "^6.0.1",
+ "detective-scss": "^5.0.1",
+ "detective-stylus": "^5.0.1",
+ "detective-typescript": "^14.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "typescript": "^5.4.4"
+ }
+ },
"node_modules/dezalgo": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz",
@@ -3672,6 +4319,76 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/escodegen": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz",
+ "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "esprima": "^4.0.1",
+ "estraverse": "^5.2.0",
+ "esutils": "^2.0.2"
+ },
+ "bin": {
+ "escodegen": "bin/escodegen.js",
+ "esgenerate": "bin/esgenerate.js"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "optionalDependencies": {
+ "source-map": "~0.6.1"
+ }
+ },
+ "node_modules/escodegen/node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "optional": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "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/esprima": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "bin": {
+ "esparse": "bin/esparse.js",
+ "esvalidate": "bin/esvalidate.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
"node_modules/estree-walker": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
@@ -3682,6 +4399,16 @@
"@types/estree": "^1.0.0"
}
},
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/execa": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz",
@@ -3771,6 +4498,42 @@
"node": ">= 12"
}
},
+ "node_modules/filing-cabinet": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/filing-cabinet/-/filing-cabinet-5.0.3.tgz",
+ "integrity": "sha512-PlPcMwVWg60NQkhvfoxZs4wEHjhlOO/y7OAm4sKM60o1Z9nttRY4mcdQxp/iZ+kg/Vv6Hw1OAaTbYVM9DA9pYg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "app-module-path": "^2.2.0",
+ "commander": "^12.1.0",
+ "enhanced-resolve": "^5.18.0",
+ "module-definition": "^6.0.1",
+ "module-lookup-amd": "^9.0.3",
+ "resolve": "^1.22.10",
+ "resolve-dependency-path": "^4.0.1",
+ "sass-lookup": "^6.1.0",
+ "stylus-lookup": "^6.1.0",
+ "tsconfig-paths": "^4.2.0",
+ "typescript": "^5.7.3"
+ },
+ "bin": {
+ "filing-cabinet": "bin/cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/filing-cabinet/node_modules/commander": {
+ "version": "12.1.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
+ "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/fill-range": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
@@ -3934,6 +4697,20 @@
"node": ">=6.9.0"
}
},
+ "node_modules/get-amd-module-type": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/get-amd-module-type/-/get-amd-module-type-6.0.1.tgz",
+ "integrity": "sha512-MtjsmYiCXcYDDrGqtNbeIYdAl85n+5mSv2r3FbzER/YV3ZILw4HNNIw34HuV5pyl0jzs6GFYU1VHVEefhgcNHQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ast-module-types": "^6.0.1",
+ "node-source-walk": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
@@ -3987,6 +4764,13 @@
"node": ">=6"
}
},
+ "node_modules/get-own-enumerable-property-symbols": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz",
+ "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==",
+ "dev": true,
+ "license": "ISC"
+ },
"node_modules/get-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
@@ -4044,6 +4828,22 @@
"node": ">=4"
}
},
+ "node_modules/gonzales-pe": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/gonzales-pe/-/gonzales-pe-4.3.0.tgz",
+ "integrity": "sha512-otgSPpUmdWJ43VXyiNgEYE4luzHCL2pz4wQ0OnDluC6Eg4Ko3Vexy/SrSynglw/eR+OhkzmqFCZa/OFa/RgAOQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "minimist": "^1.2.5"
+ },
+ "bin": {
+ "gonzales": "bin/gonzales.js"
+ },
+ "engines": {
+ "node": ">=0.6.0"
+ }
+ },
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
@@ -4273,6 +5073,27 @@
"node": ">=0.10.0"
}
},
+ "node_modules/ieee754": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
+ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "BSD-3-Clause"
+ },
"node_modules/immediate": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
@@ -4321,6 +5142,13 @@
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"license": "ISC"
},
+ "node_modules/ini": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
+ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
+ "dev": true,
+ "license": "ISC"
+ },
"node_modules/is-arrayish": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
@@ -4388,6 +5216,16 @@
"node": ">=0.10.0"
}
},
+ "node_modules/is-interactive": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz",
+ "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
@@ -4398,6 +5236,16 @@
"node": ">=0.12.0"
}
},
+ "node_modules/is-obj": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz",
+ "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/is-potential-custom-element-name": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
@@ -4405,6 +5253,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/is-regexp": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz",
+ "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/is-stream": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz",
@@ -4418,6 +5276,39 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/is-unicode-supported": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz",
+ "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-url": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz",
+ "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/is-url-superb": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-url-superb/-/is-url-superb-4.0.0.tgz",
+ "integrity": "sha512-GI+WjezhPPcbM+tqE9LnmsY5qqjwHzTvjJ36wxYX5ujNXefSUJ/T17r5bqDV8yLhcgB59KTPNOc9O9cmHTPWsA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
@@ -5062,6 +5953,23 @@
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"license": "MIT"
},
+ "node_modules/log-symbols": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
+ "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chalk": "^4.1.0",
+ "is-unicode-supported": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
@@ -5103,6 +6011,45 @@
"lz-string": "bin/bin.js"
}
},
+ "node_modules/madge": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/madge/-/madge-8.0.0.tgz",
+ "integrity": "sha512-9sSsi3TBPhmkTCIpVQF0SPiChj1L7Rq9kU2KDG1o6v2XH9cCw086MopjVCD+vuoL5v8S77DTbVopTO8OUiQpIw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chalk": "^4.1.2",
+ "commander": "^7.2.0",
+ "commondir": "^1.0.1",
+ "debug": "^4.3.4",
+ "dependency-tree": "^11.0.0",
+ "ora": "^5.4.1",
+ "pluralize": "^8.0.0",
+ "pretty-ms": "^7.0.1",
+ "rc": "^1.2.8",
+ "stream-to-array": "^2.3.0",
+ "ts-graphviz": "^2.1.2",
+ "walkdir": "^0.4.1"
+ },
+ "bin": {
+ "madge": "bin/cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://www.paypal.me/pahen"
+ },
+ "peerDependencies": {
+ "typescript": "^5.4.4"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
"node_modules/magic-string": {
"version": "0.30.17",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
@@ -5359,6 +6306,52 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/module-definition": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/module-definition/-/module-definition-6.0.1.tgz",
+ "integrity": "sha512-FeVc50FTfVVQnolk/WQT8MX+2WVcDnTGiq6Wo+/+lJ2ET1bRVi3HG3YlJUfqagNMc/kUlFSoR96AJkxGpKz13g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ast-module-types": "^6.0.1",
+ "node-source-walk": "^7.0.1"
+ },
+ "bin": {
+ "module-definition": "bin/cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/module-lookup-amd": {
+ "version": "9.0.5",
+ "resolved": "https://registry.npmjs.org/module-lookup-amd/-/module-lookup-amd-9.0.5.tgz",
+ "integrity": "sha512-Rs5FVpVcBYRHPLuhHOjgbRhosaQYLtEo3JIeDIbmNo7mSssi1CTzwMh8v36gAzpbzLGXI9wB/yHh+5+3fY1QVw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "commander": "^12.1.0",
+ "glob": "^7.2.3",
+ "requirejs": "^2.3.7",
+ "requirejs-config-file": "^4.0.0"
+ },
+ "bin": {
+ "lookup-amd": "bin/cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/module-lookup-amd/node_modules/commander": {
+ "version": "12.1.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
+ "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -5438,6 +6431,19 @@
"integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
"license": "MIT"
},
+ "node_modules/node-source-walk": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/node-source-walk/-/node-source-walk-7.0.1.tgz",
+ "integrity": "sha512-3VW/8JpPqPvnJvseXowjZcirPisssnBuDikk6JIZ8jQzF7KJQX52iPFX4RYYxLycYH7IbMRSPUOga/esVjy5Yg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.26.7"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/nopt": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz",
@@ -5578,6 +6584,30 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/ora": {
+ "version": "5.4.1",
+ "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz",
+ "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "bl": "^4.1.0",
+ "chalk": "^4.1.0",
+ "cli-cursor": "^3.1.0",
+ "cli-spinners": "^2.5.0",
+ "is-interactive": "^1.0.0",
+ "is-unicode-supported": "^0.1.0",
+ "log-symbols": "^4.1.0",
+ "strip-ansi": "^6.0.0",
+ "wcwidth": "^1.0.1"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/os-homedir": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
@@ -5658,6 +6688,16 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/parse-ms": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-2.1.0.tgz",
+ "integrity": "sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/parse5": {
"version": "7.3.0",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
@@ -5857,10 +6897,20 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
+ "node_modules/pluralize": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz",
+ "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/postcss": {
- "version": "8.5.3",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
- "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==",
+ "version": "8.5.6",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
+ "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
"funding": [
{
"type": "opencollective",
@@ -5877,7 +6927,7 @@
],
"license": "MIT",
"dependencies": {
- "nanoid": "^3.3.8",
+ "nanoid": "^3.3.11",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
@@ -6186,6 +7236,64 @@
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
"license": "MIT"
},
+ "node_modules/postcss-values-parser": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/postcss-values-parser/-/postcss-values-parser-6.0.2.tgz",
+ "integrity": "sha512-YLJpK0N1brcNJrs9WatuJFtHaV9q5aAOj+S4DI5S7jgHlRfm0PIbDCAFRYMQD5SHq7Fy6xsDhyutgS0QOAs0qw==",
+ "dev": true,
+ "license": "MPL-2.0",
+ "dependencies": {
+ "color-name": "^1.1.4",
+ "is-url-superb": "^4.0.0",
+ "quote-unquote": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "postcss": "^8.2.9"
+ }
+ },
+ "node_modules/precinct": {
+ "version": "12.2.0",
+ "resolved": "https://registry.npmjs.org/precinct/-/precinct-12.2.0.tgz",
+ "integrity": "sha512-NFBMuwIfaJ4SocE9YXPU/n4AcNSoFMVFjP72nvl3cx69j/ke61/hPOWFREVxLkFhhEGnA8ZuVfTqJBa+PK3b5w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@dependents/detective-less": "^5.0.1",
+ "commander": "^12.1.0",
+ "detective-amd": "^6.0.1",
+ "detective-cjs": "^6.0.1",
+ "detective-es6": "^5.0.1",
+ "detective-postcss": "^7.0.1",
+ "detective-sass": "^6.0.1",
+ "detective-scss": "^5.0.1",
+ "detective-stylus": "^5.0.1",
+ "detective-typescript": "^14.0.0",
+ "detective-vue2": "^2.2.0",
+ "module-definition": "^6.0.1",
+ "node-source-walk": "^7.0.1",
+ "postcss": "^8.5.1",
+ "typescript": "^5.7.3"
+ },
+ "bin": {
+ "precinct": "bin/cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/precinct/node_modules/commander": {
+ "version": "12.1.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
+ "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/pretty-format": {
"version": "27.5.1",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
@@ -6228,6 +7336,22 @@
"node": ">= 0.8"
}
},
+ "node_modules/pretty-ms": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-7.0.1.tgz",
+ "integrity": "sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "parse-ms": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
@@ -6307,12 +7431,35 @@
],
"license": "MIT"
},
+ "node_modules/quote-unquote": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/quote-unquote/-/quote-unquote-1.0.0.tgz",
+ "integrity": "sha512-twwRO/ilhlG/FIgYeKGFqyHhoEhqgnKVkcmqMKi2r524gz3ZbDTcyFt38E9xjJI2vT+KbRNHVbnJ/e0I25Azwg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/raf-schd": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz",
"integrity": "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==",
"license": "MIT"
},
+ "node_modules/rc": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
+ "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
+ "dev": true,
+ "license": "(BSD-2-Clause OR MIT OR Apache-2.0)",
+ "dependencies": {
+ "deep-extend": "^0.6.0",
+ "ini": "~1.3.0",
+ "minimist": "^1.2.0",
+ "strip-json-comments": "~2.0.1"
+ },
+ "bin": {
+ "rc": "cli.js"
+ }
+ },
"node_modules/react": {
"version": "19.1.0",
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
@@ -6596,8 +7743,8 @@
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+ "devOptional": true,
"license": "MIT",
- "optional": true,
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
@@ -6666,6 +7813,34 @@
"node": ">=0.10.0"
}
},
+ "node_modules/requirejs": {
+ "version": "2.3.7",
+ "resolved": "https://registry.npmjs.org/requirejs/-/requirejs-2.3.7.tgz",
+ "integrity": "sha512-DouTG8T1WanGok6Qjg2SXuCMzszOo0eHeH9hDZ5Y4x8Je+9JB38HdTLT4/VA8OaUhBa0JPVHJ0pyBkM1z+pDsw==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "r_js": "bin/r.js",
+ "r.js": "bin/r.js"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/requirejs-config-file": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/requirejs-config-file/-/requirejs-config-file-4.0.0.tgz",
+ "integrity": "sha512-jnIre8cbWOyvr8a5F2KuqBnY+SDA4NXr/hzEZJG79Mxm2WiFQz2dzhC8ibtPJS7zkmBEl1mxSwp5HhC1W4qpxw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esprima": "^4.0.0",
+ "stringify-object": "^3.2.1"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
"node_modules/requires-port": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
@@ -6693,6 +7868,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/resolve-dependency-path": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/resolve-dependency-path/-/resolve-dependency-path-4.0.1.tgz",
+ "integrity": "sha512-YQftIIC4vzO9UMhO/sCgXukNyiwVRCVaxiWskCBy7Zpqkplm8kTAISZ8O1MoKW1ca6xzgLUBjZTcDgypXvXxiQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/resolve-from": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
@@ -6702,6 +7887,46 @@
"node": ">=4"
}
},
+ "node_modules/restore-cursor": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz",
+ "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "onetime": "^5.1.0",
+ "signal-exit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/restore-cursor/node_modules/mimic-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/restore-cursor/node_modules/onetime": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
+ "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mimic-fn": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/reusify": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
@@ -6805,6 +8030,7 @@
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "devOptional": true,
"funding": [
{
"type": "github",
@@ -6819,8 +8045,7 @@
"url": "https://feross.org/support"
}
],
- "license": "MIT",
- "optional": true
+ "license": "MIT"
},
"node_modules/safer-buffer": {
"version": "2.1.2",
@@ -6829,6 +8054,33 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/sass-lookup": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/sass-lookup/-/sass-lookup-6.1.0.tgz",
+ "integrity": "sha512-Zx+lVyoWqXZxHuYWlTA17Z5sczJ6braNT2C7rmClw+c4E7r/n911Zwss3h1uHI9reR5AgHZyNHF7c2+VIp5AUA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "commander": "^12.1.0",
+ "enhanced-resolve": "^5.18.0"
+ },
+ "bin": {
+ "sass-lookup": "bin/cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/sass-lookup/node_modules/commander": {
+ "version": "12.1.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
+ "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/saxes": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
@@ -6913,8 +8165,8 @@
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
- "license": "ISC",
- "optional": true
+ "devOptional": true,
+ "license": "ISC"
},
"node_modules/simple-concat": {
"version": "1.0.1",
@@ -7050,12 +8302,22 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/stream-to-array": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/stream-to-array/-/stream-to-array-2.3.0.tgz",
+ "integrity": "sha512-UsZtOYEn4tWU2RGLOXr/o/xjRBftZRlG3dEWoaHr8j4GuypJ3isitGbVyjQKAuMu+xbiop8q224TjiZWc4XTZA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "any-promise": "^1.1.0"
+ }
+ },
"node_modules/string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+ "devOptional": true,
"license": "MIT",
- "optional": true,
"dependencies": {
"safe-buffer": "~5.2.0"
}
@@ -7082,6 +8344,21 @@
"devOptional": true,
"license": "MIT"
},
+ "node_modules/stringify-object": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz",
+ "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "get-own-enumerable-property-symbols": "^3.0.0",
+ "is-obj": "^1.0.1",
+ "is-regexp": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
@@ -7095,6 +8372,16 @@
"node": ">=8"
}
},
+ "node_modules/strip-bom": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
+ "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/strip-final-newline": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz",
@@ -7120,6 +8407,16 @@
"node": ">=8"
}
},
+ "node_modules/strip-json-comments": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
+ "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/strip-literal": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.1.1.tgz",
@@ -7146,6 +8443,32 @@
"integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==",
"license": "MIT"
},
+ "node_modules/stylus-lookup": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/stylus-lookup/-/stylus-lookup-6.1.0.tgz",
+ "integrity": "sha512-5QSwgxAzXPMN+yugy61C60PhoANdItfdjSEZR8siFwz7yL9jTmV0UBKDCfn3K8GkGB4g0Y9py7vTCX8rFu4/pQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "commander": "^12.1.0"
+ },
+ "bin": {
+ "stylus-lookup": "bin/cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/stylus-lookup/node_modules/commander": {
+ "version": "12.1.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
+ "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/sugarss": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/sugarss/-/sugarss-4.0.1.tgz",
@@ -7418,6 +8741,60 @@
"node": ">=0.6"
}
},
+ "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/ts-graphviz": {
+ "version": "2.1.6",
+ "resolved": "https://registry.npmjs.org/ts-graphviz/-/ts-graphviz-2.1.6.tgz",
+ "integrity": "sha512-XyLVuhBVvdJTJr2FJJV2L1pc4MwSjMhcunRVgDE9k4wbb2ee7ORYnPewxMWUav12vxyfUM686MSGsqnVRIInuw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ts-graphviz"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/ts-graphviz"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ts-graphviz/adapter": "^2.0.6",
+ "@ts-graphviz/ast": "^2.0.7",
+ "@ts-graphviz/common": "^2.1.5",
+ "@ts-graphviz/core": "^2.0.7"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsconfig-paths": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz",
+ "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "json5": "^2.2.2",
+ "minimist": "^1.2.6",
+ "strip-bom": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
@@ -8827,6 +10204,26 @@
"node": ">=18"
}
},
+ "node_modules/walkdir": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/walkdir/-/walkdir-0.4.1.tgz",
+ "integrity": "sha512-3eBwRyEln6E1MSzcxcVpQIhRG8Q1jLvEqRmCZqS3dsfXEDR/AhOF4d+jHg1qvDCpYaVRZjENPQyrVxAkQqxPgQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/wcwidth": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",
+ "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "defaults": "^1.0.3"
+ }
+ },
"node_modules/web-vitals": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-2.1.4.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index ad945dbc2..cde323bcc 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -74,6 +74,7 @@
"@vitest/coverage-v8": "^1.0.0",
"jsdom": "^23.0.0",
"license-checker": "^25.0.1",
+ "madge": "^8.0.0",
"postcss": "^8.5.3",
"postcss-cli": "^11.0.1",
"postcss-preset-mantine": "^1.17.0",
diff --git a/frontend/public/locales/en-GB/translation.json b/frontend/public/locales/en-GB/translation.json
index 0a03fd01a..63937bc74 100644
--- a/frontend/public/locales/en-GB/translation.json
+++ b/frontend/public/locales/en-GB/translation.json
@@ -85,6 +85,7 @@
"warning": {
"tooltipTitle": "Warning"
},
+ "edit": "Edit",
"delete": "Delete",
"username": "Username",
"password": "Password",
@@ -538,10 +539,6 @@
"title": "Edit Table of Contents",
"desc": "Add or edit bookmarks and table of contents in PDF documents"
},
- "automate": {
- "title": "Automate",
- "desc": "Build multi-step workflows by chaining together PDF actions. Ideal for recurring tasks."
- },
"manageCertificates": {
"title": "Manage Certificates",
"desc": "Import, export, or delete digital certificate files used for signing PDFs."
@@ -601,6 +598,10 @@
"changePermissions": {
"title": "Change Permissions",
"desc": "Change document restrictions and permissions"
+ },
+ "automate": {
+ "title": "Automate",
+ "desc": "Build multi-step workflows by chaining together PDF actions. Ideal for recurring tasks."
}
},
"viewPdf": {
@@ -731,7 +732,8 @@
"officeDocs": "Office Documents (Word, Excel, PowerPoint)",
"imagesExt": "Images (JPG, PNG, etc.)",
"markdown": "Markdown",
- "textRtf": "Text/RTF"
+ "textRtf": "Text/RTF",
+ "grayscale": "Greyscale"
},
"imageToPdf": {
"tags": "conversion,img,jpg,picture,photo"
@@ -2021,7 +2023,8 @@
"downloadSelected": "Download Selected",
"selectedCount": "{{count}} selected",
"download": "Download",
- "delete": "Delete"
+ "delete": "Delete",
+ "unsupported":"Unsupported"
},
"storage": {
"temporaryNotice": "Files are stored temporarily in your browser and may be cleared automatically",
@@ -2191,5 +2194,68 @@
"results": {
"title": "Decrypted PDFs"
}
- }
+ },
+ "automate": {
+ "title": "Automate",
+ "desc": "Build multi-step workflows by chaining together PDF actions. Ideal for recurring tasks.",
+ "invalidStep": "Invalid step",
+ "files": {
+ "placeholder": "Select files to process with this automation"
+ },
+ "selection": {
+ "title": "Automation Selection",
+ "saved": {
+ "title": "Saved"
+ },
+ "createNew": {
+ "title": "Create New Automation"
+ },
+ "suggested": {
+ "title": "Suggested"
+ }
+ },
+ "creation": {
+ "createTitle": "Create Automation",
+ "editTitle": "Edit Automation",
+ "description": "Automations run tools sequentially. To get started, add tools in the order you want them to run.",
+ "name": {
+ "placeholder": "Automation name"
+ },
+ "tools": {
+ "selectTool": "Select a tool...",
+ "selected": "Selected Tools",
+ "remove": "Remove tool",
+ "configure": "Configure tool",
+ "notConfigured": "! Not Configured",
+ "addTool": "Add Tool",
+ "add": "Add a tool..."
+ },
+ "save": "Save Automation",
+ "unsavedChanges": {
+ "title": "Unsaved Changes",
+ "message": "You have unsaved changes. Are you sure you want to go back? All changes will be lost.",
+ "cancel": "Cancel",
+ "confirm": "Go Back"
+ }
+ },
+ "run": {
+ "title": "Run Automation"
+ },
+ "sequence": {
+ "unnamed": "Unnamed Automation",
+ "steps": "{{count}} steps",
+ "running": "Running Automation...",
+ "run": "Run Automation",
+ "finish": "Finish"
+ },
+ "reviewTitle": "Automation Results",
+ "config": {
+ "loading": "Loading tool configuration...",
+ "noSettings": "This tool does not have configurable settings.",
+ "title": "Configure {{toolName}}",
+ "description": "Configure the settings for this tool. These settings will be applied when the automation runs.",
+ "cancel": "Cancel",
+ "save": "Save Configuration"
+ }
+ }
}
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index e628dc4de..b498b0677 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -1,24 +1,29 @@
-import React, { Suspense } from 'react';
-import { RainbowThemeProvider } from './components/shared/RainbowThemeProvider';
-import { FileContextProvider } from './contexts/FileContext';
-import { NavigationProvider } from './contexts/NavigationContext';
-import { FilesModalProvider } from './contexts/FilesModalContext';
-import HomePage from './pages/HomePage';
+import React, { Suspense } from "react";
+import { RainbowThemeProvider } from "./components/shared/RainbowThemeProvider";
+import { FileContextProvider } from "./contexts/FileContext";
+import { NavigationProvider } from "./contexts/NavigationContext";
+import { FilesModalProvider } from "./contexts/FilesModalContext";
+import { ToolWorkflowProvider } from "./contexts/ToolWorkflowContext";
+import { SidebarProvider } from "./contexts/SidebarContext";
+import ErrorBoundary from "./components/shared/ErrorBoundary";
+import HomePage from "./pages/HomePage";
// Import global styles
-import './styles/tailwind.css';
-import './index.css';
+import "./styles/tailwind.css";
+import "./index.css";
// Loading component for i18next suspense
const LoadingFallback = () => (
-
+
Loading...
);
@@ -27,13 +32,19 @@ export default function App() {
return (
}>
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
);
diff --git a/frontend/src/components/FileManager.tsx b/frontend/src/components/FileManager.tsx
index 1c327cefa..4294180f3 100644
--- a/frontend/src/components/FileManager.tsx
+++ b/frontend/src/components/FileManager.tsx
@@ -111,7 +111,7 @@ const FileManager: React.FC
= ({ selectedTool }) => {
onClose={closeFilesModal}
size={isMobile ? "100%" : "auto"}
centered
- radius={30}
+ radius="md"
className="overflow-hidden p-0"
withCloseButton={false}
styles={{
@@ -144,7 +144,7 @@ const FileManager: React.FC = ({ selectedTool }) => {
height: '100%',
width: '100%',
border: 'none',
- borderRadius: '30px',
+ borderRadius: 'var(--radius-md)',
backgroundColor: 'var(--bg-file-manager)'
}}
styles={{
diff --git a/frontend/src/components/shared/ErrorBoundary.tsx b/frontend/src/components/shared/ErrorBoundary.tsx
new file mode 100644
index 000000000..3dfd32d27
--- /dev/null
+++ b/frontend/src/components/shared/ErrorBoundary.tsx
@@ -0,0 +1,56 @@
+import React from 'react';
+import { Text, Button, Stack } from '@mantine/core';
+
+interface ErrorBoundaryState {
+ hasError: boolean;
+ error?: Error;
+}
+
+interface ErrorBoundaryProps {
+ children: React.ReactNode;
+ fallback?: React.ComponentType<{error?: Error; retry: () => void}>;
+}
+
+export default class ErrorBoundary extends React.Component {
+ constructor(props: ErrorBoundaryProps) {
+ super(props);
+ this.state = { hasError: false };
+ }
+
+ static getDerivedStateFromError(error: Error): ErrorBoundaryState {
+ return { hasError: true, error };
+ }
+
+ componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
+ console.error('ErrorBoundary caught an error:', error, errorInfo);
+ }
+
+ retry = () => {
+ this.setState({ hasError: false, error: undefined });
+ };
+
+ render() {
+ if (this.state.hasError) {
+ if (this.props.fallback) {
+ const Fallback = this.props.fallback;
+ return ;
+ }
+
+ return (
+
+ Something went wrong
+ {process.env.NODE_ENV === 'development' && this.state.error && (
+
+ {this.state.error.message}
+
+ )}
+
+
+ );
+ }
+
+ return this.props.children;
+ }
+}
\ No newline at end of file
diff --git a/frontend/src/components/tools/ToolPicker.tsx b/frontend/src/components/tools/ToolPicker.tsx
index 411ecdb7a..a8dbd7993 100644
--- a/frontend/src/components/tools/ToolPicker.tsx
+++ b/frontend/src/components/tools/ToolPicker.tsx
@@ -1,13 +1,11 @@
import React, { useMemo, useRef, useLayoutEffect, useState } from "react";
-import { Box, Text, Stack } from "@mantine/core";
+import { Box, Stack } from "@mantine/core";
import { useTranslation } from "react-i18next";
-import { getSubcategoryLabel, ToolRegistryEntry } from "../../data/toolsTaxonomy";
-import ToolButton from "./toolPicker/ToolButton";
+import { ToolRegistryEntry } from "../../data/toolsTaxonomy";
import "./toolPicker/ToolPicker.css";
-import { SubcategoryGroup, useToolSections } from "../../hooks/useToolSections";
-import SubcategoryHeader from "./shared/SubcategoryHeader";
+import { useToolSections } from "../../hooks/useToolSections";
import NoToolsFound from "./shared/NoToolsFound";
-import { TFunction } from "i18next";
+import { renderToolButtons } from "./shared/renderToolButtons";
interface ToolPickerProps {
selectedToolKey: string | null;
@@ -16,32 +14,6 @@ interface ToolPickerProps {
isSearching?: boolean;
}
-// Helper function to render tool buttons for a subcategory
-const renderToolButtons = (
- t: TFunction,
- subcategory: SubcategoryGroup,
- selectedToolKey: string | null,
- onSelect: (id: string) => void,
- showSubcategoryHeader: boolean = true
-) => (
-
- {showSubcategoryHeader && (
-
- )}
-
- {subcategory.tools.map(({ id, tool }: { id: string; tool: any }) => (
-
- ))}
-
-
-);
-
const ToolPicker = ({ selectedToolKey, onSelect, filteredTools, isSearching = false }: ToolPickerProps) => {
const { t } = useTranslation();
const [quickHeaderHeight, setQuickHeaderHeight] = useState(0);
diff --git a/frontend/src/components/tools/addWatermark/AddWatermarkSingleStepSettings.tsx b/frontend/src/components/tools/addWatermark/AddWatermarkSingleStepSettings.tsx
new file mode 100644
index 000000000..59ed48e95
--- /dev/null
+++ b/frontend/src/components/tools/addWatermark/AddWatermarkSingleStepSettings.tsx
@@ -0,0 +1,70 @@
+/**
+ * AddWatermarkSingleStepSettings - Used for automation only
+ *
+ * This component combines all watermark settings into a single step interface
+ * for use in the automation system. It includes type selection and all relevant
+ * settings in one unified component.
+ */
+
+import React from "react";
+import { Stack } from "@mantine/core";
+import { AddWatermarkParameters } from "../../../hooks/tools/addWatermark/useAddWatermarkParameters";
+import WatermarkTypeSettings from "./WatermarkTypeSettings";
+import WatermarkWording from "./WatermarkWording";
+import WatermarkTextStyle from "./WatermarkTextStyle";
+import WatermarkImageFile from "./WatermarkImageFile";
+import WatermarkFormatting from "./WatermarkFormatting";
+
+interface AddWatermarkSingleStepSettingsProps {
+ parameters: AddWatermarkParameters;
+ onParameterChange: (key: K, value: AddWatermarkParameters[K]) => void;
+ disabled?: boolean;
+}
+
+const AddWatermarkSingleStepSettings = ({ parameters, onParameterChange, disabled = false }: AddWatermarkSingleStepSettingsProps) => {
+ return (
+
+ {/* Watermark Type Selection */}
+ onParameterChange("watermarkType", type)}
+ disabled={disabled}
+ />
+
+ {/* Conditional settings based on watermark type */}
+ {parameters.watermarkType === "text" && (
+ <>
+
+
+ >
+ )}
+
+ {parameters.watermarkType === "image" && (
+
+ )}
+
+ {/* Formatting settings for both text and image */}
+ {parameters.watermarkType && (
+
+ )}
+
+ );
+};
+
+export default AddWatermarkSingleStepSettings;
\ No newline at end of file
diff --git a/frontend/src/components/tools/addWatermark/WatermarkFormatting.tsx b/frontend/src/components/tools/addWatermark/WatermarkFormatting.tsx
index 9a267f638..b6af3365c 100644
--- a/frontend/src/components/tools/addWatermark/WatermarkFormatting.tsx
+++ b/frontend/src/components/tools/addWatermark/WatermarkFormatting.tsx
@@ -6,7 +6,7 @@ import NumberInputWithUnit from "../shared/NumberInputWithUnit";
interface WatermarkFormattingProps {
parameters: AddWatermarkParameters;
- onParameterChange: (key: keyof AddWatermarkParameters, value: any) => void;
+ onParameterChange: (key: K, value: AddWatermarkParameters[K]) => void;
disabled?: boolean;
}
diff --git a/frontend/src/components/tools/addWatermark/WatermarkImageFile.tsx b/frontend/src/components/tools/addWatermark/WatermarkImageFile.tsx
index 6f38ae206..85e723ccb 100644
--- a/frontend/src/components/tools/addWatermark/WatermarkImageFile.tsx
+++ b/frontend/src/components/tools/addWatermark/WatermarkImageFile.tsx
@@ -6,7 +6,7 @@ import FileUploadButton from "../../shared/FileUploadButton";
interface WatermarkImageFileProps {
parameters: AddWatermarkParameters;
- onParameterChange: (key: keyof AddWatermarkParameters, value: any) => void;
+ onParameterChange: (key: K, value: AddWatermarkParameters[K]) => void;
disabled?: boolean;
}
@@ -17,7 +17,7 @@ const WatermarkImageFile = ({ parameters, onParameterChange, disabled = false }:
onParameterChange('watermarkImage', file)}
+ onChange={(file) => onParameterChange('watermarkImage', file || undefined)}
accept="image/*"
disabled={disabled}
placeholder={t('watermark.settings.image.choose', 'Choose Image')}
diff --git a/frontend/src/components/tools/addWatermark/WatermarkStyleSettings.tsx b/frontend/src/components/tools/addWatermark/WatermarkStyleSettings.tsx
index 2de9335b0..f3c6751cf 100644
--- a/frontend/src/components/tools/addWatermark/WatermarkStyleSettings.tsx
+++ b/frontend/src/components/tools/addWatermark/WatermarkStyleSettings.tsx
@@ -5,7 +5,7 @@ import { AddWatermarkParameters } from "../../../hooks/tools/addWatermark/useAdd
interface WatermarkStyleSettingsProps {
parameters: AddWatermarkParameters;
- onParameterChange: (key: keyof AddWatermarkParameters, value: any) => void;
+ onParameterChange: (key: K, value: AddWatermarkParameters[K]) => void;
disabled?: boolean;
}
@@ -19,7 +19,7 @@ const WatermarkStyleSettings = ({ parameters, onParameterChange, disabled = fals
{t('watermark.settings.rotation', 'Rotation (degrees)')}
onParameterChange('rotation', value || 0)}
+ onChange={(value) => onParameterChange('rotation', typeof value === 'number' ? value : (parseInt(value as string, 10) || 0))}
min={-360}
max={360}
disabled={disabled}
@@ -28,7 +28,7 @@ const WatermarkStyleSettings = ({ parameters, onParameterChange, disabled = fals
{t('watermark.settings.opacity', 'Opacity (%)')}
onParameterChange('opacity', value || 50)}
+ onChange={(value) => onParameterChange('opacity', typeof value === 'number' ? value : (parseInt(value as string, 10) || 50))}
min={0}
max={100}
disabled={disabled}
@@ -40,7 +40,7 @@ const WatermarkStyleSettings = ({ parameters, onParameterChange, disabled = fals
{t('watermark.settings.spacing.width', 'Width Spacing')}
onParameterChange('widthSpacer', value || 50)}
+ onChange={(value) => onParameterChange('widthSpacer', typeof value === 'number' ? value : (parseInt(value as string, 10) || 50))}
min={0}
max={200}
disabled={disabled}
@@ -49,7 +49,7 @@ const WatermarkStyleSettings = ({ parameters, onParameterChange, disabled = fals
{t('watermark.settings.spacing.height', 'Height Spacing')}
onParameterChange('heightSpacer', value || 50)}
+ onChange={(value) => onParameterChange('heightSpacer', typeof value === 'number' ? value : (parseInt(value as string, 10) || 50))}
min={0}
max={200}
disabled={disabled}
diff --git a/frontend/src/components/tools/addWatermark/WatermarkTextStyle.tsx b/frontend/src/components/tools/addWatermark/WatermarkTextStyle.tsx
index 00fd21d09..91217f76b 100644
--- a/frontend/src/components/tools/addWatermark/WatermarkTextStyle.tsx
+++ b/frontend/src/components/tools/addWatermark/WatermarkTextStyle.tsx
@@ -6,7 +6,7 @@ import { alphabetOptions } from "../../../constants/addWatermarkConstants";
interface WatermarkTextStyleProps {
parameters: AddWatermarkParameters;
- onParameterChange: (key: keyof AddWatermarkParameters, value: any) => void;
+ onParameterChange: (key: K, value: AddWatermarkParameters[K]) => void;
disabled?: boolean;
}
diff --git a/frontend/src/components/tools/addWatermark/WatermarkWording.tsx b/frontend/src/components/tools/addWatermark/WatermarkWording.tsx
index 621a0f399..5278ca332 100644
--- a/frontend/src/components/tools/addWatermark/WatermarkWording.tsx
+++ b/frontend/src/components/tools/addWatermark/WatermarkWording.tsx
@@ -6,7 +6,7 @@ import { removeEmojis } from "../../../utils/textUtils";
interface WatermarkWordingProps {
parameters: AddWatermarkParameters;
- onParameterChange: (key: keyof AddWatermarkParameters, value: any) => void;
+ onParameterChange: (key: K, value: AddWatermarkParameters[K]) => void;
disabled?: boolean;
}
diff --git a/frontend/src/components/tools/automate/AutomationCreation.tsx b/frontend/src/components/tools/automate/AutomationCreation.tsx
new file mode 100644
index 000000000..49b12c396
--- /dev/null
+++ b/frontend/src/components/tools/automate/AutomationCreation.tsx
@@ -0,0 +1,199 @@
+import React, { useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import {
+ Button,
+ Text,
+ Stack,
+ Group,
+ TextInput,
+ Divider,
+ Modal
+} from '@mantine/core';
+import CheckIcon from '@mui/icons-material/Check';
+import { ToolRegistryEntry } from '../../../data/toolsTaxonomy';
+import ToolConfigurationModal from './ToolConfigurationModal';
+import ToolList from './ToolList';
+import { AutomationConfig, AutomationMode, AutomationTool } from '../../../types/automation';
+import { useAutomationForm } from '../../../hooks/tools/automate/useAutomationForm';
+
+
+interface AutomationCreationProps {
+ mode: AutomationMode;
+ existingAutomation?: AutomationConfig;
+ onBack: () => void;
+ onComplete: (automation: AutomationConfig) => void;
+ toolRegistry: Record;
+}
+
+export default function AutomationCreation({ mode, existingAutomation, onBack, onComplete, toolRegistry }: AutomationCreationProps) {
+ const { t } = useTranslation();
+
+ const {
+ automationName,
+ setAutomationName,
+ selectedTools,
+ addTool,
+ removeTool,
+ updateTool,
+ hasUnsavedChanges,
+ canSaveAutomation,
+ getToolName,
+ getToolDefaultParameters
+ } = useAutomationForm({ mode, existingAutomation, toolRegistry });
+
+ const [configModalOpen, setConfigModalOpen] = useState(false);
+ const [configuraingToolIndex, setConfiguringToolIndex] = useState(-1);
+ const [unsavedWarningOpen, setUnsavedWarningOpen] = useState(false);
+
+
+ const configureTool = (index: number) => {
+ setConfiguringToolIndex(index);
+ setConfigModalOpen(true);
+ };
+
+ const handleToolConfigSave = (parameters: Record) => {
+ if (configuraingToolIndex >= 0) {
+ updateTool(configuraingToolIndex, {
+ configured: true,
+ parameters
+ });
+ }
+ setConfigModalOpen(false);
+ setConfiguringToolIndex(-1);
+ };
+
+ const handleToolConfigCancel = () => {
+ setConfigModalOpen(false);
+ setConfiguringToolIndex(-1);
+ };
+
+ const handleToolAdd = () => {
+ const newTool: AutomationTool = {
+ id: `tool-${Date.now()}`,
+ operation: '',
+ name: t('automate.creation.tools.selectTool', 'Select a tool...'),
+ configured: false,
+ parameters: {}
+ };
+ updateTool(selectedTools.length, newTool);
+ };
+
+ const handleBackClick = () => {
+ if (hasUnsavedChanges()) {
+ setUnsavedWarningOpen(true);
+ } else {
+ onBack();
+ }
+ };
+
+ const handleConfirmBack = () => {
+ setUnsavedWarningOpen(false);
+ onBack();
+ };
+
+ const handleCancelBack = () => {
+ setUnsavedWarningOpen(false);
+ };
+
+ const saveAutomation = async () => {
+ if (!canSaveAutomation()) return;
+
+ const automation = {
+ name: automationName.trim(),
+ description: '',
+ operations: selectedTools.map(tool => ({
+ operation: tool.operation,
+ parameters: tool.parameters || {}
+ }))
+ };
+
+ try {
+ const { automationStorage } = await import('../../../services/automationStorage');
+ const savedAutomation = await automationStorage.saveAutomation(automation);
+ onComplete(savedAutomation);
+ } catch (error) {
+ console.error('Error saving automation:', error);
+ }
+ };
+
+ const currentConfigTool = configuraingToolIndex >= 0 ? selectedTools[configuraingToolIndex] : null;
+
+ return (
+
+
+ {t("automate.creation.description", "Automations run tools sequentially. To get started, add tools in the order you want them to run.")}
+
+
+
+
+ {/* Automation Name */}
+ setAutomationName(e.currentTarget.value)}
+ size="sm"
+ />
+
+
+ {/* Selected Tools List */}
+ {selectedTools.length > 0 && (
+
+ )}
+
+
+
+ {/* Save Button */}
+ }
+ onClick={saveAutomation}
+ disabled={!canSaveAutomation()}
+ fullWidth
+ >
+ {t('automate.creation.save', 'Save Automation')}
+
+
+
+ {/* Tool Configuration Modal */}
+ {currentConfigTool && (
+
+ )}
+
+ {/* Unsaved Changes Warning Modal */}
+
+
+
+ {t('automate.creation.unsavedChanges.message', 'You have unsaved changes. Are you sure you want to go back? All changes will be lost.')}
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/frontend/src/components/tools/automate/AutomationEntry.tsx b/frontend/src/components/tools/automate/AutomationEntry.tsx
new file mode 100644
index 000000000..3314831be
--- /dev/null
+++ b/frontend/src/components/tools/automate/AutomationEntry.tsx
@@ -0,0 +1,163 @@
+import React, { useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import { Button, Group, Text, ActionIcon, Menu, Box } from '@mantine/core';
+import MoreVertIcon from '@mui/icons-material/MoreVert';
+import EditIcon from '@mui/icons-material/Edit';
+import DeleteIcon from '@mui/icons-material/Delete';
+
+interface AutomationEntryProps {
+ /** Optional title for the automation (usually for custom ones) */
+ title?: string;
+ /** MUI Icon component for the badge */
+ badgeIcon?: React.ComponentType;
+ /** Array of tool operation names in the workflow */
+ operations: string[];
+ /** Click handler */
+ onClick: () => void;
+ /** Whether to keep the icon at normal color (for special cases like "Add New") */
+ keepIconColor?: boolean;
+ /** Show menu for saved/suggested automations */
+ showMenu?: boolean;
+ /** Edit handler */
+ onEdit?: () => void;
+ /** Delete handler */
+ onDelete?: () => void;
+}
+
+export default function AutomationEntry({
+ title,
+ badgeIcon: BadgeIcon,
+ operations,
+ onClick,
+ keepIconColor = false,
+ showMenu = false,
+ onEdit,
+ onDelete
+}: AutomationEntryProps) {
+ const { t } = useTranslation();
+ const [isHovered, setIsHovered] = useState(false);
+ const [isMenuOpen, setIsMenuOpen] = useState(false);
+
+ // Keep item in hovered state if menu is open
+ const shouldShowHovered = isHovered || isMenuOpen;
+
+ const renderContent = () => {
+ if (title) {
+ // Custom automation with title
+ return (
+
+ {BadgeIcon && (
+
+ )}
+
+ {title}
+
+
+ );
+ } else {
+ // Suggested automation showing tool chain
+ return (
+
+ {BadgeIcon && (
+
+ )}
+
+ {operations.map((op, index) => (
+
+
+ {t(`${op}.title`, op)}
+
+
+ {index < operations.length - 1 && (
+
+ →
+
+ )}
+
+ ))}
+
+
+ );
+ }
+ };
+
+ return (
+ setIsHovered(true)}
+ onMouseLeave={() => setIsHovered(false)}
+ >
+
+
+ {renderContent()}
+
+
+ {showMenu && (
+
+ )}
+
+
+ );
+}
diff --git a/frontend/src/components/tools/automate/AutomationRun.tsx b/frontend/src/components/tools/automate/AutomationRun.tsx
new file mode 100644
index 000000000..640f802f6
--- /dev/null
+++ b/frontend/src/components/tools/automate/AutomationRun.tsx
@@ -0,0 +1,223 @@
+import React, { useState } from "react";
+import { useTranslation } from "react-i18next";
+import { Button, Text, Stack, Group, Card, Progress } from "@mantine/core";
+import PlayArrowIcon from "@mui/icons-material/PlayArrow";
+import CheckIcon from "@mui/icons-material/Check";
+import { useFileSelection } from "../../../contexts/FileContext";
+import { useFlatToolRegistry } from "../../../data/useTranslatedToolRegistry";
+import { AutomationConfig, ExecutionStep } from "../../../types/automation";
+import { AUTOMATION_CONSTANTS, EXECUTION_STATUS } from "../../../constants/automation";
+import { useResourceCleanup } from "../../../utils/resourceManager";
+
+interface AutomationRunProps {
+ automation: AutomationConfig;
+ onComplete: () => void;
+ automateOperation?: any; // TODO: Type this properly when available
+}
+
+export default function AutomationRun({ automation, onComplete, automateOperation }: AutomationRunProps) {
+ const { t } = useTranslation();
+ const { selectedFiles } = useFileSelection();
+ const toolRegistry = useFlatToolRegistry();
+ const cleanup = useResourceCleanup();
+
+ // Progress tracking state
+ const [executionSteps, setExecutionSteps] = useState([]);
+ const [currentStepIndex, setCurrentStepIndex] = useState(-1);
+
+ // Use the operation hook's loading state
+ const isExecuting = automateOperation?.isLoading || false;
+ const hasResults = automateOperation?.files.length > 0 || automateOperation?.downloadUrl !== null;
+
+ // Initialize execution steps from automation
+ React.useEffect(() => {
+ if (automation?.operations) {
+ const steps = automation.operations.map((op: any, index: number) => {
+ const tool = toolRegistry[op.operation];
+ return {
+ id: `${op.operation}-${index}`,
+ operation: op.operation,
+ name: tool?.name || op.operation,
+ status: EXECUTION_STATUS.PENDING
+ };
+ });
+ setExecutionSteps(steps);
+ setCurrentStepIndex(-1);
+ }
+ }, [automation, toolRegistry]);
+
+ // Cleanup when component unmounts
+ React.useEffect(() => {
+ return () => {
+ // Reset progress state when component unmounts
+ setExecutionSteps([]);
+ setCurrentStepIndex(-1);
+ // Clean up any blob URLs
+ cleanup();
+ };
+ }, [cleanup]);
+
+ const executeAutomation = async () => {
+ if (!selectedFiles || selectedFiles.length === 0) {
+ return;
+ }
+
+ if (!automateOperation) {
+ console.error('No automateOperation provided');
+ return;
+ }
+
+ // Reset progress tracking
+ setCurrentStepIndex(0);
+ setExecutionSteps(prev => prev.map(step => ({ ...step, status: EXECUTION_STATUS.PENDING, error: undefined })));
+
+ try {
+ // Use the automateOperation.executeOperation to handle file consumption properly
+ await automateOperation.executeOperation(
+ {
+ automationConfig: automation,
+ onStepStart: (stepIndex: number, operationName: string) => {
+ setCurrentStepIndex(stepIndex);
+ setExecutionSteps(prev => prev.map((step, idx) =>
+ idx === stepIndex ? { ...step, status: EXECUTION_STATUS.RUNNING } : step
+ ));
+ },
+ onStepComplete: (stepIndex: number, resultFiles: File[]) => {
+ setExecutionSteps(prev => prev.map((step, idx) =>
+ idx === stepIndex ? { ...step, status: EXECUTION_STATUS.COMPLETED } : step
+ ));
+ },
+ onStepError: (stepIndex: number, error: string) => {
+ setExecutionSteps(prev => prev.map((step, idx) =>
+ idx === stepIndex ? { ...step, status: EXECUTION_STATUS.ERROR, error } : step
+ ));
+ }
+ },
+ selectedFiles
+ );
+
+ // Mark all as completed and reset current step
+ setCurrentStepIndex(-1);
+ console.log(`✅ Automation completed successfully`);
+ } catch (error: any) {
+ console.error("Automation execution failed:", error);
+ setCurrentStepIndex(-1);
+ }
+ };
+
+ const getProgress = () => {
+ if (executionSteps.length === 0) return 0;
+ const completedSteps = executionSteps.filter(step => step.status === EXECUTION_STATUS.COMPLETED).length;
+ return (completedSteps / executionSteps.length) * 100;
+ };
+
+ const getStepIcon = (step: ExecutionStep) => {
+ switch (step.status) {
+ case EXECUTION_STATUS.COMPLETED:
+ return ;
+ case EXECUTION_STATUS.ERROR:
+ return ✕;
+ case EXECUTION_STATUS.RUNNING:
+ return ;
+ default:
+ return ;
+ }
+ };
+
+ return (
+
+
+ {/* Automation Info */}
+
+
+ {automation?.name || t("automate.sequence.unnamed", "Unnamed Automation")}
+
+
+ {t("automate.sequence.steps", "{{count}} steps", { count: executionSteps.length })}
+
+
+
+ {/* Progress Bar */}
+ {isExecuting && (
+
+
+ Progress: {currentStepIndex + 1}/{executionSteps.length}
+
+
+
+ )}
+
+ {/* Execution Steps */}
+
+ {executionSteps.map((step, index) => (
+
+
+ {index + 1}
+
+
+ {getStepIcon(step)}
+
+
+
+ {step.name}
+
+ {step.error && (
+
+ {step.error}
+
+ )}
+
+
+ ))}
+
+
+ {/* Action Buttons */}
+
+ }
+ onClick={executeAutomation}
+ disabled={isExecuting || !selectedFiles || selectedFiles.length === 0}
+ loading={isExecuting}
+ >
+ {isExecuting
+ ? t("automate.sequence.running", "Running Automation...")
+ : t("automate.sequence.run", "Run Automation")}
+
+
+ {hasResults && (
+
+ )}
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/frontend/src/components/tools/automate/AutomationSelection.tsx b/frontend/src/components/tools/automate/AutomationSelection.tsx
new file mode 100644
index 000000000..f55cf4c5d
--- /dev/null
+++ b/frontend/src/components/tools/automate/AutomationSelection.tsx
@@ -0,0 +1,76 @@
+import React from "react";
+import { useTranslation } from "react-i18next";
+import { Title, Stack, Divider } from "@mantine/core";
+import AddCircleOutline from "@mui/icons-material/AddCircleOutline";
+import SettingsIcon from "@mui/icons-material/Settings";
+import AutomationEntry from "./AutomationEntry";
+import { useSuggestedAutomations } from "../../../hooks/tools/automate/useSuggestedAutomations";
+import { AutomationConfig } from "../../../types/automation";
+
+interface AutomationSelectionProps {
+ savedAutomations: AutomationConfig[];
+ onCreateNew: () => void;
+ onRun: (automation: AutomationConfig) => void;
+ onEdit: (automation: AutomationConfig) => void;
+ onDelete: (automation: AutomationConfig) => void;
+}
+
+export default function AutomationSelection({
+ savedAutomations,
+ onCreateNew,
+ onRun,
+ onEdit,
+ onDelete
+}: AutomationSelectionProps) {
+ const { t } = useTranslation();
+ const suggestedAutomations = useSuggestedAutomations();
+
+ return (
+
+
+ {t("automate.selection.saved.title", "Saved")}
+
+
+
+
+ {/* Saved Automations */}
+ {savedAutomations.map((automation) => (
+ typeof op === 'string' ? op : op.operation)}
+ onClick={() => onRun(automation)}
+ showMenu={true}
+ onEdit={() => onEdit(automation)}
+ onDelete={() => onDelete(automation)}
+ />
+ ))}
+
+
+ {/* Suggested Automations */}
+
+
+ {t("automate.selection.suggested.title", "Suggested")}
+
+
+ {suggestedAutomations.map((automation) => (
+ op.operation)}
+ onClick={() => onRun(automation)}
+ />
+ ))}
+
+
+
+
+ );
+}
diff --git a/frontend/src/components/tools/automate/ToolConfigurationModal.tsx b/frontend/src/components/tools/automate/ToolConfigurationModal.tsx
new file mode 100644
index 000000000..d97819fb8
--- /dev/null
+++ b/frontend/src/components/tools/automate/ToolConfigurationModal.tsx
@@ -0,0 +1,138 @@
+import React, { useState, useEffect, useMemo } from 'react';
+import { useTranslation } from 'react-i18next';
+import {
+ Modal,
+ Title,
+ Button,
+ Group,
+ Stack,
+ Text,
+ Alert
+} from '@mantine/core';
+import SettingsIcon from '@mui/icons-material/Settings';
+import CheckIcon from '@mui/icons-material/Check';
+import CloseIcon from '@mui/icons-material/Close';
+import WarningIcon from '@mui/icons-material/Warning';
+import { ToolRegistry } from '../../../data/toolsTaxonomy';
+import { getAvailableToExtensions } from '../../../utils/convertUtils';
+interface ToolConfigurationModalProps {
+ opened: boolean;
+ tool: {
+ id: string;
+ operation: string;
+ name: string;
+ parameters?: any;
+ };
+ onSave: (parameters: any) => void;
+ onCancel: () => void;
+ toolRegistry: ToolRegistry;
+}
+
+export default function ToolConfigurationModal({ opened, tool, onSave, onCancel, toolRegistry }: ToolConfigurationModalProps) {
+ const { t } = useTranslation();
+
+ const [parameters, setParameters] = useState({});
+ const [isValid, setIsValid] = useState(true);
+
+ // Get tool info from registry
+ const toolInfo = toolRegistry[tool.operation];
+ const SettingsComponent = toolInfo?.settingsComponent;
+
+ // Initialize parameters from tool (which should contain defaults from registry)
+ useEffect(() => {
+ if (tool.parameters) {
+ setParameters(tool.parameters);
+ } else {
+ // Fallback to empty parameters if none provided
+ setParameters({});
+ }
+ }, [tool.parameters, tool.operation]);
+
+ // Render the settings component
+ const renderToolSettings = () => {
+ if (!SettingsComponent) {
+ return (
+ } color="orange">
+
+ {t('automate.config.noSettings', 'This tool does not have configurable settings.')}
+
+
+ );
+ }
+
+ // Special handling for ConvertSettings which needs additional props
+ if (tool.operation === 'convert') {
+ return (
+ {
+ setParameters((prev: any) => ({ ...prev, [key]: value }));
+ }}
+ getAvailableToExtensions={getAvailableToExtensions}
+ selectedFiles={[]}
+ disabled={false}
+ />
+ );
+ }
+
+ return (
+ {
+ setParameters((prev: any) => ({ ...prev, [key]: value }));
+ }}
+ disabled={false}
+ />
+ );
+ };
+
+ const handleSave = () => {
+ if (isValid) {
+ onSave(parameters);
+ }
+ };
+
+ return (
+
+
+
+ {t('automate.config.title', 'Configure {{toolName}}', { toolName: tool.name })}
+
+
+ }
+ size="lg"
+ centered
+ >
+
+
+ {t('automate.config.description', 'Configure the settings for this tool. These settings will be applied when the automation runs.')}
+
+
+
+ {renderToolSettings()}
+
+
+
+ }
+ onClick={onCancel}
+ >
+ {t('automate.config.cancel', 'Cancel')}
+
+ }
+ onClick={handleSave}
+ disabled={!isValid}
+ >
+ {t('automate.config.save', 'Save Configuration')}
+
+
+
+
+ );
+}
diff --git a/frontend/src/components/tools/automate/ToolList.tsx b/frontend/src/components/tools/automate/ToolList.tsx
new file mode 100644
index 000000000..8b24b5c17
--- /dev/null
+++ b/frontend/src/components/tools/automate/ToolList.tsx
@@ -0,0 +1,149 @@
+import React from 'react';
+import { useTranslation } from 'react-i18next';
+import { Text, Stack, Group, ActionIcon } from '@mantine/core';
+import DeleteIcon from '@mui/icons-material/Delete';
+import SettingsIcon from '@mui/icons-material/Settings';
+import CloseIcon from '@mui/icons-material/Close';
+import AddCircleOutline from '@mui/icons-material/AddCircleOutline';
+import { AutomationTool } from '../../../types/automation';
+import { ToolRegistryEntry } from '../../../data/toolsTaxonomy';
+import ToolSelector from './ToolSelector';
+import AutomationEntry from './AutomationEntry';
+
+interface ToolListProps {
+ tools: AutomationTool[];
+ toolRegistry: Record;
+ onToolUpdate: (index: number, updates: Partial) => void;
+ onToolRemove: (index: number) => void;
+ onToolConfigure: (index: number) => void;
+ onToolAdd: () => void;
+ getToolName: (operation: string) => string;
+ getToolDefaultParameters: (operation: string) => Record;
+}
+
+export default function ToolList({
+ tools,
+ toolRegistry,
+ onToolUpdate,
+ onToolRemove,
+ onToolConfigure,
+ onToolAdd,
+ getToolName,
+ getToolDefaultParameters
+}: ToolListProps) {
+ const { t } = useTranslation();
+
+ const handleToolSelect = (index: number, newOperation: string) => {
+ const defaultParams = getToolDefaultParameters(newOperation);
+
+ onToolUpdate(index, {
+ operation: newOperation,
+ name: getToolName(newOperation),
+ configured: false,
+ parameters: defaultParams
+ });
+ };
+
+ return (
+
+
+ {t('automate.creation.tools.selected', 'Selected Tools')} ({tools.length})
+
+
+ {tools.map((tool, index) => (
+
+
+ {/* Delete X in top right */}
+
onToolRemove(index)}
+ title={t('automate.creation.tools.remove', 'Remove tool')}
+ style={{
+ position: 'absolute',
+ top: '4px',
+ right: '4px',
+ zIndex: 1,
+ color: 'var(--mantine-color-gray-6)'
+ }}
+ >
+
+
+
+
+ {/* Tool Selection Dropdown with inline settings cog */}
+
+
+ handleToolSelect(index, newOperation)}
+ excludeTools={['automate']}
+ toolRegistry={toolRegistry}
+ selectedValue={tool.operation}
+ placeholder={tool.name}
+ />
+
+
+ {/* Settings cog - only show if tool is selected, aligned right */}
+ {tool.operation && (
+ onToolConfigure(index)}
+ title={t('automate.creation.tools.configure', 'Configure tool')}
+ style={{ color: 'var(--mantine-color-gray-6)' }}
+ >
+
+
+ )}
+
+
+ {/* Configuration status underneath */}
+ {tool.operation && !tool.configured && (
+
+ {t('automate.creation.tools.notConfigured', "! Not Configured")}
+
+ )}
+
+
+
+ {index < tools.length - 1 && (
+
+ ↓
+
+ )}
+
+ ))}
+
+ {/* Arrow before Add Tool Button */}
+ {tools.length > 0 && (
+
+ ↓
+
+ )}
+
+ {/* Add Tool Button */}
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/frontend/src/components/tools/automate/ToolSelector.tsx b/frontend/src/components/tools/automate/ToolSelector.tsx
new file mode 100644
index 000000000..80b68b0a4
--- /dev/null
+++ b/frontend/src/components/tools/automate/ToolSelector.tsx
@@ -0,0 +1,182 @@
+import React, { useState, useMemo, useCallback } from 'react';
+import { useTranslation } from 'react-i18next';
+import { Menu, Stack, Text, ScrollArea } from '@mantine/core';
+import { ToolRegistryEntry } from '../../../data/toolsTaxonomy';
+import { useToolSections } from '../../../hooks/useToolSections';
+import { renderToolButtons } from '../shared/renderToolButtons';
+import ToolSearch from '../toolPicker/ToolSearch';
+
+interface ToolSelectorProps {
+ onSelect: (toolKey: string) => void;
+ excludeTools?: string[];
+ toolRegistry: Record; // Pass registry as prop to break circular dependency
+ selectedValue?: string; // For showing current selection when editing existing tool
+ placeholder?: string; // Custom placeholder text
+}
+
+export default function ToolSelector({
+ onSelect,
+ excludeTools = [],
+ toolRegistry,
+ selectedValue,
+ placeholder
+}: ToolSelectorProps) {
+ const { t } = useTranslation();
+ const [opened, setOpened] = useState(false);
+ const [searchTerm, setSearchTerm] = useState('');
+
+ // Filter out excluded tools (like 'automate' itself)
+ const baseFilteredTools = useMemo(() => {
+ return Object.entries(toolRegistry).filter(([key]) => !excludeTools.includes(key));
+ }, [toolRegistry, excludeTools]);
+
+ // Apply search filter
+ const filteredTools = useMemo(() => {
+ if (!searchTerm.trim()) {
+ return baseFilteredTools;
+ }
+
+ const lowercaseSearch = searchTerm.toLowerCase();
+ return baseFilteredTools.filter(([key, tool]) => {
+ return (
+ tool.name.toLowerCase().includes(lowercaseSearch) ||
+ tool.description?.toLowerCase().includes(lowercaseSearch) ||
+ key.toLowerCase().includes(lowercaseSearch)
+ );
+ });
+ }, [baseFilteredTools, searchTerm]);
+
+ // Create filtered tool registry for ToolSearch
+ const filteredToolRegistry = useMemo(() => {
+ const registry: Record = {};
+ baseFilteredTools.forEach(([key, tool]) => {
+ registry[key] = tool;
+ });
+ return registry;
+ }, [baseFilteredTools]);
+
+ // Use the same tool sections logic as the main ToolPicker
+ const { sections, searchGroups } = useToolSections(filteredTools);
+
+ // Determine what to display: search results or organized sections
+ const isSearching = searchTerm.trim().length > 0;
+ const displayGroups = useMemo(() => {
+ if (isSearching) {
+ return searchGroups || [];
+ }
+
+ if (!sections || sections.length === 0) {
+ return [];
+ }
+
+ // Find the "all" section which contains all tools without duplicates
+ const allSection = sections.find(s => (s as any).key === 'all');
+ return allSection?.subcategories || [];
+ }, [isSearching, searchGroups, sections]);
+
+ const handleToolSelect = useCallback((toolKey: string) => {
+ onSelect(toolKey);
+ setOpened(false);
+ setSearchTerm(''); // Clear search to show the selected tool display
+ }, [onSelect]);
+
+ const renderedTools = useMemo(() =>
+ displayGroups.map((subcategory) =>
+ renderToolButtons(t, subcategory, null, handleToolSelect, !isSearching)
+ ), [displayGroups, handleToolSelect, isSearching, t]
+ );
+
+ const handleSearchFocus = () => {
+ setOpened(true);
+ };
+
+ const handleSearchChange = (value: string) => {
+ setSearchTerm(value);
+ if (!opened) {
+ setOpened(true);
+ }
+ };
+
+ // Get display value for selected tool
+ const getDisplayValue = () => {
+ if (selectedValue && toolRegistry[selectedValue]) {
+ return toolRegistry[selectedValue].name;
+ }
+ return placeholder || t('automate.creation.tools.add', 'Add a tool...');
+ };
+
+ return (
+
+
+
+ );
+}
diff --git a/frontend/src/components/tools/shared/SuggestedToolsSection.tsx b/frontend/src/components/tools/shared/SuggestedToolsSection.tsx
index da0418571..fca3b5e56 100644
--- a/frontend/src/components/tools/shared/SuggestedToolsSection.tsx
+++ b/frontend/src/components/tools/shared/SuggestedToolsSection.tsx
@@ -2,6 +2,7 @@ import React from 'react';
import { Stack, Text, Divider, Card, Group } from '@mantine/core';
import { useTranslation } from 'react-i18next';
import { useSuggestedTools } from '../../../hooks/useSuggestedTools';
+
export interface SuggestedToolsSectionProps {}
export function SuggestedToolsSection(): React.ReactElement {
diff --git a/frontend/src/components/tools/shared/createToolFlow.tsx b/frontend/src/components/tools/shared/createToolFlow.tsx
index 3cb46ed60..ff6d2a6be 100644
--- a/frontend/src/components/tools/shared/createToolFlow.tsx
+++ b/frontend/src/components/tools/shared/createToolFlow.tsx
@@ -9,6 +9,7 @@ export interface FilesStepConfig {
isCollapsed?: boolean;
placeholder?: string;
onCollapsedClick?: () => void;
+ isVisible?: boolean;
}
export interface MiddleStepConfig {
@@ -63,7 +64,7 @@ export function createToolFlow(config: ToolFlowConfig) {
{/* Files Step */}
- {steps.createFilesStep({
+ {config.files.isVisible !== false && steps.createFilesStep({
selectedFiles: config.files.selectedFiles,
isCollapsed: config.files.isCollapsed,
placeholder: config.files.placeholder,
diff --git a/frontend/src/components/tools/shared/renderToolButtons.tsx b/frontend/src/components/tools/shared/renderToolButtons.tsx
new file mode 100644
index 000000000..eb9c9be6d
--- /dev/null
+++ b/frontend/src/components/tools/shared/renderToolButtons.tsx
@@ -0,0 +1,34 @@
+import React from 'react';
+import { Box, Stack } from '@mantine/core';
+import ToolButton from '../toolPicker/ToolButton';
+import SubcategoryHeader from './SubcategoryHeader';
+
+import { getSubcategoryLabel } from "../../../data/toolsTaxonomy";
+import { TFunction } from 'i18next';
+import { SubcategoryGroup } from '../../../hooks/useToolSections';
+
+// Helper function to render tool buttons for a subcategory
+export const renderToolButtons = (
+ t: TFunction,
+ subcategory: SubcategoryGroup,
+ selectedToolKey: string | null,
+ onSelect: (id: string) => void,
+ showSubcategoryHeader: boolean = true
+) => (
+
+ {showSubcategoryHeader && (
+
+ )}
+
+ {subcategory.tools.map(({ id, tool }) => (
+
+ ))}
+
+
+);
diff --git a/frontend/src/components/tools/toolPicker/ToolSearch.tsx b/frontend/src/components/tools/toolPicker/ToolSearch.tsx
index f01a9f87d..c17784a52 100644
--- a/frontend/src/components/tools/toolPicker/ToolSearch.tsx
+++ b/frontend/src/components/tools/toolPicker/ToolSearch.tsx
@@ -12,19 +12,26 @@ interface ToolSearchProps {
onToolSelect?: (toolId: string) => void;
mode: 'filter' | 'dropdown';
selectedToolKey?: string | null;
+ placeholder?: string;
+ hideIcon?: boolean;
+ onFocus?: () => void;
}
-const ToolSearch = ({
- value,
- onChange,
- toolRegistry,
- onToolSelect,
+const ToolSearch = ({
+ value,
+ onChange,
+ toolRegistry,
+ onToolSelect,
mode = 'filter',
- selectedToolKey
+ selectedToolKey,
+ placeholder,
+ hideIcon = false,
+ onFocus
}: ToolSearchProps) => {
const { t } = useTranslation();
const [dropdownOpen, setDropdownOpen] = useState(false);
const searchRef = useRef(null);
+ const dropdownRef = useRef(null);
const filteredTools = useMemo(() => {
if (!value.trim()) return [];
@@ -47,7 +54,12 @@ const ToolSearch = ({
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
- if (searchRef.current && !searchRef.current.contains(event.target as Node)) {
+ if (
+ searchRef.current &&
+ dropdownRef.current &&
+ !searchRef.current.contains(event.target as Node) &&
+ !dropdownRef.current.contains(event.target as Node)
+ ) {
setDropdownOpen(false);
}
};
@@ -61,9 +73,10 @@ const ToolSearch = ({
ref={searchRef}
value={value}
onChange={handleSearchChange}
- placeholder={t("toolPicker.searchPlaceholder", "Search tools...")}
- icon={search}
+ placeholder={placeholder || t("toolPicker.searchPlaceholder", "Search tools...")}
+ icon={hideIcon ? undefined : search}
autoComplete="off"
+
/>
);
@@ -77,19 +90,19 @@ const ToolSearch = ({
{searchInput}
{dropdownOpen && filteredTools.length > 0 && (
@@ -97,7 +110,10 @@ const ToolSearch = ({