From 23d86deae71e0e5cbf8c0af1bab5e5d54e30a7f8 Mon Sep 17 00:00:00 2001 From: ConnorYoh <40631091+ConnorYoh@users.noreply.github.com> Date: Fri, 22 Aug 2025 14:40:27 +0100 Subject: [PATCH] Feature/v2/automate (#4248) * automate feature * Moved all providers to app level to simplify homepage * Circular dependency fixes * You will see that now toolRegistry gets a tool config and a tool settings object. These enable automate to run the tools using as much static code as possible. --------- Co-authored-by: Connor Yoh --- frontend/package-lock.json | 1431 ++++++++++++++++- frontend/package.json | 1 + .../public/locales/en-GB/translation.json | 80 +- frontend/src/App.tsx | 57 +- frontend/src/components/FileManager.tsx | 4 +- .../src/components/shared/ErrorBoundary.tsx | 56 + frontend/src/components/tools/ToolPicker.tsx | 36 +- .../AddWatermarkSingleStepSettings.tsx | 70 + .../addWatermark/WatermarkFormatting.tsx | 2 +- .../tools/addWatermark/WatermarkImageFile.tsx | 4 +- .../addWatermark/WatermarkStyleSettings.tsx | 10 +- .../tools/addWatermark/WatermarkTextStyle.tsx | 2 +- .../tools/addWatermark/WatermarkWording.tsx | 2 +- .../tools/automate/AutomationCreation.tsx | 199 +++ .../tools/automate/AutomationEntry.tsx | 163 ++ .../tools/automate/AutomationRun.tsx | 223 +++ .../tools/automate/AutomationSelection.tsx | 76 + .../tools/automate/ToolConfigurationModal.tsx | 138 ++ .../components/tools/automate/ToolList.tsx | 149 ++ .../tools/automate/ToolSelector.tsx | 182 +++ .../tools/shared/SuggestedToolsSection.tsx | 1 + .../tools/shared/createToolFlow.tsx | 3 +- .../tools/shared/renderToolButtons.tsx | 34 + .../tools/toolPicker/ToolSearch.tsx | 50 +- frontend/src/constants/automation.ts | 42 + frontend/src/contexts/NavigationContext.tsx | 77 +- frontend/src/contexts/ToolWorkflowContext.tsx | 117 +- frontend/src/data/toolsTaxonomy.ts | 30 +- .../src/data/useTranslatedToolRegistry.tsx | 106 +- .../useAddPasswordOperation.test.ts | 2 +- .../addPassword/useAddPasswordOperation.ts | 49 +- .../addWatermark/useAddWatermarkOperation.ts | 20 +- .../addWatermark/useAddWatermarkParameters.ts | 1 + .../tools/automate/useAutomateOperation.ts | 49 + .../hooks/tools/automate/useAutomationForm.ts | 114 ++ .../tools/automate/useSavedAutomations.ts | 55 + .../tools/automate/useSuggestedAutomations.ts | 53 + .../useChangePermissionsOperation.test.ts | 4 +- .../useChangePermissionsOperation.ts | 43 +- .../tools/compress/useCompressOperation.ts | 23 +- .../tools/compress/useCompressParameters.ts | 2 +- .../tools/convert/useConvertOperation.ts | 112 +- .../tools/convert/useConvertParameters.ts | 29 +- .../src/hooks/tools/ocr/useOCROperation.ts | 104 +- .../src/hooks/tools/ocr/useOCRParameters.ts | 2 +- .../useRemoveCertificateSignOperation.ts | 30 +- .../useRemovePasswordOperation.test.ts | 2 +- .../useRemovePasswordOperation.ts | 32 +- .../hooks/tools/repair/useRepairOperation.ts | 30 +- .../tools/sanitize/useSanitizeOperation.ts | 20 +- .../hooks/tools/shared/useToolOperation.ts | 3 + .../useSingleLargePageOperation.ts | 30 +- .../hooks/tools/split/useSplitOperation.ts | 24 +- .../hooks/tools/split/useSplitParameters.ts | 2 +- .../useUnlockPdfFormsOperation.ts | 30 +- frontend/src/hooks/useSuggestedTools.ts | 9 +- frontend/src/hooks/useToolManagement.tsx | 35 +- frontend/src/hooks/useUrlSync.ts | 2 +- frontend/src/pages/HomePage.tsx | 31 +- frontend/src/services/automationStorage.ts | 183 +++ .../tests/convert/ConvertIntegration.test.tsx | 2 +- frontend/src/tools/AddPassword.tsx | 6 +- frontend/src/tools/AddWatermark.tsx | 7 +- frontend/src/tools/Automate.tsx | 168 ++ frontend/src/tools/ChangePermissions.tsx | 7 +- frontend/src/tools/Compress.tsx | 7 +- frontend/src/tools/Convert.tsx | 7 +- frontend/src/tools/OCR.tsx | 7 +- frontend/src/tools/RemoveCertificateSign.tsx | 7 +- frontend/src/tools/RemovePassword.tsx | 7 +- frontend/src/tools/Repair.tsx | 7 +- frontend/src/tools/Sanitize.tsx | 9 +- frontend/src/tools/SingleLargePage.tsx | 7 +- frontend/src/tools/Split.tsx | 4 +- frontend/src/tools/UnlockPdfForms.tsx | 7 +- frontend/src/types/automation.ts | 70 + frontend/src/types/navigation.ts | 42 + frontend/src/types/navigationActions.ts | 21 + frontend/src/types/tool.ts | 24 + frontend/src/utils/automationExecutor.ts | 157 ++ frontend/src/utils/automationFileProcessor.ts | 186 +++ frontend/src/utils/convertUtils.ts | 33 +- frontend/src/utils/resourceManager.ts | 71 + frontend/src/utils/urlRouting.ts | 23 +- 84 files changed, 4784 insertions(+), 572 deletions(-) create mode 100644 frontend/src/components/shared/ErrorBoundary.tsx create mode 100644 frontend/src/components/tools/addWatermark/AddWatermarkSingleStepSettings.tsx create mode 100644 frontend/src/components/tools/automate/AutomationCreation.tsx create mode 100644 frontend/src/components/tools/automate/AutomationEntry.tsx create mode 100644 frontend/src/components/tools/automate/AutomationRun.tsx create mode 100644 frontend/src/components/tools/automate/AutomationSelection.tsx create mode 100644 frontend/src/components/tools/automate/ToolConfigurationModal.tsx create mode 100644 frontend/src/components/tools/automate/ToolList.tsx create mode 100644 frontend/src/components/tools/automate/ToolSelector.tsx create mode 100644 frontend/src/components/tools/shared/renderToolButtons.tsx create mode 100644 frontend/src/constants/automation.ts create mode 100644 frontend/src/hooks/tools/automate/useAutomateOperation.ts create mode 100644 frontend/src/hooks/tools/automate/useAutomationForm.ts create mode 100644 frontend/src/hooks/tools/automate/useSavedAutomations.ts create mode 100644 frontend/src/hooks/tools/automate/useSuggestedAutomations.ts create mode 100644 frontend/src/services/automationStorage.ts create mode 100644 frontend/src/tools/Automate.tsx create mode 100644 frontend/src/types/automation.ts create mode 100644 frontend/src/types/navigation.ts create mode 100644 frontend/src/types/navigationActions.ts create mode 100644 frontend/src/utils/automationExecutor.ts create mode 100644 frontend/src/utils/automationFileProcessor.ts create mode 100644 frontend/src/utils/resourceManager.ts 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 */} + + + + {/* 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 && ( + setIsMenuOpen(true)} + onClose={() => setIsMenuOpen(false)} + > + + e.stopPropagation()} + style={{ + opacity: shouldShowHovered ? 1 : 0, + transform: shouldShowHovered ? 'scale(1)' : 'scale(0.8)', + transition: 'opacity 0.3s ease, transform 0.3s ease', + pointerEvents: shouldShowHovered ? 'auto' : 'none' + }} + > + + + + + + {onEdit && ( + } + onClick={(e) => { + e.stopPropagation(); + onEdit(); + }} + > + {t('edit', 'Edit')} + + )} + {onDelete && ( + } + onClick={(e) => { + e.stopPropagation(); + onDelete(); + }} + > + {t('delete', 'Delete')} + + )} + + + )} +
+
+ ); +} 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 */} + + + + {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()} +
+ + + + + +
+
+ ); +} 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 ( +
+ { + setOpened(isOpen); + // Clear search term when menu closes to show proper display + if (!isOpen) { + setSearchTerm(''); + } + }} + closeOnClickOutside={true} + closeOnEscape={true} + position="bottom-start" + offset={4} + withinPortal={false} + trapFocus={false} + shadow="sm" + transitionProps={{ duration: 0 }} + > + +
+ {selectedValue && toolRegistry[selectedValue] && !opened ? ( + // Show selected tool in AutomationEntry style when tool is selected and not searching +
+
+
+ {toolRegistry[selectedValue].icon} +
+ + {toolRegistry[selectedValue].name} + +
+
+ ) : ( + // Show search input when no tool selected or actively searching + + )} +
+
+ + + + + {displayGroups.length === 0 ? ( + + {isSearching + ? t('tools.noSearchResults', 'No tools found') + : t('tools.noTools', 'No tools available') + } + + ) : ( + renderedTools + )} + + + +
+
+ ); +} 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 = ({