diff --git a/APP/LICENSE.md b/APP/LICENSE.md new file mode 100644 index 0000000..8b6c2ef --- /dev/null +++ b/APP/LICENSE.md @@ -0,0 +1,11 @@ +# Page Templates and Building Blocks + +This package is part of the [Telerik and Kendo UI Accelerator](https://www.telerik.com/page-templates-and-ui-blocks) add-on. + +## License + +This is commercial software. To use it, you need to agree to the [**End User License Agreement for Progress® Telerik and Kendo UI Accelerator**](https://www.telerik.com/purchase/license-agreement/ui-accelerator). + +All available Progress® Telerik and Kendo UI Accelerator commercial licenses may be obtained at the [Progress® Telerik and Kendo UI Accelerator website](https://www.telerik.com/purchase.aspx?filter=ui-accelerator#individual-products). + +*Copyright © 2025 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved.* diff --git a/APP/angular.json b/APP/angular.json new file mode 100644 index 0000000..810c586 --- /dev/null +++ b/APP/angular.json @@ -0,0 +1,116 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "client-bridge": { + "projectType": "application", + "schematics": { + "@schematics/angular:class": { + "skipTests": true + }, + "@schematics/angular:component": { + "skipTests": true + }, + "@schematics/angular:directive": { + "skipTests": true + }, + "@schematics/angular:guard": { + "skipTests": true + }, + "@schematics/angular:interceptor": { + "skipTests": true + }, + "@schematics/angular:pipe": { + "skipTests": true + }, + "@schematics/angular:resolver": { + "skipTests": true + }, + "@schematics/angular:service": { + "skipTests": true + } + }, + "root": "", + "sourceRoot": "src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular/build:application", + "options": { + "browser": "src/main.ts", + "polyfills": [ + "zone.js", + "@angular/localize/init" + ], + "tsConfig": "tsconfig.app.json", + "assets": [ + "src/assets" + ], + "styles": [ + "src/styles.scss" + ] + }, + "configurations": { + "production": { + "budgets": [ + { + "type": "initial", + "maximumWarning": "500kB", + "maximumError": "5mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "4kB", + "maximumError": "1mb" + } + ], + "outputHashing": "all" + }, + "development": { + "optimization": false, + "extractLicenses": false, + "sourceMap": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular/build:dev-server", + "options": { + "buildTarget": "client-bridge:build" + }, + "configurations": { + "production": { + "buildTarget": "client-bridge:build:production" + }, + "development": { + "buildTarget": "client-bridge:build:development" + } + }, + "defaultConfiguration": "development" + }, + "extract-i18n": { + "builder": "@angular/build:extract-i18n" + }, + "test": { + "builder": "@angular/build:karma", + "options": { + "polyfills": [ + "zone.js", + "zone.js/testing", + "@angular/localize/init" + ], + "tsConfig": "tsconfig.spec.json", + "assets": [ + "src/assets" + ], + "styles": [ + "src/styles.scss" + ] + } + } + } + } + } +} \ No newline at end of file diff --git a/APP/email-template.html b/APP/email-template.html new file mode 100644 index 0000000..19f1033 --- /dev/null +++ b/APP/email-template.html @@ -0,0 +1,387 @@ + + + + + + + Escrow Portal Access - RBJ Identity + + + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + + \ No newline at end of file diff --git a/APP/package-lock.json b/APP/package-lock.json new file mode 100644 index 0000000..c747765 --- /dev/null +++ b/APP/package-lock.json @@ -0,0 +1,11376 @@ +{ + "name": "client-bridge", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "client-bridge", + "version": "0.0.0", + "dependencies": { + "@angular/animations": "^20.1.0", + "@angular/common": "^20.1.0", + "@angular/compiler": "^20.1.0", + "@angular/core": "^20.1.0", + "@angular/forms": "^20.1.0", + "@angular/localize": "^20.1.6", + "@angular/platform-browser": "^20.1.0", + "@angular/router": "^20.1.0", + "@progress/kendo-angular-buttons": "^20.0.0", + "@progress/kendo-angular-charts": "^20.0.0", + "@progress/kendo-angular-common": "^20.0.0", + "@progress/kendo-angular-conversational-ui": "^20.0.0", + "@progress/kendo-angular-dateinputs": "^20.0.0", + "@progress/kendo-angular-dialog": "^20.0.0", + "@progress/kendo-angular-dropdowns": "^20.0.0", + "@progress/kendo-angular-editor": "^20.0.0", + "@progress/kendo-angular-excel-export": "^20.0.3", + "@progress/kendo-angular-gauges": "^20.0.0", + "@progress/kendo-angular-grid": "^20.0.0", + "@progress/kendo-angular-icons": "^20.0.0", + "@progress/kendo-angular-indicators": "^20.0.0", + "@progress/kendo-angular-inputs": "^20.0.0", + "@progress/kendo-angular-intl": "^20.0.0", + "@progress/kendo-angular-l10n": "^20.0.0", + "@progress/kendo-angular-label": "^20.0.0", + "@progress/kendo-angular-layout": "^20.0.0", + "@progress/kendo-angular-listview": "^20.0.0", + "@progress/kendo-angular-map": "^20.0.0", + "@progress/kendo-angular-menu": "^20.0.0", + "@progress/kendo-angular-navigation": "^20.0.0", + "@progress/kendo-angular-pager": "^20.0.0", + "@progress/kendo-angular-pdf-export": "^20.0.3", + "@progress/kendo-angular-popup": "^20.0.0", + "@progress/kendo-angular-progressbar": "^20.0.0", + "@progress/kendo-angular-scrollview": "^20.0.0", + "@progress/kendo-angular-toolbar": "^20.0.3", + "@progress/kendo-angular-tooltip": "^20.0.0", + "@progress/kendo-angular-treeview": "^20.0.0", + "@progress/kendo-angular-upload": "^20.0.0", + "@progress/kendo-angular-utils": "^20.0.0", + "@progress/kendo-data-query": "^1.7.1", + "@progress/kendo-drawing": "^1.22.0", + "@progress/kendo-licensing": "^1.7.0", + "@progress/kendo-svg-icons": "^4.5.0", + "@progress/kendo-theme-default": "^12.0.0", + "@progress/kendo-theme-utils": "^12.0.0", + "rxjs": "~7.8.0", + "tslib": "^2.3.0", + "zone.js": "~0.15.0" + }, + "devDependencies": { + "@angular/build": "^20.1.6", + "@angular/cli": "^20.1.6", + "@angular/compiler-cli": "^20.1.0", + "@angular/localize": "^20.2.1", + "@types/jasmine": "~5.1.0", + "jasmine-core": "~5.8.0", + "karma": "~6.4.0", + "karma-chrome-launcher": "~3.2.0", + "karma-coverage": "~2.2.0", + "karma-jasmine": "~5.1.0", + "karma-jasmine-html-reporter": "~2.1.0", + "typescript": "~5.8.2" + } + }, + "node_modules/@algolia/abtesting": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@algolia/abtesting/-/abtesting-1.1.0.tgz", + "integrity": "sha512-sEyWjw28a/9iluA37KLGu8vjxEIlb60uxznfTUmXImy7H5NvbpSO6yYgmgH5KiD7j+zTUUihiST0jEP12IoXow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.35.0", + "@algolia/requester-browser-xhr": "5.35.0", + "@algolia/requester-fetch": "5.35.0", + "@algolia/requester-node-http": "5.35.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-abtesting": { + "version": "5.35.0", + "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.35.0.tgz", + "integrity": "sha512-uUdHxbfHdoppDVflCHMxRlj49/IllPwwQ2cQ8DLC4LXr3kY96AHBpW0dMyi6ygkn2MtFCc6BxXCzr668ZRhLBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.35.0", + "@algolia/requester-browser-xhr": "5.35.0", + "@algolia/requester-fetch": "5.35.0", + "@algolia/requester-node-http": "5.35.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-analytics": { + "version": "5.35.0", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.35.0.tgz", + "integrity": "sha512-SunAgwa9CamLcRCPnPHx1V2uxdQwJGqb1crYrRWktWUdld0+B2KyakNEeVn5lln4VyeNtW17Ia7V7qBWyM/Skw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.35.0", + "@algolia/requester-browser-xhr": "5.35.0", + "@algolia/requester-fetch": "5.35.0", + "@algolia/requester-node-http": "5.35.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-common": { + "version": "5.35.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.35.0.tgz", + "integrity": "sha512-ipE0IuvHu/bg7TjT2s+187kz/E3h5ssfTtjpg1LbWMgxlgiaZIgTTbyynM7NfpSJSKsgQvCQxWjGUO51WSCu7w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-insights": { + "version": "5.35.0", + "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.35.0.tgz", + "integrity": "sha512-UNbCXcBpqtzUucxExwTSfAe8gknAJ485NfPN6o1ziHm6nnxx97piIbcBQ3edw823Tej2Wxu1C0xBY06KgeZ7gA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.35.0", + "@algolia/requester-browser-xhr": "5.35.0", + "@algolia/requester-fetch": "5.35.0", + "@algolia/requester-node-http": "5.35.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-personalization": { + "version": "5.35.0", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.35.0.tgz", + "integrity": "sha512-/KWjttZ6UCStt4QnWoDAJ12cKlQ+fkpMtyPmBgSS2WThJQdSV/4UWcqCUqGH7YLbwlj3JjNirCu3Y7uRTClxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.35.0", + "@algolia/requester-browser-xhr": "5.35.0", + "@algolia/requester-fetch": "5.35.0", + "@algolia/requester-node-http": "5.35.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-query-suggestions": { + "version": "5.35.0", + "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.35.0.tgz", + "integrity": "sha512-8oCuJCFf/71IYyvQQC+iu4kgViTODbXDk3m7yMctEncRSRV+u2RtDVlpGGfPlJQOrAY7OONwJlSHkmbbm2Kp/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.35.0", + "@algolia/requester-browser-xhr": "5.35.0", + "@algolia/requester-fetch": "5.35.0", + "@algolia/requester-node-http": "5.35.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-search": { + "version": "5.35.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.35.0.tgz", + "integrity": "sha512-FfmdHTrXhIduWyyuko1YTcGLuicVbhUyRjO3HbXE4aP655yKZgdTIfMhZ/V5VY9bHuxv/fGEh3Od1Lvv2ODNTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.35.0", + "@algolia/requester-browser-xhr": "5.35.0", + "@algolia/requester-fetch": "5.35.0", + "@algolia/requester-node-http": "5.35.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/ingestion": { + "version": "1.35.0", + "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.35.0.tgz", + "integrity": "sha512-gPzACem9IL1Co8mM1LKMhzn1aSJmp+Vp434An4C0OBY4uEJRcqsLN3uLBlY+bYvFg8C8ImwM9YRiKczJXRk0XA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.35.0", + "@algolia/requester-browser-xhr": "5.35.0", + "@algolia/requester-fetch": "5.35.0", + "@algolia/requester-node-http": "5.35.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/monitoring": { + "version": "1.35.0", + "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.35.0.tgz", + "integrity": "sha512-w9MGFLB6ashI8BGcQoVt7iLgDIJNCn4OIu0Q0giE3M2ItNrssvb8C0xuwJQyTy1OFZnemG0EB1OvXhIHOvQwWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.35.0", + "@algolia/requester-browser-xhr": "5.35.0", + "@algolia/requester-fetch": "5.35.0", + "@algolia/requester-node-http": "5.35.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/recommend": { + "version": "5.35.0", + "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.35.0.tgz", + "integrity": "sha512-AhrVgaaXAb8Ue0u2nuRWwugt0dL5UmRgS9LXe0Hhz493a8KFeZVUE56RGIV3hAa6tHzmAV7eIoqcWTQvxzlJeQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.35.0", + "@algolia/requester-browser-xhr": "5.35.0", + "@algolia/requester-fetch": "5.35.0", + "@algolia/requester-node-http": "5.35.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-browser-xhr": { + "version": "5.35.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.35.0.tgz", + "integrity": "sha512-diY415KLJZ6x1Kbwl9u96Jsz0OstE3asjXtJ9pmk1d+5gPuQ5jQyEsgC+WmEXzlec3iuVszm8AzNYYaqw6B+Zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.35.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-fetch": { + "version": "5.35.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.35.0.tgz", + "integrity": "sha512-uydqnSmpAjrgo8bqhE9N1wgcB98psTRRQXcjc4izwMB7yRl9C8uuAQ/5YqRj04U0mMQ+fdu2fcNF6m9+Z1BzDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.35.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-node-http": { + "version": "5.35.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.35.0.tgz", + "integrity": "sha512-RgLX78ojYOrThJHrIiPzT4HW3yfQa0D7K+MQ81rhxqaNyNBu4F1r+72LNHYH/Z+y9I1Mrjrd/c/Ue5zfDgAEjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.35.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@angular-devkit/architect": { + "version": "0.2002.0", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2002.0.tgz", + "integrity": "sha512-PaBXFP1kdUuNtMie0lWnitlYbq8o1gz/s0YIa8oY1X3swOJ7bP6kBfxTb9opV5uXAOkXg2zCdnZ4Eu1aVkgPGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": "20.2.0", + "rxjs": "7.8.2" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular-devkit/core": { + "version": "20.2.0", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.2.0.tgz", + "integrity": "sha512-3CM6Zsr09Kf92ItFkxijlnC4+ZOgkxdCk0vFYvuw9UuvTDNwyIqJi6693PRPRbcXgpdY2vs6u99elSvQVmoEEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "8.17.1", + "ajv-formats": "3.0.1", + "jsonc-parser": "3.3.1", + "picomatch": "4.0.3", + "rxjs": "7.8.2", + "source-map": "0.7.6" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^4.0.0" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@angular-devkit/schematics": { + "version": "20.2.0", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-20.2.0.tgz", + "integrity": "sha512-TCPIN6Bd04oGuNocETmsd9hzGYrjrivisbMKb0WOuDi3OnCkmWqsPR+QA2kYwTOGqG3HXkz/z3CA0g04M2fgrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": "20.2.0", + "jsonc-parser": "3.3.1", + "magic-string": "0.30.17", + "ora": "8.2.0", + "rxjs": "7.8.2" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular/animations": { + "version": "20.3.4", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-20.3.4.tgz", + "integrity": "sha512-b+vFsTtMYtOrcZZLXB4BxuErbrLlShFT6khTvkwu/pFK8ri3tasyJGkeKRZJHao5ZsWdZSqV2mRwzg7vphchnA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@angular/core": "20.3.4" + } + }, + "node_modules/@angular/build": { + "version": "20.2.0", + "resolved": "https://registry.npmjs.org/@angular/build/-/build-20.2.0.tgz", + "integrity": "sha512-/Yhqhg01UvX0E+tx4WAeK3AnwpZLqcw+XKTmsPsH5rbqpLKNRR9XsC3PJ4qBFU1u9/Lh13mmmr1+pG2p8ixMug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "2.3.0", + "@angular-devkit/architect": "0.2002.0", + "@babel/core": "7.28.3", + "@babel/helper-annotate-as-pure": "7.27.3", + "@babel/helper-split-export-declaration": "7.24.7", + "@inquirer/confirm": "5.1.14", + "@vitejs/plugin-basic-ssl": "2.1.0", + "beasties": "0.3.5", + "browserslist": "^4.23.0", + "esbuild": "0.25.9", + "https-proxy-agent": "7.0.6", + "istanbul-lib-instrument": "6.0.3", + "jsonc-parser": "3.3.1", + "listr2": "9.0.1", + "magic-string": "0.30.17", + "mrmime": "2.0.1", + "parse5-html-rewriting-stream": "8.0.0", + "picomatch": "4.0.3", + "piscina": "5.1.3", + "rolldown": "1.0.0-beta.32", + "sass": "1.90.0", + "semver": "7.7.2", + "source-map-support": "0.5.21", + "tinyglobby": "0.2.14", + "vite": "7.1.2", + "watchpack": "2.4.4" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "optionalDependencies": { + "lmdb": "3.4.2" + }, + "peerDependencies": { + "@angular/compiler": "^20.0.0", + "@angular/compiler-cli": "^20.0.0", + "@angular/core": "^20.0.0", + "@angular/localize": "^20.0.0", + "@angular/platform-browser": "^20.0.0", + "@angular/platform-server": "^20.0.0", + "@angular/service-worker": "^20.0.0", + "@angular/ssr": "^20.2.0", + "karma": "^6.4.0", + "less": "^4.2.0", + "ng-packagr": "^20.0.0", + "postcss": "^8.4.0", + "tailwindcss": "^2.0.0 || ^3.0.0 || ^4.0.0", + "tslib": "^2.3.0", + "typescript": ">=5.8 <6.0", + "vitest": "^3.1.1" + }, + "peerDependenciesMeta": { + "@angular/core": { + "optional": true + }, + "@angular/localize": { + "optional": true + }, + "@angular/platform-browser": { + "optional": true + }, + "@angular/platform-server": { + "optional": true + }, + "@angular/service-worker": { + "optional": true + }, + "@angular/ssr": { + "optional": true + }, + "karma": { + "optional": true + }, + "less": { + "optional": true + }, + "ng-packagr": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tailwindcss": { + "optional": true + }, + "vitest": { + "optional": true + } + } + }, + "node_modules/@angular/cli": { + "version": "20.2.0", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-20.2.0.tgz", + "integrity": "sha512-p62hkuQOxf5kJsVq6AT7B1MHYo1uPGoZV4lf47qOrLjl0WANwfxEgLvyuVgL47ylnINbPnITeeUdoadVn4t1sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/architect": "0.2002.0", + "@angular-devkit/core": "20.2.0", + "@angular-devkit/schematics": "20.2.0", + "@inquirer/prompts": "7.8.2", + "@listr2/prompt-adapter-inquirer": "3.0.1", + "@modelcontextprotocol/sdk": "1.17.3", + "@schematics/angular": "20.2.0", + "@yarnpkg/lockfile": "1.1.0", + "algoliasearch": "5.35.0", + "ini": "5.0.0", + "jsonc-parser": "3.3.1", + "listr2": "9.0.1", + "npm-package-arg": "13.0.0", + "pacote": "21.0.0", + "resolve": "1.22.10", + "semver": "7.7.2", + "yargs": "18.0.0", + "zod": "3.25.76" + }, + "bin": { + "ng": "bin/ng.js" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular/common": { + "version": "20.2.1", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-20.2.1.tgz", + "integrity": "sha512-T6RYnDZA9TyYhj2hUz4set8p4RbBCg6IKUvy6qzdKTl4nn4xQ0XUV7aGBYN4LKiGrse9lzlVUAyXtkhmwuBbCQ==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@angular/core": "20.2.1", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/compiler": { + "version": "20.2.1", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-20.2.1.tgz", + "integrity": "sha512-ghVt1E8xmwjMwqyGRwXYJkr7fz40VEreUSX1q+gEzbGTftVrK1foxPT8jcueIn0ztArDf7+zSMtu314FiJZyYA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@angular/compiler-cli": { + "version": "20.2.1", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-20.2.1.tgz", + "integrity": "sha512-VpbcRqNPJvy1L9RDtGGQsQiOrMzxodUWklphbtnh9MrrK6lLuy6Qj2ROiW7vKL9WfLTCXWA24gBAcMAR76dq3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "7.28.3", + "@jridgewell/sourcemap-codec": "^1.4.14", + "chokidar": "^4.0.0", + "convert-source-map": "^1.5.1", + "reflect-metadata": "^0.2.0", + "semver": "^7.0.0", + "tslib": "^2.3.0", + "yargs": "^18.0.0" + }, + "bin": { + "ng-xi18n": "bundles/src/bin/ng_xi18n.js", + "ngc": "bundles/src/bin/ngc.js" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@angular/compiler": "20.2.1", + "typescript": ">=5.8 <6.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@angular/core": { + "version": "20.2.1", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-20.2.1.tgz", + "integrity": "sha512-/hl3AkmdQ62P9ttmfULEDg9GIz7BkzhGv9bSH2ssiU3Y4ax6eM8uQXEbMxBA8OUKOvg1Q4POcNHIiJQgO5t28Q==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@angular/compiler": "20.2.1", + "rxjs": "^6.5.3 || ^7.4.0", + "zone.js": "~0.15.0" + }, + "peerDependenciesMeta": { + "@angular/compiler": { + "optional": true + }, + "zone.js": { + "optional": true + } + } + }, + "node_modules/@angular/forms": { + "version": "20.2.1", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-20.2.1.tgz", + "integrity": "sha512-SfkiHEIFPLtTKeaXUTpRfYnpJDxaeKiTi0YqfvzEjKE68qH0t+pQ4rL0Poch2/l4snP6JS1XzO/nDve1dk3vZw==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@angular/common": "20.2.1", + "@angular/core": "20.2.1", + "@angular/platform-browser": "20.2.1", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/localize": { + "version": "20.2.1", + "resolved": "https://registry.npmjs.org/@angular/localize/-/localize-20.2.1.tgz", + "integrity": "sha512-vemzYcHt6YX4FutpgNXiXTpKCMVaJdOG/m2+oJyvnr8KvdlrJKczXraPVY4ER+WJiHC5IQSg24otdSFc0UH2JA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "7.28.3", + "@types/babel__core": "7.20.5", + "tinyglobby": "^0.2.12", + "yargs": "^18.0.0" + }, + "bin": { + "localize-extract": "tools/bundles/src/extract/cli.js", + "localize-migrate": "tools/bundles/src/migrate/cli.js", + "localize-translate": "tools/bundles/src/translate/cli.js" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@angular/compiler": "20.2.1", + "@angular/compiler-cli": "20.2.1" + } + }, + "node_modules/@angular/platform-browser": { + "version": "20.2.1", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-20.2.1.tgz", + "integrity": "sha512-oxDih/A8G7W+I6oAip+sev+kebioYmzhB/NMzF8C8zx/ieVDzatJ+YeEZQt7eDaJLH94S4sIC25SPq3OFIabxg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@angular/animations": "20.2.1", + "@angular/common": "20.2.1", + "@angular/core": "20.2.1" + }, + "peerDependenciesMeta": { + "@angular/animations": { + "optional": true + } + } + }, + "node_modules/@angular/router": { + "version": "20.2.1", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-20.2.1.tgz", + "integrity": "sha512-f8KfG55EVnFDC9ud+MbxAP6voKi7hVQH4YaqPK0Lm6pyc1Xp0I5W25iRbg+Y1rO1csHKHauBPkUEESEuVGBGqg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@angular/common": "20.2.1", + "@angular/core": "20.2.1", + "@angular/platform-browser": "20.2.1", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz", + "integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.3.tgz", + "integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.3", + "@babel/parser": "^7.28.3", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.3", + "@babel/types": "^7.28.2", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", + "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.3", + "@babel/types": "^7.28.2", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", + "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.3.tgz", + "integrity": "sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz", + "integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.3.tgz", + "integrity": "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.3", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.2", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", + "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@emnapi/core": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.5.tgz", + "integrity": "sha512-XsLw1dEOpkSX/WucdqUhPWP7hDxSvZiY+fsUC14h+FtQ2Ifni4znbBt8punRX+Uj2JG/uDb8nEHVKvrVlvdZ5Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.0.4", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.5.tgz", + "integrity": "sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.4.tgz", + "integrity": "sha512-PJR+bOmMOPH8AtcTGAyYNiuJ3/Fcoj2XN/gBEWzDIKh254XO+mM9XoXHk5GNEhodxeMznbg7BlRojVbKN+gC6g==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz", + "integrity": "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.9.tgz", + "integrity": "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.9.tgz", + "integrity": "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.9.tgz", + "integrity": "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.9.tgz", + "integrity": "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.9.tgz", + "integrity": "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.9.tgz", + "integrity": "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.9.tgz", + "integrity": "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.9.tgz", + "integrity": "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.9.tgz", + "integrity": "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.9.tgz", + "integrity": "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.9.tgz", + "integrity": "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.9.tgz", + "integrity": "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.9.tgz", + "integrity": "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.9.tgz", + "integrity": "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.9.tgz", + "integrity": "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.9.tgz", + "integrity": "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.9.tgz", + "integrity": "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.9.tgz", + "integrity": "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.9.tgz", + "integrity": "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.9.tgz", + "integrity": "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.9.tgz", + "integrity": "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.9.tgz", + "integrity": "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.9.tgz", + "integrity": "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.9.tgz", + "integrity": "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz", + "integrity": "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/checkbox": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.2.2.tgz", + "integrity": "sha512-E+KExNurKcUJJdxmjglTl141EwxWyAHplvsYJQgSwXf8qiNWkTxTuCCqmhFEmbIXd4zLaGMfQFJ6WrZ7fSeV3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.2.0", + "@inquirer/figures": "^1.0.13", + "@inquirer/type": "^3.0.8", + "ansi-escapes": "^4.3.2", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/confirm": { + "version": "5.1.14", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.14.tgz", + "integrity": "sha512-5yR4IBfe0kXe59r1YCTG8WXkUbl7Z35HK87Sw+WUyGD8wNUx7JvY7laahzeytyE1oLn74bQnL7hstctQxisQ8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.1.15", + "@inquirer/type": "^3.0.8" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.2.0.tgz", + "integrity": "sha512-NyDSjPqhSvpZEMZrLCYUquWNl+XC/moEcVFqS55IEYIYsY0a1cUCevSqk7ctOlnm/RaSBU5psFryNlxcmGrjaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/figures": "^1.0.13", + "@inquirer/type": "^3.0.8", + "ansi-escapes": "^4.3.2", + "cli-width": "^4.1.0", + "mute-stream": "^2.0.0", + "signal-exit": "^4.1.0", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/editor": { + "version": "4.2.18", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.18.tgz", + "integrity": "sha512-yeQN3AXjCm7+Hmq5L6Dm2wEDeBRdAZuyZ4I7tWSSanbxDzqM0KqzoDbKM7p4ebllAYdoQuPJS6N71/3L281i6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.2.0", + "@inquirer/external-editor": "^1.0.1", + "@inquirer/type": "^3.0.8" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/expand": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.18.tgz", + "integrity": "sha512-xUjteYtavH7HwDMzq4Cn2X4Qsh5NozoDHCJTdoXg9HfZ4w3R6mxV1B9tL7DGJX2eq/zqtsFjhm0/RJIMGlh3ag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.2.0", + "@inquirer/type": "^3.0.8", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/external-editor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.1.tgz", + "integrity": "sha512-Oau4yL24d2B5IL4ma4UpbQigkVhzPDXLoqy1ggK4gnHg/stmkffJE4oOXHXF3uz0UEpywG68KcyXsyYpA1Re/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "chardet": "^2.1.0", + "iconv-lite": "^0.6.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/figures": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.13.tgz", + "integrity": "sha512-lGPVU3yO9ZNqA7vTYz26jny41lE7yoQansmqdMLBEfqaGsmdg7V3W9mK9Pvb5IL4EVZ9GnSDGMO/cJXud5dMaw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/input": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.2.2.tgz", + "integrity": "sha512-hqOvBZj/MhQCpHUuD3MVq18SSoDNHy7wEnQ8mtvs71K8OPZVXJinOzcvQna33dNYLYE4LkA9BlhAhK6MJcsVbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.2.0", + "@inquirer/type": "^3.0.8" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/number": { + "version": "3.0.18", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.18.tgz", + "integrity": "sha512-7exgBm52WXZRczsydCVftozFTrrwbG5ySE0GqUd2zLNSBXyIucs2Wnm7ZKLe/aUu6NUg9dg7Q80QIHCdZJiY4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.2.0", + "@inquirer/type": "^3.0.8" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/password": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.18.tgz", + "integrity": "sha512-zXvzAGxPQTNk/SbT3carAD4Iqi6A2JS2qtcqQjsL22uvD+JfQzUrDEtPjLL7PLn8zlSNyPdY02IiQjzoL9TStA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.2.0", + "@inquirer/type": "^3.0.8", + "ansi-escapes": "^4.3.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/prompts": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.8.2.tgz", + "integrity": "sha512-nqhDw2ZcAUrKNPwhjinJny903bRhI0rQhiDz1LksjeRxqa36i3l75+4iXbOy0rlDpLJGxqtgoPavQjmmyS5UJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/checkbox": "^4.2.1", + "@inquirer/confirm": "^5.1.14", + "@inquirer/editor": "^4.2.17", + "@inquirer/expand": "^4.0.17", + "@inquirer/input": "^4.2.1", + "@inquirer/number": "^3.0.17", + "@inquirer/password": "^4.0.17", + "@inquirer/rawlist": "^4.1.5", + "@inquirer/search": "^3.1.0", + "@inquirer/select": "^4.3.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/rawlist": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.1.6.tgz", + "integrity": "sha512-KOZqa3QNr3f0pMnufzL7K+nweFFCCBs6LCXZzXDrVGTyssjLeudn5ySktZYv1XiSqobyHRYYK0c6QsOxJEhXKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.2.0", + "@inquirer/type": "^3.0.8", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/search": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.1.1.tgz", + "integrity": "sha512-TkMUY+A2p2EYVY3GCTItYGvqT6LiLzHBnqsU1rJbrpXUijFfM6zvUx0R4civofVwFCmJZcKqOVwwWAjplKkhxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.2.0", + "@inquirer/figures": "^1.0.13", + "@inquirer/type": "^3.0.8", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/select": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.3.2.tgz", + "integrity": "sha512-nwous24r31M+WyDEHV+qckXkepvihxhnyIaod2MG7eCE6G0Zm/HUF6jgN8GXgf4U7AU6SLseKdanY195cwvU6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.2.0", + "@inquirer/figures": "^1.0.13", + "@inquirer/type": "^3.0.8", + "ansi-escapes": "^4.3.2", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/type": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.8.tgz", + "integrity": "sha512-lg9Whz8onIHRthWaN1Q9EGLa/0LFJjyM8mEUbL1eTi6yMGvBf8gvyDLtxSXztQsxMvhxxNpJYrwa1YHdq+w4Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@isaacs/balanced-match": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", + "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/brace-expansion": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", + "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@isaacs/balanced-match": "^4.0.1" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.30", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz", + "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@listr2/prompt-adapter-inquirer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@listr2/prompt-adapter-inquirer/-/prompt-adapter-inquirer-3.0.1.tgz", + "integrity": "sha512-3XFmGwm3u6ioREG+ynAQB7FoxfajgQnMhIu8wC5eo/Lsih4aKDg0VuIMGaOsYn7hJSJagSeaD4K8yfpkEoDEmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/type": "^3.0.7" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@inquirer/prompts": ">= 3 < 8", + "listr2": "9.0.1" + } + }, + "node_modules/@lmdb/lmdb-darwin-arm64": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-arm64/-/lmdb-darwin-arm64-3.4.2.tgz", + "integrity": "sha512-NK80WwDoODyPaSazKbzd3NEJ3ygePrkERilZshxBViBARNz21rmediktGHExoj9n5t9+ChlgLlxecdFKLCuCKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@lmdb/lmdb-darwin-x64": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-x64/-/lmdb-darwin-x64-3.4.2.tgz", + "integrity": "sha512-zevaowQNmrp3U7Fz1s9pls5aIgpKRsKb3dZWDINtLiozh3jZI9fBrI19lYYBxqdyiIyNdlyiidPnwPShj4aK+w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@lmdb/lmdb-linux-arm": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm/-/lmdb-linux-arm-3.4.2.tgz", + "integrity": "sha512-OmHCULY17rkx/RoCoXlzU7LyR8xqrksgdYWwtYa14l/sseezZ8seKWXcogHcjulBddER5NnEFV4L/Jtr2nyxeg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@lmdb/lmdb-linux-arm64": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm64/-/lmdb-linux-arm64-3.4.2.tgz", + "integrity": "sha512-ZBEfbNZdkneebvZs98Lq30jMY8V9IJzckVeigGivV7nTHJc+89Ctomp1kAIWKlwIG0ovCDrFI448GzFPORANYg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@lmdb/lmdb-linux-x64": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-x64/-/lmdb-linux-x64-3.4.2.tgz", + "integrity": "sha512-vL9nM17C77lohPYE4YaAQvfZCSVJSryE4fXdi8M7uWPBnU+9DJabgKVAeyDb84ZM2vcFseoBE4/AagVtJeRE7g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@lmdb/lmdb-win32-arm64": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-arm64/-/lmdb-win32-arm64-3.4.2.tgz", + "integrity": "sha512-SXWjdBfNDze4ZPeLtYIzsIeDJDJ/SdsA0pEXcUBayUIMO0FQBHfVZZyHXQjjHr4cvOAzANBgIiqaXRwfMhzmLw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@lmdb/lmdb-win32-x64": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-x64/-/lmdb-win32-x64-3.4.2.tgz", + "integrity": "sha512-IY+r3bxKW6Q6sIPiMC0L533DEfRJSXibjSI3Ft/w9Q8KQBNqEIvUFXt+09wV8S5BRk0a8uSF19YWxuRwEfI90g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.17.3", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.17.3.tgz", + "integrity": "sha512-JPwUKWSsbzx+DLFznf/QZ32Qa+ptfbUlHhRLrBQBAFu9iI1iYvizM4p+zhhRDceSsPutXp4z+R/HPVphlIiclg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.6", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.0.1", + "express-rate-limit": "^7.5.0", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.23.8", + "zod-to-json-schema": "^3.24.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz", + "integrity": "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.3.tgz", + "integrity": "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.3.tgz", + "integrity": "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.3.tgz", + "integrity": "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.3.tgz", + "integrity": "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.3.tgz", + "integrity": "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@napi-rs/nice": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice/-/nice-1.1.1.tgz", + "integrity": "sha512-xJIPs+bYuc9ASBl+cvGsKbGrJmS6fAKaSZCnT0lhahT5rhA2VVy9/EcIgd2JhtEuFOJNx7UHNn/qiTPTY4nrQw==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "optionalDependencies": { + "@napi-rs/nice-android-arm-eabi": "1.1.1", + "@napi-rs/nice-android-arm64": "1.1.1", + "@napi-rs/nice-darwin-arm64": "1.1.1", + "@napi-rs/nice-darwin-x64": "1.1.1", + "@napi-rs/nice-freebsd-x64": "1.1.1", + "@napi-rs/nice-linux-arm-gnueabihf": "1.1.1", + "@napi-rs/nice-linux-arm64-gnu": "1.1.1", + "@napi-rs/nice-linux-arm64-musl": "1.1.1", + "@napi-rs/nice-linux-ppc64-gnu": "1.1.1", + "@napi-rs/nice-linux-riscv64-gnu": "1.1.1", + "@napi-rs/nice-linux-s390x-gnu": "1.1.1", + "@napi-rs/nice-linux-x64-gnu": "1.1.1", + "@napi-rs/nice-linux-x64-musl": "1.1.1", + "@napi-rs/nice-openharmony-arm64": "1.1.1", + "@napi-rs/nice-win32-arm64-msvc": "1.1.1", + "@napi-rs/nice-win32-ia32-msvc": "1.1.1", + "@napi-rs/nice-win32-x64-msvc": "1.1.1" + } + }, + "node_modules/@napi-rs/nice-android-arm-eabi": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-android-arm-eabi/-/nice-android-arm-eabi-1.1.1.tgz", + "integrity": "sha512-kjirL3N6TnRPv5iuHw36wnucNqXAO46dzK9oPb0wj076R5Xm8PfUVA9nAFB5ZNMmfJQJVKACAPd/Z2KYMppthw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-android-arm64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-android-arm64/-/nice-android-arm64-1.1.1.tgz", + "integrity": "sha512-blG0i7dXgbInN5urONoUCNf+DUEAavRffrO7fZSeoRMJc5qD+BJeNcpr54msPF6qfDD6kzs9AQJogZvT2KD5nw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-darwin-arm64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-darwin-arm64/-/nice-darwin-arm64-1.1.1.tgz", + "integrity": "sha512-s/E7w45NaLqTGuOjC2p96pct4jRfo61xb9bU1unM/MJ/RFkKlJyJDx7OJI/O0ll/hrfpqKopuAFDV8yo0hfT7A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-darwin-x64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-darwin-x64/-/nice-darwin-x64-1.1.1.tgz", + "integrity": "sha512-dGoEBnVpsdcC+oHHmW1LRK5eiyzLwdgNQq3BmZIav+9/5WTZwBYX7r5ZkQC07Nxd3KHOCkgbHSh4wPkH1N1LiQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-freebsd-x64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-freebsd-x64/-/nice-freebsd-x64-1.1.1.tgz", + "integrity": "sha512-kHv4kEHAylMYmlNwcQcDtXjklYp4FCf0b05E+0h6nDHsZ+F0bDe04U/tXNOqrx5CmIAth4vwfkjjUmp4c4JktQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-arm-gnueabihf": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm-gnueabihf/-/nice-linux-arm-gnueabihf-1.1.1.tgz", + "integrity": "sha512-E1t7K0efyKXZDoZg1LzCOLxgolxV58HCkaEkEvIYQx12ht2pa8hoBo+4OB3qh7e+QiBlp1SRf+voWUZFxyhyqg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-arm64-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm64-gnu/-/nice-linux-arm64-gnu-1.1.1.tgz", + "integrity": "sha512-CIKLA12DTIZlmTaaKhQP88R3Xao+gyJxNWEn04wZwC2wmRapNnxCUZkVwggInMJvtVElA+D4ZzOU5sX4jV+SmQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-arm64-musl": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm64-musl/-/nice-linux-arm64-musl-1.1.1.tgz", + "integrity": "sha512-+2Rzdb3nTIYZ0YJF43qf2twhqOCkiSrHx2Pg6DJaCPYhhaxbLcdlV8hCRMHghQ+EtZQWGNcS2xF4KxBhSGeutg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-ppc64-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-ppc64-gnu/-/nice-linux-ppc64-gnu-1.1.1.tgz", + "integrity": "sha512-4FS8oc0GeHpwvv4tKciKkw3Y4jKsL7FRhaOeiPei0X9T4Jd619wHNe4xCLmN2EMgZoeGg+Q7GY7BsvwKpL22Tg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-riscv64-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-riscv64-gnu/-/nice-linux-riscv64-gnu-1.1.1.tgz", + "integrity": "sha512-HU0nw9uD4FO/oGCCk409tCi5IzIZpH2agE6nN4fqpwVlCn5BOq0MS1dXGjXaG17JaAvrlpV5ZeyZwSon10XOXw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-s390x-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-s390x-gnu/-/nice-linux-s390x-gnu-1.1.1.tgz", + "integrity": "sha512-2YqKJWWl24EwrX0DzCQgPLKQBxYDdBxOHot1KWEq7aY2uYeX+Uvtv4I8xFVVygJDgf6/92h9N3Y43WPx8+PAgQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-x64-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-x64-gnu/-/nice-linux-x64-gnu-1.1.1.tgz", + "integrity": "sha512-/gaNz3R92t+dcrfCw/96pDopcmec7oCcAQ3l/M+Zxr82KT4DljD37CpgrnXV+pJC263JkW572pdbP3hP+KjcIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-x64-musl": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-x64-musl/-/nice-linux-x64-musl-1.1.1.tgz", + "integrity": "sha512-xScCGnyj/oppsNPMnevsBe3pvNaoK7FGvMjT35riz9YdhB2WtTG47ZlbxtOLpjeO9SqqQ2J2igCmz6IJOD5JYw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-openharmony-arm64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-openharmony-arm64/-/nice-openharmony-arm64-1.1.1.tgz", + "integrity": "sha512-6uJPRVwVCLDeoOaNyeiW0gp2kFIM4r7PL2MczdZQHkFi9gVlgm+Vn+V6nTWRcu856mJ2WjYJiumEajfSm7arPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-win32-arm64-msvc": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-arm64-msvc/-/nice-win32-arm64-msvc-1.1.1.tgz", + "integrity": "sha512-uoTb4eAvM5B2aj/z8j+Nv8OttPf2m+HVx3UjA5jcFxASvNhQriyCQF1OB1lHL43ZhW+VwZlgvjmP5qF3+59atA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-win32-ia32-msvc": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-ia32-msvc/-/nice-win32-ia32-msvc-1.1.1.tgz", + "integrity": "sha512-CNQqlQT9MwuCsg1Vd/oKXiuH+TcsSPJmlAFc5frFyX/KkOh0UpBLEj7aoY656d5UKZQMQFP7vJNa1DNUNORvug==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-win32-x64-msvc": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-x64-msvc/-/nice-win32-x64-msvc-1.1.1.tgz", + "integrity": "sha512-vB+4G/jBQCAh0jelMTY3+kgFy00Hlx2f2/1zjMoH821IbplbWZOkLiTYXQkygNTzQJTq5cvwBDgn2ppHD+bglQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.0.3.tgz", + "integrity": "sha512-rZxtMsLwjdXkMUGC3WwsPwLNVqVqnTJT6MNIB6e+5fhMcSCPP0AOsNWuMQ5mdCq6HNjs/ZeWAEchpqeprqBD2Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.5", + "@emnapi/runtime": "^1.4.5", + "@tybys/wasm-util": "^0.10.0" + } + }, + "node_modules/@npmcli/agent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-3.0.0.tgz", + "integrity": "sha512-S79NdEgDQd/NGCay6TCoVzXSj74skRZIKJcpJjC5lOq34SZzyI6MqtiiWoiVWoVrTcGjNeC4ipbh1VIHlpfF5Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^10.0.1", + "socks-proxy-agent": "^8.0.3" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/agent/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@npmcli/fs": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-4.0.0.tgz", + "integrity": "sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/git": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-6.0.3.tgz", + "integrity": "sha512-GUYESQlxZRAdhs3UhbB6pVRNUELQOHXwK9ruDkwmCv2aZ5y0SApQzUJCg02p3A7Ue2J5hxvlk1YI53c00NmRyQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/promise-spawn": "^8.0.0", + "ini": "^5.0.0", + "lru-cache": "^10.0.1", + "npm-pick-manifest": "^10.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/git/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/@npmcli/git/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@npmcli/git/node_modules/which": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/installed-package-contents": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-3.0.0.tgz", + "integrity": "sha512-fkxoPuFGvxyrH+OQzyTkX2LUEamrF4jZSmxjAtPPHHGO0dqsQ8tTKjnIS8SAnPHdk2I03BDtSMR5K/4loKg79Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "npm-bundled": "^4.0.0", + "npm-normalize-package-bin": "^4.0.0" + }, + "bin": { + "installed-package-contents": "bin/index.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/node-gyp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-4.0.0.tgz", + "integrity": "sha512-+t5DZ6mO/QFh78PByMq1fGSAub/agLJZDRfJRMeOSNCt8s9YVlTjmGpIPwPhvXTGUIJk+WszlT0rQa1W33yzNA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/package-json": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-6.2.0.tgz", + "integrity": "sha512-rCNLSB/JzNvot0SEyXqWZ7tX2B5dD2a1br2Dp0vSYVo5jh8Z0EZ7lS9TsZ1UtziddB1UfNUaMCc538/HztnJGA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^6.0.0", + "glob": "^10.2.2", + "hosted-git-info": "^8.0.0", + "json-parse-even-better-errors": "^4.0.0", + "proc-log": "^5.0.0", + "semver": "^7.5.3", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/package-json/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/@npmcli/package-json/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@npmcli/package-json/node_modules/hosted-git-info": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.1.0.tgz", + "integrity": "sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/package-json/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@npmcli/package-json/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/@npmcli/promise-spawn": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-8.0.2.tgz", + "integrity": "sha512-/bNJhjc+o6qL+Dwz/bqfTQClkEO5nTQ1ZEcdCkAQjhkZMHIh22LPG7fNh1enJP1NKWDqYiiABnjFCY7E0zHYtQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "which": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/promise-spawn/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/@npmcli/promise-spawn/node_modules/which": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/redact": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-3.2.2.tgz", + "integrity": "sha512-7VmYAmk4csGv08QzrDKScdzn11jHPFGyqJW39FyPgPuAp3zIaUmuCo1yxw9aGs+NEJuTGQ9Gwqpt93vtJubucg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/run-script": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-9.1.0.tgz", + "integrity": "sha512-aoNSbxtkePXUlbZB+anS1LqsJdctG5n3UVhfU47+CDdwMi6uNTBMF9gPcQRnqghQd2FGzcwwIFBruFMxjhBewg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/node-gyp": "^4.0.0", + "@npmcli/package-json": "^6.0.0", + "@npmcli/promise-spawn": "^8.0.0", + "node-gyp": "^11.0.0", + "proc-log": "^5.0.0", + "which": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/run-script/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/@npmcli/run-script/node_modules/which": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@oxc-project/runtime": { + "version": "0.81.0", + "resolved": "https://registry.npmjs.org/@oxc-project/runtime/-/runtime-0.81.0.tgz", + "integrity": "sha512-zm/LDVOq9FEmHiuM8zO4DWirv0VP2Tv2VsgaiHby9nvpq+FVrcqNYgv+TysLKOITQXWZj/roluTxFvpkHP0Iuw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.81.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.81.0.tgz", + "integrity": "sha512-CnOqkybZK8z6Gx7Wb1qF7AEnSzbol1WwcIzxYOr8e91LytGOjo0wCpgoYWZo8sdbpqX+X+TJayIzo4Pv0R/KjA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@parcel/watcher": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", + "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^1.0.3", + "is-glob": "^4.0.3", + "micromatch": "^4.0.5", + "node-addon-api": "^7.0.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.1", + "@parcel/watcher-darwin-arm64": "2.5.1", + "@parcel/watcher-darwin-x64": "2.5.1", + "@parcel/watcher-freebsd-x64": "2.5.1", + "@parcel/watcher-linux-arm-glibc": "2.5.1", + "@parcel/watcher-linux-arm-musl": "2.5.1", + "@parcel/watcher-linux-arm64-glibc": "2.5.1", + "@parcel/watcher-linux-arm64-musl": "2.5.1", + "@parcel/watcher-linux-x64-glibc": "2.5.1", + "@parcel/watcher-linux-x64-musl": "2.5.1", + "@parcel/watcher-win32-arm64": "2.5.1", + "@parcel/watcher-win32-ia32": "2.5.1", + "@parcel/watcher-win32-x64": "2.5.1" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", + "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", + "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", + "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", + "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", + "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", + "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", + "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", + "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", + "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", + "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", + "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", + "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", + "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher/node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/@parcel/watcher/node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@progress/jszip-esm": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@progress/jszip-esm/-/jszip-esm-1.0.4.tgz", + "integrity": "sha512-A5i26JcTosFKeHCrklarNsByW3RUJd8osRq69eskZgIaq05weTCXdpztlFMwrHpgOGods1D0WFoSQcMNE0eI8Q==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@progress/pako-esm": "^1.0.1" + } + }, + "node_modules/@progress/kendo-angular-buttons": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/@progress/kendo-angular-buttons/-/kendo-angular-buttons-20.0.3.tgz", + "integrity": "sha512-X0I5fyl0RUAhm8cbATeGFNC6XhnjsPyIpGXWn4s5po93T9BxGeFMpom/jKsIYNQPrRVYbYSo9TheC5/xGh4TuQ==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@progress/kendo-angular-schematics": "20.0.3", + "@progress/kendo-common": "^1.0.1", + "@progress/kendo-webspeech-common": "1.0.1", + "tslib": "^2.3.1" + }, + "peerDependencies": { + "@angular/animations": "16 - 20", + "@angular/common": "16 - 20", + "@angular/core": "16 - 20", + "@angular/platform-browser": "16 - 20", + "@progress/kendo-angular-common": "20.0.3", + "@progress/kendo-angular-icons": "20.0.3", + "@progress/kendo-angular-l10n": "20.0.3", + "@progress/kendo-angular-popup": "20.0.3", + "@progress/kendo-licensing": "^1.7.0", + "rxjs": "^6.5.3 || ^7.0.0" + } + }, + "node_modules/@progress/kendo-angular-charts": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/@progress/kendo-angular-charts/-/kendo-angular-charts-20.0.3.tgz", + "integrity": "sha512-8VKilTQufdAuel5yiOdcO5+bf53O3JP/DclPKbIhu18ydjudXlV/zT/MW5n9RwCbYbegsCMPU9mVyvUJI4uFOw==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@progress/kendo-angular-schematics": "20.0.3", + "@progress/kendo-charts": "2.8.0", + "@progress/kendo-svg-icons": "^4.0.0", + "tslib": "^2.3.1" + }, + "peerDependencies": { + "@angular/animations": "16 - 20", + "@angular/common": "16 - 20", + "@angular/core": "16 - 20", + "@angular/platform-browser": "16 - 20", + "@progress/kendo-angular-common": "20.0.3", + "@progress/kendo-angular-icons": "20.0.3", + "@progress/kendo-angular-intl": "20.0.3", + "@progress/kendo-angular-l10n": "20.0.3", + "@progress/kendo-angular-navigation": "20.0.3", + "@progress/kendo-angular-popup": "20.0.3", + "@progress/kendo-drawing": "^1.21.0", + "@progress/kendo-licensing": "^1.7.0", + "rxjs": "^6.5.3 || ^7.0.0" + } + }, + "node_modules/@progress/kendo-angular-common": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/@progress/kendo-angular-common/-/kendo-angular-common-20.0.3.tgz", + "integrity": "sha512-vhr4AJa0HvoecKcG9EF35Wf4CuzBFm/yd+xny/GO5LioTQreeEVqpkFLLwchToU8+Tq1NWhIKYTdwUefl3eB6A==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@progress/kendo-angular-schematics": "20.0.3", + "@progress/kendo-common": "^1.0.1", + "@progress/kendo-draggable": "^3.0.2", + "tslib": "^2.3.1" + }, + "peerDependencies": { + "@angular/animations": "16 - 20", + "@angular/common": "16 - 20", + "@angular/core": "16 - 20", + "rxjs": "^6.5.3 || ^7.0.0" + } + }, + "node_modules/@progress/kendo-angular-conversational-ui": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/@progress/kendo-angular-conversational-ui/-/kendo-angular-conversational-ui-20.0.3.tgz", + "integrity": "sha512-yeCEnG73tNajkdmeFju4uPEcLGZsxIhhbZkgzoaNjV+DHSfTc6Iep1glJGZ5d2wurkADHJJ4JuuW9DPFUEsnnQ==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@progress/kendo-angular-schematics": "20.0.3", + "tslib": "^2.3.1" + }, + "peerDependencies": { + "@angular/animations": "16 - 20", + "@angular/common": "16 - 20", + "@angular/core": "16 - 20", + "@angular/platform-browser": "16 - 20", + "@progress/kendo-angular-buttons": "20.0.3", + "@progress/kendo-angular-common": "20.0.3", + "@progress/kendo-angular-icons": "20.0.3", + "@progress/kendo-angular-inputs": "20.0.3", + "@progress/kendo-angular-intl": "20.0.3", + "@progress/kendo-angular-l10n": "20.0.3", + "@progress/kendo-angular-layout": "20.0.3", + "@progress/kendo-angular-menu": "20.0.3", + "@progress/kendo-angular-popup": "20.0.3", + "@progress/kendo-angular-toolbar": "20.0.3", + "@progress/kendo-angular-upload": "20.0.3", + "@progress/kendo-licensing": "^1.7.0", + "rxjs": "^6.5.3 || ^7.0.0" + } + }, + "node_modules/@progress/kendo-angular-dateinputs": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/@progress/kendo-angular-dateinputs/-/kendo-angular-dateinputs-20.0.3.tgz", + "integrity": "sha512-wZbsCiyDZA4cYUKIerOXnDfnr3Hqpy4SkE04gf5enno4nSUetrPBAhkKBCDUJBD0QfIft7TbFQC/DntWuwVjYA==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@progress/kendo-angular-schematics": "20.0.3", + "@progress/kendo-common": "^1.0.1", + "@progress/kendo-date-math": "^1.1.0", + "@progress/kendo-dateinputs-common": "^0.4.6", + "node-html-parser": "^7.0.1", + "tslib": "^2.3.1" + }, + "peerDependencies": { + "@angular/animations": "16 - 20", + "@angular/common": "16 - 20", + "@angular/core": "16 - 20", + "@angular/forms": "16 - 20", + "@angular/platform-browser": "16 - 20", + "@progress/kendo-angular-buttons": "20.0.3", + "@progress/kendo-angular-common": "20.0.3", + "@progress/kendo-angular-icons": "20.0.3", + "@progress/kendo-angular-intl": "20.0.3", + "@progress/kendo-angular-l10n": "20.0.3", + "@progress/kendo-angular-navigation": "20.0.3", + "@progress/kendo-angular-popup": "20.0.3", + "@progress/kendo-angular-utils": "20.0.3", + "@progress/kendo-licensing": "^1.7.0", + "rxjs": "^6.5.3 || ^7.0.0" + } + }, + "node_modules/@progress/kendo-angular-dialog": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/@progress/kendo-angular-dialog/-/kendo-angular-dialog-20.0.3.tgz", + "integrity": "sha512-HuMLwQMtIrTgHMEhBp3/Rpl8EtzHbbqFpG5TcNkwomkAptRyQwHqlzo6v/S94Zc5WtVQdwjmS8wQLq68XFH4GA==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@progress/kendo-angular-schematics": "20.0.3", + "@progress/kendo-popup-common": "^1.7.0", + "tslib": "^2.3.1" + }, + "peerDependencies": { + "@angular/animations": "16 - 20", + "@angular/common": "16 - 20", + "@angular/core": "16 - 20", + "@angular/platform-browser": "16 - 20", + "@progress/kendo-angular-buttons": "20.0.3", + "@progress/kendo-angular-common": "20.0.3", + "@progress/kendo-angular-icons": "20.0.3", + "@progress/kendo-angular-l10n": "20.0.3", + "@progress/kendo-licensing": "^1.7.0", + "rxjs": "^6.5.3 || ^7.0.0" + } + }, + "node_modules/@progress/kendo-angular-dropdowns": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/@progress/kendo-angular-dropdowns/-/kendo-angular-dropdowns-20.0.3.tgz", + "integrity": "sha512-RBSOTkkTsYIT9fjB5GiQVhuBr7kj4Guqryd7A9xTvkka+VdR1Yh3+vRjPkB/VvPVllzNK1qpBDCCzaNQ9HmgUw==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@progress/kendo-angular-schematics": "20.0.3", + "@progress/kendo-common": "^1.0.1", + "node-html-parser": "^7.0.1", + "tslib": "^2.3.1" + }, + "peerDependencies": { + "@angular/animations": "16 - 20", + "@angular/common": "16 - 20", + "@angular/core": "16 - 20", + "@angular/forms": "16 - 20", + "@angular/platform-browser": "16 - 20", + "@progress/kendo-angular-common": "20.0.3", + "@progress/kendo-angular-icons": "20.0.3", + "@progress/kendo-angular-l10n": "20.0.3", + "@progress/kendo-angular-navigation": "20.0.3", + "@progress/kendo-angular-popup": "20.0.3", + "@progress/kendo-angular-treeview": "20.0.3", + "@progress/kendo-angular-utils": "20.0.3", + "@progress/kendo-licensing": "^1.7.0", + "rxjs": "^6.5.3 || ^7.0.0" + } + }, + "node_modules/@progress/kendo-angular-editor": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/@progress/kendo-angular-editor/-/kendo-angular-editor-20.0.3.tgz", + "integrity": "sha512-bSI9GfHlYLghxjAXHTmVVCiZx61a6dCi4NFg97sE/AqUHaSE2Aa5tBVdCwb6MDezDJAb6RHyawI6MZxbI14L3w==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@progress/kendo-angular-schematics": "20.0.3", + "@progress/kendo-editor-common": "1.12.3", + "tslib": "^2.3.1" + }, + "peerDependencies": { + "@angular/animations": "16 - 20", + "@angular/common": "16 - 20", + "@angular/core": "16 - 20", + "@angular/forms": "16 - 20", + "@angular/platform-browser": "16 - 20", + "@progress/kendo-angular-buttons": "20.0.3", + "@progress/kendo-angular-common": "20.0.3", + "@progress/kendo-angular-dialog": "20.0.3", + "@progress/kendo-angular-dropdowns": "20.0.3", + "@progress/kendo-angular-icons": "20.0.3", + "@progress/kendo-angular-inputs": "20.0.3", + "@progress/kendo-angular-intl": "20.0.3", + "@progress/kendo-angular-l10n": "20.0.3", + "@progress/kendo-angular-label": "20.0.3", + "@progress/kendo-angular-layout": "20.0.3", + "@progress/kendo-angular-popup": "20.0.3", + "@progress/kendo-angular-toolbar": "20.0.3", + "@progress/kendo-drawing": "^1.21.0", + "@progress/kendo-licensing": "^1.7.0", + "rxjs": "^6.5.3 || ^7.0.0" + } + }, + "node_modules/@progress/kendo-angular-excel-export": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/@progress/kendo-angular-excel-export/-/kendo-angular-excel-export-20.0.3.tgz", + "integrity": "sha512-AzIgnkvun8Yr/AM859d+HSP9lZAfSIRc31GbPiU+mp4lVdUTLcCfb0L731tRcOKbC6CCAnxDMYSZLOxbb2CR6Q==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@progress/kendo-angular-schematics": "20.0.3", + "@progress/kendo-file-saver": "^1.0.0", + "@progress/kendo-intl": "^3.0.0", + "@progress/kendo-ooxml": "^1.9.0", + "tslib": "^2.3.1" + }, + "peerDependencies": { + "@angular/animations": "16 - 20", + "@angular/common": "16 - 20", + "@angular/core": "16 - 20", + "@angular/platform-browser": "16 - 20", + "@progress/kendo-angular-common": "20.0.3", + "@progress/kendo-angular-l10n": "20.0.3", + "@progress/kendo-licensing": "^1.7.0", + "rxjs": "^6.5.3 || ^7.0.0" + } + }, + "node_modules/@progress/kendo-angular-gauges": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/@progress/kendo-angular-gauges/-/kendo-angular-gauges-20.0.3.tgz", + "integrity": "sha512-HwIBcZxf1UmDwmP3HXnLA+AIJraDr4qDkNDFWN1QnHIcL+pD38QmCdvXX3Sl374aK/QYCdk2irENjpBWYV9Gpg==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@progress/kendo-angular-schematics": "20.0.3", + "@progress/kendo-charts": "2.8.0", + "tslib": "^2.3.1" + }, + "peerDependencies": { + "@angular/animations": "16 - 20", + "@angular/common": "16 - 20", + "@angular/core": "16 - 20", + "@angular/platform-browser": "16 - 20", + "@progress/kendo-angular-common": "20.0.3", + "@progress/kendo-angular-intl": "20.0.3", + "@progress/kendo-angular-l10n": "20.0.3", + "@progress/kendo-drawing": "^1.21.0", + "@progress/kendo-licensing": "^1.7.0", + "rxjs": "^6.5.3 || ^7.0.0" + } + }, + "node_modules/@progress/kendo-angular-grid": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/@progress/kendo-angular-grid/-/kendo-angular-grid-20.0.3.tgz", + "integrity": "sha512-l1zuhn7mPFXE1qcgozM4NyP4qzZTfok0kh1vM2+z+qUzS9Xj75VGemsEhKBMxguibdxIcqeGiqnttLXrR8dUcg==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@progress/kendo-angular-schematics": "20.0.3", + "@progress/kendo-common": "^1.0.1", + "@progress/kendo-file-saver": "^1.0.0", + "tslib": "^2.3.1" + }, + "peerDependencies": { + "@angular/animations": "16 - 20", + "@angular/common": "16 - 20", + "@angular/core": "16 - 20", + "@angular/forms": "16 - 20", + "@angular/platform-browser": "16 - 20", + "@progress/kendo-angular-buttons": "20.0.3", + "@progress/kendo-angular-common": "20.0.3", + "@progress/kendo-angular-conversational-ui": "20.0.3", + "@progress/kendo-angular-dateinputs": "20.0.3", + "@progress/kendo-angular-dropdowns": "20.0.3", + "@progress/kendo-angular-excel-export": "20.0.3", + "@progress/kendo-angular-icons": "20.0.3", + "@progress/kendo-angular-inputs": "20.0.3", + "@progress/kendo-angular-intl": "20.0.3", + "@progress/kendo-angular-l10n": "20.0.3", + "@progress/kendo-angular-label": "20.0.3", + "@progress/kendo-angular-layout": "20.0.3", + "@progress/kendo-angular-navigation": "20.0.3", + "@progress/kendo-angular-pager": "20.0.3", + "@progress/kendo-angular-pdf-export": "20.0.3", + "@progress/kendo-angular-popup": "20.0.3", + "@progress/kendo-angular-toolbar": "20.0.3", + "@progress/kendo-angular-utils": "20.0.3", + "@progress/kendo-data-query": "^1.0.0", + "@progress/kendo-drawing": "^1.21.0", + "@progress/kendo-licensing": "^1.7.0", + "rxjs": "^6.5.3 || ^7.0.0" + } + }, + "node_modules/@progress/kendo-angular-icons": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/@progress/kendo-angular-icons/-/kendo-angular-icons-20.0.3.tgz", + "integrity": "sha512-2QI6siZeCfC3ABWbm8It8fgm5RitqF4UEBooPFU0aBQPUdm/LtcQq653nozBRCAsUZUgRVOu9++D74LFp1JERw==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@progress/kendo-angular-schematics": "20.0.3", + "tslib": "^2.3.1" + }, + "peerDependencies": { + "@angular/animations": "16 - 20", + "@angular/common": "16 - 20", + "@angular/core": "16 - 20", + "@angular/platform-browser": "16 - 20", + "@progress/kendo-angular-common": "20.0.3", + "@progress/kendo-licensing": "^1.7.0", + "@progress/kendo-svg-icons": "^4.0.0", + "rxjs": "^6.5.3 || ^7.0.0" + } + }, + "node_modules/@progress/kendo-angular-indicators": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/@progress/kendo-angular-indicators/-/kendo-angular-indicators-20.0.3.tgz", + "integrity": "sha512-mZDIZWf93yp72aoKWHCPlgBmilVvjDMV+s6SPW5Rkc60xEpIoCbJlkVKw9/de6VrOYXT7ecHPbRyYR5sl4i7zQ==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@progress/kendo-angular-schematics": "20.0.3", + "tslib": "^2.3.1" + }, + "peerDependencies": { + "@angular/animations": "16 - 20", + "@angular/common": "16 - 20", + "@angular/core": "16 - 20", + "@angular/platform-browser": "16 - 20", + "@progress/kendo-angular-common": "20.0.3", + "@progress/kendo-angular-intl": "20.0.3", + "@progress/kendo-angular-l10n": "20.0.3", + "@progress/kendo-licensing": "^1.7.0", + "rxjs": "^6.5.3 || ^7.0.0" + } + }, + "node_modules/@progress/kendo-angular-inputs": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/@progress/kendo-angular-inputs/-/kendo-angular-inputs-20.0.3.tgz", + "integrity": "sha512-gIVbgltclEV2l2bqXTX5num6E9Jfe9vrhUKT6lbVCcYdvZKw7NPq66OFmC9utVNwy7RGN5MUgbqC7T66IQkibA==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@progress/kendo-angular-schematics": "20.0.3", + "@progress/kendo-common": "^1.0.1", + "@progress/kendo-draggable": "^3.0.0", + "@progress/kendo-inputs-common": "^3.1.0", + "tslib": "^2.3.1" + }, + "peerDependencies": { + "@angular/animations": "16 - 20", + "@angular/common": "16 - 20", + "@angular/core": "16 - 20", + "@angular/forms": "16 - 20", + "@angular/platform-browser": "16 - 20", + "@progress/kendo-angular-buttons": "20.0.3", + "@progress/kendo-angular-common": "20.0.3", + "@progress/kendo-angular-dialog": "20.0.3", + "@progress/kendo-angular-icons": "20.0.3", + "@progress/kendo-angular-intl": "20.0.3", + "@progress/kendo-angular-l10n": "20.0.3", + "@progress/kendo-angular-navigation": "20.0.3", + "@progress/kendo-angular-popup": "20.0.3", + "@progress/kendo-angular-utils": "20.0.3", + "@progress/kendo-drawing": "^1.21.0", + "@progress/kendo-licensing": "^1.7.0", + "rxjs": "^6.5.3 || ^7.0.0" + } + }, + "node_modules/@progress/kendo-angular-intl": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/@progress/kendo-angular-intl/-/kendo-angular-intl-20.0.3.tgz", + "integrity": "sha512-4f+kD+qTyyE6QGcXTlJoQHY1L9rMbDx70fzZfHBRZywyoURJ7Ha9Jpkr9wEEWCLworrh0Uph6TFOx02ly4li6g==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@progress/kendo-angular-schematics": "20.0.3", + "@progress/kendo-intl": "^3.1.0", + "tslib": "^2.3.1" + }, + "peerDependencies": { + "@angular/animations": "16 - 20", + "@angular/common": "16 - 20", + "@angular/core": "16 - 20", + "@angular/platform-browser": "16 - 20", + "@progress/kendo-angular-common": "20.0.3", + "@progress/kendo-licensing": "^1.7.0", + "rxjs": "^6.5.3 || ^7.0.0" + } + }, + "node_modules/@progress/kendo-angular-l10n": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/@progress/kendo-angular-l10n/-/kendo-angular-l10n-20.0.3.tgz", + "integrity": "sha512-wIdAbOYUlEP9aN9JImizy0ZJVeayZb3j1DiRf7+LRDLonVpoqKpS3dMVlwOyl5ndAoawg0CYswHx/SCPWViPKQ==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@progress/kendo-angular-schematics": "20.0.3", + "tslib": "^2.3.1" + }, + "peerDependencies": { + "@angular/animations": "16 - 20", + "@angular/common": "16 - 20", + "@angular/core": "16 - 20", + "@angular/platform-browser": "16 - 20", + "@progress/kendo-angular-common": "20.0.3", + "@progress/kendo-licensing": "^1.7.0", + "rxjs": "^6.5.3 || ^7.0.0" + } + }, + "node_modules/@progress/kendo-angular-label": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/@progress/kendo-angular-label/-/kendo-angular-label-20.0.3.tgz", + "integrity": "sha512-a0Ib0lTzaVjp6a1uVSrRAme+ba4IUTPY17uO1HnYnvQkMh652AsWy+5JgU5yakq6w/GSQVM2dnwLPxXez0xE5A==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@progress/kendo-angular-schematics": "20.0.3", + "tslib": "^2.3.1" + }, + "peerDependencies": { + "@angular/animations": "16 - 20", + "@angular/common": "16 - 20", + "@angular/core": "16 - 20", + "@angular/forms": "16 - 20", + "@angular/platform-browser": "16 - 20", + "@progress/kendo-angular-common": "20.0.3", + "@progress/kendo-angular-l10n": "20.0.3", + "@progress/kendo-licensing": "^1.7.0", + "rxjs": "^6.5.3 || ^7.0.0" + } + }, + "node_modules/@progress/kendo-angular-layout": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/@progress/kendo-angular-layout/-/kendo-angular-layout-20.0.3.tgz", + "integrity": "sha512-vR/YfMC2QDA2+G0MKuVCrS59rWXpccSq9+uyDkwtZSpIIx6AlAfvLDM9E/QisZsFYSC6ysLzszksXycMLRLWmg==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@progress/kendo-angular-schematics": "20.0.3", + "@progress/kendo-draggable": "^3.0.2", + "node-html-parser": "^7.0.1", + "tslib": "^2.3.1" + }, + "peerDependencies": { + "@angular/animations": "16 - 20", + "@angular/common": "16 - 20", + "@angular/core": "16 - 20", + "@angular/platform-browser": "16 - 20", + "@progress/kendo-angular-buttons": "20.0.3", + "@progress/kendo-angular-common": "20.0.3", + "@progress/kendo-angular-icons": "20.0.3", + "@progress/kendo-angular-intl": "20.0.3", + "@progress/kendo-angular-l10n": "20.0.3", + "@progress/kendo-angular-progressbar": "20.0.3", + "@progress/kendo-licensing": "^1.7.0", + "rxjs": "^6.5.3 || ^7.0.0" + } + }, + "node_modules/@progress/kendo-angular-listview": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/@progress/kendo-angular-listview/-/kendo-angular-listview-20.0.3.tgz", + "integrity": "sha512-q50iaSRtno82DcLdTi5AV5+Yk2jMXvz7/EUotHyjVyMa0k/3Q+9PzLMgsjlHzgd6KTFid+eJrgq8Q/wF3tLnTQ==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@progress/kendo-angular-schematics": "20.0.3", + "tslib": "^2.3.1" + }, + "peerDependencies": { + "@angular/animations": "16 - 20", + "@angular/common": "16 - 20", + "@angular/core": "16 - 20", + "@angular/forms": "16 - 20", + "@angular/platform-browser": "16 - 20", + "@progress/kendo-angular-buttons": "20.0.3", + "@progress/kendo-angular-common": "20.0.3", + "@progress/kendo-angular-icons": "20.0.3", + "@progress/kendo-angular-l10n": "20.0.3", + "@progress/kendo-angular-pager": "20.0.3", + "@progress/kendo-licensing": "^1.7.0", + "rxjs": "^6.5.3 || ^7.0.0" + } + }, + "node_modules/@progress/kendo-angular-map": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/@progress/kendo-angular-map/-/kendo-angular-map-20.0.3.tgz", + "integrity": "sha512-p5QpblvDUAZt4HdxJGxmuhc1gtMdq/KaIdoV1/lIJ06J3GQCbGcLAwq0rUHVtWroxQR2dhV6sfESR/F5Pg2uEw==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@progress/kendo-angular-schematics": "20.0.3", + "@progress/kendo-charts": "2.8.0", + "tslib": "^2.3.1" + }, + "peerDependencies": { + "@angular/animations": "16 - 20", + "@angular/common": "16 - 20", + "@angular/core": "16 - 20", + "@angular/platform-browser": "16 - 20", + "@progress/kendo-angular-common": "20.0.3", + "@progress/kendo-angular-icons": "20.0.3", + "@progress/kendo-angular-l10n": "20.0.3", + "@progress/kendo-angular-popup": "20.0.3", + "@progress/kendo-drawing": "^1.21.0", + "@progress/kendo-licensing": "^1.7.0", + "rxjs": "^6.4.0 || ^7.0.0" + } + }, + "node_modules/@progress/kendo-angular-menu": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/@progress/kendo-angular-menu/-/kendo-angular-menu-20.0.3.tgz", + "integrity": "sha512-6retMcGkONcUndpXT3whCJKP7OaUnf2Z2PZrWuZ94E8Z3FbPxaorbUbUmmLtCupP+1PQ8Q9RPyLdI89kxkDn9w==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@progress/kendo-angular-schematics": "20.0.3", + "tslib": "^2.3.1" + }, + "peerDependencies": { + "@angular/animations": "16 - 20", + "@angular/common": "16 - 20", + "@angular/core": "16 - 20", + "@angular/platform-browser": "16 - 20", + "@progress/kendo-angular-common": "20.0.3", + "@progress/kendo-angular-icons": "20.0.3", + "@progress/kendo-angular-l10n": "20.0.3", + "@progress/kendo-angular-popup": "20.0.3", + "@progress/kendo-licensing": "^1.7.0", + "rxjs": "^6.5.3 || ^7.0.0" + } + }, + "node_modules/@progress/kendo-angular-navigation": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/@progress/kendo-angular-navigation/-/kendo-angular-navigation-20.0.3.tgz", + "integrity": "sha512-Tcxub3lG5JQn8r7ub8b7VP6fVGhp12fueZdIHVYD5dTfuFIJNPPsRiuoTx+gFsSA5zX4KWHfDRYAMk8yf4QBEg==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@progress/kendo-angular-schematics": "20.0.3", + "tslib": "^2.3.1" + }, + "peerDependencies": { + "@angular/animations": "16 - 20", + "@angular/common": "16 - 20", + "@angular/core": "16 - 20", + "@angular/platform-browser": "16 - 20", + "@progress/kendo-angular-buttons": "20.0.3", + "@progress/kendo-angular-common": "20.0.3", + "@progress/kendo-angular-icons": "20.0.3", + "@progress/kendo-angular-l10n": "20.0.3", + "@progress/kendo-licensing": "^1.7.0", + "rxjs": "^6.5.3 || ^7.0.0" + } + }, + "node_modules/@progress/kendo-angular-pager": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/@progress/kendo-angular-pager/-/kendo-angular-pager-20.0.3.tgz", + "integrity": "sha512-lUMZ3GO30CveWntR3woKPPK9wKN+m2kvXEd5TnqsCx5RFYhzyLqkCRUpkkUeTPNGH31ykJay4mLLWBCsnINTyA==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@progress/kendo-angular-schematics": "20.0.3", + "tslib": "^2.3.1" + }, + "peerDependencies": { + "@angular/animations": "16 - 20", + "@angular/common": "16 - 20", + "@angular/core": "16 - 20", + "@angular/platform-browser": "16 - 20", + "@progress/kendo-angular-common": "20.0.3", + "@progress/kendo-angular-dropdowns": "20.0.3", + "@progress/kendo-angular-icons": "20.0.3", + "@progress/kendo-angular-inputs": "20.0.3", + "@progress/kendo-angular-l10n": "20.0.3", + "@progress/kendo-licensing": "^1.7.0", + "rxjs": "^6.5.3 || ^7.0.0" + } + }, + "node_modules/@progress/kendo-angular-pdf-export": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/@progress/kendo-angular-pdf-export/-/kendo-angular-pdf-export-20.0.3.tgz", + "integrity": "sha512-AcmNUwHY6n8bYL+rXvzKQSS95om99b/NtnsTaTbmZ7StSwSVelk+DCK88a7odZ9NWnForyMgxQNwlw5rceD1Aw==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@progress/kendo-angular-schematics": "20.0.3", + "@progress/kendo-file-saver": "^1.0.1", + "tslib": "^2.3.1" + }, + "peerDependencies": { + "@angular/animations": "16 - 20", + "@angular/common": "16 - 20", + "@angular/core": "16 - 20", + "@angular/platform-browser": "16 - 20", + "@progress/kendo-angular-common": "20.0.3", + "@progress/kendo-drawing": "^1.21.0", + "@progress/kendo-licensing": "^1.7.0", + "rxjs": "^6.5.3 || ^7.0.0" + } + }, + "node_modules/@progress/kendo-angular-popup": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/@progress/kendo-angular-popup/-/kendo-angular-popup-20.0.3.tgz", + "integrity": "sha512-2Uql09Gg1ZFsHpmMR/iV5U5EHvpeoykJ3ixZG/bCcy58i11ku/OjjlKnLkZPGGYZD/H6UFKBy+T2UOYqwDEHCw==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@progress/kendo-angular-schematics": "20.0.3", + "@progress/kendo-popup-common": "^1.7.0", + "tslib": "^2.3.1" + }, + "peerDependencies": { + "@angular/animations": "16 - 20", + "@angular/common": "16 - 20", + "@angular/core": "16 - 20", + "@angular/platform-browser": "16 - 20", + "@progress/kendo-angular-common": "20.0.3", + "@progress/kendo-licensing": "^1.7.0", + "rxjs": "^6.5.3 || ^7.0.0" + } + }, + "node_modules/@progress/kendo-angular-progressbar": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/@progress/kendo-angular-progressbar/-/kendo-angular-progressbar-20.0.3.tgz", + "integrity": "sha512-JPhgOSEOZFGUA5s7SCnTQZHodt3ee8IzlShzVcP6R/HhHRCrMq3G6cEXoLvj1GB5t5Ilc3eWlm9w4H7xEOuYVg==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@progress/kendo-angular-schematics": "20.0.3", + "tslib": "^2.3.1" + }, + "peerDependencies": { + "@angular/animations": "16 - 20", + "@angular/common": "16 - 20", + "@angular/core": "16 - 20", + "@angular/platform-browser": "16 - 20", + "@progress/kendo-angular-common": "20.0.3", + "@progress/kendo-angular-l10n": "20.0.3", + "@progress/kendo-licensing": "^1.7.0", + "rxjs": "^6.5.3 || ^7.0.0" + } + }, + "node_modules/@progress/kendo-angular-schematics": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/@progress/kendo-angular-schematics/-/kendo-angular-schematics-20.0.3.tgz", + "integrity": "sha512-2SVJOFfwrv1yy36HJgoU8veQlp268OzRh3D9E+hLzj7uYllxbDLIWEy5cGMt3PMUuikkqgARAVeTSbJP0wYKcw==", + "license": "SEE LICENSE in LICENSE.md", + "dependencies": { + "@progress/kendo-angular-schematics": "20.0.3", + "@types/semver": "^7.5.0", + "semver": "^7.5.3" + }, + "peerDependencies": { + "@angular-devkit/core": "16 - 20", + "@angular-devkit/schematics": "16 - 20", + "@angular/core": "16 - 20", + "@schematics/angular": "16 - 20", + "rxjs": "^6.5.3 || ^7.0.0" + } + }, + "node_modules/@progress/kendo-angular-scrollview": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/@progress/kendo-angular-scrollview/-/kendo-angular-scrollview-20.0.3.tgz", + "integrity": "sha512-Crd0K53wqetPMaQyt2I2FALK5/lKLRWQ72EwXG4lcokrMzLrqN663Pe2H2IgatCNt3jGQ9P91Xw0J0B1Hu6sXw==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@progress/kendo-angular-schematics": "20.0.3", + "tslib": "^2.3.1" + }, + "peerDependencies": { + "@angular/animations": "16 - 20", + "@angular/common": "16 - 20", + "@angular/core": "16 - 20", + "@angular/platform-browser": "16 - 20", + "@progress/kendo-angular-common": "20.0.3", + "@progress/kendo-angular-icons": "20.0.3", + "@progress/kendo-angular-l10n": "20.0.3", + "@progress/kendo-licensing": "^1.7.0", + "rxjs": "^6.5.3 || ^7.0.0" + } + }, + "node_modules/@progress/kendo-angular-toolbar": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/@progress/kendo-angular-toolbar/-/kendo-angular-toolbar-20.0.3.tgz", + "integrity": "sha512-CM1Pd8I13eBDbXpRKVOj9C4YbwsiWCa99UCkdbaDVvfTJ7YcDkEvWh9xI5n5FsIYHp3p0DakyNUWeX4iMsAIUA==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@progress/kendo-angular-schematics": "20.0.3", + "node-html-parser": "^7.0.1", + "tslib": "^2.3.1" + }, + "peerDependencies": { + "@angular/animations": "16 - 20", + "@angular/common": "16 - 20", + "@angular/core": "16 - 20", + "@angular/platform-browser": "16 - 20", + "@progress/kendo-angular-buttons": "20.0.3", + "@progress/kendo-angular-common": "20.0.3", + "@progress/kendo-angular-icons": "20.0.3", + "@progress/kendo-angular-indicators": "20.0.3", + "@progress/kendo-angular-l10n": "20.0.3", + "@progress/kendo-angular-popup": "20.0.3", + "@progress/kendo-licensing": "^1.7.0", + "rxjs": "^6.5.3 || ^7.0.0" + } + }, + "node_modules/@progress/kendo-angular-tooltip": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/@progress/kendo-angular-tooltip/-/kendo-angular-tooltip-20.0.3.tgz", + "integrity": "sha512-rEf9LADFa8IXu7zihhV++BtWoi7PgKyh+CKqrUMyQUz/yqSJ2xz7nnED6W6u7fO1GqOyh9sI68K+/tkeNyUkZA==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@progress/kendo-angular-schematics": "20.0.3", + "tslib": "^2.3.1" + }, + "peerDependencies": { + "@angular/animations": "16 - 20", + "@angular/common": "16 - 20", + "@angular/core": "16 - 20", + "@angular/platform-browser": "16 - 20", + "@progress/kendo-angular-common": "20.0.3", + "@progress/kendo-angular-icons": "20.0.3", + "@progress/kendo-angular-l10n": "20.0.3", + "@progress/kendo-angular-popup": "20.0.3", + "@progress/kendo-licensing": "^1.7.0", + "rxjs": "^6.5.3 || ^7.0.0" + } + }, + "node_modules/@progress/kendo-angular-treeview": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/@progress/kendo-angular-treeview/-/kendo-angular-treeview-20.0.3.tgz", + "integrity": "sha512-ge/uuWXXB5BEt3N5CJ2L7v0FQtvifL+Ftigq1Yi2GBqEKSWcPsEx/lpoDI4drbr/QyUz9PgYUzE4TZDMB2W0Bw==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@progress/kendo-angular-schematics": "20.0.3", + "@progress/kendo-common": "^1.0.1", + "@progress/kendo-draggable": "^3.0.2", + "tslib": "^2.3.1" + }, + "peerDependencies": { + "@angular/animations": "16 - 20", + "@angular/common": "16 - 20", + "@angular/core": "16 - 20", + "@angular/platform-browser": "16 - 20", + "@progress/kendo-angular-common": "20.0.3", + "@progress/kendo-angular-icons": "20.0.3", + "@progress/kendo-angular-inputs": "20.0.3", + "@progress/kendo-angular-l10n": "20.0.3", + "@progress/kendo-licensing": "^1.7.0", + "rxjs": "^6.5.3 || ^7.0.0" + } + }, + "node_modules/@progress/kendo-angular-upload": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/@progress/kendo-angular-upload/-/kendo-angular-upload-20.0.3.tgz", + "integrity": "sha512-IXFRsEQeARFnkgyiaNP686vOFSNvYujcmqfQoXDKevgV3XCFfNLQZRAD34rT04m1eFEnbeo6ZbpSYV1VQfmcrA==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@progress/kendo-angular-schematics": "20.0.3", + "tslib": "^2.3.1" + }, + "peerDependencies": { + "@angular/animations": "16 - 20", + "@angular/common": "16 - 20", + "@angular/core": "16 - 20", + "@angular/forms": "16 - 20", + "@angular/platform-browser": "16 - 20", + "@progress/kendo-angular-buttons": "20.0.3", + "@progress/kendo-angular-common": "20.0.3", + "@progress/kendo-angular-icons": "20.0.3", + "@progress/kendo-angular-l10n": "20.0.3", + "@progress/kendo-angular-progressbar": "20.0.3", + "@progress/kendo-licensing": "^1.7.0", + "rxjs": "^6.5.3 || ^7.0.0" + } + }, + "node_modules/@progress/kendo-angular-utils": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/@progress/kendo-angular-utils/-/kendo-angular-utils-20.0.3.tgz", + "integrity": "sha512-MIkQLxPToryj33ed9RX/DKMnhG32bpCC5h8rD6WlmCpFBCwxFKrtovluQrc1lK6fMIT9yXzwmSHi3hILtc6O+w==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@progress/kendo-angular-schematics": "20.0.3", + "@progress/kendo-draggable-common": "^0.2.3", + "tslib": "^2.3.1" + }, + "peerDependencies": { + "@angular/animations": "16 - 20", + "@angular/common": "16 - 20", + "@angular/core": "16 - 20", + "@angular/platform-browser": "16 - 20", + "@progress/kendo-angular-common": "20.0.3", + "@progress/kendo-licensing": "^1.7.0", + "rxjs": "^6.5.3 || ^7.0.0" + } + }, + "node_modules/@progress/kendo-charts": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@progress/kendo-charts/-/kendo-charts-2.8.0.tgz", + "integrity": "sha512-KHP6RgsCSFDTO1ZvdWgqK/9t0z/KEthhrqAmPC/EC5dqezGqfJJ5H1Y/Cw1e9HeQ4qgtZXz/q8R84cCJg/hTiQ==", + "license": "SEE LICENSE IN license.txt", + "peerDependencies": { + "@progress/kendo-drawing": "^1.21.0" + } + }, + "node_modules/@progress/kendo-common": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@progress/kendo-common/-/kendo-common-1.0.2.tgz", + "integrity": "sha512-PHxnquetSmtmXiF4dmlQiypzXaFLUEPK3VAOHxmnRDrLxaPrcZfaW9FOOiyur8hv4QmXlohISMwMElZS8Xi1Ag==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "tslib": "^1.7.0" + } + }, + "node_modules/@progress/kendo-common/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "license": "0BSD" + }, + "node_modules/@progress/kendo-data-query": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@progress/kendo-data-query/-/kendo-data-query-1.7.1.tgz", + "integrity": "sha512-1ax6mNx1XVr5A8d9VhzuZprAq1il7oES+XwIGnLikCmkKnFk+jcBmGVksw4MKB+kcdGzQPd4RV4iO6G0kaknEA==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "tslib": "^2.8.1" + } + }, + "node_modules/@progress/kendo-date-math": { + "version": "1.5.15", + "resolved": "https://registry.npmjs.org/@progress/kendo-date-math/-/kendo-date-math-1.5.15.tgz", + "integrity": "sha512-muQ5LtWARWArq0TpexjtyGmpFbF93Vwc9C+bPVOuidipBcR3j0SuLfO4b7pAmE4AWbEXScOEgkt/6Uw7iL/cZA==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "tslib": "^2.4.1" + }, + "engines": { + "node": ">=20.0.0", + "npm": ">=10.0.0" + } + }, + "node_modules/@progress/kendo-dateinputs-common": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/@progress/kendo-dateinputs-common/-/kendo-dateinputs-common-0.4.6.tgz", + "integrity": "sha512-ZEU6wCTXxmmS3qDUyKuk+5/uNUtZJ+ONJ/F8UALb5Dqk+AUAJSh/dKX0HvZLK9PcIV/s/8aIFlIAXWoqBFaG1w==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@progress/kendo-date-math": "^1.5.9", + "tslib": "^2.4.1" + }, + "engines": { + "node": ">=20.0.0", + "npm": ">=10.0.0" + } + }, + "node_modules/@progress/kendo-draggable": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@progress/kendo-draggable/-/kendo-draggable-3.1.0.tgz", + "integrity": "sha512-S5AHF9uiy44um+06ABJcjZn/wpO3ZwLahd2BhiTd7NeBVPt5lkj2bjdmkd88GEIIBKmT7FOK308WUt5/MmKVTQ==", + "license": "Apache-2.0" + }, + "node_modules/@progress/kendo-draggable-common": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@progress/kendo-draggable-common/-/kendo-draggable-common-0.2.3.tgz", + "integrity": "sha512-e1FraFsT7zwevswzZlQYL//K+fzmRUvkr/4emp51dzkARLDtGd95BtPNSoXYRG5xYHeueKBS75hzVwQI6Dm3Dg==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "tslib": "^1.7.0" + } + }, + "node_modules/@progress/kendo-draggable-common/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "license": "0BSD" + }, + "node_modules/@progress/kendo-drawing": { + "version": "1.22.2", + "resolved": "https://registry.npmjs.org/@progress/kendo-drawing/-/kendo-drawing-1.22.2.tgz", + "integrity": "sha512-/X5sSgB4XWTtPfOpew0PKwkRJ9hABcmKEWLMVI/zIiiZDxm65txDWUmllsIOhOL4ADr5M89qJfSFbNRq8MRIHA==", + "license": "See license in LICENSE.md", + "dependencies": { + "@progress/kendo-common": "^1.0.1", + "@progress/pako-esm": "^1.0.1" + } + }, + "node_modules/@progress/kendo-editor-common": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/@progress/kendo-editor-common/-/kendo-editor-common-1.12.3.tgz", + "integrity": "sha512-vuB0ZE60uBqBTPOEP4YHkr1yJIVB4/HEq5YzrpH1CRuhn1Oexq1r0hItw+VgAEw3cfwdXQnCi20ZtnTvH7GepQ==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@progress/kendo-common": "^1.0.2", + "prosemirror-commands": "1.7.1", + "prosemirror-dropcursor": "1.8.2", + "prosemirror-gapcursor": "1.3.2", + "prosemirror-history": "1.4.1", + "prosemirror-inputrules": "1.5.0", + "prosemirror-keymap": "1.2.3", + "prosemirror-model": "1.25.1", + "prosemirror-schema-list": "1.5.1", + "prosemirror-state": "1.4.3", + "prosemirror-tables": "1.7.1", + "prosemirror-transform": "1.10.4", + "prosemirror-view": "1.39.3", + "tslib": "^2.8.1" + } + }, + "node_modules/@progress/kendo-file-saver": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@progress/kendo-file-saver/-/kendo-file-saver-1.1.2.tgz", + "integrity": "sha512-hWpJ67L8b2+GIhsIWR09NgGaEh87jvcHv7kScC671cbVWJycXTGqdy3ZoI0pzIaH8K0IgP2TNkF1ay4HGxe+pg==", + "license": "Apache-2.0" + }, + "node_modules/@progress/kendo-inputs-common": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@progress/kendo-inputs-common/-/kendo-inputs-common-3.1.2.tgz", + "integrity": "sha512-iCYz0yuDeR/YS+mW5tY7UkWEsf5EjdTKLpx8O5VJAFFcd2dxLCK/HAihSHS+KpWqIZIw4EIWeR6lzGQf0nN0nA==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=20.0.0", + "npm": ">=10.0.0" + }, + "peerDependencies": { + "@progress/kendo-drawing": "^1.21.2" + } + }, + "node_modules/@progress/kendo-intl": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@progress/kendo-intl/-/kendo-intl-3.1.2.tgz", + "integrity": "sha512-rOtMppQSrScwryMfeQSOdsnRi9Oj1l08HFoEC2ticZ0T2N0/JN9CHt+fuToRx5onXK7QkcbbuNM0D09o8TeeMw==", + "license": "Apache-2.0" + }, + "node_modules/@progress/kendo-licensing": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@progress/kendo-licensing/-/kendo-licensing-1.7.1.tgz", + "integrity": "sha512-7uYrYjd++/4SVYrjoywT88EbG4pnpkAnYWRpshToVwsSDoprwurtW1Jo6Pzz0kwCIbQYBinY5PDroSdrE0tgVQ==", + "hasInstallScript": true, + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "glob": "^10.4.5", + "jsonwebtoken": "^9.0.2" + }, + "bin": { + "kendo-ui-license": "bin/kendo-ui-license.js" + } + }, + "node_modules/@progress/kendo-licensing/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==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@progress/kendo-licensing/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@progress/kendo-licensing/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@progress/kendo-ooxml": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@progress/kendo-ooxml/-/kendo-ooxml-1.9.3.tgz", + "integrity": "sha512-RUrn3k6IiWteD79vaRTkbRWhYKiHr0QzBswmuEnNBj0UySVjJ5ScPOytpRYcwkKE4sUq1JnclmoGaWpjX/o6og==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@progress/jszip-esm": "^1.0.4", + "@progress/pako-esm": "^1.0.1" + }, + "engines": { + "node": ">=20.0.0", + "npm": ">=10.0.0" + } + }, + "node_modules/@progress/kendo-popup-common": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/@progress/kendo-popup-common/-/kendo-popup-common-1.9.5.tgz", + "integrity": "sha512-HpUy0PLbS/bhhs/FxxFGaJxxbskQbAVMPWE5i6qxQ5G1krQHWApJ9B+43Rx5IQiWtoenjCGtYv0QSZlRQxDqsg==", + "license": "Apache-2.0" + }, + "node_modules/@progress/kendo-svg-icons": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@progress/kendo-svg-icons/-/kendo-svg-icons-4.5.0.tgz", + "integrity": "sha512-KO7KZNrm1Fi8jR4HLvaPmwzzsvswAZUlm6jQ0jHz9ck/iuREo8YWIUaVQxnzcmtCT3nRyrYMofLa13eR4PEhQg==", + "license": "Apache-2.0" + }, + "node_modules/@progress/kendo-theme-core": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/@progress/kendo-theme-core/-/kendo-theme-core-12.1.0.tgz", + "integrity": "sha512-31Ibhb11hxJVXY457q8elrxeGFDO4g3ANKwB2XTzNksufwY/ky7DRVGQkUs6nBmVcI2us8B997l7l12kEhMhzg==", + "license": "Apache-2.0" + }, + "node_modules/@progress/kendo-theme-default": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/@progress/kendo-theme-default/-/kendo-theme-default-12.1.0.tgz", + "integrity": "sha512-KaUi+cC9IpxKE9Nd8iunDUioxoHMj5pl69LRiOnr2nc+5kxvaCC3pEY/tcEU4o4amPa9QEFaQdzAFg5jeUaT+Q==", + "license": "Apache-2.0", + "dependencies": { + "@progress/kendo-svg-icons": "^4.5.0", + "@progress/kendo-theme-core": "12.1.0", + "@progress/kendo-theme-utils": "12.1.0" + } + }, + "node_modules/@progress/kendo-theme-utils": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/@progress/kendo-theme-utils/-/kendo-theme-utils-12.1.0.tgz", + "integrity": "sha512-3tZY/x6Ly/lcfiVNEw36hyhR6FnF9IM3MWUOAPa40+zNHYIDLvCcuNTN5yBovUoCTlrVg9FszxNstmun3qAObw==", + "license": "Apache-2.0", + "dependencies": { + "@progress/kendo-theme-core": "12.1.0" + } + }, + "node_modules/@progress/kendo-webspeech-common": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@progress/kendo-webspeech-common/-/kendo-webspeech-common-1.0.1.tgz", + "integrity": "sha512-c5IHS5vakAtxbaVfd26rM1dZnt9JfUOqWFq7gK8mU3HaZ9rqoH+DEIn72bbiRs+Ju5e81d7H1Q/nMx8u09pwRg==", + "license": "SEE LICENSE IN LICENSE.md" + }, + "node_modules/@progress/pako-esm": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@progress/pako-esm/-/pako-esm-1.0.1.tgz", + "integrity": "sha512-O4A3b1EuE9Xe1pC3Xz9Tcn1M/CYrL71f4y/5TXeytOVTkmkzBgYW97fYP2f+54H0e0erWRaqV/kUUB/a8Uxfbw==", + "license": "SEE LICENSE IN LICENSE.md" + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-beta.32", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-beta.32.tgz", + "integrity": "sha512-Gs+313LfR4Ka3hvifdag9r44WrdKQaohya7ZXUXzARF7yx0atzFlVZjsvxtKAw1Vmtr4hB/RjUD1jf73SW7zDw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-beta.32", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-beta.32.tgz", + "integrity": "sha512-W8oMqzGcI7wKPXUtS3WJNXzbghHfNiuM1UBAGpVb+XlUCgYRQJd2PRGP7D3WGql3rR3QEhUvSyAuCBAftPQw6Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-beta.32", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-beta.32.tgz", + "integrity": "sha512-pM4c4sKUk37noJrnnDkJknLhCsfZu7aWyfe67bD0GQHfzAPjV16wPeD9CmQg4/0vv+5IfHYaa4VE536xbA+W0Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-beta.32", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-beta.32.tgz", + "integrity": "sha512-M8SUgFlYb5kJJWcFC8gUMRiX4WLFxPKMed3SJ2YrxontgIrEcpizPU8nLNVsRYEStoSfKHKExpQw3OP6fm+5bw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-beta.32", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-beta.32.tgz", + "integrity": "sha512-FuQpbNC/hE//bvv29PFnk0AtpJzdPdYl5CMhlWPovd9g3Kc3lw9TrEPIbL7gRPUdhKAiq6rVaaGvOnXxsa0eww==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-beta.32", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-beta.32.tgz", + "integrity": "sha512-hRZygRlaGCjcNTNY9GV7dDI18sG1dK3cc7ujHq72LoDad23zFDUGMQjiSxHWK+/r92iMV+j2MiHbvzayxqynsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-beta.32", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-beta.32.tgz", + "integrity": "sha512-HzgT6h+CXLs+GKAU0Wvkt3rvcv0CmDBsDjlPhh4GHysOKbG9NjpKYX2zvjx671E9pGbTvcPpwy7gGsy7xpu+8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-beta.32", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-beta.32.tgz", + "integrity": "sha512-Ab/wbf6gdzphDbsg51UaxsC93foQ7wxhtg0SVCXd25BrV4MAJ1HoDtKN/f4h0maFmJobkqYub2DlmoasUzkvBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-beta.32", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-beta.32.tgz", + "integrity": "sha512-VoxqGEfh5A1Yx+zBp/FR5QwAbtzbuvky2SVc+ii4g1gLD4zww6mt/hPi5zG+b88zYPFBKHpxMtsz9cWqXU5V5Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-beta.32", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-beta.32.tgz", + "integrity": "sha512-qZ1ViyOUDGbiZrSAJ/FIAhYUElDfVxxFW6DLT/w4KeoZN3HsF4jmRP95mXtl51/oGrqzU9l9Q2f7/P4O/o2ZZA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-beta.32", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-beta.32.tgz", + "integrity": "sha512-hEkG3wD+f3wytV0lqwb/uCrXc4r4Ny/DWJFJPfQR3VeMWplhWGgSHNwZc2Q7k86Yi36f9NNzzWmrIuvHI9lCVw==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^1.0.3" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-beta.32", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-beta.32.tgz", + "integrity": "sha512-k3MvDf8SiA7uP2ikP0unNouJ2YCrnwi7xcVW+RDgMp5YXVr3Xu6svmT3HGn0tkCKUuPmf+uy8I5uiHt5qWQbew==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rolldown/binding-win32-ia32-msvc": { + "version": "1.0.0-beta.32", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.0.0-beta.32.tgz", + "integrity": "sha512-wAi/FxGh7arDOUG45UmnXE1sZUa0hY4cXAO2qWAjFa3f7bTgz/BqwJ7XN5SUezvAJPNkME4fEpInfnBvM25a0w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-beta.32", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-beta.32.tgz", + "integrity": "sha512-Ej0i4PZk8ltblZtzVK8ouaGUacUtxRmTm5S9794mdyU/tYxXjAJNseOfxrnHpMWKjMDrOKbqkPqJ52T9NR4LQQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.32", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.32.tgz", + "integrity": "sha512-QReCdvxiUZAPkvp1xpAg62IeNzykOFA6syH2CnClif4YmALN1XKpB39XneL80008UbtMShthSVDKmrx05N1q/g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.49.0.tgz", + "integrity": "sha512-rlKIeL854Ed0e09QGYFlmDNbka6I3EQFw7iZuugQjMb11KMpJCLPFL4ZPbMfaEhLADEL1yx0oujGkBQ7+qW3eA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.49.0.tgz", + "integrity": "sha512-cqPpZdKUSQYRtLLr6R4X3sD4jCBO1zUmeo3qrWBCqYIeH8Q3KRL4F3V7XJ2Rm8/RJOQBZuqzQGWPjjvFUcYa/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.49.0.tgz", + "integrity": "sha512-99kMMSMQT7got6iYX3yyIiJfFndpojBmkHfTc1rIje8VbjhmqBXE+nb7ZZP3A5skLyujvT0eIUCUsxAe6NjWbw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.49.0.tgz", + "integrity": "sha512-y8cXoD3wdWUDpjOLMKLx6l+NFz3NlkWKcBCBfttUn+VGSfgsQ5o/yDUGtzE9HvsodkP0+16N0P4Ty1VuhtRUGg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.49.0.tgz", + "integrity": "sha512-3mY5Pr7qv4GS4ZvWoSP8zha8YoiqrU+e0ViPvB549jvliBbdNLrg2ywPGkgLC3cmvN8ya3za+Q2xVyT6z+vZqA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.49.0.tgz", + "integrity": "sha512-C9KzzOAQU5gU4kG8DTk+tjdKjpWhVWd5uVkinCwwFub2m7cDYLOdtXoMrExfeBmeRy9kBQMkiyJ+HULyF1yj9w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.49.0.tgz", + "integrity": "sha512-OVSQgEZDVLnTbMq5NBs6xkmz3AADByCWI4RdKSFNlDsYXdFtlxS59J+w+LippJe8KcmeSSM3ba+GlsM9+WwC1w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.49.0.tgz", + "integrity": "sha512-ZnfSFA7fDUHNa4P3VwAcfaBLakCbYaxCk0jUnS3dTou9P95kwoOLAMlT3WmEJDBCSrOEFFV0Y1HXiwfLYJuLlA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.49.0.tgz", + "integrity": "sha512-Z81u+gfrobVK2iV7GqZCBfEB1y6+I61AH466lNK+xy1jfqFLiQ9Qv716WUM5fxFrYxwC7ziVdZRU9qvGHkYIJg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.49.0.tgz", + "integrity": "sha512-zoAwS0KCXSnTp9NH/h9aamBAIve0DXeYpll85shf9NJ0URjSTzzS+Z9evmolN+ICfD3v8skKUPyk2PO0uGdFqg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.49.0.tgz", + "integrity": "sha512-2QyUyQQ1ZtwZGiq0nvODL+vLJBtciItC3/5cYN8ncDQcv5avrt2MbKt1XU/vFAJlLta5KujqyHdYtdag4YEjYQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.49.0.tgz", + "integrity": "sha512-k9aEmOWt+mrMuD3skjVJSSxHckJp+SiFzFG+v8JLXbc/xi9hv2icSkR3U7uQzqy+/QbbYY7iNB9eDTwrELo14g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.49.0.tgz", + "integrity": "sha512-rDKRFFIWJ/zJn6uk2IdYLc09Z7zkE5IFIOWqpuU0o6ZpHcdniAyWkwSUWE/Z25N/wNDmFHHMzin84qW7Wzkjsw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.49.0.tgz", + "integrity": "sha512-FkkhIY/hYFVnOzz1WeV3S9Bd1h0hda/gRqvZCMpHWDHdiIHn6pqsY3b5eSbvGccWHMQ1uUzgZTKS4oGpykf8Tw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.49.0.tgz", + "integrity": "sha512-gRf5c+A7QiOG3UwLyOOtyJMD31JJhMjBvpfhAitPAoqZFcOeK3Kc1Veg1z/trmt+2P6F/biT02fU19GGTS529A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.49.0.tgz", + "integrity": "sha512-BR7+blScdLW1h/2hB/2oXM+dhTmpW3rQt1DeSiCP9mc2NMMkqVgjIN3DDsNpKmezffGC9R8XKVOLmBkRUcK/sA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.49.0.tgz", + "integrity": "sha512-hDMOAe+6nX3V5ei1I7Au3wcr9h3ktKzDvF2ne5ovX8RZiAHEtX1A5SNNk4zt1Qt77CmnbqT+upb/umzoPMWiPg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.49.0.tgz", + "integrity": "sha512-wkNRzfiIGaElC9kXUT+HLx17z7D0jl+9tGYRKwd8r7cUqTL7GYAvgUY++U2hK6Ar7z5Z6IRRoWC8kQxpmM7TDA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.49.0.tgz", + "integrity": "sha512-gq5aW/SyNpjp71AAzroH37DtINDcX1Qw2iv9Chyz49ZgdOP3NV8QCyKZUrGsYX9Yyggj5soFiRCgsL3HwD8TdA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.49.0.tgz", + "integrity": "sha512-gEtqFbzmZLFk2xKh7g0Rlo8xzho8KrEFEkzvHbfUGkrgXOpZ4XagQ6n+wIZFNh1nTb8UD16J4nFSFKXYgnbdBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@schematics/angular": { + "version": "20.2.0", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-20.2.0.tgz", + "integrity": "sha512-7sZVj7hOcytQrPE17ixjzul9ih81IfXGcEZvr7fT77qy7Hm5rbMjxqSYxCTf3kAyBFRSLq/E8GTapPAjk2coOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": "20.2.0", + "@angular-devkit/schematics": "20.2.0", + "jsonc-parser": "3.3.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@sigstore/bundle": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-3.1.0.tgz", + "integrity": "sha512-Mm1E3/CmDDCz3nDhFKTuYdB47EdRFRQMOE/EAbiG1MJW77/w1b3P7Qx7JSrVJs8PfwOLOVcKQCHErIwCTyPbag==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.4.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@sigstore/core": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sigstore/core/-/core-2.0.0.tgz", + "integrity": "sha512-nYxaSb/MtlSI+JWcwTHQxyNmWeWrUXJJ/G4liLrGG7+tS4vAz6LF3xRXqLH6wPIVUoZQel2Fs4ddLx4NCpiIYg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@sigstore/protobuf-specs": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.4.3.tgz", + "integrity": "sha512-fk2zjD9117RL9BjqEwF7fwv7Q/P9yGsMV4MUJZ/DocaQJ6+3pKr+syBq1owU5Q5qGw5CUbXzm+4yJ2JVRDQeSA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@sigstore/sign": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-3.1.0.tgz", + "integrity": "sha512-knzjmaOHOov1Ur7N/z4B1oPqZ0QX5geUfhrVaqVlu+hl0EAoL4o+l0MSULINcD5GCWe3Z0+YJO8ues6vFlW0Yw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^3.1.0", + "@sigstore/core": "^2.0.0", + "@sigstore/protobuf-specs": "^0.4.0", + "make-fetch-happen": "^14.0.2", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@sigstore/tuf": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-3.1.1.tgz", + "integrity": "sha512-eFFvlcBIoGwVkkwmTi/vEQFSva3xs5Ot3WmBcjgjVdiaoelBLQaQ/ZBfhlG0MnG0cmTYScPpk7eDdGDWUcFUmg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.4.1", + "tuf-js": "^3.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@sigstore/verify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@sigstore/verify/-/verify-2.1.1.tgz", + "integrity": "sha512-hVJD77oT67aowHxwT4+M6PGOp+E2LtLdTK3+FC0lBO9T7sYwItDMXZ7Z07IDCvR1M717a4axbIWckrW67KMP/w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^3.1.0", + "@sigstore/core": "^2.0.0", + "@sigstore/protobuf-specs": "^0.4.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tufjs/canonical-json": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-2.0.0.tgz", + "integrity": "sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@tufjs/models": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-3.0.1.tgz", + "integrity": "sha512-UUYHISyhCU3ZgN8yaear3cGATHb3SMuKHsQ/nVbHXcmnBf+LzQ/cQfhNG+rfaSHgqGKNEm2cOCLVLELStUQ1JA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tufjs/canonical-json": "2.0.0", + "minimatch": "^9.0.5" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@tufjs/models/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/@tufjs/models/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/@tybys/wasm-util": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.0.tgz", + "integrity": "sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/cors": { + "version": "2.8.19", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", + "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/jasmine": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-5.1.9.tgz", + "integrity": "sha512-8t4HtkW4wxiPVedMpeZ63n3vlWxEIquo/zc1Tm8ElU+SqVV7+D3Na2PWaJUp179AzTragMWVwkMv7mvty0NfyQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.3.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.0.tgz", + "integrity": "sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.10.0" + } + }, + "node_modules/@types/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==", + "license": "MIT" + }, + "node_modules/@vitejs/plugin-basic-ssl": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-2.1.0.tgz", + "integrity": "sha512-dOxxrhgyDIEUADhb/8OlV9JIqYLgos03YorAueTIeOUskLJSEsfwCByjbu98ctXitUN3znXKp0bYD/WHSudCeA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "peerDependencies": { + "vite": "^6.0.0 || ^7.0.0" + } + }, + "node_modules/@yarnpkg/lockfile": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", + "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/abbrev": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-3.0.1.tgz", + "integrity": "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/algoliasearch": { + "version": "5.35.0", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.35.0.tgz", + "integrity": "sha512-Y+moNhsqgLmvJdgTsO4GZNgsaDWv8AOGAaPeIeHKlDn/XunoAqYbA+XNpBd1dW8GOXAUDyxC9Rxc7AV4kpFcIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/abtesting": "1.1.0", + "@algolia/client-abtesting": "5.35.0", + "@algolia/client-analytics": "5.35.0", + "@algolia/client-common": "5.35.0", + "@algolia/client-insights": "5.35.0", + "@algolia/client-personalization": "5.35.0", + "@algolia/client-query-suggestions": "5.35.0", + "@algolia/client-search": "5.35.0", + "@algolia/ingestion": "1.35.0", + "@algolia/monitoring": "1.35.0", + "@algolia/recommend": "5.35.0", + "@algolia/requester-browser-xhr": "5.35.0", + "@algolia/requester-fetch": "5.35.0", + "@algolia/requester-node-http": "5.35.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.0.tgz", + "integrity": "sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ansis": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.1.0.tgz", + "integrity": "sha512-BGcItUBWSMRgOCe+SVZJ+S7yTRG0eGt9cXAHev72yuGcY23hnLA7Bky5L/xLyPINoSN95geovfBkqoTlNZYa7w==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, + "node_modules/beasties": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/beasties/-/beasties-0.3.5.tgz", + "integrity": "sha512-NaWu+f4YrJxEttJSm16AzMIFtVldCvaJ68b1L098KpqXmxt9xOLtKoLkKxb8ekhOrLqEJAbvT6n6SEvB/sac7A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "css-select": "^6.0.0", + "css-what": "^7.0.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "htmlparser2": "^10.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.49", + "postcss-media-query-parser": "^0.2.3" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/body-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", + "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.0", + "http-errors": "^2.0.0", + "iconv-lite": "^0.6.3", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.0", + "type-is": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.25.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.3.tgz", + "integrity": "sha512-cDGv1kkDI4/0e5yON9yM5G/0A5u8sf5TnmdX5C9qHzI9PPu++sQ9zjm1k9NiOrf3riY4OkK0zSGqfvJyJsgCBQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001735", + "electron-to-chromium": "^1.5.204", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cacache": { + "version": "19.0.1", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-19.0.1.tgz", + "integrity": "sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/fs": "^4.0.0", + "fs-minipass": "^3.0.0", + "glob": "^10.2.2", + "lru-cache": "^10.0.1", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^7.0.2", + "ssri": "^12.0.0", + "tar": "^7.4.3", + "unique-filename": "^4.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/cacache/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/cacache/node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/cacache/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/cacache/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/cacache/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/cacache/node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/cacache/node_modules/tar": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", + "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/cacache/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001737", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001737.tgz", + "integrity": "sha512-BiloLiXtQNrY5UyF0+1nSJLXUENuhka2pzy2Fx5pGxqavdrxSCW4U6Pn/PoG3Efspi2frRbHpBV2XsrPE6EDlw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.0.tgz", + "integrity": "sha512-46QrSQFyVSEyYAgQ22hQ+zDa60YHA4fBstHmtSApj1Y5vKtG27fWowW03jCk5KcbXEWPZUIR894aARCA/G1kfQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chardet": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.0.tgz", + "integrity": "sha512-bNFETTG/pM5ryzQ9Ad0lJOTa6HWD/YsScAR3EnCPZRPlQh77JocYktSHOUHelyhm8IARL+o4c4F1bP5KVOjiRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/cli-truncate": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", + "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", + "dev": true, + "license": "MIT", + "dependencies": { + "slice-ansi": "^5.0.0", + "string-width": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 12" + } + }, + "node_modules/cliui": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", + "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "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", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/connect/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/connect/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/connect/node_modules/finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/connect/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/connect/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/connect/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-disposition": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-select": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-6.0.0.tgz", + "integrity": "sha512-rZZVSLle8v0+EY8QAkDWrKhpgt6SA5OtHsgBnsj6ZaLb5dmDVOWUDtQitd9ydxxvEjhewNudS6eTVU7uOyzvXw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^7.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "nth-check": "^2.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-7.0.0.tgz", + "integrity": "sha512-wD5oz5xibMOPHzy13CyGmogB3phdvcDaB5t0W/Nr5Z2O/agcB8YwOz6e2Lsp10pNDzBoDO9nVa3RGs/2BttpHQ==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/custom-event": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", + "integrity": "sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg==", + "dev": true, + "license": "MIT" + }, + "node_modules/date-format": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz", + "integrity": "sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-libc": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/di": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", + "integrity": "sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA==", + "dev": true, + "license": "MIT" + }, + "node_modules/dom-serialize": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", + "integrity": "sha512-Yra4DbvoW7/Z6LBN560ZwXMjoNOSAN2wRsKFGc4iBeso+mpIA6qj1vfdf9HpMaKAqG6wXTy+1SYEzmNpKXOSsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "custom-event": "~1.0.0", + "ent": "~2.2.0", + "extend": "^3.0.0", + "void-elements": "^2.0.0" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.209", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.209.tgz", + "integrity": "sha512-Xoz0uMrim9ZETCQt8UgM5FxQF9+imA7PBpokoGcZloA1uw2LeHzTlip5cb5KOAsXZLjh/moN2vReN3ZjJmjI9A==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "dev": true, + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/engine.io": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz", + "integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.7.2", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io/node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/engine.io/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/engine.io/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/engine.io/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ent": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.2.tgz", + "integrity": "sha512-kKvD1tO6BM+oK9HzCPpUdRb4vKFQY/FPTFmurMvh6LlN68VMrdj77w8yp51/kDbpkFOS9J8w5W6zIzgM2H8/hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "punycode": "^1.4.1", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz", + "integrity": "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.9", + "@esbuild/android-arm": "0.25.9", + "@esbuild/android-arm64": "0.25.9", + "@esbuild/android-x64": "0.25.9", + "@esbuild/darwin-arm64": "0.25.9", + "@esbuild/darwin-x64": "0.25.9", + "@esbuild/freebsd-arm64": "0.25.9", + "@esbuild/freebsd-x64": "0.25.9", + "@esbuild/linux-arm": "0.25.9", + "@esbuild/linux-arm64": "0.25.9", + "@esbuild/linux-ia32": "0.25.9", + "@esbuild/linux-loong64": "0.25.9", + "@esbuild/linux-mips64el": "0.25.9", + "@esbuild/linux-ppc64": "0.25.9", + "@esbuild/linux-riscv64": "0.25.9", + "@esbuild/linux-s390x": "0.25.9", + "@esbuild/linux-x64": "0.25.9", + "@esbuild/netbsd-arm64": "0.25.9", + "@esbuild/netbsd-x64": "0.25.9", + "@esbuild/openbsd-arm64": "0.25.9", + "@esbuild/openbsd-x64": "0.25.9", + "@esbuild/openharmony-arm64": "0.25.9", + "@esbuild/sunos-x64": "0.25.9", + "@esbuild/win32-arm64": "0.25.9", + "@esbuild/win32-ia32": "0.25.9", + "@esbuild/win32-x64": "0.25.9" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true, + "license": "MIT" + }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.5.tgz", + "integrity": "sha512-bSRG85ZrMdmWtm7qkF9He9TNRzc/Bm99gEJMaQoHJ9E6Kv9QBbsldh2oMj7iXmYNEAVvNgvv5vPorG6W+XtBhQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/exponential-backoff": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.2.tgz", + "integrity": "sha512-8QxYTVXUkuy7fIIoitQkPwGonB8F3Zj8eEO8Sqg9Zv/bkI7RJAzowee4gr81Hak/dUTpA2Z7VfQgoijjPNlUZA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/express": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz", + "integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/fs-minipass": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", + "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", + "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/hosted-git-info": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.0.tgz", + "integrity": "sha512-gEf705MZLrDPkbbhi8PnoO4ZwYgKoNL+ISZ3AjZMht2r3N5tuTwncyDi6Fv2/qDnMmZxgs0yI8WDOyR8q3G+SQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^11.1.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.1.0.tgz", + "integrity": "sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==", + "dev": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/htmlparser2": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.0.0.tgz", + "integrity": "sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.1", + "entities": "^6.0.0" + } + }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore-walk": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-8.0.0.tgz", + "integrity": "sha512-FCeMZT4NiRQGh+YkeKMtWrOmBgWjHjMJ26WQWrRQyoyzqevdaGSakUaJW5xQYmjLlUVk2qUnCjYVBax9EKKg8A==", + "dev": true, + "license": "ISC", + "dependencies": { + "minimatch": "^10.0.3" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/ignore-walk/node_modules/minimatch": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", + "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@isaacs/brace-expansion": "^5.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/immutable": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.3.tgz", + "integrity": "sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg==", + "dev": true, + "license": "MIT" + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ini": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-5.0.0.tgz", + "integrity": "sha512-+N0ngpO3e7cRUWOJAS7qw0IZIVc6XPrW4MlFBdD066F2L4k1L6ker3hLqSq7iXxU5tgS4WGkIUElWn5vogAEnw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/ip-address": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", + "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-interactive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isbinaryfile": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", + "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps/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", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jasmine-core": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-5.8.0.tgz", + "integrity": "sha512-Q9dqmpUAfptwyueW3+HqBOkSuYd9I/clZSSfN97wXE/Nr2ROFNCwIBEC1F6kb3QXS9Fcz0LjFYSDQT+BiwjuhA==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-4.0.0.tgz", + "integrity": "sha512-lR4MXjGNgkJc7tkQ97kb2nuEMnNCyU//XYVH0MKTGcXEiSudQ5MKGKen3C5QubYy0vmq+JGitUg92uuywGEwIA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "dev": true, + "engines": [ + "node >= 0.2.0" + ], + "license": "MIT" + }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "license": "MIT", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jwa": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", + "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "license": "MIT", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/karma": { + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.4.tgz", + "integrity": "sha512-LrtUxbdvt1gOpo3gxG+VAJlJAEMhbWlM4YrFQgql98FwF7+K8K12LYO4hnDdUkNjeztYrOXEMqgTajSWgmtI/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@colors/colors": "1.5.0", + "body-parser": "^1.19.0", + "braces": "^3.0.2", + "chokidar": "^3.5.1", + "connect": "^3.7.0", + "di": "^0.0.1", + "dom-serialize": "^2.2.1", + "glob": "^7.1.7", + "graceful-fs": "^4.2.6", + "http-proxy": "^1.18.1", + "isbinaryfile": "^4.0.8", + "lodash": "^4.17.21", + "log4js": "^6.4.1", + "mime": "^2.5.2", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.5", + "qjobs": "^1.2.0", + "range-parser": "^1.2.1", + "rimraf": "^3.0.2", + "socket.io": "^4.7.2", + "source-map": "^0.6.1", + "tmp": "^0.2.1", + "ua-parser-js": "^0.7.30", + "yargs": "^16.1.1" + }, + "bin": { + "karma": "bin/karma" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/karma-chrome-launcher": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-3.2.0.tgz", + "integrity": "sha512-rE9RkUPI7I9mAxByQWkGJFXfFD6lE4gC5nPuZdobf/QdTEJI6EU4yIay/cfU/xV4ZxlM5JiTv7zWYgA64NpS5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "which": "^1.2.1" + } + }, + "node_modules/karma-chrome-launcher/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/karma-coverage": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/karma-coverage/-/karma-coverage-2.2.1.tgz", + "integrity": "sha512-yj7hbequkQP2qOSb20GuNSIyE//PgJWHwC2IydLE6XRtsnaflv+/OSGNssPjobYUlhVVagy99TQpqUt3vAUG7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.1", + "istanbul-reports": "^3.0.5", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/karma-coverage/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/karma-coverage/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/karma-jasmine": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-5.1.0.tgz", + "integrity": "sha512-i/zQLFrfEpRyQoJF9fsCdTMOF5c2dK7C7OmsuKg2D0YSsuZSfQDiLuaiktbuio6F2wiCsZSnSnieIQ0ant/uzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "jasmine-core": "^4.1.0" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "karma": "^6.0.0" + } + }, + "node_modules/karma-jasmine-html-reporter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-2.1.0.tgz", + "integrity": "sha512-sPQE1+nlsn6Hwb5t+HHwyy0A1FNCVKuL1192b+XNauMYWThz2kweiBVW1DqloRpVvZIJkIoHVB7XRpK78n1xbQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "jasmine-core": "^4.0.0 || ^5.0.0", + "karma": "^6.0.0", + "karma-jasmine": "^5.0.0" + } + }, + "node_modules/karma-jasmine/node_modules/jasmine-core": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-4.6.1.tgz", + "integrity": "sha512-VYz/BjjmC3klLJlLwA4Kw8ytk0zDSmbbDLNs794VnWmkcCB7I9aAL/D48VNQtmITyPvea2C3jdUMfc3kAoy0PQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/karma/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/karma/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/karma/node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/karma/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/karma/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/karma/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/karma/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/karma/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/karma/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/karma/node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/karma/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/karma/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/karma/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/karma/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/karma/node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/karma/node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/karma/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/karma/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", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/karma/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/karma/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/karma/node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/karma/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/karma/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/karma/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/listr2": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.1.tgz", + "integrity": "sha512-SL0JY3DaxylDuo/MecFeiC+7pedM0zia33zl0vcjgwcq1q1FWWF1To9EIauPbl8GbMCU0R2e0uJ8bZunhYKD2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cli-truncate": "^4.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/listr2/node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "dev": true, + "license": "MIT" + }, + "node_modules/listr2/node_modules/wrap-ansi": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/lmdb": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/lmdb/-/lmdb-3.4.2.tgz", + "integrity": "sha512-nwVGUfTBUwJKXd6lRV8pFNfnrCC1+l49ESJRM19t/tFb/97QfJEixe5DYRvug5JO7DSFKoKaVy7oGMt5rVqZvg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "msgpackr": "^1.11.2", + "node-addon-api": "^6.1.0", + "node-gyp-build-optional-packages": "5.2.2", + "ordered-binary": "^1.5.3", + "weak-lru-cache": "^1.2.2" + }, + "bin": { + "download-lmdb-prebuilds": "bin/download-prebuilds.js" + }, + "optionalDependencies": { + "@lmdb/lmdb-darwin-arm64": "3.4.2", + "@lmdb/lmdb-darwin-x64": "3.4.2", + "@lmdb/lmdb-linux-arm": "3.4.2", + "@lmdb/lmdb-linux-arm64": "3.4.2", + "@lmdb/lmdb-linux-x64": "3.4.2", + "@lmdb/lmdb-win32-arm64": "3.4.2", + "@lmdb/lmdb-win32-x64": "3.4.2" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz", + "integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "is-unicode-supported": "^1.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/is-unicode-supported": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", + "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", + "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-escapes": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz", + "integrity": "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/is-fullwidth-code-point": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz", + "integrity": "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/slice-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.0.tgz", + "integrity": "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/wrap-ansi": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/log4js": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.9.1.tgz", + "integrity": "sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "date-format": "^4.0.14", + "debug": "^4.3.4", + "flatted": "^3.2.7", + "rfdc": "^1.3.0", + "streamroller": "^3.1.5" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-fetch-happen": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-14.0.3.tgz", + "integrity": "sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/agent": "^3.0.0", + "cacache": "^19.0.1", + "http-cache-semantics": "^4.1.1", + "minipass": "^7.0.2", + "minipass-fetch": "^4.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^1.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "ssri": "^12.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-collect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", + "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-fetch": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-4.0.1.tgz", + "integrity": "sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^1.0.3", + "minizlib": "^3.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-flush/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/minizlib": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", + "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/msgpackr": { + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.5.tgz", + "integrity": "sha512-UjkUHN0yqp9RWKy0Lplhh+wlpdt9oQBYgULZOiFhV3VclSF1JnSQWZ5r9gORQlNYaUKQoR8itv7g7z1xDDuACA==", + "dev": true, + "license": "MIT", + "optional": true, + "optionalDependencies": { + "msgpackr-extract": "^3.0.2" + } + }, + "node_modules/msgpackr-extract": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.3.tgz", + "integrity": "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-gyp-build-optional-packages": "5.2.2" + }, + "bin": { + "download-msgpackr-prebuilds": "bin/download-prebuilds.js" + }, + "optionalDependencies": { + "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3" + } + }, + "node_modules/mute-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", + "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-addon-api": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", + "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/node-gyp": { + "version": "11.4.2", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-11.4.2.tgz", + "integrity": "sha512-3gD+6zsrLQH7DyYOUIutaauuXrcyxeTPyQuZQCQoNPZMHMMS5m4y0xclNpvYzoK3VNzuyxT6eF4mkIL4WSZ1eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^14.0.3", + "nopt": "^8.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5", + "tar": "^7.4.3", + "tinyglobby": "^0.2.12", + "which": "^5.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/node-gyp-build-optional-packages": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz", + "integrity": "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.1" + }, + "bin": { + "node-gyp-build-optional-packages": "bin.js", + "node-gyp-build-optional-packages-optional": "optional.js", + "node-gyp-build-optional-packages-test": "build-test.js" + } + }, + "node_modules/node-gyp/node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/node-gyp/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/node-gyp/node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/node-gyp/node_modules/tar": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", + "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/node-gyp/node_modules/which": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/node-gyp/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/node-html-parser": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-7.0.1.tgz", + "integrity": "sha512-KGtmPY2kS0thCWGK0VuPyOS+pBKhhe8gXztzA2ilAOhbUbxa9homF1bOyKvhGzMLXUoRds9IOmr/v5lr/lqNmA==", + "license": "MIT", + "dependencies": { + "css-select": "^5.1.0", + "he": "1.2.0" + } + }, + "node_modules/node-html-parser/node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/node-html-parser/node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true, + "license": "MIT" + }, + "node_modules/nopt": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-8.1.0.tgz", + "integrity": "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "^3.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-bundled": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-4.0.0.tgz", + "integrity": "sha512-IxaQZDMsqfQ2Lz37VvyyEtKLe8FsRZuysmedy/N06TU1RyVppYKXrO4xIhR0F+7ubIBox6Q7nir6fQI3ej39iA==", + "dev": true, + "license": "ISC", + "dependencies": { + "npm-normalize-package-bin": "^4.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm-install-checks": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-7.1.2.tgz", + "integrity": "sha512-z9HJBCYw9Zr8BqXcllKIs5nI+QggAImbBdHphOzVYrz2CB4iQ6FzWyKmlqDZua+51nAu7FcemlbTc9VgQN5XDQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "semver": "^7.1.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm-normalize-package-bin": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-4.0.0.tgz", + "integrity": "sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm-package-arg": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-13.0.0.tgz", + "integrity": "sha512-+t2etZAGcB7TbbLHfDwooV9ppB2LhhcT6A+L9cahsf9mEUAoQ6CktLEVvEnpD0N5CkX7zJqnPGaFtoQDy9EkHQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "hosted-git-info": "^9.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^6.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm-packlist": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-10.0.1.tgz", + "integrity": "sha512-vaC03b2PqJA6QqmwHi1jNU8fAPXEnnyv4j/W4PVfgm24C4/zZGSVut3z0YUeN0WIFCo1oGOL02+6LbvFK7JL4Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "ignore-walk": "^8.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm-pick-manifest": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-10.0.0.tgz", + "integrity": "sha512-r4fFa4FqYY8xaM7fHecQ9Z2nE9hgNfJR+EmoKv0+chvzWkBcORX3r0FpTByP+CbOVJDladMXnPQGVN8PBLGuTQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "npm-install-checks": "^7.1.0", + "npm-normalize-package-bin": "^4.0.0", + "npm-package-arg": "^12.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm-pick-manifest/node_modules/hosted-git-info": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.1.0.tgz", + "integrity": "sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm-pick-manifest/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/npm-pick-manifest/node_modules/npm-package-arg": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-12.0.2.tgz", + "integrity": "sha512-f1NpFjNI9O4VbKMOlA5QoBq/vSQPORHcTZ2feJpFkTHJ9eQkdlmZEKSjcAhxTGInC7RlEyScT9ui67NaOsjFWA==", + "dev": true, + "license": "ISC", + "dependencies": { + "hosted-git-info": "^8.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^6.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm-registry-fetch": { + "version": "18.0.2", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-18.0.2.tgz", + "integrity": "sha512-LeVMZBBVy+oQb5R6FDV9OlJCcWDU+al10oKpe+nsvcHnG24Z3uM3SvJYKfGJlfGjVU8v9liejCrUR/M5HO5NEQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/redact": "^3.0.0", + "jsonparse": "^1.3.1", + "make-fetch-happen": "^14.0.0", + "minipass": "^7.0.2", + "minipass-fetch": "^4.0.0", + "minizlib": "^3.0.1", + "npm-package-arg": "^12.0.0", + "proc-log": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm-registry-fetch/node_modules/hosted-git-info": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.1.0.tgz", + "integrity": "sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm-registry-fetch/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/npm-registry-fetch/node_modules/npm-package-arg": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-12.0.2.tgz", + "integrity": "sha512-f1NpFjNI9O4VbKMOlA5QoBq/vSQPORHcTZ2feJpFkTHJ9eQkdlmZEKSjcAhxTGInC7RlEyScT9ui67NaOsjFWA==", + "dev": true, + "license": "ISC", + "dependencies": { + "hosted-git-info": "^8.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^6.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-8.2.0.tgz", + "integrity": "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "cli-cursor": "^5.0.0", + "cli-spinners": "^2.9.2", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^2.0.0", + "log-symbols": "^6.0.0", + "stdin-discarder": "^0.2.2", + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ordered-binary": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ordered-binary/-/ordered-binary-1.6.0.tgz", + "integrity": "sha512-IQh2aMfMIDbPjI/8a3Edr+PiOpcsB7yo8NdW7aHWVaoR/pcDldunMvnnwbk/auPGqmKeAdxtZl7MHX/QmPwhvQ==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/orderedmap": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-2.1.1.tgz", + "integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==", + "license": "MIT" + }, + "node_modules/p-map": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.3.tgz", + "integrity": "sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0" + }, + "node_modules/pacote": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-21.0.0.tgz", + "integrity": "sha512-lcqexq73AMv6QNLo7SOpz0JJoaGdS3rBFgF122NZVl1bApo2mfu+XzUBU/X/XsiJu+iUmKpekRayqQYAs+PhkA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^6.0.0", + "@npmcli/installed-package-contents": "^3.0.0", + "@npmcli/package-json": "^6.0.0", + "@npmcli/promise-spawn": "^8.0.0", + "@npmcli/run-script": "^9.0.0", + "cacache": "^19.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^7.0.2", + "npm-package-arg": "^12.0.0", + "npm-packlist": "^10.0.0", + "npm-pick-manifest": "^10.0.0", + "npm-registry-fetch": "^18.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "sigstore": "^3.0.0", + "ssri": "^12.0.0", + "tar": "^6.1.11" + }, + "bin": { + "pacote": "bin/index.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/pacote/node_modules/hosted-git-info": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.1.0.tgz", + "integrity": "sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/pacote/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/pacote/node_modules/npm-package-arg": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-12.0.2.tgz", + "integrity": "sha512-f1NpFjNI9O4VbKMOlA5QoBq/vSQPORHcTZ2feJpFkTHJ9eQkdlmZEKSjcAhxTGInC7RlEyScT9ui67NaOsjFWA==", + "dev": true, + "license": "ISC", + "dependencies": { + "hosted-git-info": "^8.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^6.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/parse5": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz", + "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-html-rewriting-stream": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/parse5-html-rewriting-stream/-/parse5-html-rewriting-stream-8.0.0.tgz", + "integrity": "sha512-wzh11mj8KKkno1pZEu+l2EVeWsuKDfR5KNWZOTsslfUX8lPDZx77m9T0kIoAVkFtD1nx6YF8oh4BnPHvxMtNMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0", + "parse5": "^8.0.0", + "parse5-sax-parser": "^8.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-html-rewriting-stream/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/parse5-sax-parser": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/parse5-sax-parser/-/parse5-sax-parser-8.0.0.tgz", + "integrity": "sha512-/dQ8UzHZwnrzs3EvDj6IkKrD/jIZyTlB+8XrHJvcjNgRdmWruNdN9i9RK/JtxakmlUdPwKubKPTCqvbTgzGhrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse5": "^8.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/path-to-regexp": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", + "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/piscina": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/piscina/-/piscina-5.1.3.tgz", + "integrity": "sha512-0u3N7H4+hbr40KjuVn2uNhOcthu/9usKhnw5vT3J7ply79v3D3M8naI00el9Klcy16x557VsEkkUQaHCWFXC/g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.x" + }, + "optionalDependencies": { + "@napi-rs/nice": "^1.0.4" + } + }, + "node_modules/pkce-challenge": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz", + "integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-media-query-parser": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", + "integrity": "sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==", + "dev": true, + "license": "MIT" + }, + "node_modules/proc-log": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-5.0.0.tgz", + "integrity": "sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/prosemirror-commands": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.7.1.tgz", + "integrity": "sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.10.2" + } + }, + "node_modules/prosemirror-dropcursor": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.2.tgz", + "integrity": "sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw==", + "license": "MIT", + "dependencies": { + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.1.0", + "prosemirror-view": "^1.1.0" + } + }, + "node_modules/prosemirror-gapcursor": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/prosemirror-gapcursor/-/prosemirror-gapcursor-1.3.2.tgz", + "integrity": "sha512-wtjswVBd2vaQRrnYZaBCbyDqr232Ed4p2QPtRIUK5FuqHYKGWkEwl08oQM4Tw7DOR0FsasARV5uJFvMZWxdNxQ==", + "license": "MIT", + "dependencies": { + "prosemirror-keymap": "^1.0.0", + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-view": "^1.0.0" + } + }, + "node_modules/prosemirror-history": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.4.1.tgz", + "integrity": "sha512-2JZD8z2JviJrboD9cPuX/Sv/1ChFng+xh2tChQ2X4bB2HeK+rra/bmJ3xGntCcjhOqIzSDG6Id7e8RJ9QPXLEQ==", + "license": "MIT", + "dependencies": { + "prosemirror-state": "^1.2.2", + "prosemirror-transform": "^1.0.0", + "prosemirror-view": "^1.31.0", + "rope-sequence": "^1.3.0" + } + }, + "node_modules/prosemirror-inputrules": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.5.0.tgz", + "integrity": "sha512-K0xJRCmt+uSw7xesnHmcn72yBGTbY45vm8gXI4LZXbx2Z0jwh5aF9xrGQgrVPu0WbyFVFF3E/o9VhJYz6SQWnA==", + "license": "MIT", + "dependencies": { + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.0.0" + } + }, + "node_modules/prosemirror-keymap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.2.3.tgz", + "integrity": "sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==", + "license": "MIT", + "dependencies": { + "prosemirror-state": "^1.0.0", + "w3c-keyname": "^2.2.0" + } + }, + "node_modules/prosemirror-model": { + "version": "1.25.1", + "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.1.tgz", + "integrity": "sha512-AUvbm7qqmpZa5d9fPKMvH1Q5bqYQvAZWOGRvxsB6iFLyycvC9MwNemNVjHVrWgjaoxAfY8XVg7DbvQ/qxvI9Eg==", + "license": "MIT", + "dependencies": { + "orderedmap": "^2.0.0" + } + }, + "node_modules/prosemirror-schema-list": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.5.1.tgz", + "integrity": "sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.7.3" + } + }, + "node_modules/prosemirror-state": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.3.tgz", + "integrity": "sha512-goFKORVbvPuAQaXhpbemJFRKJ2aixr+AZMGiquiqKxaucC6hlpHNZHWgz5R7dS4roHiwq9vDctE//CZ++o0W1Q==", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-transform": "^1.0.0", + "prosemirror-view": "^1.27.0" + } + }, + "node_modules/prosemirror-tables": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.7.1.tgz", + "integrity": "sha512-eRQ97Bf+i9Eby99QbyAiyov43iOKgWa7QCGly+lrDt7efZ1v8NWolhXiB43hSDGIXT1UXgbs4KJN3a06FGpr1Q==", + "license": "MIT", + "dependencies": { + "prosemirror-keymap": "^1.2.2", + "prosemirror-model": "^1.25.0", + "prosemirror-state": "^1.4.3", + "prosemirror-transform": "^1.10.3", + "prosemirror-view": "^1.39.1" + } + }, + "node_modules/prosemirror-transform": { + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.10.4.tgz", + "integrity": "sha512-pwDy22nAnGqNR1feOQKHxoFkkUtepoFAd3r2hbEDsnf4wp57kKA36hXsB3njA9FtONBEwSDnDeCiJe+ItD+ykw==", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.21.0" + } + }, + "node_modules/prosemirror-view": { + "version": "1.39.3", + "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.39.3.tgz", + "integrity": "sha512-bY/7kg0LzRE7ytR0zRdSMWX3sknEjw68l836ffLPMh0OG3OYnNuBDUSF3v0vjvnzgYjgY9ZH/RypbARURlcMFA==", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.20.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.1.0" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/qjobs": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", + "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.9" + } + }, + "node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", + "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.6.3", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true, + "license": "MIT" + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rolldown": { + "version": "1.0.0-beta.32", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-beta.32.tgz", + "integrity": "sha512-vxI2sPN07MMaoYKlFrVva5qZ1Y7DAZkgp7MQwTnyHt4FUMz9Sh+YeCzNFV9JYHI6ZNwoGWLCfCViE3XVsRC1cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/runtime": "=0.81.0", + "@oxc-project/types": "=0.81.0", + "@rolldown/pluginutils": "1.0.0-beta.32", + "ansis": "^4.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.0-beta.32", + "@rolldown/binding-darwin-arm64": "1.0.0-beta.32", + "@rolldown/binding-darwin-x64": "1.0.0-beta.32", + "@rolldown/binding-freebsd-x64": "1.0.0-beta.32", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-beta.32", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-beta.32", + "@rolldown/binding-linux-arm64-musl": "1.0.0-beta.32", + "@rolldown/binding-linux-x64-gnu": "1.0.0-beta.32", + "@rolldown/binding-linux-x64-musl": "1.0.0-beta.32", + "@rolldown/binding-openharmony-arm64": "1.0.0-beta.32", + "@rolldown/binding-wasm32-wasi": "1.0.0-beta.32", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-beta.32", + "@rolldown/binding-win32-ia32-msvc": "1.0.0-beta.32", + "@rolldown/binding-win32-x64-msvc": "1.0.0-beta.32" + } + }, + "node_modules/rollup": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.49.0.tgz", + "integrity": "sha512-3IVq0cGJ6H7fKXXEdVt+RcYvRCt8beYY9K1760wGQwSAHZcS9eot1zDG5axUbcp/kWRi5zKIIDX8MoKv/TzvZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.49.0", + "@rollup/rollup-android-arm64": "4.49.0", + "@rollup/rollup-darwin-arm64": "4.49.0", + "@rollup/rollup-darwin-x64": "4.49.0", + "@rollup/rollup-freebsd-arm64": "4.49.0", + "@rollup/rollup-freebsd-x64": "4.49.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.49.0", + "@rollup/rollup-linux-arm-musleabihf": "4.49.0", + "@rollup/rollup-linux-arm64-gnu": "4.49.0", + "@rollup/rollup-linux-arm64-musl": "4.49.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.49.0", + "@rollup/rollup-linux-ppc64-gnu": "4.49.0", + "@rollup/rollup-linux-riscv64-gnu": "4.49.0", + "@rollup/rollup-linux-riscv64-musl": "4.49.0", + "@rollup/rollup-linux-s390x-gnu": "4.49.0", + "@rollup/rollup-linux-x64-gnu": "4.49.0", + "@rollup/rollup-linux-x64-musl": "4.49.0", + "@rollup/rollup-win32-arm64-msvc": "4.49.0", + "@rollup/rollup-win32-ia32-msvc": "4.49.0", + "@rollup/rollup-win32-x64-msvc": "4.49.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/rope-sequence": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.4.tgz", + "integrity": "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==", + "license": "MIT" + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "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/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/sass": { + "version": "1.90.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.90.0.tgz", + "integrity": "sha512-9GUyuksjw70uNpb1MTYWsH9MQHOHY6kwfnkafC24+7aOMZn9+rVMBxRbLvw756mrBFbIsFg6Xw9IkR2Fnn3k+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.0", + "immutable": "^5.0.2", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true, + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sigstore": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-3.1.0.tgz", + "integrity": "sha512-ZpzWAFHIFqyFE56dXqgX/DkDRZdz+rRcjoIk/RQU4IX0wiCv1l8S7ZrXDHcCc+uaf+6o7w3h2l3g6GYG5TKN9Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^3.1.0", + "@sigstore/core": "^2.0.0", + "@sigstore/protobuf-specs": "^0.4.0", + "@sigstore/sign": "^3.1.0", + "@sigstore/tuf": "^3.1.0", + "@sigstore/verify": "^2.1.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socket.io": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz", + "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.6.0", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", + "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "~4.3.4", + "ws": "~8.17.1" + } + }, + "node_modules/socket.io-adapter/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io/node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/socket.io/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/socket.io/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/socket.io/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/socks": { + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ip-address": "^10.0.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/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", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true, + "license": "CC-BY-3.0" + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.22", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.22.tgz", + "integrity": "sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/ssri": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-12.0.0.tgz", + "integrity": "sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stdin-discarder": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", + "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/streamroller": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.5.tgz", + "integrity": "sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "date-format": "^4.0.14", + "debug": "^4.3.4", + "fs-extra": "^8.1.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "dev": true, + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar/node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/tar/node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/tar/node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/tinyglobby": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tmp": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", + "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/tuf-js": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-3.1.0.tgz", + "integrity": "sha512-3T3T04WzowbwV2FDiGXBbr81t64g1MUGGJRgT4x5o97N+8ArdhVCAF9IxFrxuSJmM3E5Asn7nKHkao0ibcZXAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tufjs/models": "3.0.1", + "debug": "^4.4.1", + "make-fetch-happen": "^14.0.3" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ua-parser-js": { + "version": "0.7.41", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.41.tgz", + "integrity": "sha512-O3oYyCMPYgNNHuO7Jjk3uacJWZF8loBgwrfd/5LE/HyZ3lUIOdniQ7DNXJcIgZbwioZxk0fLfI4EVnetdiX5jg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + }, + { + "type": "github", + "url": "https://github.com/sponsors/faisalman" + } + ], + "license": "MIT", + "bin": { + "ua-parser-js": "script/cli.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/undici-types": { + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz", + "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/unique-filename": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-4.0.0.tgz", + "integrity": "sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "unique-slug": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/unique-slug": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-5.0.0.tgz", + "integrity": "sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/uri-js/node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/validate-npm-package-name": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-6.0.2.tgz", + "integrity": "sha512-IUoow1YUtvoBBC06dXs8bR8B9vuA3aJfmQNKMoaPG/OFsPmoQvw8xh+6Ye25Gx9DQhoEom3Pcu9MKHerm/NpUQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vite": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.2.tgz", + "integrity": "sha512-J0SQBPlQiEXAF7tajiH+rUooJPo0l8KQgyg4/aMunNtrOa7bwuZJsJbDWzeljqQpgftxuq5yNJxQ91O9ts29UQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.6", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.14" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/void-elements": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", + "integrity": "sha512-qZKX4RnBzH2ugr8Lxa7x+0V6XD9Sb/ouARtiasEQCHB1EVU4NXtmHsDDrx1dO4ne5fc3J6EW05BP1Dl0z0iung==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", + "license": "MIT" + }, + "node_modules/watchpack": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz", + "integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/weak-lru-cache": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/weak-lru-cache/-/weak-lru-cache-1.2.2.tgz", + "integrity": "sha512-DEAoo25RfSYMuTGc9vPJzZcZullwIqRDSI9LOy+fkCJPi6hykCnfKaXTuPBDuXAUcqHXyOgFtHNp/kB2FjYHbw==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz", + "integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^9.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "string-width": "^7.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^22.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/yargs-parser": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/yoctocolors-cjs": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz", + "integrity": "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.24.6", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz", + "integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==", + "dev": true, + "license": "ISC", + "peerDependencies": { + "zod": "^3.24.1" + } + }, + "node_modules/zone.js": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.15.1.tgz", + "integrity": "sha512-XE96n56IQpJM7NAoXswY3XRLcWFW83xe0BiAOeMD7K5k5xecOeul3Qcpx6GqEeeHNkW5DWL5zOyTbEfB4eti8w==", + "license": "MIT" + } + } +} diff --git a/APP/package.json b/APP/package.json new file mode 100644 index 0000000..f94f1c4 --- /dev/null +++ b/APP/package.json @@ -0,0 +1,89 @@ +{ + "name": "RBJ.Identity.App", + "version": "0.0.0", + "scripts": { + "ng": "ng", + "start": "ng serve", + "build": "ng build", + "watch": "ng build --watch --configuration development", + "test": "ng test" + }, + "prettier": { + "printWidth": 100, + "singleQuote": true, + "overrides": [ + { + "files": "*.html", + "options": { + "parser": "angular" + } + } + ] + }, + "private": true, + "dependencies": { + "@angular/animations": "^20.1.0", + "@angular/common": "^20.1.0", + "@angular/compiler": "^20.1.0", + "@angular/core": "^20.1.0", + "@angular/forms": "^20.1.0", + "@angular/localize": "^20.1.6", + "@angular/platform-browser": "^20.1.0", + "@angular/router": "^20.1.0", + "@progress/kendo-angular-buttons": "^20.0.0", + "@progress/kendo-angular-charts": "^20.0.0", + "@progress/kendo-angular-common": "^20.0.0", + "@progress/kendo-angular-conversational-ui": "^20.0.0", + "@progress/kendo-angular-dateinputs": "^20.0.0", + "@progress/kendo-angular-dialog": "^20.0.0", + "@progress/kendo-angular-dropdowns": "^20.0.0", + "@progress/kendo-angular-editor": "^20.0.0", + "@progress/kendo-angular-excel-export": "^20.0.3", + "@progress/kendo-angular-gauges": "^20.0.0", + "@progress/kendo-angular-grid": "^20.0.0", + "@progress/kendo-angular-icons": "^20.0.0", + "@progress/kendo-angular-indicators": "^20.0.0", + "@progress/kendo-angular-inputs": "^20.0.0", + "@progress/kendo-angular-intl": "^20.0.0", + "@progress/kendo-angular-l10n": "^20.0.0", + "@progress/kendo-angular-label": "^20.0.0", + "@progress/kendo-angular-layout": "^20.0.0", + "@progress/kendo-angular-listview": "^20.0.0", + "@progress/kendo-angular-map": "^20.0.0", + "@progress/kendo-angular-menu": "^20.0.0", + "@progress/kendo-angular-navigation": "^20.0.0", + "@progress/kendo-angular-pager": "^20.0.0", + "@progress/kendo-angular-pdf-export": "^20.0.3", + "@progress/kendo-angular-popup": "^20.0.0", + "@progress/kendo-angular-progressbar": "^20.0.0", + "@progress/kendo-angular-scrollview": "^20.0.0", + "@progress/kendo-angular-toolbar": "^20.0.3", + "@progress/kendo-angular-tooltip": "^20.0.0", + "@progress/kendo-angular-treeview": "^20.0.0", + "@progress/kendo-angular-upload": "^20.0.0", + "@progress/kendo-angular-utils": "^20.0.0", + "@progress/kendo-data-query": "^1.7.1", + "@progress/kendo-drawing": "^1.22.0", + "@progress/kendo-licensing": "^1.7.0", + "@progress/kendo-svg-icons": "^4.5.0", + "@progress/kendo-theme-default": "^12.0.0", + "@progress/kendo-theme-utils": "^12.0.0", + "rxjs": "~7.8.0", + "tslib": "^2.3.0", + "zone.js": "~0.15.0" + }, + "devDependencies": { + "@angular/build": "^20.1.6", + "@angular/cli": "^20.1.6", + "@angular/compiler-cli": "^20.1.0", + "@angular/localize": "^20.2.1", + "@types/jasmine": "~5.1.0", + "jasmine-core": "~5.8.0", + "karma": "~6.4.0", + "karma-chrome-launcher": "~3.2.0", + "karma-coverage": "~2.2.0", + "karma-jasmine": "~5.1.0", + "karma-jasmine-html-reporter": "~2.1.0", + "typescript": "~5.8.2" + } +} \ No newline at end of file diff --git a/APP/src/app/app.config.ts b/APP/src/app/app.config.ts new file mode 100644 index 0000000..8ecee26 --- /dev/null +++ b/APP/src/app/app.config.ts @@ -0,0 +1,16 @@ +import { ApplicationConfig } from '@angular/core'; +import { provideRouter } from '@angular/router'; +import { provideAnimations } from '@angular/platform-browser/animations'; +import { provideHttpClient, withInterceptors } from '@angular/common/http'; + +import { routes } from './app.routes'; +import { authInterceptor } from './core/interceptors/auth.interceptor'; + +export const appConfig: ApplicationConfig = { + providers: [ + provideRouter(routes), + provideAnimations(), + provideHttpClient(withInterceptors([authInterceptor])) + ] +}; + diff --git a/APP/src/app/app.html b/APP/src/app/app.html new file mode 100644 index 0000000..09af448 --- /dev/null +++ b/APP/src/app/app.html @@ -0,0 +1,2 @@ + +
\ No newline at end of file diff --git a/APP/src/app/app.routes.ts b/APP/src/app/app.routes.ts new file mode 100644 index 0000000..768dba1 --- /dev/null +++ b/APP/src/app/app.routes.ts @@ -0,0 +1,27 @@ +import { Routes } from '@angular/router'; +import { DashboardComponent } from './portals/user-portal/pages/dashboard/dashboard.component'; +import { LoginPage } from './features/login-page/login-page'; +import { UserPortalComponent } from './portals/user-portal/user-portal.component'; +import { AuthGuard } from './core/guards/auth.guard'; + +export const routes: Routes = [ + // Public routes + { path: 'login', component: LoginPage }, + + // Keep the startup surface intentionally small: login + guarded mock dashboard. + { + path: 'user-portal', + component: UserPortalComponent, + canActivate: [AuthGuard], + children: [ + { path: '', redirectTo: 'dashboard', pathMatch: 'full' }, + { path: 'dashboard', component: DashboardComponent } + ] + }, + + { path: '', redirectTo: 'login', pathMatch: 'full' }, + { path: 'dashboard', redirectTo: 'user-portal/dashboard' }, + + // Catch all route - redirect to login + { path: '**', redirectTo: 'login' } +]; diff --git a/APP/src/app/app.scss b/APP/src/app/app.scss new file mode 100644 index 0000000..f8f183e --- /dev/null +++ b/APP/src/app/app.scss @@ -0,0 +1,53 @@ +/* Global layout styles */ + +/* Ensure AppBar sections are in one row */ +kendo-appbar { + display: flex !important; + flex-direction: row !important; + align-items: center !important; + width: 100% !important; +} + +kendo-appbar-section { + display: flex !important; + align-items: center !important; +} + +/* Make sure the drawer container takes full height */ +kendo-drawer-container { + min-height: calc(100vh - 46px); +} + +/* Global mobile optimizations */ +@media (max-width: 767px) { + /* Improve touch targets for mobile */ + button[kendoButton] { + min-height: 44px; + min-width: 44px; + } + + /* Make drawer content full width on mobile */ + kendo-drawer-content { + width: 100%; + } + + /* Prevent horizontal scroll on mobile */ + body { + overflow-x: hidden; + } +} + +/* iOS specific fixes */ +@supports (-webkit-touch-callout: none) { + /* Prevent iOS zoom on input focus */ + input, + select, + textarea { + font-size: 16px !important; + } + + /* Smooth scrolling for iOS - legacy support for older devices */ + kendo-drawer-container { + overflow-y: auto; + } +} diff --git a/APP/src/app/app.ts b/APP/src/app/app.ts new file mode 100644 index 0000000..f706dbc --- /dev/null +++ b/APP/src/app/app.ts @@ -0,0 +1,28 @@ +import { Component, ViewEncapsulation, OnInit } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { RouterOutlet } from '@angular/router'; +import { DialogModule } from '@progress/kendo-angular-dialog'; +import { AuthService } from './shared/services/auth.service'; + +@Component({ + selector: 'app-root', + standalone: true, + imports: [ + CommonModule, + RouterOutlet, + DialogModule + ], + templateUrl: './app.html', + styleUrls: ['./app.scss', '../styles.scss'], + encapsulation: ViewEncapsulation.None +}) +export class App implements OnInit { + title = 'RBJ Identity'; + + constructor(private authService: AuthService) { } + + ngOnInit(): void { + // Initialize authentication state from localStorage + this.authService.initializeAuth(); + } +} diff --git a/APP/src/app/core/guards/README.md b/APP/src/app/core/guards/README.md new file mode 100644 index 0000000..e3c54ab --- /dev/null +++ b/APP/src/app/core/guards/README.md @@ -0,0 +1,120 @@ +# Authentication Guard System + +This implementation provides a complete authentication system that automatically detects if a user is logged in and redirects to the login page if not. + +## Components + +### AuthGuard (`auth.guard.ts`) +- **Purpose**: Protects routes that require authentication +- **Functionality**: + - Checks if user is authenticated using `AuthService.isAuthenticated()` + - Stores attempted URL for redirect after login + - Redirects to `/login` if not authenticated + - Allows access if authenticated + +### LoginPage (`login-page.ts`) +- **Purpose**: Full-page login interface for routing +- **Features**: + - Beautiful landing page with company branding + - Integrated login dialog + - Demo credentials display + - Automatic redirect after successful login + - Prevents access if already logged in + +## How It Works + +### 1. Route Protection +All protected routes use the `AuthGuard`: +```typescript +{ path: 'dashboard', component: Dashboard, canActivate: [AuthGuard] } +``` + +### 2. Authentication Flow +1. User tries to access protected route +2. `AuthGuard` checks authentication status +3. If not authenticated: + - Stores attempted URL + - Redirects to `/login` +4. If authenticated: + - Allows access to requested route + +### 3. Login Process +1. User lands on `/login` page +2. Clicks "Sign In" button +3. Login dialog opens with MFA support +4. After successful login: + - User data stored in localStorage + - Redirected to originally requested URL or dashboard + +### 4. Logout Process +1. User clicks logout from header dropdown +2. `AuthService.logout()` clears user data +3. Redirected to `/login` page + +## User State Management + +### AuthService Features +- **Persistent Login**: User stays logged in across browser sessions +- **State Management**: Reactive user state with RxJS +- **Redirect URLs**: Remembers where user was trying to go +- **localStorage**: Automatic persistence and restoration + +### Header Integration +- **Dynamic User Menu**: Shows different options based on auth state +- **User Information**: Displays current user name/email +- **Logout Button**: Easy access to logout functionality + +## Route Structure + +``` +/login - Public login page +/dashboard - Protected (requires auth) +/schedule - Protected (requires auth) +/patients - Protected (requires auth) +... - All other routes protected +/** - Catch-all redirects to /login +``` + +## Usage Examples + +### Adding New Protected Routes +```typescript +{ path: 'new-feature', component: NewFeatureComponent, canActivate: [AuthGuard] } +``` + +### Checking Auth State in Components +```typescript +constructor(private authService: AuthService) {} + +ngOnInit() { + this.authService.currentUser$.subscribe(user => { + if (user) { + // User is logged in + } else { + // User is not logged in + } + }); +} +``` + +### Manual Logout +```typescript +this.authService.logout(); +this.router.navigate(['/login']); +``` + +## Security Features + +- **Route Protection**: All sensitive routes are protected +- **Persistent Sessions**: Users stay logged in across browser sessions +- **Automatic Redirects**: Seamless user experience +- **State Validation**: Authentication state is checked on every route change +- **Clean Logout**: Complete session cleanup on logout + +## Testing + +Use these test credentials: +- **Direct Login**: `user@example.com` / `password123` +- **MFA Required**: `admin@example.com` / `password123` / `123456` + +The system will automatically redirect unauthenticated users to the login page and remember where they were trying to go. diff --git a/APP/src/app/core/guards/auth.guard.ts b/APP/src/app/core/guards/auth.guard.ts new file mode 100644 index 0000000..dd00e22 --- /dev/null +++ b/APP/src/app/core/guards/auth.guard.ts @@ -0,0 +1,32 @@ +import { Injectable } from '@angular/core'; +import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; +import { Observable, of } from 'rxjs'; +import { map, catchError } from 'rxjs/operators'; +import { AuthService } from '../../shared/services/auth.service'; + +@Injectable({ + providedIn: 'root' +}) +export class AuthGuard implements CanActivate { + constructor( + private authService: AuthService, + private router: Router + ) { } + + canActivate( + route: ActivatedRouteSnapshot, + state: RouterStateSnapshot + ): Observable | Promise | boolean { + // Check if user is authenticated + if (this.authService.isAuthenticated()) { + return true; + } + + // Store the attempted URL for redirecting after login + this.authService.setRedirectUrl(state.url); + + // Redirect to login page + this.router.navigate(['/login']); + return false; + } +} diff --git a/APP/src/app/core/interceptors/auth.interceptor.ts b/APP/src/app/core/interceptors/auth.interceptor.ts new file mode 100644 index 0000000..9fe78cd --- /dev/null +++ b/APP/src/app/core/interceptors/auth.interceptor.ts @@ -0,0 +1,60 @@ +import { HttpInterceptorFn, HttpErrorResponse } from '@angular/common/http'; +import { inject } from '@angular/core'; +import { catchError, throwError } from 'rxjs'; +import { AuthService } from '../../shared/services/auth.service'; +import { Router } from '@angular/router'; +import { ApiConfigService } from '../services/api-config.service'; + +export const authInterceptor: HttpInterceptorFn = (request, next) => { + const authService = inject(AuthService); + const apiConfigService = inject(ApiConfigService); + const router = inject(Router); + + // Get the current user and token + const token = authService.getToken(); + + // Clone the request and add the Authorization header if token exists + if (token && shouldAddToken(apiConfigService, request)) { + request = request.clone({ + setHeaders: { + Authorization: `Bearer ${token}` + } + }); + } + + // Handle the request and catch 401 errors + return next(request).pipe( + catchError((error: HttpErrorResponse) => { + if (error.status === 401) { + // Token is invalid or expired, logout user + authService.logout(); + router.navigate(['/login']); + } + return throwError(() => error); + }) + ); +}; + +/** + * Determine if the token should be added to this request + * Skip adding token to login requests and other public endpoints + */ +function shouldAddToken(apiConfigService: ApiConfigService, request: any): boolean { + // Don't add token to outbound requests to other domains + if (!request.url.startsWith(apiConfigService.getBaseUrl())) { + return false; + } + + // Don't add token to login requests + if (request.url.includes('/Auth/login') || request.url.includes('/Token/Create')) { + return false; + } + // Don't add token to public endpoints (you can customize this list) + const publicEndpoints = [ + '/Auth/register', + '/Auth/forgot-password', + '/Auth/reset-password' + ]; + + return !publicEndpoints.some(endpoint => request.url.includes(endpoint)); +} diff --git a/APP/src/app/core/services/api-config.service.ts b/APP/src/app/core/services/api-config.service.ts new file mode 100644 index 0000000..f2cd637 --- /dev/null +++ b/APP/src/app/core/services/api-config.service.ts @@ -0,0 +1,58 @@ +import { Injectable } from '@angular/core'; +import { environment } from '../../../environments/environment'; + +@Injectable({ + providedIn: 'root' +}) +export class ApiConfigService { + private readonly baseUrl = environment.apiUrl; + + constructor() { } + + /** + * Get the full API URL for a specific endpoint + * @param endpoint - The API endpoint (e.g., 'Auth', 'Users', 'Transactions') + * @returns Full API URL + */ + getApiUrl(endpoint: string): string { + return `${this.baseUrl}/${endpoint}`; + } + + /** + * Get the base API URL + * @returns Base API URL + */ + getBaseUrl(): string { + return this.baseUrl; + } + + /** + * Get specific API endpoints + */ + get authUrl(): string { + return this.getApiUrl('Auth'); + } + + get tokenUrl(): string { + return this.getApiUrl('Token'); + } + + get usersUrl(): string { + return this.getApiUrl('Users'); + } + + get transactionsUrl(): string { + return this.getApiUrl('Transactions'); + } + + get dashboardUrl(): string { + return this.getApiUrl('Dashboard'); + } + get ordersUrl(): string { + return this.getApiUrl('ClientBridgeOrder'); + } + + get orderDetailUrl(): string { + return this.getApiUrl('OrderDetail'); + } +} diff --git a/APP/src/app/core/services/crud-base-api.service.ts b/APP/src/app/core/services/crud-base-api.service.ts new file mode 100644 index 0000000..9df027f --- /dev/null +++ b/APP/src/app/core/services/crud-base-api.service.ts @@ -0,0 +1,168 @@ +import { HttpClient, HttpErrorResponse, HttpHeaders } from '@angular/common/http'; +import { Inject, Injectable } from '@angular/core'; +import { Observable, throwError } from 'rxjs'; +import { catchError, map } from 'rxjs/operators'; +import { ApiConfigService } from './api-config.service'; + +// Type definitions +type TextResponse = { message: string }; +/** +* Base CRUD service that targets the provided controller path. +* +* It mirrors the endpoints of CrudBaseApiController: +* GET /api/{controller} +* GET /api/{controller}/{id} +* POST /api/{controller} -> string +* POST /api/{controller}/batch -> string[] +* PUT /api/{controller} +* PUT /api/{controller}/batch -> number +* DELETE /api/{controller}/{id} +* DELETE /api/{controller}/batch -> text summary +* GET /api/{controller}/{id}/exists -> boolean +* GET /api/{controller}/count -> number +*/ +@Injectable({ providedIn: 'root' }) +export class CrudBaseApiService { + /** + * Example: baseUrl = 'https://your-api', controller = 'Customer' → + * endpoint = 'https://your-api/api/Customer' + */ + protected readonly endpoint: string; + + + /** + * @param http Angular HttpClient + * @param baseUrl API root without trailing slash (e.g., environment.apiBaseUrl) + * @param controllerName Controller name (e.g., 'Customer', 'Orders') + */ + constructor( + protected http: HttpClient, + protected apiConfig: ApiConfigService, + @Inject(String) private controllerName: string + ) { + this.endpoint = apiConfig.getApiUrl(this.controllerName); + } + + + /** Optional default headers (JSON). Override in subclasses if needed. */ + protected get jsonHeaders(): HttpHeaders { + return new HttpHeaders({ 'Content-Type': 'application/json' }); + } + + + /** Shared error handler that surfaces useful messages. */ + protected handleError(error: HttpErrorResponse): Observable { + let msg = 'Unknown error'; + if (error.error instanceof Blob) { + // In case backend returns text/plain; charset=utf-8 as Blob + return throwError(() => new Error('Server returned an error blob')); + } + if (typeof error.error === 'string') msg = error.error; + else if (error.error?.message) msg = error.error.message; + else if (error.message) msg = error.message; + return throwError(() => new Error(msg)); + } + /** Prepare the response for the given entity. Override in subclasses if needed. */ + protected prepareResponse(response: T): T { + // Do nothing by default + return response; + } + + /** GET /api/{controller} */ + getAll(): Observable { + return this.http + .get(this.endpoint) + .pipe( + map(response => { + + for (let i = 0; i < response.length; i++) { + const element = response[i]; + response[i] = this.prepareResponse(element); + } + return response; + }), + catchError(err => this.handleError(err))); + } + + + /** GET /api/{controller}/{id} */ + getById(id: string): Observable { + return this.http + .get(`${this.endpoint}/${id}`) + .pipe( + map(response => this.prepareResponse(response)), + catchError(err => this.handleError(err))); + } + + + + /** POST /api/{controller} -> string */ + create(entity: T): Observable { + return this.http + .post(this.endpoint, entity, { headers: this.jsonHeaders }) + .pipe(catchError(err => this.handleError(err))); + } + + + /** POST /api/{controller}/batch -> string[] */ + createRange(entities: T[]): Observable { + return this.http + .post(`${this.endpoint}/batch`, entities, { headers: this.jsonHeaders }) + .pipe(catchError(err => this.handleError(err))); + } + + + /** PUT /api/{controller} */ + update(entity: T): Observable { + return this.http + .put(this.endpoint, entity, { headers: this.jsonHeaders }) + .pipe(catchError(err => this.handleError(err))); + } + + + /** PUT /api/{controller}/batch -> number (updated count) */ + updateRange(entities: T[]): Observable { + return this.http + .put(`${this.endpoint}/batch`, entities, { headers: this.jsonHeaders }) + .pipe(catchError(err => this.handleError(err))); + } + + + /** DELETE /api/{controller}/{id} */ + delete(id: string): Observable { + return this.http + .delete(`${this.endpoint}/${id}`) + .pipe(catchError(err => this.handleError(err))); + } + + + /** DELETE /api/{controller}/batch -> text summary */ + deleteRange(ids: string[]): Observable { + // API returns a plain text message; map it into a TextResponse for convenience + return this.http + .delete(`${this.endpoint}/batch`, { + body: ids, + headers: this.jsonHeaders + }) + .pipe( + map((response: any) => ({ message: response || 'Batch delete completed' })), + catchError(err => this.handleError(err)) + ); + } + + + /** GET /api/{controller}/{id}/exists -> boolean */ + exists(id: string): Observable { + return this.http + .get(`${this.endpoint}/${id}/exists`) + .pipe(catchError(err => this.handleError(err))); + } + + + /** GET /api/{controller}/count -> number */ + count(): Observable { + return this.http + .get(`${this.endpoint}/count`) + .pipe(catchError(err => this.handleError(err))); + } +} \ No newline at end of file diff --git a/APP/src/app/features/dashboard/dashboard.css b/APP/src/app/features/dashboard/dashboard.css new file mode 100644 index 0000000..e69de29 diff --git a/APP/src/app/features/dashboard/dashboard.html b/APP/src/app/features/dashboard/dashboard.html new file mode 100644 index 0000000..25d970f --- /dev/null +++ b/APP/src/app/features/dashboard/dashboard.html @@ -0,0 +1,294 @@ +
+

Dashboard

+
+ + +
+ +
+ {{card.title}} + {{card.info}} +
+
+ + + +
+
+ Calendar +
+
+ + +
+
+ + + +
+
+ Bed + Occupancy + +
+
+ + + + + + + + + + + + + + + + + +
+
+ + + +
+
+ Staff + +
+
+ + +
+ + + + +
+
{{dataItem.name}}
+
{{dataItem.specialty}}
+
+
+
+
+
+
+ +
+
+ + + +
+
+ Appointments + +
+
+
+
+ {{appointment.doctor}} +
+ + {{appointment.start}} + +
+
+
+
Appointment with {{appointment.patient.name}}.
+ +
+
+ + +
+
+
+
+ +
+
+ + + +
+
+ Infection + Rate +
+
+ + + + + + + + +
+
+ + + +
+
+ Equipment + Availability + +
+
+ + + + + + + + +
+
+ + + +
+
+ Average Length of + Stay + +
+
+ + + + + + + + + + + + + + +
+
+ + + +
+
+ Hospital + Visits + +
+
+ + + + + + + + + + + + + + +
+
+ + + +
+
+ Satisfaction + Score + +
+
+ + + + + + + + + +
+
+ + + +
+
+ Mortality + Rate + +
+
+ + + + + + + + + + + + + + + + +
+
+ +
+
\ No newline at end of file diff --git a/APP/src/app/features/dashboard/dashboard.ts b/APP/src/app/features/dashboard/dashboard.ts new file mode 100644 index 0000000..da15747 --- /dev/null +++ b/APP/src/app/features/dashboard/dashboard.ts @@ -0,0 +1,86 @@ +import { Component } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { ChartsModule, SeriesLabelsContentArgs } from '@progress/kendo-angular-charts'; +import { DateInputsModule } from '@progress/kendo-angular-dateinputs'; +import { DropDownsModule } from '@progress/kendo-angular-dropdowns'; +import { ListViewModule } from '@progress/kendo-angular-listview'; +import { BadgeAlign, IndicatorsModule } from '@progress/kendo-angular-indicators'; +import { ButtonsModule } from '@progress/kendo-angular-buttons'; +import { IconsModule } from '@progress/kendo-angular-icons'; +import { LayoutModule } from '@progress/kendo-angular-layout'; +import { SVGIcon, envelopeIcon } from '@progress/kendo-svg-icons'; +import { + appointments, + averageStay, + compactCards, + departments, + donutData, + heatmapDataCDC, + heatmapDataCampylobacteriosis, + heatmapDataHepatitis, + heatmapDataInfluenza, + heatmapDataMeasles, + heatmapDataRSV, + hospitalVisits, + hours, + listItems, + mortalityCauses, + satisfaction +} from './models'; + +@Component({ + selector: 'app-dashboard', + standalone: true, + imports: [ + CommonModule, + FormsModule, + ChartsModule, + DateInputsModule, + DropDownsModule, + ListViewModule, + IndicatorsModule, + ButtonsModule, + IconsModule, + LayoutModule + ], + templateUrl: './dashboard.html', + styleUrl: './dashboard.css' +}) +export class Dashboard { + public cardClasses = 'k-d-flex k-border k-border-solid k-border-border k-bg-surface-alt k-align-items-center k-overflow-x-auto k-p-3 k-gap-6 k-elevation-1 k-rounded-md'; + public dashboardClasses = 'k-d-flex k-flex-col k-border k-border-solid k-border-border k-bg-surface-alt k-overflow-x-auto k-elevation-1 k-rounded-md'; + + public envelopeIcon: SVGIcon = envelopeIcon; + + public badgeAlignBottomEnd: BadgeAlign = { + vertical: 'bottom', + horizontal: 'end' + }; + + public chartLabelContent(e: SeriesLabelsContentArgs): string { + return e.category; + } + + public date = new Date(2023, 5, 14); + public date2 = new Date(2023, 5, 15); + public averageStay = averageStay; + public hours = hours + public hospitalVisits = hospitalVisits; + public departments = departments; + public mortalityCauses = mortalityCauses; + public satisfaction = satisfaction; + public donutData = donutData; + public heatmapDataRSV = heatmapDataRSV; + public heatmapDataCDC = heatmapDataCDC; + public heatmapDataMeasles = heatmapDataMeasles; + public heatmapDataInfluenza = heatmapDataInfluenza; + public heatmapDataHepatitis = heatmapDataHepatitis + public heatmapDataCampylobacteriosis = heatmapDataCampylobacteriosis; + public heatmapData = (dataset: string): any[] => (this as any)[`heatmapData${dataset}`]; + public appointments = appointments; + public ddlData = ['All Departments']; + public ddlValue = 'All Departments' + public compactCards = compactCards; + public listItems: any[] = listItems; +} diff --git a/APP/src/app/features/dashboard/models.ts b/APP/src/app/features/dashboard/models.ts new file mode 100644 index 0000000..b308208 --- /dev/null +++ b/APP/src/app/features/dashboard/models.ts @@ -0,0 +1,502 @@ +import { accessibilityIcon, calendarDateIcon, calendarIcon, displayBlockIcon, dollarIcon, fileIcon, inboxIcon, myspaceIcon, pencilIcon, starOutlineIcon } from "@progress/kendo-svg-icons"; + +export const menuItems = [ + "Settings", + "Support", + "Log out" +]; + +export const averageStay = [4, 3, 2, 14, 5, 7, 5, 6, 12, 1, 4]; + +export const hours = Array(48).fill({}).map((_, idx) => `${Math.floor(idx / 2)}:${idx % 2 ? '30': '00'}`); + +export const hospitalVisits = [14, 20, 20, 26, 30, 26, 29, 32, 31, 29, 31, 35, 36, 40, 42, 45, 61, 63, 65, 66, 67, 67, 63, 64, 63, 62, 60, 45, 52, 55, 48, 44, 38, 35, 31, 35, 36, 40, 42, 55, 50, 41, 41, 39, 31, 32, 23, 27]; + +export const departments = [ + 'Pharmacology & Toxicology', + 'Gastroenterology', + 'Radiology', + 'Orthopedics', + 'Outpatient', + 'Oncology', + 'Neurology', + 'ICU', + 'Cardiology', + 'Emergency', + 'Delivery' +]; + +export const mortalityCauses = [ + 'Pharmacology & Toxicology', + 'Oncological diseases', + 'Circulatory diseases', + 'Injury and poisoning', + 'Respiratory diseases', + 'Endocrine diseases', + 'Digestive diseases', + 'Nervous system diseases', + 'Infectious diseases', + 'Kidney diseases', + 'Other causes' +]; + +export const satisfaction = [ + { + kind: 'Very dissatisfied', + share: 60 + }, + { + kind: 'Dissatisfied', + share: 60 + }, + { + kind: 'Neutral', + share: 60 + }, + { + kind: 'Satisfied', + share: 60 + }, + { + kind: 'Very satisfied', + share: 60 + }, + { + kind: 'Didn\'t answer', + share: 60 +}]; + +export const donutData = [ + { + kind: 'Imaging Equipment', + share: 0.17, + }, + { + kind: 'Surgical Instruments', + share: 0.17, + }, + { + kind: 'Electromedical Equipment', + share: 0.17, + }, + { + kind: 'Transport and Storage', + share: 0.17, + }, + { + kind: 'Endoscopic Instruments', + share: 0.17, + }, + { + kind: 'Others', + share: 0.17, +}]; + +export const heatmapDataRSV = [{ + a: 'June 2023', + b: 'RSV', + value: 66 +}, { + a: 'May 2023', + b: 'RSV', + value: 34 +}, { + a: 'Apr 2023', + b: 'RSV', + value: 13 +}, { + a: 'Mar 2023', + b: 'RSV', + value: 49 +}, { + a: 'Feb 2023', + b: 'RSV', + value: 22 +}, { + a: 'Jan 2023', + b: 'RSV', + value: 66 +}, { + a: 'Dec 2022', + b: 'RSV', + value: 78 +}, { + a: 'Nov 2022', + b: 'RSV', + value: 89 +}, { + a: 'Oct 2022', + b: 'RSV', + value: 27 +}, { + a: 'Sep 2022', + b: 'RSV', + value: 83 +}]; + +export const heatmapDataCDC = [{ + a: 'June 2023', + b: 'CDC', + value: 51 +}, { + a: 'May 2023', + b: 'CDC', + value: 84 +}, { + a: 'Apr 2023', + b: 'CDC', + value: 32 +}, { + a: 'Mar 2023', + b: 'CDC', + value: 16 +}, { + a: 'Feb 2023', + b: 'CDC', + value: 11 +}, { + a: 'Jan 2023', + b: 'CDC', + value: 55 +}, { + a: 'Dec 2022', + b: 'CDC', + value: 99 +}, { + a: 'Nov 2022', + b: 'CDC', + value: 42 +}, { + a: 'Oct 2022', + b: 'CDC', + value: 30 +}, { + a: 'Sep 2022', + b: 'CDC', + value: 10 +}]; + +export const heatmapDataMeasles = [{ + a: 'June 2023', + b: 'Measles', + value: 80 +}, { + a: 'May 2023', + b: 'Measles', + value: 56 +}, { + a: 'Apr 2023', + b: 'Measles', + value: 78 +}, { + a: 'Mar 2023', + b: 'Measles', + value: 63 +}, { + a: 'Feb 2023', + b: 'Measles', + value: 24 +}, { + a: 'Jan 2023', + b: 'Measles', + value: 33 +}, { + a: 'Dec 2022', + b: 'Measles', + value: 38 +}, { + a: 'Nov 2022', + b: 'Measles', + value: 17 +}, { + a: 'Oct 2022', + b: 'Measles', + value: 62 +}, { + a: 'Sep 2022', + b: 'Measles', + value: 82 +}]; + +export const heatmapDataInfluenza = [{ + a: 'June 2023', + b: 'Influenza', + value: 84 +}, { + a: 'May 2023', + b: 'Influenza', + value: 25 +}, { + a: 'Apr 2023', + b: 'Influenza', + value: 59 +}, { + a: 'Mar 2023', + b: 'Influenza', + value: 74 +}, { + a: 'Feb 2023', + b: 'Influenza', + value: 41 +}, { + a: 'Jan 2023', + b: 'Influenza', + value: 69 +}, { + a: 'Dec 2022', + b: 'Influenza', + value: 71 +}, { + a: 'Nov 2022', + b: 'Influenza', + value: 11 +}, { + a: 'Oct 2022', + b: 'Influenza', + value: 23 +}, { + a: 'Sep 2022', + b: 'Influenza', + value: 43 +}]; + +export const heatmapDataHepatitis = [{ + a: 'June 2023', + b: 'Hepatitis', + value: 31 +}, { + a: 'May 2023', + b: 'Hepatitis', + value: 27 +}, { + a: 'Apr 2023', + b: 'Hepatitis', + value: 16 +}, { + a: 'Mar 2023', + b: 'Hepatitis', + value: 74 +}, { + a: 'Feb 2023', + b: 'Hepatitis', + value: 50 +}, { + a: 'Jan 2023', + b: 'Hepatitis', + value: 6 +}, { + a: 'Dec 2022', + b: 'Hepatitis', + value: 22 +}, { + a: 'Nov 2022', + b: 'Hepatitis', + value: 65 +}, { + a: 'Oct 2022', + b: 'Hepatitis', + value: 37 +}, { + a: 'Sep 2022', + b: 'Hepatitis', + value: 13 +}]; + +export const heatmapDataCampylobacteriosis = [{ + a: 'June 2023', + b: 'Campylobacteriosis', + value: 66 +}, { + a: 'May 2023', + b: 'Campylobacteriosis', + value: 21 +}, { + a: 'Apr 2023', + b: 'Campylobacteriosis', + value: 52 +}, { + a: 'Mar 2023', + b: 'Campylobacteriosis', + value: 43 +}, { + a: 'Feb 2023', + b: 'Campylobacteriosis', + value: 97 +}, { + a: 'Jan 2023', + b: 'Campylobacteriosis', + value: 81 +}, { + a: 'Dec 2022', + b: 'Campylobacteriosis', + value: 28 +}, { + a: 'Nov 2022', + b: 'Campylobacteriosis', + value: 34 +}, { + a: 'Oct 2022', + b: 'Campylobacteriosis', + value: 45 +}, { + a: 'Sep 2022', + b: 'Campylobacteriosis', + value: 18 +}]; + +export const appointments = [{ + doctor: 'Dr. Terrell Fashey', + start: '8:30 AM', + patient: { + name: 'Flora Strosin', + phone: '679-747-6105', + email: 'flora.strosin@email.com' + } +}, { + doctor: 'Dr. Clarence Gulgowski', + start: '9:10 AM', + patient: { + name: 'Michele Nicolas', + phone: '884-528-7089', + email: 'm.nicolas@email.com' + } +}, { + doctor: 'Dr. Jay Mohr', + start: '9:45 AM', + patient: { + name: 'Joseph Pacocha', + phone: '777-284-2912', + email: 'j.pacocha@email.com' + } +}]; + +export const compactCards = [{ + svgIcon: calendarIcon, + title: 'Appointments', + info: '78 appointments today' +}, { + svgIcon: accessibilityIcon, + title: 'Patients', + info: '1234 active cases' +}, { + svgIcon: displayBlockIcon, + title: 'Beds', + info: '56 occupied beds' +}, { + svgIcon: myspaceIcon, + title: 'Staff', + info: '78 colleagues at work' +}]; + +export const listItems = [{ + name: 'Dr. Teresa Conn', + specialty: 'Internal medicine', + imageSrc: 'assets/healthcare-dashboard/avatar_1.png' +}, { + name: 'Dr. Mitchell Robel', + specialty: 'Pediatrics', + imageSrc: 'assets/healthcare-dashboard/avatar_2.png' +}, { + name: 'Dr. Barry Jacobs', + specialty: 'Gastroenterology', + imageSrc: 'assets/healthcare-dashboard/avatar_3.png' +}, { + name: 'Dr. Nina Bosco', + specialty: 'Cardiology', + imageSrc: 'assets/healthcare-dashboard/avatar_4.png' +}]; + +export const drawerItems = [{ + text: 'Dashboard', + svgIcon: inboxIcon, + selected: true, + id: 0, +}, { + text: 'Schedule', + svgIcon: calendarDateIcon, + id: 1 +}, { + text: 'Patients', + svgIcon: accessibilityIcon, + id: 2, +}, { + text: 'Bed Management', + svgIcon: displayBlockIcon, + id: 3 +}, { + text: 'Staff', + svgIcon: myspaceIcon, + id: 4, +}, { + text: 'Doctors', + svgIcon: accessibilityIcon, + id: 40, + parentId: 4 +}, { + text: 'Nurses', + svgIcon: accessibilityIcon, + id: 41, + parentId: 4 +}, { + text: 'Therapists', + svgIcon: accessibilityIcon, + id: 42, + parentId: 4 +}, { + text: 'Technicians', + svgIcon: accessibilityIcon, + id: 43, + parentId: 4 +}, { + text: 'Information technology', + svgIcon: accessibilityIcon, + id: 44, + parentId: 4 +}, { + text: 'Food services', + svgIcon: accessibilityIcon, + id: 45, + parentId: 4 +}, { + text: 'Environmental services', + svgIcon: accessibilityIcon, + id: 46, + parentId: 4 +}, { + text: 'Pharmacy', + svgIcon: pencilIcon, + id: 5, +}, { + text: 'Reports', + svgIcon: fileIcon, + id: 6, +}, { + text: 'Report 1', + svgIcon: fileIcon, + id: 60, + parentId: 6 +}, { + text: 'Departments', + svgIcon: calendarIcon, + id: 7, +}, { + text: 'Report 1', + svgIcon: calendarIcon, + id: 70, + parentId: 7 +}, { + text: 'Payments', + svgIcon: dollarIcon, + id: 8, +}, { + text: 'Payments 1', + svgIcon: dollarIcon, + id: 80, + parentId: 8 +}, { + separator: true +}, { + text: 'Support', + svgIcon: starOutlineIcon, + id: 9, +}]; diff --git a/APP/src/app/features/login-page/login-page.component.html b/APP/src/app/features/login-page/login-page.component.html new file mode 100644 index 0000000..9647663 --- /dev/null +++ b/APP/src/app/features/login-page/login-page.component.html @@ -0,0 +1,238 @@ + \ No newline at end of file diff --git a/APP/src/app/features/login-page/login-page.component.scss b/APP/src/app/features/login-page/login-page.component.scss new file mode 100644 index 0000000..f232d12 --- /dev/null +++ b/APP/src/app/features/login-page/login-page.component.scss @@ -0,0 +1,727 @@ +.login-page-container { + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + background: linear-gradient(135deg, #1e3a8a 0%, #1e40af 50%, #3b82f6 100%); + position: relative; + overflow: hidden; + padding: 1rem; +} + +// Background Shapes +.background-shapes { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + overflow: hidden; + z-index: 0; +} + +.shape { + position: absolute; + border-radius: 50%; + background: rgba(255, 255, 255, 0.1); + animation: float 6s ease-in-out infinite; + + &.shape-1 { + width: 200px; + height: 200px; + top: 10%; + left: 10%; + animation-delay: 0s; + } + + &.shape-2 { + width: 150px; + height: 150px; + top: 60%; + right: 15%; + animation-delay: 2s; + } + + &.shape-3 { + width: 100px; + height: 100px; + bottom: 20%; + left: 20%; + animation-delay: 4s; + } +} + +@keyframes float { + 0%, + 100% { + transform: translateY(0px) rotate(0deg); + } + 50% { + transform: translateY(-20px) rotate(180deg); + } +} + +// Main Content +.login-content { + display: grid; + grid-template-columns: 1fr 1fr; + max-width: 1200px; + width: 100%; + background: rgba(255, 255, 255, 0.95); + border-radius: 20px; + box-shadow: 0 25px 50px rgba(0, 0, 0, 0.15); + -webkit-backdrop-filter: blur(10px); + backdrop-filter: blur(10px); + overflow: hidden; + position: relative; + z-index: 1; +} + +// Branding Section +.branding-section { + background: linear-gradient(135deg, #1e3a8a 0%, #1e40af 50%, #3b82f6 100%); + padding: 3rem; + display: flex; + align-items: center; + justify-content: center; + color: white; + position: relative; + + &::before { + content: ""; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: url('data:image/svg+xml,'); + opacity: 0.3; + } +} + +.branding-content { + position: relative; + z-index: 1; + text-align: center; +} + +.logo-container { + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 2rem; + gap: 1rem; + + .logo-image { + height: 60px; + width: auto; + filter: brightness(0) invert(1); + } + + .logo-text { + h1 { + margin: 0; + font-size: 2.5rem; + font-weight: 700; + letter-spacing: -0.02em; + } + + .tagline { + font-size: 1rem; + opacity: 0.9; + font-weight: 300; + } + } +} + +.welcome-text { + margin-bottom: 3rem; + + h2 { + font-size: 2rem; + font-weight: 600; + margin: 0 0 1rem 0; + letter-spacing: -0.01em; + } + + p { + font-size: 1.1rem; + opacity: 0.9; + line-height: 1.6; + margin: 0; + } +} + +.features-list { + display: flex; + flex-direction: column; + gap: 1rem; + + .feature-item { + display: flex; + align-items: center; + gap: 1rem; + padding: 0.75rem; + background: rgba(255, 255, 255, 0.1); + border-radius: 12px; + -webkit-backdrop-filter: blur(10px); + backdrop-filter: blur(10px); + transition: all 0.3s ease; + + &:hover { + background: rgba(255, 255, 255, 0.2); + transform: translateX(5px); + } + + .feature-icon { + font-size: 1.5rem; + width: 40px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; + background: rgba(255, 255, 255, 0.2); + border-radius: 10px; + } + + span { + font-weight: 500; + font-size: 1rem; + } + } +} + +// Login Section +.login-section { + padding: 3rem; + display: flex; + align-items: center; + justify-content: center; +} + +.login-card { + width: 100%; + max-width: 400px; +} + +.login-header { + text-align: center; + margin-bottom: 2rem; + + h3 { + font-size: 2rem; + font-weight: 700; + color: #1a1a1a; + margin: 0 0 0.5rem 0; + letter-spacing: -0.01em; + } + + p { + color: #666; + font-size: 1rem; + margin: 0; + } +} + +.login-actions { + margin-bottom: 2rem; +} + +.signin-button { + width: 100%; + height: 56px; + border-radius: 12px; + font-size: 1.1rem; + font-weight: 600; + background: linear-gradient(135deg, #1e3a8a 0%, #1e40af 50%, #3b82f6 100%); + border: none; + box-shadow: 0 8px 25px rgba(30, 58, 138, 0.3); + transition: all 0.3s ease; + + &:hover { + transform: translateY(-2px); + box-shadow: 0 12px 35px rgba(30, 58, 138, 0.4); + } + + &:active { + transform: translateY(0); + } + + .button-content { + display: flex; + align-items: center; + justify-content: center; + gap: 0.5rem; + } + + .button-icon { + width: 20px; + height: 20px; + } +} + +// Demo Section +.demo-section { + background: #f8f9fa; + border-radius: 16px; + padding: 1.5rem; + border: 1px solid #e9ecef; +} + +.demo-header { + display: flex; + align-items: center; + gap: 0.5rem; + margin-bottom: 1rem; + color: #495057; + font-weight: 600; + font-size: 0.9rem; + + .demo-icon { + width: 16px; + height: 16px; + } +} + +.credential-tabs { + display: flex; + background: white; + border-radius: 8px; + padding: 4px; + margin-bottom: 1rem; + border: 1px solid #e9ecef; +} + +.tab-button { + flex: 1; + padding: 0.75rem 1rem; + border: none; + background: transparent; + border-radius: 6px; + font-size: 0.9rem; + font-weight: 500; + color: #6c757d; + cursor: pointer; + transition: all 0.2s ease; + + &.active { + background: #1e40af; + color: white; + box-shadow: 0 2px 4px rgba(30, 64, 175, 0.2); + } + + &:hover:not(.active) { + background: #f8f9fa; + color: #495057; + } +} + +.credential-content { + .credential-item { + display: flex; + align-items: center; + gap: 0.75rem; + padding: 0.75rem; + background: white; + border-radius: 8px; + margin-bottom: 0.5rem; + border: 1px solid #e9ecef; + transition: all 0.2s ease; + + &:hover { + border-color: #1e40af; + box-shadow: 0 2px 8px rgba(30, 64, 175, 0.1); + } + + .label { + font-weight: 600; + color: #495057; + min-width: 60px; + font-size: 0.9rem; + } + + .value { + flex: 1; + font-family: "Monaco", "Menlo", "Ubuntu Mono", monospace; + font-size: 0.9rem; + color: #1a1a1a; + background: #f8f9fa; + padding: 0.25rem 0.5rem; + border-radius: 4px; + } + + .copy-btn { + background: #1e40af; + border: none; + border-radius: 6px; + padding: 0.5rem; + color: white; + cursor: pointer; + transition: all 0.2s ease; + display: flex; + align-items: center; + justify-content: center; + + &:hover { + background: #1e3a8a; + transform: scale(1.05); + } + + svg { + width: 14px; + height: 14px; + } + } + } +} + +// Login Form Styles +.login-form-state { + .login-header { + position: relative; + text-align: center; + + .back-button { + position: absolute; + left: 0; + top: 50%; + transform: translateY(-50%); + background: none; + border: none; + padding: 0.5rem; + border-radius: 8px; + cursor: pointer; + color: #666; + transition: all 0.2s ease; + display: flex; + align-items: center; + justify-content: center; + + &:hover { + background: #f8f9fa; + color: #1e40af; + } + + svg { + width: 20px; + height: 20px; + } + } + + h3 { + margin: 0 0 0.5rem 0; + } + + p { + margin: 0 0 1.5rem 0; + font-size: 0.95rem; + } + } +} + +.login-form { + .error-message { + display: flex; + align-items: center; + gap: 0.5rem; + background: #fee2e2; + color: #dc2626; + padding: 0.75rem 1rem; + border-radius: 8px; + border: 1px solid #fecaca; + margin-bottom: 1.5rem; + font-size: 0.9rem; + font-weight: 500; + + .error-icon { + width: 16px; + height: 16px; + flex-shrink: 0; + } + } + + .form-field { + margin-bottom: 1.5rem; + + kendo-label { + display: block; + margin-bottom: 0.5rem; + font-weight: 600; + color: #374151; + font-size: 0.9rem; + } + + kendo-textbox { + width: 100%; + + .k-textbox { + height: 48px; + border-radius: 8px; + border: 2px solid #e5e7eb; + font-size: 1rem; + transition: all 0.2s ease; + + &:focus { + border-color: #1e40af; + box-shadow: 0 0 0 3px rgba(30, 64, 175, 0.1); + } + + &.k-invalid { + border-color: #dc2626; + } + } + } + + .field-error { + margin-top: 0.25rem; + font-size: 0.8rem; + color: #dc2626; + font-weight: 500; + } + + &.checkbox-field { + margin-bottom: 1rem; + + .checkbox-container { + display: flex; + align-items: center; + gap: 0.5rem; + cursor: pointer; + user-select: none; + + kendo-checkbox { + margin: 0; + } + + .checkbox-label { + font-size: 0.9rem; + color: #6b7280; + font-weight: 500; + margin: 0; + } + + &:hover .checkbox-label { + color: #1e40af; + } + + // Style when checkbox is checked + &:has(kendo-checkbox:checked) .checkbox-label { + color: #1e40af; + } + } + } + } + + .form-actions { + margin-top: 2rem; + + .submit-button { + width: 100%; + height: 48px; + border-radius: 8px; + font-size: 1rem; + font-weight: 600; + background: linear-gradient(135deg, #1e3a8a 0%, #1e40af 50%, #3b82f6 100%); + border: none; + box-shadow: 0 4px 12px rgba(30, 58, 138, 0.3); + transition: all 0.3s ease; + + &:hover:not(:disabled) { + transform: translateY(-1px); + box-shadow: 0 6px 20px rgba(30, 58, 138, 0.4); + } + + &:disabled { + opacity: 0.6; + cursor: not-allowed; + transform: none; + } + + .button-content { + display: flex; + align-items: center; + justify-content: center; + gap: 0.5rem; + } + + .button-icon { + width: 18px; + height: 18px; + } + } + } +} + +// Mobile Responsive +@media (max-width: 768px) { + .login-page-container { + padding: 0.5rem; + } + + .login-content { + grid-template-columns: 1fr; + border-radius: 16px; + } + + .branding-section { + padding: 2rem 1.5rem; + order: 2; + } + + .login-section { + padding: 2rem 1.5rem; + order: 1; + } + + .logo-container { + flex-direction: column; + gap: 0.5rem; + + .logo-text h1 { + font-size: 2rem; + } + } + + .welcome-text { + margin-bottom: 2rem; + + h2 { + font-size: 1.5rem; + } + + p { + font-size: 1rem; + } + } + + .features-list { + .feature-item { + padding: 0.5rem; + + .feature-icon { + width: 35px; + height: 35px; + font-size: 1.25rem; + } + + span { + font-size: 0.9rem; + } + } + } + + .login-header h3 { + font-size: 1.75rem; + } + + .signin-button { + height: 50px; + font-size: 1rem; + } + + .demo-section { + padding: 1rem; + } + + .credential-tabs { + .tab-button { + padding: 0.5rem 0.75rem; + font-size: 0.85rem; + } + } + + .credential-content { + .credential-item { + padding: 0.5rem; + + .label { + min-width: 50px; + font-size: 0.85rem; + } + + .value { + font-size: 0.85rem; + } + } + } + + // Login form mobile styles + .login-form-state { + .login-header { + .back-button { + padding: 0.4rem; + + svg { + width: 18px; + height: 18px; + } + } + } + } + + .login-form { + .form-field { + margin-bottom: 1.25rem; + + kendo-textbox .k-textbox { + height: 44px; + font-size: 0.95rem; + } + } + + .form-actions .submit-button { + height: 44px; + font-size: 0.95rem; + } + } +} + +@media (max-width: 480px) { + .login-page-container { + padding: 0.25rem; + } + + .branding-section, + .login-section { + padding: 1.5rem 1rem; + } + + .logo-container .logo-text h1 { + font-size: 1.75rem; + } + + .welcome-text h2 { + font-size: 1.25rem; + } + + .features-list { + .feature-item { + .feature-icon { + width: 30px; + height: 30px; + font-size: 1rem; + } + + span { + font-size: 0.85rem; + } + } + } + + // Login form extra small mobile styles + .login-form { + .form-field { + margin-bottom: 1rem; + + kendo-textbox .k-textbox { + height: 40px; + font-size: 0.9rem; + } + } + + .form-actions .submit-button { + height: 40px; + font-size: 0.9rem; + } + } +} diff --git a/APP/src/app/features/login-page/login-page.ts b/APP/src/app/features/login-page/login-page.ts new file mode 100644 index 0000000..3e5ac84 --- /dev/null +++ b/APP/src/app/features/login-page/login-page.ts @@ -0,0 +1,205 @@ +import { Component, OnInit, ViewChild } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { DialogModule, DialogService } from '@progress/kendo-angular-dialog'; +import { ButtonsModule } from '@progress/kendo-angular-buttons'; +import { FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms'; +import { InputsModule } from '@progress/kendo-angular-inputs'; +import { LabelModule } from '@progress/kendo-angular-label'; +import { IndicatorsModule } from '@progress/kendo-angular-indicators'; +import { MfaDialogComponent } from '../../shared/mfa-dialog/mfa-dialog.component'; +import { AuthService, LoginCredentials, LoginResultType, TokenVerificationResult } from '../../shared/services/auth.service'; +import { Router, ActivatedRoute } from '@angular/router'; + +@Component({ + selector: 'app-login-page', + standalone: true, + imports: [ + CommonModule, + DialogModule, + ButtonsModule, + ReactiveFormsModule, + InputsModule, + LabelModule, + IndicatorsModule, + MfaDialogComponent + ], + templateUrl: './login-page.component.html', + styleUrls: ['./login-page.component.scss'] +}) +export class LoginPage implements OnInit { + @ViewChild('mfaDialog') mfaDialog!: MfaDialogComponent; + + activeTab: 'user' | 'admin' = 'user'; + showLoginForm = false; + loginForm: FormGroup; + isProcessing = false; + showError = false; + errorMessage = ''; + + constructor( + private dialogService: DialogService, + private authService: AuthService, + private router: Router, + private route: ActivatedRoute, + private fb: FormBuilder + ) { + this.loginForm = this.fb.group({ + email: ['', [Validators.required, Validators.email]], + password: ['', [Validators.required, Validators.minLength(6)]], + rememberMe: [false] + }); + } + + ngOnInit(): void { + // Check if user is already logged in + if (this.authService.isAuthenticated()) { + this.redirectToDashboard(); + return; + } + + // Check for token in URL parameters + this.route.queryParams.subscribe(params => { + const token = params['token']; + if (token) { + this.verifySecretLinkToken(token); + } + }); + } + + setActiveTab(tab: 'user' | 'admin'): void { + this.activeTab = tab; + } + + copyToClipboard(text: string): void { + navigator.clipboard.writeText(text).then(() => { + // You could add a toast notification here + console.log('Copied to clipboard:', text); + }).catch(err => { + console.error('Failed to copy text: ', err); + }); + } + + showLoginFormView(): void { + this.showLoginForm = true; + // Focus on email input when form appears + setTimeout(() => { + const emailInput = document.querySelector('input[formControlName="email"]') as HTMLInputElement; + if (emailInput) { + emailInput.focus(); + } + }, 100); + } + + goBackToInitialState(): void { + this.showLoginForm = false; + this.loginForm.reset(); + this.showError = false; + this.errorMessage = ''; + } + + onSubmit(): void { + if (this.loginForm.valid && !this.isProcessing) { + this.isProcessing = true; + this.showError = false; + + const credentials: LoginCredentials = this.loginForm.value; + + this.authService.login(credentials).subscribe({ + next: (result) => { + this.isProcessing = false; + + if (result.result === LoginResultType.Success) { + this.authService.setCurrentUser(result.responseData!); + this.redirectToDashboard(); + } else if (result.result === LoginResultType.MfaRequired) { + this.showMfaDialog(credentials); + } else { + this.showError = true; + this.errorMessage = result.message || 'Invalid email or password'; + } + }, + error: (error) => { + this.isProcessing = false; + this.showError = true; + this.errorMessage = 'An error occurred during login. Please try again.'; + console.error('Login error:', error); + } + }); + } + } + + get emailControl() { + return this.loginForm.get('email'); + } + + get passwordControl() { + return this.loginForm.get('password'); + } + + private showMfaDialog(credentials: LoginCredentials): void { + if (this.mfaDialog) { + // Set the login data for MFA dialog + (this.mfaDialog as any).loginData = credentials; + + // Show MFA dialog + this.mfaDialog.show(); + } + } + + onMfaSuccess(userData: any): void { + this.authService.setCurrentUser(userData); + this.redirectToDashboard(); + } + + onMfaCancel(): void { + // Reset form and focus on email + this.loginForm.reset(); + setTimeout(() => { + const emailInput = document.querySelector('input[formControlName="email"]') as HTMLInputElement; + if (emailInput) { + emailInput.focus(); + } + }, 100); + } + + + private verifySecretLinkToken(token: string): void { + this.isProcessing = true; + this.showError = false; + + // First check if token is expired locally + if (this.authService.isTokenExpired(token)) { + this.isProcessing = false; + this.showError = true; + this.errorMessage = 'This link has expired. Please request a new one.'; + return; + } + + this.authService.verifySecretLinkToken(token).subscribe({ + next: (result: TokenVerificationResult) => { + this.isProcessing = false; + + if (result.isValid && result.user) { + // Token is valid, set user and redirect + this.authService.setCurrentUser(result.user); + this.redirectToDashboard(); + } else { + // Token verification failed + this.showError = true; + this.errorMessage = result.message || 'Invalid or expired link. Please request a new one.'; + } + }, + error: (error) => { + this.isProcessing = false; + this.showError = true; + this.errorMessage = 'An error occurred while verifying the link. Please try again.'; + console.error('Token verification error:', error); + } + }); + } + + private redirectToDashboard(): void { + const redirectUrl = this.authService.getRedirectUrl(); + this.router.navigate([redirectUrl || '/dashboard']); + } +} diff --git a/APP/src/app/features/schedule/schedule.css b/APP/src/app/features/schedule/schedule.css new file mode 100644 index 0000000..e69de29 diff --git a/APP/src/app/layout/footer/footer.component.html b/APP/src/app/layout/footer/footer.component.html new file mode 100644 index 0000000..c387d55 --- /dev/null +++ b/APP/src/app/layout/footer/footer.component.html @@ -0,0 +1,5 @@ + +
+

Copyright © {{ currentYear }} RBJ Software, Inc. All rights reserved.

+
+ \ No newline at end of file diff --git a/APP/src/app/layout/footer/footer.component.scss b/APP/src/app/layout/footer/footer.component.scss new file mode 100644 index 0000000..9922668 --- /dev/null +++ b/APP/src/app/layout/footer/footer.component.scss @@ -0,0 +1,3 @@ +footer { + margin-top: auto; +} diff --git a/APP/src/app/layout/footer/footer.component.ts b/APP/src/app/layout/footer/footer.component.ts new file mode 100644 index 0000000..deac2c2 --- /dev/null +++ b/APP/src/app/layout/footer/footer.component.ts @@ -0,0 +1,14 @@ +import { Component } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +@Component({ + selector: 'app-footer', + standalone: true, + imports: [CommonModule], + templateUrl: './footer.component.html', + styleUrls: ['./footer.component.scss'] +}) +export class FooterComponent { + public currentYear = new Date().getFullYear(); +} + diff --git a/APP/src/app/layout/header/header.component.html b/APP/src/app/layout/header/header.component.html new file mode 100644 index 0000000..2a67fa3 --- /dev/null +++ b/APP/src/app/layout/header/header.component.html @@ -0,0 +1,42 @@ + +
+ + + + + RBJ RBJ Identity logo + RBJ Identity Portal + + + RBJ RBJ Identity compact logo + + + +
+ +
+
+ + + + + + +
+
+ + + + + + + + + {{ isAuthenticated ? (getDisplayName() || currentUser?.email || 'User') : 'Sign In' }} + + + +
+
+ \ No newline at end of file diff --git a/APP/src/app/layout/header/header.component.scss b/APP/src/app/layout/header/header.component.scss new file mode 100644 index 0000000..66836bc --- /dev/null +++ b/APP/src/app/layout/header/header.component.scss @@ -0,0 +1,48 @@ +/* Logo styling */ +.logo-link { + //display: flex; + align-items: center; + gap: 12px; + text-decoration: none; + transition: opacity 0.2s ease; + + &:hover { + opacity: 0.85; + } +} + +.logo-text { + font-size: 1.25rem; + font-weight: 600; + letter-spacing: -0.02em; + background: linear-gradient(135deg, #0066cc 0%, #0052a3 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + white-space: nowrap; + + // Fallback for browsers that don't support background-clip + @supports not ((-webkit-background-clip: text) or (background-clip: text)) { + color: #0066cc; + background: none; + -webkit-text-fill-color: initial; + } +} + +/* Search box responsive styling */ +.search-box-wrapper { + width: 100%; + max-width: 360px; +} + +.search-box { + width: 100%; +} + +/* Mobile optimizations */ +@media (max-width: 767px) { + /* Optimize spacing on mobile */ + kendo-appbar { + padding: 0 8px; + } +} diff --git a/APP/src/app/layout/header/header.component.ts b/APP/src/app/layout/header/header.component.ts new file mode 100644 index 0000000..3e845d3 --- /dev/null +++ b/APP/src/app/layout/header/header.component.ts @@ -0,0 +1,128 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { Router } from '@angular/router'; +import { AppBarModule } from '@progress/kendo-angular-navigation'; +import { ButtonsModule } from '@progress/kendo-angular-buttons'; +import { IndicatorsModule } from '@progress/kendo-angular-indicators'; +import { InputsModule } from '@progress/kendo-angular-inputs'; +import { DropDownsModule } from '@progress/kendo-angular-dropdowns'; +import { IconsModule } from '@progress/kendo-angular-icons'; +import { SVGIcon, bellIcon, menuIcon, searchIcon, userIcon, logoutIcon } from '@progress/kendo-svg-icons'; +import { LayoutService } from '../services/layout.service'; +import { AuthService, User } from '../../shared/services/auth.service'; +import { Subject, takeUntil } from 'rxjs'; + +@Component({ + selector: 'app-header', + standalone: true, + imports: [ + CommonModule, + AppBarModule, + ButtonsModule, + IndicatorsModule, + InputsModule, + IconsModule, + DropDownsModule + ], + templateUrl: './header.component.html', + styleUrls: ['./header.component.scss'] +}) +export class HeaderComponent implements OnInit, OnDestroy { + public menuIcon: SVGIcon = menuIcon; + public searchIcon: SVGIcon = searchIcon; + public bellIcon: SVGIcon = bellIcon; + public userIcon: SVGIcon = userIcon; + public logoutIcon: SVGIcon = logoutIcon; + + public userMenuItems: any[] = []; + public currentUser: User | null = null; + public isAuthenticated = false; + + public badgeAlign = { + vertical: 'top' as const, + horizontal: 'end' as const + }; + + private destroy$ = new Subject(); + + constructor( + public layoutService: LayoutService, + private authService: AuthService, + private router: Router + ) { } + + ngOnInit(): void { + // Subscribe to authentication state changes + this.authService.currentUser$ + .pipe(takeUntil(this.destroy$)) + .subscribe(user => { + this.currentUser = user; + this.isAuthenticated = !!user; + this.updateUserMenu(); + }); + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + + public onMenuClick(): void { + this.layoutService.toggleDrawer(); + } + + public onLogout(): void { + this.authService.logout(); + this.router.navigate(['/login']); + } + + public onUserMenuClick(item: any): void { + if (item.click) { + item.click(); + } + } + + public getDisplayName(): string { + if (this.currentUser) { + const fullName = `${this.currentUser.firstName} ${this.currentUser.lastName}`.trim(); + return fullName || this.currentUser.email; + } + return ''; + } + + private updateUserMenu(): void { + if (this.isAuthenticated && this.currentUser) { + this.userMenuItems = [ + { + text: `Welcome, ${this.getDisplayName() || this.currentUser.email}`, + disabled: true + }, + { separator: true }, + { + text: 'Profile', + icon: 'user', + disabled: true + }, + { + text: 'Settings', + icon: 'settings', + disabled: true + }, + { separator: true }, + { + text: 'Logout', + icon: 'logout', + click: () => this.onLogout() + } + ]; + } else { + this.userMenuItems = [ + { + text: 'Sign In', + click: () => this.router.navigate(['/login']) + } + ]; + } + } +} + diff --git a/APP/src/app/layout/navbar/navbar.component.html b/APP/src/app/layout/navbar/navbar.component.html new file mode 100644 index 0000000..ae2858b --- /dev/null +++ b/APP/src/app/layout/navbar/navbar.component.html @@ -0,0 +1,23 @@ + + + + @if (item.svgIcon) { + + } + {{item.text}} + @if (hasChildren) { + + + + + } + + + + + + + \ No newline at end of file diff --git a/APP/src/app/layout/navbar/navbar.component.scss b/APP/src/app/layout/navbar/navbar.component.scss new file mode 100644 index 0000000..6e80432 --- /dev/null +++ b/APP/src/app/layout/navbar/navbar.component.scss @@ -0,0 +1,20 @@ +/* Drawer animation */ +kendo-drawer { + transition: transform 0.3s ease-in-out; +} + +/* Mobile optimizations */ +@media (max-width: 767px) { + /* Ensure drawer overlay has proper z-index */ + kendo-drawer.k-drawer-overlay { + z-index: 9999; + } +} + +/* Tablet optimizations */ +@media (min-width: 768px) and (max-width: 1023px) { + /* Adjust drawer width for tablets if needed */ + kendo-drawer { + max-width: 280px; + } +} diff --git a/APP/src/app/layout/navbar/navbar.component.ts b/APP/src/app/layout/navbar/navbar.component.ts new file mode 100644 index 0000000..5408c97 --- /dev/null +++ b/APP/src/app/layout/navbar/navbar.component.ts @@ -0,0 +1,77 @@ +import { Component } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { Router, RouterOutlet } from '@angular/router'; +import { LayoutModule } from '@progress/kendo-angular-layout'; +import { IconsModule } from '@progress/kendo-angular-icons'; +import { SVGIcon, chevronDownIcon, chevronUpIcon } from '@progress/kendo-svg-icons'; +import { DrawerItemExpandedFn, DrawerSelectEvent } from '@progress/kendo-angular-layout'; +import { LayoutService } from '../services/layout.service'; +import { drawerItems } from '../../features/dashboard/models'; +import { FooterComponent } from '../footer/footer.component'; + +@Component({ + selector: 'app-navbar', + standalone: true, + imports: [ + CommonModule, + RouterOutlet, + LayoutModule, + IconsModule, + FooterComponent + ], + templateUrl: './navbar.component.html', + styleUrls: ['./navbar.component.scss'] +}) +export class NavbarComponent { + public chevronUpIcon: SVGIcon = chevronUpIcon; + public chevronDownIcon: SVGIcon = chevronDownIcon; + + public drawerItems = drawerItems; + public selectedDrawerItem = 'Dashboard'; + public expandedItems: Array = [4]; + + constructor( + private router: Router, + public layoutService: LayoutService + ) { } + + public onSelect(ev: DrawerSelectEvent): void { + this.selectedDrawerItem = ev.item.text; + const current = ev.item.id; + + if (this.expandedItems.indexOf(current) >= 0) { + this.expandedItems = this.expandedItems.filter((id) => id !== current); + } else { + this.expandedItems.push(current); + } + + // Auto-collapse drawer on mobile after selection + if (this.layoutService.isMobile()) { + this.layoutService.closeDrawer(); + } + + // Navigate based on the selected item + const routeMap: { [key: string]: string } = { + 'Dashboard': '/dashboard', + 'Schedule': '/schedule', + 'Patients': '/patients', + 'Bed Management': '/bed-management', + 'Staff': '/staff', + 'Pharmacy': '/pharmacy', + 'Reports': '/reports', + 'Departments': '/departments', + 'Payments': '/payments', + 'Support': '/support' + }; + + const route = routeMap[ev.item.text]; + if (route) { + this.router.navigate([route]); + } + } + + public isItemExpanded: DrawerItemExpandedFn = (item): boolean => { + return this.expandedItems.indexOf(item.id) >= 0; + }; +} + diff --git a/APP/src/app/layout/services/layout.service.ts b/APP/src/app/layout/services/layout.service.ts new file mode 100644 index 0000000..5c3fcd9 --- /dev/null +++ b/APP/src/app/layout/services/layout.service.ts @@ -0,0 +1,84 @@ +import { Injectable, signal, computed } from '@angular/core'; +import { DrawerMode } from '@progress/kendo-angular-layout'; + +@Injectable({ + providedIn: 'root' +}) +export class LayoutService { + // Signals for reactive state management + private readonly windowWidth = signal(typeof window !== 'undefined' ? window.innerWidth : 1024); + + // Computed signals for responsive breakpoints + public readonly isMobile = computed(() => this.windowWidth() < 768); + public readonly isTablet = computed(() => this.windowWidth() >= 768 && this.windowWidth() < 1024); + public readonly isDesktop = computed(() => this.windowWidth() >= 1024); + + // Drawer state + public readonly drawerExpanded = signal(true); + public readonly drawerMode = computed(() => + this.isMobile() ? 'overlay' : 'push' + ); + public readonly drawerAutoCollapse = computed(() => this.isMobile()); + + constructor() { + this.initializeResizeListener(); + this.updateDrawerState(); + } + + /** + * Initialize window resize listener + */ + private initializeResizeListener(): void { + if (typeof window !== 'undefined') { + window.addEventListener('resize', () => this.handleResize()); + } + } + + /** + * Handle window resize events + */ + private handleResize(): void { + this.windowWidth.set(window.innerWidth); + this.updateDrawerState(); + } + + /** + * Update drawer state based on screen size + */ + private updateDrawerState(): void { + if (this.isMobile()) { + this.drawerExpanded.set(false); + } else { + this.drawerExpanded.set(true); + } + } + + /** + * Toggle drawer open/closed state + */ + public toggleDrawer(): void { + this.drawerExpanded.update(expanded => !expanded); + } + + /** + * Close drawer (useful for mobile after navigation) + */ + public closeDrawer(): void { + this.drawerExpanded.set(false); + } + + /** + * Open drawer + */ + public openDrawer(): void { + this.drawerExpanded.set(true); + } + + /** + * Get current window width + */ + public getWindowWidth(): number { + return this.windowWidth(); + } +} + diff --git a/APP/src/app/portals/user-portal/components/user-header/user-header.component.html b/APP/src/app/portals/user-portal/components/user-header/user-header.component.html new file mode 100644 index 0000000..026598b --- /dev/null +++ b/APP/src/app/portals/user-portal/components/user-header/user-header.component.html @@ -0,0 +1,40 @@ +
+ + + + + RBJ RBJ Identity logo + RBJ Identity Portal + + + RBJ RBJ Identity compact logo + + + +
+ +
+
+ + + + + + +
+
+ + + + + + + + + {{ getDisplayName() || currentUser?.email || 'User' }} + + + +
+
\ No newline at end of file diff --git a/APP/src/app/portals/user-portal/components/user-header/user-header.component.scss b/APP/src/app/portals/user-portal/components/user-header/user-header.component.scss new file mode 100644 index 0000000..7b4176f --- /dev/null +++ b/APP/src/app/portals/user-portal/components/user-header/user-header.component.scss @@ -0,0 +1,21 @@ +.logo-link { + display: flex; + align-items: center; + text-decoration: none; + color: inherit; + + .logo-text { + margin-left: 0.5rem; + font-weight: 600; + font-size: 1.1rem; + } +} + +.search-box-wrapper { + width: 100%; + max-width: 400px; + + .search-box { + width: 100%; + } +} diff --git a/APP/src/app/portals/user-portal/components/user-header/user-header.component.ts b/APP/src/app/portals/user-portal/components/user-header/user-header.component.ts new file mode 100644 index 0000000..a203c42 --- /dev/null +++ b/APP/src/app/portals/user-portal/components/user-header/user-header.component.ts @@ -0,0 +1,118 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { Router } from '@angular/router'; +import { AppBarModule } from '@progress/kendo-angular-navigation'; +import { ButtonsModule } from '@progress/kendo-angular-buttons'; +import { IndicatorsModule } from '@progress/kendo-angular-indicators'; +import { InputsModule } from '@progress/kendo-angular-inputs'; +import { DropDownsModule } from '@progress/kendo-angular-dropdowns'; +import { IconsModule } from '@progress/kendo-angular-icons'; +import { SVGIcon, bellIcon, menuIcon, searchIcon, userIcon, logoutIcon } from '@progress/kendo-svg-icons'; +import { AuthService, User } from '../../../../shared/services/auth.service'; +import { LayoutService } from '../../../../layout/services/layout.service'; +import { Subject, takeUntil } from 'rxjs'; + +@Component({ + selector: 'app-user-header', + standalone: true, + imports: [ + CommonModule, + AppBarModule, + ButtonsModule, + IndicatorsModule, + InputsModule, + IconsModule, + DropDownsModule + ], + templateUrl: './user-header.component.html', + styleUrls: ['./user-header.component.scss'] +}) +export class UserHeaderComponent implements OnInit, OnDestroy { + public menuIcon: SVGIcon = menuIcon; + public searchIcon: SVGIcon = searchIcon; + public bellIcon: SVGIcon = bellIcon; + public userIcon: SVGIcon = userIcon; + public logoutIcon: SVGIcon = logoutIcon; + + public userMenuItems: any[] = []; + public currentUser: User | null = null; + + public badgeAlign = { + vertical: 'top' as const, + horizontal: 'end' as const + }; + + private destroy$ = new Subject(); + + constructor( + private authService: AuthService, + private layoutService: LayoutService, + private router: Router + ) { } + + ngOnInit(): void { + // Subscribe to authentication state changes + this.authService.currentUser$ + .pipe(takeUntil(this.destroy$)) + .subscribe(user => { + this.currentUser = user; + this.updateUserMenu(); + }); + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + + public onMenuClick(): void { + this.layoutService.toggleDrawer(); + } + + public onLogout(): void { + this.authService.logout(); + this.router.navigate(['/login']); + } + + public onUserMenuClick(item: any): void { + if (item.click) { + item.click(); + } + } + + public getDisplayName(): string { + if (this.currentUser) { + const fullName = `${this.currentUser.firstName} ${this.currentUser.lastName}`.trim(); + return fullName || this.currentUser.email; + } + return ''; + } + + private updateUserMenu(): void { + if (this.currentUser) { + this.userMenuItems = [ + { + text: `Welcome, ${this.getDisplayName() || this.currentUser.email}`, + disabled: true + }, + { separator: true }, + { + text: 'Profile', + icon: 'user', + disabled: true + }, + { + text: 'Settings', + icon: 'settings', + disabled: true + }, + { separator: true }, + { + text: 'Logout', + icon: 'logout', + click: () => this.onLogout() + } + ]; + } + } +} diff --git a/APP/src/app/portals/user-portal/components/user-navbar/user-navbar.component.html b/APP/src/app/portals/user-portal/components/user-navbar/user-navbar.component.html new file mode 100644 index 0000000..7fb8370 --- /dev/null +++ b/APP/src/app/portals/user-portal/components/user-navbar/user-navbar.component.html @@ -0,0 +1,42 @@ + + + + +
+
+

User Portal

+

RBJ Identity Portal

+
+ + +
+
+
+
\ No newline at end of file diff --git a/APP/src/app/portals/user-portal/components/user-navbar/user-navbar.component.scss b/APP/src/app/portals/user-portal/components/user-navbar/user-navbar.component.scss new file mode 100644 index 0000000..1e84f9f --- /dev/null +++ b/APP/src/app/portals/user-portal/components/user-navbar/user-navbar.component.scss @@ -0,0 +1,66 @@ +.drawer-content { + height: 100%; + display: flex; + flex-direction: column; +} + +.drawer-header { + padding: 1.5rem 1rem; + border-bottom: 1px solid #e0e0e0; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + + h3 { + margin: 0 0 0.25rem 0; + font-size: 1.25rem; + font-weight: 600; + } + + p { + margin: 0; + font-size: 0.875rem; + opacity: 0.9; + } +} + +.drawer-nav { + flex: 1; + padding: 1rem 0; + overflow-y: auto; +} + +.nav-section { + margin-bottom: 1.5rem; + + h4 { + margin: 0 0 0.5rem 0; + padding: 0 1rem; + font-size: 0.75rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.05em; + color: #666; + } +} + +.nav-button { + width: 100%; + justify-content: flex-start; + text-align: left; + padding: 0.75rem 1rem; + margin: 0.125rem 0; + border-radius: 0; + + &:hover { + background-color: #f8f9fa; + } + + &.k-button-solid { + background-color: #e3f2fd; + color: #1976d2; + + &:hover { + background-color: #bbdefb; + } + } +} diff --git a/APP/src/app/portals/user-portal/components/user-navbar/user-navbar.component.ts b/APP/src/app/portals/user-portal/components/user-navbar/user-navbar.component.ts new file mode 100644 index 0000000..dc92aab --- /dev/null +++ b/APP/src/app/portals/user-portal/components/user-navbar/user-navbar.component.ts @@ -0,0 +1,106 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { Router, NavigationEnd } from '@angular/router'; +import { LayoutModule } from '@progress/kendo-angular-layout'; +import { ButtonsModule } from '@progress/kendo-angular-buttons'; +import { IconsModule } from '@progress/kendo-angular-icons'; +import { SVGIcon, homeIcon, calendarIcon, userIcon } from '@progress/kendo-svg-icons'; +import { LayoutService } from '../../../../layout/services/layout.service'; +import { Subject, takeUntil, filter } from 'rxjs'; + +interface NavItem { + text: string; + icon: SVGIcon; + path: string; + active?: boolean; +} + +@Component({ + selector: 'app-user-navbar', + standalone: true, + imports: [ + CommonModule, + LayoutModule, + ButtonsModule, + IconsModule + ], + templateUrl: './user-navbar.component.html', + styleUrls: ['./user-navbar.component.scss'] +}) +export class UserNavbarComponent implements OnInit, OnDestroy { + public homeIcon: SVGIcon = homeIcon; + public calendarIcon: SVGIcon = calendarIcon; + public peopleIcon: SVGIcon = userIcon; // Using userIcon as fallback + public bedIcon: SVGIcon = userIcon; // Using userIcon as fallback + public userIcon: SVGIcon = userIcon; + public pillIcon: SVGIcon = userIcon; // Using userIcon as fallback + public chartIcon: SVGIcon = userIcon; // Using userIcon as fallback + public buildingIcon: SVGIcon = userIcon; // Using userIcon as fallback + public creditCardIcon: SVGIcon = userIcon; // Using userIcon as fallback + public supportIcon: SVGIcon = userIcon; // Using userIcon as fallback + + public mainNavItems: NavItem[] = [ + { text: 'Dashboard', icon: this.homeIcon, path: '/user-portal/dashboard' }, + { text: 'Schedule', icon: this.calendarIcon, path: '/user-portal/schedule' }, + { text: 'Patients', icon: this.peopleIcon, path: '/user-portal/patients' } + ]; + + public managementNavItems: NavItem[] = [ + { text: 'Bed Management', icon: this.bedIcon, path: '/user-portal/bed-management' }, + { text: 'Staff', icon: this.userIcon, path: '/user-portal/staff' }, + { text: 'Pharmacy', icon: this.pillIcon, path: '/user-portal/pharmacy' }, + { text: 'Reports', icon: this.chartIcon, path: '/user-portal/reports' }, + { text: 'Departments', icon: this.buildingIcon, path: '/user-portal/departments' }, + { text: 'Payments', icon: this.creditCardIcon, path: '/user-portal/payments' } + ]; + + public supportNavItems: NavItem[] = [ + { text: 'Support', icon: this.supportIcon, path: '/user-portal/support' } + ]; + + private destroy$ = new Subject(); + + constructor( + public layoutService: LayoutService, + private router: Router + ) { } + + ngOnInit(): void { + // Listen to route changes to update active states + this.router.events + .pipe( + filter(event => event instanceof NavigationEnd), + takeUntil(this.destroy$) + ) + .subscribe((event: NavigationEnd) => { + this.updateActiveStates(event.url); + }); + + // Set initial active state + this.updateActiveStates(this.router.url); + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + + public navigateTo(path: string): void { + this.router.navigate([path]); + this.layoutService.closeDrawer(); + } + + private updateActiveStates(currentUrl: string): void { + // Reset all active states + [...this.mainNavItems, ...this.managementNavItems, ...this.supportNavItems] + .forEach(item => item.active = false); + + // Set active state for current route + const activeItem = [...this.mainNavItems, ...this.managementNavItems, ...this.supportNavItems] + .find(item => currentUrl.startsWith(item.path)); + + if (activeItem) { + activeItem.active = true; + } + } +} diff --git a/APP/src/app/portals/user-portal/pages/dashboard/dashboard.component.html b/APP/src/app/portals/user-portal/pages/dashboard/dashboard.component.html new file mode 100644 index 0000000..a46cfe5 --- /dev/null +++ b/APP/src/app/portals/user-portal/pages/dashboard/dashboard.component.html @@ -0,0 +1,172 @@ +
+ +
+
+

Welcome back, {{ getDisplayName() || 'User' }}!

+

Here's a mock overview of the RBJ Identity escrow dashboard.

+
+
+ + +
+
+
+ + + + +
+
+
{{ activeTransactions }}
+
Active Transactions
+
+
+ +
+
+ + + + + + + +
+
+
{{ pendingTasks }}
+
Pending Tasks
+
+
+ +
+
+ + + + +
+
+
{{ completedTransactions }}
+
Completed
+
+
+ +
+
+ + + +
+
+
${{ totalValue | number:'1.0-0' }}
+
Total Value
+
+
+
+ + +
+
+

Recent Transactions

+
+ +
+ +
+
+
+
{{ transaction.title }}
+
+ {{ transaction.statusLabel }} +
+
+
+
${{ transaction.amount | number:'1.0-0' }}
+
{{ transaction.date | date:'MMM d, y' }}
+
+
+
+
+
+ {{ transaction.progress }}% Complete +
+
+
+ + +
+
+ + + + +
+

No Recent Transactions

+

You don't have any recent transactions yet.

+
+
+
+ + +
+
+

Quick Actions

+
+ +
+ + + + + + + +
+
+
\ No newline at end of file diff --git a/APP/src/app/portals/user-portal/pages/dashboard/dashboard.component.scss b/APP/src/app/portals/user-portal/pages/dashboard/dashboard.component.scss new file mode 100644 index 0000000..eb7af1d --- /dev/null +++ b/APP/src/app/portals/user-portal/pages/dashboard/dashboard.component.scss @@ -0,0 +1,536 @@ +.dashboard-container { + max-width: 1200px; + margin: 0 auto; +} + +// Welcome Section +.welcome-section { + background: linear-gradient(135deg, #1e3a8a 0%, #1e40af 50%, #3b82f6 100%); + border-radius: 16px; + padding: 2rem; + margin-bottom: 2rem; + color: white; + display: flex; + align-items: center; + justify-content: space-between; + position: relative; + overflow: hidden; + + &::before { + content: ""; + position: absolute; + top: 0; + right: 0; + width: 200px; + height: 200px; + background: rgba(255, 255, 255, 0.1); + border-radius: 50%; + transform: translate(50%, -50%); + } + + .welcome-content { + position: relative; + z-index: 1; + + h1 { + margin: 0 0 0.5rem 0; + font-size: 2rem; + font-weight: 700; + letter-spacing: -0.01em; + } + + p { + margin: 0; + font-size: 1.1rem; + opacity: 0.9; + } + } + + .welcome-actions { + position: relative; + z-index: 1; + } + + .action-btn { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.75rem 1.5rem; + background: rgba(255, 255, 255, 0.2); + border: 1px solid rgba(255, 255, 255, 0.3); + border-radius: 8px; + color: white; + text-decoration: none; + font-weight: 600; + transition: all 0.3s ease; + cursor: pointer; + + &:hover { + background: rgba(255, 255, 255, 0.3); + transform: translateY(-2px); + } + + svg { + width: 18px; + height: 18px; + } + } +} + +// Stats Grid +.stats-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 1.5rem; + margin-bottom: 2rem; +} + +.stat-card { + background: white; + border-radius: 12px; + padding: 1.5rem; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); + border: 1px solid #e5e7eb; + display: flex; + align-items: center; + gap: 1rem; + transition: all 0.3s ease; + + &:hover { + transform: translateY(-2px); + box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1); + } + + .stat-icon { + width: 48px; + height: 48px; + border-radius: 12px; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + + &.active { + background: rgba(34, 197, 94, 0.1); + color: #16a34a; + } + + &.pending { + background: rgba(251, 191, 36, 0.1); + color: #d97706; + } + + &.completed { + background: rgba(59, 130, 246, 0.1); + color: #2563eb; + } + + &.total { + background: rgba(168, 85, 247, 0.1); + color: #9333ea; + } + + svg { + width: 24px; + height: 24px; + } + } + + .stat-content { + flex: 1; + + .stat-value { + font-size: 1.75rem; + font-weight: 700; + color: #1f2937; + margin-bottom: 0.25rem; + } + + .stat-label { + font-size: 0.9rem; + color: #6b7280; + font-weight: 500; + } + } +} + +// Section Styles +.section { + margin-bottom: 2rem; + + .section-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 1.5rem; + + h2 { + margin: 0; + font-size: 1.5rem; + font-weight: 600; + color: #1f2937; + } + + .view-all-btn { + display: flex; + align-items: center; + gap: 0.5rem; + background: none; + border: none; + color: #1e40af; + font-weight: 500; + cursor: pointer; + transition: all 0.2s ease; + + &:hover { + color: #1e3a8a; + } + + svg { + width: 16px; + height: 16px; + } + } + } +} + +// Transactions List +.transactions-list { + display: grid; + gap: 1rem; +} + +// Loading State +.loading-container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 3rem 1rem; + text-align: center; + + .loading-spinner { + width: 40px; + height: 40px; + border: 3px solid #e5e7eb; + border-top: 3px solid #1e40af; + border-radius: 50%; + animation: spin 1s linear infinite; + margin-bottom: 1rem; + } + + .loading-text { + color: #6b7280; + font-size: 0.9rem; + margin: 0; + } +} + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + +// Empty State +.empty-state { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 3rem 1rem; + text-align: center; + + .empty-icon { + width: 64px; + height: 64px; + background: rgba(30, 64, 175, 0.1); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + color: #1e40af; + margin-bottom: 1.5rem; + + svg { + width: 32px; + height: 32px; + } + } + + h3 { + margin: 0 0 0.5rem 0; + font-size: 1.25rem; + font-weight: 600; + color: #1f2937; + } + + p { + margin: 0 0 1.5rem 0; + color: #6b7280; + font-size: 0.9rem; + } + + .action-btn { + display: inline-flex; + align-items: center; + gap: 0.5rem; + padding: 0.75rem 1.5rem; + background: #1e40af; + border: none; + border-radius: 8px; + color: white; + text-decoration: none; + font-weight: 600; + transition: all 0.3s ease; + cursor: pointer; + + &:hover { + background: #1e3a8a; + transform: translateY(-2px); + } + } +} + +.transaction-card { + background: white; + border-radius: 12px; + padding: 1.5rem; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); + border: 1px solid #e5e7eb; + cursor: pointer; + transition: all 0.3s ease; + margin-bottom: 10px; + &:hover { + transform: translateY(-2px); + box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1); + border-color: #1e40af; + } + + .transaction-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 1rem; + + .transaction-title { + font-size: 1.1rem; + font-weight: 600; + color: #1f2937; + } + + .transaction-status { + padding: 0.25rem 0.75rem; + border-radius: 20px; + font-size: 0.8rem; + font-weight: 600; + text-transform: uppercase; + + &.active { + background: rgba(34, 197, 94, 0.1); + color: #16a34a; + } + + &.pending { + background: rgba(251, 191, 36, 0.1); + color: #d97706; + } + + &.completed { + background: rgba(59, 130, 246, 0.1); + color: #2563eb; + } + } + } + + .transaction-details { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 1rem; + + .transaction-amount { + font-size: 1.25rem; + font-weight: 700; + color: #1f2937; + } + + .transaction-date { + font-size: 0.9rem; + color: #6b7280; + } + } + + .transaction-progress { + display: flex; + align-items: center; + gap: 1rem; + + .progress-bar { + flex: 1; + height: 6px; + background: #e5e7eb; + border-radius: 3px; + overflow: hidden; + + .progress-fill { + height: 100%; + background: linear-gradient(135deg, #1e3a8a 0%, #1e40af 50%, #3b82f6 100%); + border-radius: 3px; + transition: width 0.3s ease; + } + } + + .progress-text { + font-size: 0.8rem; + color: #6b7280; + font-weight: 500; + min-width: 80px; + } + } +} + +// Quick Actions +.quick-actions-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 1rem; +} + +.quick-action-btn { + background: white; + border: 1px solid #e5e7eb; + border-radius: 12px; + padding: 1.5rem; + cursor: pointer; + transition: all 0.3s ease; + display: flex; + align-items: center; + gap: 1rem; + text-align: left; + + &:hover { + transform: translateY(-2px); + box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1); + border-color: #1e40af; + } + + .action-icon { + width: 48px; + height: 48px; + background: rgba(30, 64, 175, 0.1); + border-radius: 12px; + display: flex; + align-items: center; + justify-content: center; + color: #1e40af; + flex-shrink: 0; + + svg { + width: 24px; + height: 24px; + } + } + + .action-content { + flex: 1; + + .action-title { + font-size: 1.1rem; + font-weight: 600; + color: #1f2937; + margin-bottom: 0.25rem; + } + + .action-description { + font-size: 0.9rem; + color: #6b7280; + } + } +} + +// Mobile Responsive +@media (max-width: 768px) { + .welcome-section { + flex-direction: column; + text-align: center; + gap: 1.5rem; + + .welcome-content h1 { + font-size: 1.5rem; + } + + .welcome-content p { + font-size: 1rem; + } + } + + .stats-grid { + grid-template-columns: 1fr; + } + + .quick-actions-grid { + grid-template-columns: 1fr; + } + + .transaction-card { + padding: 1rem; + + .transaction-header { + flex-direction: column; + align-items: flex-start; + gap: 0.5rem; + } + + .transaction-details { + flex-direction: column; + align-items: flex-start; + gap: 0.5rem; + } + } +} + +@media (max-width: 480px) { + .welcome-section { + padding: 1.5rem; + + .welcome-content h1 { + font-size: 1.25rem; + } + } + + .stat-card { + padding: 1rem; + + .stat-icon { + width: 40px; + height: 40px; + + svg { + width: 20px; + height: 20px; + } + } + + .stat-content .stat-value { + font-size: 1.5rem; + } + } + + .quick-action-btn { + padding: 1rem; + + .action-icon { + width: 40px; + height: 40px; + + svg { + width: 20px; + height: 20px; + } + } + } +} diff --git a/APP/src/app/portals/user-portal/pages/dashboard/dashboard.component.ts b/APP/src/app/portals/user-portal/pages/dashboard/dashboard.component.ts new file mode 100644 index 0000000..0fd3f88 --- /dev/null +++ b/APP/src/app/portals/user-portal/pages/dashboard/dashboard.component.ts @@ -0,0 +1,75 @@ +import { Component, OnInit } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { AuthService, User } from '../../../../shared/services/auth.service'; + +interface Transaction { + id: string; + title: string; + amount: number; + status: 'active' | 'pending' | 'completed'; + statusLabel: string; + date: Date; + progress: number; +} + +@Component({ + selector: 'app-dashboard', + standalone: true, + imports: [CommonModule], + templateUrl: './dashboard.component.html', + styleUrls: ['./dashboard.component.scss'] +}) +export class DashboardComponent implements OnInit { + currentUser: User | null = null; + + activeTransactions = 5; + pendingTasks = 12; + completedTransactions = 23; + totalValue = 1250000; + + recentTransactions: Transaction[] = [ + { + id: 'RBJ-1001', + title: 'Maple Street Purchase Escrow', + amount: 425000, + status: 'active', + statusLabel: 'Open', + date: new Date('2026-04-24'), + progress: 68 + }, + { + id: 'RBJ-1002', + title: 'Oak Ridge Refinance', + amount: 310000, + status: 'pending', + statusLabel: 'Review', + date: new Date('2026-04-20'), + progress: 42 + }, + { + id: 'RBJ-1003', + title: 'Cedar Lane Closing', + amount: 515000, + status: 'completed', + statusLabel: 'Closed', + date: new Date('2026-04-12'), + progress: 100 + } + ]; + + constructor(private authService: AuthService) { } + + ngOnInit(): void { + this.authService.currentUser$.subscribe(user => { + this.currentUser = user; + }); + } + + getDisplayName(): string { + if (this.currentUser) { + const fullName = `${this.currentUser.firstName} ${this.currentUser.lastName}`.trim(); + return fullName || this.currentUser.email; + } + return ''; + } +} diff --git a/APP/src/app/portals/user-portal/user-portal.component.html b/APP/src/app/portals/user-portal/user-portal.component.html new file mode 100644 index 0000000..fb11bae --- /dev/null +++ b/APP/src/app/portals/user-portal/user-portal.component.html @@ -0,0 +1,122 @@ +
+ +
+
+
+
+
+ + +
+ + + + + + + +
+ +
+
+ + +
+ +
+
+ + + +
+
+
+ + +
+ +
+
+
+
\ No newline at end of file diff --git a/APP/src/app/portals/user-portal/user-portal.component.scss b/APP/src/app/portals/user-portal/user-portal.component.scss new file mode 100644 index 0000000..67a59f3 --- /dev/null +++ b/APP/src/app/portals/user-portal/user-portal.component.scss @@ -0,0 +1,554 @@ +.user-portal-container { + min-height: 100vh; + background: linear-gradient(135deg, #1e3a8a 0%, #1e40af 50%, #3b82f6 100%); + position: relative; + overflow: hidden; +} + +// Background Shapes +.background-shapes { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + overflow: hidden; + z-index: 0; +} + +.shape { + position: absolute; + border-radius: 50%; + background: rgba(255, 255, 255, 0.1); + animation: float 6s ease-in-out infinite; + + &.shape-1 { + width: 200px; + height: 200px; + top: 10%; + left: 10%; + animation-delay: 0s; + } + + &.shape-2 { + width: 150px; + height: 150px; + top: 60%; + right: 15%; + animation-delay: 2s; + } + + &.shape-3 { + width: 100px; + height: 100px; + bottom: 20%; + left: 20%; + animation-delay: 4s; + } +} + +@keyframes float { + 0%, + 100% { + transform: translateY(0px) rotate(0deg); + } + 50% { + transform: translateY(-20px) rotate(180deg); + } +} + +// Main Portal Layout +.portal-layout { + display: flex; + min-height: 100vh; + position: relative; + z-index: 1; +} + +// Desktop layout - fixed sidebar with scrolling content +@media (min-width: 769px) { + .portal-layout { + .sidebar { + position: fixed; + top: 0; + left: 0; + z-index: 1000; + } + + .main-content { + margin-left: 290px; // Account for fixed sidebar width + flex: 1; + } + } + + // When sidebar is collapsed on desktop + .sidebar.collapsed + .main-content { + margin-left: 70px; + } +} + +// Sidebar Overlay for Mobile +.sidebar-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.5); + z-index: 999; +} + +// Sidebar Styles +.sidebar { + width: 280px; + height: 100vh; + background: rgba(255, 255, 255, 0.95); + -webkit-backdrop-filter: blur(10px); + backdrop-filter: blur(10px); + border-right: 1px solid rgba(255, 255, 255, 0.2); + display: flex; + flex-direction: column; + transition: all 0.3s ease; + box-shadow: 2px 0 20px rgba(0, 0, 0, 0.1); + flex-shrink: 0; + + &.collapsed { + width: 70px; + + .logo-text, + .nav-section h4, + .nav-item span, + .user-details, + .logout-btn span { + opacity: 0; + visibility: hidden; + } + + .sidebar-footer { + padding: 1rem 0.5rem; + } + + .user-info { + justify-content: center; + } + } +} + +.sidebar-header { + padding: 1.5rem; + border-bottom: 1px solid rgba(0, 0, 0, 0.1); + display: flex; + align-items: center; + justify-content: space-between; +} + +.logo-section { + display: flex; + align-items: center; + gap: 0.75rem; + + .logo-image { + height: 40px; + width: auto; + //filter: brightness(0) saturate(100%) invert(27%) sepia(51%) saturate(2878%) hue-rotate(346deg) brightness(104%)contrast(97%); + } + + .logo-text { + h2 { + margin: 0; + font-size: 1.25rem; + font-weight: 700; + color: #1e3a8a; + letter-spacing: -0.01em; + } + + .tagline { + font-size: 0.75rem; + color: #6b7280; + font-weight: 500; + } + } +} + +.sidebar-toggle { + background: none; + border: none; + padding: 0.5rem; + border-radius: 8px; + cursor: pointer; + color: #6b7280; + transition: all 0.2s ease; + display: flex; + align-items: center; + justify-content: center; + + &:hover { + background: #f3f4f6; + color: #1e40af; + } + + svg { + width: 20px; + height: 20px; + } +} + +.sidebar-nav { + flex: 1; + padding: 1rem 0; + overflow-y: auto; + max-height: calc(100vh - 200px); // Account for header and footer +} + +.nav-section { + margin-bottom: 2rem; + + h4 { + margin: 0 0 1rem 1.5rem; + font-size: 0.75rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.05em; + color: #6b7280; + } +} + +.nav-item { + display: flex; + align-items: center; + gap: 0.75rem; + padding: 0.75rem 1.5rem; + color: #6b7280; + text-decoration: none; + transition: all 0.2s ease; + position: relative; + margin: 0.125rem 0; + + &:hover { + background: rgba(30, 64, 175, 0.1); + color: #1e40af; + } + + &.active { + background: rgba(30, 64, 175, 0.15); + color: #1e40af; + font-weight: 600; + + &::before { + content: ""; + position: absolute; + left: 0; + top: 0; + bottom: 0; + width: 3px; + background: #1e40af; + } + } + + .nav-icon { + width: 20px; + height: 20px; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + + svg { + width: 100%; + height: 100%; + } + } + + span { + font-size: 0.9rem; + font-weight: 500; + } + + .nav-badge { + background: #ef4444; + color: white; + font-size: 0.75rem; + font-weight: 600; + padding: 0.25rem 0.5rem; + border-radius: 10px; + margin-left: auto; + min-width: 20px; + text-align: center; + } +} + +.sidebar-footer { + padding: 1.5rem; + border-top: 1px solid rgba(0, 0, 0, 0.1); +} + +.user-info { + display: flex; + align-items: center; + gap: 0.75rem; + margin-bottom: 1rem; + + .user-avatar { + width: 40px; + height: 40px; + background: linear-gradient(135deg, #1e3a8a 0%, #1e40af 50%, #3b82f6 100%); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + color: white; + flex-shrink: 0; + + svg { + width: 20px; + height: 20px; + } + } + + .user-details { + flex: 1; + min-width: 0; + + .user-name { + font-weight: 600; + color: #1f2937; + font-size: 0.9rem; + margin-bottom: 0.125rem; + } + + .user-email { + font-size: 0.8rem; + color: #6b7280; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + } +} + +.logout-btn { + width: 100%; + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.75rem; + background: none; + border: 1px solid #e5e7eb; + border-radius: 8px; + color: #6b7280; + cursor: pointer; + transition: all 0.2s ease; + font-size: 0.9rem; + font-weight: 500; + + &:hover { + background: #fef2f2; + border-color: #fecaca; + color: #dc2626; + } + + svg { + width: 16px; + height: 16px; + } +} + +// Main Content Area +.main-content { + flex: 1; + display: flex; + flex-direction: column; + background: rgba(255, 255, 255, 0.95); + -webkit-backdrop-filter: blur(10px); + backdrop-filter: blur(10px); + margin: 1rem; + border-radius: 16px; + box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1); + overflow: hidden; + height: calc(100vh - 2rem); + max-height: calc(100vh - 2rem); +} + +.top-header { + padding: 1.5rem 2rem; + border-bottom: 1px solid rgba(0, 0, 0, 0.1); + display: flex; + align-items: center; + justify-content: space-between; + background: rgba(255, 255, 255, 0.8); +} + +.header-left { + display: flex; + align-items: center; + gap: 1rem; +} + +.mobile-menu-btn { + display: none; + background: none; + border: none; + padding: 0.5rem; + border-radius: 8px; + cursor: pointer; + color: #6b7280; + transition: all 0.2s ease; + + &:hover { + background: #f3f4f6; + color: #1e40af; + } + + svg { + width: 20px; + height: 20px; + } +} + +.breadcrumb { + .breadcrumb-item { + font-size: 1.25rem; + font-weight: 600; + color: #1f2937; + } +} + +.header-right { + display: flex; + align-items: center; + gap: 1rem; +} + +.header-actions { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.action-btn { + position: relative; + background: none; + border: none; + padding: 0.75rem; + border-radius: 8px; + cursor: pointer; + color: #6b7280; + transition: all 0.2s ease; + display: flex; + align-items: center; + justify-content: center; + + &:hover { + background: #f3f4f6; + color: #1e40af; + } + + svg { + width: 20px; + height: 20px; + } + + .notification-badge { + position: absolute; + top: 0.25rem; + right: 0.25rem; + background: #ef4444; + color: white; + font-size: 0.75rem; + font-weight: 600; + padding: 0.125rem 0.375rem; + border-radius: 10px; + min-width: 18px; + text-align: center; + } +} + +.page-content { + flex: 1; + padding: 2rem; + overflow-y: auto; + height: 100%; + max-height: 100%; +} + +// Mobile Responsive +@media (max-width: 768px) { + .portal-layout { + flex-direction: column; + } + + .sidebar { + position: fixed; + top: 0; + left: 0; + z-index: 1000; + transform: translateX(-100%); + transition: transform 0.3s ease; + + &:not(.collapsed) { + transform: translateX(0); + } + + &.collapsed { + transform: translateX(-100%); + } + } + + .main-content { + margin: 0; + border-radius: 0; + height: 100vh; + max-height: 100vh; + } + + .mobile-menu-btn { + display: block; + } + + .page-content { + padding: 1rem; + } + + .top-header { + padding: 1rem; + } +} + +@media (max-width: 480px) { + .page-content { + padding: 0.75rem; + } + + .top-header { + padding: 0.75rem; + } + + .breadcrumb .breadcrumb-item { + font-size: 1.1rem; + } +} + +// Overlay for mobile sidebar +@media (max-width: 768px) { + .sidebar:not(.collapsed)::before { + content: ""; + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.5); + z-index: -1; + } +} + +// Desktop sidebar collapsed state +@media (min-width: 769px) { + .sidebar.collapsed { + width: 70px; + } + + .main-content.sidebar-collapsed { + margin-left: 70px; + } +} diff --git a/APP/src/app/portals/user-portal/user-portal.component.ts b/APP/src/app/portals/user-portal/user-portal.component.ts new file mode 100644 index 0000000..1530697 --- /dev/null +++ b/APP/src/app/portals/user-portal/user-portal.component.ts @@ -0,0 +1,139 @@ +import { Component, OnInit, OnDestroy, HostListener } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { Router, NavigationEnd, RouterModule, RouterLink, RouterLinkActive } from '@angular/router'; +import { RouterOutlet } from '@angular/router'; +import { AuthService, User } from '../../shared/services/auth.service'; +import { Subject, takeUntil, filter } from 'rxjs'; + +@Component({ + selector: 'app-user-portal', + standalone: true, + imports: [ + CommonModule, + RouterModule, + RouterLink, + RouterLinkActive, + RouterOutlet + ], + templateUrl: './user-portal.component.html', + styleUrls: ['./user-portal.component.scss'] +}) +export class UserPortalComponent implements OnInit, OnDestroy { + sidebarCollapsed = false; + isMobile = false; + currentUser: User | null = null; + currentPageTitle = 'Dashboard'; + unreadMessages = 3; + unreadNotifications = 2; + + private destroy$ = new Subject(); + + constructor( + private authService: AuthService, + private router: Router + ) { } + + ngOnInit(): void { + this.checkScreenSize(); + this.setupUserSubscription(); + this.setupRouteSubscription(); + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + + @HostListener('window:resize', ['$event']) + onResize(event: any): void { + this.checkScreenSize(); + } + + private checkScreenSize(): void { + this.isMobile = window.innerWidth <= 768; + if (this.isMobile) { + this.sidebarCollapsed = true; + } else { + // On desktop, start with sidebar expanded + this.sidebarCollapsed = false; + } + } + + private setupUserSubscription(): void { + this.authService.currentUser$ + .pipe(takeUntil(this.destroy$)) + .subscribe(user => { + this.currentUser = user; + }); + } + + private setupRouteSubscription(): void { + this.router.events + .pipe( + filter(event => event instanceof NavigationEnd), + takeUntil(this.destroy$) + ) + .subscribe(() => { + this.updatePageTitle(); + }); + } + + private updatePageTitle(): void { + const url = this.router.url; + const segments = url.split('/').filter(segment => segment); + + if (segments.length >= 2) { + const page = segments[1]; + this.currentPageTitle = this.getPageTitle(page); + } else { + this.currentPageTitle = 'Dashboard'; + } + } + + private getPageTitle(page: string): string { + const titles: { [key: string]: string } = { + 'dashboard': 'Dashboard', + 'transactions': 'Escrow Transactions', + 'tasks': 'Tasks & Todos', + 'contacts': 'Contacts', + 'documents': 'Documents', + 'messages': 'Messages', + 'settings': 'Settings' + }; + + return titles[page] || 'Dashboard'; + } + + toggleSidebar(): void { + this.sidebarCollapsed = !this.sidebarCollapsed; + } + + get mainContentClass(): string { + return this.sidebarCollapsed ? 'main-content sidebar-collapsed' : 'main-content'; + } + + onSidebarOverlayClick(): void { + if (this.isMobile && !this.sidebarCollapsed) { + this.sidebarCollapsed = true; + } + } + + onNavigationClick(): void { + if (this.isMobile) { + this.sidebarCollapsed = true; + } + } + + logout(): void { + this.authService.logout(); + this.router.navigate(['/login']); + } + + getDisplayName(): string { + if (this.currentUser) { + const fullName = `${this.currentUser.firstName} ${this.currentUser.lastName}`.trim(); + return fullName || this.currentUser.email; + } + return ''; + } +} \ No newline at end of file diff --git a/APP/src/app/shared/mfa-dialog/mfa-dialog.component.html b/APP/src/app/shared/mfa-dialog/mfa-dialog.component.html new file mode 100644 index 0000000..00f79fb --- /dev/null +++ b/APP/src/app/shared/mfa-dialog/mfa-dialog.component.html @@ -0,0 +1,88 @@ +
+
+ +
+
+
+
+ + +
+ +
+ + +
+
+ + + + + +
+

Two-Factor Authentication

+

Enter the 6-digit code sent to your device

+
+
+ + +
+
+ + + +
+ + +
+ + + + + + Invalid code. Please try again. +
+
+ + +
+ + + +
+ + +
+

Didn't receive the code? Check your spam folder or try resending.

+
+
+
+
\ No newline at end of file diff --git a/APP/src/app/shared/mfa-dialog/mfa-dialog.component.scss b/APP/src/app/shared/mfa-dialog/mfa-dialog.component.scss new file mode 100644 index 0000000..5fe4843 --- /dev/null +++ b/APP/src/app/shared/mfa-dialog/mfa-dialog.component.scss @@ -0,0 +1,418 @@ +.mfa-dialog-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.6); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; + padding: 1rem; + -webkit-backdrop-filter: blur(8px); + backdrop-filter: blur(8px); +} + +.mfa-dialog-container { + position: relative; + width: 100%; + max-width: 450px; + background: rgba(255, 255, 255, 0.98); + border-radius: 20px; + box-shadow: 0 25px 50px rgba(0, 0, 0, 0.25); + -webkit-backdrop-filter: blur(20px); + backdrop-filter: blur(20px); + overflow: hidden; + animation: dialogSlideIn 0.3s ease-out; +} + +@keyframes dialogSlideIn { + from { + opacity: 0; + transform: translateY(-20px) scale(0.95); + } + to { + opacity: 1; + transform: translateY(0) scale(1); + } +} + +// Background Shapes +.dialog-background-shapes { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + overflow: hidden; + z-index: 0; +} + +.shape { + position: absolute; + border-radius: 50%; + background: rgba(30, 64, 175, 0.05); + animation: float 8s ease-in-out infinite; + + &.shape-1 { + width: 80px; + height: 80px; + top: 15%; + right: 10%; + animation-delay: 0s; + } + + &.shape-2 { + width: 60px; + height: 60px; + bottom: 20%; + left: 15%; + animation-delay: 3s; + } +} + +@keyframes float { + 0%, + 100% { + transform: translateY(0px) rotate(0deg); + } + 50% { + transform: translateY(-15px) rotate(180deg); + } +} + +// Dialog Content +.mfa-dialog-content { + position: relative; + z-index: 1; + padding: 2rem; +} + +// Header +.mfa-header { + position: relative; + text-align: center; + margin-bottom: 2rem; + + .close-button { + position: absolute; + top: -0.5rem; + right: -0.5rem; + background: #f8f9fa; + border: none; + border-radius: 50%; + width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: all 0.2s ease; + color: #6b7280; + + &:hover { + background: #e5e7eb; + color: #374151; + transform: scale(1.1); + } + + svg { + width: 16px; + height: 16px; + } + } + + .header-content { + .mfa-icon { + width: 60px; + height: 60px; + margin: 0 auto 1rem; + background: linear-gradient(135deg, #1e3a8a 0%, #1e40af 50%, #3b82f6 100%); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + color: white; + box-shadow: 0 8px 25px rgba(30, 58, 138, 0.3); + + svg { + width: 28px; + height: 28px; + } + } + + h3 { + font-size: 1.75rem; + font-weight: 700; + color: #1a1a1a; + margin: 0 0 0.5rem 0; + letter-spacing: -0.01em; + } + + p { + color: #6b7280; + font-size: 1rem; + margin: 0; + line-height: 1.5; + } + } +} + +// MFA Code Section +.mfa-code-section { + margin-bottom: 2rem; + + .code-inputs-container { + display: flex; + gap: 0.75rem; + justify-content: center; + margin-bottom: 1rem; + + .mfa-input { + width: 50px; + height: 50px; + border: 2px solid #e5e7eb; + border-radius: 12px; + text-align: center; + font-size: 1.5rem; + font-weight: 600; + color: #1a1a1a; + background: #ffffff; + transition: all 0.2s ease; + outline: none; + + &:focus { + border-color: #1e40af; + box-shadow: 0 0 0 3px rgba(30, 64, 175, 0.1); + transform: scale(1.05); + } + + &:hover:not(:focus) { + border-color: #d1d5db; + } + + &::-webkit-outer-spin-button, + &::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; + } + + &[type="number"] { + -moz-appearance: textfield; + } + } + } + + .error-message { + display: flex; + align-items: center; + justify-content: center; + gap: 0.5rem; + background: #fee2e2; + color: #dc2626; + padding: 0.75rem 1rem; + border-radius: 12px; + border: 1px solid #fecaca; + font-size: 0.9rem; + font-weight: 500; + animation: errorShake 0.5s ease-in-out; + + .error-icon { + width: 16px; + height: 16px; + flex-shrink: 0; + } + } +} + +@keyframes errorShake { + 0%, + 100% { + transform: translateX(0); + } + 25% { + transform: translateX(-5px); + } + 75% { + transform: translateX(5px); + } +} + +// Actions +.mfa-actions { + display: flex; + gap: 1rem; + margin-bottom: 1.5rem; + + .resend-button { + flex: 1; + height: 48px; + border-radius: 12px; + font-size: 0.95rem; + font-weight: 600; + background: #f8f9fa; + border: 2px solid #e5e7eb; + color: #6b7280; + transition: all 0.3s ease; + + &:hover:not(:disabled) { + background: #e9ecef; + border-color: #d1d5db; + color: #495057; + transform: translateY(-1px); + } + + &:disabled { + opacity: 0.5; + cursor: not-allowed; + transform: none; + } + + .button-content { + display: flex; + align-items: center; + justify-content: center; + gap: 0.5rem; + } + + .button-icon { + width: 16px; + height: 16px; + } + } + + .verify-button { + flex: 2; + height: 48px; + border-radius: 12px; + font-size: 1rem; + font-weight: 600; + background: linear-gradient(135deg, #1e3a8a 0%, #1e40af 50%, #3b82f6 100%); + border: none; + color: white; + box-shadow: 0 8px 25px rgba(30, 58, 138, 0.3); + transition: all 0.3s ease; + + &:hover:not(:disabled) { + transform: translateY(-2px); + box-shadow: 0 12px 35px rgba(30, 58, 138, 0.4); + } + + &:disabled { + opacity: 0.6; + cursor: not-allowed; + transform: none; + } + + .button-content { + display: flex; + align-items: center; + justify-content: center; + gap: 0.5rem; + } + + .button-icon { + width: 18px; + height: 18px; + } + } +} + +// Help Text +.help-text { + text-align: center; + + p { + color: #9ca3af; + font-size: 0.85rem; + margin: 0; + line-height: 1.4; + } +} + +// Mobile Responsive +@media (max-width: 480px) { + .mfa-dialog-overlay { + padding: 0.5rem; + } + + .mfa-dialog-container { + max-width: 100%; + border-radius: 16px; + } + + .mfa-dialog-content { + padding: 1.5rem; + } + + .mfa-header { + margin-bottom: 1.5rem; + + .header-content { + .mfa-icon { + width: 50px; + height: 50px; + margin-bottom: 0.75rem; + + svg { + width: 24px; + height: 24px; + } + } + + h3 { + font-size: 1.5rem; + } + + p { + font-size: 0.9rem; + } + } + } + + .mfa-code-section { + margin-bottom: 1.5rem; + + .code-inputs-container { + gap: 0.5rem; + + .mfa-input { + width: 45px; + height: 45px; + font-size: 1.25rem; + } + } + } + + .mfa-actions { + flex-direction: column; + gap: 0.75rem; + + .resend-button, + .verify-button { + flex: 1; + height: 44px; + } + + .verify-button { + order: -1; + } + } +} + +@media (max-width: 360px) { + .mfa-dialog-content { + padding: 1rem; + } + + .code-inputs-container { + gap: 0.4rem; + + .mfa-input { + width: 40px; + height: 40px; + font-size: 1.1rem; + } + } +} diff --git a/APP/src/app/shared/mfa-dialog/mfa-dialog.component.ts b/APP/src/app/shared/mfa-dialog/mfa-dialog.component.ts new file mode 100644 index 0000000..7905336 --- /dev/null +++ b/APP/src/app/shared/mfa-dialog/mfa-dialog.component.ts @@ -0,0 +1,250 @@ +import { Component, ElementRef, QueryList, ViewChildren, Output, EventEmitter, Input } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { ButtonsModule } from '@progress/kendo-angular-buttons'; +import { IndicatorsModule } from '@progress/kendo-angular-indicators'; +import { AuthService, LoginCredentials, LoginResultType } from '../services/auth.service'; +import { take } from 'rxjs/operators'; + +const CODE_LENGTH = 6; + +@Component({ + selector: 'app-mfa-dialog', + standalone: true, + imports: [ + CommonModule, + FormsModule, + ButtonsModule, + IndicatorsModule + ], + templateUrl: './mfa-dialog.component.html', + styleUrls: ['./mfa-dialog.component.scss'] +}) +export class MfaDialogComponent { + @ViewChildren('codeInput') codeInputs!: QueryList; + @Output() mfaSuccess = new EventEmitter(); + @Output() mfaCancel = new EventEmitter(); + @Input() visible = false; + + token: string = ''; + userInputCodes: (string | null)[] = []; + userInputCodes2: (string | null)[] = []; + loginData!: LoginCredentials; + processing = false; + allowSubmit = false; + isInvalidCode = false; + resendCountDown = 30; + + constructor(private authService: AuthService) { } + + ngOnInit() { + for (let i = 0; i < CODE_LENGTH; i++) { + this.userInputCodes.push(null); + this.userInputCodes2.push(null); + } + this.setReSendCountDown(); + } + + pasteCode(index: number, event: ClipboardEvent) { + event.preventDefault(); + const data = event.clipboardData?.getData('text/plain') || ''; + let pasteCode = data.replace(new RegExp("[^0-9]", 'g'), ""); + for (let i = index; i < CODE_LENGTH; i++) { + if (pasteCode.length > i) { + const code = pasteCode[i]; + let input = this.codeInputs.find((element, j) => j === i); + if (input) { + input.nativeElement.value = code; + this.userInputCodes[i] = code; + } + } + } + this.validate(5); + } + + public onKeyDown(index: number, e: KeyboardEvent): void { + const el: HTMLInputElement = e.target as HTMLInputElement; + if (e.ctrlKey && e.key.toUpperCase() == 'V') { + return; + } else { + let nextFocusInput: ElementRef | undefined = undefined; + switch (e.key) { + case 'ArrowLeft': + nextFocusInput = this.getInputElements(index, -1); + if (nextFocusInput) nextFocusInput.nativeElement.focus(); + break; + case 'ArrowRight': + nextFocusInput = this.getInputElements(index, 1); + if (nextFocusInput) nextFocusInput.nativeElement.focus(); + break; + case 'Backspace': + if (el.value) { + el.value = ''; + } else { + nextFocusInput = this.getInputElements(index, -1); + } + break; + case 'Delete': + el.value = ''; + break; + default: + break; + } + + if (nextFocusInput) { + nextFocusInput.nativeElement.focus(); + return; + } + + let isReplacing = el.selectionStart != el.selectionEnd; + + if (this.isEditingKeyPress(e)) { + e.preventDefault(); + if (new RegExp("[0-9]", 'g').test(e.key)) { + this.userInputCodes[index] = e.key; + el.value = e.key; + let nextInput = this.getInputElements(index, 1); + if (nextInput) nextInput.nativeElement.focus(); + } else { + el.value = ''; + this.userInputCodes[index] = null; + } + } + } + + this.isInvalidCode = false; + this.validate(index); + } + + private getInputElements(index: number, indexOffset: number): ElementRef | undefined { + let nextInput = this.codeInputs.find((element, i) => i === (index + indexOffset)); + return nextInput; + } + + private isEditingKeyPress(e: KeyboardEvent): boolean { + return e.key.length === 1 || e.key === 'Backspace' || e.key === 'Delete' || e.key === 'ArrowLeft' || e.key === 'ArrowRight'; + } + + validate(focusIndex: number) { + this.allowSubmit = !this.userInputCodes.some(n => n == null); + this.token = this.userInputCodes.map(n => n != null ? n : '').join(''); + if (this.token && this.token.length == 6 && focusIndex == 5) { + this.submitCode(); + } + } + + close() { + this.visible = false; + this.mfaCancel.emit(); + } + + show() { + this.visible = true; + this.resetForm(); + } + + hide() { + this.visible = false; + } + + resetForm() { + this.userInputCodes = []; + this.userInputCodes2 = []; + this.token = ''; + this.isInvalidCode = false; + this.processing = false; + this.allowSubmit = false; + + for (let i = 0; i < CODE_LENGTH; i++) { + this.userInputCodes.push(null); + this.userInputCodes2.push(null); + } + + // Focus on first input + setTimeout(() => { + const firstInput = document.querySelector('.mfa-input:first-child') as HTMLInputElement; + if (firstInput) { + firstInput.focus(); + } + }, 100); + } + + submitCode() { + this.processing = true; + this.loginData.mfaCode = this.token; + + // Check if this is token-based authentication + if ((this.loginData as any).tokenUser) { + // Handle token-based MFA verification + this.authService.verifyMfaForToken(this.token, (this.loginData as any).tokenUser).subscribe({ + next: (result) => { + this.processing = false; + + if (result.result === LoginResultType.Success) { + this.mfaSuccess.emit(result.responseData); + this.visible = false; + } else { + this.isInvalidCode = true; + } + }, + error: (error) => { + this.processing = false; + this.isInvalidCode = true; + console.error('MFA verification error:', error); + } + }); + } else { + // Handle regular login MFA verification + this.authService.login(this.loginData).subscribe({ + next: (result) => { + this.processing = false; + + if (result.result === LoginResultType.Success) { + this.mfaSuccess.emit(result.responseData); + this.visible = false; + } else { + this.isInvalidCode = true; + } + }, + error: (error) => { + this.processing = false; + this.isInvalidCode = true; + console.error('MFA verification error:', error); + } + }); + } + } + + setReSendCountDown() { + if (this.resendCountDown > 0) { + setTimeout(() => { + this.resendCountDown--; + this.setReSendCountDown(); + }, 1000); + } + } + + resendMFCode() { + this.resendCountDown = 30; + this.loginData.mfaCode = ''; + + // Simulate resend MFA code - replace with actual service call + console.log('Resending MFA code to:', this.loginData.email); + // Check if this is token-based authentication + if ((this.loginData as any).tokenUser) { + // Handle token-based MFA verification + this.authService.verifyMfaForToken(this.token, (this.loginData as any).tokenUser).pipe( + take(1) + ).subscribe(result => { + this.setReSendCountDown(); + }); + } else { + //TODO: Implement resend MFA code for regular login + } + this.setReSendCountDown(); + } + + public get resendCodeText(): string { + return 'Resend Code' + (this.resendCountDown > 0 ? ` (${this.resendCountDown})` : ''); + } +} diff --git a/APP/src/app/shared/models/index.ts b/APP/src/app/shared/models/index.ts new file mode 100644 index 0000000..a212b7c --- /dev/null +++ b/APP/src/app/shared/models/index.ts @@ -0,0 +1,2 @@ +// Export user models +export * from './user.model'; \ No newline at end of file diff --git a/APP/src/app/shared/models/user.model.ts b/APP/src/app/shared/models/user.model.ts new file mode 100644 index 0000000..e69de29 diff --git a/APP/src/app/shared/services/auth.service.ts b/APP/src/app/shared/services/auth.service.ts new file mode 100644 index 0000000..f9924f7 --- /dev/null +++ b/APP/src/app/shared/services/auth.service.ts @@ -0,0 +1,400 @@ +import { Injectable } from '@angular/core'; +import { HttpClient, HttpHeaders } from '@angular/common/http'; +import { BehaviorSubject, Observable, of } from 'rxjs'; +import { map, catchError } from 'rxjs/operators'; +import { ApiConfigService } from '../../core/services/api-config.service'; + +export interface User { + id: string; + username: string; + email: string; + firstName: string; + lastName: string; + createdAt: string; + branchIds: string[]; + token: string; + mfaVerified?: boolean; +} + +export interface LoginCredentials { + email: string; + password: string; + mfaCode?: string; +} + +export interface LoginResponse { + isAuthenticated: boolean; + requiresMfa: boolean; + token?: string; + expires?: string; + user?: UserDto; + message?: string; +} + +export interface TokenCreateResponse { + token?: string; + mfaToken?: string; + access?: AccessDto[]; + isAuthenticated?: boolean; + isAuthorized?: boolean; + isChangePassword?: boolean; + message?: string; + username?: string; + email?: string; + concurrentTabs?: number; + mfaType?: number; + mfaHint?: string; + Token?: string; + MfaToken?: string; + Access?: AccessDto[]; + IsAuthenticated?: boolean; + IsAuthorized?: boolean; + IsChangePassword?: boolean; + Message?: string; + Username?: string; + Email?: string; + ConcurrentTabs?: number; + MfaType?: number; + MfaHint?: string; +} + +export interface AccessDto { + branchName?: string; + redxBranch?: string; + BranchName?: string; + RedxBranch?: string; +} + +export interface UserDto { + id: string; + username: string; + email: string; + firstName: string; + lastName: string; + createdAt: string; + branchIds: string[]; +} + +export enum LoginResultType { + Success = 'Success', + MfaRequired = 'MfaRequired', + InvalidCredentials = 'InvalidCredentials', + Error = 'Error' +} + +export interface LoginResult { + result: LoginResultType; + responseData?: User; + message?: string; +} + +export interface TokenVerificationResult { + isValid: boolean; + user?: User; + message?: string; + expiresAt?: Date; + requiresMfa?: boolean; +} + +@Injectable({ + providedIn: 'root' +}) +export class AuthService { + private currentUserSubject = new BehaviorSubject(null); + public currentUser$ = this.currentUserSubject.asObservable(); + private redirectUrl: string = '/dashboard'; + constructor( + private http: HttpClient, + private apiConfig: ApiConfigService + ) { } + + login(credentials: LoginCredentials): Observable { + return this.authenticateUser(credentials); + } + + private authenticateUser(credentials: LoginCredentials): Observable { + const loginUrl = `${this.apiConfig.tokenUrl}/Create`; + return this.http.get(loginUrl, { + headers: this.buildTokenCreateHeaders(credentials) + }).pipe( + map((response: TokenCreateResponse) => this.mapTokenCreateResponse(response, credentials)), + catchError((error) => { + console.error('Login error:', error); + return of({ + result: LoginResultType.Error, + message: error.error?.message || 'An error occurred during login' + }); + }) + ); + } + + private buildTokenCreateHeaders(credentials: LoginCredentials): HttpHeaders { + let headers = new HttpHeaders({ + Authorization: `Basic ${btoa(`${credentials.email}:${credentials.password}`)}` + }); + + if (credentials.mfaCode) { + headers = headers.set('mfaCode', credentials.mfaCode); + } + + return headers; + } + + private mapTokenCreateResponse(response: TokenCreateResponse, credentials: LoginCredentials): LoginResult { + const token = response.token || response.Token || ''; + const message = response.message || response.Message || ''; + const isAuthenticated = response.isAuthenticated ?? response.IsAuthenticated ?? false; + const isAuthorized = response.isAuthorized ?? response.IsAuthorized ?? false; + + if (isAuthenticated && isAuthorized && token) { + return { + result: LoginResultType.Success, + responseData: this.mapUserFromTokenCreateResponse(response, credentials, token) + }; + } + + if (isAuthenticated && !token && this.isMfaRequired(message)) { + return { + result: LoginResultType.MfaRequired, + responseData: this.mapUserFromTokenCreateResponse( + response, + credentials, + response.mfaToken || response.MfaToken || '' + ), + message + }; + } + + return { + result: isAuthenticated ? LoginResultType.Error : LoginResultType.InvalidCredentials, + message: message || 'Invalid email or password' + }; + } + + private mapUserFromTokenCreateResponse( + response: TokenCreateResponse, + credentials: LoginCredentials, + token: string + ): User { + const username = response.username || response.Username || credentials.email; + const email = response.email || response.Email || credentials.email; + const access = response.access || response.Access || []; + + return { + id: username || email, + username, + email, + firstName: '', + lastName: '', + createdAt: new Date().toISOString(), + branchIds: access + .map(item => item.redxBranch || item.RedxBranch || item.branchName || item.BranchName || '') + .filter(branchId => !!branchId), + token, + mfaVerified: !!credentials.mfaCode + }; + } + + private isMfaRequired(message: string): boolean { + return message.toLowerCase().includes('mfa verification required'); + } + + logout(): void { + this.currentUserSubject.next(null); + this.redirectUrl = '/dashboard'; + // Clear any stored tokens, etc. + localStorage.removeItem('currentUser'); + } + + getCurrentUser(): User | null { + return this.currentUserSubject.value; + } + + getToken(): string | null { + const user = this.currentUserSubject.value; + return user?.token || null; + } + + isAuthenticated(): boolean { + return this.currentUserSubject.value !== null; + } + + setCurrentUser(user: User): void { + this.currentUserSubject.next(user); + // Store user in localStorage for persistence + localStorage.setItem('currentUser', JSON.stringify(user)); + } + + setRedirectUrl(url: string): void { + this.redirectUrl = url; + } + + getRedirectUrl(): string { + return this.redirectUrl; + } + + // Initialize user from localStorage on app start + initializeAuth(): void { + const storedUser = localStorage.getItem('currentUser'); + if (storedUser) { + try { + const user = JSON.parse(storedUser); + this.currentUserSubject.next(user); + } catch (error) { + console.error('Error parsing stored user:', error); + localStorage.removeItem('currentUser'); + } + } + } + + /** + * Verify JWT token from email link (local verification) + * @param token The JWT token to verify + * @returns Observable with verification result + */ + verifySecretLinkToken(token: string): Observable { + try { + // Parse JWT token locally + const tokenData = this.parseJwtToken(token); + + if (!tokenData) { + return of({ + isValid: false, + message: 'Invalid token format' + }); + } + + // Check if token is expired + if (this.isTokenExpired(token)) { + return of({ + isValid: false, + message: 'This link has expired. Please request a new one.' + }); + } + console.log('tokenData', tokenData); + + // Extract user data from token + const user: User = { + id: tokenData.userId || tokenData.sub || tokenData.id, + username: tokenData.username || tokenData.preferred_username || '', + email: tokenData.email || tokenData.email_address || '', + firstName: tokenData.firstName || tokenData.given_name || tokenData.first_name || '', + lastName: tokenData.lastName || tokenData.family_name || tokenData.last_name || '', + createdAt: tokenData.createdAt || tokenData.created_at || new Date().toISOString(), + branchIds: tokenData.branchIds || tokenData.branch_ids || [], + token: token, + mfaVerified: false // Token users still need MFA verification + }; + + return of({ + isValid: true, + user: user, + message: 'Token verified successfully. MFA required.', + expiresAt: tokenData.exp ? new Date(tokenData.exp * 1000) : undefined, + requiresMfa: true + }); + + } catch (error) { + console.error('Token verification error:', error); + return of({ + isValid: false, + message: 'Invalid or corrupted token' + }); + } + } + + /** + * Check if a token is expired locally (basic check) + * @param token JWT token to check + * @returns boolean indicating if token is expired + */ + isTokenExpired(token: string): boolean { + try { + const payload = JSON.parse(atob(token.split('.')[1])); + const currentTime = Math.floor(Date.now() / 1000); + return payload.exp < currentTime; + } catch (error) { + console.error('Error parsing token:', error); + return true; // Consider invalid tokens as expired + } + } + + /** + * Parse JWT token and extract payload + * @param token JWT token to parse + * @returns parsed token data or null if invalid + */ + private parseJwtToken(token: string): any | null { + try { + // Split token into parts + const parts = token.split('.'); + if (parts.length !== 3) { + return null; + } + + // Decode the payload (middle part) + const payload = parts[1]; + const decodedPayload = atob(payload.replace(/-/g, '+').replace(/_/g, '/')); + return JSON.parse(decodedPayload); + } catch (error) { + console.error('Error parsing JWT token:', error); + return null; + } + } + + /** + * Verify MFA code for token-based authentication + * @param mfaCode MFA code entered by user + * @param user User data from token verification + * @returns Observable with MFA verification result + */ + verifyMfaForToken(mfaCode: string, user: User): Observable { + // For token-based users, we can either: + // 1. Verify MFA locally (if MFA code is embedded in token) + // 2. Make a server call to verify MFA + + // For now, we'll simulate MFA verification + // In a real implementation, you might want to verify against a server + //TODO: Implement MFA verification + + const loginUrl = `${this.apiConfig.authUrl}/mfa/token-login`; + return this.http.post(loginUrl, { + mfaToken: user.token, + mfaCode: mfaCode + }).pipe( + map((response: LoginResponse) => { + + return { + result: LoginResultType.Success, + responseData: { + ...user, + mfaVerified: true + }, + message: 'MFA verification successful' + }; + }), + catchError((error) => { + console.error('MFA verification error:', error); + return of({ + result: LoginResultType.Error, + message: error.error?.message || 'An error occurred during MFA verification' + }); + }) + ); + } + + /** + * Extract token from URL parameters + * @param url URL containing token parameter + * @returns token string or null if not found + */ + extractTokenFromUrl(url: string): string | null { + try { + const urlObj = new URL(url); + return urlObj.searchParams.get('token'); + } catch (error) { + console.error('Error parsing URL:', error); + return null; + } + } +} diff --git a/APP/src/app/shared/services/ui-utils.service.ts b/APP/src/app/shared/services/ui-utils.service.ts new file mode 100644 index 0000000..31f2a01 --- /dev/null +++ b/APP/src/app/shared/services/ui-utils.service.ts @@ -0,0 +1,182 @@ +import { Injectable } from '@angular/core'; +import { SVGIcon } from '@progress/kendo-angular-icons'; +import { EscrowStatus, CbAssigneeRole } from '../models/enums.model'; +import { Subject } from 'rxjs'; + +@Injectable({ + providedIn: 'root' +}) +export class UiUtilsService { + public setPageTitleSubject = new Subject(); + public setPageTitle$ = this.setPageTitleSubject.asObservable(); + // Icon properties - these should be injected or provided by a parent component + // For now, we'll make them optional and let the calling component provide them + checkCircleIcon?: SVGIcon; + clockIcon?: SVGIcon; + alertCircleIcon?: SVGIcon; + userIcon?: SVGIcon; + + constructor() { } + + /** + * Get CSS class for escrow status + */ + getStatusClass(status: EscrowStatus): string { + switch (status) { + case EscrowStatus.Open: + return 'status-active'; + case EscrowStatus.Closed: + return 'status-completed'; + case EscrowStatus.Cancelled: + return 'status-cancelled'; + default: + return ''; + } + } + + /** + * Get display label for escrow status + */ + getEscrowStatusLabel(status: EscrowStatus): string { + switch (status) { + case EscrowStatus.Open: + return 'Open'; + case EscrowStatus.Closed: + return 'Closed'; + case EscrowStatus.Cancelled: + return 'Cancelled'; + default: + return ''; + } + } + + /** + * Get CSS class for priority level + */ + getPriorityClass(priority: string): string { + switch (priority) { + case 'high': + return 'priority-high'; + case 'medium': + return 'priority-medium'; + case 'low': + return 'priority-low'; + default: + return ''; + } + } + + /** + * Get icon for escrow status + */ + getStatusIcon(status: EscrowStatus): SVGIcon | undefined { + switch (status) { + case EscrowStatus.Open: + return this.checkCircleIcon; + case EscrowStatus.Closed: + return this.checkCircleIcon; + case EscrowStatus.Cancelled: + return this.alertCircleIcon; + default: + return this.clockIcon; + } + } + + /** + * Get CSS class for assignee role + */ + getRoleClass(role: CbAssigneeRole): string { + switch (role) { + case CbAssigneeRole.Buyer: + return 'role-buyer'; + case CbAssigneeRole.Seller: + return 'role-seller'; + case CbAssigneeRole.BuyerRealEstateAgent: + case CbAssigneeRole.SellerRealEstateAgent: + return 'role-agent'; + case CbAssigneeRole.EscrowOfficer: + case CbAssigneeRole.EscrowAssignee: + return 'role-escrow'; + case CbAssigneeRole.LoanBroker: + case CbAssigneeRole.Lender: + return 'role-lender'; + case CbAssigneeRole.SellerTransactionCoordinator: + case CbAssigneeRole.BuyerTransactionCoordinator: + return 'role-coordinator'; + case CbAssigneeRole.None: + return 'role-default'; + default: + return 'role-default'; + } + } + + /** + * Get icon for assignee role + */ + getRoleIcon(role: CbAssigneeRole): SVGIcon | undefined { + switch (role) { + case CbAssigneeRole.Buyer: + case CbAssigneeRole.Seller: + case CbAssigneeRole.BuyerRealEstateAgent: + case CbAssigneeRole.SellerRealEstateAgent: + case CbAssigneeRole.EscrowOfficer: + case CbAssigneeRole.EscrowAssignee: + case CbAssigneeRole.LoanBroker: + case CbAssigneeRole.Lender: + case CbAssigneeRole.SellerTransactionCoordinator: + case CbAssigneeRole.BuyerTransactionCoordinator: + return this.userIcon; + case CbAssigneeRole.None: + return this.userIcon; + default: + return this.userIcon; + } + } + + /** + * Get display label for assignee role + */ + getRoleLabel(role: CbAssigneeRole): string { + switch (role) { + case CbAssigneeRole.Buyer: + return 'Buyer'; + case CbAssigneeRole.Seller: + return 'Seller'; + case CbAssigneeRole.BuyerRealEstateAgent: + return 'Buyer Agent'; + case CbAssigneeRole.SellerRealEstateAgent: + return 'Seller Agent'; + case CbAssigneeRole.EscrowOfficer: + return 'Escrow Officer'; + case CbAssigneeRole.EscrowAssignee: + return 'Escrow Assignee'; + case CbAssigneeRole.LoanBroker: + return 'Loan Broker'; + case CbAssigneeRole.Lender: + return 'Lender'; + case CbAssigneeRole.SellerTransactionCoordinator: + return 'Seller Coordinator'; + case CbAssigneeRole.BuyerTransactionCoordinator: + return 'Buyer Coordinator'; + case CbAssigneeRole.None: + return 'None'; + default: + return 'Unknown'; + } + } + + /** + * Set icons for the service (should be called by components that use this service) + */ + setIcons(icons: { + checkCircleIcon?: SVGIcon; + clockIcon?: SVGIcon; + alertCircleIcon?: SVGIcon; + userIcon?: SVGIcon; + }): void { + this.checkCircleIcon = icons.checkCircleIcon; + this.clockIcon = icons.clockIcon; + this.alertCircleIcon = icons.alertCircleIcon; + this.userIcon = icons.userIcon; + } +} diff --git a/APP/src/app/shared/styles/ui-utils.scss b/APP/src/app/shared/styles/ui-utils.scss new file mode 100644 index 0000000..d56f233 --- /dev/null +++ b/APP/src/app/shared/styles/ui-utils.scss @@ -0,0 +1,271 @@ +// ============================================================================= +// UI Utility Classes +// ============================================================================= +// Centralized SCSS for utility classes used by UiUtilsService +// These classes provide consistent styling across all components + +// ============================================================================= +// Status Badge Classes +// ============================================================================= +.status-badge { + display: inline-flex; + align-items: center; + gap: 0.25rem; + padding: 0.25rem 0.75rem; + border-radius: 20px; + font-size: 0.8rem; + font-weight: 600; + text-transform: uppercase; + + kendo-svgicon { + width: 12px; + height: 12px; + } + + // Status variants + &.status-active { + background: rgba(34, 197, 94, 0.1); + color: #16a34a; + } + + &.status-pending { + background: rgba(251, 191, 36, 0.1); + color: #d97706; + } + + &.status-completed { + background: rgba(59, 130, 246, 0.1); + color: #2563eb; + } + + &.status-cancelled { + background: rgba(239, 68, 68, 0.1); + color: #dc2626; + } +} + +// ============================================================================= +// Priority Badge Classes +// ============================================================================= +.priority-badge { + padding: 0.125rem 0.5rem; + border-radius: 12px; + font-size: 0.75rem; + font-weight: 600; + text-transform: uppercase; + + // Priority variants + &.priority-high { + background: rgba(239, 68, 68, 0.1); + color: #dc2626; + } + + &.priority-medium { + background: rgba(251, 191, 36, 0.1); + color: #d97706; + } + + &.priority-low { + background: rgba(34, 197, 94, 0.1); + color: #16a34a; + } +} + +// ============================================================================= +// Role Badge Classes +// ============================================================================= +.role-badge { + display: flex; + align-items: center; + gap: 0.25rem; + padding: 0.25rem 0.75rem; + border-radius: 20px; + font-size: 0.8rem; + font-weight: 600; + text-transform: uppercase; + + kendo-svgicon { + width: 12px; + height: 12px; + } + + // Role variants + &.role-buyer { + background: rgba(59, 130, 246, 0.1); + color: #2563eb; + } + + &.role-seller { + background: rgba(168, 85, 247, 0.1); + color: #9333ea; + } + + &.role-agent { + background: rgba(34, 197, 94, 0.1); + color: #16a34a; + } + + &.role-escrow { + background: rgba(245, 158, 11, 0.1); + color: #d97706; + } + + &.role-lender { + background: rgba(139, 69, 19, 0.1); + color: #8b4513; + } + + &.role-coordinator { + background: rgba(75, 85, 99, 0.1); + color: #4b5563; + } + + &.role-default { + background: rgba(107, 114, 128, 0.1); + color: #6b7280; + } +} + +// ============================================================================= +// Task Status Classes (for transaction-detail component) +// ============================================================================= +.task-status { + padding: 0.25rem 0.75rem; + border-radius: 12px; + font-size: 0.75rem; + font-weight: 600; + text-transform: uppercase; + + &.task-completed { + background: rgba(34, 197, 94, 0.1); + color: #16a34a; + } + + &.task-in-progress { + background: rgba(59, 130, 246, 0.1); + color: #2563eb; + } + + &.task-pending { + background: rgba(251, 191, 36, 0.1); + color: #d97706; + } + + &.task-cancelled { + background: rgba(239, 68, 68, 0.1); + color: #dc2626; + } +} + +// ============================================================================= +// Utility Mixins (for consistent styling) +// ============================================================================= +@mixin badge-base { + display: inline-flex; + align-items: center; + gap: 0.25rem; + font-weight: 600; + text-transform: uppercase; +} + +@mixin status-colors($bg-color, $text-color) { + background: rgba($bg-color, 0.1); + color: $text-color; +} + +// ============================================================================= +// Responsive Design +// ============================================================================= +@media (max-width: 768px) { + .status-badge, + .role-badge { + font-size: 0.75rem; + padding: 0.2rem 0.6rem; + } + + .priority-badge { + font-size: 0.7rem; + padding: 0.1rem 0.4rem; + } +} + +// ============================================================================= +// Dark Mode Support (if needed in the future) +// ============================================================================= +@media (prefers-color-scheme: dark) { + .status-badge { + &.status-active { + background: rgba(34, 197, 94, 0.2); + color: #4ade80; + } + + &.status-pending { + background: rgba(251, 191, 36, 0.2); + color: #fbbf24; + } + + &.status-completed { + background: rgba(59, 130, 246, 0.2); + color: #60a5fa; + } + + &.status-cancelled { + background: rgba(239, 68, 68, 0.2); + color: #f87171; + } + } + + .priority-badge { + &.priority-high { + background: rgba(239, 68, 68, 0.2); + color: #f87171; + } + + &.priority-medium { + background: rgba(251, 191, 36, 0.2); + color: #fbbf24; + } + + &.priority-low { + background: rgba(34, 197, 94, 0.2); + color: #4ade80; + } + } + + .role-badge { + &.role-buyer { + background: rgba(59, 130, 246, 0.2); + color: #60a5fa; + } + + &.role-seller { + background: rgba(168, 85, 247, 0.2); + color: #a78bfa; + } + + &.role-agent { + background: rgba(34, 197, 94, 0.2); + color: #4ade80; + } + + &.role-escrow { + background: rgba(245, 158, 11, 0.2); + color: #fbbf24; + } + + &.role-lender { + background: rgba(139, 69, 19, 0.2); + color: #d97706; + } + + &.role-coordinator { + background: rgba(75, 85, 99, 0.2); + color: #9ca3af; + } + + &.role-default { + background: rgba(107, 114, 128, 0.2); + color: #9ca3af; + } + } +} diff --git a/APP/src/app/shared/token-verification/token-verification.component.html b/APP/src/app/shared/token-verification/token-verification.component.html new file mode 100644 index 0000000..bd800cb --- /dev/null +++ b/APP/src/app/shared/token-verification/token-verification.component.html @@ -0,0 +1,77 @@ +
+
+
+ +
+ +
+ +
+ +

Verifying your access...

+

Please wait while we verify your email link.

+
+ + +
+
+ +
+

Access Granted!

+

Welcome back, {{ verificationResult?.user?.firstName }}!

+

Redirecting you to the dashboard...

+
+ + +
+
+ +
+

Token Verified!

+

Welcome back, {{ verificationResult?.user?.firstName }}!

+

Please complete MFA verification to continue...

+ + +
+ +
+
+ + +
+
+ +
+

Link Verification Failed

+

{{ errorMessage }}

+ +
+

If you're having trouble accessing your account:

+
    +
  • Make sure you clicked the complete link from your email
  • +
  • Check if the link has expired
  • +
  • Try requesting a new access link
  • +
+
+ +
+ + + +
+
+
+
+ + + + +
\ No newline at end of file diff --git a/APP/src/app/shared/token-verification/token-verification.component.scss b/APP/src/app/shared/token-verification/token-verification.component.scss new file mode 100644 index 0000000..d9f0347 --- /dev/null +++ b/APP/src/app/shared/token-verification/token-verification.component.scss @@ -0,0 +1,164 @@ +.token-verification-container { + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + padding: 20px; +} + +.verification-card { + background: white; + border-radius: 12px; + box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1); + padding: 40px; + max-width: 500px; + width: 100%; + text-align: center; +} + +.logo-section { + margin-bottom: 30px; + + .logo { + height: 60px; + width: auto; + } +} + +.verification-content { + h2 { + color: #333; + margin-bottom: 16px; + font-size: 24px; + font-weight: 600; + } + + p { + color: #666; + margin-bottom: 12px; + line-height: 1.5; + } +} + +.verifying-state { + .k-loader { + margin-bottom: 20px; + } +} + +.success-state { + .success-icon { + margin-bottom: 20px; + + .k-icon { + font-size: 48px; + color: #28a745; + } + } + + .redirect-message { + color: #28a745; + font-weight: 500; + margin-top: 20px; + } +} + +.mfa-required-state { + .success-icon { + margin-bottom: 20px; + + .k-icon { + font-size: 48px; + color: #28a745; + } + } + + .mfa-message { + color: #007bff; + font-weight: 500; + margin-top: 20px; + } +} + +.error-state { + .error-icon { + margin-bottom: 20px; + + .k-icon { + font-size: 48px; + color: #dc3545; + } + } + + .error-message { + color: #dc3545; + font-weight: 500; + margin-bottom: 20px; + } + + .help-text { + background-color: #f8f9fa; + border: 1px solid #e9ecef; + border-radius: 6px; + padding: 16px; + margin-bottom: 30px; + text-align: left; + + p { + margin-bottom: 8px; + font-weight: 500; + color: #495057; + } + + ul { + margin: 0; + padding-left: 20px; + + li { + margin-bottom: 4px; + color: #6c757d; + font-size: 14px; + } + } + } + + .action-buttons { + display: flex; + gap: 12px; + justify-content: center; + flex-wrap: wrap; + + .action-btn { + min-width: 140px; + + &.secondary { + background-color: #6c757d; + border-color: #6c757d; + + &:hover { + background-color: #5a6268; + border-color: #545b62; + } + } + } + } +} + +// Responsive design +@media (max-width: 768px) { + .verification-card { + padding: 30px 20px; + margin: 10px; + } + + .action-buttons { + flex-direction: column; + align-items: center; + + .action-btn { + width: 100%; + max-width: 200px; + } + } +} diff --git a/APP/src/app/shared/token-verification/token-verification.component.ts b/APP/src/app/shared/token-verification/token-verification.component.ts new file mode 100644 index 0000000..9d6fbda --- /dev/null +++ b/APP/src/app/shared/token-verification/token-verification.component.ts @@ -0,0 +1,177 @@ +import { Component, OnInit, ViewChild, AfterViewInit } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ActivatedRoute, Router } from '@angular/router'; +import { IndicatorsModule } from '@progress/kendo-angular-indicators'; +import { ButtonsModule } from '@progress/kendo-angular-buttons'; +import { DialogModule } from '@progress/kendo-angular-dialog'; +import { AuthService, TokenVerificationResult, User } from '../services/auth.service'; +import { MfaDialogComponent } from '../mfa-dialog/mfa-dialog.component'; +import { take } from 'rxjs/operators'; + +@Component({ + selector: 'app-token-verification', + standalone: true, + imports: [ + CommonModule, + IndicatorsModule, + ButtonsModule, + DialogModule, + MfaDialogComponent + ], + templateUrl: './token-verification.component.html', + styleUrls: ['./token-verification.component.scss'] +}) +export class TokenVerificationComponent implements OnInit, AfterViewInit { + @ViewChild('mfaDialog') mfaDialog!: MfaDialogComponent; + + isVerifying = true; + verificationResult: TokenVerificationResult | null = null; + errorMessage = ''; + tokenUser: User | null = null; + + constructor( + private route: ActivatedRoute, + private router: Router, + private authService: AuthService + ) { } + + ngOnInit(): void { + this.route.queryParams.subscribe(params => { + const token = params['token']; + if (token) { + this.verifyToken(token); + } else { + this.handleError('No token provided in the link'); + } + }); + } + + ngAfterViewInit(): void { + console.log('AfterViewInit - MFA dialog reference:', this.mfaDialog); + } + + private verifyToken(token: string): void { + this.isVerifying = true; + this.errorMessage = ''; + + // Validate token format first + if (!this.isValidJwtFormat(token)) { + this.handleError('Invalid token format. Please check your email link.'); + return; + } + + this.authService.verifySecretLinkToken(token).subscribe({ + next: (result: TokenVerificationResult) => { + this.isVerifying = false; + this.verificationResult = result; + + if (result.isValid && result.user) { + // Token is valid, store user data and show MFA dialog + this.tokenUser = result.user; + this.verificationResult = result; + + console.log('Token verification result:', result); + console.log('Requires MFA:', result.requiresMfa); + + if (result.requiresMfa) { + // Show MFA dialog for token-based authentication + console.log('Showing MFA dialog...'); + this.authService.verifyMfaForToken('', result.user).pipe( + take(1) + ).subscribe(result => { + this.showMfaDialog(); + }); + + } else { + // If MFA is not required, proceed directly + console.log('MFA not required, proceeding directly...'); + this.authService.setCurrentUser(result.user); + setTimeout(() => { + this.redirectToDashboard(); + }, 2000); + } + } else { + this.handleError(result.message || 'Invalid or expired link. Please request a new one.'); + } + }, + error: (error) => { + this.isVerifying = false; + this.handleError('An error occurred while verifying the link. Please try again.'); + console.error('Token verification error:', error); + } + }); + } + + private isValidJwtFormat(token: string): boolean { + // Basic JWT format validation (3 parts separated by dots) + const parts = token.split('.'); + return parts.length === 3 && parts.every(part => part.length > 0); + } + + private handleError(message: string): void { + this.isVerifying = false; + this.errorMessage = message; + } + + private redirectToDashboard(): void { + const redirectUrl = this.authService.getRedirectUrl(); + this.router.navigate([redirectUrl || '/dashboard']); + } + + goToLogin(): void { + this.router.navigate(['/login']); + } + + requestNewLink(): void { + // This could redirect to a "request new link" page or contact form + // For now, redirect to login + this.router.navigate(['/login']); + } + + showMfaDialog(): void { + console.log('showMfaDialog called, tokenUser:', this.tokenUser); + + if (this.tokenUser) { + // Set the user data for MFA dialog first + const loginData = { + email: this.tokenUser.email, + password: '', // Not needed for token-based auth + tokenUser: this.tokenUser + }; + + // Use multiple approaches to ensure the dialog shows + const tryShowDialog = (attempt: number = 1) => { + console.log(`Attempt ${attempt} to show MFA dialog`); + + if (this.mfaDialog) { + console.log('MFA dialog found, setting loginData and showing...'); + (this.mfaDialog as any).loginData = loginData; + this.mfaDialog.show(); + } else if (attempt < 5) { + // Try again after a short delay + setTimeout(() => tryShowDialog(attempt + 1), 100); + } else { + console.error('MFA dialog not found after multiple attempts'); + } + }; + + // Start trying to show the dialog + tryShowDialog(); + } else { + console.error('No token user available for MFA dialog'); + } + } + + onMfaSuccess(userData: any): void { + this.authService.setCurrentUser(userData); + this.redirectToDashboard(); + } + + onMfaCancel(): void { + // Reset to initial state + this.isVerifying = true; + this.verificationResult = null; + this.tokenUser = null; + this.errorMessage = ''; + } +} diff --git a/APP/src/app/shared/utilities/array-utils.ts b/APP/src/app/shared/utilities/array-utils.ts new file mode 100644 index 0000000..9857608 --- /dev/null +++ b/APP/src/app/shared/utilities/array-utils.ts @@ -0,0 +1,90 @@ + +export class ArrayUtils { + + public static Equals(array1: Array, array2: Array): boolean { + // if the other array is a falsy value, return + if (!array2) + return false; + + // compare lengths - can save a lot of time + if (array1.length != array2.length) + return false; + + for (var i = 0, l = array1.length; i < l; i++) { + // Check if we have nested arrays + if (array1[i] instanceof Array && array2[i] instanceof Array) { + // recurse into the nested arrays + if (!ArrayUtils.Equals(array1[i], array2[i])) + return false; + } + else if (array1[i] != array2[i]) { + // Warning - two different object instances will never be equal: {x:20} != {x:20} + return false; + } + } + return true; + } + + // public static ToDropDownOptions(array: any[], keyProperty: string | number, valueProperty: string | number): DropDownOption[] { + // var result = []; + // for (let i = 0; i < array.length; i++) { + // const element = array[i]; + // result.push(new DropDownOption(element[keyProperty], element[valueProperty])); + // } + // return result; + // } + + + public static GroupBy(arr: Array, groupKeyPrepareFunction: (obj: T) => any | any[]) { + + let groups = arr.reduce(function (groupModel, obj) { + + let keys = groupKeyPrepareFunction(obj); + + const addToGroup = (key: any, obj: T) => { + let group = groupModel.find(g => g.key == key); + if (group == null) { + group = new GroupModel(key); + groupModel.push(group); + } + group.values.push(obj); + } + + if (Array.isArray(keys)) { + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + addToGroup(key, obj); + } + } else { + addToGroup(keys, obj); + } + return groupModel; + }, [] as GroupModel[]); + return groups; + } + public static RemoveDuplicate(objArray: T[], duplicateDetection: (a: T, b: T) => boolean) { + return objArray.filter((value, index, self) => + index === self.findIndex((t) => duplicateDetection(t, value)) + ); + } + + public static insertAt = (arr: any[], index: number, newItems: any) => [ + // part of the array before the specified index + ...arr.slice(0, index), + // inserted items + newItems, + // part of the array after the specified index + ...arr.slice(index) + ]; + +} + +export class GroupModel { + constructor(key: any) { + this.key = key; + this.values = []; + } + + key: any; + values: T[]; +} \ No newline at end of file diff --git a/APP/src/app/shared/utilities/date-utils.ts b/APP/src/app/shared/utilities/date-utils.ts new file mode 100644 index 0000000..05b1c21 --- /dev/null +++ b/APP/src/app/shared/utilities/date-utils.ts @@ -0,0 +1,190 @@ + +export class DateUtils { + + + // public static getIntervalDays(from: Date, to: Date, daysOfYear: DaysOfYear = DaysOfYear.ThirtyDaysPerMonth, countEndDate: boolean = false): number { + // let isNegative = false; + // if (from > to) { + // isNegative = true; + // let temp = new Date(to); + // to = from; + // from = temp; + // } + + // var days = 0; + // if (from && to) { + // //Get date without time + // from = new Date(from.getFullYear(), from.getMonth(), from.getDate()); + // to = new Date(to.getFullYear(), to.getMonth(), to.getDate()); + // var differenceTime = to.getTime() - from.getTime(); + // if (differenceTime > 0) { + // if (daysOfYear == DaysOfYear.ThirtyDaysPerMonth) { + // var fromYear = from.getFullYear(); + // var toYear = to.getFullYear(); + // var fromMonth = from.getMonth() + 1; + // var toMonth = to.getMonth() + 1; + + // var fromDays = from.getDate() > 30 ? 30 : from.getDate(); + // var toDays = to.getDate(); + + // days += 30 - fromDays; + // days += toDays; + // //calculate full 12 months years + // if (toYear > (fromYear + 1)) { + // days += (toYear - fromYear - 1) * 12 * 30; + // } + + // //if it's two different years, calculate the interval months + // if (toYear > fromYear) { + // days += (12 - fromMonth) * 30; + // days += (toMonth - 1) * 30; + // } + // else { + // //same year + // days += (toMonth - fromMonth - 1) * 30 + // } + // } + // else { + // // To calculate the no. of days between two dates + // days = Math.round(differenceTime / (1000 * 3600 * 24)); + // } + // } + // } + + // return (days + (countEndDate ? 1 : 0)) * (isNegative ? -1 : 1); + // } + + public static addDays(date: Date, days: number): Date { + if (date) { + var result = new Date(date); + result.setDate(result.getDate() + days); + return result; + } + return date; + } + + public static format(date: Date, format: string = 'MM/dd/yyyy hh:mm:ss', nullFormat: string = ''): string { + if (date) { + var z = { + M: date.getMonth() + 1, + d: date.getDate(), + H: date.getHours(), + h: (date.getHours() == 0 ? date.getHours() + 12 : date.getHours() > 12 ? date.getHours() - 12 : date.getHours()), + m: date.getMinutes(), + s: date.getSeconds(), + a: (date.getHours() > 11 ? 'PM' : 'AM') + }; + format = format.replace(/(M+|d+|H+|h+|m+|s+|a+)/g, function (v) { + return ((v.length > 1 ? "0" : "") + z[v.slice(-1) as keyof typeof z]).slice(-2); + }); + + return format.replace(/(y+)/g, function (v) { + return date.getFullYear().toString().slice(-v.length) + }); + } + return nullFormat; + } + public static isValidDate(d: Date): boolean { + return d instanceof Date && d.getTime() == d.getTime(); + } + public static parse(value: string | Date | null | undefined, changeToLocalTime = false): Date | null { + if (value) { + if (typeof value === 'string' && value.includes('-')) { + value = this.parseLocalDate(value); + return value; + } + value = new Date(value); + if (changeToLocalTime) { + //todo: change to local time from UTC + } + } else { + return null; + } + return value + } + public static parseLocalDate(localDate: string): Date { + const [year, month, day] = localDate.split('-').map(Number); + return new Date(year, month, day); + } + public static toLocalDate(date: Date): string { + return this.format(date, 'yyyy-MM-dd'); + } + public static getToday(endOfDay: boolean = false): Date { + let value = new Date(); + if (!endOfDay) { + value.setHours(0, 0, 0, 0); + } + else { + value.setHours(23, 59, 59, 999); + } + return value + } + + public static getBeginOfDate(value: Date): Date { + if (value) { + value = new Date(value); + value.setHours(0, 0, 0, 0); + } + return value + } + + public static getEndOfDate(value: Date): Date { + if (value) { + value = new Date(value); + value.setHours(23, 59, 59, 999); + } + return value + } + public static getEndOfMonth(value: Date): Date { + if (value) { + return new Date(value.getFullYear(), value.getMonth() + 1, 0) + } + return value; + } + public static getFirstDayOfCurrentMonth = (): Date => { + const now = new Date(); + return new Date(now.getFullYear(), now.getMonth(), 1); + }; + + public static getLastDayOfCurrentMonth = (): Date => { + const now = new Date(); + return new Date(now.getFullYear(), now.getMonth() + 1, 0); + }; + + public static isSameDate(date: Date, comparison: Date): boolean { + if (!date || !comparison) return (!date && !comparison); + date = this.parse(date, false) as Date; + comparison = this.parse(comparison, false) as Date; + return date.getFullYear() == comparison.getFullYear() && date.getMonth() == comparison.getMonth() && date.getDate() == comparison.getDate(); + } + + + public static getTimeStamp() { + var now = new Date(); + return ((now.getMonth() + 1) + '/' + (now.getDate()) + '/' + now.getFullYear() + " " + now.getHours() + ':' + + ((now.getMinutes() < 10) ? ("0" + now.getMinutes()) : (now.getMinutes())) + ':' + ((now.getSeconds() < 10) ? ("0" + now + .getSeconds()) : (now.getSeconds()))); + } + + + public static getDatesBetween(startDate: Date, endDate: Date): Date[] { + startDate = this.getBeginOfDate(startDate); + endDate = this.getBeginOfDate(endDate); + let result = [startDate]; + if (startDate < endDate) { + let tempDate = new Date(startDate); + while (tempDate < endDate) { + tempDate = this.addDays(tempDate, 1); + result.push(tempDate); + } + } + return result; + } + + /** + * Returns the stored time value in milliseconds since midnight, January 1, 1970 UTC., return 0 if date is null + */ + public static getTime(date?: Date): number { + return date != null ? date.getTime() : 0; + } +} \ No newline at end of file diff --git a/APP/src/app/shared/utilities/enum-utils.ts b/APP/src/app/shared/utilities/enum-utils.ts new file mode 100644 index 0000000..a0a751f --- /dev/null +++ b/APP/src/app/shared/utilities/enum-utils.ts @@ -0,0 +1,18 @@ +export class EnumUtils { + + public static hasFlag(obj: number, enumValue: number): boolean { + if (obj) { + if (obj & enumValue) { + return true; + } + } + return false; + } + + public static GetAllEnumValue(enumType: any): number[] { + return Object + .keys(enumType) + .filter((v) => !isNaN(Number(v))) + .map(v => Number.parseInt(v)); + } +} diff --git a/APP/src/app/shared/utilities/file-utils.ts b/APP/src/app/shared/utilities/file-utils.ts new file mode 100644 index 0000000..6b57ded --- /dev/null +++ b/APP/src/app/shared/utilities/file-utils.ts @@ -0,0 +1,26 @@ +export class FileUtils { + public static formatFileSize(bytes: number): string { + if (bytes < 1024) { + return `${bytes} Bytes`; + } else if (bytes < 1024 * 1024) { + return `${(bytes / 1024).toFixed(2)} KB`; + } else { + return `${(bytes / (1024 * 1024)).toFixed(2)} MB`; + } + } + + public static getFileName(fileFullPath: string): string | null { + const lastSlashIndex = fileFullPath.lastIndexOf('\\'); + if (lastSlashIndex === -1 || lastSlashIndex === 0 || lastSlashIndex === fileFullPath.length - 1) { + return fileFullPath; // No folder found + } + return fileFullPath.slice(lastSlashIndex + 1); + } + public static getFileExt(filename: string): string | null { + const lastDotIndex = filename.lastIndexOf('.'); + if (lastDotIndex === -1 || lastDotIndex === 0 || lastDotIndex === filename.length - 1) { + return null; // No extension found + } + return filename.slice(lastDotIndex); + } +} \ No newline at end of file diff --git a/APP/src/app/shared/utilities/html-element-utils.ts b/APP/src/app/shared/utilities/html-element-utils.ts new file mode 100644 index 0000000..7b6fb06 --- /dev/null +++ b/APP/src/app/shared/utilities/html-element-utils.ts @@ -0,0 +1,20 @@ +import { Observable } from "rxjs"; + +export class HtmlElementUtils { + + + public static findChildByClassName(children: HTMLCollection, className: string): T { + + let childrenNodeArr = Array.from(children); + for (let i = 0; i < childrenNodeArr.length; i++) { + const element = childrenNodeArr[i]; + if (element.classList.contains(className)) { + return element as T; + } else if (element.children) { + let child = this.findChildByClassName(element.children, className) + if (child) return child as T; + } + } + return null as T; + } +} diff --git a/APP/src/app/shared/utilities/linq-utils.ts b/APP/src/app/shared/utilities/linq-utils.ts new file mode 100644 index 0000000..e5e48d6 --- /dev/null +++ b/APP/src/app/shared/utilities/linq-utils.ts @@ -0,0 +1,9 @@ + +export class LinqUtils { + public static GroupBy(xs: any[], key: any) { + return xs.reduce(function (rv, x) { + (rv[x[key]] = rv[x[key]] || []).push(x); + return rv; + }, {}); + } +} \ No newline at end of file diff --git a/APP/src/app/shared/utilities/number-utils.ts b/APP/src/app/shared/utilities/number-utils.ts new file mode 100644 index 0000000..fdab236 --- /dev/null +++ b/APP/src/app/shared/utilities/number-utils.ts @@ -0,0 +1,84 @@ +import { formatCurrency } from "@angular/common"; + +const PPI = 96; +export class NumberUtils { + public static Ordinal(value: number): string { + let suffix = ''; + const last = value % 10; + const specialLast = value % 100; + if (!value || value < 1) { + return value.toString(); + } + if (last === 1 && specialLast !== 11) { + suffix = 'st'; + } else if (last === 2 && specialLast !== 12) { + suffix = 'nd'; + } else if (last === 3 && specialLast !== 13) { + suffix = 'rd'; + } else { + suffix = 'th'; + } + return value + suffix; + } + + public static Clamp(n: number, min: number, max: number): number { + return Math.min(max, Math.max(min, n)); + } + + public static SortFunction(a: number, b: number): number { + return a - b; + } + + public static Sum(array: number[]): number { + + return array.reduce((a, b) => (isNaN(a) ? 0 : a) + (isNaN(b) ? 0 : b), 0); + } + + public static FormatCurrency(v: number, zeroExpression: string = '0'): string { + + return ['', 0, null, undefined, NaN].includes(v) ? zeroExpression : formatCurrency(v, "en", "", "", `0.2`); + } + public static Round(num: number, precision: number) { + const factor = 10 ** precision; + return Math.round(num * factor) / factor; + } + + public static RoundCurrency(num: number): number { + return this.Round(num, 2); + } + + public static VersionDiff(v1: string, v2: string) { + let versionDefine = [ + { index: 0, name: 'major' }, + { index: 1, name: 'minor' }, + { index: 2, name: 'patch' }, + { index: 3, name: 'build' } + ] + if (v1 && v2) { + let v1Versions = v1.split('.'); + let v2Versions = v2.split('.'); + + for (let i = 0; i < v1Versions.length; i++) { + if (v2Versions.length == i) return versionDefine[i]; + if (v1Versions[i] != v2Versions[i]) return versionDefine[i]; + } + } + return null; + } + + public static Average(numArr: number[]) { + return numArr.reduce((a, b) => a + b) / numArr.length; + } + + public static PixelToInch(pixel: number) { + return this.Round(pixel / PPI, 2); + } + public static InchToPixel(inch: number) { + return Math.round(inch * PPI); + } + + public static Mid(a: number, b: number) { + return a + Math.floor((b - a) / 2); + } + +} diff --git a/APP/src/app/shared/utilities/object-utils.ts b/APP/src/app/shared/utilities/object-utils.ts new file mode 100644 index 0000000..8113972 --- /dev/null +++ b/APP/src/app/shared/utilities/object-utils.ts @@ -0,0 +1,217 @@ +import { Observable } from "rxjs"; + +const dateAndTimeRegex = new RegExp(/^(?\d{4}-\d{2}-\d{2})T(?\d{2}:\d{2}):((?\d{2}\.\d{0,6})|(?\d{2}))$/); + +export class ObjectUtils { + + private static ReviveDateTime(key: any, value: any): any { + if (typeof value === 'string') { + if (dateAndTimeRegex.test(value)) { + let newDate = new Date(value); + return newDate; + } + } + + return value; + } + + public static HasAnyData(obj: any, excludes: string[] = []) { + var hasData = false; + + for (const p in obj) { + if (false == excludes.includes(p) && Object.prototype.hasOwnProperty.call(obj, p)) { + const element = obj[p]; + if (element) { + if (typeof element !== 'object' || this.HasAnyData(element, excludes)) { + hasData = true; + break; + } + } + } + } + + return hasData; + } + public static Clone(obj: T, avoidCirculateRef = false): T { + if (avoidCirculateRef) { + return JSON.parse(this.stringify(obj), ObjectUtils.ReviveDateTime); + } + return JSON.parse(JSON.stringify(obj), ObjectUtils.ReviveDateTime); + } + + public static CopyValue(source: any, destination: any, excludes: string[] = ["id"], overwriting: boolean = true) { + + for (const p in source) { + if (false == excludes.includes(p) && Object.prototype.hasOwnProperty.call(source, p)) { + const element = source[p]; + + if (element && Array.isArray(element)) { + if ([null, undefined].includes(destination[p])) { + destination[p] = []; + for (let i = 0; i < element.length; i++) { + const cloneItem = {}; + this.CopyValue(element[i], cloneItem, excludes, true); + destination[p].push(cloneItem); + } + } else if (Array.isArray(destination[p])) { + + for (let i = 0; i < element.length; i++) { + const item = element[i]; + let destLength = destination[p].length; + if (i >= destLength) { + destination[p].push(this.Clone(source[p][i])); + } + this.CopyValue(item, destination[p][i], excludes, overwriting); + } + } + } + else if (element && typeof element.getMonth === 'function') { + //For angular will treat Date as object + try { + destination[p] = element; + } catch (error) { + console.log(`can\'t copy ${p}`, error); + } + } + else if (element && typeof element == 'object') { + let objectOverwriting = overwriting; + if ([null, undefined].includes(destination[p])) { + destination[p] = {}; + objectOverwriting = true; + } + try { + this.CopyValue(element, destination[p], excludes, objectOverwriting); + } catch (error) { + console.log(`can\'t copy ${p}`, error); + } + } else if (overwriting || [null, '', undefined].includes(destination[p])) { + try { + destination[p] = element; + } catch (error) { + console.log(`can\'t copy ${p}`, error); + } + } + } + } + } + public static ClearPkAndFk(source: any, excludes: string[] = [], clearExtra: string[] = []) { + + for (const p in source) { + const element = source[p]; + + if (element) { + if (clearExtra.includes(p)) { + source[p] = null; + } else if (typeof element == 'object') { + this.ClearPkAndFk(element, excludes, clearExtra); + } else if ( + (typeof element == 'string' && (p == 'id' || p.indexOf('Id') > -1) && + false == excludes.includes(p)) + ) { + source[p] = null; + } + } + } + } + public static ClearValue(source: any, excludes: string[] = ["id"]) { + for (const p in source) { + if (false == excludes.includes(p) && Object.prototype.hasOwnProperty.call(source, p)) { + const element = source[p]; + + if (element) { + if (typeof element == 'object') { + this.ClearValue(element, excludes); + } else if (typeof element == 'number') { + source[p] = 0; + } else { + source[p] = null; + } + } + } + } + } + /** + * return `true` if the comparison has any different value with source. + */ + public static CompareDiffValue(source: any, comparison: any, excludes: string[] = ["id"]) { + let isDifferent = false; + for (const p in source) { + if (false == excludes.includes(p) && + Object.prototype.hasOwnProperty.call(source, p)) { + const element = source[p]; + if (element && Array.isArray(element)) { + if (Array.isArray(comparison[p])) { + for (let i = 0; i < element.length; i++) { + const item = element[i]; + let destLength = comparison[p].length; + if (i >= destLength) { + return true; + } + isDifferent = this.CompareDiffValue(item, comparison[p][i], excludes); + if (isDifferent) return true; + } + } + } + else if (element && typeof element.getMonth === 'function') { + //For angular will treat Date as object + //TODO:Compare Date + //comparison[p] = element; + } + else if (element && typeof element == 'object') { + isDifferent = this.CompareDiffValue(element, comparison[p], excludes); + + } else { + isDifferent = comparison[p] != element; + } + + if (isDifferent) return true; + } + } + return false; + } + /** + * return `true` if the comparison has any different value with source. + */ + public static CompareDiffArrayContent(source: any[], comparison: any[], excludes: string[] = ["id"]) { + let isDifferent = false; + if (source && comparison) { + if (source.length == comparison.length) { + for (let i = 0; i < source.length; i++) { + const sourceItem = source[i]; + const comparisonItem = comparison[i]; + + isDifferent = this.CompareDiffValue(sourceItem, comparisonItem); + if (isDifferent) return true; + } + return false; + } + } + + return true; + } + + public static isNullOrUndefined(obj: any) { + return [null, undefined].includes(obj); + } + + public static isObservable(template: T | Observable): template is Observable { + return (template as Observable).subscribe !== undefined; + } + public static stringify(obj: any): string { + let cache: any[] = []; + let str = JSON.stringify(obj, function (key, value) { + if (typeof value === "object" && value !== null) { + if (cache.indexOf(value) !== -1) { + // Circular reference found, discard key + return; + } + // Store value in our collection + cache.push(value); + } + return value; + }); + cache = []; // reset the cache + return str; + } + +} diff --git a/APP/src/app/shared/utilities/string-utils.ts b/APP/src/app/shared/utilities/string-utils.ts new file mode 100644 index 0000000..c6edc74 --- /dev/null +++ b/APP/src/app/shared/utilities/string-utils.ts @@ -0,0 +1,204 @@ +import { AddressInfo } from "../models"; + +export class StringUtils { + static compare(aval: string, bval: string) { + return aval ? aval.localeCompare(bval) : -1; + } + + // Sorting function for SemVer + // Returns 1 if a is greater, -1 if b is greater, 0 if equal + public static compareSemVer(a: string, b: string): number { + if (a === b) { + return 0; + } + + var a_components = a.split("."); + var b_components = b.split("."); + + var len = Math.min(a_components.length, b_components.length); + + // loop while the components are equal + for (var i = 0; i < len; i++) { + // A bigger than B + if (parseInt(a_components[i]) > parseInt(b_components[i])) { + return 1; + } + + // B bigger than A + if (parseInt(a_components[i]) < parseInt(b_components[i])) { + return -1; + } + } + + // If one's a prefix of the other, the longer one is greater. + if (a_components.length > b_components.length) { + return 1; + } + + if (a_components.length < b_components.length) { + return -1; + } + + // Otherwise they are the same. + return 0; + } + + public static isNullOrWhitespace(input: string): boolean { + return !input || !input.trim(); + } + + public static getTrimmedValue(input: string, emptyFormat: string = ''): string { + if (input) { + input = input.trim(); + } + if (input) return input; + return emptyFormat; + } + + public static removeNewLines(input: string): string { + if (input) { + input = input.replace(/[\r\n]+/g, ''); + } + return input; + } + + public static firstIsVowel(s: string): boolean { + if (s) { + return ['a', 'e', 'i', 'o', 'u'].indexOf(s[0].toLowerCase()) !== -1 + } + return false; + } + + public static makeCommaSeparatedString(arr: string[], useOxfordComma: boolean, + conjunctionWord: string = "and") { + arr = arr.filter(s => s); + const listStart = arr.slice(0, -1).join(', '); + const listEnd = arr.slice(-1); + const conjunction = + arr.length <= 1 + ? "" + : useOxfordComma && arr.length > 2 + ? `, ${conjunctionWord} ` + : ` ${conjunctionWord} `; + + return [listStart, listEnd].join(conjunction); + } + + + + public static getFormattedTerm(input: string, emptyFormatter: string = 'Empty'): string { + if (false == this.isNullOrWhitespace(input)) { + return input.trim(); + } + return emptyFormatter; + } + public static camelToTitle(camelCase: string): string { + // no side-effects + return camelCase + // inject space before the upper case letters + .replace(/([A-Z])/g, function (match) { + return " " + match; + }) + // replace first char with upper case + .replace(/^./, function (match) { + return match.toUpperCase(); + }).trim(); + } + public static getCapitalLetters(str: string) { + return str.replace(/[^A-Z]+/g, ""); + } + + /** + * status could be `info` , `primary` , `danger` , `warning` , `success` + */ + public static getHtmlBadge(text: string, badgeStatus: string, mergingClass = 'mr-1') { + return ``; + + } + + public static getHtmlInnerText(htmlText: string) { + return htmlText ? htmlText.replace(/<[^>]+>/g, '') : ''; + + + } + + /** + * Try to parse city sate zip string like `Monrovia, CA 91016` to AddressInfo, if failed will return `null` instead. + */ + public static tryParseCityStateZip(cityStateZip: string): AddressInfo { + + let addressInfo = {} as AddressInfo; + var regex = /([\w\s]*),\s*([A-Z]{2})\s*(\d*-?\d*)/g; + var match = regex.exec(cityStateZip); + if (match) { + addressInfo = { + city: match[1], + state: match[2], + zip: match[3] + } as AddressInfo; + return addressInfo; + } else { + return {} as AddressInfo; + } + } + + public static getCityStateZipString(addressInfo: AddressInfo): string { + let result = ''; + if (false == this.isNullOrWhitespace(addressInfo.city)) { + result += `${addressInfo.city}, `; + } + if (false == this.isNullOrWhitespace(addressInfo.state)) { + result += `${addressInfo.state} `; + } + if (false == this.isNullOrWhitespace(addressInfo.zip)) { + result += addressInfo.zip; + } + return result; + } + + public static getFullAddressString(addressInfo: AddressInfo): string { + + if (addressInfo && StringUtils.isNullOrWhitespace(addressInfo.address)) { + return 'No Subject Property Entered'; + } + else { + return `${addressInfo.address}, ${this.getCityStateZipString(addressInfo)}` + } + } + public static toUpperString(prop: any) { + if (prop) { + if (typeof prop === 'string') return prop.toUpperCase(); + return prop.toString().toUpperCase(); + } else { + return ''; + } + } + public static toLowerString(prop: any) { + if (prop) { + if (typeof prop === 'string') return prop.toLowerCase(); + return prop.toString().toLowerCase(); + } else { + return ''; + } + } + public static truncateString(str: string, maxLength: number) { + if (str && str.length > maxLength) { + return str.slice(0, maxLength); + } else { + return str; + } + } + public static insertAt(str: string, insertValue: string, index: number) { + return [str.slice(0, index), insertValue, str.slice(index)].join(''); + } + public static replaceAll(str: string, find: string, replace: string) { + return str.replace(new RegExp(find, 'g'), replace); + } + public static safeLocaleCompare(a: string, b: string, sortDirection: string = 'asc') { + if (sortDirection == 'asc' && a) + return b ? a.localeCompare(b) : -1; + else if (b) + return a ? b.localeCompare(a) : 1; + else return 0; + } +} diff --git a/APP/src/app/shared/utilities/textarea-utils.ts b/APP/src/app/shared/utilities/textarea-utils.ts new file mode 100644 index 0000000..7cf4a81 --- /dev/null +++ b/APP/src/app/shared/utilities/textarea-utils.ts @@ -0,0 +1,49 @@ + +export class TextareaUtils { + + public static autoExpand(field: HTMLTextAreaElement) { + // + if (field) { + // Reset field height + field.style.height = '0px'; + const computed = window.getComputedStyle(field); + // Calculate the height + var height = 0 + + parseInt(computed.getPropertyValue('border-top-width'), 10) + + field.scrollHeight + + parseInt(computed.getPropertyValue('border-bottom-width'), 10); + + field.style.height = height + 'px'; + } + } + + public static isEditingKeyPress(e: KeyboardEvent) { + + if ([46, 8, 9, 27, 13, 190].indexOf(e.keyCode) !== -1 || + // Allow: Ctrl+A + (e.keyCode === 65 && (e.ctrlKey || e.metaKey)) || + // Allow: Ctrl+C + (e.keyCode === 67 && (e.ctrlKey || e.metaKey)) || + // Allow: Ctrl+V + (e.keyCode === 86 && (e.ctrlKey || e.metaKey)) || + // Allow: Ctrl+X + (e.keyCode === 88 && (e.ctrlKey || e.metaKey)) || + // Allow: page up, page down, home, end, left, right + (e.keyCode >= 33 && e.keyCode <= 39)) { + // let it happen, don't do anything + return false; + } + + if (typeof e.which == "undefined") { + // This is IE, which only fires keypress events for printable keys + return true; + } else if (e.ctrlKey && e.key.toUpperCase() == 'V') { + return true; + } + else if (!e.ctrlKey && !e.metaKey && !e.altKey && e.code != 'Tab' && e.key != 'Control') { + return true; + } + + return false; + } +} diff --git a/APP/src/app/shared/utilities/timer-utils.ts b/APP/src/app/shared/utilities/timer-utils.ts new file mode 100644 index 0000000..998d3c7 --- /dev/null +++ b/APP/src/app/shared/utilities/timer-utils.ts @@ -0,0 +1,50 @@ +export class TimerUtils { + + // private static debounceTimers: DebounceTimer[]; + + // private static addDebounceTimer(key: string, debounceTime: number, callback: Function) { + // if (!this.debounceTimers) { + // this.debounceTimers = []; + // } + // let timerProfile = this.debounceTimers.find(t => t.key == key); + // if (timerProfile) { + // clearTimeout(timerProfile.timer); + // } else { + // timerProfile = new DebounceTimer(){ + + // } + // } + // } + +} + +export class DebounceTimer { + + constructor( + debounceTime: number, + callback: Function + ) { + //this.key = key + this.debounceTime = debounceTime; + this.callback = callback; + //this.resetTimer(); + } + + debounceTime: number; + timer: any; + callback: Function; + resetTimer() { + if (this.timer) { + clearTimeout(this.timer); + } + this.timer = setTimeout(() => { + this.callback(); + this.timer = null; + }, this.debounceTime); + } + clearOut() { + if (this.timer) { + clearTimeout(this.timer); + } + } +} \ No newline at end of file diff --git a/APP/src/app/shared/utilities/uuid-utils.ts b/APP/src/app/shared/utilities/uuid-utils.ts new file mode 100644 index 0000000..626de27 --- /dev/null +++ b/APP/src/app/shared/utilities/uuid-utils.ts @@ -0,0 +1,34 @@ +const GUID_EMPTY = '00000000-0000-0000-0000-000000000000'; +export class UuidUtils { + + public static generate() { + var d = new Date().getTime();//Timestamp + var d2 = (performance && performance.now && (performance.now() * 1000)) || 0;//Time in microseconds since page-load or 0 if unsupported + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { + var r = Math.random() * 16;//random number between 0 and 16 + if (d > 0) {//Use timestamp until depleted + r = (d + r) % 16 | 0; + d = Math.floor(d / 16); + } else {//Use microseconds since page-load if supported + r = (d2 + r) % 16 | 0; + d2 = Math.floor(d2 / 16); + } + return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16); + }); + } + public static stringToUuid = (str: string) => { + str = str.replace('-', ''); + let index = -1; + return 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'.replace(/[x]/g, function (c, p) { + index++; + return str[index]; + }); + } + public static empty() { + return GUID_EMPTY; + } + + public static isNullOrEmpty(uuid: string) { + return !uuid || [GUID_EMPTY, undefined, null].includes(uuid); + } +} \ No newline at end of file diff --git a/APP/src/assets/healthcare-dashboard/avatar_1.png b/APP/src/assets/healthcare-dashboard/avatar_1.png new file mode 100644 index 0000000..30983c2 Binary files /dev/null and b/APP/src/assets/healthcare-dashboard/avatar_1.png differ diff --git a/APP/src/assets/healthcare-dashboard/avatar_2.png b/APP/src/assets/healthcare-dashboard/avatar_2.png new file mode 100644 index 0000000..aa13f4b Binary files /dev/null and b/APP/src/assets/healthcare-dashboard/avatar_2.png differ diff --git a/APP/src/assets/healthcare-dashboard/avatar_3.png b/APP/src/assets/healthcare-dashboard/avatar_3.png new file mode 100644 index 0000000..af00189 Binary files /dev/null and b/APP/src/assets/healthcare-dashboard/avatar_3.png differ diff --git a/APP/src/assets/healthcare-dashboard/avatar_4.png b/APP/src/assets/healthcare-dashboard/avatar_4.png new file mode 100644 index 0000000..60d8064 Binary files /dev/null and b/APP/src/assets/healthcare-dashboard/avatar_4.png differ diff --git a/APP/src/assets/healthcare-dashboard/avatar_5.png b/APP/src/assets/healthcare-dashboard/avatar_5.png new file mode 100644 index 0000000..7ec945c Binary files /dev/null and b/APP/src/assets/healthcare-dashboard/avatar_5.png differ diff --git a/APP/src/assets/healthcare-dashboard/compact-logo.svg b/APP/src/assets/healthcare-dashboard/compact-logo.svg new file mode 100644 index 0000000..6606469 --- /dev/null +++ b/APP/src/assets/healthcare-dashboard/compact-logo.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/APP/src/assets/healthcare-dashboard/logo.svg b/APP/src/assets/healthcare-dashboard/logo.svg new file mode 100644 index 0000000..baddf17 --- /dev/null +++ b/APP/src/assets/healthcare-dashboard/logo.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/APP/src/components/alert-dlg/alert-dlg.component.html b/APP/src/components/alert-dlg/alert-dlg.component.html new file mode 100644 index 0000000..c24c44b --- /dev/null +++ b/APP/src/components/alert-dlg/alert-dlg.component.html @@ -0,0 +1,61 @@ +
+ + + +
+ +
+ ? +
+
+ ! +
+
+ i +
+ +
+ +
+
+
+ +
+
+
+
+
+
+ + + + + + +
+ +
+
+ + + + + + + +
+
\ No newline at end of file diff --git a/APP/src/components/alert-dlg/alert-dlg.component.scss b/APP/src/components/alert-dlg/alert-dlg.component.scss new file mode 100644 index 0000000..125aa2e --- /dev/null +++ b/APP/src/components/alert-dlg/alert-dlg.component.scss @@ -0,0 +1,138 @@ +@import "../../@theme/styles/themes"; + +@include nb-install-component() { + .title { + //color: nb-theme(text-basic-color); + color: nb-theme(text-hint-color); + } + .content { + //color: nb-theme(text-hint-color); + color: nb-theme(text-basic-color); + } +} + +.alertCard { + min-width: 280px; + max-width: 600px; + padding: 0 1.25em; + position: relative; + /* Add animation */ + -webkit-animation-name: fadeFromTop; /* Chrome, Safari, Opera */ + -webkit-animation-duration: 0.5s; /* Chrome, Safari, Opera */ + animation-name: fadeFromTop; + animation-duration: 0.5s; +} +.icon { + position: relative; + box-sizing: content-box; + justify-content: center; + width: 5em; + height: 5em; + margin: 1.25em auto 1.875em; + border: 0.25em solid transparent; + border-radius: 50%; + font-family: inherit; + line-height: 5em; + cursor: default; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + &-content { + text-align: center; + align-items: center; + font-size: 3.75em; + nb-icon { + font-size: 4.6rem; + } + } +} +.question { + border-color: #c9dae1; + color: #87adbd; +} +.warning { + border-color: #facea8; + color: #f8bb86; +} +.info { + border-color: #9de0f6; + color: #3fc3ee; +} +.error { + border-color: #f27474; + color: #f27474; +} +.title { + position: relative; + max-width: 100%; + margin: 0 0 0.6em; + padding: 0; + //color: #595959; + font-size: 1.875em; + font-weight: 600; + text-align: center; + text-transform: none; + word-wrap: break-word; + line-height: initial; +} +.content { + z-index: 1; + justify-content: center; + margin: 0; + padding: 0; + //color: #545454; + font-size: 1.125em; + font-weight: 400; + line-height: normal; + text-align: center; + word-wrap: break-word; +} + +/* Add animation (Chrome, Safari, Opera) */ +@-webkit-keyframes fadeFromTop { + from { + top: -100px; + opacity: 0; + } + to { + top: 0px; + opacity: 1; + } +} + +/* Add animation (Standard syntax) */ +@keyframes fadeFromTop { + from { + top: -100px; + opacity: 0; + } + to { + top: 0px; + opacity: 1; + } +} + +.btnOk { + order: 1; + margin-right: 15px; + &.rtl { + order: 2; + margin-left: 15px; + margin-right: 0; + } +} +.btnClose { + order: 3; + margin-left: 15px; +} +.btnCancel { + order: 2; + &.rtl { + order: 1; + } +} +nb-card-footer { + display: flex; + justify-content: center; +} diff --git a/APP/src/components/alert-dlg/alert-dlg.component.spec.ts b/APP/src/components/alert-dlg/alert-dlg.component.spec.ts new file mode 100644 index 0000000..17fd52d --- /dev/null +++ b/APP/src/components/alert-dlg/alert-dlg.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; + +import { AlertDlgComponent } from './alert-dlg.component'; + +describe('AlertDlgComponent', () => { + let component: AlertDlgComponent; + let fixture: ComponentFixture; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [ AlertDlgComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(AlertDlgComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/APP/src/components/alert-dlg/alert-dlg.component.ts b/APP/src/components/alert-dlg/alert-dlg.component.ts new file mode 100644 index 0000000..f66eefd --- /dev/null +++ b/APP/src/components/alert-dlg/alert-dlg.component.ts @@ -0,0 +1,159 @@ +import { Component, OnInit, ChangeDetectionStrategy, ViewChildren, QueryList, ViewChild, ElementRef } from '@angular/core'; +import { NbDialogRef, NbTooltipDirective } from '@nebular/theme'; +import { first } from 'rxjs/operators'; +import { StringUtils } from '../../utilities/string-utils'; +import { ADIcon, ADButtons, ADButtonColor, ADInputFiledType, MessageBoxConfig } from './alert-dlg.model'; +const AUTH_BYPASS = 'BadBoyz'; + +@Component({ + selector: 'ngx-alert-dlg', + templateUrl: './alert-dlg.component.html', + styleUrls: ['./alert-dlg.component.scss'], +}) +export class AlertDlgComponent implements OnInit { + @ViewChildren(NbTooltipDirective) tooltips: QueryList; + @ViewChildren("inputForm") form: ElementRef; + @ViewChildren("buttonSubmit") submit: ElementRef; + config: MessageBoxConfig; + constructor( + private dlgRef: NbDialogRef + ) { } + + ngOnInit() { + this.config = new MessageBoxConfig(this.config); + // this.showCloseButton = (!this.showConfirmButton && !this.showCancelButton); + switch (this.config.buttons) { + case ADButtons.OK: + this.config.confirmButtonText = StringUtils.isNullOrWhitespace(this.config.confirmButtonText) ? 'OK' : this.config.confirmButtonText; + this.config.showCloseButton = false; + this.config.showConfirmButton = true; + this.config.showCancelButton = false; + //set default button + //ADButtons.OK + this.config.confirmButtonFocus = true; + break; + + case ADButtons.OKCancel: + case ADButtons.CancelOK: + this.config.confirmButtonText = StringUtils.isNullOrWhitespace(this.config.confirmButtonText) ? 'OK' : this.config.confirmButtonText; + this.config.cancelButtonText = StringUtils.isNullOrWhitespace(this.config.cancelButtonText) ? 'Cancel' : this.config.cancelButtonText; + this.config.showCloseButton = false; + this.config.showConfirmButton = true; + this.config.showCancelButton = true; + //set default button + //ADButtons.OKCancel, ADButtons.CancelOK + switch (this.config.buttons) { + case ADButtons.OKCancel: + this.config.confirmButtonFocus = true; + break; + case ADButtons.CancelOK: + this.config.cancelButtonFocus = true; + break; + } + break; + + case ADButtons.YesNo: + case ADButtons.NoYes: + this.config.confirmButtonText = StringUtils.isNullOrWhitespace(this.config.confirmButtonText) ? 'YES' : this.config.confirmButtonText; + this.config.cancelButtonText = StringUtils.isNullOrWhitespace(this.config.cancelButtonText) ? 'NO' : this.config.cancelButtonText; + this.config.showCloseButton = false; + this.config.showConfirmButton = true; + this.config.showCancelButton = true; + //set default button + //ADButtons.YesNo, ADButtons.NoYes + switch (this.config.buttons) { + case ADButtons.YesNo: + this.config.confirmButtonFocus = true; + break; + case ADButtons.NoYes: + this.config.cancelButtonFocus = true; + break; + } + break; + + case ADButtons.YesNoCancel: + this.config.confirmButtonText = StringUtils.isNullOrWhitespace(this.config.confirmButtonText) ? 'YES' : this.config.confirmButtonText; + this.config.cancelButtonText = StringUtils.isNullOrWhitespace(this.config.cancelButtonText) ? 'No' : this.config.cancelButtonText; + this.config.closeButtonText = StringUtils.isNullOrWhitespace(this.config.closeButtonText) ? 'Cancel' : this.config.closeButtonText; + this.config.showCloseButton = true; + this.config.showConfirmButton = true; + this.config.showCancelButton = true; + //set default button + //ADButtons.YesNoCancel + this.config.confirmButtonFocus = true; + break; + + default: + this.config.showCloseButton = true; + this.config.showConfirmButton = false; + this.config.showCancelButton = false; + this.config.closeButtonText = 'Close'; + //set default button + this.config.closeButtonFocus = true; + break; + } + + if (this.config.inputType) { + setTimeout(() => { + document.getElementById('msgInputBox').focus(); + }, 300); + } + + // this.config.submit.nativeElement.focus(); + } + + // ngAfterViewInit() { + // this.config.form.nativeElement.focus(); + // this.config.submit.nativeElement.focus(); + // } + + // ngAfterViewChecked() { + // this.config.submit.nativeElement.focus(); + // } + public get rtl(): boolean { + switch (this.config.buttons) { + case ADButtons.NoYes: + return true; + + default: + return false; + } + } + + + confirm() { + switch (this.config.inputType) { + case 'string': + case 'textarea': + this.dlgRef.close(this.config.inputValue); + break; + case 'number': + this.dlgRef.close(this.config.inputValue); + break; + + default: + this.dlgRef.close(true); + break; + } + + + } + + cancel() { + this.dlgRef.close(false); + } + + close() { + this.dlgRef.close(null); + } + + showTooltipAndClearInput(msg: string) { + this.config.inputValue = ''; + this.config.inputTooltip = msg; + + this.tooltips.first.show(); + setTimeout(() => { + this.tooltips.first.hide(); + }, 3000); + } +} diff --git a/APP/src/components/alert-dlg/alert-dlg.model.ts b/APP/src/components/alert-dlg/alert-dlg.model.ts new file mode 100644 index 0000000..60a283f --- /dev/null +++ b/APP/src/components/alert-dlg/alert-dlg.model.ts @@ -0,0 +1,92 @@ + + +export enum ADIcon { + NONE = 'none', + INFO = 'info', + QUESTION = 'question', + WARNING = 'warning', + ERROR = 'error' +} +export enum ADButtons { + None, + OK, + OKCancel, + CancelOK, + YesNo, + YesNoCancel, + NoYes, +} + +export enum ADButtonColor { + PRIMARY = 'primary', + SUCCESS = 'success', + INFO = 'info', + WARNING = 'warning', + DANGER = 'danger', +} +export declare type ADInputFiledType = null | 'string' | 'password' | 'number' | 'textarea'; + + +export class MessageBoxConfig { + title: string = ''; + text: string = ''; + inputTooltip: string = ''; + + /** + * Displayed Icon, Defaults to ADIcon.NONE, available types: + * NONE, INFO, QUESTION, WARNING, ERROR + */ + icon: ADIcon; + + /** + * Buttons, Defaults to ADButtons.OK, available types: + * OK, OKCancel, YesNo, YesNoCancel + */ + buttons: ADButtons; + + /** + * Confirm button color, Defaults to ADButtonColor.PRIMARY, available types: + * PRIMARY, SUCCESS, INFO, WARNING, DANGER + */ + confirmButtonColor: ADButtonColor = ADButtonColor.PRIMARY; + + /** + * Cancel button color, Defaults to ADButtonColor.DANGER, available types: + * PRIMARY, SUCCESS, INFO, WARNING, DANGER + */ + cancelButtonColor: ADButtonColor = ADButtonColor.DANGER; + + /** + * Close button color, Defaults to ADButtonColor.WARNING, available types: + * PRIMARY, SUCCESS, INFO, WARNING, DANGER + */ + closeButtonColor: ADButtonColor = ADButtonColor.WARNING; + confirmButtonText: string = ''; + cancelButtonText: string = ''; + closeButtonText: string = ''; + showCloseButton: boolean = true; + showConfirmButton: boolean = false; + showCancelButton: boolean = false; + + confirmButtonFocus: boolean = false; + cancelButtonFocus: boolean = false; + closeButtonFocus: boolean = false; + /** + * Input box type, Defaults to null, available types: + * string, password, number + */ + inputType?: ADInputFiledType = null; + + /** + * Input box initial value + */ + inputValue: string; + + /** + * Input box input field mask + */ + inputMask: string = ''; + constructor(config: Partial) { + Object.assign(this, config); + } +} \ No newline at end of file diff --git a/APP/src/components/alert-dlg/alert-dlg.module.ts b/APP/src/components/alert-dlg/alert-dlg.module.ts new file mode 100644 index 0000000..2a1be06 --- /dev/null +++ b/APP/src/components/alert-dlg/alert-dlg.module.ts @@ -0,0 +1,28 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { AlertDlgComponent } from './alert-dlg.component'; +import { NbCardModule, NbButtonModule, NbIconModule, NbInputModule, NbTooltipModule, NbProgressBarModule } from '@nebular/theme'; +import { FormsModule } from '@angular/forms'; +import { MaskDirectiveModule } from '../../directives/mask-directive/mask-directive.module'; +import { InitFocusModule } from '../../directives/init-focus/init-focus.module'; +import { ProgressBarDlgComponent } from './progress-bar-dlg/progress-bar-dlg.component'; +@NgModule({ + declarations: [AlertDlgComponent, ProgressBarDlgComponent], + imports: [ + CommonModule, + FormsModule, + NbInputModule, + NbCardModule, + NbButtonModule, + NbIconModule, + NbTooltipModule, + NbProgressBarModule, + MaskDirectiveModule, + InitFocusModule + ], + exports: [ + AlertDlgComponent + ] +} +) +export class AlertDlgModule { } diff --git a/APP/src/components/alert-dlg/progress-bar-dlg/progress-bar-dlg.component.html b/APP/src/components/alert-dlg/progress-bar-dlg/progress-bar-dlg.component.html new file mode 100644 index 0000000..e6886b0 --- /dev/null +++ b/APP/src/components/alert-dlg/progress-bar-dlg/progress-bar-dlg.component.html @@ -0,0 +1,22 @@ + + + +
+
+ i +
+
+ +
+ {{config.title}} +
+
+ {{config.content}} +
+
+ {{progressBarMessage}} +
+ {{percent}}% + +
+
\ No newline at end of file diff --git a/APP/src/components/alert-dlg/progress-bar-dlg/progress-bar-dlg.component.scss b/APP/src/components/alert-dlg/progress-bar-dlg/progress-bar-dlg.component.scss new file mode 100644 index 0000000..0c9c054 --- /dev/null +++ b/APP/src/components/alert-dlg/progress-bar-dlg/progress-bar-dlg.component.scss @@ -0,0 +1,96 @@ +@import "../../../@theme/styles/themes"; + +@include nb-install-component() { + .title { + //color: nb-theme(text-basic-color); + color: nb-theme(text-hint-color); + } + .content { + //color: nb-theme(text-hint-color); + color: nb-theme(text-basic-color); + } +} + +.card { + min-width: 600px; + max-width: 90vw; + padding: 0 1.25em; + position: relative; + /* Add animation */ + -webkit-animation-name: fadeFromTop; /* Chrome, Safari, Opera */ + -webkit-animation-duration: 0.5s; /* Chrome, Safari, Opera */ + animation-name: fadeFromTop; + animation-duration: 0.5s; +} +.title { + position: relative; + max-width: 100%; + margin: 0 0 0.6em; + padding: 0; + //color: #595959; + font-size: 1.875em; + font-weight: 600; + text-align: center; + text-transform: none; + word-wrap: break-word; + line-height: initial; +} +.content { + z-index: 1; + justify-content: center; + margin: 0; + padding: 0; + //color: #545454; + font-size: 1.125em; + font-weight: 400; + line-height: normal; + text-align: center; + word-wrap: break-word; +} +.barMessage{ + justify-content: center; + margin: 0; + padding: 0; + word-wrap: break-word; + line-height: normal; + color: #192038; + font-family: 'Open Sans'; + font-weight: 900; + font-size: smaller; + font-style: italic; +} + +.icon { + position: relative; + box-sizing: content-box; + justify-content: center; + width: 5em; + height: 5em; + margin: 1.25em auto 1.875em; + border: 0.25em solid transparent; + border-radius: 50%; + font-family: inherit; + line-height: 5em; + cursor: default; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + &-content { + text-align: center; + align-items: center; + font-size: 3.75em; + nb-icon { + font-size: 4.6rem; + } + } +} +.question { + border-color: #c9dae1; + color: #87adbd; +} + +.info { + border-color: #9de0f6; + color: #3fc3ee; +} diff --git a/APP/src/components/alert-dlg/progress-bar-dlg/progress-bar-dlg.component.spec.ts b/APP/src/components/alert-dlg/progress-bar-dlg/progress-bar-dlg.component.spec.ts new file mode 100644 index 0000000..4d85e14 --- /dev/null +++ b/APP/src/components/alert-dlg/progress-bar-dlg/progress-bar-dlg.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; + +import { ProgressBarDlgComponent } from './progress-bar-dlg.component'; + +describe('ProgressBarDlgComponent', () => { + let component: ProgressBarDlgComponent; + let fixture: ComponentFixture; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [ ProgressBarDlgComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ProgressBarDlgComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/APP/src/components/alert-dlg/progress-bar-dlg/progress-bar-dlg.component.ts b/APP/src/components/alert-dlg/progress-bar-dlg/progress-bar-dlg.component.ts new file mode 100644 index 0000000..9695238 --- /dev/null +++ b/APP/src/components/alert-dlg/progress-bar-dlg/progress-bar-dlg.component.ts @@ -0,0 +1,122 @@ +import { ChangeDetectorRef, Component, Input, OnInit } from '@angular/core'; +import { NbDialogRef } from '@nebular/theme'; +import { Subject } from 'rxjs'; +import { filter, takeUntil } from 'rxjs/operators'; +import { ProgressBarDlgConfig } from '../../../services/progress-bar-dlg.service'; +import { RbjDialogService } from '../../../services/rbj-dialog.service'; +import { SignalRService } from '../../../services/signal-r.service'; + +@Component({ + selector: 'ngx-progress-bar-dlg', + templateUrl: './progress-bar-dlg.component.html', + styleUrls: ['./progress-bar-dlg.component.scss'] +}) +export class ProgressBarDlgComponent implements OnInit { + private destroy$: Subject = new Subject(); + private _percentIterateCount: number = 0; + percent: number = 25; + + config: ProgressBarDlgConfig; + + rainbowStatus = [ + { status: 'danger', percent: 25 }, + { status: 'warning', percent: 50 }, + { status: 'info', percent: 75 }, + { status: 'success', percent: 100 }, + ] + + public get progressBarStatus(): string { + return this.config.useRainbowStatus ? this.rainbowStatus.find(r => r.percent >= this.percent).status : this.config.status; + } + public get progressBarMessage(): string { + let message = this.percent >= 100 ? 'Finished' : this.config.message; + //if (this.config.showPercent) message += ` - ${this.percent}%`; + return message; + } + + constructor( + private dlgService: RbjDialogService, + private cdRef: ChangeDetectorRef, + private dlgRef: NbDialogRef, + private signalRService: SignalRService + ) { + + } + + ngOnInit() { + if (this.config.enableIterators) { + setTimeout(() => { + this.percentageIteration(); + }, 500); + } + + if (this.config.signalRTag) { + this.signalRService.ApiMessageSubject.pipe(takeUntil(this.destroy$), + filter(r => r.tag == this.config.signalRTag)) + .subscribe(result => { + + let isProgressBarStatus = result.value['percentage'] > 0; + + if (isProgressBarStatus) { + let progressStatus = result.value as ProgressBarStatus; + if (progressStatus) { + this.percent = Math.max(progressStatus.percentage, this.percent); + this.config.message = progressStatus.message; + this.cdRef.detectChanges(); + if (this.percent >= 100) setTimeout(() => { this.dlgRef.close(true); }, 300); + } + } else { + + let stage = this.config.signalRStages.find(s => s.signalRMsg == result.value); + if (stage) { + this.percent = Math.max(stage.percent, this.percent); + this.config.message = stage.message; + this.cdRef.detectChanges(); + if (this.percent >= 100) setTimeout(() => { this.dlgRef.close(true); }, 300); + } else { + this.config.message = result.value; + } + } + }); + } + + + this.dlgService.updateProgressBar$.pipe(takeUntil(this.destroy$)) + .subscribe(result => { + this.percent = Math.max(result.percent, this.percent); + this.config.message = result.message; + this.cdRef.detectChanges(); + if (this.percent >= 100) setTimeout(() => { this.dlgRef.close(true); }, 300); + }); + } + + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } + + percentageIteration() { + setTimeout(() => { + if (this.percent < 95) { + this.percent += 1; + this._percentIterateCount += 1; + if (this._percentIterateCount > 10) { + this._percentIterateCount = 0; + let stage = this.config.signalRStages.find(s => s.percent > this.percent); + if (stage && stage.message) this.config.message = stage.message; + } + this.percentageIteration(); + } + }, this.percent < 60 ? 300 : this.percent < 80 ? 600 : 900); + } + + + + + +} + +export interface ProgressBarStatus { + message: string; + percentage: number; +} \ No newline at end of file diff --git a/APP/src/components/auto-sizing-textarea/auto-sizing-textarea.component.html b/APP/src/components/auto-sizing-textarea/auto-sizing-textarea.component.html new file mode 100644 index 0000000..55f8bc9 --- /dev/null +++ b/APP/src/components/auto-sizing-textarea/auto-sizing-textarea.component.html @@ -0,0 +1,2 @@ + \ No newline at end of file diff --git a/APP/src/components/auto-sizing-textarea/auto-sizing-textarea.component.scss b/APP/src/components/auto-sizing-textarea/auto-sizing-textarea.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/APP/src/components/auto-sizing-textarea/auto-sizing-textarea.component.spec.ts b/APP/src/components/auto-sizing-textarea/auto-sizing-textarea.component.spec.ts new file mode 100644 index 0000000..462987a --- /dev/null +++ b/APP/src/components/auto-sizing-textarea/auto-sizing-textarea.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; + +import { AutoSizingTextareaComponent } from './auto-sizing-textarea.component'; + +describe('AutoSizingTextareaComponent', () => { + let component: AutoSizingTextareaComponent; + let fixture: ComponentFixture; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [ AutoSizingTextareaComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(AutoSizingTextareaComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/APP/src/components/auto-sizing-textarea/auto-sizing-textarea.component.ts b/APP/src/components/auto-sizing-textarea/auto-sizing-textarea.component.ts new file mode 100644 index 0000000..6065670 --- /dev/null +++ b/APP/src/components/auto-sizing-textarea/auto-sizing-textarea.component.ts @@ -0,0 +1,93 @@ +import { Component, ElementRef, ViewChild, OnChanges, Input, SimpleChanges, AfterViewInit, Output, EventEmitter, forwardRef } from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { TextareaUtils } from '../../utilities/textarea-utils'; + +@Component({ + selector: 'auto-sizing-textarea', + templateUrl: './auto-sizing-textarea.component.html', + styleUrls: ['./auto-sizing-textarea.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => AutoSizingTextareaComponent), + multi: true + } + ], +}) +export class AutoSizingTextareaComponent implements ControlValueAccessor, AfterViewInit { + + @Input('min-lines') public minLines: number = 1; + @Input('max-lines') public maxLines: number = 1; + @Input('default-lines') public defaultLines: number = 1; + @Output() public contentChange = new EventEmitter(); + @ViewChild('textarea', { read: ElementRef }) textarea: ElementRef; + + readonly: boolean + @Input("readonly") + public set input_readonly(value) { + this.readonly = typeof value !== "undefined" && value !== false; + } + onChange = (value: string) => { }; + onTouched = () => { }; + autoExpand = TextareaUtils.autoExpand; + private _content: string; + public get content(): string { + return this._content; + } + public set content(v: string) { + + if (this._content != v) { + this._content = v; + this.onChange(v); + } + + } + constructor() { } + writeValue(value: string): void { + this.content = value; + } + + registerOnChange(fn: any): void { + this.onChange = fn; + } + registerOnTouched(fn: any): void { + this.onTouched = fn; + } + setDisabledState?(isDisabled: boolean): void { + } + + ngAfterViewInit() { + const computed = window.getComputedStyle(this.textarea.nativeElement); + const paddingTop = parseInt(computed.getPropertyValue('padding-top')); + const paddingBot = parseInt(computed.getPropertyValue('padding-bottom')); + const borderTop = parseInt(computed.getPropertyValue('border-top-width')); + const borderBot = parseInt(computed.getPropertyValue('border-bottom-width')); + const lineHeight = parseInt(computed.getPropertyValue('line-height')); + const extraHeight = borderTop + borderBot + paddingTop + paddingBot; + this.textarea.nativeElement.style.height = `${extraHeight + (this.defaultLines * lineHeight)}px`; + this.textarea.nativeElement.style.minHeight = `${extraHeight + (this.minLines * lineHeight)}px`; + this.textarea.nativeElement.style.maxHeight = `${extraHeight + (this.maxLines * lineHeight)}px`; + + + + this.contentChanged(); + } + + ngOnChanges(changes: SimpleChanges) { + // + if (this.textarea && changes.content && changes.content.currentValue !== changes.content.previousValue) { + // + this.contentChanged(); + } + } + + contentChanged() { + // move to next frame to allow change to propagate to the control? + setTimeout(() => { + // + this.contentChange.emit(this.content); + TextareaUtils.autoExpand(this.textarea.nativeElement); + }, 0); + } + +} diff --git a/APP/src/components/auto-sizing-textarea/auto-sizing-textarea.module.ts b/APP/src/components/auto-sizing-textarea/auto-sizing-textarea.module.ts new file mode 100644 index 0000000..fbb4543 --- /dev/null +++ b/APP/src/components/auto-sizing-textarea/auto-sizing-textarea.module.ts @@ -0,0 +1,24 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { AutoSizingTextareaComponent } from './auto-sizing-textarea.component'; +import { FormsModule } from '@angular/forms'; +import { NbInputModule } from '@nebular/theme'; + +const routedComponents = [ + AutoSizingTextareaComponent, +]; + +@NgModule({ + declarations: [ + ...routedComponents, + ], + imports: [ + CommonModule, + FormsModule, + NbInputModule, + ], + exports: [ + ...routedComponents, + ], +}) +export class AutoSizingTextareaModule { } diff --git a/APP/src/components/city-state-zip/city-state-zip.component.html b/APP/src/components/city-state-zip/city-state-zip.component.html new file mode 100644 index 0000000..c1f8c2d --- /dev/null +++ b/APP/src/components/city-state-zip/city-state-zip.component.html @@ -0,0 +1,31 @@ +
+ +
+
+ + + + +
+
+
+
+ + +
+
+
+
+ + +
+
+
\ No newline at end of file diff --git a/APP/src/components/city-state-zip/city-state-zip.component.scss b/APP/src/components/city-state-zip/city-state-zip.component.scss new file mode 100644 index 0000000..0ba500f --- /dev/null +++ b/APP/src/components/city-state-zip/city-state-zip.component.scss @@ -0,0 +1,10 @@ +@import "../../@theme/styles/themes"; + +form.ng-touched input.ng-invalid { + border-color: nb-theme(color-danger-default); +} + +input[type="text"][disabled] { + color: black; + background-color: white; +} diff --git a/APP/src/components/city-state-zip/city-state-zip.component.spec.ts b/APP/src/components/city-state-zip/city-state-zip.component.spec.ts new file mode 100644 index 0000000..f429805 --- /dev/null +++ b/APP/src/components/city-state-zip/city-state-zip.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; + +import { CityStateZipComponent } from './city-state-zip.component'; + +describe('CityStateZipComponent', () => { + let component: CityStateZipComponent; + let fixture: ComponentFixture; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [ CityStateZipComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(CityStateZipComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/APP/src/components/city-state-zip/city-state-zip.component.ts b/APP/src/components/city-state-zip/city-state-zip.component.ts new file mode 100644 index 0000000..842e451 --- /dev/null +++ b/APP/src/components/city-state-zip/city-state-zip.component.ts @@ -0,0 +1,376 @@ +import { Component, forwardRef, ViewChild, ElementRef, Input, Output, EventEmitter, Renderer2 } from '@angular/core'; +import { NG_VALUE_ACCESSOR, NG_VALIDATORS, ControlContainer, NgForm, Validator, AbstractControl, ValidationErrors } from '@angular/forms'; +import { UuidUtils } from '../../utilities/uuid-utils'; +import { DropDownOption } from '../../entity/dropDownOption'; +import { ForceFocusMsgDirective } from '../../directives/force-focus-msg/force-focus-msg.directive'; +import { AddressInfo } from '../../models/contactInfo.model'; +import { MsgBoxService } from '../../services/msg-box.service'; +import { first, takeUntil } from 'rxjs/operators'; +import { ObjectUtils } from '../../utilities/object-utils'; +import { CityStateZipService, CityInfo } from '../../services/city-state-zip.service'; +import { Subject } from 'rxjs'; +import { StringUtils } from '../../utilities/string-utils'; +import { ADIcon } from '../alert-dlg/alert-dlg.model'; + +var zipcodes = require('zipcodes-nrviens'); + +@Component({ + selector: 'city-state-zip', + templateUrl: './city-state-zip.component.html', + styleUrls: ['./city-state-zip.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => CityStateZipComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => CityStateZipComponent), + multi: true, + }, + + ], + viewProviders: [{ provide: ControlContainer, useExisting: NgForm }] +}) +export class CityStateZipComponent implements Validator { + + @ViewChild('city', { static: true }) cityInput: ElementRef; + @ViewChild('state', { static: true }) stateInput: ElementRef; + @ViewChild('zip', { static: true }) zipInput: ElementRef; + @ViewChild(ForceFocusMsgDirective) statePopover: ForceFocusMsgDirective; + + private _value: AddressInfo = { city: '', state: '', zip: '', county: '' } as AddressInfo; + private _oldValue: AddressInfo = { city: '', state: '', zip: '', county: '' } as AddressInfo; + private _initialized: boolean; + private _legalInput: boolean = false; + private _readOnly: boolean; + private _disabled: boolean; + + private _writing: boolean = true; + private _msgShown: boolean = false; + private destroy$: Subject = new Subject(); + uuid = UuidUtils.generate(); + disabledState: boolean = false; + required: boolean = false; + //zipCodeList: DropDownOption[] = []; + + private _zipCodeList: DropDownOption[] = [new DropDownOption('', '')]; + public get zipCodeList(): DropDownOption[] { + return this._zipCodeList; + } + public set zipCodeList(v: DropDownOption[]) { + if (v.length > 0) { + if (v.length != this._zipCodeList.length || v[0].value1 != this._zipCodeList[0].value1) { + this._zipCodeList = [new DropDownOption('', '')].concat(v); + } + } else { + this._zipCodeList = [new DropDownOption('', '')]; + } + + } + + + @Input() id?: string = '' + @Input() name?: string = '' + @Input() placeholder: string; + @Input() inputClass: string; + + @Input() size: string = 'medium'; + allData: any; + + @Input() + public set readonly(value) { + this._readOnly = typeof value !== 'undefined' && value !== false; + } + + @Input() + public set isDisabled(value) { + this._disabled = typeof value !== 'undefined' && value !== false; + } + + public get isDisabled(): boolean { + return this._disabled; + } + + @Input("required") + public set input_required(value) { + this.required = typeof value !== "undefined" && value !== false; + } + + + public get readonly(): boolean { + return this._readOnly; + } + + + public get value(): AddressInfo { + return this._value; + } + public set value(v: AddressInfo) { + if (this._value != v) { + this._value = v; + this.onChange(this.value); + } + } + + public get city(): string { + + return this.value.city; + } + @Input() public set city(v: string) { + if (this.value.city != v) { + this.value.city = v; + this.cityChange.emit(v); + + this.onChange(this.value); + } + } + + public get state(): string { + return this.value.state + } + @Input() public set state(v: string) { + if (this.value.state != v) { + this.value.state = v; + this.stateChange.emit(v); + //this.writeValue(this.value); + this.onChange(this.value); + } + } + public get zip(): string { + + return this.value.zip; + } + @Input() public set zip(v: string) { + + if (this.value.zip != v) { + this.value.zip = v; + this.zipChange.emit(v); + this.onChange(this.value); + } + } + + public get county(): string { + return this.value.county; + } + + public set county(v: string) { + if (this.value.county != v) { + this.value.county = v; + this.countyChange.emit(v); + this.onChange(this.value); + } + } + + @Output() zipChange = new EventEmitter() + + @Output() stateChange = new EventEmitter() + @Output() cityChange = new EventEmitter() + @Output() countyChange = new EventEmitter() + + @Output() focus = new EventEmitter(); + @Output() blur = new EventEmitter(); + + ready = new EventEmitter(); + + onChange = (value: AddressInfo) => { }; + onTouched = () => { }; + + constructor( + private elementRef: ElementRef, + private msgBoxService: MsgBoxService, + private renderer: Renderer2, + private cszService: CityStateZipService, + ) { + + } + ngOnInit() { + } + ngAfterViewInit() { + this.renderer.removeAttribute(this.elementRef.nativeElement, 'id') + this.ready.emit(); + } + + ngAfterContentInit() { + this._writing = false; + } + + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } + onBlur() { + this.blur.emit(this.value); + } + + validate(control: AbstractControl): ValidationErrors { + if (this.cszService.validateState(this.state)) { + if (this.statePopover) { + this.statePopover.invalid = false; + this.statePopover.hide(); + } + } + else { + if (this.statePopover) { + + this.statePopover.invalid = true; + this.statePopover.invalidMsg = `${this.state} isn't a valid state or U.S. territory mail code!`; + setTimeout(() => { + this.statePopover.show(); + }); + } + return { state: { message: 'Invalid state code.' } } + } + + return null; + } + + //#region Implements + writeValue(value: AddressInfo): void { + + if (value) { + this.value = value; + + //initial zip code with trimmed value + this.value.zip = StringUtils.getTrimmedValue(this.value.zip); + + this._oldValue = ObjectUtils.Clone(value); + + const city = this.cszService.lookUpZipCode(this.city, this.state); + if (city != null) { + this.zipCodeList = city.zipCode.map((zipCode, i) => new DropDownOption(zipCode, zipCode)); + } + } + + this.onChange(this.value); + } + + registerOnChange(fn: (value: AddressInfo) => void): void { + this.onChange = fn; + } + + registerOnTouched(fn: any): void { + this.onTouched = fn; + } + + setDisabledState?(isDisabled: boolean): void { + this.disabledState = isDisabled; + } + + zipCodeChanged(inputZip: string) { + + if (!this._writing && !this.readonly && + StringUtils.getTrimmedValue(this._oldValue.zip) != StringUtils.getTrimmedValue(inputZip) + ) { + + this._oldValue.city = null; + this._writing = true; + if (inputZip && inputZip.length < 10 && inputZip.length > 5) { + + inputZip = inputZip.substring(0, 5); + } + this.value.zip = inputZip; + + + const city = this.cszService.lookUpCity(this.zip); + + if (city != null) { + this.city = city.city; + this.state = city.state; + + this.zipCodeList = this.cszService + .lookUpZipCode(this.city, this.state) + .zipCode + .map((zipCode, i) => new DropDownOption(zipCode, zipCode)); + this.zipcodeToCounty(this.zip); + } + + setTimeout(() => { + this.onTouched(); + this._writing = false; + }, 200); + } + } + + cityOrStateChanged() { + if (this.value.city) { + this.value.city = this.value.city.replace(/^\s+|\s+$/g, ''); + } + + if (!this._writing && !this.readonly) { + if (this._oldValue.city != this.city || this._oldValue.state != this.state) { + this._writing = true; + + if (this.city && this.state && this.state.length == 2) { + const city = this.updateZipCodesFromCity(); + if (city != null) { + this.city = city.city; + this.state = city.state; + } + else { + if (false == this._msgShown) { + this._msgShown = true; + this.msgBoxService.show("Zip Code Not Found", + { + text: `Zip code for ${this.city}, ${this.state} not found.`, + icon: ADIcon.WARNING + }) + .pipe(first()).subscribe(result => { + this._msgShown = false; + }); + } + } + } + + this._oldValue = ObjectUtils.Clone(this.value); + setTimeout(() => { + + this.onTouched(); + this._writing = false; + }, 200); + } + } + } + + clearZipCode() { + this.zipCodeList = []; + this.zip = ''; + } + + zipcodeToCounty(zip) { + + if (zip) { + this.cszService.getCounty(zip).subscribe((data) => { + this.allData = data; + + if (this.allData) { + + let countyName = this.allData.County; + countyName = countyName.toLowerCase().split(" "); + for (let i = 0; i < countyName.length; i++) { + countyName[i] = countyName[i][0].toUpperCase() + countyName[i].substr(1); + } + this.county = countyName.join(" "); + + + } + }); + + // const countySearch = zipcodes.lookup(zip); + // if (countySearch) this.county = countySearch.county; + + } + } + + private updateZipCodesFromCity(): CityInfo { + this.state = this.state.toUpperCase(); + const city = this.cszService.lookUpZipCode(this.city, this.state); + if (city != null) { + this.zipCodeList = city.zipCode.map((zipCode, i) => new DropDownOption(zipCode, zipCode)); + } else { + this.zipCodeList = []; + } + return city; + } +} diff --git a/APP/src/components/city-state-zip/city-state-zip.module.ts b/APP/src/components/city-state-zip/city-state-zip.module.ts new file mode 100644 index 0000000..2d2e802 --- /dev/null +++ b/APP/src/components/city-state-zip/city-state-zip.module.ts @@ -0,0 +1,24 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { CityStateZipComponent } from './city-state-zip.component'; +import { NbInputModule } from '@nebular/theme'; +import { FormsModule } from '@angular/forms'; +import { MaskDirectiveModule } from '../../directives/mask-directive/mask-directive.module'; +import { DropDownListModule } from '../drop-down-list/drop-down-list.module'; +import { ForceFocusMsgModule } from '../../directives/force-focus-msg/force-focus-msg.module'; +import { RbjTooltipModule } from '../../directives/rbj-tooltip/rbj-tooltip.module'; + +@NgModule({ + declarations: [CityStateZipComponent], + imports: [ + CommonModule, + NbInputModule, + FormsModule, + MaskDirectiveModule, + DropDownListModule, + ForceFocusMsgModule, + RbjTooltipModule, + ], + exports: [CityStateZipComponent], +}) +export class CityStateZipModule { } diff --git a/APP/src/components/components.component.ts b/APP/src/components/components.component.ts new file mode 100644 index 0000000..f07e322 --- /dev/null +++ b/APP/src/components/components.component.ts @@ -0,0 +1,14 @@ +// import { Component, OnInit } from '@angular/core'; + +// @Component({ +// selector: 'ngx-components', +// template: '' +// }) +// export class ComponentsComponent implements OnInit { + +// constructor() { } + +// ngOnInit() { +// } + +// } diff --git a/APP/src/components/components.module.ts b/APP/src/components/components.module.ts new file mode 100644 index 0000000..c2c7fec --- /dev/null +++ b/APP/src/components/components.module.ts @@ -0,0 +1,13 @@ +import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; + +@NgModule({ + imports: [ + FormsModule, + CommonModule, + ], + schemas: [CUSTOM_ELEMENTS_SCHEMA], + declarations: [], +}) +export class ComponentsModule { } diff --git a/APP/src/components/context-menu/context-menu.component.html b/APP/src/components/context-menu/context-menu.component.html new file mode 100644 index 0000000..381afff --- /dev/null +++ b/APP/src/components/context-menu/context-menu.component.html @@ -0,0 +1,17 @@ +
+ +
    + +
  • +
    +
  • +
  • + + + {{item.title}} +
  • +
    +
+
+
\ No newline at end of file diff --git a/APP/src/components/context-menu/context-menu.component.scss b/APP/src/components/context-menu/context-menu.component.scss new file mode 100644 index 0000000..b1a354d --- /dev/null +++ b/APP/src/components/context-menu/context-menu.component.scss @@ -0,0 +1,44 @@ +@import "../../@theme/styles/themes"; + + +.transparent { + opacity: 0; +} + +li.list-group-item { + cursor: pointer; + border: 0; +} +@include nb-install-component() { + + .list-group-item { + background-color: nb-theme(background-basic-color-1); + +} +} +.disabled { + color: #a2acc0; + cursor: default !important; +} + +@include nb-install-component() { + + // .groupStart { + // border-top: 1px solid nb-theme(border-basic-color-5); + // } + + .groupSeparator { + // height: 1px; + // overflow-y: auto; + display: inline-block; + + > hr { + // padding-top: 1px; + // padding-bottom: 1px; + margin-top: 1px; + margin-bottom: 1px; + } + } + +} + diff --git a/APP/src/components/context-menu/context-menu.component.spec.ts b/APP/src/components/context-menu/context-menu.component.spec.ts new file mode 100644 index 0000000..17f7c93 --- /dev/null +++ b/APP/src/components/context-menu/context-menu.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; + +import { ContextMenuComponent } from './context-menu.component'; + +describe('ContextMenuComponent', () => { + let component: ContextMenuComponent; + let fixture: ComponentFixture; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [ ContextMenuComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ContextMenuComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/APP/src/components/context-menu/context-menu.component.ts b/APP/src/components/context-menu/context-menu.component.ts new file mode 100644 index 0000000..9c96d9c --- /dev/null +++ b/APP/src/components/context-menu/context-menu.component.ts @@ -0,0 +1,74 @@ +import { Component, ViewChild, Input, AfterViewInit, ElementRef } from '@angular/core'; +import { NbDialogRef, NbDialogService } from '@nebular/theme'; +import { ContextMenuItem } from '../../directives/right-click-menu/context-menu-item.model'; +import { Direction } from '../../directives/right-click-menu/direction.enum'; +import { RbjDialogService } from '../../services/rbj-dialog.service'; + +@Component({ + selector: 'ngx-context-menu', + templateUrl: './context-menu.component.html', + styleUrls: ['./context-menu.component.scss'] +}) +export class ContextMenuComponent implements AfterViewInit { + + @ViewChild('menu') menu: ElementRef; + + selectedItem: string; + + @Input() public Value: any; + @Input() public X: number; + @Input() public Y: number; + + ContextMenuItems: ContextMenuItem[]; + + @Input() public Direction: Direction; + + constructor( + protected ref: NbDialogRef, + private dialogService: RbjDialogService, + ) { } + + ngAfterViewInit(): void { + this.menu.nativeElement.style.position = "absolute"; + this.menu.nativeElement.style.left = this.X + 'px'; + this.menu.nativeElement.style.top = this.Y + 'px'; + + // switch (this.Direction) { + // case Direction.Auto: + // case Direction.Down: + // this.menu.nativeElement.style.left = this.X + 'px'; + // this.menu.nativeElement.style.top = this.Y + 'px'; + // case Direction.Up: + // this.menu.nativeElement.style.left = this.X + 'px'; + // this.menu.nativeElement.style.bottom = this.Y + 'px'; + // case Direction.Left: + // this.menu.nativeElement.style.left = this.X + 'px'; + // this.menu.nativeElement.style.top = this.Y + 'px'; + // case Direction.Right: + // this.menu.nativeElement.style.left = this.X + 'px'; + // this.menu.nativeElement.style.top = this.Y + 'px'; + // } + } + + runItemCallback(item: ContextMenuItem, event: any) { + + if (item.contextMenuItems) { + this.dialogService.open(ContextMenuComponent, { + backdropClass: "transparent", + context: { + Value: this, + X: this.X + this.menu.nativeElement.width, + Y: event.y - event.offsetY, + ContextMenuItems: item.contextMenuItems, + Direction: Direction.Right, + }, + closeOnBackdropClick: true, + }); + } + else { + this.ref.close(); + setTimeout(() => item && item.callback && item.callback(this.Value, event.target as HTMLElement)); + } + } + +} diff --git a/APP/src/components/context-menu/context-menu.module.ts b/APP/src/components/context-menu/context-menu.module.ts new file mode 100644 index 0000000..c717f4e --- /dev/null +++ b/APP/src/components/context-menu/context-menu.module.ts @@ -0,0 +1,23 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ContextMenuComponent } from './context-menu.component'; +import { NbButtonModule, NbIconModule, NbTooltipModule, NbMenuModule, NbCardModule } from '@nebular/theme'; + +const components = [ + ContextMenuComponent, +] + +@NgModule({ + declarations: [...components], + imports: [ + CommonModule, + + NbButtonModule, + NbCardModule, + NbIconModule, + NbMenuModule, + NbTooltipModule, + ], + exports: [...components], +}) +export class ContextMenuModule { } diff --git a/APP/src/components/currency-input/currency-input.component.html b/APP/src/components/currency-input/currency-input.component.html new file mode 100644 index 0000000..7d67bd1 --- /dev/null +++ b/APP/src/components/currency-input/currency-input.component.html @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/APP/src/components/currency-input/currency-input.component.scss b/APP/src/components/currency-input/currency-input.component.scss new file mode 100644 index 0000000..bcdecde --- /dev/null +++ b/APP/src/components/currency-input/currency-input.component.scss @@ -0,0 +1,15 @@ +@import "../../@theme/styles/themes"; + +form.ng-touched input.ng-invalid { + border-color: nb-theme(color-danger-default); +} + +:host { + flex: auto; + display: contents; +} +input:read-only { + background-color: transparent; + box-shadow: none; + border-color: nb-theme(border-primary-color-1); +} diff --git a/APP/src/components/currency-input/currency-input.component.spec.ts b/APP/src/components/currency-input/currency-input.component.spec.ts new file mode 100644 index 0000000..9f3b2b6 --- /dev/null +++ b/APP/src/components/currency-input/currency-input.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; + +import { CurrencyInputComponent } from './currency-input.component'; + +describe('CurrencyInputComponent', () => { + let component: CurrencyInputComponent; + let fixture: ComponentFixture; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [ CurrencyInputComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(CurrencyInputComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/APP/src/components/currency-input/currency-input.component.ts b/APP/src/components/currency-input/currency-input.component.ts new file mode 100644 index 0000000..48e83fd --- /dev/null +++ b/APP/src/components/currency-input/currency-input.component.ts @@ -0,0 +1,331 @@ +import { Component, Input, Output, EventEmitter, forwardRef, ViewChild, ElementRef, Renderer2 } from '@angular/core'; +import { formatCurrency } from '@angular/common'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR, Validator, AbstractControl, ValidationErrors, NG_VALIDATORS } from '@angular/forms'; +import { NbTooltipDirective, NbTrigger } from '@nebular/theme'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import { StringUtils } from '../../utilities/string-utils'; + +@Component({ + selector: 'rbj-currency-input', + templateUrl: './currency-input.component.html', + styleUrls: ['./currency-input.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => CurrencyInputComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => CurrencyInputComponent), + multi: true, + }, + ] +}) +export class CurrencyInputComponent implements ControlValueAccessor, Validator { + + @ViewChild('currencyInput', { static: true }) input: ElementRef; + @ViewChild(NbTooltipDirective) tooltip: NbTooltipDirective; + private destroy$: Subject = new Subject(); + private _ngModel: number; + private _reverseStatus: boolean = false; + private _disableStatus: boolean = false; + private _initialized: boolean; + private _lastBlurValue: number; + realTimeUpdate: boolean + status = ''; + displayValue: string; + readOnly: boolean = false; + required: boolean = false; + leftToRight: boolean = false; + @Input() id?= ""; + @Input() name = ""; + onChange = (value: number) => { }; + onTouched = () => { }; + @Input() min = -999999999.99; + @Input() max = 999999999.99; + @Input() currencyStep = '0.01'; + @Input() class = ''; + + + /** + * A calculated best value should be for this field, if got mismatch will have hover message + */ + @Input() valueShouldBe: number = null; + /** + * The hover message format for mismatch, Default:`The value should be {1}`, `{1}` will be replace with formatted value + */ + @Input() valueShouldBeWarningText: string = "The value should be {1}"; + /** + * The hover message color default:warning | empty, primary, info, success, warning, danger + */ + @Input() valueShouldBeWarningStatus = 'warning'; + + @Input() zeroStatus = ''; + @Input() zeroExpression = ''; + @Input() nullExpression = ''; + + /** + * Positive value status color default:empty | empty, primary, info, success, warning, danger + */ + @Input() positiveStatus = ''; + + /** + * Negative value status color default:danger | primary, info, success, warning, danger + */ + @Input() negativeStatus = 'danger'; + @Input() currencyCode = 'USD'; + + @Input() nullValue = 0; + @Input() disabled = false; + @Input() precision = 2; + @Input() fieldSize = 'medium'; + @Input() + public set reverseStatus(value) { + this._reverseStatus = typeof value !== 'undefined' && value !== false; + this.setStatusColor(); + } + @Input() + public set disableStatus(value) { + this._disableStatus = typeof value !== 'undefined' && value !== false; + this.setStatusColor(); + } + + @Input("readonly") + public set input_readOnly(value) { + this.readOnly = typeof value !== "undefined" && value !== false; + } + + @Input("required") + public set input_required(value) { + this.required = typeof value !== "undefined" && value !== false; + } + + @Input("LTR") + public set input_leftToRight(value) { + this.leftToRight = typeof value !== "undefined" && value !== false; + } + @Input("realTimeUpdate") + public set input_realTimeUpdate(value) { + this.realTimeUpdate = typeof value !== "undefined" && value !== false; + } + @Input("tooltips") tooltipContent: string; + @Output() change = new EventEmitter(); + @Output() focus = new EventEmitter(); + @Output() blur = new EventEmitter(); + + constructor( + private elementRef: ElementRef, + private renderer: Renderer2) { + + } + validate(control: AbstractControl): ValidationErrors { + if (this.required && (this.value == null || this.value == 0)) { + return { "currency": "" }; + } + + return null; + } + registerOnValidatorChange?(fn: () => void): void { + } + //#region Implements + writeValue(value: string): void { + if (value) { + this.value = Number(value); + } else { + this.value = this.nullValue; + } + this._lastBlurValue = this.value; + //this.onChange(this.value); + this.setDisplayValue(this.value); + this.setStatusColor(); + } + registerOnChange(fn: (value: number) => void): void { + this.onChange = fn; + } + registerOnTouched(fn: any): void { + this.onTouched = fn; + } + setDisabledState?(isDisabled: boolean): void { + this.disabled = isDisabled; + } + + ngAfterViewInit() { + this.tooltip.nbTooltipShowStateChange + .pipe(takeUntil(this.destroy$)) + .subscribe(isShown => { + if (!this.tooltipContent && isShown) { + this.tooltip.hide(); + } + }) + this.renderer.removeAttribute(this.elementRef.nativeElement, 'id') + } + + ngOnInit() { + // if (this.readOnly) { + // this.positiveStatus = ''; + // this.negativeStatus = ''; + // } + } + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } + + //#endregion + + + public get value(): number { + return this._ngModel; + } + public set value(v: number) { + if (v) v = Number(v.toFixed(this.precision)); + if (v < this.min) { + v = this.min; + } else if (v > this.max) { + v = this.max; + } + + if (this._initialized) { + if (v != this._ngModel) { + + this._ngModel = v; + this.onChange(this.value); + + + } + } else { + this._initialized = true; + this._ngModel = v; + } + } + onBlur() { + this.setDisplayValue(this.value); + if (this.value != this._lastBlurValue) { + + this.change.emit(this.value); + this.onChange(this.value); + this._lastBlurValue = this.value; + this.onTouched(); + this.blur.emit(this.value); + this.setStatusColor(); + } + } + setDisplayValue(v: number) { + if (null === v) { + this.displayValue = this.nullExpression; + } else if (['', 0, undefined, NaN].includes(v)) { + + this.displayValue = this.zeroExpression; + } else { + this.displayValue = formatCurrency(v, "en", "", this.currencyCode, `0.${this.precision}`); + } + //to avoid input 0 twice will not update empty to ui + this.input.nativeElement.value = this.displayValue; + } + + currencyInputChanged(value: string) { + if (this.displayValue != value) { + + if (this.realTimeUpdate) { + this.value = Number(value.replace('..', '.').replace(/[$,]/g, '')); + + if (false == [0, null, undefined, NaN].includes(this.value)) { + let focus = this.input.nativeElement.selectionStart; + let lengthBeforeCursor = this.input.nativeElement.value.substring(0, focus).replace(',', '').length; + this.setDisplayValue(this.value); + + let formattedValue = this.input.nativeElement.value; + let commaCount = 0; + for (let i = 0; i < lengthBeforeCursor; i++) { + const c = formattedValue[i]; + if (c == ',') commaCount++; + } + + this.setCursorPosition(lengthBeforeCursor + commaCount); + } else { + + //this.setDisplayValue(this.value); + } + } else { + this.value = StringUtils.isNullOrWhitespace(value) ? this.nullValue : Number(value.replace(/[$,]/g, '')); + } + + + + + } + } + + + setCursorPosition(position) { + let input = this.input.nativeElement; + if (input.setSelectionRange) { + input.focus(); + input.setSelectionRange(position, position); + } else if (input.createTextRange) { + var range = input.createTextRange(); + range.collapse(true); + range.moveEnd('character', position); + range.moveStart('character', position); + range.select(); + } + } + setStatusColor() { + if (this._disableStatus) { + this.status = ""; + return; + } + + if (this._ngModel > 0) this.status = this._reverseStatus ? this.negativeStatus : this.positiveStatus; + else if (['', 0, null, undefined, NaN].includes(this._ngModel)) { + this.status = this.zeroStatus; + } + else { + this.status = this._reverseStatus ? this.positiveStatus : this.negativeStatus; + } + + if (this.valueShouldBe != null) { + let shouldBe = formatCurrency(this.valueShouldBe, "en", "", this.currencyCode); + if (this.displayValue != (this.valueShouldBe == 0 ? this.zeroExpression : shouldBe)) { + + this.status = this.valueShouldBeWarningStatus; + this.tooltipContent = this.valueShouldBeWarningText.replace('{1}', shouldBe); + } else { + + this.tooltipContent = ''; + } + } + } + get alignmentClass(): string { + return this.leftToRight ? "text-left" : "text-right"; + } + + public onKeyDown(e: KeyboardEvent): void { + + + if ([46, 8, 9, 27, 13, 110, 190].indexOf(e.keyCode) !== -1 || + // Allow: Ctrl+A + (e.keyCode === 65 && (e.ctrlKey || e.metaKey)) || + // Allow: Ctrl+C + (e.keyCode === 67 && (e.ctrlKey || e.metaKey)) || + // Allow: Ctrl+V + (e.keyCode === 86 && (e.ctrlKey || e.metaKey)) || + // Allow: Ctrl+X + (e.keyCode === 88 && (e.ctrlKey || e.metaKey)) || + // Allow: page up, page down, home, end, left, right + (e.keyCode >= 33 && e.keyCode <= 39)) { + // let it happen, don't do anything + return; + } + + let regMatch = e.key.match(/[\d.,-]/g); + if (regMatch == null) { + //e.stopPropagation() + e.preventDefault(); + } + } + + +} diff --git a/APP/src/components/currency-input/currency-input.module.ts b/APP/src/components/currency-input/currency-input.module.ts new file mode 100644 index 0000000..9b3e7e0 --- /dev/null +++ b/APP/src/components/currency-input/currency-input.module.ts @@ -0,0 +1,19 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { CurrencyInputComponent } from './currency-input.component'; +import { FormsModule } from '@angular/forms'; +import { NbInputModule, NbTooltipModule } from '@nebular/theme'; +import { MaskDirectiveModule } from '../../directives/mask-directive/mask-directive.module'; + +@NgModule({ + declarations: [CurrencyInputComponent], + imports: [ + CommonModule, + FormsModule, + NbInputModule, + NbTooltipModule, + MaskDirectiveModule + ], + exports: [CurrencyInputComponent] +}) +export class CurrencyInputModule { } diff --git a/APP/src/components/date-input/calendar-view/calendar-view.component.html b/APP/src/components/date-input/calendar-view/calendar-view.component.html new file mode 100644 index 0000000..187e5eb --- /dev/null +++ b/APP/src/components/date-input/calendar-view/calendar-view.component.html @@ -0,0 +1,48 @@ + + + + + + + + + + \ No newline at end of file diff --git a/APP/src/components/date-input/calendar-view/calendar-view.component.scss b/APP/src/components/date-input/calendar-view/calendar-view.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/APP/src/components/date-input/calendar-view/calendar-view.component.spec.ts b/APP/src/components/date-input/calendar-view/calendar-view.component.spec.ts new file mode 100644 index 0000000..445ff81 --- /dev/null +++ b/APP/src/components/date-input/calendar-view/calendar-view.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; + +import { CalendarViewComponent } from './calendar-view.component'; + +describe('CalendarViewComponent', () => { + let component: CalendarViewComponent; + let fixture: ComponentFixture; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [ CalendarViewComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(CalendarViewComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/APP/src/components/date-input/calendar-view/calendar-view.component.ts b/APP/src/components/date-input/calendar-view/calendar-view.component.ts new file mode 100644 index 0000000..ea94b0c --- /dev/null +++ b/APP/src/components/date-input/calendar-view/calendar-view.component.ts @@ -0,0 +1,183 @@ +import { Component, HostBinding, HostListener, Input, OnInit, Renderer2, Type } from '@angular/core'; +import { NbCalendarCell, NbCalendarRange, NbCalendarRangeYearCellComponent, NbCalendarSize, NbCalendarViewMode, NbCalendarViewModeValues, NbCalendarYearModelService, NbDateService, NbPositionedContainerComponent, NbRenderableContainer } from '@nebular/theme'; +import { Subject } from 'rxjs'; + +@Component({ + selector: 'ngx-calendar-view', + templateUrl: './calendar-view.component.html', + styleUrls: ['./calendar-view.component.scss'] +}) +export class CalendarViewComponent extends NbPositionedContainerComponent implements NbRenderableContainer { + + /** + * Defines active view for calendar. + * */ + @Input('startView') mode: NbCalendarViewMode = NbCalendarViewMode.DATE; + static ngAcceptInputType_activeViewMode: NbCalendarViewModeValues; + + /** + * Determines whether we should show calendar navigation or not. + * */ + @Input() + @HostBinding('class.has-navigation') + showNavigation: boolean = true; + + date: Date; + visibleDate: Date; + ViewMode = NbCalendarViewMode; + max: Date; + min: Date; + size: NbCalendarSize = NbCalendarSize.MEDIUM; + context: contextSetting; + private listener: any; + private destroy$: Subject = new Subject(); + constructor( + protected dateService: NbDateService, + private renderer: Renderer2, + protected yearModelService: NbCalendarYearModelService, + ) { + super(); + } + renderContent() { + + } + filter: (Date) => boolean; + private hasParentClass(child, classname) { + if (child.className && child.className.split(' ').indexOf(classname) >= 0) return true; + try { + //Throws TypeError if child doesn't have parent any more + return child.parentNode && this.hasParentClass(child.parentNode, classname); + } catch (TypeError) { + return false; + } + } + ngOnInit() { + this.date = this.context.date; + this.visibleDate = this.date ? this.date : this.dateService.today(); + + + setTimeout(() => { + this.listener = this.renderer.listen('window', 'click', (e: Event) => { + // + let el = (e.target as HTMLElement); + if ( + this.hasParentClass(el, 'rbjCalendar') + // el.className.indexOf('rbjCalendar') > -1 || + // el.tagName.indexOf('NB-CALENDAR') > -1 + ) { + this.context.focus(); + } else { + this.context.hide(); + } + }) + }, 50); + + } + + ngOnDestroy() { + if (this.listener) { this.listener(); } + this.destroy$.next(); + this.destroy$.complete(); + } + setViewMode(mode: NbCalendarViewMode) { + this.mode = mode; + } + setVisibleDate(date: Date) { + + } + + prevMonth() { + this.changeVisibleMonth(-1); + } + + nextMonth() { + this.changeVisibleMonth(1); + } + + prevYear() { + this.changeVisibleYear(-1); + } + + nextYear() { + this.changeVisibleYear(1); + } + + prevYears() { + this.changeVisibleYears(-1); + } + + nextYears() { + this.changeVisibleYears(1); + } + + + navigateNext() { + switch (this.mode) { + case NbCalendarViewMode.DATE: + return this.nextMonth(); + case NbCalendarViewMode.MONTH: + return this.nextYear(); + case NbCalendarViewMode.YEAR: + return this.nextYears(); + } + } + onChangeViewMode() { + if (this.mode === NbCalendarViewMode.DATE) { + return this.setViewMode(NbCalendarViewMode.YEAR); + } + + this.setViewMode(NbCalendarViewMode.DATE); + } + + private changeVisibleMonth(direction: number) { + this.visibleDate = this.dateService.addMonth(this.visibleDate, direction); + } + + private changeVisibleYear(direction: number) { + this.visibleDate = this.dateService.addYear(this.visibleDate, direction); + } + + private changeVisibleYears(direction: number) { + this.visibleDate = this.dateService.addYear(this.visibleDate, direction * this.yearModelService.getYearsInView()); + } + + navigateToday() { + + this.visibleDate = this.dateService.today(); + + this.context.dateChange(this.dateService.today()); + } + + + onChange(date: Date) { + this.context.dateChange(date); + } + + @HostListener('focus') + focus() { + this.context.focus(); + } + + dayCellComponent: Type>> + + /** + * Custom month cell component. Have to implement `NbCalendarCell` interface. + * */ + monthCellComponent: Type>>; + + /** + * Custom year cell component. Have to implement `NbCalendarCell` interface. + * */ + yearCellComponent: Type>> = NbCalendarRangeYearCellComponent; + + + +} + + +export interface contextSetting { + date: Date, + hide: () => {}, + focus: () => {}, + dateChange: (d: Date) => {}; +} \ No newline at end of file diff --git a/APP/src/components/date-input/date-input.component.html b/APP/src/components/date-input/date-input.component.html new file mode 100644 index 0000000..92644f9 --- /dev/null +++ b/APP/src/components/date-input/date-input.component.html @@ -0,0 +1,21 @@ +
+ + +
+ +
+ +
+ +
+ +
+
+ \ No newline at end of file diff --git a/APP/src/components/date-input/date-input.component.scss b/APP/src/components/date-input/date-input.component.scss new file mode 100644 index 0000000..12cdffe --- /dev/null +++ b/APP/src/components/date-input/date-input.component.scss @@ -0,0 +1,13 @@ +@import "../../@theme/styles/themes"; + +form.ng-touched input.ng-invalid { + border-color: nb-theme(color-danger-default); +} +:host { + display: block; +} + +.editBtn { + width: 29px !important; + padding: 0 !important; +} diff --git a/APP/src/components/date-input/date-input.component.spec.ts b/APP/src/components/date-input/date-input.component.spec.ts new file mode 100644 index 0000000..edde824 --- /dev/null +++ b/APP/src/components/date-input/date-input.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; + +import { DateInputComponent } from './date-input.component'; + +describe('DateInputComponent', () => { + let component: DateInputComponent; + let fixture: ComponentFixture; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [ DateInputComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(DateInputComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/APP/src/components/date-input/date-input.component.ts b/APP/src/components/date-input/date-input.component.ts new file mode 100644 index 0000000..d7484ce --- /dev/null +++ b/APP/src/components/date-input/date-input.component.ts @@ -0,0 +1,375 @@ +import { Component, OnInit, ChangeDetectionStrategy, forwardRef, ViewChild, ElementRef, Output, Input, EventEmitter, Renderer2, ChangeDetectorRef } from '@angular/core'; +import { NG_VALUE_ACCESSOR, NG_VALIDATORS, Validator, AbstractControl, ValidationErrors } from '@angular/forms'; +import { DatePipe } from '@angular/common'; +import { ApplyMaskService } from '../../services/apply-mask.service'; +import { ForceFocusMsgDirective } from '../../directives/force-focus-msg/force-focus-msg.directive'; +import { NbAdjustment, NbDatepickerComponent, NbDynamicOverlay, NbDynamicOverlayHandler, NbPosition, NbTrigger } from '@nebular/theme'; +import { MsgBoxService } from '../../services/msg-box.service'; +import { first } from 'rxjs/operators'; +import { StringUtils } from '../../utilities/string-utils'; +import { CalendarViewComponent } from './calendar-view/calendar-view.component'; +import { Subject } from 'rxjs'; +import { DateUtilsService } from '../../services/date-utils.service'; +import { ADIcon } from '../alert-dlg/alert-dlg.model'; +const DATE_FORMAT = 'MM/dd/yyyy' +@Component({ + selector: 'rbj-date-input', + templateUrl: './date-input.component.html', + styleUrls: ['./date-input.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => DateInputComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => DateInputComponent), + multi: true, + }, + NbDynamicOverlayHandler, + NbDynamicOverlay + ] +}) +export class DateInputComponent implements OnInit, Validator { + + @ViewChild('datePickerContainer', { static: true }) datePickerContainer: ElementRef; + @ViewChild('inputBox', { static: true }) input: ElementRef; + @ViewChild(ForceFocusMsgDirective) popover: ForceFocusMsgDirective; + private _value: Date; + private _lastBlurValue: Date; + + private dynamicOverlay: NbDynamicOverlay; + private _calendarIsShown: boolean = false; + private _passwordTag: string = null; + + _readOnly: boolean; + + //this is for password protected field + passwordSecured: boolean = false; + + onChange = (value: Date) => { }; + onTouched = () => { }; + name: string; + invalid: boolean = false; + @Input() id?: string = ""; + @Input() placeholder: string; + @Input() inputClass: string; + @Input() size: string = 'medium'; + @Input() min: Date; + @Input() max: Date; + @Input() maskExpression: string = '00/00/0000'; + _disabled = false; + _required = false; + + @Input() + public set readonly(value) { + this._readOnly = typeof value !== 'undefined' && value !== false; + } + @Input() + public set required(value) { + this._required = typeof value !== 'undefined' && value !== false; + } + + skipHoliday: boolean + @Input("skipHoliday") + public set input_skipHoliday(value) { + this.skipHoliday = typeof value !== "undefined" && value !== false; + } + + @Input() + public set passwordTag(value) { + this._passwordTag = value; + this.passwordSecured = true; + } + public get passwordTag() { + return this._passwordTag; + } + + public get showUnlockBtn(): boolean { + return !this.readonly && this.passwordSecured; + } + + @Output() dateChange = new EventEmitter(); + @Output() focus = new EventEmitter(); + + constructor( + public datepipe: DatePipe, + private applyMaskService: ApplyMaskService, + private cdRef: ChangeDetectorRef, + private msgBoxService: MsgBoxService, + private renderer: Renderer2, + private el: ElementRef, + private dynamicOverlayHandler: NbDynamicOverlayHandler, + private dateUtilsService: DateUtilsService, + + ) { + } + + ngAfterViewInit() { + this.renderer.removeAttribute(this.el.nativeElement, 'id'); + setTimeout(() => { + this.dynamicOverlay = this.configureDynamicOverlay().build(); + }, 400); + + } + + writeValue(value: Date): void { + if (value) { + this.value = value; + this.displayValue = this.dateUtilsService.format(this.value, DATE_FORMAT) + } + else if (value !== undefined) { + this.value = null; + this.displayValue = ''; + } + this._lastBlurValue = this.value; + } + + registerOnChange(fn: (value: Date) => void): void { + this.onChange = fn; + } + + registerOnTouched(fn: any): void { + this.onTouched = fn; + } + + setDisabledState?(isDisabled: boolean): void { + this._disabled = isDisabled; + } + + //#endregion + + ngOnInit() { + + this.dynamicOverlayHandler + .host(this.input) + .componentType(CalendarViewComponent); + } + public get displayValue(): string { + return this.input.nativeElement.value; + } + public set displayValue(v: string) { + this.input.nativeElement.value = v; + } + + public get value(): Date { + return this._value; + } + public set value(v: Date) { + + var changedByLimited = false; + if (v) { + //Check max and min + if (v < this.min) { + v = this.min; + changedByLimited = true; + } else if (v > this.max) { + v = this.max; + changedByLimited = true; + } + } + + if (false == this.dateUtilsService.isSameDate(this._lastBlurValue, v)) { + this._value = v; + this.onChange(this.value); + } + //this.datePicker.hide(); + if (changedByLimited) { + this.displayValue = this.dateUtilsService.format(v, DATE_FORMAT); + } + } + + toggleCalendar() { + this._calendarIsShown = !this._calendarIsShown; + if (this._calendarIsShown) { + this.dynamicOverlay = this.configureDynamicOverlay().rebuild(); + } + this.dynamicOverlay.toggle(); + // if (this.datePicker.isShown) { + + // this.datePicker.hide(); + // } else { + + // this.datePicker.show(); + // } + } + + + setDisplayFormat() { + + this.displayValue = this.applyMaskService + .applyMask(this.displayValue, this.maskExpression) + + //this.validate(); + } + + validate(control: AbstractControl): ValidationErrors { + + if (this._required && !this.value && this.displayValue.length == 0) { + this.invalid = true; + } + else { + this.invalid = !this.applyMaskService.validate(this.displayValue, this.maskExpression); + } + + if (this.invalid && this.input.nativeElement.parentElement.className.indexOf('ng-touched') > -1) { + if (this.popover) this.popover.show(); + return { key: 'Date', } + } + + if (this.popover) this.popover.hide(); + return null; + } + + // triggerChange(wipeOutInvalidDate = false) { + // if (this.displayValue.length == this.maskExpression.length) { + // var newValue = new Date(this.displayValue); + // let isValid = this.dateUtilsService.isValidDate(newValue); + // if ( + // this.value == null || + // false === this.dateUtilsService.isSameDate(newValue, this.value) + // ) { + // this.value = newValue; + // } + // } + // else if (this.displayValue.length == 0 || wipeOutInvalidDate) { + // this.clearDate(); + // } + + // if (this.value != this._lastEditValue) { + // this._lastEditValue = this.value; + // this.onChange(this.value); + // this.dateChange.emit(this.value); + // } + + // } + + + pickDate(date: Date) { + if (!this._disabled && !this._readOnly) { + this.displayValue = this.dateUtilsService.format(date, DATE_FORMAT); + this.onBlur(); + } + this.hide(); + this.input.nativeElement.focus(); + this.dynamicOverlay = this.configureDynamicOverlay().rebuild(); + } + + unlockField() { + this.msgBoxService.getAuthPermission(this._passwordTag).pipe(first()).subscribe(result => { + if (result) { + this.passwordSecured = false; + setTimeout(() => { + this.setCursorPosition(0); + }, 100); + } + }); + } + + setCursorPosition(position) { + let input = this.input.nativeElement; + if (input.setSelectionRange) { + input.focus(); + input.setSelectionRange(position, position); + } + else if (input.createTextRange) { + var range = input.createTextRange(); + range.collapse(true); + range.moveEnd('character', position); + range.moveStart('character', position); + range.select(); + } + } + + private destroy$: Subject = new Subject(); + + + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + this.dynamicOverlayHandler.destroy(); + } + + + + private configureDynamicOverlay() { + + + //.el[0].scrollTop = (i - MAX_VISIBLE_ITEMS / 2) * VISIBLE_ITEM_HEIGHT; + + var windowHeight = window.innerHeight; + //include input box height + //var itemHeight = (visibleItemsCount + 1) * VISIBLE_ITEM_HEIGHT; + var itemHeight = 405; + //this.input.nativeElement.offsetHeight + //var y = (this.input.nativeElement.offsetHeight + itemHeight) > windowHeight ? (windowHeight - itemHeight) : this.input.nativeElement.offsetHeight; + var y = this.input.nativeElement ? this.input.nativeElement.getBoundingClientRect().y : 0; + let menuOnBottom = (y + itemHeight) < windowHeight; + + return this.dynamicOverlayHandler + .position(menuOnBottom ? NbPosition.BOTTOM_START : NbPosition.TOP_START) + .trigger(NbTrigger.NOOP) + .offset(menuOnBottom ? 2 : -11) + .adjustment(NbAdjustment.NOOP) + .context({ + position: menuOnBottom ? NbPosition.BOTTOM : NbPosition.TOP, + date: this.value, + class: 'text-left', + width: this.input.nativeElement.parentElement.clientWidth, + hide: () => { this.hide(); }, + focus: () => { this.isFocused = true; }, + dateChange: (date) => { this.pickDate(date); } + }); + } + isFocused: boolean = false; + hide() { + this._calendarIsShown = false; + if (this.dynamicOverlay && !this.cdRef['destroyed']) { + this.dynamicOverlay.hide(); + this.cdRef.detectChanges(); + } + } + onBlur() { + this.onTouched(); + if (false == this._calendarIsShown) { + + //For quick input `07/09/21` => `07/09/2021` + if (this.displayValue.match(/^\d{2}\/\d{2}\/\d{2}$/g)) { + this.displayValue = this.displayValue.slice(0, 6) + '20' + this.displayValue.slice(6, 8); + } else if (this.displayValue.match(/^\d{2}\/\d{2}$/g)) { + this.displayValue = this.displayValue.slice(0, 6) + '20' + this.displayValue.slice(6, 8); + } + } + if (this.displayValue) { + var newValue = new Date(this.displayValue); + let isValid = this.displayValue.length == this.maskExpression.length && this.dateUtilsService.isValidDate(newValue); + if (isValid) { + //This is for 02/30/2022 detection, new Date('02/30/2022') will parse it as 03/02/2022 + isValid = this.displayValue == this.dateUtilsService.format(newValue, DATE_FORMAT); + } + if (isValid) { + this.value = newValue; + } else { + this.msgBoxService.show('Invalid Date Input', { + text: `${this.displayValue} is not a valid date!`, + icon: ADIcon.WARNING + }); + this.clearDate(); + } + } else { + this.clearDate(); + } + + if (false == this.dateUtilsService.isSameDate(this._lastBlurValue, this.value)) { + + this._lastBlurValue = this.value; + this.dateChange.emit(this.value); + } + } + private clearDate() { + this.value = null; + this.displayValue = ''; + + } +} diff --git a/APP/src/components/date-input/date-input.module.ts b/APP/src/components/date-input/date-input.module.ts new file mode 100644 index 0000000..2eca945 --- /dev/null +++ b/APP/src/components/date-input/date-input.module.ts @@ -0,0 +1,35 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { DateInputComponent } from './date-input.component'; +import { NbInputModule, NbDatepickerModule, NbIconModule, NbButtonModule, NbCardModule, NbCalendarKitModule, NbTooltipModule } from '@nebular/theme'; +import { RegexValidatorModule } from '../../directives/regex-validator/regex-validator.module'; +import { FormsModule } from '@angular/forms'; +import { ForceFocusMsgModule } from '../../directives/force-focus-msg/force-focus-msg.module'; +import { CalendarViewComponent } from './calendar-view/calendar-view.component'; +import { MaskDirectiveModule } from '../../directives/mask-directive/mask-directive.module'; +import { DxCalendarModule } from 'devextreme-angular'; + +@NgModule({ + declarations: [DateInputComponent, CalendarViewComponent], + imports: [ + CommonModule, + FormsModule, + NbInputModule, + NbDatepickerModule, + NbIconModule, + NbButtonModule, + RegexValidatorModule, + NbIconModule, + NbButtonModule, + NbCardModule, + NbCalendarKitModule, + NbTooltipModule, + ForceFocusMsgModule, + MaskDirectiveModule, + DxCalendarModule + ], + exports: [ + DateInputComponent + ] +}) +export class DateInputModule { } diff --git a/APP/src/components/drop-down-list/drop-down-list.component.html b/APP/src/components/drop-down-list/drop-down-list.component.html new file mode 100644 index 0000000..ebb1c5f --- /dev/null +++ b/APP/src/components/drop-down-list/drop-down-list.component.html @@ -0,0 +1,16 @@ +
+ +
+ +
+
\ No newline at end of file diff --git a/APP/src/components/drop-down-list/drop-down-list.component.scss b/APP/src/components/drop-down-list/drop-down-list.component.scss new file mode 100644 index 0000000..d4f0e6a --- /dev/null +++ b/APP/src/components/drop-down-list/drop-down-list.component.scss @@ -0,0 +1,33 @@ +@import "../../@theme/styles/themes"; + +form.ng-touched input.ng-invalid { + border-color: nb-theme(color-danger-default); +} +.dropdownBtn { + width: 25px !important; + padding: 0 !important; +} + +[nbInput]:disabled { + background-color: white !important; +} + +input { + cursor: pointer; + text-overflow: ellipsis; + .dropdownInput { + border-top-right-radius: 0 !important; + border-bottom-right-radius: 0 !important; + min-width: 25px; + height: auto; + } + .editable { + cursor: text; + } + &:read-only:not(.editable) { + cursor: pointer; + } + &.upperFirstLetter { + text-transform: capitalize; + } +} diff --git a/APP/src/components/drop-down-list/drop-down-list.component.spec.ts b/APP/src/components/drop-down-list/drop-down-list.component.spec.ts new file mode 100644 index 0000000..3069d83 --- /dev/null +++ b/APP/src/components/drop-down-list/drop-down-list.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; + +import { DropDownListComponent } from './drop-down-list.component'; + +describe('DropDownListComponent', () => { + let component: DropDownListComponent; + let fixture: ComponentFixture; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [ DropDownListComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(DropDownListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/APP/src/components/drop-down-list/drop-down-list.component.ts b/APP/src/components/drop-down-list/drop-down-list.component.ts new file mode 100644 index 0000000..6d16cd5 --- /dev/null +++ b/APP/src/components/drop-down-list/drop-down-list.component.ts @@ -0,0 +1,699 @@ +import { Component, Input, Output, EventEmitter, forwardRef, ViewChild, ElementRef, ChangeDetectionStrategy, ChangeDetectorRef, Renderer2, HostListener, ViewChildren, QueryList } from '@angular/core'; +import { AbstractControl, ControlValueAccessor, NG_VALIDATORS, NG_VALUE_ACCESSOR, ValidationErrors, Validator } from '@angular/forms'; +import { DropDownOption } from '../../entity/dropDownOption'; +import { NbContextMenuDirective, NbDynamicOverlay, NbDynamicOverlayHandler, NbTrigger, NbAdjustment, NbPosition, NbTooltipDirective } from '@nebular/theme'; +import { filter, map, takeUntil } from 'rxjs/operators'; +import { trigger, state, transition, animate, style } from '@angular/animations'; +import { Subject } from 'rxjs'; +import { DropDownMenuComponent } from './drop-down-menu/drop-down-menu.component'; +import { UuidUtils } from '../../utilities/uuid-utils'; +import { DropDownListService } from './drop-down-list.service'; +import { StringUtils } from '../../utilities/string-utils'; +import { DebounceTimer } from '../../utilities/timer-utils'; +import { RbjTooltipService } from '../../services/rbj-tooltip.service'; +import { TextareaUtils } from '../../utilities/textarea-utils'; + +const MAX_VISIBLE_ITEMS = 8; +const VISIBLE_ITEM_HEIGHT = 40; +const TEXT_CLEAR = "Clear The Field"; + +@Component({ + selector: 'rbj-drop-down', + templateUrl: './drop-down-list.component.html', + styleUrls: ['./drop-down-list.component.scss'], + animations: [ + trigger('openClose', [ + // ... + state('open', style({ + transform: 'rotate(180deg)' + })), + state('closed', style({ + transform: 'rotate(0deg)' + })), + transition('* => *', [ + animate(100) + ]), + ]), + ], + changeDetection: ChangeDetectionStrategy.OnPush, + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => DropDownListComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => DropDownListComponent), + multi: true, + }, + NbDynamicOverlayHandler, + NbDynamicOverlay + ] +}) +export class DropDownListComponent implements ControlValueAccessor, Validator { + + @ViewChild('inputBox', { static: true }) input: ElementRef; + @ViewChild('dropButton', { static: true }) button: ElementRef; + @ViewChild(NbContextMenuDirective, { static: true }) contextMenu: NbContextMenuDirective; + + refreshMenuDebounceTimer = new DebounceTimer(20, () => { if (this.menuIsOpen) { this.setUpDropdownList(); } }); + constructor( + private dropDownListService: DropDownListService, + private cdRef: ChangeDetectorRef, + private dynamicOverlayHandler: NbDynamicOverlayHandler, + private elementRef: ElementRef, + private renderer: Renderer2, + private rbjTooltipService: RbjTooltipService, + ) { + this.tabindex = elementRef.nativeElement.getAttribute('tabindex'); + } + + private destroy$: Subject = new Subject(); + private _initialized: boolean; + private _viewInitialized: boolean = false; + private _value: any; + private _text: string; + private _textTyping: boolean = false; + private dynamicOverlay: NbDynamicOverlay; + private isFocused: boolean = false; + private _propDisplay: string = 'value1'; + + status = ''; + displayValue: string; + selectedItem: DropDownOption; + firstMatchOption: DropDownOption; + uuid = 'rbjDropDown'; + items: DropDownOption[] = []; + + readonly: boolean = false; + required: boolean = false; + mustMatch: boolean = true; + @Input() allowSearch: boolean = true; + compact: boolean + menuIsOpen: boolean = false; + selectedIndex: number = -1; + tabindex: string = null; + focusIndex: number = -1; + usingFocusIndex: boolean = false; + + public get tooltip(): string { + return this.selectedItem ? this.selectedItem[this.propItemDisplay] || '' : ''; + } + + public get value(): any { + return this._value; + } + public set value(v: any) { + if (this._initialized) { + if (v != this._value) { + this._value = v; + this.onChange(this.value); + } + } + else { + this._initialized = true; + this._value = v; + } + this.keyValueChangedByBinding(); + } + + @Input() + public get text(): string { + return this._text; + } + public set text(v: string) { + this.input.nativeElement.value = v; + if (v != this._text) { + + this._text = v; + this.textChange.emit(this._text); + } + } + + @Input() public set source(v: DropDownOption[]) { + if (this._source != v) { + this._source = v; + this.refreshMenuDebounceTimer.resetTimer(); + //if (this.menuIsOpen) { this.setUpDropdownList(); } + this.keyValueChangedByBinding(); + } + } + private _source: DropDownOption[]; + public get source(): DropDownOption[] { + return this._source; + } + + public get availableOptions(): DropDownOption[] { + if (this.source) { + return this.source.filter(o => this.itemIsVisible(o) && !o[this.propDisabled]); + } + return []; + } + + @Input() name?: string = '' + @Input() id?: string = '' + @Input() hideButton: boolean = false; + @Input() inputClass = ''; + + public get propDisplay(): string { + return this._propDisplay; + } + + /** + * Display text of dropdown will displaying this property of dropdown item, defaults as `value1`, will set `propItemDisplay` also, if it's default. + */ + @Input() public set propDisplay(v: string) { + this._propDisplay = v; + if (this.propItemDisplay == 'value1') this.propItemDisplay = v; + } + + /** + * Display text of dropdown context menu will displaying this property of dropdown item, support HTML code, defaults as `value1` + */ + @Input() propItemDisplay = 'value1'; + /** + * The `key` property name of dropdown item, defaults as `key` + */ + @Input() propKey = 'key'; + + /** + * The `disabled` property name of dropdown item, defaults as `disabled` + */ + @Input() propDisabled = 'disabled'; + /** + * The `visible` property name of dropdown item, defaults as `visible` + */ + @Input() propVisible = 'visible'; + @Input() matchWith = 'key'; + @Input() disabled = false; + @Input() placeholder = ''; + @Input() maskExpression = null; + @Input() clearOption = false; + + /** + * Tooltip message for no matching found, default as "$inputText Not Matched", `$inputText` will replace with user input text + */ + @Input() inputNotMatchedTooltip = "$inputText does not exist"; + + @HostListener('focus') + focusHandler() { + this.focus(); + } + + @Input("mustMatch") + public set input_mustMatch(value) { + this.mustMatch = typeof value !== "undefined" && value !== false; + } + + @Input("required") + public set input_required(value) { + this.required = typeof value !== "undefined" && value !== false; + } + @Input("readonly") + public set input_readonly(value) { + this.readonly = typeof value !== "undefined" && value !== false; + } + @Input("compact") + public set input_compact(value) { + this.compact = typeof value !== "undefined" && value !== false; + } + @Input("clearOption") + public set input_clearOption(value) { + this.clearOption = typeof value !== "undefined" && value !== false; + } + + @Output() selectedChange = new EventEmitter(); + @Output() selectedChangeFrom = new EventEmitter<{ previousValue: any, currentValue: any }>(); + @Output() menuItemSelected = new EventEmitter(); + @Output() textChange = new EventEmitter(); + @Output() blur: EventEmitter = new EventEmitter(); + @Output() inputTextNotMatched = new EventEmitter(); + + /** + * Triggering when dropdown option been change by user, when subscribe this will not trigger selectedChange any more + */ + @Output() beforeChange = new EventEmitter(); + + onChange = (value: any) => { }; + onTouched = () => { }; + + writeValue(value: any): void { + this.value = value; + } + + registerOnChange(fn: (value: any) => void): void { + this.onChange = fn; + } + + registerOnTouched(fn: any): void { + this.onTouched = fn; + } + + setDisabledState?(isDisabled: boolean): void { + this.disabled = isDisabled; + } + + validate(control: AbstractControl): ValidationErrors { + if (this.required && (this.value == null)) { + return { "dropdown": "" }; + } + return null; + } + + registerOnValidatorChange?(fn: () => void): void { + } + + ngOnInit() { + this.uuid += UuidUtils.generate(); + + this.dropDownListService.onItemSelect + .pipe( + takeUntil(this.destroy$), + filter(({ tag }) => tag === this.uuid), + map(({ item }) => item), + ) + .subscribe(item => { + this.selectByMenu(item); + }); + + this.dynamicOverlayHandler + .host(this.input) + .componentType(DropDownMenuComponent); + } + + ngAfterViewInit() { + this._viewInitialized = true; + this.setUpDropdownList(); + this.dynamicOverlay = this.configureDynamicOverlay().build(); + this.renderer.removeAttribute(this.elementRef.nativeElement, 'id') + } + ngOnChanges() { + //this.dynamicOverlay = this.configureDynamicOverlay().rebuild(); + } + + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + this.dynamicOverlayHandler.destroy(); + } + + private itemIsVisible(item: any) { + if (item[this.propVisible] == undefined) { + item[this.propVisible] = true; + } + + return true === item[this.propVisible]; + } + + focusout() { + this.isFocused = false; + } + + onFocus() { + this.isFocused = true; + } + + onTextFocus() { + this.isFocused = true; + this._textTyping = true; + } + + onTextFocusout() { + this._textTyping = false; + this.focusout(); + } + + focus() { + this.input.nativeElement.focus(); + } + + /** + * Remove `ng-invalid` class from input field, only calling this method for data been changed in background but UI doesn't refresh. + */ + private removeInvalidClassFormInput() { + this.renderer.removeClass(this.input.nativeElement, 'ng-invalid'); + } + updateTextFromValue() { + if (this.clearOption && StringUtils.isNullOrWhitespace(this.value)) { + this.text = ''; + this.input.nativeElement.value = ""; + } + else if (this.source) { + let item: DropDownOption = null; + + if (null == this.value && this.mustMatch) { + item = this.availableOptions.find(o => o.default); + } + else { + item = this.availableOptions.find(o => o[this.propKey] == this.value); + } + + this.selectedIndex = item ? this.source.indexOf(item) : -1; + this._text = item ? item[this.propDisplay] : this.value; + + if (this.input) { + this.input.nativeElement.value = this._text; + this.removeInvalidClassFormInput(); + } + } + else { + this.text = ''; + } + this.cdRef.detectChanges(); + } + + + clickSelectAllText() { + if (this.allowSearch) { + this.input.nativeElement.select(); + } + } + + onTextFieldBlur() { + + //set timeout for avoiding menu been hided before click event triggered + // setTimeout(() => { + this.updateSelectedItemByText(); + this.onTouched(); + this.blur.next(this.text); + // }, 200); + } + + private setUpDropdownList() { + this.items = []; + if (this.source && this.source.length > 0) { + if (this.clearOption) { + this.items = [new DropDownOption('', '')]; + this.items[0][this.propKey] = ""; + this.items[0][this.propDisplay] = ""; + this.items[0][this.propItemDisplay] = TEXT_CLEAR; + } + + if (this.menuIsOpen && this.allowSearch && this.input.nativeElement) { + let filter = this.input.nativeElement.value.toLowerCase(); + this.items = this.items.concat( + this.availableOptions.filter(s => s[this.propItemDisplay].toLowerCase().indexOf(filter) > -1 + )); + + //unique by this.propItemDisplay + this.items = Array.from(new Map(this.items.map(item => + [item[this.propItemDisplay], item])).values()); + + if (this.items.length > 0) { + this.firstMatchOption = this.availableOptions.find(s => s[this.propKey] == this.items[0][this.propKey]); + this.firstMatchOption = this.items[this.focusIndex]; + } + else { + this.firstMatchOption = null; + } + } + else { + this.items = this.items.concat(this.source) + } + + if (this.selectedItem) { + this.focusIndex = this.items.indexOf(this.selectedItem) + this.focusIndex = this.focusIndex > -1 ? this.focusIndex : 0; + } + + this.usingFocusIndex = false; + } + + if (this._viewInitialized) { + + this.dynamicOverlay = this.configureDynamicOverlay() + .rebuild(); + } + } + + private configureDynamicOverlay() { + let visibleItemsCount = this.items.filter(i => i.visible).length; + visibleItemsCount = visibleItemsCount < MAX_VISIBLE_ITEMS ? visibleItemsCount : MAX_VISIBLE_ITEMS; + var windowHeight = window.innerHeight; + //include input box height + var itemHeight = (visibleItemsCount + 1) * VISIBLE_ITEM_HEIGHT; + var y = this.input.nativeElement ? this.input.nativeElement.getBoundingClientRect().y : 0; + let menuOnBottom = (y + itemHeight) < windowHeight; + + return this.dynamicOverlayHandler + .position(menuOnBottom ? NbPosition.BOTTOM_END : NbPosition.TOP_END) + .trigger(NbTrigger.NOOP) + .offset(menuOnBottom ? 2 : -40) + .adjustment(NbAdjustment.NOOP) + .context({ + position: menuOnBottom ? NbPosition.BOTTOM : NbPosition.TOP, + items: this.items, + tag: this.uuid, + class: 'text-left', + width: this.input.nativeElement.parentElement.clientWidth, + propDisplay: this.propItemDisplay, + propDisabled: this.propDisabled, + focusIndex: this.focusIndex, + hide: () => { this.hide(); }, + focus: () => { this.isFocused = true; } + }); + } + + private updateSelectedItemInMenu() { + this.items.forEach(item => { + item.selected = item[this.propKey] == this.value; + }); + } + + private show() { + this.menuIsOpen = true; + this.dynamicOverlay.show(); + this.cdRef.detectChanges(); + } + + private hide() { + this.menuIsOpen = false; + if (this.dynamicOverlay && !this.cdRef['destroyed']) { + this.dynamicOverlay.hide(); + this.cdRef.detectChanges(); + } + } + + toggle(selectAllText: boolean = true) { + if (!this.disabled && !this.readonly) { + if (false == this.menuIsOpen) { + this.setUpDropdownList(); + } + this.dynamicOverlay.toggle(); + if (this.dynamicOverlay.isAttached) { + this.updateSelectedItemInMenu(); + + this.scrollingMenuByIndex(this.selectedIndex); + + this.menuIsOpen = true; + if (selectAllText) { + this.clickSelectAllText(); + } + } + else { + this.menuIsOpen = false; + } + } + } + + private scrollingMenuByIndex(i: number) { + if (this.dynamicOverlay && this.dynamicOverlay.isAttached) { + if (this.items.length > MAX_VISIBLE_ITEMS) { + const el = document.getElementsByClassName(this.uuid); + el[0].scrollTop = (i - MAX_VISIBLE_ITEMS / 2) * VISIBLE_ITEM_HEIGHT; + } + } + } + + //Triggered when user clicks option from dropdown menu + private selectByMenu(item: DropDownOption) { + if (this.beforeChange.observers.length == 0) { + this.menuItemSelected.emit(item[this.propKey]); + + if (this.value != item[this.propKey]) { + const previousValue = this.value; + this.value = item[this.propKey] + this._text = item[this.propDisplay] == TEXT_CLEAR ? '' : item[this.propDisplay]; + + //this line is for triggering html input field validating without triggering whole textChange process + this.removeInvalidClassFormInput(); + this.selectedIndex = this.items.indexOf(item); + this.blur.next(this.text); + this.textChange.next(this.text); + this.onChange(this.value); + this.selectedChange.emit(this.value); + this.selectedChangeFrom.emit({ previousValue, currentValue: this.value }); + + if (this.hideButton) { + this.input.nativeElement.focus(); + } + else { + this.input.nativeElement.focus(); + this.clickSelectAllText(); + } + + this.onTouched(); + this.updateSelectedItemInMenu(); + } + } + else { + this.beforeChange.emit(item[this.propKey]); + } + } + + //Key value changed from binding, will update the displaying text and selected item in menu. + private keyValueChangedByBinding() { + let defaultOption = null; + if (this.source) { + + let comparison = this._value; + this.selectedItem = this.availableOptions.find(o => o[this.propKey] == comparison); + defaultOption = this.availableOptions.find(o => o.default); + } + if (!this.selectedItem) { + if (this.mustMatch) { + if (defaultOption) { + this._value = defaultOption[this.propKey]; + this.selectedItem = defaultOption; + } + else { + this._value = null; + this.selectedItem = new DropDownOption('', ''); + } + } + this.onChange(this.value); + } + + //this.text = this.selectedItem[this.propDisplay]; + this.updateTextFromValue(); + } + private updateSelectedItemByText() { + this._text = this.input.nativeElement.value; + let originSelectedItem = this.selectedItem; + if (!this.readonly && this.source && this.allowSearch) { + var newValue = null; + var inputText = StringUtils.toUpperString(this.text); + var foundByInput = this.availableOptions.find(o => StringUtils.toUpperString(o[this.propDisplay]) == inputText); + + //Find again with Key Value + if (!foundByInput) { + foundByInput = this.availableOptions.find(o => StringUtils.toUpperString(o[this.matchWith]) == inputText); + } + + if (this.usingFocusIndex || (!foundByInput && this.menuIsOpen && this.items.length > 0)) { + foundByInput = this.items[this.focusIndex]; + //this.firstMatchOption = null; + } + + if (!foundByInput) { + if (this.mustMatch) { + //Reset text to current value if mustMatch is true and no result + this.textInputNoMatched(this.text); + inputText = this.selectedItem[this.propDisplay]; + } + else { + inputText = this.text; + newValue = inputText; + //Set inputText as option to selected item + this.selectedItem = new DropDownOption(newValue, inputText); + this.selectedIndex = -1; + } + } + else { + //Have matched option with text input + this.selectedItem = foundByInput; + this.selectedIndex = this.source.indexOf(this.selectedItem); + newValue = this.selectedItem[this.propKey]; + inputText = this.selectedItem[this.propDisplay]; + } + + if ((newValue != null || false == this.mustMatch) && newValue != this.value) { + + if (this.beforeChange.observers.length == 0) { + + let previousValue = this.value; + this.value = newValue; + this.selectedChange.emit(this.value); + this.selectedChangeFrom.emit({ previousValue, currentValue: this.value }); + } + else { + this.beforeChange.emit(newValue); + } + } + + if (this.beforeChange.observers.length == 0) { + if (!this.clearOption || inputText != TEXT_CLEAR) { + this.text = inputText; + } + } + else { + this.selectedItem = originSelectedItem; + this.text = this.selectedItem[this.propDisplay]; + } + } + } + + private textInputNoMatched(inputValue: string) { + this.inputTextNotMatched.next(this.text); + if (false == StringUtils.isNullOrWhitespace(this.text)) { + let tempMsg = this.inputNotMatchedTooltip.replace("$inputText", inputValue.toUpperCase()); + //this.tooltips.first.content = `"${inputValue}" does not exist`; + this.rbjTooltipService.show(this.input, tempMsg, this.uuid, 3, NbPosition.TOP_START); + + } + } + + public onKeyDown(e: KeyboardEvent): void { + switch (e.key) { + case 'Enter': + this.updateSelectedItemByText(); + this.hide(); + return; + break; + + case 'ArrowDown': + case 'ArrowUp': + if (this.menuIsOpen) { + if (e.key == "ArrowUp") { + this.focusIndex = this.focusIndex > 0 ? this.focusIndex - 1 : this.focusIndex; + } + else { + this.focusIndex = this.focusIndex == this.items.length - 1 ? this.focusIndex : this.focusIndex + 1 + } + + this.dropDownListService.setFocusIndex(this.uuid, this.focusIndex); + this.scrollingMenuByIndex(this.focusIndex); + this.usingFocusIndex = true; + } + else { + this.toggle(); + } + return; + break; + + case 'Tab': + this.isFocused = false; + //use time out to let tab out support select the last index user choose from menu + setTimeout(() => { + this.hide(); + }, 100); + return; + + break; + + default: + break; + } + if (TextareaUtils.isEditingKeyPress(e)) { + if (this.allowSearch) { + if (this.isFocused && false == this.menuIsOpen) { + this.toggle(false); + } + } + this.refreshMenuDebounceTimer.resetTimer(); + } + } + +} + diff --git a/APP/src/components/drop-down-list/drop-down-list.module.ts b/APP/src/components/drop-down-list/drop-down-list.module.ts new file mode 100644 index 0000000..f535eb6 --- /dev/null +++ b/APP/src/components/drop-down-list/drop-down-list.module.ts @@ -0,0 +1,32 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { DropDownListComponent } from './drop-down-list.component'; +import { FormsModule } from '@angular/forms'; +import { NbInputModule, NbSelectModule, NbMenuModule, NbContextMenuModule, NbButtonModule, NbIconModule, NbCardModule, NbTooltipModule } from '@nebular/theme'; +import { MaskDirectiveModule } from '../../directives/mask-directive/mask-directive.module'; +import { DropDownMenuComponent } from './drop-down-menu/drop-down-menu.component'; + + +@NgModule({ + declarations: [ + DropDownListComponent, + DropDownMenuComponent, + ], + imports: [ + CommonModule, + FormsModule, + NbInputModule, + NbSelectModule, + NbMenuModule, + NbContextMenuModule, + NbButtonModule, + NbIconModule, + NbCardModule, + NbTooltipModule, + MaskDirectiveModule + ], + exports: [ + DropDownListComponent, + ] +}) +export class DropDownListModule { } diff --git a/APP/src/components/drop-down-list/drop-down-list.service.spec.ts b/APP/src/components/drop-down-list/drop-down-list.service.spec.ts new file mode 100644 index 0000000..5c90191 --- /dev/null +++ b/APP/src/components/drop-down-list/drop-down-list.service.spec.ts @@ -0,0 +1,12 @@ +import { TestBed } from '@angular/core/testing'; + +import { DropDownListService } from './drop-down-list.service'; + +describe('DropDownListService', () => { + beforeEach(() => TestBed.configureTestingModule({})); + + it('should be created', () => { + const service: DropDownListService = TestBed.get(DropDownListService); + expect(service).toBeTruthy(); + }); +}); diff --git a/APP/src/components/drop-down-list/drop-down-list.service.ts b/APP/src/components/drop-down-list/drop-down-list.service.ts new file mode 100644 index 0000000..2c70db2 --- /dev/null +++ b/APP/src/components/drop-down-list/drop-down-list.service.ts @@ -0,0 +1,24 @@ +import { Injectable } from '@angular/core'; +import { Observable, Subject } from 'rxjs'; +import { DropDownOption } from '../../entity/dropDownOption'; +import { DropDownBag } from './drop-down-menu/drop-down-menu.component'; + +@Injectable({ + providedIn: 'root' +}) +export class DropDownListService { + + constructor() { } + onItemSelect = new Subject(); + + focusIndexChange = new Subject(); + + + selectItem(tag: string, item: DropDownOption) { + this.onItemSelect.next({ tag: tag, item: item } as DropDownBag) + } + setFocusIndex(tag: string, i: number) { + this.focusIndexChange.next( + { tag: tag, focusIndex: i } as DropDownBag); + } +} diff --git a/APP/src/components/drop-down-list/drop-down-menu/drop-down-menu.component.html b/APP/src/components/drop-down-list/drop-down-menu/drop-down-menu.component.html new file mode 100644 index 0000000..6bbf244 --- /dev/null +++ b/APP/src/components/drop-down-list/drop-down-menu/drop-down-menu.component.html @@ -0,0 +1,15 @@ + + + + +
    + +
  • + +
    +
+
+
\ No newline at end of file diff --git a/APP/src/components/drop-down-list/drop-down-menu/drop-down-menu.component.scss b/APP/src/components/drop-down-list/drop-down-menu/drop-down-menu.component.scss new file mode 100644 index 0000000..3894851 --- /dev/null +++ b/APP/src/components/drop-down-list/drop-down-menu/drop-down-menu.component.scss @@ -0,0 +1,87 @@ +@import "../../../@theme/styles/themes"; + +@include nb-install-component() { + .context-menu { + background-color: nb-theme(background-basic-color-1); + box-shadow: nb-theme(shadow); + } + + ul { + display: block; + list-style-type: none; + padding: 0; + margin: 0; + li { + font-size: 0.9375rem; + font-weight: 600; + line-height: 1.5rem; + padding: 0.4375rem 1rem; + cursor: pointer; + &.focus { + background-color: nb-theme(color-basic-transparent-300); + color: nb-theme(text-basic-color); //#222b45 + outline: none; + } + &:hover { + background-color: nb-theme(color-basic-transparent-200); + color: nb-theme(text-basic-color); + } + &.selected { + background-color: nb-theme(color-primary-500); + color: nb-theme(text-control-color); + } + &:hover.selected { + background-color: nb-theme(color-primary-400); + color: nb-theme(text-control-color); + } + &.disabled { + color: nb-theme(text-disabled-color) !important; + } + } + } +} +:host { + border: 0 solid transparent; + border-radius: 0.25rem; + //box-shadow: 0 0.5rem 1rem 0 rgba(44, 51, 73, 0.1); + color: #192038; + font-family: Open Sans, sans-serif; + font-size: 0.9375rem; + font-weight: 400; + line-height: 1.25rem; + margin-bottom: 1.875rem; + scrollbar-face-color: #e4e9f2; + scrollbar-track-color: #f7f9fc; + .context-menu { + -webkit-animation-name: fadeFromTop; /* Chrome, Safari, Opera */ + -webkit-animation-duration: 0.25s; /* Chrome, Safari, Opera */ + animation-name: fadeFromTop; + animation-duration: 0.25s; + max-height: 300px; + overflow-y: auto !important; + } +} + +/* Add animation (Chrome, Safari, Opera) */ +@-webkit-keyframes fadeFromTop { + from { + max-height: 0px; + } + to { + max-height: 300px; + } +} + +/* Add animation (Standard syntax) */ +@keyframes fadeFromTop { + from { + max-height: 0px; + } + to { + max-height: 300px; + } +} + +// .rbjDropdownMenuItem:empty::before{ +// content: "\200b"; /* unicode zero width space character */ +// } \ No newline at end of file diff --git a/APP/src/components/drop-down-list/drop-down-menu/drop-down-menu.component.spec.ts b/APP/src/components/drop-down-list/drop-down-menu/drop-down-menu.component.spec.ts new file mode 100644 index 0000000..375b898 --- /dev/null +++ b/APP/src/components/drop-down-list/drop-down-menu/drop-down-menu.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; + +import { DropDownMenuComponent } from './drop-down-menu.component'; + +describe('DropDownMenuComponent', () => { + let component: DropDownMenuComponent; + let fixture: ComponentFixture; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [ DropDownMenuComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(DropDownMenuComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/APP/src/components/drop-down-list/drop-down-menu/drop-down-menu.component.ts b/APP/src/components/drop-down-list/drop-down-menu/drop-down-menu.component.ts new file mode 100644 index 0000000..d381981 --- /dev/null +++ b/APP/src/components/drop-down-list/drop-down-menu/drop-down-menu.component.ts @@ -0,0 +1,103 @@ +import { Component, OnInit, Input, HostListener, Renderer2 } from '@angular/core'; +import { NbPositionedContainerComponent, NbRenderableContainer, NbMenuItem } from '@nebular/theme'; +import { Subscription } from 'rxjs'; +import { filter, map } from 'rxjs/operators'; +import { DropDownOption } from '../../../entity/dropDownOption'; +import { DropDownListService } from '../drop-down-list.service'; + +@Component({ + selector: 'ngx-drop-down-menu', + templateUrl: './drop-down-menu.component.html', + styleUrls: ['./drop-down-menu.component.scss'] +}) +export class DropDownMenuComponent extends NbPositionedContainerComponent implements NbRenderableContainer { + + // [style.width.px]="clientWidth" + + private _context: menuSetting; + private _indexSubscription: Subscription; + clientWidth: number = 0; + items: DropDownOption[] = []; + private listener: any; + constructor(private dropDownListService: DropDownListService, + private renderer: Renderer2) { + super(); + + } + + ngOnInit() { + setTimeout(() => { + this.listener = this.renderer.listen('window', 'click', (e: Event) => { + // + var element = (e.target as HTMLElement); + if (typeof element.className === 'string' && element.className.indexOf('rbjDropdownMenu') > -1) { + this.context.focus(); + } else { + this.context.hide(); + this.listener(); + } + }) + }, 50); + + this._indexSubscription = this.dropDownListService.focusIndexChange + + .pipe( + filter(({ tag }) => tag === this._context.tag), + map(({ focusIndex }) => focusIndex), + ) + .subscribe(i => { + this._context.focusIndex = i; + }) + } + ngOnDestroy() { + if (this.listener) { this.listener(); } + if (this._indexSubscription) { + + this._indexSubscription.unsubscribe(); + } + } + public get context(): menuSetting { + return this._context; + } + @Input() public set context(v: menuSetting) { + if (this._context != v) { + this._context = v; + this.clientWidth = document.getElementsByClassName(`div${this._context.tag}`)[0].clientWidth; + this.items = v.items; + } + } + + @HostListener('focus') + focus() { + this.context.focus(); + } + + renderContent() { + + } + + choose(item: DropDownOption) { + + if (!item[this.context.propDisabled]) { + this.dropDownListService.selectItem(this.context.tag, item); + this.context.hide(); + } + } + +} +export interface menuSetting { + items: DropDownOption[], + tag?: string, + class?: string, + propDisplay: string, + propDisabled: string, + focusIndex: number + hide: () => {}, + focus: () => {} +} +export interface DropDownBag { + tag: string, + item: DropDownOption, + focusIndex?: number +} + diff --git a/APP/src/components/email-input/email-input.component.html b/APP/src/components/email-input/email-input.component.html new file mode 100644 index 0000000..8b12bb0 --- /dev/null +++ b/APP/src/components/email-input/email-input.component.html @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/APP/src/components/email-input/email-input.component.scss b/APP/src/components/email-input/email-input.component.scss new file mode 100644 index 0000000..2011e7d --- /dev/null +++ b/APP/src/components/email-input/email-input.component.scss @@ -0,0 +1,4 @@ +:host { + flex: auto; + display: contents; +} diff --git a/APP/src/components/email-input/email-input.component.spec.ts b/APP/src/components/email-input/email-input.component.spec.ts new file mode 100644 index 0000000..0f90842 --- /dev/null +++ b/APP/src/components/email-input/email-input.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; + +import { EmailInputComponent } from './email-input.component'; + +describe('EmailInputComponent', () => { + let component: EmailInputComponent; + let fixture: ComponentFixture; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [ EmailInputComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(EmailInputComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/APP/src/components/email-input/email-input.component.ts b/APP/src/components/email-input/email-input.component.ts new file mode 100644 index 0000000..e35232d --- /dev/null +++ b/APP/src/components/email-input/email-input.component.ts @@ -0,0 +1,74 @@ +import { Component, OnInit, ChangeDetectionStrategy, forwardRef, Input, Output, EventEmitter, ViewChild, ElementRef, Renderer2 } from '@angular/core'; +import { NG_VALUE_ACCESSOR, ControlContainer, NgForm, DefaultValueAccessor } from '@angular/forms'; +import { UuidUtils } from '../../utilities/uuid-utils'; + +@Component({ + selector: 'rbj-email-input', + templateUrl: './email-input.component.html', + styleUrls: ['./email-input.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => EmailInputComponent), + multi: true + } + ], + viewProviders: [{ provide: ControlContainer, useExisting: NgForm }] +}) +export class EmailInputComponent implements OnInit { + + private _value: string; + disabledState: boolean = false; + readOnly: boolean = false; + + @Input() + public get value(): string { + return this._value; + } + + onChange = (value: string) => { }; + onTouched = () => { }; + + uuid: string = UuidUtils.generate(); + @Input() id?= ""; + @Input() placeholder: string; + @Input() class: string; + @Input() size: string = 'medium'; + public set value(v: string) { + if (this._value != v) { + this._value = v; + this.onChange(v) + } + } + + @Input("readonly") + public set input_readOnly(value) { + this.readOnly = typeof value !== "undefined" && value !== false; + } + @ViewChild('inputBox', { static: true }) input: ElementRef; + + constructor( + private renderer: Renderer2, + private el: ElementRef) { } + + ngAfterViewInit() { + this.renderer.removeAttribute(this.el.nativeElement, 'id') + } + writeValue(value: any): void { + this.value = value; + } + + registerOnChange(fn: (value: string) => void): void { + this.onChange = fn; + } + registerOnTouched(fn: any): void { + this.onTouched = fn; + } + setDisabledState(isDisabled: boolean): void { + this.disabledState = isDisabled; + } + + ngOnInit() { + } + +} diff --git a/APP/src/components/email-input/email-input.module.ts b/APP/src/components/email-input/email-input.module.ts new file mode 100644 index 0000000..a786106 --- /dev/null +++ b/APP/src/components/email-input/email-input.module.ts @@ -0,0 +1,20 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { EmailInputComponent } from './email-input.component'; +import { NbInputModule } from '@nebular/theme'; +import { RegexValidatorModule } from '../../directives/regex-validator/regex-validator.module'; +import { FormsModule } from '@angular/forms'; +import { RbjTooltipModule } from '../../directives/rbj-tooltip/rbj-tooltip.module'; + +@NgModule({ + declarations: [EmailInputComponent], + imports: [ + CommonModule, + FormsModule, + NbInputModule, + RegexValidatorModule, + RbjTooltipModule + ], + exports: [EmailInputComponent] +}) +export class EmailInputModule { } diff --git a/APP/src/components/fancy-table/confirm-dialog/confirm-dialog.component.html b/APP/src/components/fancy-table/confirm-dialog/confirm-dialog.component.html new file mode 100644 index 0000000..8c2f804 --- /dev/null +++ b/APP/src/components/fancy-table/confirm-dialog/confirm-dialog.component.html @@ -0,0 +1,9 @@ + + {{ title }} + +
+ + +
+
+
diff --git a/APP/src/components/fancy-table/confirm-dialog/confirm-dialog.component.scss b/APP/src/components/fancy-table/confirm-dialog/confirm-dialog.component.scss new file mode 100644 index 0000000..5a47eb9 --- /dev/null +++ b/APP/src/components/fancy-table/confirm-dialog/confirm-dialog.component.scss @@ -0,0 +1,25 @@ +@import './../../../@theme/styles/themes'; + +@include nb-install-component() { + .buttons-row { + margin: -0.5rem; + } + + button[nbButton] { + margin: 0.5rem; + } + + .action-icon { + @include nb-ltr(margin-right, 0.5rem); + @include nb-rtl(margin-left, 0.5rem); + } + + .actions-card { + height: 8rem; + } + + nb-card { + max-width: 600px; + max-height: 500px; + } +} diff --git a/APP/src/components/fancy-table/confirm-dialog/confirm-dialog.component.spec.ts b/APP/src/components/fancy-table/confirm-dialog/confirm-dialog.component.spec.ts new file mode 100644 index 0000000..384fdf3 --- /dev/null +++ b/APP/src/components/fancy-table/confirm-dialog/confirm-dialog.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; + +import { ConfirmDialogComponent } from './confirm-dialog.component'; + +describe('ConfirmDialogComponent', () => { + let component: ConfirmDialogComponent; + let fixture: ComponentFixture; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [ ConfirmDialogComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ConfirmDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/APP/src/components/fancy-table/confirm-dialog/confirm-dialog.component.ts b/APP/src/components/fancy-table/confirm-dialog/confirm-dialog.component.ts new file mode 100644 index 0000000..e3f4dc2 --- /dev/null +++ b/APP/src/components/fancy-table/confirm-dialog/confirm-dialog.component.ts @@ -0,0 +1,24 @@ +import { Component, Input, Output } from '@angular/core'; +import { NbDialogRef } from '@nebular/theme'; + +@Component({ + selector: 'ngx-confirm-dialog', + templateUrl: './confirm-dialog.component.html', + styleUrls: ['./confirm-dialog.component.scss'] +}) +export class ConfirmDialogComponent { + + @Input() title: string; + + constructor( + protected ref: NbDialogRef, + ) { } + + cancel() { + this.ref.close(); + } + + delete(confirm) { + this.ref.close(confirm); + } +} diff --git a/APP/src/components/fancy-table/custom-columns-dlg/custom-columns-dlg.component.html b/APP/src/components/fancy-table/custom-columns-dlg/custom-columns-dlg.component.html new file mode 100644 index 0000000..df92ed7 --- /dev/null +++ b/APP/src/components/fancy-table/custom-columns-dlg/custom-columns-dlg.component.html @@ -0,0 +1,3 @@ +

+ custom-columns-dlg works! +

diff --git a/APP/src/components/fancy-table/custom-columns-dlg/custom-columns-dlg.component.scss b/APP/src/components/fancy-table/custom-columns-dlg/custom-columns-dlg.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/APP/src/components/fancy-table/custom-columns-dlg/custom-columns-dlg.component.spec.ts b/APP/src/components/fancy-table/custom-columns-dlg/custom-columns-dlg.component.spec.ts new file mode 100644 index 0000000..eb0661e --- /dev/null +++ b/APP/src/components/fancy-table/custom-columns-dlg/custom-columns-dlg.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; + +import { CustomColumnsDlgComponent } from './custom-columns-dlg.component'; + +describe('CustomColumnsDlgComponent', () => { + let component: CustomColumnsDlgComponent; + let fixture: ComponentFixture; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [ CustomColumnsDlgComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(CustomColumnsDlgComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/APP/src/components/fancy-table/custom-columns-dlg/custom-columns-dlg.component.ts b/APP/src/components/fancy-table/custom-columns-dlg/custom-columns-dlg.component.ts new file mode 100644 index 0000000..43f0644 --- /dev/null +++ b/APP/src/components/fancy-table/custom-columns-dlg/custom-columns-dlg.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'rbj-custom-columns-dlg', + templateUrl: './custom-columns-dlg.component.html', + styleUrls: ['./custom-columns-dlg.component.scss'] +}) +export class CustomColumnsDlgComponent implements OnInit { + + constructor() { } + + ngOnInit() { + } + +} diff --git a/APP/src/components/fancy-table/fancy-group-menu/fancy-group-menu.component.html b/APP/src/components/fancy-table/fancy-group-menu/fancy-group-menu.component.html new file mode 100644 index 0000000..4f8fa50 --- /dev/null +++ b/APP/src/components/fancy-table/fancy-group-menu/fancy-group-menu.component.html @@ -0,0 +1,18 @@ +
+ + +
+ + +
+
\ No newline at end of file diff --git a/APP/src/components/fancy-table/fancy-group-menu/fancy-group-menu.component.scss b/APP/src/components/fancy-table/fancy-group-menu/fancy-group-menu.component.scss new file mode 100644 index 0000000..1940c5c --- /dev/null +++ b/APP/src/components/fancy-table/fancy-group-menu/fancy-group-menu.component.scss @@ -0,0 +1,91 @@ +.fancyGroupMenuContainer { + width: inherit; + position: sticky; + top: 0px; +} +.dx-tag-remove-button { + top: 0; + right: 10px; +} +::-webkit-scrollbar { + width: 0.3rem !important; +} +.fancyGroupMenu { + position: relative; + cursor: pointer; + display: flex; + flex-direction: column; + padding-left: 0; + margin-bottom: 0; + width: 220px; + max-height: 67vh; + overflow-y: auto; + padding-bottom: 1px; + &::before { + bottom: 0; + //background-color: rgba(0, 0, 0, 0.2); + //background-color: var(--palette-black-alpha-20, rgba(0, 0, 0, 0.2)); + content: ""; + left: 0; + position: absolute; + top: 0; + width: 3px; + height: inherit; + } + + .menu-item { + display: block; + padding: 0.75rem 1.25rem; + margin-bottom: -1px; + position: relative; + cursor: pointer; + &:hover { + //background-color: rgba(3, 169, 244, 0.08); + background-color: rgba(240, 91, 65, 0.12); + &.altColor { + background-color: rgba(0, 0, 0, 0.04); + } + } + &.hasData { + color: rgba(240, 91, 65, 1); + &.altColor { + color: rgba(60, 186, 178, 1); + } + } + &.active { + border-radius: 2px; + //color: rgba(0, 90, 158, 1); + //color: var(--communication-foreground, rgba(0, 90, 158, 1)); + //color: #03a9f4; + color: rgba(255, 255, 255, 1) !important; + background-color: rgba(240, 91, 65, 1); + font-weight: 600; + transition: + color 80ms cubic-bezier(0.165, 0.84, 0.44, 1), + background 80ms linear; + &::before { + //background-color: #0078d4; + //background-color: var(--communication-background, #0078d4); + content: ""; + left: 0px; + top: 0px; + position: absolute; + width: 3px; + height: 100%; + } + &.altColor { + background-color: rgba(60, 186, 178, 1); + } + } + } + &.dropHover .menu-item:hover { + border-color: rgba(240, 91, 65, 1); + &.altColor { + border-color: rgba(60, 186, 178, 1); + } + border: nb-theme(border-basic-color-5); + border-style: dashed; + border-width: medium; + cursor: copy; + } +} diff --git a/APP/src/components/fancy-table/fancy-group-menu/fancy-group-menu.component.spec.ts b/APP/src/components/fancy-table/fancy-group-menu/fancy-group-menu.component.spec.ts new file mode 100644 index 0000000..cd4abbd --- /dev/null +++ b/APP/src/components/fancy-table/fancy-group-menu/fancy-group-menu.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FancyGroupMenuComponent } from './fancy-group-menu.component'; + +describe('FancyGroupMenuComponent', () => { + let component: FancyGroupMenuComponent; + let fixture: ComponentFixture>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [FancyGroupMenuComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(FancyGroupMenuComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/APP/src/components/fancy-table/fancy-group-menu/fancy-group-menu.component.ts b/APP/src/components/fancy-table/fancy-group-menu/fancy-group-menu.component.ts new file mode 100644 index 0000000..e401c32 --- /dev/null +++ b/APP/src/components/fancy-table/fancy-group-menu/fancy-group-menu.component.ts @@ -0,0 +1,95 @@ +import { ChangeDetectionStrategy, Component, inject, Injector, Input, NgZone, Output } from '@angular/core'; +import { FancyGroup } from '../fancy-row-column.model'; +import { Subject } from 'rxjs'; +import { FancySettings } from '../fancy-settings.model'; +import { DebounceTimer } from '../../../utilities/timer-utils'; +import { DropFileEvent } from '../../../shared/file-drag-drop/file-drag-drop.directive'; + +@Component({ + selector: 'rbj-fancy-group-menu', + templateUrl: './fancy-group-menu.component.html', + styleUrl: './fancy-group-menu.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class FancyGroupMenuComponent { + injector = inject(Injector); + ngZone = inject(NgZone); + @Input() usingAltColor = false; + deletingGroup = false; + + private clearDroppingOnGroupKeyTimer = new DebounceTimer(100, () => { + this._droppingOnGroupKey = null; + this.droppingOnGroupKeyChange.next(null); + }) + @Input() groups: FancyGroup[]; + + @Input() settings: FancySettings; + private _draggingRow: boolean; + public get draggingRow(): boolean { + return this._draggingRow; + } + @Input() public set draggingRow(v: boolean) { + if (this._draggingRow != v) { + this._draggingRow = v; + this.draggingRowChange.next(v); + } + } + @Output() draggingRowChange = new Subject() + + @Output() itemDropInGroup = new Subject(); + + private _droppingOnGroupKey: string; + public get droppingOnGroupKey(): string { + return this._droppingOnGroupKey; + } + @Input() public set droppingOnGroupKey(v: string) { + if (this._droppingOnGroupKey != v) { + this._droppingOnGroupKey = v; + this.droppingOnGroupKeyChange.next(this.droppingOnGroupKey); + } + } + @Input() filteringGroupKey: string; + @Output() droppingOnGroupKeyChange = new Subject(); + @Output() onFileDroppedInGroup = new Subject>(); + + @Output() clickOnGroup = new Subject>(); + @Output() clickOnDeleteGroup = new Subject>(); + + constructor() { + + } + + public onGroupListMouseUp(group: FancyGroup, $event: MouseEvent) { + if (!this.deletingGroup) { + + this.droppingOnGroupKey = group.groupKey; + if (this.draggingRow) { + this.itemDropInGroup.next(group.groupKey); + //this.draggingRow = false; + } + setTimeout(() => { + this.draggingRow = false; + this.droppingOnGroupKey = null; + }, 500); + } + } + + fileDroppedInGroup(group: FancyGroup, event: DropFileEvent) { + if (group) { + group.uiFileDropList = event.files; + this.onFileDroppedInGroup.next(group); + } + } + clickGroup(group: FancyGroup) { + if (!this.deletingGroup) { + this.clickOnGroup.next(group); + } + } + removeGroup(group: FancyGroup) { + this.deletingGroup = true; + setTimeout(() => { + this.deletingGroup = false; + }, 50); + this.clickOnDeleteGroup.next(group); + } +} diff --git a/APP/src/components/fancy-table/fancy-row-column.model.ts b/APP/src/components/fancy-table/fancy-row-column.model.ts new file mode 100644 index 0000000..dfd2095 --- /dev/null +++ b/APP/src/components/fancy-table/fancy-row-column.model.ts @@ -0,0 +1,185 @@ +import { SafeHtml } from "@angular/platform-browser"; +import { DropDownOption } from "../../entity/dropDownOption"; +import { TemplateRef } from "@angular/core"; + +export enum FtEditableType { + NONE = 'none', + READONLY = 'readOnly', + EDITABLE = 'editable', +} + +export enum FtTrigger { + NOOP = 'noop', + DOUBLE_CLICK = 'doubleClick', +} +export declare type FtTagType = 'info' | 'primary' | 'danger' | 'warning' | 'success' | 'secondary' | 'light' | 'dark'; +export declare type FtColType = 'text' | 'date' | 'dateTime' | 'dropdown' | 'number' | 'checkbox' | 'checkall' | 'currency' | 'tag' | 'template'; +export declare type FtFilterMode = 'startsWith' | 'contains' | 'allkeywords'; +export class FancyColumn { + public name: string; + public title: string; + + /** + * Col data type, Defaults to text, available types: + * text, date, dateTime, dropdown, number, checkbox, checkall, currency, tag + */ + public type?: FtColType = 'text'; // 'text', 'date', 'dropdown' etc. Defaults to 'text' + public dropdownSource?: DropDownOption[]; + public class?: string; // apply this class to the cells + //public sort?: boolean; + + /** + * Sort by this column, available types:'asc', 'desc' + */ + public sortDirection?: string; + public filter?: any; + + /** + * Col filter mode, Defaults to contains, available types: + * startsWith, contains, allkeyswords + */ + public filterMode?: FtFilterMode = 'contains'; + /** + * Col filter case sensitive setting, Defaults to `true` + */ + public filterIgnoreCase?: boolean = true; + public filterPreparedValue?: boolean = false; + public dateRange?: any; + public group?: boolean; + + /** + * Col visible setting, Defaults to `true` + */ + public visible?: boolean = true; + /** + * Disable column hover tooltip, Defaults to `false` + */ + public noToolTip?: boolean = false; + public editableType?: FtEditableType = FtEditableType.NONE; + public editingFieldIndex?: number; + public editingFieldWidth?: number; + public footerClass?: string; // apply this class to the cells in the footer + public valuePrepareFunction?: (value: T) => string | SafeHtml | number | TemplateRef; + /** + * `(optional)` Preparation function for tooltip, if not exist will using same value with col value + */ + public toolTipPrepareFunction?: (value: T) => string; + public groupKeyPrepareFunction?: (value: T) => string; + public sortFunction?: (a: T, b: T) => number; + public filterFunction?: (value: FancyDisplayRow, input) => boolean; + public tagsPrepareFunction?: (value: T) => FancyTag[]; + + /** + * apply the width px to column 0 for auto width + */ + public widthPx?: number; + /** + * apply the width rem to column 0 for auto width, will overwrite `widthPx` + */ + public widthRem?: number; + /** + * apply the width % to column 0 for auto width, will overwrite `widthRem` and `widthPx` + */ + public widthPct?: number; + + + /** + * maxWidth for text wrapping, calculated by `widthPct`, do NOT use this for now! + */ + public maxWidth?: string; + + public initFocusFilterInput?: boolean = false; + + /** + * the tagging field name for `checkall` column type to track checked order + */ + public taggingTarget?: string = null; + + constructor(config: Partial) { + Object.assign(this, config); + } + /** + * When `allowDragAndDrop` is enabled, then this column is draggable, default as `true` + */ + public draggable?: boolean = true; + + /** + * Set `td colspan`, currently only works in footer + */ + public colspan?: number = 1; + + uiCheckAll?: boolean = false; + uiWidth?: string; +} + +export class FancyTag { + + + /** + * status could be `info` , `primary` , `danger` , `warning` , `success` + */ + constructor(text: string, status: FtTagType, actionTag: string = null, tooltip = null) { + this.text = text; + this.status = status; + this.tooltip = tooltip || text; + this.actionTag = actionTag; + } + + text: string; + tooltip: string; + status: FtTagType; + + actionTag: string; +} + +export class FancyDisplayRow { + + index: number; + seq: number; + /** + * Sequence number for UI only + */ + uiSeq: number; + isGroupHeader: boolean; + groupKey?: any; + colHtmlTexts: any; + colTooltips: any; + rowDatum: T; + visible: boolean; + /** + * this is for custom groups to store row id for many to many relationships. + */ + customRowId: string; + + /** + * this only has value when file drop to the row + */ + uiFileDropList: FileList | File[]; +} +export class FancyGroup { + seq: number; + /** + * Origin value type of groupKey + */ + groupKey: any; + /** + * Formatted string value of groupValue + */ + groupValue: any; + rows: FancyDisplayRow[]; + expanded: boolean; + /** + * this only has value when file drop to the Group + */ + uiFileDropList: FileList | File[]; + uiAllowDeleteWhenEmpty: boolean; +} + +export class FancyColumnCustomSetting { + id: string + name: string + seq: number + visible: boolean + groupBy: boolean + widthPx: number +} \ No newline at end of file diff --git a/APP/src/components/fancy-table/fancy-selection.model.ts b/APP/src/components/fancy-table/fancy-selection.model.ts new file mode 100644 index 0000000..293f090 --- /dev/null +++ b/APP/src/components/fancy-table/fancy-selection.model.ts @@ -0,0 +1,22 @@ +import { FancyDisplayRow, FancyGroup } from "./fancy-row-column.model"; + +export class FancySelection { + index: number; + focusedCustomRowId: string; + focusedRow: T; + selectedRows: T[]; +} + +export class FancyEditorInstance { + index: number; + object: T; +} +export class FancyDragDrop { + previousIndex: number; + currentIndex: number; + focusRow: FancyDisplayRow; + previousRow?: FancyDisplayRow; + nextRow?: FancyDisplayRow; + previousGroup?: FancyGroup; + currentGroup?: FancyGroup; +} \ No newline at end of file diff --git a/APP/src/components/fancy-table/fancy-settings.model.ts b/APP/src/components/fancy-table/fancy-settings.model.ts new file mode 100644 index 0000000..5ffb9a8 --- /dev/null +++ b/APP/src/components/fancy-table/fancy-settings.model.ts @@ -0,0 +1,188 @@ +import { Observable, Subject } from 'rxjs'; +import { FancyColumn, FancyGroup, FtTrigger } from './fancy-row-column.model'; +import { FancyEditorInstance, FancySelection } from './fancy-selection.model'; +import { EventEmitter } from '@angular/core'; +import { ContextMenuItem } from '../../directives/right-click-menu/context-menu-item.model'; + +export const isFancyGroup = (o: T | FancyGroup): o is FancyGroup => { + return o && o.hasOwnProperty("groupKey") && typeof (o as FancyGroup).groupKey === "string"; +} +export const isNotFancyGroup = (o: T | FancyGroup): o is T => { + return false == (o && o.hasOwnProperty("groupKey") && typeof (o as FancyGroup).groupKey === "string"); +} +export class FancySettings { + + /** + * Set width to 100% + */ + public autoWidth?: boolean = true; + /** + * Displaying titles of each column, default as `true` + */ + public showHeader?: boolean = true; + /** + * Displaying footer, default as `false` + */ + public showFooter?: boolean = false; + /** + * Enable sticky footer, default as `false` + */ + public stickyFooter?: boolean = false; + /** + * Displaying filter of each column, default as `true` + */ + public showFilters?: boolean = true; + /** + * Displaying group side menu when group by a column, default as `false` + */ + public showGroupsMenu?: boolean = false; + + /** + * Allow user selecting row and hight light it, default as `true` + */ + public allowSelection?: boolean = true; + /** + * Allow user drag and drop row, default as `false` + */ + public allowDragAndDrop?: boolean = false; + /** + * Allow user drag and drop row, default as `false` + */ + public allowDrag?: boolean = false; + /** + * When allow user drag and drop row, using drag icon instead of drag all row, default as `false` + */ + public usingDragIcon?: boolean = false; + /** + * Allow user selecting multi rows, default as `false` + */ + public multiselect?: boolean = false; + /** + * Allow user resizing columns, default as `true` + */ + public resizingColumn?: boolean = true; + + /** + * Allow user dropping files, default as `false` + */ + public enableFileDrop?: boolean = false; + + private _columns?: FancyColumn[]; + public get columns(): FancyColumn[] { + return this._columns; + } + public set columns(v: FancyColumn[]) { + if (v) { + for (let i = 0; i < v.length; i++) { + v[i] = new FancyColumn({ ...v[i] }); + } + } + this._columns = v; + } + + // public groups: FancyGroup[]; + public contextMenuItems?: ContextMenuItem[]; + public onContextMenuOpening?: (datum: T | FancyGroup, rightClickMenuItems: ContextMenuItem[], selection: FancySelection, event: MouseEvent) => void; + public editorTrigger?: FtTrigger = FtTrigger.NOOP; + + public groupMissing?: string = 'N/A'; + + /** + * Using custom groups object list + */ + public customGroups?: any[] = null; + /** + * The group key field name of custom groups + */ + public customGroupKeyFieldName?: string = 'id'; + /** + * The group value field name of custom groups + */ + public customGroupValueFieldName?: string = 'name'; + /** + * The group seq field name of custom groups + */ + public customGroupSeqFieldName?: string = 'seq'; + + /** + * The group rows field name of custom groups + */ + public customGroupRowsFieldName?: string = ''; + + /** + * Group sort rule, available types:'asc', 'desc' + */ + public groupSortDirection?: string = 'asc'; + public groupSortFunction?: (a: FancyGroup, b: FancyGroup) => number; + + /** + * For generating Group Row Description + */ + public groupSeqPrepareFunction?: (groupKey: any) => number; + /** + * For generating Group Row Description + */ + public groupValuePrepareFunction?: (groupKey: any, groupValues: T[]) => string; + + /** + * Display group without data + */ + public displayEmptyGroup?: boolean = false; + /** + * initial rows count for infinite scrolling, default as 50 + */ + public initialRows?: number = 50; + /** + * append rows count for infinite scrolling, default as 10 + */ + public appendRowsOnScroll?: number = 10; + /** + * Smooth scroll duration when using scrollToItem. Set to 0 to disable smooth scrolling. + */ + public smoothScrollDurationMs?: number = 0; + + /** + * adding validation for build-in editor, return `Observable` error message, if valid return `null` + */ + public addingValidation?: (obj: FancyEditorInstance) => Observable | string; + /** + * editing validation for build-in editor, return `Observable` error message, if valid return `null` + */ + public editingValidation?: (obj: FancyEditorInstance) => Observable | string; + /** + * deleting validation for build-in editor, return `Observable` error message, if valid return `null` + */ + public deletingValidation?: (obj: FancyEditorInstance) => Observable | string; + + + /** + * the call back buttons for easy table picker + */ + public headerCallbackButtons?: ContextMenuItem[]; + + /** + * Reload subject must be assigned from component setting + */ + public reloadSubject?: Subject = null; + + /** + * Reassign custom group and reload table,Reload subject must be assigned from component setting + */ + public reloadSettingSubject?: Subject> = null; + + /** + * The tagged row target field values, it's for `checkall` column type + */ + public taggedValues?: any[] = []; + /** + * The tagged row custom row data, it's for `checkall` column type + */ + public taggedRowData?: T[] = []; + + public dialogCssClass?: string = null; + + + constructor(config: Partial>) { + Object.assign(this, config); + } +} diff --git a/APP/src/components/fancy-table/fancy-table.component.html b/APP/src/components/fancy-table/fancy-table.component.html new file mode 100644 index 0000000..8fc9fb1 --- /dev/null +++ b/APP/src/components/fancy-table/fancy-table.component.html @@ -0,0 +1,239 @@ +
+ +
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ {{column.title}} + + +
+ + +
+ + + + + + + + + + + + + + + +
+ + +
+
+ + + All + Disabled + Enabled + + + + + +
+ +
+ + +
+ + +
+ +
+
+
+ + {{(settings.taggedValues.indexOf(row.rowDatum[column.taggingTarget])+1)}} + + + +
+
+
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ + + + + No Match Found + +

+ Tip: + Please try another keyword. +

+
+
+ + +
+
+
+
+
+
+ +
+
+ +
+
+ + + + +
+ + + \ No newline at end of file diff --git a/APP/src/components/fancy-table/fancy-table.component.scss b/APP/src/components/fancy-table/fancy-table.component.scss new file mode 100644 index 0000000..a00caf8 --- /dev/null +++ b/APP/src/components/fancy-table/fancy-table.component.scss @@ -0,0 +1,244 @@ +@import "../../@theme/styles/themes"; + +@include nb-install-component() { + table.fancy { + table-layout: fixed; + border-collapse: separate !important; + border: 1px solid nb-theme(border-basic-color-5); + box-shadow: nb-theme(shadow); + border-top: 0px; + user-select: none; + border-spacing: 0; + table tbody, + table thead { + display: block; + } + + th:first-child, + td:first-child { + /* Apply a left border on the first or in a row */ + border-left: 1px solid nb-theme(border-basic-color-5); + } + td { + //display: block; + text-overflow: ellipsis; + //border: 1px solid nb-theme(border-basic-color-5); + padding: 5px 0.5rem; //0.5rem; + background: transparent; + border-bottom: 1px solid nb-theme(border-basic-color-5); + border-right: 1px solid nb-theme(border-basic-color-5); + } + + tr.grouprow > td { + padding: 0.5rem; + } + tr.groupedItem > td { + padding: 0 0.5rem; + &.checkbox { + padding: 0.3rem 0.5rem; + } + } + td > div { + //width: 50px; + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + tr { + height: nb-theme(text-paragraph-font-size); + white-space: nowrap; + overflow: hidden; + } + + tr.grouprow { + color: nb-theme(text-alternate-color); + background: nb-theme(color-primary-300) !important; + cursor: pointer; + user-select: none; + } + + tr.collapsed { + display: none; + } + + tbody { + tr:hover { + background: nb-theme(background-basic-color-4); + } + + tr.selected { + color: nb-theme(text-alternate-color); + background: nb-theme(color-info-300); + } + + tr.focused { + // border: 2px dotted nb-theme(color-info-900); + border: 2px dotted nb-theme(text-basic-color); + + td { + border-top: 2px dotted nb-theme(text-basic-color); + border-bottom: 2px dotted nb-theme(text-basic-color); + } + td:first-child { + border-left: 2px dotted nb-theme(text-basic-color); + } + td:last-child { + border-right: 2px dotted nb-theme(text-basic-color); + } + } + } + + tbody tr:nth-child(even):not(:hover):not(.selected) { + background: nb-theme(background-basic-color-1); + } + + tbody tr:nth-child(odd):not(:hover):not(.selected) { + background: nb-theme(background-basic-color-2); + } + thead { + th { + background: white; + position: sticky; + top: 0; + //top: -16px; + box-shadow: 0 2px 2px -1px rgba(0, 0, 0, 0.4); + height: 2.4rem; + padding: 0 5px; + background: nb-theme(background-basic-color-1); + border-bottom: 1px solid nb-theme(border-basic-color-5); + border-right: 1px solid nb-theme(border-basic-color-5); + z-index: 100; + } + tr:nth-child(1) th { + border-top: 2px solid nb-theme(border-basic-color-5); + cursor: pointer; + user-select: none; + } + tr:nth-child(2) th { + top: calc(2.4rem); + height: 3rem; + } + } + tfoot.stickyFooter { + td { + position: sticky; + bottom: 0; + box-shadow: 0 -2px 2px -1px rgba(0, 0, 0, 0.4); + height: 2.4rem; + padding: 0 5px; + background: nb-theme(background-basic-color-1); + border-top: 1px solid nb-theme(border-basic-color-5); + border-right: 1px solid nb-theme(border-basic-color-5); + z-index: 100; + } + tr:nth-child(1) td { + border-bottom: 2px solid nb-theme(border-basic-color-5); + } + } + } +} +table.fancy.overflow-x { + table-layout: auto; +} +// .searchclear { +// position: absolute; +// right: 5px; +// top: 0; +// height: 14px; +// margin: auto; +// font-size: 14px; +// cursor: pointer; +// } +::ng-deep nb-checkbox.ftCheck { + width: 20px; + display: block; + > .label { + cursor: pointer; + } +} +.ftCheckBadge { + height: 17px; + margin-top: 3px; +} +.cdk-drag-preview { + box-sizing: border-box; + border-radius: 4px; + box-shadow: + 0 5px 5px -3px rgba(0, 0, 0, 0.2), + 0 8px 10px 1px rgba(0, 0, 0, 0.14), + 0 3px 14px 2px rgba(0, 0, 0, 0.12); +} + +.cdk-drag-placeholder { + opacity: 0; +} + +.cdk-drag-animating { + transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); +} +.tableContainer { + height: 100%; + max-height: 100%; //calc(80vh - 20px); + overflow-y: auto; + overflow-x: hidden; + &.colResizing { + cursor: ew-resize !important; + } +} +.tableContainer.overflow-x { + overflow-x: auto; +} + +:host { + //height: 100%; + //display: block; + //overflow: hidden; +} +.bottomShadow { + box-shadow: 0 2px 1rem 10px rgba(44, 51, 73, 0.25); + position: relative; + bottom: 4px; + width: 98%; + left: 30px; +} + +.dragHandle { + position: absolute; + //background-color: cornflowerblue; + z-index: 2; + transform: translate(0, 0) !important; +} + +.dragHandle.right { + right: 0px; + width: 2px; + height: 100%; + cursor: ew-resize; + top: 0; +} + +.btnClearAllFilter { + /* Add animation */ + -webkit-animation-name: fadeFromTop; /* Chrome, Safari, Opera */ + -webkit-animation-duration: 0.5s; /* Chrome, Safari, Opera */ + animation-name: fadeFromTop; + animation-duration: 0.5s; + + position: absolute; + right: 28px; + margin-top: 41px; + z-index: 1011; + .clearTooltip { + -webkit-transition: width 0.5s ease-in-out; + -moz-transition: width 0.5s ease-in-out; + -o-transition: width 0.5s ease-in-out; + transition: width 0.5s ease-in-out; + width: 0; + overflow: hidden; + } + &:hover .clearTooltip { + padding-left: 6px; + width: 110px; + } +} diff --git a/APP/src/components/fancy-table/fancy-table.component.spec.ts b/APP/src/components/fancy-table/fancy-table.component.spec.ts new file mode 100644 index 0000000..594a387 --- /dev/null +++ b/APP/src/components/fancy-table/fancy-table.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; + +import { FancyTableComponent } from './fancy-table.component'; + +describe('FancyTableComponent', () => { + let component: FancyTableComponent; + let fixture: ComponentFixture; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [ FancyTableComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(FancyTableComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/APP/src/components/fancy-table/fancy-table.component.ts b/APP/src/components/fancy-table/fancy-table.component.ts new file mode 100644 index 0000000..bef146e --- /dev/null +++ b/APP/src/components/fancy-table/fancy-table.component.ts @@ -0,0 +1,1668 @@ +import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy, ChangeDetectorRef, ViewChild, ElementRef, OnInit, HostListener, AfterViewInit } from '@angular/core'; +import { FancySettings } from './fancy-settings.model'; +import { StringUtils } from '../../utilities/string-utils'; +import { LinqUtils } from '../../utilities/linq-utils'; +import { FancyDragDrop, FancyEditorInstance, FancySelection } from './fancy-selection.model'; +import { first, take, takeUntil } from 'rxjs/operators'; +import { UuidUtils } from '../../utilities/uuid-utils'; +import { MsgBoxService } from '../../services/msg-box.service'; +import { ADButtons, ADIcon } from '../alert-dlg/alert-dlg.model'; +import { DebounceTimer } from '../../utilities/timer-utils'; +import { CDK_DRAG_CONFIG, CdkDragDrop, CdkDragEnd, CdkDragMove, CdkDragStart, moveItemInArray, transferArrayItem } from '@angular/cdk/drag-drop'; +import { FancyDisplayRow, FancyGroup, FancyColumn, FancyTag, FtTrigger, FtEditableType, } from './fancy-row-column.model'; +import { Observable, of, Subject, Subscription } from 'rxjs'; +import { NumberUtils } from '../../utilities/number-utils'; +import { DateUtilsService } from '../../services/date-utils.service'; +import { ObjectUtils } from '../../utilities/object-utils'; +import { ArrayUtils } from '../../utilities/array-utils'; +import { DropFileEvent } from '../../shared/file-drag-drop/file-drag-drop.directive'; +import { Console } from 'console'; +import { CheckBoxComponent } from '@progress/kendo-angular-inputs'; +import { EasyEditorService } from '../../ui/easy-editor/easy-editor.service'; +import { ContextMenuItem } from '../../directives/right-click-menu/context-menu-item.model'; + +const DATE_FORMAT = 'MM/dd/yyyy' +const DATE_TIME_FORMAT = 'MM/dd/yyyy hh:mm aa' +const HTML_INNERTEXT_REGEX = />((.|\n*?))<\//g; +const SCROLLING_ROW_OFFSET = -150; +const DragConfig = { + dragStartThreshold: 0, + pointerDirectionChangeThreshold: 5, + zIndex: 10000 +}; + +@Component({ + selector: 'fancy-table', + templateUrl: './fancy-table.component.html', + styleUrls: ['./fancy-table.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, + providers: [{ provide: CDK_DRAG_CONFIG, useValue: DragConfig }] +}) +export class FancyTableComponent implements OnInit, AfterViewInit { + // #region Properties (57) + + private _afterReloadSubscription: Subscription; + private _data: T[]; + private _draggingLeftColClientWidth: number; + private _draggingLeftColIndex: number; + private _footerData: T[]; + private _footerSettings: FancySettings; + private _itemsPerPage: number = 10; + private _settings: FancySettings; + private _sortBySeq: boolean = false; + private destroy$: Subject = new Subject(); + + protected selectedRowIndex: number = undefined; + + @Input() public id!: string; + @Input() public noDataMessage: string = "No Data Entered"; + @Input() public usingAltColor: boolean = false; + @Output() public adding = new EventEmitter>(); + @Output() public afterDisplayDataGenerated = new EventEmitter(); + @Output() public afterReload = new EventEmitter(); + @Output() public deleting = new EventEmitter>(); + @Output() public doubleClick = new EventEmitter>(); + @Output() public itemDrop = new EventEmitter(); + @Output() public itemDropInGroup = new EventEmitter(); + @Output() public onClickOnDeleteGroup = new EventEmitter>(); + @Output() public onFileDropped = new EventEmitter>(); + @Output() public onFileDroppedInGroup = new EventEmitter>(); + @Output() public onFilterGroupKey = new EventEmitter>(); + @Output() public onMouseUp = new EventEmitter>(); + @Output() public onTagClick = new EventEmitter<{ actionTag: string, row: FancyDisplayRow }>(); + @Output() public pressEnter = new EventEmitter>(); + @Output() public rowSelected = new EventEmitter>(); + @Output() public saving = new EventEmitter>(); + @Output() public sortByColumn = new EventEmitter(); + @ViewChild('checkAllBox', { static: false }) public checkAllBox: CheckBoxComponent; + @ViewChild('table', { static: true }) public tableToMeasureElement: ElementRef; + + /** + * All FancyDisplayRow (Not Includes Group Header) + */ + public allRows: FancyDisplayRow[]; + public anyExpanded: boolean = true; + //private _draggingRightColClientWidth: number; + public colDragResizing: boolean = false; + public displayGroup: FancyGroup[]; + public draggingRow = false; + public droppingOnGroupKey: string = null; + /** + * All generated displaying UI rows (Includes Group Header), the source of visibleData, if the row is visible,probably need to change the name of filteredRows + */ + public filteredRows: FancyDisplayRow[]; + public filteringDebounceTimer: DebounceTimer = new DebounceTimer(400, () => { this.generateVisibleData(); }); + public filteringGroupKey: string = null; + public firstSelectedRow: FancyDisplayRow; + public footerDisplayData: FancyDisplayRow[]; + public groupColumn: FancyColumn; + public groupValues = {}; + public groups: FancyGroup[] = null; + public lastFocusedFilterInput: HTMLInputElement; + public maxPage: number; + public page: number = 1; + public pages: number[]; + public processing: boolean = false; + //public resizingDebounceTimer: DebounceTimer = new DebounceTimer(500, () => { this.renderColMaxWidth() }); + //output focusedRow and selectedRows + public selectedRows = new Set>(); + public sortColumn: FancyColumn; + public tableId: string = UuidUtils.generate().substr(0, 9); + public visibleColumns: FancyColumn[]; + /** + * Visible displaying UI rows (Includes Group Header), it's dynamic filteredRows base on scrolling + */ + public visibleData: FancyDisplayRow[]; + + // #endregion Properties (57) + + // #region Constructors (1) + + constructor( + private dateUtilsService: DateUtilsService, + private el: ElementRef, + private cdRef: ChangeDetectorRef, + private easyEditorService: EasyEditorService, + private msgBoxService: MsgBoxService, + ) { } + + // #endregion Constructors (1) + + // #region Public Getters And Setters (14) + + @Input() public set data(value: T[]) { + // + this._data = value; + if (this._data !== undefined) { + this.generateDisplayData(); + //this._data.forEach((d, index) => d.index = index); + } + this.calculatePages(this._data); + } + + @Input() public set footerData(value: any[]) { + this._footerData = value; + if (this.settings && value) { + this.generateFooterDisplayData(); + } + } + + // Optional. If not set, uses regular settings + @Input() public set footerSettings(value: FancySettings) { + this._footerSettings = value; + } + + @Input() public set itemsPerPage(value: number) { // 0 to disable pager + this._itemsPerPage = Number(value); + this.calculatePages(this._data); + } + + @Input() public set settings(value: FancySettings) { + this._settings = new FancySettings({ ...value }); + if (value) { + this.initializeColumns(); + if ((value.customGroups || this.data) && !this.visibleData) { + this.generateDisplayData(); + if (this._settings.reloadSubject) { + this._settings.reloadSubject.pipe(takeUntil(this.destroy$)).subscribe(result => { + this.reload(); + }); + } + if (this._settings.reloadSettingSubject) { + this._settings.reloadSettingSubject.pipe(takeUntil(this.destroy$)).subscribe(result => { + this._settings = new FancySettings({ ...result }); + this.reload(); + }); + } + } + } + + // if (this.anyGroups) { + // + // this.grouped(this.filtered(this.data)); + // } + } + + public get anyFiltered(): boolean { + //null check for checkbox equals false + return this.settings.columns && this.settings.columns.some(c => { + if (c.filter != undefined && c.filter != '') return true; + if (['date', 'dateTime'].includes(c.type) && c.dateRange != undefined) return true; + return false; + } + ); + } + + public get anyGroups(): boolean { + return !!this._settings.customGroups || (this._settings.columns ? this._settings.columns.some(c => c.group) : false) + } + + public get data(): T[] { + return this._data; + } + + public get footerData(): any[] { + return this._footerData; + } + + public get footerSettings(): FancySettings { + return this._footerSettings || this._settings; + } + + public get hasScrollBar(): boolean { + var div = document.querySelector(`.tableContainer${this.tableId}`); + if (div) { + return div.scrollHeight > div.clientHeight; + } + return false; + } + + public get itemsPerPage(): number { + return Number(this._itemsPerPage); + } + + public get settings(): FancySettings { + return this._settings; + } + + public get visibleColumnCount(): number { + if (this._settings) { + let count = this._settings.columns.map(d => d.visible ? 1 : 0).reduce((a, b) => a + b, 0); + if (this.anyGroups) { + count++; + } + return count; + } + return 0; + } + + // #endregion Public Getters And Setters (14) + + // #region Public Methods (68) + + public ngOnInit(): void { + } + + public ngAfterViewInit() { + // setTimeout(() => { + // this.renderColMaxWidth(); + // }, 200); + console.log('Fancytable ngAfterViewInit'); + } + + public ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } + + @HostListener('focus', ['$event']) + public focusLastAccessFilter() { + if (this.lastFocusedFilterInput) { + this.lastFocusedFilterInput.focus(); + } + } + + @HostListener('window:resize', ['$event']) + public screenResizing() { + //this.resizingDebounceTimer.resetTimer(); + } + + public addRow(defaultData: T = {}) { + this.openEditor(-1, defaultData); + // this.dialogService.open(FancyTableEditorComponent, { + // context: { + // columns: this.settings.columns, + // datum: defaultData, + // isAdding: true, + // index: -1, + // validation: this.settings.addingValidation + // }, + // }).onClose.pipe(take(1)).subscribe(instance => { + // if (instance) { + // this.adding.emit({ index: -1, object: instance }); + // } + // }); + } + + public checkboxCheckAll(col: FancyColumn, checked: boolean) { + //Ignore intermediate status + if (checked !== null) { + this.filteredRows.filter(r => r.visible && !r.isGroupHeader).forEach(datum => { + this.toggleCheckBox(col, datum, checked, true); + }); + this.reload(); + } + } + + public checkboxGetFilter(filter) { + return { + undefined: 0, + true: 1, + false: 2, + }[filter]; + } + + public checkboxSetFilter(colName: string, filter: number) { + let checked = [ + undefined, + true, + false, + ][filter]; + this.settings.columns.find(c => c.name === colName).filter = checked; + this.generateVisibleData(); + } + + public clearAllFilters() { + for (let i = 0; i < this.settings.columns.length; i++) { + const col = this.settings.columns[i]; + col.filter = undefined; + col.dateRange = undefined; + } + this.filteringGroupKey = null; + this.reload(); + } + + public colResizeDragResize(col: FancyColumn, $event: CdkDragMove) { + let leftColumn = this.visibleColumns[this._draggingLeftColIndex]; + //let rightColumn = this.visibleColumns[this._draggingLeftColIndex + 1]; + console.log('resize', $event.distance.x); + + let offsetX = $event.distance.x; + leftColumn.widthPct = 0; + leftColumn.widthRem = 0; + leftColumn.widthPx = this._draggingLeftColClientWidth + offsetX; + this.calculateColWidth(); + // rightColumn.widthPct = 0; + // rightColumn.widthRem = 0; + // rightColumn.widthPx = this._draggingRightColClientWidth - offsetX; + } + + public colResizeDragStart(col: FancyColumn, $event: CdkDragStart) { + this.colDragResizing = true; + + let colIndex = this.visibleColumns.indexOf(col); + this._draggingLeftColIndex = colIndex;// < (this.visibleColumns.length - 1) ? colIndex : (colIndex - 1); + + this._draggingLeftColClientWidth = document.getElementById(`${this.tableId}col-${this._draggingLeftColIndex}`).clientWidth; + //this._draggingRightColClientWidth = document.getElementById(`${this.tableId}col-${this._draggingLeftColIndex + 1}`).clientWidth; + } + + public colResizeEnd(col: FancyColumn, $event: CdkDragEnd) { + this.colDragResizing = false; + } + + public columnFilterFunction(column: FancyColumn): boolean { + return (column.filter !== undefined || column.filterFunction !== undefined || column.dateRange !== undefined); + } + + public deleteRow(selectedRowIndex: number = undefined) { + if (undefined === selectedRowIndex) { + selectedRowIndex = this.firstSelectedRow.index;//this.data.findIndex(d => this.selectedRows.has(d)); + } + let _deletingIndex = selectedRowIndex; + let deletingInstance = { index: _deletingIndex, object: this.data[_deletingIndex] }; + + let msg = null; + let async = false; + if (this.settings.deletingValidation) { + let tempResult = this.settings.deletingValidation(deletingInstance); + if (typeof tempResult === 'string') { + msg = tempResult; + } + else { + async = true; + tempResult.pipe(first()).subscribe(result => { + this.showDeletingConfirm(result, deletingInstance); + }); + } + } + if (false == async) { + this.showDeletingConfirm(msg, deletingInstance); + } + } + + public detectChanges() { + if (!this.cdRef['destroyed']) { + this.cdRef.detectChanges(); + } + } + + public drop(event: CdkDragDrop) { + console.log('dropRow', this.droppingOnGroupKey); + this.draggingRow = false; + if (this.droppingOnGroupKey) { + return; + } + if (false == this.settings.allowDragAndDrop || event.currentIndex == event.previousIndex) return; + + //let datum = this.visibleData.find(d => d.uiSeq == event.previousIndex); + //if (false == this.selectedRows.has(datum)) this.selectedRows.add(datum); + + //moveItemInArray(this.visibleData, event.previousIndex, event.currentIndex); + let previousGroup: FancyGroup = null; + let currentGroup: FancyGroup = null; + + let previousRow = undefined as FancyDisplayRow; + let nextRow = undefined as FancyDisplayRow; + let moveDown = event.currentIndex > event.previousIndex; + //if it's not moving to the end of table, should always has next row at current index before resorting + if (event.currentIndex != (this.visibleData.length - 1)) { + const displayRow = this.visibleData[event.currentIndex + (moveDown ? 1 : 0)]; + if (!displayRow.isGroupHeader) { + nextRow = displayRow; + console.log('nextRow', displayRow.rowDatum['description']); + } + } + + //if current index > 0 should always has previous row at current index-1 + if (event.currentIndex > 0) { + const displayRow = this.visibleData[event.currentIndex - (moveDown ? 0 : 1)]; + if (!displayRow.isGroupHeader) { + previousRow = displayRow; + console.log('previousRow', displayRow.rowDatum['description']); + } + } + + var offset = 0; + let selectedRows = Array.from(this.selectedRows) + + if (this.settings.taggedRowData) + + if (this.anyGroups) { + let moveDown = event.previousIndex < event.currentIndex; + currentGroup = this.groups.slice().reverse().find(g => g.seq < (event.currentIndex + (moveDown ? 1 : 0))); + } + + for (let i = 0; i < selectedRows.length; i++) { + const row = selectedRows[i]; + let moveTo = i == 0 ? event.currentIndex : (this.filteredRows.indexOf(selectedRows[0]) + i - 1); + + if (this.anyGroups) { + previousGroup = this.groups.slice().reverse().find(g => g.seq < row.uiSeq); + } + + moveItemInArray(this.filteredRows, this.filteredRows.indexOf(row), moveTo); + if (this.anyGroups && previousGroup && currentGroup && previousGroup != currentGroup) { + //expands group if it collapsed + if (false == currentGroup.expanded) currentGroup.expanded = true; + row.groupKey = currentGroup.groupKey; + if (this.groupColumn) { + row.rowDatum[this.groupColumn.name] = currentGroup.groupValue; + } + + let insertIndex = 0; + if (currentGroup.rows.length > 0) { + insertIndex = previousRow ? Math.max(0, currentGroup.rows.indexOf(previousRow)) : currentGroup.rows.length; + } + + transferArrayItem(previousGroup.rows, currentGroup.rows, previousGroup.rows.indexOf(row), insertIndex) + } + } + this.filteredRows.forEach((row, i) => { + row.uiSeq = i; + row.seq = i; + }); + //Enable sort by Seq after drag + this._sortBySeq = true; + this.generateVisibleData(false); + //Set focus to drag and drop row + this.firstSelectedRow = this.filteredRows[event.currentIndex]; + this.selectedRows = new Set([this.firstSelectedRow]); + //this.setFocusedRowByUiSeq(event.currentIndex); + this.itemDrop.emit({ + previousIndex: event.previousIndex, currentIndex: event.currentIndex, + previousGroup, currentGroup, previousRow, nextRow, + focusRow: this.firstSelectedRow, + }); + } + + public dropColumn(event: CdkDragDrop) { + } + + public expandedGroups(key: any): boolean { + let group = this.getGroupByKey(key); + return group && group.expanded; + } + + public fileDropped(row: FancyDisplayRow, event: DropFileEvent) { + if (row == null) { + row = new FancyDisplayRow(); + } + row.uiFileDropList = event.files; + this.onFileDropped.next(row); + + console.log('FancyTable file drop', row); + //{{this.tableId}}-GroupRow-{{row.index}} + //{{this.tableId}}-Row-{{row.index}} + //this.onFileDropped.next(files); + } + + public filterGroupKey(group: FancyGroup) { + if (!this.draggingRow) { + if (this.filteringGroupKey == group.groupKey) { + this.filteringGroupKey = null; + } else { + this.filteringGroupKey = group.groupKey; + } + this.visibleData = []; + this.generateVisibleData(true); + this.onFilterGroupKey.emit(group); + } + } + + public filtered(data: FancyDisplayRow[]): FancyDisplayRow[] { + // Todo: configure this somewhere in FancySettings + let filterAll = true; + + if (data) { + data.forEach(datum => { + datum.visible = false; + }); + + data.filter(d => { + let filteredColumns = this.settings.columns.filter(this.columnFilterFunction); + if (filterAll) { + return filteredColumns.every(c => this.rowFilterFunction(d, c)); + } + else { // filter Any + return filteredColumns.some(c => this.rowFilterFunction(d, c)); // unused until filterAll is configurable + } + }).forEach(d => d.visible = true); + } + return data; + } + + public generateDisplayData() { + this.filteredRows = []; + this.allRows = []; + if (this.settings && (this.data || this.settings.customGroups)) { + //Do NOT clean up visible data here, that will cause table reload and lose dynamic data + //this.visibleData = []; + if (this.settings.customGroups) { + //this.groups = null; + } else { + this.data.forEach((d, index) => { + //d.index = index; + this.allRows.push({ + seq: index, + uiSeq: index, + index: index, + rowDatum: d, + colHtmlTexts: {},//ObjectUtils.Clone(d), + colTooltips: {},//ObjectUtils.Clone(d), + isGroupHeader: false + } as FancyDisplayRow); + }); + + this.visibleColumns.forEach(col => { + if (!col.group && col.visible) { + this.allRows.forEach(datum => { + this.getFieldDisplayValue(datum, col); + //datum[col.name] = col.valuePrepareFunction(datum); + }); + col.uiWidth = this.getColWidthSetting(col); + } + }); + } + + this.generateVisibleData(); + this.generateFooterDisplayData(); + this.afterDisplayDataGenerated.emit(); + } + } + + public generateFooterDisplayData() { + this.footerDisplayData = []; + if (this.settings && this.footerData) { + this.footerData.forEach((d, index) => { + this.footerDisplayData.push({ + seq: index, + index: index, + rowDatum: d, + colHtmlTexts: {}, + colTooltips: {}, + isGroupHeader: false + } as FancyDisplayRow); + }); + + this.footerSettings.columns.filter(f => f.visible).forEach(col => { + if (!col.group) { + this.footerDisplayData.forEach(datum => { + this.getFieldDisplayValue(datum, col); + //datum[col.name] = col.valuePrepareFunction(datum); + }); + } + }); + this.detectChanges(); + } + } + + public generateVisibleData(reGrouping: boolean = true) { + let visibleRowCount = this.visibleData && this.visibleData.length > this.settings.initialRows ? this.visibleData.length : this.settings.initialRows; + + if (!this.settings.customGroups) { + this.filtered(this.allRows); + } + + //with grouping + if (this.anyGroups /*&& !this.groups || Object.keys(this.groups).length === 0*/) { + this.filteredRows = []; + let lastExpandSetting = this.groups ? this.groups.map( + g => { return { groupKey: g.groupKey, expanded: g.expanded } } + ) : []; + if (!this.groups || reGrouping) { + if (this.settings.customGroups) { + this.groups = []; + this.allRows = []; + for (let i = 0; i < this.settings.customGroups.length; i++) { + const customGroup = this.settings.customGroups[i]; + let groupKey = customGroup[this.settings.customGroupKeyFieldName]; + const rows = customGroup[this.settings.customGroupRowsFieldName].map((d, index) => { + return { + seq: index, + index: index, + rowDatum: d, + groupKey: groupKey, + colHtmlTexts: {}, + colTooltips: {}, + isGroupHeader: false + } as FancyDisplayRow + } + ) as FancyDisplayRow[]; + this.allRows = this.allRows.concat(rows); + + this.visibleColumns.forEach(col => { + if (!col.group && col.visible) { + rows.forEach(datum => { + this.getFieldDisplayValue(datum, col); + //datum[col.name] = col.valuePrepareFunction(datum); + }); + } + }); + let groupDisplayValue = customGroup[this.settings.customGroupValueFieldName]; + this.groups.push({ + seq: customGroup[this.settings.customGroupSeqFieldName], + groupKey: groupKey, + groupValue: this.settings.groupValuePrepareFunction ? this.settings.groupValuePrepareFunction(groupDisplayValue, rows) : groupDisplayValue, + rows: this.filtered(rows), + expanded: true, + uiAllowDeleteWhenEmpty: customGroup['uiAllowDeleteWhenEmpty'] + } as FancyGroup); + } + } else { + this.groups = this.grouped(this.allRows.filter(d => d.visible)); + } + + if (this.settings.groupSortDirection) { + this.groups = this.sortedGroupKeys(this.groups); + } + + if (this.settings.customGroups) { + //this.allRows = ArrayUtils.ConcatMultiArray(this.groups.map(g => g.rows)); + for (let i = 0; i < this.allRows.length; i++) { + const row = this.allRows[i]; + row.index = i; + row.uiSeq = i; + } + } + } + + //Adding Group header + this.groups.forEach(group => { + if (!this.filteringGroupKey || this.filteringGroupKey == group.groupKey) { + if (group.rows.filter(r => r.visible).length > 0 || this.settings.displayEmptyGroup) { + this.filteredRows.push({ isGroupHeader: true, groupKey: group.groupKey } as FancyDisplayRow); + group.expanded = lastExpandSetting.length == 0 || lastExpandSetting.find(s => s.groupKey == group.groupKey)?.expanded; + if (group.expanded === undefined) { + group.expanded = true; + } + //Sort grouped data + if (group.expanded) { + this.filteredRows = this.filteredRows.concat(this.sorted(group.rows)); + } + } + } + }); + } + else { + //Sorting + this.filteredRows = this.sorted(this.allRows.filter(d => d.visible)); + } + + this.visibleData = this.filteredRows.filter(r => r.isGroupHeader || r.visible).slice(0, visibleRowCount); + this.allRows.forEach(row => row.uiSeq = -1); + this.filteredRows.forEach((row, i) => { + row.uiSeq = i; + if (row.isGroupHeader) this.getGroupByKey(row.groupKey).seq = i; + }); + this.detectChanges(); + } + + public getColWidthSetting(col: FancyColumn) { + if (col.type == 'checkall') { + return !col.taggingTarget ? '37px' : '55px'; + } else if (col.widthPct) { + return col.widthPct.toString() + '%'; + } else if (col.widthRem) { + return col.widthRem.toString() + 'rem'; + } else if (col.widthPx) { + return col.widthPx.toString() + 'px'; + } + return 'auto'; + } + + public getFieldDisplayValue(row: FancyDisplayRow, column: FancyColumn) { + if (column.valuePrepareFunction) { + let preparedValue = column.valuePrepareFunction(row.rowDatum); + row.colHtmlTexts[column.name] = preparedValue == null ? '' : preparedValue; + } else { + switch (column.type) { + case "tag": + let tags = column.tagsPrepareFunction(row.rowDatum); + row.colHtmlTexts[column.name] = tags; + row.colTooltips[column.name] = StringUtils.makeCommaSeparatedString(tags.map(t => t.tooltip), true); + break; + case "checkall": + //need to set all row to false by default to avoid intermediate status of the check box + if ([undefined, null].includes(row.rowDatum[column.name])) { + row.rowDatum[column.name] = false; + }; + break; + default: + row.colHtmlTexts[column.name] = row.rowDatum[column.name]; + break; + } + } + + switch (column.type) { + case "currency": + row.colHtmlTexts[column.name] = NumberUtils.FormatCurrency(row.colHtmlTexts[column.name], "0.00"); + break; + + case "date": + row.colHtmlTexts[column.name] = this.dateUtilsService.format(row.rowDatum[column.name], DATE_FORMAT); + break; + + case "dateTime": + row.colHtmlTexts[column.name] = this.dateUtilsService.format(row.rowDatum[column.name], DATE_TIME_FORMAT); + break; + } + + if (column.toolTipPrepareFunction) { + row.colTooltips[column.name] = column.toolTipPrepareFunction(row.rowDatum); + } else if (!row.colTooltips[column.name]) { + //Parse HTML inner text for tooltip, can comment this out if got performance problem + row.colTooltips[column.name] = StringUtils.getHtmlInnerText('' + row.colHtmlTexts[column.name]); + } + + return row.colHtmlTexts[column.name]; + } + + public getGroupByKey(groupKey) { + return this.groups.find(g => g.groupKey == groupKey); + } + + public getGroupValue(key: any) { + return this.getGroupByKey(key).groupValue; + } + + public getScrollHeightOfIndex(index: number = -1): number { + return 100; + } + + public getSelected() { + return >{ + index: this.firstSelectedRow && this.firstSelectedRow.index >= 0 ? this.firstSelectedRow.index : -1, + focusedRow: this.firstSelectedRow && this.firstSelectedRow.rowDatum || undefined, + focusedCustomRowId: this.firstSelectedRow && this.firstSelectedRow.customRowId || undefined, + selectedRows: Array.from(this.selectedRows.keys()).map(r => r?.rowDatum) as T[], + } + } + + public gotoPage(p: number) { + this.page = p; + } + + public grouped(data: FancyDisplayRow[]): FancyGroup[] { + // only update grouping the first time, TODO: or on a change to group settings + if (this.groupColumn /*&& !this.groups || Object.keys(this.groups).length === 0*/) { + data.forEach(row => { + row.groupKey = this.groupColumn.groupKeyPrepareFunction ? this.groupColumn.groupKeyPrepareFunction(row.rowDatum) : row.rowDatum[this.groupColumn.name]; + + }); + + let groups = ArrayUtils.GroupBy(data, r => r.groupKey); + let tempList: FancyGroup[] = []; + var groupCount = 0; + for (let i = 0; i < groups.length; i++) { + const item = groups[i]; + let group = { + groupKey: item.key, + seq: this.settings.groupSeqPrepareFunction ? this.settings.groupSeqPrepareFunction(item.key) : item.values[0].seq + groupCount, + rows: item.values, + expanded: true + } as FancyGroup; + group.groupValue = this.settings.groupValuePrepareFunction ? this.settings.groupValuePrepareFunction(item.key, group.rows.map(r => r.rowDatum)) : item.key; + if (!group.groupValue) { + //TODO:add configuration + group.groupValue = this.settings.groupMissing; + } + group.rows = this.sorted(group.rows); + tempList.push(group); + groupCount++; + } + this.groups = tempList; + } + + return this.groups; + } + + public keyHandler(event: KeyboardEvent) { + switch (event.key) { + case 'ArrowDown': + { + let currentUiSeq = this.firstSelectedRow ? this.firstSelectedRow.uiSeq : -1; + currentUiSeq = NumberUtils.Clamp(currentUiSeq, -1, this.visibleData.length - 2); + this.setFocusedRowByUiSeq(currentUiSeq + 1); + } + event.preventDefault(); + break; + + case 'ArrowUp': + { + let currentUiSeq = this.firstSelectedRow ? this.firstSelectedRow.uiSeq : -1; + currentUiSeq = NumberUtils.Clamp(currentUiSeq, 1, this.visibleData.length); + this.setFocusedRowByUiSeq(currentUiSeq - 1); + } + event.preventDefault(); + break; + case 'Enter': + if (this.pressEnter.observers.length > 0) { + this.pressEnter.next(this.getSelected()); + event.preventDefault(); + } + break; + } + } + + public markForCheck() { + this.cdRef.markForCheck(); + } + + public onCellMouseDown(col: FancyColumn, $event: MouseEvent) { + if (this.settings.allowDragAndDrop && !col.draggable) { + $event.stopPropagation(); + } + } + + // has this [RightClickValue]="this", so "self" is "this" FancyTableComponent + // "this" is not in the click MouseEvent scope, afaik + public onContextMenuOpening(self: FancyTableComponent, rightClickMenuItems: ContextMenuItem[], event: MouseEvent) { + // datum will be undefined, since context menu is bound to tbody and unset + + let targetElement = event.target as Element; + + let targetRow = self.getTrFromChild(targetElement); + + if (self.groupColumn) { + let targetGroup = self.getGroupKeyFromTR(targetRow) as FancyGroup; + if (targetGroup) { + rightClickMenuItems.forEach(function (item, index) { + item.visible = item.forGroupHeader == true; + }); + if (self.settings.onContextMenuOpening) { + self.settings.onContextMenuOpening(targetGroup, rightClickMenuItems, self.getSelected(), event); + } + return targetGroup; + } else { + } + } + + let targetDatum = self.getDatumFromTR(targetRow); + + rightClickMenuItems.forEach(function (item, index) { + item.visible = item.forGroupHeader == false; + }); + let rowDatum = targetDatum ? targetDatum.rowDatum : null; + self.onRowSelect(targetDatum ? targetDatum.index : -1, event); + if (self.settings.onContextMenuOpening) { + self.settings.onContextMenuOpening(rowDatum, rightClickMenuItems, self.getSelected(), event); + } + return rowDatum; + } + + public onInfinityScrollDown() { + this.visibleData = this.visibleData.concat(this.filteredRows.slice(this.visibleData.length, this.visibleData.length + this.settings.appendRowsOnScroll)); + } + + public onInfinityScrollUp() { + } + + // getGroupDisplayValue(group) { + // let fancyGroup = this.settings && this.settings.groups.find(g => g.name === group.key); + // if (fancyGroup && fancyGroup.valuePrepareFunction) + // return fancyGroup.valuePrepareFunction(group.key); + // return group.key; + // } + public onRowMouseUp(row: FancyDisplayRow, $event: MouseEvent) { + this.onMouseUp.emit(row); + } + + public onRowSelect(index: number, event: MouseEvent) { + if (!this.settings.allowSelection) return; + //let targetElement = event.srcElement as Element; + let targetRow = this.allRows[index]; + //let datum = targetRow.rowDatum; + + if (this.selectedRows.has(targetRow) && !(event.ctrlKey || event.shiftKey)) { + //when user click same row without control or shift key, don't need to clean out selectedRows + return; + } + + // + + if (!this.settings.multiselect || !(event.ctrlKey || event.shiftKey)) { + //if (false == (this.settings.taggedValues && this.settings.taggedValues.length > 1)) { + //don't reset if the using the check box for row selection + this.selectedRows = new Set(); + this.firstSelectedRow = undefined; + //} + } + + if (this.firstSelectedRow === undefined) { + this.firstSelectedRow = targetRow; + // console.log("firstSelectedRow", datum) + } else if (this.selectedRows.has(targetRow) && this.firstSelectedRow === targetRow && this.selectedRows.size === 1) { + this.firstSelectedRow = undefined; + } + + // select all from `datum` to `firstSelectedRow` + if (event.shiftKey) { + let data = this.visibleData; + let datumFound = false; + let firstSelectedRowFound = false; + + for (let i = 0; i < data.length; i++) { + if (false == data[i].isGroupHeader) { + if (data[i] === targetRow) { + datumFound = true; + } + if (data[i] === this.firstSelectedRow) { + firstSelectedRowFound = true; + } + + if (datumFound != firstSelectedRowFound || data[i] === targetRow || data[i] === this.firstSelectedRow) { // xor + this.selectedRows.add(data[i]); + } else if (this.selectedRows.has(data[i])) { + this.selectedRows.delete(data[i]); + } + } + } + } else { + this.selectedRows.has(targetRow) ? this.selectedRows.delete(targetRow) : this.selectedRows.add(targetRow); + } + + this.rowSelected.emit(this.getSelected()); + } + + /** + * If editingIndex is not supplied or undefined, use the current selected row. + * Set editingIndex to -1 to enable Create mode. + */ + public openEditor(editingIndex: number = undefined, defaultData: T = {}) { + if (undefined === editingIndex) { + // selectedRowIndex = this.data.findIndex(d => this.selectedRows.has(d)); + editingIndex = this.firstSelectedRow.index; + } + + const datum = editingIndex > -1 ? this.data[editingIndex] : defaultData; // or {}? + + let onEditorClosed = this.easyEditorService.openEditorForFancyTable(datum, this.settings.columns, editingIndex); + + onEditorClosed.pipe(first()).subscribe(result => { + if (result && result.object) { + const instance = result.object; + if (editingIndex > -1) { + if (!this.saving.observed) { + // allow for default behavior if not subscribed to saving event + this.data[editingIndex] = instance; + this.reload(); + } else { + this.saving.emit({ index: editingIndex, object: instance }); + } + } else { + if (!this.adding.observed) { + // allow for default behavior if not subscribed to adding event + this.data.push(instance); + this.reload(); + } + else { + this.adding.emit({ index: editingIndex, object: instance }); + } + } + } + }); + + return onEditorClosed; + } + + public reload() { + this.initializeColumns(); + this.generateDisplayData(); + // this.groups = undefined; + this.detectChanges(); + this.afterReload.emit(); + } + + public renderColMaxWidth(): boolean { + let divToMeasureWidth = this.el.nativeElement.clientWidth; + if (divToMeasureWidth > 0) { + this._settings.columns.forEach(col => { + let maxWidth = 'auto'; + if (col.visible) { + if (col.widthPct) { + maxWidth = `${divToMeasureWidth * col.widthPct / 100}px`; + } else if (col.widthRem) { + maxWidth = col.widthRem.toString() + 'rem'; + } else if (col.widthPx) { + maxWidth = col.widthPx.toString() + 'px'; + } + } + col.maxWidth = maxWidth; + }); + this.detectChanges(); + return true; + } + return false; + } + + /** + * Will return UI seq if found it other wise return `-1`, + * Example:`fancyTable.renderRowHtml(s => s.id == 'The id')` + */ + public renderRowHtml(predicate: (value: T) => boolean, focusAfterRender: boolean = false): number { + let index = -1; + const searchResult = this.visibleData.filter(d => d.rowDatum && predicate(d.rowDatum)); + if (searchResult) { + for (let i = 0; i < searchResult.length; i++) { + const row = searchResult[i]; + + this.visibleColumns.forEach(col => { + if (!col.group && col.visible) { + this.getFieldDisplayValue(row, col); + } + }); + if (index == -1) { + index = row.index; + } + } + } + if (focusAfterRender) { + this.setFocusedRowByIndex(index); + } + this.detectChanges(); + return index; + } + + public resetSort() { + this.sortColumn = null; + if (this.settings && this.settings.columns) { + this.sortColumn = this.settings.columns.find(c => !StringUtils.isNullOrWhitespace(c.sortDirection)); + } + } + + public rowFilterFunction(row: FancyDisplayRow, c: FancyColumn): boolean { + if (row.isGroupHeader) return true; + // Use filterFunction if it was defined + if (c.filterFunction) { + return c.filterFunction(row, c.filter); + } + + // Use filter string if it was defined + if ((c.filter !== undefined || c.dateRange !== undefined) /*&& d[c.name]*/) { + switch (c.type || 'text') { + case 'date': + case 'dateTime': + + if (c.dateRange) { + // If a filter is set and the date is null, filter it out + if (false == this.dateUtilsService.isValidDate(row.rowDatum[c.name])) { + return false; + } + + let start = this.dateUtilsService.getLocalISOTime(c.dateRange.start); + let end = this.dateUtilsService.getLocalISOTime(this.dateUtilsService.addDays(c.dateRange.end, 1)); // increment day by 1 + if (!c.dateRange.end) { + c.dateRange.end = this.dateUtilsService.getLocalISOTime(this.dateUtilsService.addDays(c.dateRange.start, 1)); + } + + if (!start) start = this.dateUtilsService.getLocalISOTime(this.dateUtilsService.MIN); + if (!end) end = this.dateUtilsService.getLocalISOTime(this.dateUtilsService.MAX); + + // let value = typeof d[c.name] === 'Date' ? d[c.name] : new Date(d[c.name]); + let value = this.dateUtilsService.getLocalISOTime(new Date(row.rowDatum[c.name])); + // + + // + return start <= value && value < end; + } else { + return true; + } + + case 'dropdown': + case 'text': + case 'number': + case 'currency': + { + let strval = ((c.valuePrepareFunction && c.filterPreparedValue) || c.type == 'currency') ? row.colHtmlTexts[c.name] : '' + row.rowDatum[c.name]; + strval = strval.toString(); + let value = c.filterIgnoreCase ? strval.toUpperCase() : strval; + let filter = c.filter ? (c.filterIgnoreCase ? ('' + c.filter).toUpperCase() : '' + c.filter) : ''; + + switch (c.filterMode) { + case 'startsWith': + return value.startsWith(filter); + case 'allkeywords': + return !filter || (filter && filter.split(' ').every(token => value.includes(token))); + case 'contains': + default: + return value.includes(filter); + } + } + + case 'checkbox': + { + let value = row.rowDatum[c.name]; + let filter = c.filter; + // + // + return value === filter; + } + case 'tag': + { + let filter = c.filter ? (c.filterIgnoreCase ? c.filter.toUpperCase() : c.filter) : ''; + if (filter) { + if (c.filterPreparedValue) { + let strval = row.rowDatum[c.name] || ''; + let value = c.filterIgnoreCase ? strval.toUpperCase() : strval; + return value.includes(filter); + } else { + let tags: FancyTag[] = row.colHtmlTexts[c.name]; + return tags.some(tag => (tag.text ? (c.filterIgnoreCase ? tag.text.toUpperCase() : tag.text) : '').indexOf(filter) > -1); + } + } + } + // case 'text': + // if (false === StringUtils.isNullOrWhitespace(d[c.name])) { + // let value = d[c.name].toUpperCase(); + // let filter = c.filter.toUpperCase(); + // switch (c.filterMode) { + // case 'contains': + // return value.includes(filter); + // default: + // return value.startsWith(filter); + // } + // } + } + } + + // If no filters, return true + return true; + } + + public scrollTo(y: number) { + if (document.querySelector('.tableContainer' + this.tableId)) { + document.querySelector('.tableContainer' + this.tableId).scrollTop = y; + } + } + + public scrollToItem(itemIndex: number) { + let currentVisibleRowsContainsItem = this.visibleData.some(d => d.index == itemIndex); + if (currentVisibleRowsContainsItem) { + let targetRow = this.allRows.find(r => r.index == itemIndex); + //TODO:Optimize this, the problem here is the visibleData do contains the item but the uiSeq and itemIndex is not match. + if (targetRow && !this.visibleData.some(d => d.uiSeq == targetRow.uiSeq)) { + //check if the item already append to visible data + while (this.visibleData.length < targetRow.uiSeq) { + this.onInfinityScrollDown(); + } + } + } + + const el = document.getElementById(`${this.tableId}${itemIndex}`); + if (el) { + const tableContainer = document.querySelector('.tableContainer' + this.tableId); + const finalScroll = el['offsetTop'] + SCROLLING_ROW_OFFSET; + const initialScroll = tableContainer.scrollTop; + const deltaScroll = finalScroll - initialScroll; + + if (this.settings.smoothScrollDurationMs > 0) { + const duration = this.settings.smoothScrollDurationMs; + const step = duration / 60; // 60 fps + let current = 0; + const scrollInterval = setInterval(() => { + current += step; + let scrollAmount = deltaScroll * this.smoothstep(0, duration, current); + tableContainer.scrollTop = initialScroll + scrollAmount; + if (current >= duration) { + clearInterval(scrollInterval); + } + }, step); + } else { + tableContainer.scrollTop = finalScroll; + } + //el.scrollIntoView({ behavior: 'smooth' }) + } + } + + private smoothstep(edge0: number, edge1: number, x: number) { + // Scale, and clamp x to 0..1 range + x = this.clamp((x - edge0) / (edge1 - edge0)); + + return x * x * (3.0 - 2.0 * x); + } + + private clamp(x: number, lowerlimit: number = 0.0, upperlimit: number = 1.0) { + if (x < lowerlimit) return lowerlimit; + if (x > upperlimit) return upperlimit; + return x; + } + + /** + * Will return found rows count, + * Example:`fancyTable.searchMultiRowsAndSelect(s => idArray.some(id=>id===s.id))` + */ + public searchMultiRowsAndSelect(predicate: (value: T) => boolean): number { + this.selectedRows = new Set(); + let count = 0; + const searchResult = this.filteredRows.filter(d => + d.rowDatum && predicate(d.rowDatum)); + if (searchResult) { + searchResult.forEach(row => { + this.selectedRows.add(row); + }); + count = searchResult.length; + if (count > 0) { + this.scrollToItem(this.allRows.indexOf(searchResult[0])); + } + } + this.detectChanges(); + + return count; + } + + /** + * Will return UI seq if found it other wise return `-1`, + * Example:`fancyTable.searchRowAndFocus(s => s.id == 'The id')` + */ + public searchRowAndFocus(predicate: (value: T) => boolean, focusAfterDisplayDataRegen: boolean = false): number { + if (focusAfterDisplayDataRegen) { + this.afterDisplayDataGenerated.pipe(first()).subscribe(result => { + this.searchRowAndFocus(predicate, false); + this.detectChanges(); + }); + //this.reload(); + } else { + let index = -1; + const searchResult = this.filteredRows.find(d => d.rowDatum && predicate(d.rowDatum)); + if (searchResult) { + index = searchResult.index; + } + this.setFocusedRowByIndex(index); + this.detectChanges(); + return index; + } + return 0; + } + + public setColEditableType(name: string, type: FtEditableType, fieldWidth = -1) { + let col = this.settings.columns.find(c => c.name == name); + if (col) { + col.editableType = type; + if (fieldWidth > -1) { + col.editingFieldWidth = fieldWidth; + } + } + } + + public setFocusedFilterInput(event: FocusEvent) { + this.lastFocusedFilterInput = event ? event.target : null; + } + + public setFocusedRowByIndex(index: number) { + if (index === -1) { + this.selectedRows = new Set(); + this.firstSelectedRow = undefined; + this.rowSelected.emit(this.getSelected()); + } + else if (this.filteredRows.length >= index + 1) { + this.firstSelectedRow = this.filteredRows.find(r => r.index == index); + this.selectedRows = new Set([this.firstSelectedRow]); + this.rowSelected.emit(this.getSelected()); + + //Need a UI change detection here but doesn't need to regenerate display data, doesn't need to call this.reload(); + this.detectChanges(); + + this.scrollToItem(index); + } + } + + public setFocusedRowByUiSeq(uiSeq: number) { + if (uiSeq === -1) { + this.selectedRows = new Set(); + this.firstSelectedRow = undefined; + this.rowSelected.emit(this.getSelected()); + } + else { + let focusRow = this.filteredRows.find(r => r.uiSeq == uiSeq); + if (focusRow) { + this.firstSelectedRow = focusRow; + this.selectedRows = new Set([focusRow]); + this.rowSelected.emit(this.getSelected()); + + //Need a UI change detection here but doesn't need to regenerate display data, doesn't need to call this.reload(); + this.detectChanges(); + + this.scrollToItem(focusRow.index); + } + } + } + + public setSort(column: FancyColumn) { + let sortDirection = 'asc'; + if (this.sortColumn && + this.sortColumn.name == column.name && + this.sortColumn.sortDirection === sortDirection) { + sortDirection = 'desc'; + } + + this.sortColumn = { ...column } as FancyColumn; + this.sortColumn.sortDirection = sortDirection; + + this.generateVisibleData(); + this.detectChanges(); + this.sortByColumn.next(this.filteredRows.filter(d => d.rowDatum).map(d => d.rowDatum)); + } + + public showHideGroup(groupKey) { + let group = this.getGroupByKey(groupKey); + + group.expanded = !group.expanded; + //this.anyExpanded = Object.keys(this.expandedGroups).some(k => this.expandedGroups[k]); + this.generateVisibleData(false); + if (group.expanded && group.rows.length > 1) { + this.onInfinityScrollDown(); + this.detectChanges(); + setTimeout(() => { + this.scrollToItem(group.rows[0].index); + }, 200); + } + } + + // public selectAll() { + // if (0 < this.data.length) { + // delete this.selectedRows; // useful? + // this.firstSelectedRow = this.data[0]; + // this.selectedRows = new Set(this.data); + // this.reload(); + // } + // } + public showProcessing() { + this.processing = true; + setTimeout(() => { + this.processing = false; + }, 300); + } + + public slice(data: T[], start: number, length: number) { + if (this.itemsPerPage > 0 && data) { + return data.slice(start, start + length); + } + return data; + } + + public sorted(data: FancyDisplayRow[]): FancyDisplayRow[] { + if (data && data.length > 0) { + if (this._sortBySeq) { + return data.sort((a, b) => a.uiSeq - b.uiSeq); + } + else if (this.sortColumn) { + // Todo: these values should be enum'd, or a function lookup table or something + let descendingSort = this.sortColumn.sortDirection === 'desc'; + let doStringSort = (this.sortColumn.valuePrepareFunction && this.sortColumn.filterPreparedValue) || typeof (data[0].rowDatum[this.sortColumn.name]) === 'string'; + + let doDateSort = this.sortColumn.type == 'date' || this.sortColumn.type == 'dateTime'; + let doTagSort = false; + if (this.sortColumn.type == 'tag') { + doTagSort = true; + doStringSort = true; + } + + // const sortFunctions = {typeof date.getMonth === 'function' + // 'string': (a, b) => a.localeCompare(b), + // ... etc + // } + + const sortedDataFunction = (a: FancyDisplayRow, b: FancyDisplayRow) => { + if (this.sortColumn.sortFunction) { + return this.sortColumn.sortFunction(a.rowDatum, b.rowDatum); + } + + let aval = a.rowDatum[this.sortColumn.name]; + let bval = b.rowDatum[this.sortColumn.name]; + + if (doTagSort) { + aval = a.colTooltips[this.sortColumn.name]; + bval = b.colTooltips[this.sortColumn.name]; + + } else if (this.sortColumn.valuePrepareFunction && this.sortColumn.filterPreparedValue) { + aval = a.colHtmlTexts[this.sortColumn.name]; + bval = b.colHtmlTexts[this.sortColumn.name]; + } + + if (doDateSort) { + if (doStringSort) { + aval = this.dateUtilsService.parse(aval); + bval = this.dateUtilsService.parse(bval); + } + return this.dateUtilsService.getTime(aval) - this.dateUtilsService.getTime(bval); + } else if (doStringSort) { + return StringUtils.compare(aval, bval); + } else { + return ((!aval && aval !== 0) || (!bval && bval !== 0)) ? 0 : aval - bval; + } + }; + + if (descendingSort) { + return data.sort((a, b) => sortedDataFunction(b, a)); // swapped a and b + } + else { + return data.sort((a, b) => sortedDataFunction(a, b)); + } + } + } + return data; + } + + public sortedGroupKeys(groups: FancyGroup[]) { + // + if (groups) { + let descendingSort = this.settings.groupSortDirection === 'desc';// swapped a and b + + const sortFunction = (a: FancyGroup, b: FancyGroup) => { + if (this.settings.groupSortFunction) { + return descendingSort ? this.settings.groupSortFunction(b, a) : this.settings.groupSortFunction(a, b); + } + if (this.settings.groupSeqPrepareFunction) { + return descendingSort ? (b.seq - a.seq) : (a.seq - b.seq); + } + switch (typeof (a.groupKey)) { + case 'string': + return descendingSort ? b.groupKey.localeCompare(a.groupKey) : a.groupKey.localeCompare(b.groupKey); + default: + return descendingSort ? (b.groupKey - a.groupKey) : (a.groupKey - b.groupKey); + } + }; + + return groups.sort(sortFunction); + } + else { + return []; + } + } + + public startDragging() { + this.draggingRow = true; + } + + public tagOnClick(row: FancyDisplayRow, actionTag: string) { + if (actionTag) { + this.onTagClick.next({ row, actionTag }); + } + } + + public toFancyColumn(v): FancyColumn { + return v as FancyColumn; + } + + public toggleCheckBox(taggingColumn: FancyColumn, row: FancyDisplayRow, $event: boolean, bulkUpdate = false) { + let datum = row.rowDatum; + datum[taggingColumn.name] = $event; + if (taggingColumn.taggingTarget) { + let rowId = datum[taggingColumn.taggingTarget]; + if ($event) { + if (!this.settings.taggedValues.includes(rowId)) { + this.settings.taggedValues.push(rowId); + this.settings.taggedRowData.push(datum); + } + + //TODO:The checkbox adding to selected rows still too buggy, circle back to this latter + // if (!this.selectedRows.has(row)) { + // this.selectedRows.add(row); + // } + // this.firstSelectedRow = row; + + } else { + let removeIndex = this.settings.taggedValues.indexOf(rowId); + if (removeIndex > -1) { + this.settings.taggedValues.splice(removeIndex, 1); + this.settings.taggedRowData.splice(removeIndex, 1); + } + + // if (this.selectedRows.has(row)) { + // this.selectedRows.delete(row); + // } + + // if (this.selectedRows.size > 0) { + // this.firstSelectedRow = this.selectedRows[this.selectedRows.size - 1]; + // } else { + // this.firstSelectedRow = null; + // } + } + + if (!bulkUpdate) { + this.rowSelected.emit(this.getSelected()); + } + } + if (!bulkUpdate) { + let dataRows = this.filteredRows.filter(r => !r.isGroupHeader); + let checkedRowCount = dataRows.filter(r => r.rowDatum[taggingColumn.name]).length; + taggingColumn.uiCheckAll = checkedRowCount == dataRows.length ? true : checkedRowCount == 0 ? false : null; + //this.checkAllBox.ngDoCheck(); + // if ($event) { + // this.setFocusedRowByIndex(row.index); + // } + } + // if (!bulkUpdate && taggingColumn.taggingTarget && this.settings.taggedValues && this.settings.taggedValues.length > 0) { + // taggingColumn.uiCheckAll = null; + // this.checkAllBox.ngDoCheck(); + // } + //taggingColumn.uiCheckAll= taggingColumn.taggingTarget&& this.settings.taggedValues&& this.settings.taggedValues.length>0 + } + + public toggleExpandAll() { + let expanded = !this.anyExpanded; + this.groups.forEach(group => { + group.expanded = expanded; + }); + this.anyExpanded = expanded; + this.generateVisibleData(false); + } + + public trackByUiSeq(index: number, item: FancyDisplayRow) { + return item.uiSeq; + } + + public triggerDoubleClick(row: FancyDisplayRow) { + let selectedRows = this.getSelected(); + if (selectedRows.index === -1) { + // if selection is disabled this should still return the row that was double clicked + selectedRows = >{ + index: row?.index ?? -1, + focusedRow: row?.rowDatum, + focusedCustomRowId: row?.customRowId, + selectedRows: row ? [row.rowDatum] : [], + }; + } + this.doubleClick.emit(selectedRows); + if (this.settings.editorTrigger == FtTrigger.DOUBLE_CLICK) { + if (row && row.index > -1) { + this.openEditor(row.index); + } + } + } + + // #endregion Public Methods (68) + + // #region Private Methods (7) + + private calculateColWidth() { + this.visibleColumns.forEach(col => { + if (!col.group && col.visible) { + col.uiWidth = this.getColWidthSetting(col); + } + }); + } + + private calculatePages(data: T[]) { + if (this.itemsPerPage > 0 && data && data.length > 0) { + this.maxPage = Math.ceil(data.length / this.itemsPerPage); + this.pages = Array.from({ length: this.maxPage }, (x, i) => i + 1); + } + else { + this.maxPage = 1; + this.pages = [1,]; + this.page = 1; + } + } + + private getDatumFromTR(tr: Element): FancyDisplayRow { + if (tr) { + // + let rowId = tr.id.substr(this.tableId.length); + // + + let rowIndex = Number(rowId); + // + + return this.allRows[rowIndex]; + } + else { + return null; + } + } + + private getGroupKeyFromTR(tr: Element): FancyGroup { + if (tr && tr.attributes['groupkey']) { + return this.groups.find(g => g.groupKey == tr.attributes['groupkey'].value); + } + else { + return null; + } + } + + private getTrFromChild(element: Element): Element { + if (element && element.id && element.id.startsWith(this.tableId)) { + // console.log(element.id) + return element; + } + else if (element && element.parentElement) { + return this.getTrFromChild(element.parentElement); + } + else { + return null; + } + } + + private initializeColumns() { + if (this.settings && this.settings.columns) { + this.sortColumn = this.settings.columns.find(c => !StringUtils.isNullOrWhitespace(c.sortDirection)); + this.visibleColumns = this.settings.columns.filter(c => c.visible && !c.group); + this.groupColumn = this.settings.columns.find(c => c.group); + + this.calculateColWidth(); + } + } + + private showDeletingConfirm(validationMsg: string, instance: FancyEditorInstance) { + if (null === validationMsg) { + this.msgBoxService.showConfirmDeleteBox().pipe(first()).subscribe(answer => { + if (answer === true) { + if (this.deleting.observed) { + this.deleting.emit(instance); + } else { + // allow for default behavior if not subscribed to deleting event + this.data.splice(instance.index, 1); + this.reload(); + } + } + }); + } + else { + this.msgBoxService.show('Invalid Operation', + { text: validationMsg, icon: ADIcon.WARNING, buttons: ADButtons.OK }); + } + } + + // #endregion Private Methods (7) +} diff --git a/APP/src/components/fancy-table/fancy-table.module.ts b/APP/src/components/fancy-table/fancy-table.module.ts new file mode 100644 index 0000000..47c1253 --- /dev/null +++ b/APP/src/components/fancy-table/fancy-table.module.ts @@ -0,0 +1,75 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FancyTableComponent } from './fancy-table.component'; +import { FormsModule } from '@angular/forms'; +import { + NbButtonModule, + NbDatepickerModule, + NbCalendarRangeModule, + NbCardModule, + NbCheckboxModule, + NbDialogModule, + NbIconModule, + NbInputModule, + NbSelectModule, + NbTooltipModule, + NbAlertModule, + NbSpinnerModule, + NbActionsModule, +} from '@nebular/theme'; +import { PagerModule } from '../pager/pager.module'; +import { CurrencyInputModule } from '../currency-input/currency-input.module'; +import { DropDownListModule } from '../drop-down-list/drop-down-list.module'; +import { DateInputModule } from '../date-input/date-input.module'; +import { ConfirmDialogComponent } from './confirm-dialog/confirm-dialog.component'; +import { DragDropModule } from '@angular/cdk/drag-drop'; +import { InfiniteScrollModule } from 'ngx-infinite-scroll'; +import { TablePickerComponent } from './table-picker/table-picker.component'; +import { CustomColumnsDlgComponent } from './custom-columns-dlg/custom-columns-dlg.component'; +import { FileDragDropModule } from '../../shared/file-drag-drop/file-drag-drop.module'; +import { InputsModule } from '@progress/kendo-angular-inputs'; +import { FancyGroupMenuComponent } from './fancy-group-menu/fancy-group-menu.component'; +import { EmailInputModule } from '../email-input/email-input.module'; +import { PhoneInputModule } from '../phone-input/phone-input.module'; +import { RightClickMenuModule } from '../../directives/right-click-menu/right-click-menu.module'; +import { InitFocusModule } from '../../directives/init-focus/init-focus.module'; + +const components = [ + FancyTableComponent, + ConfirmDialogComponent, +] + +@NgModule({ + declarations: [...components, TablePickerComponent, CustomColumnsDlgComponent, FancyGroupMenuComponent], + imports: [ + CommonModule, + FormsModule, + NbButtonModule, + NbCalendarRangeModule, + NbCardModule, + NbCheckboxModule, + NbDatepickerModule, + NbDialogModule, + NbIconModule, + NbInputModule, + NbSelectModule, + NbTooltipModule, + NbAlertModule, + NbSpinnerModule, + NbActionsModule, + InfiniteScrollModule, + CurrencyInputModule, + DateInputModule, + DragDropModule, + DropDownListModule, + PagerModule, + RightClickMenuModule, + InitFocusModule, + FileDragDropModule, + PhoneInputModule, + EmailInputModule, + InputsModule + ], + exports: [...components] +}) +export class FancyTableModule { } diff --git a/APP/src/components/fancy-table/seq-calc.service.spec.ts b/APP/src/components/fancy-table/seq-calc.service.spec.ts new file mode 100644 index 0000000..5673904 --- /dev/null +++ b/APP/src/components/fancy-table/seq-calc.service.spec.ts @@ -0,0 +1,12 @@ +import { TestBed } from '@angular/core/testing'; + +import { SeqCalcService } from './seq-calc.service'; + +describe('SeqCalcService', () => { + beforeEach(() => TestBed.configureTestingModule({})); + + it('should be created', () => { + const service: SeqCalcService = TestBed.get(SeqCalcService); + expect(service).toBeTruthy(); + }); +}); diff --git a/APP/src/components/fancy-table/seq-calc.service.ts b/APP/src/components/fancy-table/seq-calc.service.ts new file mode 100644 index 0000000..8e62bcc --- /dev/null +++ b/APP/src/components/fancy-table/seq-calc.service.ts @@ -0,0 +1,162 @@ +import { Inject, Injectable, Optional } from '@angular/core'; +import { FancyDragDrop } from './fancy-selection.model'; +import { ISeqBusinessEntity } from '../../models/base.model'; +import { moveItemInArray } from '@angular/cdk/drag-drop'; +import { UpdateSeqViewModel } from '../../shared/models/common.model'; +import { NumberUtils } from '../../utilities/number-utils'; + +export const SEQ_INTERVAL = 'SEQ_INTERVAL_DEFAULT'; +export const DEFAULT_SEQ_INTERVAL = 1000; +@Injectable({ + providedIn: 'root' +}) +export class SeqCalcService { + private SEQ_INTERVAL_REGULAR_AMOUNT = 10; // Regular interval amount for seq calculation + private baseInterval: number; + constructor( + @Optional() @Inject(SEQ_INTERVAL) baseInterval: number | null, + ) { + + // use DEFAULT when token missing + this.baseInterval = baseInterval ?? DEFAULT_SEQ_INTERVAL; + + } + + calculateAndAssignSeq(event: FancyDragDrop, + allData: ISeqBusinessEntity[], updateSeqFunc: (model: UpdateSeqViewModel) => void) { + let movedRows = [event.focusRow.rowDatum] as ISeqBusinessEntity[]; + this.calculateAndAssignSeqs(event, allData, movedRows, updateSeqFunc); + } + calculateAndAssignSeqs(event: FancyDragDrop, + allData: ISeqBusinessEntity[], + movedRows: ISeqBusinessEntity[], updateSeqFunc: (model: UpdateSeqViewModel) => void) { + let intervalCount = this.baseInterval; + let insertSeq = 0; + //allData = allData.sort((a, b) => a.seq - b.seq); // Ensure allData is sorted by seq before processing + //Find the start seq, and interval amount for moving + if (event.previousRow && event.nextRow) { + console.log('previousRowSeq', event.previousRow.rowDatum.seq, event.nextRow.rowDatum.seq); + if (movedRows.length > 1) { + insertSeq = (event.previousRow.rowDatum.seq + event.nextRow.rowDatum.seq) / (movedRows.length + 1); + intervalCount = insertSeq + (insertSeq % 2); + } else { + insertSeq = (event.previousRow.rowDatum.seq + event.nextRow.rowDatum.seq) / 2; + } + } else if (event.previousRow) { + insertSeq = event.previousRow.rowDatum.seq + this.baseInterval; + } + else if (event.nextRow) { + intervalCount = event.nextRow.rowDatum.seq / movedRows.length; + intervalCount += intervalCount % 2; + } + let updateDocuments = [] as ISeqBusinessEntity[]; + // if (this.tableSettings.taggedRowData.length > 0) { + // movedRows = this.tableSettings.taggedRowData as DocumentGroupTemplate[]; + // } + + //moveItemInArray(allData, event.previousIndex, event.currentIndex); + let needToUpdateAllRows = false; + let lastSeq = null; + for (let i = 0; i < movedRows.length; i++) { + const row = movedRows[i]; + row.seq = Math.round(insertSeq + intervalCount * i); + if (lastSeq != null && (row.seq - lastSeq) === 0) { + needToUpdateAllRows = true; + break; + } + lastSeq = row.seq; + allData.find(d => d.id == row.id).seq = row.seq; + if (allData.filter(d => d.seq == row.seq).length > 1) { + needToUpdateAllRows = true; + } + updateDocuments.push(row); + console.log('newSeq', row.seq); + } + + allData = allData.sort((a, b) => a.seq - b.seq); // Ensure allData is sorted by seq after processing + + if (needToUpdateAllRows) { + this.resetAllSeq(allData, updateSeqFunc, allData[0].seq); + } else { + + updateSeqFunc( + { ids: updateDocuments.map(d => d.id), seqs: updateDocuments.map(d => d.seq) } as UpdateSeqViewModel + ); + } + + } + + resetAllSeq(allData: ISeqBusinessEntity[], updateSeqFunc: (model: UpdateSeqViewModel) => void, startSeq: number = 0) { + let seq = startSeq; + for (let i = 0; i < allData.length; i++) { + const document = allData[i]; + document.seq = seq; + seq += this.baseInterval; + } + if (updateSeqFunc) { + updateSeqFunc( + { ids: allData.map(d => d.id), seqs: allData.map(d => d.seq) } as UpdateSeqViewModel + ); + } + } + + calculateNewSeq( + allData: ISeqBusinessEntity[], + focusedDatum: ISeqBusinessEntity, + insertAfter: boolean, + updateSeqFunc: (model: UpdateSeqViewModel) => void + ): number { + if (!Array.isArray(allData) || !focusedDatum) { + throw new Error("Invalid arguments."); + } + + const base = this.baseInterval; + const idx = allData.indexOf(focusedDatum); + if (idx === -1) throw new Error("focusedDatum is not in allData."); + + + // Helper: resequence using the current array order + const resequence = () => { + for (let i = 0; i < allData.length; i++) { + allData[i].seq = (i + 1) * base; + } + }; + + let newSeq = 0; + + if (insertAfter) { + // Append at end + if (idx === allData.length - 1) { + newSeq = focusedDatum.seq + base; + } else { + const next = allData[idx + 1]; + const gap = next.seq - focusedDatum.seq; + if (gap >= 2) { + newSeq = NumberUtils.Mid(focusedDatum.seq, next.seq); + } else { + // No integer room; normalize then take the middle + //resequence(); + this.resetAllSeq(allData, updateSeqFunc, allData[0].seq); + newSeq = NumberUtils.Mid(allData[idx].seq, allData[idx + 1].seq); + } + } + } else { + // Insert before + if (idx === 0) { + newSeq = focusedDatum.seq - base; + } else { + const prev = allData[idx - 1]; + const gap = focusedDatum.seq - prev.seq; + if (gap >= 2) { + newSeq = NumberUtils.Mid(prev.seq, focusedDatum.seq); + } else { + //resequence(); + this.resetAllSeq(allData, updateSeqFunc, allData[0].seq); + newSeq = NumberUtils.Mid(allData[idx - 1].seq, allData[idx].seq); + } + } + } + + return newSeq; + } +} diff --git a/APP/src/components/fancy-table/table-picker/table-picker.component.html b/APP/src/components/fancy-table/table-picker/table-picker.component.html new file mode 100644 index 0000000..49d122b --- /dev/null +++ b/APP/src/components/fancy-table/table-picker/table-picker.component.html @@ -0,0 +1,22 @@ + + + {{title}} + + + + + + + + + + +
+ + +
+
+
\ No newline at end of file diff --git a/APP/src/components/fancy-table/table-picker/table-picker.component.scss b/APP/src/components/fancy-table/table-picker/table-picker.component.scss new file mode 100644 index 0000000..50270b1 --- /dev/null +++ b/APP/src/components/fancy-table/table-picker/table-picker.component.scss @@ -0,0 +1,8 @@ +.nbCard { + max-width: 1080px; + max-height: 99vh; + height: calc(70vh - 20px); +} +.tableContainer { + height: calc(50vh - 20px); +} diff --git a/APP/src/components/fancy-table/table-picker/table-picker.component.spec.ts b/APP/src/components/fancy-table/table-picker/table-picker.component.spec.ts new file mode 100644 index 0000000..8621a48 --- /dev/null +++ b/APP/src/components/fancy-table/table-picker/table-picker.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; + +import { TablePickerComponent } from './table-picker.component'; + +describe('TablePickerComponent', () => { + let component: TablePickerComponent; + let fixture: ComponentFixture; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [ TablePickerComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(TablePickerComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/APP/src/components/fancy-table/table-picker/table-picker.component.ts b/APP/src/components/fancy-table/table-picker/table-picker.component.ts new file mode 100644 index 0000000..9c05f88 --- /dev/null +++ b/APP/src/components/fancy-table/table-picker/table-picker.component.ts @@ -0,0 +1,92 @@ +import { Component, OnInit, ViewChild } from '@angular/core'; +import { NbDialogRef } from '@nebular/theme'; +import { ArrayUtils } from '../../../utilities/array-utils'; +import { FancyColumn } from '../fancy-row-column.model'; +import { FancySelection } from '../fancy-selection.model'; +import { FancySettings } from '../fancy-settings.model'; +import { FancyTableComponent } from '../fancy-table.component'; + +export const TABLE_PICKER_COL_CHECK_NAME = 'uiChecked'; +const COL_ID = 'id'; +@Component({ + selector: 'ngx-table-picker', + templateUrl: './table-picker.component.html', + styleUrls: ['./table-picker.component.scss'] +}) +export class TablePickerComponent implements OnInit { + + @ViewChild(FancyTableComponent) fancyTable: FancyTableComponent; + title: string; + data: any[]; + selectedIds: string[]; + settings: FancySettings; + displaySubmitBtn: boolean; + datum: any; + cardCssClass: string = 'nbCard'; + constructor( + private dlgRef: NbDialogRef + ) { } + + ngOnInit() { + if (this.settings.multiselect && !this.settings.columns.find(c => c.name == TABLE_PICKER_COL_CHECK_NAME)) { + + this.settings.columns.unshift( + new FancyColumn({ name: TABLE_PICKER_COL_CHECK_NAME, type: 'checkall', widthPx: 20 }) + ); + } + if (this.settings.dialogCssClass) { + this.cardCssClass = this.settings.dialogCssClass; + } + for (let i = 0; i < this.data.length; i++) { + const element = this.data[i]; + element[TABLE_PICKER_COL_CHECK_NAME] = this.selectedIds.includes(element[COL_ID]); + } + + setTimeout(() => { + if (!this.settings.multiselect) { + this.fancyTable.searchRowAndFocus(r => r[COL_ID] == this.selectedIds[0]); + } + }, 200); + } + submit() { + if (this.settings.multiselect) { + this.removeCheckboxCol(); + this.dlgRef.close(this.data.filter(d => d[TABLE_PICKER_COL_CHECK_NAME])); + } else { + //let selectedId = this.fancyTable.getSelected().focusedRow[COL_ID]; + this.dlgRef.close(this.fancyTable.getSelected().focusedRow); + + } + } + close() { + this.removeCheckboxCol(); + this.dlgRef.close(null); + } + removeCheckboxCol() { + if (this.settings.multiselect) { + this.settings.multiselect = false; + this.settings.columns.splice(0, 1); + } + } + + doubleClickRow(select: FancySelection) { + if (this.displaySubmitBtn) { + if (!this.settings.multiselect) { + this.dlgRef.close(select.focusedRow); + } else { + this.data[select.index][TABLE_PICKER_COL_CHECK_NAME] = !this.data[select.index][TABLE_PICKER_COL_CHECK_NAME]; + } + } else { + let editContextMenuItem = this.settings.contextMenuItems.find(c => c.id == 'edit'); + if (editContextMenuItem) { + editContextMenuItem.callback(select.focusedRow, null); + } + } + + } + selectRow(select: FancySelection) { + if (select) { + this.datum = select.focusedRow; + } + } +} diff --git a/APP/src/components/loading-spinner/loading-spinner.component.html b/APP/src/components/loading-spinner/loading-spinner.component.html new file mode 100644 index 0000000..c83818a --- /dev/null +++ b/APP/src/components/loading-spinner/loading-spinner.component.html @@ -0,0 +1,9 @@ + +
\ No newline at end of file diff --git a/APP/src/components/loading-spinner/loading-spinner.component.scss b/APP/src/components/loading-spinner/loading-spinner.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/APP/src/components/loading-spinner/loading-spinner.component.spec.ts b/APP/src/components/loading-spinner/loading-spinner.component.spec.ts new file mode 100644 index 0000000..8273775 --- /dev/null +++ b/APP/src/components/loading-spinner/loading-spinner.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; + +import { LoadingSpinnerComponent } from './loading-spinner.component'; + +describe('LoadingSpinnerComponent', () => { + let component: LoadingSpinnerComponent; + let fixture: ComponentFixture; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [ LoadingSpinnerComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(LoadingSpinnerComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/APP/src/components/loading-spinner/loading-spinner.component.ts b/APP/src/components/loading-spinner/loading-spinner.component.ts new file mode 100644 index 0000000..dc71182 --- /dev/null +++ b/APP/src/components/loading-spinner/loading-spinner.component.ts @@ -0,0 +1,14 @@ +import { Component, OnInit, Input } from '@angular/core'; + +@Component({ + selector: 'loading-spinner', + templateUrl: './loading-spinner.component.html', + styleUrls: ['./loading-spinner.component.scss'] +}) +export class LoadingSpinnerComponent { + + @Input() public isLoading: boolean = false; + + constructor() { } + +} diff --git a/APP/src/components/loading-spinner/loading-spinner.module.ts b/APP/src/components/loading-spinner/loading-spinner.module.ts new file mode 100644 index 0000000..4116dee --- /dev/null +++ b/APP/src/components/loading-spinner/loading-spinner.module.ts @@ -0,0 +1,16 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { NbSpinnerModule } from '@nebular/theme'; +import { LoadingSpinnerComponent } from './loading-spinner.component'; + +const components = [LoadingSpinnerComponent,]; + +@NgModule({ + declarations: [...components], + imports: [ + CommonModule, + NbSpinnerModule, + ], + exports: [...components], +}) +export class LoadingSpinnerModule { } diff --git a/APP/src/components/pager/pager.component.html b/APP/src/components/pager/pager.component.html new file mode 100644 index 0000000..805bd24 --- /dev/null +++ b/APP/src/components/pager/pager.component.html @@ -0,0 +1,48 @@ +
+ +
\ No newline at end of file diff --git a/APP/src/components/pager/pager.component.scss b/APP/src/components/pager/pager.component.scss new file mode 100644 index 0000000..f00e031 --- /dev/null +++ b/APP/src/components/pager/pager.component.scss @@ -0,0 +1,12 @@ +nb-icon { + // margin-top: -2px !important; + // margin-bottom: -2px !important; + margin: -2px -6px !important; +} +ul { + margin-top: 0; + margin-bottom: 0px; +} +.justify-content-center { + margin-bottom: 0 !important; +} diff --git a/APP/src/components/pager/pager.component.spec.ts b/APP/src/components/pager/pager.component.spec.ts new file mode 100644 index 0000000..8dea758 --- /dev/null +++ b/APP/src/components/pager/pager.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; + +import { PagerComponent } from './pager.component'; + +describe('PagerComponent', () => { + let component: PagerComponent; + let fixture: ComponentFixture; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [ PagerComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(PagerComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/APP/src/components/pager/pager.component.ts b/APP/src/components/pager/pager.component.ts new file mode 100644 index 0000000..8afb91c --- /dev/null +++ b/APP/src/components/pager/pager.component.ts @@ -0,0 +1,98 @@ +import { Component, Input, Output, EventEmitter, OnChanges, SimpleChanges } from '@angular/core'; +import { NumberUtils } from '../../utilities/number-utils'; + +@Component({ + selector: 'pager', + templateUrl: './pager.component.html', + styleUrls: ['./pager.component.scss'] +}) +export class PagerComponent implements OnChanges { + + @Input() public alignJustify: boolean = true; + @Input() public page: number; + @Input() public pages: number[]; + @Input() public toolTips: string[]; + @Input() public minPage: number = 1; + @Input() public maxPage: number = Number.POSITIVE_INFINITY; // default no limit + @Input() public prefix: string = "Page"; + @Input() public numberOfPages: number = 5; // max number of pages to show at a time between "Previous" and "Next" buttons + @Output() public pageChange = new EventEmitter(); + @Output() public addingNewPage = new EventEmitter(); + + pagesStart: number; + pagesEnd: number; + + constructor() { } + + public get lastPage(): number { + return this.pages[this.pages.length - 1]; + } + + ngOnChanges(change: SimpleChanges) { + if (false + || (change.pages && change.pages.currentValue !== change.pages.previousValue) + || (change.maxPage && change.maxPage.currentValue !== change.maxPage.previousValue) + ) { + if (this.pages.length > 0 && (this.page > Math.min(this.pages.length + 1, this.maxPage) || this.page < this.minPage)) { + + this.page = NumberUtils.Clamp(this.page, this.minPage, Math.min(this.pages.length + 1, this.maxPage)); + this.gotoPage(this.page); + // this.calculatePages(); + } + } + if (false + || (change.page && change.page.currentValue !== change.page.previousValue) + || (change.maxPage && change.maxPage.currentValue !== change.maxPage.previousValue) + ) { + if (this.pages.length > 0) { + + this.page = NumberUtils.Clamp(this.page, this.minPage, Math.min(this.pages.length + 1, this.maxPage)); + // + this.calculatePages(); + } + } + } + + gotoPage(p: number) { + // + this.pageChange.emit(p); + } + + getPageToolTip(page: number): string { + if (this.toolTips && this.toolTips.length > (page - 1)) { + return this.toolTips[(page - 1)]; + } + return `Page ${page}`; + } + + getNewPageToolTip(): string { + return this.page === this.pages[this.pages.length - 1] ? `New ${this.prefix}` : `Next ${this.prefix}`; + } + + calculatePages() { + // + if (this.pages.length > 0) { + + if (!this.pages.includes(this.page)) { + this.pages.push(this.page); + this.pages = this.pages.sort(NumberUtils.SortFunction); + } + let currentPage = this.page; + let lastPage = this.pages[this.pages.length - 1]; + let pagesAfter = Math.floor(this.numberOfPages / 2); + let pagesBefore = this.numberOfPages - pagesAfter; + if (currentPage - pagesBefore < 0) { + pagesAfter += pagesBefore - currentPage; + } + if (currentPage + pagesAfter > lastPage) { + pagesBefore += currentPage + pagesAfter - lastPage; + } + this.pagesStart = Math.max(0, currentPage - pagesBefore); + this.pagesEnd = Math.min(lastPage, currentPage + pagesAfter); + } + } + addNewPage() { + if (this.page < this.maxPage) + this.addingNewPage.emit(this.page + 1); + } +} diff --git a/APP/src/components/pager/pager.module.ts b/APP/src/components/pager/pager.module.ts new file mode 100644 index 0000000..bbcd0e6 --- /dev/null +++ b/APP/src/components/pager/pager.module.ts @@ -0,0 +1,21 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { PagerComponent } from './pager.component'; +import { NbIconModule, NbButtonModule, NbTooltipModule } from '@nebular/theme'; + +const components = [ + PagerComponent, +] + +@NgModule({ + declarations: [...components], + imports: [ + CommonModule, + + NbButtonModule, + NbIconModule, + NbTooltipModule, + ], + exports: [...components], +}) +export class PagerModule { } diff --git a/APP/src/components/password-input/password-input.component.html b/APP/src/components/password-input/password-input.component.html new file mode 100644 index 0000000..544ee0b --- /dev/null +++ b/APP/src/components/password-input/password-input.component.html @@ -0,0 +1,11 @@ +
+ +
+ +
+
\ No newline at end of file diff --git a/APP/src/components/password-input/password-input.component.scss b/APP/src/components/password-input/password-input.component.scss new file mode 100644 index 0000000..839f95d --- /dev/null +++ b/APP/src/components/password-input/password-input.component.scss @@ -0,0 +1,19 @@ +:host { + flex: auto; + display: contents; +} + +.input-group { + display: flex; + align-items: stretch; + width: 100%; + + input { + flex: 1; + min-width: 0; + } + + .input-group-append { + flex-shrink: 0; + } +} diff --git a/APP/src/components/password-input/password-input.component.spec.ts b/APP/src/components/password-input/password-input.component.spec.ts new file mode 100644 index 0000000..7f94045 --- /dev/null +++ b/APP/src/components/password-input/password-input.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { FormsModule } from '@angular/forms'; +import { PasswordInputComponent } from './password-input.component'; +import { PasswordInputModule } from './password-input.module'; + +describe('PasswordInputComponent', () => { + let component: PasswordInputComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [FormsModule, PasswordInputModule], + }).compileComponents(); + + fixture = TestBed.createComponent(PasswordInputComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/APP/src/components/password-input/password-input.component.ts b/APP/src/components/password-input/password-input.component.ts new file mode 100644 index 0000000..1b88535 --- /dev/null +++ b/APP/src/components/password-input/password-input.component.ts @@ -0,0 +1,78 @@ +import { + ChangeDetectorRef, + Component, + EventEmitter, + forwardRef, + Input, + Output, +} from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { ControlContainer, NgForm } from '@angular/forms'; +import { UuidUtils } from '../../utilities/uuid-utils'; + +@Component({ + selector: 'rbj-password-input', + templateUrl: './password-input.component.html', + styleUrl: './password-input.component.scss', + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => PasswordInputComponent), + multi: true, + }, + ], + viewProviders: [{ provide: ControlContainer, useExisting: NgForm }], +}) +export class PasswordInputComponent implements ControlValueAccessor { + private _value = ''; + disabledState = false; + uuid: string = UuidUtils.generate(); + /** + * + */ + constructor( + private cdRef: ChangeDetectorRef, + ) { + + } + @Input() id = ''; + @Input() placeholder = 'Passphrase'; + text: string = ''; + + + onChange = (value: string) => { }; + onTouched = () => { }; + @Output() blur = new EventEmitter(); + + fieldTextType = false; + + toggleFieldTextType(): void { + this.fieldTextType = !this.fieldTextType; + } + + writeValue(value: string): void { + if (value != this.lastBlurValue) { + this.text = value; + this.lastBlurValue = value; + } + } + + registerOnChange(fn: (value: string) => void): void { + this.onChange = fn; + } + registerOnTouched(fn: any): void { + this.onTouched = fn; + } + + setDisabledState(isDisabled: boolean): void { + this.disabledState = isDisabled; + } + + lastBlurValue: string = ''; + onBlur() { + if (this.lastBlurValue != this.text) { + this.lastBlurValue = this.text; + this.blur.emit(this.text); + } + } +} diff --git a/APP/src/components/password-input/password-input.module.ts b/APP/src/components/password-input/password-input.module.ts new file mode 100644 index 0000000..94d4aa9 --- /dev/null +++ b/APP/src/components/password-input/password-input.module.ts @@ -0,0 +1,19 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { NbInputModule, NbButtonModule, NbIconModule, NbTooltipModule } from '@nebular/theme'; +import { PasswordInputComponent } from './password-input.component'; + +@NgModule({ + declarations: [PasswordInputComponent], + imports: [ + CommonModule, + FormsModule, + NbInputModule, + NbButtonModule, + NbIconModule, + NbTooltipModule, + ], + exports: [PasswordInputComponent], +}) +export class PasswordInputModule {} diff --git a/APP/src/components/phone-input/phone-input.component.html b/APP/src/components/phone-input/phone-input.component.html new file mode 100644 index 0000000..3767081 --- /dev/null +++ b/APP/src/components/phone-input/phone-input.component.html @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/APP/src/components/phone-input/phone-input.component.scss b/APP/src/components/phone-input/phone-input.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/APP/src/components/phone-input/phone-input.component.spec.ts b/APP/src/components/phone-input/phone-input.component.spec.ts new file mode 100644 index 0000000..cfc26cf --- /dev/null +++ b/APP/src/components/phone-input/phone-input.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; + +import { PhoneInputComponent } from './phone-input.component'; + +describe('PhoneInputComponent', () => { + let component: PhoneInputComponent; + let fixture: ComponentFixture; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [ PhoneInputComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(PhoneInputComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/APP/src/components/phone-input/phone-input.component.ts b/APP/src/components/phone-input/phone-input.component.ts new file mode 100644 index 0000000..44758f9 --- /dev/null +++ b/APP/src/components/phone-input/phone-input.component.ts @@ -0,0 +1,146 @@ +import { Component, OnInit, ChangeDetectionStrategy, forwardRef, ViewChild, ElementRef, Output, Input, EventEmitter, Renderer2 } from '@angular/core'; +import { NG_VALUE_ACCESSOR, ControlContainer, NgForm, NgModel } from '@angular/forms'; +import { ApplyMaskService } from '../../services/apply-mask.service'; +import { Util } from 'leaflet'; +import { StringUtils } from '../../utilities/string-utils'; +import { UuidUtils } from '../../utilities/uuid-utils'; +import { NbPopoverDirective } from '@nebular/theme'; +@Component({ + selector: 'rbj-phone-input', + templateUrl: './phone-input.component.html', + styleUrls: ['./phone-input.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => PhoneInputComponent), + multi: true + } + ], + viewProviders: [{ provide: ControlContainer, useExisting: NgForm }] +}) +export class PhoneInputComponent implements OnInit { + + @ViewChild('inputBox', { static: true }) input: NgModel; + @ViewChild(NbPopoverDirective) popover: NbPopoverDirective; + private _value: string; + private _initialized: boolean; + private _legalInput: boolean = false; + private _allowExtension: boolean = true; + private _lastBlurValue: string; + _readOnly: boolean; + + + onChange = (value: string) => { }; + onTouched = () => { }; + uuid: string = UuidUtils.generate(); + name: string; + outputWithSymbol: boolean = true; + isValid: boolean = true; + + + @Input() id?: string = '' + @Input() placeholder: string; + @Input() class: string; + @Input() size: string = 'medium'; + @Input() maskExpression: string = '(000) 000-0000'; + @Input() extensionMask: string = ' ??????????'; + _disabled = false; + @Input() + public set readonly(value) { + this._readOnly = typeof value !== 'undefined' && value !== false; + } + public get readonly(): boolean { + return this._readOnly; + } + + @Input() + public set allowExtension(value) { + this._allowExtension = typeof value !== 'undefined' && value !== false; + } + public get allowExtension(): boolean { + return this._allowExtension; + } + + + @Output() inputChange = new EventEmitter(); + @Output() focus = new EventEmitter(); + + constructor( + private renderer: Renderer2, + private el: ElementRef, private applyMaskService: ApplyMaskService) { } + + ngAfterViewInit() { + this.renderer.removeAttribute(this.el.nativeElement, 'id') + } + //#region Implements + writeValue(value: string): void { + if (value) { + if (value == "( ) -") value = ""; + + this.value = this.outputWithSymbol ? value : value.replace(/[^\d]/g, ''); + //this.setDisplayFormat(this.value); + } else { + this.value = null; + } + this._lastBlurValue = this.value; + //this.onChange(this.value); + } + registerOnChange(fn: (value: string) => void): void { + this.onChange = fn; + } + registerOnTouched(fn: any): void { + this.onTouched = fn; + } + setDisabledState?(isDisabled: boolean): void { + this._disabled = isDisabled; + } + //#endregion + + + public get value(): string { + return this._value; + } + public set value(v: string) { + + if (this._initialized) { + if (v != this._value) { + this._value = v; + } + } else { + this._initialized = true; + this._value = v; + } + } + + onFocus() { + + if (this.input.invalid) { + + this.popover.show(); + } else { + + this.popover.hide(); + } + } + onFocusOut() { + if (this.input.invalid) { + setTimeout(() => { + this.el.nativeElement.querySelector('input').focus(); + }, 200); + } else { + + this.popover.hide(); + } + } + ShowPopover() { + } + + ngOnInit() { + } + onBlur() { + if (this._lastBlurValue != this.value) { + this.onChange(this.value); + this._lastBlurValue = this.value; + } + } +} diff --git a/APP/src/components/phone-input/phone-input.module.ts b/APP/src/components/phone-input/phone-input.module.ts new file mode 100644 index 0000000..60293cf --- /dev/null +++ b/APP/src/components/phone-input/phone-input.module.ts @@ -0,0 +1,20 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { PhoneInputComponent } from './phone-input.component'; +import { FormsModule } from '@angular/forms'; +import { NbInputModule, NbPopoverModule, NbIconModule } from '@nebular/theme'; +import { MaskDirectiveModule } from '../../directives/mask-directive/mask-directive.module'; + +@NgModule({ + declarations: [PhoneInputComponent], + imports: [ + CommonModule, + FormsModule, + NbInputModule, + MaskDirectiveModule, + NbPopoverModule, + NbIconModule + ], + exports: [PhoneInputComponent] +}) +export class PhoneInputModule { } diff --git a/APP/src/components/pipes/ordinal.pipe.ts b/APP/src/components/pipes/ordinal.pipe.ts new file mode 100644 index 0000000..5ca7b50 --- /dev/null +++ b/APP/src/components/pipes/ordinal.pipe.ts @@ -0,0 +1,11 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import { NumberUtils } from '../../utilities/number-utils'; + +@Pipe({ + name: 'ordinal', +}) +export class OrdinalPipe implements PipeTransform { + transform(value: number): string { + return NumberUtils.Ordinal(value); + } +} diff --git a/APP/src/components/popover-msg/popover-msg.component.html b/APP/src/components/popover-msg/popover-msg.component.html new file mode 100644 index 0000000..53bc4ba --- /dev/null +++ b/APP/src/components/popover-msg/popover-msg.component.html @@ -0,0 +1,10 @@ +
+ +
+
+ ! + {{context.message}} +
+
+ +
\ No newline at end of file diff --git a/APP/src/components/popover-msg/popover-msg.component.scss b/APP/src/components/popover-msg/popover-msg.component.scss new file mode 100644 index 0000000..d934706 --- /dev/null +++ b/APP/src/components/popover-msg/popover-msg.component.scss @@ -0,0 +1,61 @@ +$arrow-width: 12; +$inner-arrow-width: 11; +$border-color: #808080; +:host { + .arrow { + width: 0; + height: 0; + margin-left: $arrow-width - 2 + px; + z-index: 50; + border-left: $arrow-width + px solid transparent; + border-right: $arrow-width + px solid transparent; + + &.up { + border-bottom: $arrow-width + px solid $border-color; + } + &.down { + border-top: $arrow-width + px solid $border-color; + margin-top: -1px; + } + + &:before { + height: $inner-arrow-width + px; + display: block; + width: $inner-arrow-width + px; + border-left: $inner-arrow-width + px solid transparent; + border-right: $inner-arrow-width + px solid transparent; + margin-left: -$inner-arrow-width + px; + position: absolute; + content: ""; + } + + &.up:before { + border-bottom: $inner-arrow-width + px solid white; + top: 1px; + } + + &.down:before { + border-top: $inner-arrow-width + px solid white; + bottom: 1px; + } + } + .box { + background: white; + border-radius: 0.25rem; + -webkit-box-shadow: 3px 3px 6px 0px rgba(0, 0, 0, 0.25); + -moz-box-shadow: 3px 3px 6px 0px rgba(0, 0, 0, 0.25); + box-shadow: 3px 3px 6px 0px rgba(0, 0, 0, 0.25); + border: solid $border-color; + border-width: 1px; + margin-top: -1px; + } + .symbol { + border-radius: 0.2rem; + background-color: #ffa300; + color: white; + padding: 1px 10px; + font-size: 16px; + font-weight: 900; + margin-right: 10px; + } +} diff --git a/APP/src/components/popover-msg/popover-msg.component.spec.ts b/APP/src/components/popover-msg/popover-msg.component.spec.ts new file mode 100644 index 0000000..a2ea732 --- /dev/null +++ b/APP/src/components/popover-msg/popover-msg.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; + +import { PopoverMsgComponent } from './popover-msg.component'; + +describe('PopoverMsgComponent', () => { + let component: PopoverMsgComponent; + let fixture: ComponentFixture; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [ PopoverMsgComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(PopoverMsgComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/APP/src/components/popover-msg/popover-msg.component.ts b/APP/src/components/popover-msg/popover-msg.component.ts new file mode 100644 index 0000000..5279450 --- /dev/null +++ b/APP/src/components/popover-msg/popover-msg.component.ts @@ -0,0 +1,26 @@ +import { Component, OnInit, ChangeDetectionStrategy, Input } from '@angular/core'; +import { NbPositionedContainerComponent, NbRenderableContainer, NbPosition } from '@nebular/theme'; + +@Component({ + selector: 'ngx-popover-msg', + templateUrl: './popover-msg.component.html', + styleUrls: ['./popover-msg.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class PopoverMsgComponent extends NbPositionedContainerComponent implements NbRenderableContainer { + renderContent() { + //throw new Error("Method not implemented."); + } + + @Input() message: string + + @Input() + context: { message?: string, position: NbPosition } = { + position: NbPosition.BOTTOM_END + }; + ngOnInit() { + this.message = this.context.message; + this.position = this.context.position || NbPosition.BOTTOM_END; + } + +} diff --git a/APP/src/components/popover-msg/popover-msg.module.ts b/APP/src/components/popover-msg/popover-msg.module.ts new file mode 100644 index 0000000..5cd4eb7 --- /dev/null +++ b/APP/src/components/popover-msg/popover-msg.module.ts @@ -0,0 +1,12 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { PopoverMsgComponent } from './popover-msg.component'; + +@NgModule({ + declarations: [PopoverMsgComponent], + imports: [ + CommonModule + ], + exports: [PopoverMsgComponent] +}) +export class PopoverMsgModule { } diff --git a/APP/src/components/rbj-md-editor/rbj-md-editor.component.html b/APP/src/components/rbj-md-editor/rbj-md-editor.component.html new file mode 100644 index 0000000..79b78de --- /dev/null +++ b/APP/src/components/rbj-md-editor/rbj-md-editor.component.html @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/APP/src/components/rbj-md-editor/rbj-md-editor.component.scss b/APP/src/components/rbj-md-editor/rbj-md-editor.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/APP/src/components/rbj-md-editor/rbj-md-editor.component.spec.ts b/APP/src/components/rbj-md-editor/rbj-md-editor.component.spec.ts new file mode 100644 index 0000000..87cb473 --- /dev/null +++ b/APP/src/components/rbj-md-editor/rbj-md-editor.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { RbjMdEditorComponent } from './rbj-md-editor.component'; + +describe('RbjMdEditorComponent', () => { + let component: RbjMdEditorComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [RbjMdEditorComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(RbjMdEditorComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/APP/src/components/rbj-md-editor/rbj-md-editor.component.ts b/APP/src/components/rbj-md-editor/rbj-md-editor.component.ts new file mode 100644 index 0000000..ba30679 --- /dev/null +++ b/APP/src/components/rbj-md-editor/rbj-md-editor.component.ts @@ -0,0 +1,96 @@ +import { AfterViewInit, Component, ElementRef, forwardRef, Input, OnDestroy, ViewChild } from '@angular/core'; +import { NG_VALUE_ACCESSOR } from '@angular/forms'; +import Editor from '@toast-ui/editor'; +@Component({ + selector: 'rbj-md-editor', + standalone: true, + imports: [], + templateUrl: './rbj-md-editor.component.html', + styleUrl: './rbj-md-editor.component.scss', + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => RbjMdEditorComponent), + multi: true, + }, + ], +}) +export class RbjMdEditorComponent implements AfterViewInit, OnDestroy { + @ViewChild('host', { static: true }) host!: ElementRef; + + /** Choose the starting mode (user can still toggle with UI if you add your own buttons) */ + @Input() initialEditType: 'wysiwyg' | 'markdown' = 'wysiwyg'; + @Input() height = '400px'; + + private editor?: Editor; + private _value = ''; + private _disabled = false; + + private onChange: (val: string) => void = () => { }; + private onTouched: () => void = () => { }; + + ngAfterViewInit(): void { + this.editor = new Editor({ + el: this.host.nativeElement, + height: this.height, + initialEditType: this.initialEditType, + previewStyle: 'vertical', + initialValue: this._value ?? '', + usageStatistics: false, + }); + + // Toast UI emits "change" + this.editor.on('change', () => { + const md = this.editor?.getMarkdown() ?? ''; + this._value = md; + this.onChange(md); + this.onTouched(); + }); + + this.applyDisabled(); + } + + ngOnDestroy(): void { + this.editor?.destroy(); + } + + // ---- ControlValueAccessor ---- + writeValue(value: string | null): void { + this._value = value ?? ''; + if (this.editor) { + // Avoid resetting cursor unless value really changed + const current = this.editor.getMarkdown(); + if (current !== this._value) { + this.editor.setMarkdown(this._value, false); + } + } + } + + registerOnChange(fn: (val: string) => void): void { + this.onChange = fn; + } + + registerOnTouched(fn: () => void): void { + this.onTouched = fn; + } + + setDisabledState(isDisabled: boolean): void { + this._disabled = isDisabled; + this.applyDisabled(); + } + + private applyDisabled(): void { + if (!this.editor) return; + + // Toast UI doesn't have a perfect universal "disable" API across all versions; + // simplest is to toggle contenteditable. + const root = this.host.nativeElement; + root.querySelectorAll('[contenteditable]').forEach((el) => { + (el as HTMLElement).setAttribute('contenteditable', (!this._disabled).toString()); + }); + + // Also block pointer events for toolbar etc. + root.style.pointerEvents = this._disabled ? 'none' : 'auto'; + root.style.opacity = this._disabled ? '0.7' : '1'; + } +} \ No newline at end of file diff --git a/APP/src/components/reload-dummy/reload-dummy.component.spec.ts b/APP/src/components/reload-dummy/reload-dummy.component.spec.ts new file mode 100644 index 0000000..b883442 --- /dev/null +++ b/APP/src/components/reload-dummy/reload-dummy.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; + +import { ReloadDummyComponent } from './reload-dummy.component'; + +describe('ReloadDummyComponent', () => { + let component: ReloadDummyComponent; + let fixture: ComponentFixture; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [ ReloadDummyComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ReloadDummyComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/APP/src/components/reload-dummy/reload-dummy.component.ts b/APP/src/components/reload-dummy/reload-dummy.component.ts new file mode 100644 index 0000000..ae03bdb --- /dev/null +++ b/APP/src/components/reload-dummy/reload-dummy.component.ts @@ -0,0 +1,11 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'reload-dummy', + template: '', +}) +export class ReloadDummyComponent { + + constructor() { } + +} diff --git a/APP/src/components/resizable-box/resizable-box-container/resizable-box-container.component.html b/APP/src/components/resizable-box/resizable-box-container/resizable-box-container.component.html new file mode 100644 index 0000000..1a9f5cf --- /dev/null +++ b/APP/src/components/resizable-box/resizable-box-container/resizable-box-container.component.html @@ -0,0 +1,6 @@ +
+ +
+
+ +
\ No newline at end of file diff --git a/APP/src/components/resizable-box/resizable-box-container/resizable-box-container.component.scss b/APP/src/components/resizable-box/resizable-box-container/resizable-box-container.component.scss new file mode 100644 index 0000000..15ed15f --- /dev/null +++ b/APP/src/components/resizable-box/resizable-box-container/resizable-box-container.component.scss @@ -0,0 +1,15 @@ +.resizableBoxContainer { + width: 100%; + //@at-root: 80vh; + overflow: hidden; +} +.boxHolder { + border-left: 1px solid rgba(221, 221, 221, 0.6); + -webkit-box-shadow: inset 2px 0 0 0 rgba(0, 0, 0, 0.3); + box-shadow: inset 2px 0 0 0 rgba(0, 0, 0, 0.3); + background-color: rgb(86, 202, 133); +} + +.hidden { + display: none; +} diff --git a/APP/src/components/resizable-box/resizable-box-container/resizable-box-container.component.spec.ts b/APP/src/components/resizable-box/resizable-box-container/resizable-box-container.component.spec.ts new file mode 100644 index 0000000..187538f --- /dev/null +++ b/APP/src/components/resizable-box/resizable-box-container/resizable-box-container.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; + +import { ResizableBoxContainerComponent } from './resizable-box-container.component'; + +describe('ResizableBoxContainerComponent', () => { + let component: ResizableBoxContainerComponent; + let fixture: ComponentFixture; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [ ResizableBoxContainerComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ResizableBoxContainerComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/APP/src/components/resizable-box/resizable-box-container/resizable-box-container.component.ts b/APP/src/components/resizable-box/resizable-box-container/resizable-box-container.component.ts new file mode 100644 index 0000000..68d4cc8 --- /dev/null +++ b/APP/src/components/resizable-box/resizable-box-container/resizable-box-container.component.ts @@ -0,0 +1,217 @@ +import { Component, OnInit, ChangeDetectionStrategy, ContentChildren, QueryList, ElementRef, ViewChild, Input, Output, EventEmitter } from '@angular/core'; +import { ResizableBoxComponent, boxSpec, size } from '../resizable-box.component'; + +@Component({ + selector: 'rbj-resizable-box-container', + templateUrl: './resizable-box-container.component.html', + styleUrls: ['./resizable-box-container.component.scss'] +}) +export class ResizableBoxContainerComponent implements OnInit { + + private _specList: boxSpec[]; + private _enableDrag: boolean = false; + + @ContentChildren(ResizableBoxComponent) boxes: QueryList; + + @ViewChild('container', { static: true }) containerDiv: ElementRef; + @ViewChild('boxHolder', { static: true }) boxHolder: ResizableBoxComponent; + + layoutChanged: EventEmitter = new EventEmitter(); + + @Input() + padding: number = 10; + + @Input() + gridSizeY: number = 20; + @Input() + gridSizeX: number = 120; + + @Input() + boxMinSize = { height: 150, width: 150 } as size; + + @Input() + public set enableDragging(v: boolean) { + if (this._enableDrag != v) { + + this._enableDrag = v; + this.boxes.forEach(box => { + box.draggable = v; + }); + this.calculateContainerHeight(); + } + } + + public get enableDragging(): boolean { + return this._enableDrag; + } + public set specList(v: boxSpec[]) { + this._specList = v; + } + public get specList(): boxSpec[] { + + return this._specList; + } + max_X: number; + holderSpec = new boxSpec('', 0, 0, 0, 0); + displayHolder: boolean = false; + containerHeight: number = 500; + + constructor() { } + + ngOnInit() { + } + + ngAfterContentInit() { + + this.initializeBoxes(); + }; + + initializeBoxes() { + if (this.specList) { + this.specList.forEach(spec => { + var box = this.boxes.find(b => b.id == spec.id); + box.change.subscribe(boxSpec => this.boxChanged(boxSpec)); + box.draggingStart.subscribe(boxSpec => { + this.displayHolder = true; + this.boxHolder.moveAndResize(boxSpec) + }) + box.draggingEnd.subscribe(boxSpec => { + this.displayHolder = false; + this.getBoxById(boxSpec.id).moveAndResize(this.holderSpec) + + this.boxes.toArray().filter(b => b.id != this.holderSpec.id).forEach(box => { + var newSpec = box.spec + this.fillVerticalEmpty(newSpec); + if (box.spec.diff(newSpec)) { + box.moveAndResize(newSpec); + } + }); + }) + box.animationMoveEnd.subscribe(() => { this.moving = false; }) + }); + this.renderBoxes(this.specList); + } + } + + renderBoxes(specList: boxSpec[] = null) { + this.max_X = Number(this.containerDiv.nativeElement.clientWidth); + this.max_X = this.max_X > 1582 ? 1582 : this.max_X; + this.gridSizeX = (this.max_X - this.padding * 12) / 12; + if (!specList) { + specList = this.boxes.map(b => b.spec); + } + this.boxHolder.gridSizeX = this.gridSizeX; + this.boxHolder.gridSizeY = this.gridSizeY; + this.boxHolder.gridPadding = this.padding; + specList.forEach(spec => { + var box = this.boxes.find(b => b.id == spec.id); + box.max_X = this.max_X; + box.gridSizeX = this.gridSizeX; + box.gridSizeY = this.gridSizeY; + box.gridPadding = this.padding; + box.moveAndResize(spec); + }); + this.calculateContainerHeight(specList); + } + + calculateContainerHeight(specList: boxSpec[] = null) { + if (!specList) { + specList = this.boxes.map(b => b.spec); + } + this.containerHeight = (Math.max(...specList.map(spec => (spec.position.y + spec.size.height + 1)))) * this.gridSizeY; + + } + + boxChanged(spec: boxSpec) { + this.holderSpec.id = spec.id; + + this.holderSpec.position.x = spec.position.x; + this.holderSpec.position.y = spec.position.y; + this.holderSpec.size.width = spec.size.width; + this.holderSpec.size.height = spec.size.height; + // this.holderSpec.position.x = Math.round(spec.position.x / this.gridSizeX); + // this.holderSpec.position.y = Math.round(spec.position.y / this.gridSizeY); + // this.holderSpec.size.width = Math.round(spec.size.width / this.gridSizeX); + // this.holderSpec.size.height = Math.round(spec.size.height / this.gridSizeY); + if (this.boxHolder.spec.diff(this.holderSpec)) { + + //if (this.checkSameColBox(spec)) return; + //this.checkSameRowBox(this.holderSpec); + this.fillVerticalEmpty(this.holderSpec); + this.getBoxById(spec.id).y = this.holderSpec.top; + this.boxHolder.moveAndResize(this.holderSpec, 200); + this.checkCrossBox(this.holderSpec, 1); + + this.layoutChanged.next(true); + }; + } + moving: boolean = false; + maxStack: number = 0; + + checkCrossBox(spec: boxSpec, stack: number) { + var otherBoxes = this.boxes.toArray().filter(b => b.id != spec.id); + for (let i = 0; i < otherBoxes.length; i++) { + this.maxStack = this.maxStack > stack ? this.maxStack : stack; + + const box = otherBoxes[i]; + if (box.spec.top >= spec.top && spec.bottom >= box.spec.top && ( + box.x <= spec.left && box.spec.right > spec.left || + box.x >= spec.left && box.x < spec.right) + ) { + var moveTo = new boxSpec(box.id, box.x, spec.bottom + 1, box.width, box.height); + + this.fillVerticalEmpty(moveTo); + box.moveAndResize(moveTo); + this.checkCrossBox(moveTo, stack + 1); + }; + } + if (this.maxStack == stack) { + this.maxStack = 0; + this.boxes.toArray().filter(b => b.id != this.holderSpec.id).forEach(box => { + var newSpec = box.spec + this.fillVerticalEmpty(newSpec); + if (box.spec.diff(newSpec)) { + box.moveAndResize(newSpec); + } + }); + + this.calculateContainerHeight(); + } + } + + fillVerticalEmpty(spec: boxSpec) { + var otherBoxes = this.boxes.toArray().filter(b => b.id != spec.id && + b.spec.top < spec.top + && ( + b.x <= spec.left && b.spec.right > spec.left || + b.x >= spec.left && b.x < spec.right)); + + if (otherBoxes.length == 0) { + spec.position.y = 0; + } + else { + spec.position.y = Math.max(...otherBoxes.map(b => b.spec.bottom)) + 1; + } + } + + saveBoxSpecs(): boxSpec[] { + this.layoutChanged.next(false); + + this._specList = this.boxes.map(b => b.spec); + return this._specList; + } + + getBoxById(id: string): ResizableBoxComponent { + return this.boxes.find(b => b.id == id); + } + + onResize(event) { + //event.target.innerWidth; + this.renderBoxes(); + } + + reset() { + this.layoutChanged.next(false); + this.renderBoxes(this.specList); + } +} diff --git a/APP/src/components/resizable-box/resizable-box.component.html b/APP/src/components/resizable-box/resizable-box.component.html new file mode 100644 index 0000000..3d09a1b --- /dev/null +++ b/APP/src/components/resizable-box/resizable-box.component.html @@ -0,0 +1,24 @@ +
+
+ + + + + + + + +
+ +
+
+
\ No newline at end of file diff --git a/APP/src/components/resizable-box/resizable-box.component.scss b/APP/src/components/resizable-box/resizable-box.component.scss new file mode 100644 index 0000000..a96ad00 --- /dev/null +++ b/APP/src/components/resizable-box/resizable-box.component.scss @@ -0,0 +1,59 @@ +.resizableBoxContainer { + left: 0px; + transform: translate(202px, 0px); + position: absolute; + z-index: 1; +} + +.full { + width: 100%; + //background: yellow; + overflow: auto; +} + +.dragHandle { + position: absolute; + background-color: cornflowerblue; + z-index: 2; + transform: translate(0, 0) !important; +} + +.dragHandle.corner { + right: 0; + bottom: 0; + width: 15px; + height: 15px; + cursor: nwse-resize; +} + +.dragHandle.right { + right: 0; + width: 2px; + height: 100%; + cursor: ew-resize; +} + +.dragHandle.bottom { + bottom: 0; + height: 2px; + width: 100%; + cursor: ns-resize; +} + +.dragHandle.move { + width: 15px; + height: 15px; + cursor: grab; + transform: translate(0, 0) !important; + right: 0; +} +.hidden { + display: none; +} + +.dragging { + opacity: 0.9; + box-shadow: rgba(0, 0, 0, 0.75) 0px 6px 14px -3px; + //transform: translateY(-0.2rem); + text-decoration: none; +} diff --git a/APP/src/components/resizable-box/resizable-box.component.spec.ts b/APP/src/components/resizable-box/resizable-box.component.spec.ts new file mode 100644 index 0000000..0c2feda --- /dev/null +++ b/APP/src/components/resizable-box/resizable-box.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; + +import { ResizableBoxComponent } from './resizable-box.component'; + +describe('ResizableBoxComponent', () => { + let component: ResizableBoxComponent; + let fixture: ComponentFixture; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [ ResizableBoxComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ResizableBoxComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/APP/src/components/resizable-box/resizable-box.component.ts b/APP/src/components/resizable-box/resizable-box.component.ts new file mode 100644 index 0000000..b9d8f5f --- /dev/null +++ b/APP/src/components/resizable-box/resizable-box.component.ts @@ -0,0 +1,456 @@ +import { Component, OnInit, ChangeDetectionStrategy, ViewChild, ElementRef, Input, Output, EventEmitter } from '@angular/core'; +import { CdkDragMove, CdkDragStart, CdkDragEnd } from '@angular/cdk/drag-drop'; +import { style, animate, AnimationBuilder } from '@angular/animations'; + +@Component({ + selector: 'rbj-resizable-box', + templateUrl: './resizable-box.component.html', + styleUrls: ['./resizable-box.component.scss'] +}) +export class ResizableBoxComponent implements OnInit { + + + @ViewChild('resizeBox', { static: true }) resizeBox: ElementRef; + @Input() + id: string; + @Input() + gridSizeY: number = 20; + @Input() + gridSizeX: number = 300; + @Input() + gridPadding: number = 10; + + @Input() + max_X: number = 1920; + @Input() + max_Y: number = 9999; + @Input() + min_X: number = 0; + @Input() + min_Y: number = 0; + + + @Input() + draggable: boolean = false; + @Input() + minWidth: number = 100; + @Input() + minHeight: number = 100; + + dragging: boolean = false; + + public get position(): position { + return { x: this.x, y: this.y } as position; + } + public set position(v: position) { + // this.x = v.x; + // this.y = v.y; + this.animationMove(v.x, v.y) + } + + + public get size(): size { + return { width: this.width, height: this.height } as size; + } + public set size(v: size) { + // this.width = v.width; + // this.height = v.height; + this.animationResize(v.width, v.height) + } + public get spec() { + return new boxSpec(this.id, this.x, this.y, this.width, this.height); + } + + + + @Output() draggingStart = new EventEmitter(); + @Output() draggingEnd = new EventEmitter(); + @Output() change = new EventEmitter(); + @Output() positionChange = new EventEmitter(); + @Output() sizeChange = new EventEmitter(); + @Output() animationMoveEnd = new EventEmitter(); + + private _x: number = 0; + @Input() + public get x(): number { + return this._x; + } + public set x(v: number) { + this._x = v; + } + + private _y: number = 0; + @Input() + public get y(): number { + return this._y; + } + public set y(v: number) { + this._y = v; + } + + + private _width: number = 0; + @Input() + public get width(): number { + return this._width; + } + public set width(v: number) { + this._width = v; + } + + + private _height: number = 0; + @Input() + public get height(): number { + return this._height; + } + public set height(v: number) { + this._height = v; + } + + + + displayX: number = 0; + displayY: number = 0; + displayWidth: number = 1; + displayHeight: number = 1; + + + + get resizeBoxElement(): HTMLElement { + return this.resizeBox.nativeElement; + } + + + constructor(private animationBuilder: AnimationBuilder,) { } + + ngOnInit() { + } + + ngAfterViewInit() { + } + + lastMoveX: number = 0; + lastMoveY: number = 0; + + dragStart($event: CdkDragStart) { + this.lastMoveX = 0; + this.lastMoveY = 0; + this.dragging = true; + this.draggingStart.emit(this.spec); + } + moveEnd($event: CdkDragEnd) { + this.dragging = false; + // let endX = Math.round(this.x / this.gridSizeX) * this.gridSizeX; + // let endY = Math.round(this.y / this.gridSizeY) * this.gridSizeY; + + // endX = endX < this.min_X ? this.min_X : endX; + // endX = endX > this.max_X ? this.max_X : endX; + + // endY = endY < this.min_Y ? this.min_Y : endY; + // endY = endY > this.max_Y ? this.max_Y : endY; + // this.animationMove(endX, endY); + + + + this.moveAndResize(this.spec); + this.draggingEnd.emit(this.spec); + } + resizeEnd($event: CdkDragEnd) { + this.dragging = false; + // let endWidth = Math.round(this.width / this.gridSizeX) * this.gridSizeX; + // let endHeight = Math.round(this.height / this.gridSizeY) * this.gridSizeY; + // this.animationResize(endWidth, endHeight); + + + this.moveAndResize(this.spec); + this.draggingEnd.emit(this.spec); + } + dragMove($event: CdkDragMove) { + // this.x += $event.distance.x - this.lastMoveX; + // this.y += $event.distance.y - this.lastMoveY; + this.displayX += $event.distance.x - this.lastMoveX; + this.displayY += $event.distance.y - this.lastMoveY; + this.lastMoveX = $event.distance.x; + this.lastMoveY = $event.distance.y; + + this.x = Math.round(this.displayX / this.gridSizeX); + this.y = Math.round(this.displayY / this.gridSizeY); + + if (this.x < 0) { + this.x = 0; + } + // if (this.y < 0) { + // this.y = 0; + // } + + if (this.spec.right > 12) { + this.x = 12 - this.width; + } + this.positionChange.emit(this.position); + this.change.emit(this.spec); + } + + dragResize($event: CdkDragMove) { + + this.displayWidth += $event.distance.x - this.lastMoveX; + this.displayWidth = this.displayWidth < this.minWidth ? this.minWidth : this.displayWidth; + this.displayHeight += $event.distance.y - this.lastMoveY; + this.displayHeight = this.displayHeight < this.minHeight ? this.minHeight : this.displayHeight; + + if (this.spec.right > this.max_X) { + this.displayWidth = (this.max_X - this.x); + } + + this.lastMoveX = $event.distance.x; + this.lastMoveY = $event.distance.y; + + + this.width = Math.round(this.displayWidth / this.gridSizeX); + this.height = Math.round(this.displayHeight / this.gridSizeY); + + if (this.spec.right > 12) { + this.width = 12 - this.x; + } + + if (this.width < 1) this.width = 1; + if (this.height < 1) this.height = 1; + + + this.sizeChange.emit(this.size); + this.change.emit(this.spec); + } + + animationInit(spec: boxSpec) { + + let x = spec.position.x; + let y = spec.position.y; + let w = spec.size.width; + let h = spec.size.height; + + + let newDisplayX = x * this.gridSizeX; + let newDisplayY = y * this.gridSizeY; + let newDisplayHeight = h * this.gridSizeY; + let newDisplayWidth = w * this.gridSizeX; + + const animation = this.animationBuilder.build([ + style({ + transform: `translate(${this.displayX}px,${this.displayY}px)`, + height: `${this.displayHeight}px`, + width: `${this.displayWidth}px` + }), + animate(300, style({ + transform: `translate(${newDisplayX}px,${newDisplayY}px)`, + height: `${newDisplayHeight}px`, + width: `${newDisplayWidth}px` + })) + ]); + + const player = animation.create(this.resizeBoxElement); + player.onDone(() => { + this.height = h; + this.width = w; + this.resizeBoxElement.style.height = `${newDisplayHeight}px`; + this.resizeBoxElement.style.width = `${newDisplayWidth}px`; + + this.x = x; + this.y = y; + + this.displayHeight = newDisplayHeight; + this.displayWidth = newDisplayWidth; + this.displayX = newDisplayX; + this.displayY = newDisplayY; + + this.resizeBoxElement.style.transform = `translate(${newDisplayX}px,${newDisplayY}px)`; + player.destroy(); + }) + player.play(); + } + animationMove(moveToX: number, moveToY: number, milesec: number = 300) { + //var newWidth = this.spec.right <= this.max_X ? this.width : (this.max_X - moveToX); + var newWidth = this.width; + if (this.spec.right > this.max_X) { + newWidth = (this.max_X - moveToX); + } + + if (moveToX < this.min_X) { + newWidth = this.width - (this.min_X - moveToX); + moveToX = 0 + } + + const animation = this.animationBuilder.build([ + style({ + transform: `translate(${this.x}px,${this.y}px)`, + width: this.width + 'px' + }), + animate(milesec, style({ + transform: `translate(${moveToX}px,${moveToY}px)`, + width: newWidth + 'px' + })) + ]); + + const player = animation.create(this.resizeBoxElement); + player.onDone(() => { + this.x = moveToX; + this.y = moveToY; + this.width = newWidth; + this.resizeBoxElement.style.transform = `translate(${moveToX}px,${moveToY}px)`; + player.destroy(); + this.animationMoveEnd.emit(); + }) + player.play(); + } + + animationResize(width: number, height: number, milesec: number = 300) { + const animation = this.animationBuilder.build([ + style({ + height: `${this.height}px`, + width: `${this.width}px` + }), + animate(milesec, style({ + height: `${height}px`, + width: `${width}px` + })) + ]); + + const player = animation.create(this.resizeBoxElement); + player.onDone(() => { + this.height = height; + this.width = width; + this.resizeBoxElement.style.height = `${height}px`; + this.resizeBoxElement.style.width = `${width}px`; + player.destroy(); + }) + player.play(); + } + + animationMoveAndResize(newSpec: boxSpec, milesec: number = 300) { + const animation = this.animationBuilder.build([ + style({ + transform: `translate(${this.x}px,${this.y}px)`, + height: `${this.height}px`, + width: `${this.width}px` + }), + animate(milesec, style({ + transform: `translate(${newSpec.position.x}px,${newSpec.position.y}px)`, + height: `${newSpec.size.height}px`, + width: `${newSpec.size.width}px` + })) + ]); + + const player = animation.create(this.resizeBoxElement); + player.onDone(() => { + this.height = newSpec.size.height; + this.width = newSpec.size.width; + this.resizeBoxElement.style.height = `${newSpec.size.height}px`; + this.resizeBoxElement.style.width = `${newSpec.size.width}px`; + + this.x = newSpec.position.x; + this.y = newSpec.position.y; + this.resizeBoxElement.style.transform = `translate(${newSpec.position.x}px,${newSpec.position.y}px)`; + player.destroy(); + }) + player.play(); + } + + moveAndResize(newSpec: boxSpec, milesec: number = 300) { + let x = newSpec.position.x; + let y = newSpec.position.y; + let w = newSpec.size.width; + let h = newSpec.size.height; + + + let newDisplayX = x * this.gridSizeX + (x * this.gridPadding); + let newDisplayY = y * this.gridSizeY; + let newDisplayHeight = h * this.gridSizeY; + let newDisplayWidth = w * this.gridSizeX + ((w - 1) * this.gridPadding); + + const animation = this.animationBuilder.build([ + style({ + transform: `translate(${this.displayX}px,${this.displayY}px)`, + height: `${this.displayHeight}px`, + width: `${this.displayWidth}px` + }), + animate(milesec, style({ + transform: `translate(${newDisplayX}px,${newDisplayY}px)`, + height: `${newDisplayHeight}px`, + width: `${newDisplayWidth}px` + })) + ]); + + const player = animation.create(this.resizeBoxElement); + player.onStart(() => { + + this.x = x; + this.y = y; + this.height = h; + this.width = w; + + this.displayX = newDisplayX; + this.displayY = newDisplayY; + this.displayHeight = newDisplayHeight; + this.displayWidth = newDisplayWidth; + }); + player.onDone(() => { + this.resizeBoxElement.style.height = `${newDisplayHeight}px`; + this.resizeBoxElement.style.width = `${newDisplayWidth}px`; + this.resizeBoxElement.style.transform = `translate(${newDisplayX}px,${newDisplayY}px)`; + player.destroy(); + }) + player.play(); + } + + +} + + + +export class boxSpec { + /** + * + */ + constructor(id, x, y, w, h) { + this.id = id; + this.position = { x: x, y: y } as position; + this.size = { height: h, width: w } as size; + } + id: string; + position: position; + size: size; + stack: number = 0; + public get left() { + return this.position.x; + } + public get right() { + return this.position.x + this.size.width; + } + + public get top() { + return this.position.y; + } + public get bottom() { + return this.position.y + this.size.height; + } + + diff(comparison: boxSpec) { + return this.position.x != comparison.position.x || this.position.y != comparison.position.y || this.size.height != comparison.size.height || this.size.width != comparison.size.width; + } + toString() { + return `left:${this.left},top:${this.top},right:${this.right},bottom:${this.bottom}`; + } + copy(): boxSpec { + return new boxSpec(this.id, this.position.x, this.position.y, this.size.width, this.size.height); + } +} +export class position { + x: number; + y: number; +} +export class size { + width: number; + height: number; + largerThan(comparison: size) { + return this.height > comparison.height && this.width > comparison.width; + } +} \ No newline at end of file diff --git a/APP/src/components/resizable-box/resizeable-box.module.ts b/APP/src/components/resizable-box/resizeable-box.module.ts new file mode 100644 index 0000000..e90c692 --- /dev/null +++ b/APP/src/components/resizable-box/resizeable-box.module.ts @@ -0,0 +1,18 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ResizableBoxComponent } from './resizable-box.component'; +import { DragDropModule } from '@angular/cdk/drag-drop'; +import { ResizableBoxContainerComponent } from './resizable-box-container/resizable-box-container.component'; + +@NgModule({ + declarations: [ResizableBoxComponent, ResizableBoxContainerComponent], + imports: [ + CommonModule, + DragDropModule, + ], + exports: [ + ResizableBoxComponent, + ResizableBoxContainerComponent + ] +}) +export class ResizeableBoxModule { } diff --git a/APP/src/components/screen-footer/screen-footer.component.html b/APP/src/components/screen-footer/screen-footer.component.html new file mode 100644 index 0000000..c88f6f1 --- /dev/null +++ b/APP/src/components/screen-footer/screen-footer.component.html @@ -0,0 +1,39 @@ + + +
+
+ + + + +
+ +
+ + + +
+
+
+
\ No newline at end of file diff --git a/APP/src/components/screen-footer/screen-footer.component.scss b/APP/src/components/screen-footer/screen-footer.component.scss new file mode 100644 index 0000000..190fed5 --- /dev/null +++ b/APP/src/components/screen-footer/screen-footer.component.scss @@ -0,0 +1,53 @@ +nb-icon { + // margin-top: -2px !important; + // margin-bottom: -2px !important; + margin: -2px -6px !important; +} + +.rbj-screen-footer { + margin-bottom: 1rem; + padding: 0.5rem 0; + + > nb-card-body { + padding: 0 1rem; + } +} +nb-card { + transition-property: all; + transition-timing-function: ease; + transition-delay: 0; + transition-duration: 0.3s; +} +.screenTitle { + /* Mobile */ + @media screen and (max-width: 729px) { + &.full, + &.medium { + display: none; + } + &.tiny { + display: block; + } + } + /* Tablet*/ + @media screen and (min-width: 730px) and (max-width: 1400px) { + &.medium { + display: block; + } + &.full, + &.tiny { + display: none; + } + } + + /* Desktop*/ + @media screen and (min-width: 1401px) { + &.full { + display: block; + } + &.medium, + &.tiny { + display: none; + } + } +} diff --git a/APP/src/components/screen-footer/screen-footer.component.spec.ts b/APP/src/components/screen-footer/screen-footer.component.spec.ts new file mode 100644 index 0000000..8cf26f0 --- /dev/null +++ b/APP/src/components/screen-footer/screen-footer.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; + +import { ScreenFooterComponent } from './screen-footer.component'; + +describe('ScreenFooterComponent', () => { + let component: ScreenFooterComponent; + let fixture: ComponentFixture; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [ ScreenFooterComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ScreenFooterComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/APP/src/components/screen-footer/screen-footer.component.ts b/APP/src/components/screen-footer/screen-footer.component.ts new file mode 100644 index 0000000..3c7ec19 --- /dev/null +++ b/APP/src/components/screen-footer/screen-footer.component.ts @@ -0,0 +1,122 @@ +import { Component, Input, Output, EventEmitter, ViewChildren, QueryList } from '@angular/core'; +import { StateService } from '../../services/state.service'; +import { ActivatedRoute } from '@angular/router'; +import { take, first, takeUntil } from 'rxjs/operators'; +import { NbDialogService, NbTooltipDirective } from '@nebular/theme'; +import { emit } from 'cluster'; +import { MsgBoxService } from '../../services/msg-box.service'; +import { RbjDialogService } from '../../services/rbj-dialog.service'; +import { formatCurrency } from '@angular/common'; +import { Subject } from 'rxjs'; +@Component({ + selector: 'rbj-screen-footer', + templateUrl: './screen-footer.component.html', + styleUrls: ['./screen-footer.component.scss'] +}) +export class ScreenFooterComponent { + + @ViewChildren(NbTooltipDirective) tooltips: QueryList; + private _disableSave: boolean; + + page: number; + pages: number[] = []; + maxPage: number = Number.POSITIVE_INFINITY; // default no limit + minPage: number = 1; + toolTips: string[] = []; + private destroy$: Subject = new Subject(); + + @Output() focusFirstInvalid = new EventEmitter(); + + public get disableSave(): boolean { + return this._disableSave; + } + @Input() + public set disableSave(v: boolean) { + if (this._disableSave != v) { + + this._disableSave = v; + if (true === this._disableSave && this.tooltips) { + this.tooltips.forEach(element => { element.hide(); }); + } + this.stateService.dataChanged = !v; + } + } + + @Input() isPagedScreen: boolean + + + public get waitingForScreenLock(): boolean { + return this.stateService.waitingForScreenLock; + } + + + constructor( + private stateService: StateService, + private route: ActivatedRoute, + private dialogService: RbjDialogService, + private msgBoxService: MsgBoxService + ) { + this.stateService.setPagesSubject.pipe(takeUntil(this.destroy$)).subscribe({ next: (pages) => this.setupPages(pages) }); + this.stateService.setToolTipsSubject.pipe(takeUntil(this.destroy$)).subscribe({ next: (toolTips) => this.setupToolTips(toolTips) }); + this.stateService.setPageMaxSubject.pipe(takeUntil(this.destroy$)).subscribe({ next: (maxPage) => this.maxPage = maxPage }); + this.stateService.setPageMinSubject.pipe(takeUntil(this.destroy$)).subscribe({ next: (minPage) => this.minPage = minPage }); + this.stateService.setPageDataSubject.pipe(takeUntil(this.destroy$)).subscribe({ next: (page) => this.page = page }); + + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + setupPages(pages: number[]) { + this.page = 1; + this.route.queryParamMap + .pipe(take(1)) + .subscribe(paramMap => { + if (paramMap.has('page')) { + this.page = parseInt(paramMap.get('page')); + } + }); + this.pages = pages; + } + + setupToolTips(toolTips: string[]) { + this.toolTips = toolTips; + } + + gotoPage(page: number) { + + this.page = page; + this.stateService.gotoPage(this.page); + } + + nextScreen() { + if (false == this.stateService.checkInvalidFields()) { + //this.stateService.nextScreen(); + } + } + + prevScreen() { + if (false == this.stateService.checkInvalidFields()) { + //this.stateService.prevScreen(); + } + } + + createNewPage(page: number) { + //this.stateService.createPage(page); + } + + save() { + if (false == this.stateService.checkInvalidFields() && false == (this.disableSave || this.disableSave)) { + this.stateService.save(); + } + } + + cancel() { + + + } + reload() { + this.stateService.cancel(); + } +} diff --git a/APP/src/components/screen-footer/screen-footer.module.ts b/APP/src/components/screen-footer/screen-footer.module.ts new file mode 100644 index 0000000..ee6f181 --- /dev/null +++ b/APP/src/components/screen-footer/screen-footer.module.ts @@ -0,0 +1,31 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ScreenFooterComponent } from './screen-footer.component'; +import { NbIconModule, NbTooltipModule, NbCardModule, NbButtonModule } from '@nebular/theme'; +import { PagerModule } from '../pager/pager.module'; +import { AlertDlgModule } from '../alert-dlg/alert-dlg.module'; + +const routedComponents = [ + ScreenFooterComponent, +]; + +@NgModule({ + declarations: [ + ...routedComponents, + ], + imports: [ + CommonModule, + + NbButtonModule, + NbCardModule, + NbIconModule, + NbTooltipModule, + + AlertDlgModule, + PagerModule, + ], + exports: [ + ...routedComponents, + ], +}) +export class ScreenFooterModule { } diff --git a/APP/src/components/tin-input/tin-input.component.html b/APP/src/components/tin-input/tin-input.component.html new file mode 100644 index 0000000..2f091b2 --- /dev/null +++ b/APP/src/components/tin-input/tin-input.component.html @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/APP/src/components/tin-input/tin-input.component.scss b/APP/src/components/tin-input/tin-input.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/APP/src/components/tin-input/tin-input.component.spec.ts b/APP/src/components/tin-input/tin-input.component.spec.ts new file mode 100644 index 0000000..f423bd6 --- /dev/null +++ b/APP/src/components/tin-input/tin-input.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; + +import { TinInputComponent } from './tin-input.component'; + +describe('TinInputComponent', () => { + let component: TinInputComponent; + let fixture: ComponentFixture; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [ TinInputComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(TinInputComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/APP/src/components/tin-input/tin-input.component.ts b/APP/src/components/tin-input/tin-input.component.ts new file mode 100644 index 0000000..95e5dea --- /dev/null +++ b/APP/src/components/tin-input/tin-input.component.ts @@ -0,0 +1,134 @@ +import { Component, OnInit, ChangeDetectionStrategy, forwardRef, ViewChild, ElementRef, Input, Output, EventEmitter, Renderer2 } from '@angular/core'; +import { NG_VALUE_ACCESSOR, ControlContainer, NgForm } from '@angular/forms'; +import { ApplyMaskService } from '../../services/apply-mask.service'; +import { UuidUtils } from '../../utilities/uuid-utils'; + +const LAST_4_NO_INDEX = 6; +const TIN_MASK = '00-0000000'; +const ASTERISK_MASK = '**-***'; +@Component({ + selector: 'rbj-tin-input', + templateUrl: './tin-input.component.html', + styleUrls: ['./tin-input.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => TinInputComponent), + multi: true + } + ], + viewProviders: [{ provide: ControlContainer, useExisting: NgForm }] +}) +export class TinInputComponent implements OnInit { + + @ViewChild('inputBox', { static: true }) input: ElementRef; + private _value: string; + private _initialized: boolean; + private _readOnly: boolean; + disabledState: boolean = false; + outputWithSymbol: boolean = true; + uuid: string = UuidUtils.generate(); + name: string; + inputMask = "" + @Input() id?: string = '' + @Input() placeholder: string; + @Input() inputClass: string; + @Input() size: string = 'medium'; + + @Input() + public set readonly(value) { + this._readOnly = typeof value !== 'undefined' && value !== false; + } + + public get readonly(): boolean { + return this._readOnly; + } + + + private _text: string; + public get text(): string { + return this._text; + } + public set text(v: string) { + + if (this._text != v) { + this._text = v; + } + + } + + + public get value(): string { + return this._value; + } + public set value(v: string) { + + if (this._initialized) { + v = this.outputWithSymbol ? v : v.replace(/[^\d]/g, ''); + if (v != this._value) { + + if (v == " -") v = ''; + this._value = v; + this.onChange(this.value); + } + } else { + this._initialized = true; + this._value = v; + } + } + + @Output() inputChange = new EventEmitter(); + @Output() focus = new EventEmitter(); + + onChange = (value: string) => { }; + onTouched = () => { }; + + onFocus() { + this.inputMask = TIN_MASK; + this.text = this.value; + } + onFocusout() { + this.value = this.text; + this.setAsteriskMask(); + } + constructor( + private renderer: Renderer2, + private el: ElementRef,) { } + + ngAfterViewInit() { + this.renderer.removeAttribute(this.el.nativeElement, 'id') + } + + //#region Implements + writeValue(value: string): void { + + if (value) { + this.value = this.outputWithSymbol ? value : value.replace(/[^\d]/g, ''); + this.setAsteriskMask(); + } else { + this.value = null; + this.text = ''; + } + } + registerOnChange(fn: (value: string) => void): void { + this.onChange = fn; + } + registerOnTouched(fn: any): void { + this.onTouched = fn; + } + setDisabledState?(isDisabled: boolean): void { + this.disabledState = isDisabled; + } + + private setAsteriskMask() { + this.inputMask = ""; + this.text = ''; + if (this.value && this.value.length > LAST_4_NO_INDEX) { + this.text = ASTERISK_MASK + this.value.substr(LAST_4_NO_INDEX); + } + } + //#endregion + ngOnInit() { + } + +} \ No newline at end of file diff --git a/APP/src/components/tin-input/tin-input.module.ts b/APP/src/components/tin-input/tin-input.module.ts new file mode 100644 index 0000000..2329810 --- /dev/null +++ b/APP/src/components/tin-input/tin-input.module.ts @@ -0,0 +1,18 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { TinInputComponent } from './tin-input.component'; +import { FormsModule } from '@angular/forms'; +import { NbInputModule } from '@nebular/theme'; +import { MaskDirectiveModule } from '../../directives/mask-directive/mask-directive.module'; + +@NgModule({ + declarations: [TinInputComponent], + imports: [ + CommonModule, + FormsModule, + NbInputModule, + MaskDirectiveModule + ], + exports: [TinInputComponent] +}) +export class TinInputModule { } diff --git a/APP/src/directives/focus-navigator/focus-navigator.directive.spec.ts b/APP/src/directives/focus-navigator/focus-navigator.directive.spec.ts new file mode 100644 index 0000000..b0b5040 --- /dev/null +++ b/APP/src/directives/focus-navigator/focus-navigator.directive.spec.ts @@ -0,0 +1,8 @@ +import { FocusNavigatorDirective } from './focus-navigator.directive'; + +describe('FocusNavigatorDirective', () => { + it('should create an instance', () => { + const directive = new FocusNavigatorDirective(); + expect(directive).toBeTruthy(); + }); +}); diff --git a/APP/src/directives/focus-navigator/focus-navigator.directive.ts b/APP/src/directives/focus-navigator/focus-navigator.directive.ts new file mode 100644 index 0000000..2e22a4a --- /dev/null +++ b/APP/src/directives/focus-navigator/focus-navigator.directive.ts @@ -0,0 +1,84 @@ +import { Directive, ElementRef, EventEmitter, HostListener, Input, Output, Renderer2 } from '@angular/core'; + +@Directive({ + selector: '[focusNav]' +}) +export class FocusNavigatorDirective { + + + focusTag: string = 'focusNav'; + @Input("focusNav") + public set input_focusTag(value) { + this.focusTag = 'focusNav_' + value; + + } + @Output() focusNavHitLast = new EventEmitter(); + + + constructor(private elementRef: ElementRef, private renderer: Renderer2 + ) { } + ngOnInit() { + this.renderer.addClass(this.elementRef.nativeElement, this.focusTag); + } + @HostListener('keydown', ['$event']) keyHandler(event: KeyboardEvent) { + if (event.key === 'ArrowDown' || event.key === 'ArrowUp') { + if (!event.repeat && false === (event.target as HTMLInputElement).disabled) { + //(event.target as HTMLInputElement).disabled = true; + //var inputList = document.getElementsByClassName(this.focusTag);//'d-none' + //var inputList = document.querySelectorAll(`.${this.focusTag}:not(.d-none)`); + var inputList = Array.from( + document.querySelectorAll(`.${this.focusTag}`) + ).filter(el => !el.closest('.d-none')); + let index = 0; + for (let i = 0; i < inputList.length; i++) { + const element = inputList[i]; + if (element == this.elementRef.nativeElement) { + index = i; + break; + } + } + + do { + + index += event.key === 'ArrowDown' ? 1 : -1 + index = index == -1 ? 0 : index; //== inputList.length ? 0 : index; + + if (index == inputList.length) { + this.focusNavHitLast.next(inputList.length - 1); + break; + } + + + var inputElement = this.getFirstHtmlInputElement(inputList[index]); + if (!inputElement.readOnly) inputElement.focus(); + + + } while (inputElement.readOnly); + event.preventDefault(); + } + } + } + + private getFirstHtmlInputElement(target: Element): HTMLInputElement { + if (target instanceof HTMLInputElement) { + return (target as HTMLInputElement); + } else { + return this.getFirstHtmlInputElement(target.children[0]); + } + } + + + // private setCursorPosition(position) { + // let input = this.input.nativeElement; + // if (input.setSelectionRange) { + // input.focus(); + // input.setSelectionRange(position, position); + // } else if (input.createTextRange) { + // var range = input.createTextRange(); + // range.collapse(true); + // range.moveEnd('character', position); + // range.moveStart('character', position); + // range.select(); + // } + // } +} diff --git a/APP/src/directives/focus-navigator/focus-navigator.module.ts b/APP/src/directives/focus-navigator/focus-navigator.module.ts new file mode 100644 index 0000000..59c82c9 --- /dev/null +++ b/APP/src/directives/focus-navigator/focus-navigator.module.ts @@ -0,0 +1,12 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FocusNavigatorDirective } from './focus-navigator.directive'; + +@NgModule({ + declarations: [FocusNavigatorDirective], + imports: [ + CommonModule + ], + exports: [FocusNavigatorDirective] +}) +export class FocusNavigatorModule { } diff --git a/APP/src/directives/force-focus-msg/force-focus-msg.directive.spec.ts b/APP/src/directives/force-focus-msg/force-focus-msg.directive.spec.ts new file mode 100644 index 0000000..b5e1b75 --- /dev/null +++ b/APP/src/directives/force-focus-msg/force-focus-msg.directive.spec.ts @@ -0,0 +1,8 @@ +import { ForceFocusMsgDirective } from './force-focus-msg.directive'; + +describe('ForceFocusMsgDirective', () => { + it('should create an instance', () => { + const directive = new ForceFocusMsgDirective(); + expect(directive).toBeTruthy(); + }); +}); diff --git a/APP/src/directives/force-focus-msg/force-focus-msg.directive.ts b/APP/src/directives/force-focus-msg/force-focus-msg.directive.ts new file mode 100644 index 0000000..46e9ff3 --- /dev/null +++ b/APP/src/directives/force-focus-msg/force-focus-msg.directive.ts @@ -0,0 +1,152 @@ +import { Directive, ElementRef, ChangeDetectorRef, Input, HostListener, forwardRef } from '@angular/core'; +import { AbstractControl, NG_VALIDATORS, ValidationErrors, Validator } from '@angular/forms'; +import { NbDynamicOverlay, NbDynamicOverlayHandler, NbPosition, NbTrigger, NbAdjustment } from '@nebular/theme'; +import { PopoverMsgComponent } from '../../components/popover-msg/popover-msg.component'; + +@Directive({ + selector: 'input[ffMsg], textarea[ffMsg]', + providers: [ + NbDynamicOverlayHandler, + NbDynamicOverlay, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => ForceFocusMsgDirective), + multi: true, + }, + ], +}) +export class ForceFocusMsgDirective implements Validator { + + private dynamicOverlay: NbDynamicOverlay; + constructor( + private elementRef: ElementRef, + private dynamicOverlayHandler: NbDynamicOverlayHandler, + private cdRef: ChangeDetectorRef) { } + + + + private _invalidMsg: string = 'Invalid format'; + private _required: boolean = false; + public get invalidMsg(): string { + return this._invalidMsg; + } + @Input('ffMsg') + public set invalidMsg(v: string) { + if (this._invalidMsg != v) { + this._invalidMsg = v; + this.hide(); + if (this.dynamicOverlay) this.dynamicOverlay = this.configureDynamicOverlay().rebuild(); + } + } + + @Input('ffMsgPosition') public msgPosition: NbPosition = NbPosition.BOTTOM_END; + + @Input() + public set required(value) { + this._required = typeof value !== 'undefined' && value !== false; + } + + + private _invalid: boolean = false; + public get invalid(): boolean { + return this._invalid; + } + @Input() public set invalid(v: boolean) { + if (this._invalid != v) { + this._invalid = v; + } + } + + disableForceFocus: boolean = false; + @Input("disableForceFocus") + public set input_disableForceFocus(value) { + this.disableForceFocus = typeof value !== "undefined" && value !== false; + } + ngOnInit() { + this.dynamicOverlayHandler + .host(this.elementRef) + .componentType(PopoverMsgComponent); + + } + + ngAfterViewInit() { + this.dynamicOverlay = this.configureDynamicOverlay().build(); + } + ngOnChange() { + //this.dynamicOverlay = this.configureDynamicOverlay().rebuild(); + + } + ngOnDestroy() { + this.dynamicOverlayHandler.destroy(); + } + + + validate(control: AbstractControl): ValidationErrors { + if (this.elementRef.nativeElement && this.elementRef.nativeElement.form + && this.elementRef.nativeElement.form.className.indexOf('ng-untouched') == -1) { + if (control.value != undefined && this._required && this.elementRef.nativeElement.value.length == 0) { + + this.dynamicOverlay = this.configureDynamicOverlay('Please fill out this field.').rebuild(); + this.invalid = true; + this.show(); + return { + input: { + forceMsg: "Value Required Field.", + }, + }; + } + } + this.invalid = false; + + } + registerOnValidatorChange?(fn: () => void): void { + } + + @HostListener('focusout') + onFocusOut() { + setTimeout(() => { + if (this.invalid) { + if (!this.disableForceFocus) { + this.elementRef.nativeElement.focus(); + } + } else { + this.hide(); + } + }, 200); + } + + show() { + + + if (!this.dynamicOverlay) { + this.dynamicOverlay = this.configureDynamicOverlay().rebuild(); + } + + //this.invalid = true; + if (this.dynamicOverlay) { + this.dynamicOverlay.show(); + this.cdRef.detectChanges(); + this.elementRef.nativeElement.focus(); + } + } + + hide() { + //this.invalid = false; + if (this.dynamicOverlay) { + this.dynamicOverlay.hide(); + this.cdRef.detectChanges(); + } + } + protected configureDynamicOverlay(msg: string = null) { + return this.dynamicOverlayHandler + .position(this.msgPosition) + .trigger(NbTrigger.NOOP) + .adjustment(NbAdjustment.CLOCKWISE) + .offset(0) + //.content(this.msgContext) + .context({ + position: this.msgPosition, + message: msg ? msg : this.invalidMsg + }); + } +} diff --git a/APP/src/directives/force-focus-msg/force-focus-msg.module.ts b/APP/src/directives/force-focus-msg/force-focus-msg.module.ts new file mode 100644 index 0000000..52b2e03 --- /dev/null +++ b/APP/src/directives/force-focus-msg/force-focus-msg.module.ts @@ -0,0 +1,14 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ForceFocusMsgDirective } from './force-focus-msg.directive'; +import { PopoverMsgModule } from '../../components/popover-msg/popover-msg.module'; + +@NgModule({ + declarations: [ForceFocusMsgDirective], + imports: [ + CommonModule, + PopoverMsgModule + ], + exports: [ForceFocusMsgDirective] +}) +export class ForceFocusMsgModule { } diff --git a/APP/src/directives/init-focus/init-focus.directive.ts b/APP/src/directives/init-focus/init-focus.directive.ts new file mode 100644 index 0000000..cb341c3 --- /dev/null +++ b/APP/src/directives/init-focus/init-focus.directive.ts @@ -0,0 +1,52 @@ +import { AfterContentInit, Directive, ElementRef, Input } from '@angular/core'; +import { StateService } from '../../services/state.service'; +import { DebounceTimer } from '../../utilities/timer-utils'; + +@Directive({ + selector: '[initFocus]' +}) +export class InitFocusDirective implements AfterContentInit { + + @Input('initFocus') enable: boolean = true; + + private debounceTimer = new DebounceTimer(100, () => { this.focusOnInitInput(); }) + private failedCount = 0; + + public constructor(private el: ElementRef, + private stateService: StateService + ) { } + + public ngAfterContentInit() { + this.debounceTimer.resetTimer(); + } + + private focusOnInitInput() { + if (this.stateService.waitingForScreenLock) return; + if (false === this.enable) { return; } + if (this.failedCount < 5) { + + if (!this.el.nativeElement) { + this.debounceTimer.resetTimer(); + } + else { + if (["INPUT", "TEXTAREA", "BUTTON"].indexOf(this.el.nativeElement.tagName) > -1) { + this.el.nativeElement.focus(); + } + else { + const firstInput = this.el.nativeElement.querySelector('input:not([type="checkbox"]), textarea') + if (firstInput) { + firstInput.focus(); + } + else { + this.debounceTimer.resetTimer(); + } + } + } + + this.failedCount++; + } + else { + + } + } +} diff --git a/APP/src/directives/init-focus/init-focus.module.ts b/APP/src/directives/init-focus/init-focus.module.ts new file mode 100644 index 0000000..a772386 --- /dev/null +++ b/APP/src/directives/init-focus/init-focus.module.ts @@ -0,0 +1,17 @@ + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { InitFocusDirective } from './init-focus.directive'; + + + +@NgModule({ + declarations: [InitFocusDirective], + imports: [ + CommonModule + ], + exports: [ + InitFocusDirective, + ], +}) +export class InitFocusModule { } diff --git a/APP/src/directives/mask-directive/mask-directive.module.ts b/APP/src/directives/mask-directive/mask-directive.module.ts new file mode 100644 index 0000000..6d02ee2 --- /dev/null +++ b/APP/src/directives/mask-directive/mask-directive.module.ts @@ -0,0 +1,14 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { MaskDirective } from './mask.directive'; +import { PopoverMsgModule } from '../../components/popover-msg/popover-msg.module'; + +@NgModule({ + declarations: [MaskDirective], + imports: [ + CommonModule, + PopoverMsgModule + ], + exports: [MaskDirective] +}) +export class MaskDirectiveModule { } diff --git a/APP/src/directives/mask-directive/mask.directive.ts b/APP/src/directives/mask-directive/mask.directive.ts new file mode 100644 index 0000000..d88432b --- /dev/null +++ b/APP/src/directives/mask-directive/mask.directive.ts @@ -0,0 +1,266 @@ + +import { Directive, forwardRef, HostListener, Input, OnChanges, SimpleChanges, ElementRef, Renderer2, ChangeDetectorRef, Output, EventEmitter } from '@angular/core'; +import { + ControlValueAccessor, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + ValidationErrors, + Validator, + AbstractControl, +} from '@angular/forms'; +import { ApplyMaskService, MaskSymbol } from '../../services/apply-mask.service'; +import { RbjTooltipService } from '../../services/rbj-tooltip.service'; +import { TextareaUtils } from '../../utilities/textarea-utils'; +import { UuidUtils } from '../../utilities/uuid-utils'; + +@Directive({ + selector: 'input[mask], textarea[mask]', + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => MaskDirective), + multi: true, + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => MaskDirective), + multi: true, + }, + ApplyMaskService, + ], +}) +export class MaskDirective implements ControlValueAccessor, OnChanges, Validator { + + private _maskExpression: string = ''; + private _maskSymbols: MaskSymbol[]; + public get maskExpression(): string { + return this._maskExpression; + } + @Input('mask') + public set maskExpression(v: string) { + if (this._maskExpression != v) { + this._maskExpression = v; + + this._maskSymbols = this.applyMaskService.getMaskSymbols(this.maskExpression); + if (this._maskSymbols.length) { + this.formElementProperty = ['maxLength', this._maskSymbols.length.toString()]; + }; + } + } + + @Input('multiMask') public maskExpressions: string[]; + @Input('invalidMsg') public invalidMsg: string = 'Invalid format'; + @Input() public allowedPrefix: string = ''; + + @Output() maskApplied = new EventEmitter(); + public constructor( + private applyMaskService: ApplyMaskService, + private elementRef: ElementRef, + private renderer: Renderer2, + private cdRef: ChangeDetectorRef, + private rbjTooltipService: RbjTooltipService + ) { } + + + private inputValue: string = ''; + private invalid: boolean = false; + private _required: boolean = false; + private _msgShown: boolean = false; + private uuid: string; + public writingValue: boolean = false; + public onChange = (_: any) => { }; + public onTouch = () => { }; + + @Input() + public set required(value) { + this._required = typeof value !== 'undefined' && value !== false; + } + + ngOnInit() { + this.uuid = UuidUtils.generate(); + } + + ngOnDestroy() { + this.rbjTooltipService.destroy(this.uuid); + } + + validate(control: AbstractControl): ValidationErrors { + + if (!this.maskExpression) { + return null; + } + + if (control.value != undefined && this._required && this.displayValue.length == 0) { + + this.invalid = true; + //if it's empty red border is good enough for notice people + //this.show(); + return this.createValidationError(this.displayValue) + } + + if (this.displayValue && this.displayValue.length > 0 && + !this.applyMaskService.validateWithSymbols(this.displayValue, this._maskSymbols)) { + this.invalid = true; + + return this.createValidationError(this.displayValue) + } + + this.invalid = false; + this.hide(); + return null; + } + + registerOnValidatorChange?(fn: () => void): void { + } + + writeValue(obj: any): void { + this.inputValue = obj; + this.displayValue = this.inputValue || ''; + this._lastEmitValue = this.displayValue; + } + registerOnChange(fn: any): void { + this.onChange = fn; + } + registerOnTouched(fn: any): void { + this.onTouch = fn; + } + setDisabledState?(isDisabled: boolean): void { + this.formElementProperty = ['disabled', isDisabled]; + + } + + + public get displayValue(): string { + return this.elementRef.nativeElement.value; + } + public set displayValue(v: string) { + this.formElementProperty = ['value', v]; + } + + + public ngOnChanges(changes: SimpleChanges): void { + this._applyMask(); + } + + show() { + + let formHasBeenTouched = this.elementRef.nativeElement.form && this.elementRef.nativeElement.form.className.indexOf('ng-touched') == -1; + if (false == formHasBeenTouched) { + this._msgShown = true; + this.rbjTooltipService.show(this.elementRef, this.invalidMsg, this.uuid, 0); + } + } + + hide() { + //this.isOpen = false; + if (this._msgShown) { + this._msgShown = false; + this.rbjTooltipService.hide(this.uuid); + } + } + + @HostListener('blur') + public onBlur(): void { + + this.onTouch(); + } + private _isReplacing = false; + private _selectionStart = -1; + private _lastEmitValue: string; + + @HostListener('keydown', ['$event']) + public onKeyDown(e: KeyboardEvent): void { + if (null == this.maskExpression) return; + if (!e.ctrlKey) { + const el: HTMLInputElement = e.target as HTMLInputElement; + this._isReplacing = el.selectionStart != el.selectionEnd; + this._selectionStart = el.selectionStart; + + if (this.maskExpression && (el.value.length + 1) == this._maskSymbols.length) { + this._isReplacing = true; + } + } + } + + @HostListener('keyup', ['$event']) + public onKeyUp(e: KeyboardEvent): void { + const el: HTMLInputElement = e.target as HTMLInputElement; + this.inputValue = el.value; + + if (this.maskExpression) { + let regMatch = e.key.match(/.{1}/g); + if (TextareaUtils.isEditingKeyPress(e)) { + + let focusLocation = this._isReplacing ? this._selectionStart : el.selectionStart; + this.writingValue = true; + this.inputValue = this.applyMaskService.applyMaskWithSymbols(this.inputValue, this._maskSymbols, + { + inputKey: this._isReplacing ? '' : e.key, + inputPosition: focusLocation + }); + this.writingValue = false; + this.elementRef.nativeElement.value = this.inputValue; + this.setCursorPosition(this.applyMaskService.focusIndex); + this.maskApplied.next(this.inputValue); + + } + this._isReplacing = false; + } + + this.formControlResult(this.inputValue); + } + + + + @HostListener('focusout') + onFocusOut() { + if (this.invalid) { + this.show(); + } + } + setCursorPosition(position) { + let input = this.elementRef.nativeElement; + if (input.setSelectionRange) { + input.focus(); + input.setSelectionRange(position, position); + } else if (input.createTextRange) { + var range = input.createTextRange(); + range.collapse(true); + range.moveEnd('character', position); + range.moveStart('character', position); + range.select(); + } + } + + + + + + private _applyMask(): any { + this.formElementProperty = [ + 'value', + this.applyMaskService.applyMaskWithSymbols(this.displayValue, this._maskSymbols), + ]; + } + + private set formElementProperty([name, value]: [string, string | boolean]) { + + this.renderer.setProperty(this.elementRef.nativeElement, name, value); + } + + private formControlResult(inputValue: string): void { + if (!this.writingValue && + this._lastEmitValue != this.displayValue) { + this._lastEmitValue = this.displayValue; + this.onChange(inputValue); + } + } + private createValidationError(actualValue: string): ValidationErrors { + return { + mask: { + requiredMask: this.maskExpression, + actualValue, + }, + }; + } +} diff --git a/APP/src/directives/rbj-tooltip/input-limitation.directive.ts b/APP/src/directives/rbj-tooltip/input-limitation.directive.ts new file mode 100644 index 0000000..b23be3e --- /dev/null +++ b/APP/src/directives/rbj-tooltip/input-limitation.directive.ts @@ -0,0 +1,83 @@ +import { ChangeDetectorRef, Directive, ElementRef, HostListener, Input, Renderer2 } from '@angular/core'; +import { NbAdjustment, NbDynamicOverlay, NbDynamicOverlayHandler, NbPosition, NbTrigger } from '@nebular/theme'; +import { first } from 'rxjs/operators'; +import { RbjTooltipService } from '../../services/rbj-tooltip.service'; +import { TextareaUtils } from '../../utilities/textarea-utils'; +import { DebounceTimer } from '../../utilities/timer-utils'; +import { UuidUtils } from '../../utilities/uuid-utils'; +import { PopoverMsgComponent } from './popover-msg/popover-msg.component'; + +@Directive({ + selector: '[inputLimitation]', + providers: [ + NbDynamicOverlayHandler, + NbDynamicOverlay + ], +}) +export class InputLimitationDirective { + @Input('inputLimitation') inputLimitation: number = null; + private debounceTimer = new DebounceTimer(100, () => { this.setInputLimitForElement(); }) + uuid: string; + + public get message(): string { + return `The input limit of this field is ${this.inputLimitation} characters` + } + + + constructor( + private elementRef: ElementRef, + private renderer: Renderer2, + private rbjTooltipService: RbjTooltipService + ) { } + + @HostListener('keydown', ['$event']) + public onKeyDown(e: KeyboardEvent): void { + if (this.inputLimitation) { + const el: HTMLInputElement = e.target as HTMLInputElement; + let isReplacing = el.selectionStart != el.selectionEnd; + + if (false == isReplacing && el.value.length >= this.inputLimitation && TextareaUtils.isEditingKeyPress(e)) { + this.showLimitation(); + } + } + } + + showLimitation() { + this.rbjTooltipService.show(this.elementRef, this.message, this.uuid); + + } + ngOnInit() { + this.uuid = UuidUtils.generate(); + } + + ngAfterViewInit() { + this.setInputLimitForElement(); + } + ngOnDestroy() { + this.rbjTooltipService.destroy(this.uuid); + } + private failedCount: number = 0; + private setInputLimitForElement() { + if (this.inputLimitation > 0 && this.failedCount < 5) { + if (!this.elementRef.nativeElement) { + this.debounceTimer.resetTimer(); + } + else { + if (["INPUT", "TEXTAREA"].indexOf(this.elementRef.nativeElement.tagName) > -1) { + this.elementRef.nativeElement.setAttribute('maxlength', this.inputLimitation.toString()); + } + else { + const firstInput = this.elementRef.nativeElement.querySelector('input:not([type="checkbox"]), textarea') + if (firstInput) { + firstInput.setAttribute('maxlength', this.inputLimitation.toString()); + } + else { + this.debounceTimer.resetTimer(); + } + } + } + + this.failedCount++; + } + } +} diff --git a/APP/src/directives/rbj-tooltip/popover-msg/popover-msg.component.html b/APP/src/directives/rbj-tooltip/popover-msg/popover-msg.component.html new file mode 100644 index 0000000..94c23c2 --- /dev/null +++ b/APP/src/directives/rbj-tooltip/popover-msg/popover-msg.component.html @@ -0,0 +1,10 @@ +
+ +
+
+ ! + +
+
+ +
\ No newline at end of file diff --git a/APP/src/directives/rbj-tooltip/popover-msg/popover-msg.component.scss b/APP/src/directives/rbj-tooltip/popover-msg/popover-msg.component.scss new file mode 100644 index 0000000..96db2b6 --- /dev/null +++ b/APP/src/directives/rbj-tooltip/popover-msg/popover-msg.component.scss @@ -0,0 +1,63 @@ +$arrow-width: 12; +$inner-arrow-width: 11; +$border-color: #808080; +:host { + .arrow { + width: 0; + height: 0; + //margin-left: $arrow-width - 2 + px; + margin-left: auto; + margin-right: auto; + z-index: 50; + border-left: $arrow-width + px solid transparent; + border-right: $arrow-width + px solid transparent; + + &.up { + border-bottom: $arrow-width + px solid $border-color; + } + &.down { + border-top: $arrow-width + px solid $border-color; + margin-top: -1px; + } + + &:before { + height: $inner-arrow-width + px; + display: block; + width: $inner-arrow-width + px; + border-left: $inner-arrow-width + px solid transparent; + border-right: $inner-arrow-width + px solid transparent; + margin-left: -$inner-arrow-width + px; + position: absolute; + content: ""; + } + + &.up:before { + border-bottom: $inner-arrow-width + px solid white; + top: 1px; + } + + &.down:before { + border-top: $inner-arrow-width + px solid white; + bottom: 1px; + } + } + .box { + background: white; + border-radius: 0.25rem; + -webkit-box-shadow: 3px 3px 6px 0px rgba(0, 0, 0, 0.25); + -moz-box-shadow: 3px 3px 6px 0px rgba(0, 0, 0, 0.25); + box-shadow: 3px 3px 6px 0px rgba(0, 0, 0, 0.25); + border: solid $border-color; + border-width: 1px; + margin-top: -1px; + } + .symbol { + border-radius: 0.2rem; + background-color: #ffa300; + color: white; + padding: 1px 10px; + font-size: 16px; + font-weight: 900; + margin-right: 10px; + } +} diff --git a/APP/src/directives/rbj-tooltip/popover-msg/popover-msg.component.spec.ts b/APP/src/directives/rbj-tooltip/popover-msg/popover-msg.component.spec.ts new file mode 100644 index 0000000..a2ea732 --- /dev/null +++ b/APP/src/directives/rbj-tooltip/popover-msg/popover-msg.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; + +import { PopoverMsgComponent } from './popover-msg.component'; + +describe('PopoverMsgComponent', () => { + let component: PopoverMsgComponent; + let fixture: ComponentFixture; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [ PopoverMsgComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(PopoverMsgComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/APP/src/directives/rbj-tooltip/popover-msg/popover-msg.component.ts b/APP/src/directives/rbj-tooltip/popover-msg/popover-msg.component.ts new file mode 100644 index 0000000..d1fb515 --- /dev/null +++ b/APP/src/directives/rbj-tooltip/popover-msg/popover-msg.component.ts @@ -0,0 +1,26 @@ +import { Component, OnInit, ChangeDetectionStrategy, Input } from '@angular/core'; +import { NbRenderableContainer, NbPosition, NbPositionedContainerComponent } from '@nebular/theme'; + +@Component({ + selector: 'ngx-popover-msg', + templateUrl: './popover-msg.component.html', + styleUrls: ['./popover-msg.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class PopoverMsgComponent extends NbPositionedContainerComponent implements NbRenderableContainer { + renderContent() { + //throw new Error("Method not implemented."); + } + + @Input() message: string + + @Input() + context: { message?: string, position: NbPosition } = { + position: NbPosition.BOTTOM + }; + ngOnInit() { + this.message = this.context.message; + this.position = this.context.position || NbPosition.BOTTOM; + } + +} diff --git a/APP/src/directives/rbj-tooltip/rbj-tooltip.directive.spec.ts b/APP/src/directives/rbj-tooltip/rbj-tooltip.directive.spec.ts new file mode 100644 index 0000000..bcc7e2f --- /dev/null +++ b/APP/src/directives/rbj-tooltip/rbj-tooltip.directive.spec.ts @@ -0,0 +1,8 @@ +import { RbjTooltipDirective } from './rbj-tooltip.directive'; + +describe('RbjTooltipDirective', () => { + it('should create an instance', () => { + const directive = new RbjTooltipDirective(); + expect(directive).toBeTruthy(); + }); +}); diff --git a/APP/src/directives/rbj-tooltip/rbj-tooltip.directive.ts b/APP/src/directives/rbj-tooltip/rbj-tooltip.directive.ts new file mode 100644 index 0000000..5b133db --- /dev/null +++ b/APP/src/directives/rbj-tooltip/rbj-tooltip.directive.ts @@ -0,0 +1,33 @@ +import { Directive, ElementRef, Input, Renderer2 } from '@angular/core'; +import { RbjTooltipService } from '../../services/rbj-tooltip.service'; +import { UuidUtils } from '../../utilities/uuid-utils'; +@Directive({ + selector: '[rbjTooltip]', +}) +export class RbjTooltipDirective { + + @Input('rbjTooltip') message: string; + @Input('rbjTooltipAutoHideSeconds') autoHideSeconds: number = 3; + + private uuid: string; + constructor( + private elementRef: ElementRef, + private rbjTooltipService: RbjTooltipService + ) { } + + + ngOnInit(): void { + this.uuid = UuidUtils.generate(); + } + ngOnDestroy() { + this.rbjTooltipService.destroy(this.uuid); + } + + show() { + this.rbjTooltipService.show(this.elementRef, this.message, this.uuid, this.autoHideSeconds); + } + + hide() { + this.rbjTooltipService.hide(this.uuid); + } +} diff --git a/APP/src/directives/rbj-tooltip/rbj-tooltip.module.ts b/APP/src/directives/rbj-tooltip/rbj-tooltip.module.ts new file mode 100644 index 0000000..7b313ca --- /dev/null +++ b/APP/src/directives/rbj-tooltip/rbj-tooltip.module.ts @@ -0,0 +1,21 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { PopoverMsgComponent } from './popover-msg/popover-msg.component'; +import { RbjTooltipDirective } from './rbj-tooltip.directive'; +import { InputLimitationDirective } from './input-limitation.directive'; + +@NgModule({ + declarations: [ + RbjTooltipDirective, + InputLimitationDirective, + PopoverMsgComponent, + ], + imports: [ + CommonModule + ], + exports: [ + RbjTooltipDirective, + InputLimitationDirective + ] +}) +export class RbjTooltipModule { } diff --git a/APP/src/directives/regex-validator/regex-validator.directive.spec.ts b/APP/src/directives/regex-validator/regex-validator.directive.spec.ts new file mode 100644 index 0000000..932bc69 --- /dev/null +++ b/APP/src/directives/regex-validator/regex-validator.directive.spec.ts @@ -0,0 +1,8 @@ +import { RegexValidatorDirective } from './regex-validator.directive'; + +describe('RegexValidatorDirective', () => { + it('should create an instance', () => { + const directive = new RegexValidatorDirective(); + expect(directive).toBeTruthy(); + }); +}); diff --git a/APP/src/directives/regex-validator/regex-validator.directive.ts b/APP/src/directives/regex-validator/regex-validator.directive.ts new file mode 100644 index 0000000..3bde02f --- /dev/null +++ b/APP/src/directives/regex-validator/regex-validator.directive.ts @@ -0,0 +1,117 @@ +import { Directive, forwardRef, Input, ElementRef, HostListener, Output, EventEmitter } from '@angular/core'; +import { NG_VALIDATORS, Validator, AbstractControl, ValidationErrors } from '@angular/forms'; +import { UuidUtils } from '../../utilities/uuid-utils'; +import { RbjTooltipService } from '../../services/rbj-tooltip.service'; + +@Directive({ + selector: 'input[regexValidator], textarea[regexValidator]', + providers: [ + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => RegexValidatorDirective), + multi: true, + }, + ], +}) +export class RegexValidatorDirective implements Validator { + + private uuid: string; + private _msgShown: boolean = false; + + @Input('regexValidator') public regexExpression: string = ''; + @Input('invalidMsg') public invalidMsg: string = 'Invalid format'; + + + private _invalid: boolean = false; + public get invalid(): boolean { + return this._invalid; + } + @Input() public set invalid(v: boolean) { + if (this._invalid != v) { + this._invalid = v; + this.invalidChange.emit(v); + } + } + @Output() invalidChange = new EventEmitter() + + + private _required: boolean = false; + public get required(): boolean { + return this._required; + } + public set required(v: boolean) { + this._required = typeof v !== 'undefined' && v !== false; + + } + + constructor( + private elementRef: ElementRef, + private rbjTooltipService: RbjTooltipService) { } + + + public get inputText(): string { + return this.elementRef.nativeElement.value; + } + + + validate(control: AbstractControl): ValidationErrors { + + this.hide(); + if (this.inputText && this.inputText.length > 0 && + !this.inputText.match(new RegExp(this.regexExpression, 'g'))) { + this.invalid = true; + //this.show(); + return this.createValidationError(this.inputText) + } + + this.invalid = false; + return null; + } + registerOnValidatorChange?(fn: () => void): void { + + } + + ngOnInit() { + this.uuid = UuidUtils.generate(); + } + + ngOnDestroy() { + this.rbjTooltipService.destroy(this.uuid); + } + + @HostListener('focusout') + onFocusOut() { + if (this.invalid) { + this.show(); + setTimeout(() => { + this.elementRef.nativeElement.focus(); + }, 200); + } + } + + show() { + + let formHasBeenTouched = this.elementRef.nativeElement.form && this.elementRef.nativeElement.form.className.indexOf('ng-touched') == -1; + if (false == formHasBeenTouched) { + this._msgShown = true; + this.rbjTooltipService.show(this.elementRef, this.invalidMsg, this.uuid, 0); + } + } + + hide() { + //this.isOpen = false; + if (this._msgShown) { + this._msgShown = false; + this.rbjTooltipService.hide(this.uuid); + } + } + + private createValidationError(actualValue: string): ValidationErrors { + return { + mask: { + requiredRegex: this.regexExpression, + actualValue, + }, + }; + } +} diff --git a/APP/src/directives/regex-validator/regex-validator.module.ts b/APP/src/directives/regex-validator/regex-validator.module.ts new file mode 100644 index 0000000..01aa714 --- /dev/null +++ b/APP/src/directives/regex-validator/regex-validator.module.ts @@ -0,0 +1,14 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { RegexValidatorDirective } from './regex-validator.directive'; +import { PopoverMsgModule } from '../../components/popover-msg/popover-msg.module'; + +@NgModule({ + declarations: [RegexValidatorDirective], + imports: [ + CommonModule, + PopoverMsgModule + ], + exports: [RegexValidatorDirective] +}) +export class RegexValidatorModule { } diff --git a/APP/src/directives/right-click-menu/context-menu-item.model.ts b/APP/src/directives/right-click-menu/context-menu-item.model.ts new file mode 100644 index 0000000..f52dd95 --- /dev/null +++ b/APP/src/directives/right-click-menu/context-menu-item.model.ts @@ -0,0 +1,17 @@ +import { ElementRef } from '@angular/core'; + +export class ContextMenuItem { + enabled?: boolean = true; + id?: string; + title: string; + icon?: string; + groupStart?: boolean = false; + visible?: boolean = true; + forGroupHeader?: boolean = false; + callback: (value: T, element: HTMLElement) => void; + contextMenuItems?: ContextMenuItem[]; + + constructor(config: Partial>) { + Object.assign(this, config); + } +} \ No newline at end of file diff --git a/APP/src/directives/right-click-menu/direction.enum.ts b/APP/src/directives/right-click-menu/direction.enum.ts new file mode 100644 index 0000000..b406ee6 --- /dev/null +++ b/APP/src/directives/right-click-menu/direction.enum.ts @@ -0,0 +1,8 @@ + +export enum Direction { + Auto, + Up, + Down, + Left, + Right +} \ No newline at end of file diff --git a/APP/src/directives/right-click-menu/right-click-menu.directive.spec.ts b/APP/src/directives/right-click-menu/right-click-menu.directive.spec.ts new file mode 100644 index 0000000..7929c5f --- /dev/null +++ b/APP/src/directives/right-click-menu/right-click-menu.directive.spec.ts @@ -0,0 +1,8 @@ +import { RightClickMenuDirective } from './right-click-menu.directive'; + +describe('RightClickMenuDirective', () => { + it('should create an instance', () => { + const directive = new RightClickMenuDirective(); + expect(directive).toBeTruthy(); + }); +}); diff --git a/APP/src/directives/right-click-menu/right-click-menu.directive.ts b/APP/src/directives/right-click-menu/right-click-menu.directive.ts new file mode 100644 index 0000000..3c1d5cf --- /dev/null +++ b/APP/src/directives/right-click-menu/right-click-menu.directive.ts @@ -0,0 +1,55 @@ +import { Directive, HostListener, Input } from '@angular/core'; +import { NbDialogService } from '@nebular/theme'; +import { ContextMenuComponent } from '../../components/context-menu/context-menu.component'; +import { RbjDialogService } from '../../services/rbj-dialog.service'; +import { ContextMenuItem } from './context-menu-item.model'; +import { Direction } from './direction.enum'; +const ITEM_HEIGHT = 45; +@Directive({ + selector: '[RightClickMenu]', +}) +export class RightClickMenuDirective { + + @Input() public RightClickMenuItems: ContextMenuItem[] = []; + @Input() public RightClickValue: any; // Value to pass into callback + @Input() onContextMenuOpening: (datum: any, rightClickMenuItems: ContextMenuItem[], event: MouseEvent) => any; + + constructor( + private dialogService: RbjDialogService, + ) { } + + @HostListener('contextmenu', ['$event']) contextMenuHandler(event: MouseEvent) { + if (event.which === 3 && this.RightClickMenuItems && this.RightClickMenuItems.length > 0) { + this.open(event); + event.preventDefault(); + } + } + + open(event: MouseEvent, direction: Direction = Direction.Auto) { + let targetValue = this.RightClickValue; + + for (let i = 0; i < this.RightClickMenuItems.length; i++) { + this.RightClickMenuItems[i] = new ContextMenuItem(this.RightClickMenuItems[i]); + } + + if (this.onContextMenuOpening) { + targetValue = this.onContextMenuOpening(this.RightClickValue, this.RightClickMenuItems, event); + } + + const windowHeight = window.innerHeight; + const itemHeight = this.RightClickMenuItems.filter(i => i.visible).length * ITEM_HEIGHT; + const y = (event.y + itemHeight) > windowHeight ? (windowHeight - itemHeight) : event.y; + + this.dialogService.open(ContextMenuComponent, { + backdropClass: "transparent", + context: { + Value: targetValue, + X: event.x, + Y: y, + ContextMenuItems: this.RightClickMenuItems, + Direction: direction, + }, + closeOnBackdropClick: true, + }); + } +} diff --git a/APP/src/directives/right-click-menu/right-click-menu.module.ts b/APP/src/directives/right-click-menu/right-click-menu.module.ts new file mode 100644 index 0000000..280f8ea --- /dev/null +++ b/APP/src/directives/right-click-menu/right-click-menu.module.ts @@ -0,0 +1,19 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { RightClickMenuDirective } from './right-click-menu.directive'; +import { NbDialogModule } from '@nebular/theme'; +import { ContextMenuModule } from '../../components/context-menu/context-menu.module'; +import { ContextMenuComponent } from '../../components/context-menu/context-menu.component'; + +const components = [RightClickMenuDirective,]; + +@NgModule({ + declarations: [...components], + imports: [ + CommonModule, + NbDialogModule, + ContextMenuModule + ], + exports: [...components] +}) +export class RightClickMenuModule { } diff --git a/APP/src/directives/track-caps/track-caps.directive.spec.ts b/APP/src/directives/track-caps/track-caps.directive.spec.ts new file mode 100644 index 0000000..40107a7 --- /dev/null +++ b/APP/src/directives/track-caps/track-caps.directive.spec.ts @@ -0,0 +1,8 @@ +import { TrackCapsDirective } from './track-caps.directive'; + +describe('TrackCapsDirective', () => { + it('should create an instance', () => { + const directive = new TrackCapsDirective(); + expect(directive).toBeTruthy(); + }); +}); diff --git a/APP/src/directives/track-caps/track-caps.directive.ts b/APP/src/directives/track-caps/track-caps.directive.ts new file mode 100644 index 0000000..6af30a5 --- /dev/null +++ b/APP/src/directives/track-caps/track-caps.directive.ts @@ -0,0 +1,20 @@ +import { Directive, EventEmitter, HostListener, Output } from '@angular/core'; + +@Directive({ + selector: '[capsLock]' +}) +export class TrackCapsDirective { + + @Output('capsLock') capsLock = new EventEmitter(); + + @HostListener('window:keydown', ['$event']) + onKeyDown(event: KeyboardEvent): void { + const capsOn = event.getModifierState && event.getModifierState('CapsLock'); + this.capsLock.emit(capsOn); + } + @HostListener('window:keyup', ['$event']) + onKeyUp(event: KeyboardEvent): void { + const capsOn = event.getModifierState && event.getModifierState('CapsLock'); + this.capsLock.emit(capsOn); + } +} diff --git a/APP/src/directives/track-caps/track-caps.module.ts b/APP/src/directives/track-caps/track-caps.module.ts new file mode 100644 index 0000000..f55d82d --- /dev/null +++ b/APP/src/directives/track-caps/track-caps.module.ts @@ -0,0 +1,14 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { TrackCapsDirective } from './track-caps.directive'; + +const components = [TrackCapsDirective,]; + +@NgModule({ + declarations: [...components], + imports: [ + CommonModule + ], + exports: [...components], +}) +export class TrackCapsModule { } \ No newline at end of file diff --git a/APP/src/environments/README.md b/APP/src/environments/README.md new file mode 100644 index 0000000..16a9504 --- /dev/null +++ b/APP/src/environments/README.md @@ -0,0 +1,53 @@ +# Environment Configuration + +This directory contains environment-specific configuration files for the Angular application. + +## Files + +- `environment.ts` - Development environment configuration +- `environment.prod.ts` - Production environment configuration + +## API Configuration + +The API base URL is configured in these environment files and accessed through the `ApiConfigService`. + +### Development +```typescript +export const environment = { + production: false, + apiUrl: 'http://localhost:21860/api' +}; +``` + +### Production +```typescript +export const environment = { + production: true, + apiUrl: 'https://your-production-api.com/api' +}; +``` + +## Usage + +Instead of hardcoding API URLs, use the `ApiConfigService`: + +```typescript +import { ApiConfigService } from '../core/services/api-config.service'; + +constructor(private apiConfig: ApiConfigService) {} + +// Get specific endpoint URLs +const authUrl = this.apiConfig.authUrl; // http://localhost:21860/api/Auth +const usersUrl = this.apiConfig.usersUrl; // http://localhost:21860/api/Users + +// Or build custom URLs +const customUrl = this.apiConfig.getApiUrl('CustomEndpoint'); // http://localhost:21860/api/CustomEndpoint +``` + +## Benefits + +1. **Environment-specific URLs**: Different URLs for development, staging, and production +2. **Centralized configuration**: All API URLs managed in one place +3. **Type safety**: TypeScript support for configuration +4. **Easy maintenance**: Change URLs without touching service code +5. **Build-time optimization**: Angular replaces environment variables at build time diff --git a/APP/src/environments/environment.prod.ts b/APP/src/environments/environment.prod.ts new file mode 100644 index 0000000..5e51f5f --- /dev/null +++ b/APP/src/environments/environment.prod.ts @@ -0,0 +1,4 @@ +export const environment = { + production: true, + apiUrl: 'https://your-production-api.com/api' +}; diff --git a/APP/src/environments/environment.ts b/APP/src/environments/environment.ts new file mode 100644 index 0000000..a36ef46 --- /dev/null +++ b/APP/src/environments/environment.ts @@ -0,0 +1,4 @@ +export const environment = { + production: false, + apiUrl: 'https://localhost:44302/api' +}; diff --git a/APP/src/index.html b/APP/src/index.html new file mode 100644 index 0000000..4ab0561 --- /dev/null +++ b/APP/src/index.html @@ -0,0 +1,19 @@ + + + + + + RBJ Identity + + + + + + + + + + + + \ No newline at end of file diff --git a/APP/src/main.ts b/APP/src/main.ts new file mode 100644 index 0000000..6bb4616 --- /dev/null +++ b/APP/src/main.ts @@ -0,0 +1,8 @@ +/// + +import { bootstrapApplication } from '@angular/platform-browser'; +import { appConfig } from './app/app.config'; +import { App } from './app/app'; + +bootstrapApplication(App, appConfig) + .catch((err) => console.error(err)); diff --git a/APP/src/styles.css b/APP/src/styles.css new file mode 100644 index 0000000..90d4ee0 --- /dev/null +++ b/APP/src/styles.css @@ -0,0 +1 @@ +/* You can add global styles to this file, and also import other style files */ diff --git a/APP/src/styles.scss b/APP/src/styles.scss new file mode 100644 index 0000000..18ff29e --- /dev/null +++ b/APP/src/styles.scss @@ -0,0 +1,264 @@ +/* Shared UI Utility Classes */ +@use "app/shared/styles/ui-utils.scss"; + +@import "https://fonts.googleapis.com/css2?family=Fira+Sans:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap"; + +/* Kendo UI Theme */ +//@import "@progress/kendo-theme-default/dist/all.css"; + +/* Override Kendo UI Card default width */ +.k-card { + width: 100% !important; +} + +/* Reset styles */ +*, +::before, +::after { + box-sizing: border-box; +} + +p, +ul, +ol, +h1, +h2, +h3, +h4, +h5, +h6, +p, +pre, +blockquote { + margin: 0; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + font-size: inherit; + font-weight: inherit; +} +ol, +ul { + list-style: none; + margin: 0; + padding: 0; +} + +hr { + height: 0; + border-top-width: 1px; + border-bottom-width: 0; +} +/* End of Reset styles */ + +:root { + /* Color System */ + /* Misc */ + --kendo-color-app-surface: #fff; + --kendo-color-surface: #f2f4f7; + --kendo-color-surface-alt: #fff; + --kendo-color-on-app-surface: #28323e; + --kendo-color-subtle: #50647c; + --kendo-color-border: rgba(23, 65, 99, 0.12); + --kendo-color-border-alt: rgba(23, 65, 99, 0.24); + /* Base */ + --kendo-color-base-subtle: #e6eaef; + --kendo-color-base-subtle-hover: #dae0e7; + --kendo-color-base-subtle-active: #c1cbd7; + --kendo-color-base: #ffffff; + --kendo-color-base-hover: #e6eaef; + --kendo-color-base-active: #dae0e7; + --kendo-color-base-emphasis: #405063; + --kendo-color-on-base: #202832; + --kendo-color-base-on-surface: #28323e; + --kendo-color-base-on-subtle: #181e25; + /* Primary */ + --kendo-color-primary-subtle: #daecfb; + --kendo-color-primary-subtle-hover: #c7e3f9; + --kendo-color-primary-subtle-active: #a2d0f6; + --kendo-color-primary: #0279cf; + --kendo-color-primary-hover: #136db9; + --kendo-color-primary-active: #1767ab; + --kendo-color-primary-emphasis: #15588e; + --kendo-color-on-primary: #ffffff; + --kendo-color-primary-on-surface: #136db9; + --kendo-color-primary-on-subtle: #174163; + /* Secondary */ + --kendo-color-secondary-subtle: #a8b6c7; + --kendo-color-secondary-subtle-hover: #8fa1b7; + --kendo-color-secondary-subtle-active: #768ca7; + --kendo-color-secondary: #607895; + --kendo-color-secondary-hover: #50647c; + --kendo-color-secondary-active: #405063; + --kendo-color-secondary-emphasis: #202832; + --kendo-color-on-secondary: #ffffff; + --kendo-color-secondary-on-surface: #50647c; + --kendo-color-secondary-on-subtle: #101419; + /* Tertiary */ + --kendo-color-tertiary-subtle: #dafbf9; + --kendo-color-tertiary-subtle-hover: #c7f9f7; + --kendo-color-tertiary-subtle-active: #a2f6f1; + --kendo-color-tertiary: #16d4cb; + --kendo-color-tertiary-hover: #13b9b0; + --kendo-color-tertiary-active: #16a29b; + --kendo-color-tertiary-emphasis: #137c77; + --kendo-color-on-tertiary: #000000; + --kendo-color-tertiary-on-surface: #13b9b0; + --kendo-color-tertiary-on-subtle: #104240; + /* Error */ + --kendo-color-error-subtle: #fcdcd9; + --kendo-color-error-subtle-hover: #fcc4c0; + --kendo-color-error-subtle-active: #faa9a3; + --kendo-color-error: #e71b0d; + --kendo-color-error-hover: #d41a0c; + --kendo-color-error-active: #c1170b; + --kendo-color-error-emphasis: #911108; + --kendo-color-on-error: #ffffff; + --kendo-color-error-on-surface: #d41a0c; + --kendo-color-error-on-subtle: #4d0904; + /* Success */ + --kendo-color-success-subtle: #d9fce8; + --kendo-color-success-subtle-hover: #c2fad9; + --kendo-color-success-subtle-active: #a6f7c8; + --kendo-color-success: #32ed7f; + --kendo-color-success-hover: #14e069; + --kendo-color-success-active: #12ce60; + --kendo-color-success-emphasis: #0c8d42; + --kendo-color-on-success: #000; + --kendo-color-success-on-surface: #14e069; + --kendo-color-success-on-subtle: #074b23; + /* Warning */ + --kendo-color-warning-subtle: #fcf6d9; + --kendo-color-warning-subtle-hover: #faf0c2; + --kendo-color-warning-subtle-active: #f7eaa6; + --kendo-color-warning: #edcd32; + --kendo-color-warning-hover: #e0be14; + --kendo-color-warning-active: #ceaf12; + --kendo-color-warning-emphasis: #8d770c; + --kendo-color-on-warning: #000000; + --kendo-color-warning-on-surface: #e0be14; + --kendo-color-warning-on-subtle: #4b4007; + /* Info */ + --kendo-color-info-subtle: #d9f6fc; + --kendo-color-info-subtle-hover: #c2f0fa; + --kendo-color-info-subtle-active: #a6eaf7; + --kendo-color-info: #32ceed; + --kendo-color-info-hover: #14bee0; + --kendo-color-info-active: #12afce; + --kendo-color-info-emphasis: #0c778d; + --kendo-color-on-info: #000000; + --kendo-color-info-on-surface: #14bee0; + --kendo-color-info-on-subtle: #07404b; + /* Light */ + --kendo-color-light-subtle: #e6eaef; + --kendo-color-light-subtle-hover: #dae0e7; + --kendo-color-light-subtle-active: #c1cbd7; + --kendo-color-light: #f2f4f7; + --kendo-color-light-hover: #e6eaef; + --kendo-color-light-active: #dae0e7; + --kendo-color-light-emphasis: #a8b6c7; + --kendo-color-on-light: #181e25; + --kendo-color-light-on-surface: #e6eaef; + --kendo-color-light-on-subtle: #303c4a; + /* Dark */ + --kendo-color-dark-subtle: #202832; + --kendo-color-dark-subtle-hover: #28323e; + --kendo-color-dark-subtle-active: #303c4a; + --kendo-color-dark: #101419; + --kendo-color-dark-hover: #181e25; + --kendo-color-dark-active: #202832; + --kendo-color-dark-emphasis: #50647c; + --kendo-color-on-dark: #ffffff; + --kendo-color-dark-on-surface: #181e25; + --kendo-color-dark-on-subtle: #f2f4f7; + /* Series A */ + --kendo-color-series-a-subtler: #e4b4c1; + --kendo-color-series-a: #c35573; + --kendo-color-series-a-bold: #a63a57; + --kendo-color-series-a-subtle: #d5869b; + --kendo-color-series-a-bolder: #792a3f; + /* Series B */ + --kendo-color-series-b-subtler: #accfec; + --kendo-color-series-b: #4493d5; + --kendo-color-series-b-bold: #2975b3; + --kendo-color-series-b-subtle: #7ab3e1; + --kendo-color-series-b-bolder: #1d5481; + /* Series C */ + --kendo-color-series-c-subtler: #b1d2c2; + --kendo-color-series-c: #62a583; + --kendo-color-series-c-bold: #4a8266; + --kendo-color-series-c-subtle: #8abca3; + --kendo-color-series-c-bolder: #345b47; + /* Series D */ + --kendo-color-series-d-subtler: #69bed8; + --kendo-color-series-d: #28829d; + --kendo-color-series-d-bold: #1c5b6d; + --kendo-color-series-d-subtle: #38a9cc; + --kendo-color-series-d-bolder: #10323d; + /* Series E */ + --kendo-color-series-e-subtler: #eed57c; + --kendo-color-series-e: #d6ad1c; + --kendo-color-series-e-bold: #a28415; + --kendo-color-series-e-subtle: #e7c446; + --kendo-color-series-e-bolder: #6c580e; + /* SERIES F */ + --kendo-color-series-f-subtler: #c5c0d8; + --kendo-color-series-f: #7f76a9; + --kendo-color-series-f-bold: #60568a; + --kendo-color-series-f-subtle: #a29bc0; + --kendo-color-series-f-bolder: #463f64; + + /* Typography */ + --kendo-font-family: "Fira Sans", sans-serif; + + --kendo-font-size: 14px; + --kendo-font-size-sm: 12px; + --kendo-font-size-md: var(--kendo-font-size); + --kendo-font-size-lg: 16px; + --kendo-font-size-xl: 20px; + + --kendo-h1-font-size: 56px; + --kendo-h2-font-size: 42px; + --kendo-h3-font-size: 35px; + --kendo-h4-font-size: 28px; + --kendo-h5-font-size: 21px; + --kendo-h6-font-size: 14px; + + --kendo-line-height-xs: 1.2; + --kendo-line-height-sm: 1.25; + --kendo-line-height-md: 1.42; + --kendo-line-height-lg: 1.5; + + --kendo-h1-line-height: calc((var(--kendo-font-size) * 4) * 1.25); + --kendo-h2-line-height: calc((var(--kendo-font-size) * 3) * 1.2); + --kendo-h3-line-height: calc((var(--kendo-font-size) * 2.5) * 1.2); + --kendo-h4-line-height: calc((var(--kendo-font-size) * 2) * 1.2); + --kendo-h5-line-height: calc((var(--kendo-font-size) * 1.5) * 1.25); + --kendo-h6-line-height: calc((var(--kendo-font-size) * 1) * 1.25); + + --kendo-h1-font-weight: 400; + --kendo-h2-font-weight: 400; + --kendo-h3-font-weight: 400; + --kendo-h4-font-weight: 500; + --kendo-h5-font-weight: 500; + --kendo-h6-font-weight: 500; + + /* Border radius */ + --k-border-radius-null: 0px; + --kendo-border-radius-xs: 4px; + --kendo-border-radius-sm: 6px; + --kendo-border-radius-md: 12px; + --kendo-border-radius-lg: 24px; + --kendo-border-radius-xl: 36px; + --kendo-border-radius-xxl: 48px; + --kendo-border-radius-xxxl: 60px; + --kendo-border-radius-full: 9999px; + + /* Panel Gradient */ + --panel-gradient: linear-gradient(to bottom, white 60%, #fafafa 0%); +} diff --git a/APP/src/utilities/array-utils.ts b/APP/src/utilities/array-utils.ts new file mode 100644 index 0000000..a8cd594 --- /dev/null +++ b/APP/src/utilities/array-utils.ts @@ -0,0 +1,103 @@ +import { DropDownOption } from '../entity/dropDownOption'; + +export class ArrayUtils { + + public static Equals(array1, array2) { + // if the other array is a falsy value, return + if (!array2) + return false; + + // compare lengths - can save a lot of time + if (array1.length != array2.length) + return false; + + for (var i = 0, l = array1.length; i < l; i++) { + // Check if we have nested arrays + if (array1[i] instanceof Array && array2[i] instanceof Array) { + // recurse into the nested arrays + if (!ArrayUtils.Equals(array1[i], array2[i])) + return false; + } + else if (array1[i] != array2[i]) { + // Warning - two different object instances will never be equal: {x:20} != {x:20} + return false; + } + } + return true; + } + + public static ToDropDownOptions(array: any[], keyProperty: string | number, valueProperty: string | number): DropDownOption[] { + var result = []; + for (let i = 0; i < array.length; i++) { + const element = array[i]; + result.push(new DropDownOption(element[keyProperty], element[valueProperty])); + } + return result; + } + public static MoveItem(arr: any[], old_index, new_index) { + if (new_index >= arr.length) { + var k = new_index - arr.length + 1; + while (k--) { + arr.push(undefined); + } + } + arr.splice(new_index, 0, arr.splice(old_index, 1)[0]); + }; + + public static GroupBy(arr: Array, groupKeyPrepareFunction: (obj: T) => any | any[]) { + + let groups = arr.reduce(function (groupModel, obj) { + + let keys = groupKeyPrepareFunction(obj); + + const addToGroup = (key: any, obj: T) => { + let group = groupModel.find(g => g.key == key); + if (group == null) { + group = new GroupModel(key); + groupModel.push(group); + } + group.values.push(obj); + } + + if (Array.isArray(keys)) { + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + addToGroup(key, obj); + } + } else { + addToGroup(keys, obj); + } + return groupModel; + }, [] as GroupModel[]); + return groups; + } + public static RemoveDuplicate(objArray: T[], duplicateDetection: (a: T, b: T) => boolean) { + return objArray.filter((value, index, self) => + index === self.findIndex((t) => duplicateDetection(t, value)) + ); + } + + public static ConcatMultiArray(arrays: T[][]): T[] { + return [].concat.apply([], arrays.map(arr => arr)); + } + + public static insertAt = (arr: any[], index: number, newItems: any) => [ + // part of the array before the specified index + ...arr.slice(0, index), + // inserted items + newItems, + // part of the array after the specified index + ...arr.slice(index) + ]; + +} + +export class GroupModel{ + constructor(key: any) { + this.key = key; + this.values = []; + } + + key: any; + values: T[]; +} \ No newline at end of file diff --git a/APP/src/utilities/baseUrl.ts b/APP/src/utilities/baseUrl.ts new file mode 100644 index 0000000..4f69d1b --- /dev/null +++ b/APP/src/utilities/baseUrl.ts @@ -0,0 +1,5 @@ +export class UrlBase { + + public static LoginPortalApiPrefixPath = "/TsPlusBridge/api"; + +} \ No newline at end of file diff --git a/APP/src/utilities/date-utils.ts b/APP/src/utilities/date-utils.ts new file mode 100644 index 0000000..cd2d63a --- /dev/null +++ b/APP/src/utilities/date-utils.ts @@ -0,0 +1,190 @@ +export class DateUtils { + public static getIntervalDays(from: Date, to: Date, daysOfYear: DaysOfYear = DaysOfYear.ThirtyDaysPerMonth, countEndDate: boolean = false): number { + let isNegative = false; + if (from > to) { + isNegative = true; + let temp = new Date(to); + to = from; + from = temp; + } + + var days = 0; + if (from && to) { + //Get date without time + from = new Date(from.getFullYear(), from.getMonth(), from.getDate()); + to = new Date(to.getFullYear(), to.getMonth(), to.getDate()); + var differenceTime = to.getTime() - from.getTime(); + if (differenceTime > 0) { + if (daysOfYear == DaysOfYear.ThirtyDaysPerMonth) { + var fromYear = from.getFullYear(); + var toYear = to.getFullYear(); + var fromMonth = from.getMonth() + 1; + var toMonth = to.getMonth() + 1; + + var fromDays = from.getDate() > 30 ? 30 : from.getDate(); + var toDays = to.getDate(); + + days += 30 - fromDays; + days += toDays; + //calculate full 12 months years + if (toYear > (fromYear + 1)) { + days += (toYear - fromYear - 1) * 12 * 30; + } + + //if it's two different years, calculate the interval months + if (toYear > fromYear) { + days += (12 - fromMonth) * 30; + days += (toMonth - 1) * 30; + } + else { + //same year + days += (toMonth - fromMonth - 1) * 30 + } + } + else { + // To calculate the no. of days between two dates + days = Math.round(differenceTime / (1000 * 3600 * 24)); + } + } + } + + return (days + (countEndDate ? 1 : 0)) * (isNegative ? -1 : 1); + } + + public static addDays(date: Date, days: number): Date { + if (date) { + var result = new Date(date); + result.setDate(result.getDate() + days); + return result; + } + return date; + } + + public static format(date: Date, format: string, nullFormat: string = ''): string { + if (date) { + var z = { + M: date.getMonth() + 1, + d: date.getDate(), + H: date.getHours(), + h: (date.getHours() == 0 ? date.getHours() + 12 : date.getHours() > 12 ? date.getHours() - 12 : date.getHours()), + m: date.getMinutes(), + s: date.getSeconds(), + a: (date.getHours() > 11 ? 'PM' : 'AM') + }; + format = format.replace(/(M+|d+|H+|h+|m+|s+|a+)/g, function (v) { + return ((v.length > 1 ? "0" : "") + z[v.slice(-1)]).slice(-2); + }); + + return format.replace(/(y+)/g, function (v) { + return date.getFullYear().toString().slice(-v.length) + }); + } + return nullFormat; + } + public static isValidDate(d): boolean { + return d instanceof Date && d.getTime() == d.getTime(); + } + public static parse(value): Date { + if (value) { + value = new Date(value); + } + return value + } + + public static getToday(endOfDay: boolean = false): Date { + let value = new Date(); + if (!endOfDay) { + value.setHours(0, 0, 0, 0); + } + else { + value.setHours(23, 59, 59, 999); + } + return value + } + + public static getBeginOfDate(value): Date { + if (value) { + value = new Date(value); + value.setHours(0, 0, 0, 0); + } + return value + } + + public static getEndOfDate(value): Date { + if (value) { + value = new Date(value); + value.setHours(23, 59, 59, 999); + } + return value + } + public static getEndOfMonth(value): Date { + if (value) { + return new Date(value.getFullYear(), value.getMonth() + 1, 0) + } + return value; + } + + public static isSameDate(date: Date, comparison: Date): boolean { + if (!date || !comparison) return (!date && !comparison); + date = this.parse(date); + comparison = this.parse(comparison); + return date.getFullYear() == comparison.getFullYear() && date.getMonth() == comparison.getMonth() && date.getDate() == comparison.getDate(); + } + + public static changeTimezone(date, ianatz) { + // suppose the date is 12:00 UTC + var invdate = new Date(date.toLocaleString('en-US', { + timeZone: ianatz + })); + + // then invdate will be 07:00 in Toronto + // and the diff is 5 hours + var diff = date.getTime() - invdate.getTime(); + + // so 12:00 in Toronto is 17:00 UTC + return new Date(date.getTime() + diff); + } + + public static changeTimezoneByOffset(date, tzOffset) { + let time = date.getTime(); + return new Date(time - (tzOffset * 60000)); + } + + public static getTimeStamp() { + var now = new Date(); + return ((now.getMonth() + 1) + '/' + (now.getDate()) + '/' + now.getFullYear() + " " + now.getHours() + ':' + + ((now.getMinutes() < 10) ? ("0" + now.getMinutes()) : (now.getMinutes())) + ':' + ((now.getSeconds() < 10) ? ("0" + now + .getSeconds()) : (now.getSeconds()))); + } + + + public static getDatesBetween(startDate: Date, endDate: Date): Date[] { + startDate = this.getBeginOfDate(startDate); + endDate = this.getBeginOfDate(endDate); + let result = [startDate]; + if (startDate < endDate) { + let tempDate = new Date(startDate); + while (tempDate < endDate) { + tempDate = this.addDays(tempDate, 1); + result.push(tempDate); + } + } + return result; + } +} + +export enum DaysOfYear { + ThirtyDaysPerMonth = 360, + FullYearDays = 365 +} + + +export enum DatePeriod { + Daily, + Weekly, + Monthly, + Quarterly, + SemiAnnually, + Annually, + Period, +} \ No newline at end of file diff --git a/APP/src/utilities/enum-utils.ts b/APP/src/utilities/enum-utils.ts new file mode 100644 index 0000000..a72c2e0 --- /dev/null +++ b/APP/src/utilities/enum-utils.ts @@ -0,0 +1,12 @@ +export class EnumUtils { + + public static hasFlag(obj, enumValue): boolean { + if (obj) { + if (obj & enumValue) { + return true; + } + } + return false; + + } +} diff --git a/APP/src/utilities/file-utils.ts b/APP/src/utilities/file-utils.ts new file mode 100644 index 0000000..6b57ded --- /dev/null +++ b/APP/src/utilities/file-utils.ts @@ -0,0 +1,26 @@ +export class FileUtils { + public static formatFileSize(bytes: number): string { + if (bytes < 1024) { + return `${bytes} Bytes`; + } else if (bytes < 1024 * 1024) { + return `${(bytes / 1024).toFixed(2)} KB`; + } else { + return `${(bytes / (1024 * 1024)).toFixed(2)} MB`; + } + } + + public static getFileName(fileFullPath: string): string | null { + const lastSlashIndex = fileFullPath.lastIndexOf('\\'); + if (lastSlashIndex === -1 || lastSlashIndex === 0 || lastSlashIndex === fileFullPath.length - 1) { + return fileFullPath; // No folder found + } + return fileFullPath.slice(lastSlashIndex + 1); + } + public static getFileExt(filename: string): string | null { + const lastDotIndex = filename.lastIndexOf('.'); + if (lastDotIndex === -1 || lastDotIndex === 0 || lastDotIndex === filename.length - 1) { + return null; // No extension found + } + return filename.slice(lastDotIndex); + } +} \ No newline at end of file diff --git a/APP/src/utilities/linq-utils.ts b/APP/src/utilities/linq-utils.ts new file mode 100644 index 0000000..e5e48d6 --- /dev/null +++ b/APP/src/utilities/linq-utils.ts @@ -0,0 +1,9 @@ + +export class LinqUtils { + public static GroupBy(xs: any[], key: any) { + return xs.reduce(function (rv, x) { + (rv[x[key]] = rv[x[key]] || []).push(x); + return rv; + }, {}); + } +} \ No newline at end of file diff --git a/APP/src/utilities/maths.scss b/APP/src/utilities/maths.scss new file mode 100644 index 0000000..9ce213d --- /dev/null +++ b/APP/src/utilities/maths.scss @@ -0,0 +1,8 @@ + +@function add($a, $b) { + @return $a + $b; +} + +@function mult($a, $b) { + @return $a * $b; +} diff --git a/APP/src/utilities/number-utils.ts b/APP/src/utilities/number-utils.ts new file mode 100644 index 0000000..a9354b4 --- /dev/null +++ b/APP/src/utilities/number-utils.ts @@ -0,0 +1,70 @@ +import { formatCurrency } from "@angular/common"; + +export class NumberUtils { + + public static Ordinal(value: number): string { + let suffix = ''; + const last = value % 10; + const specialLast = value % 100; + if (!value || value < 1) { + return value.toString(); + } + if (last === 1 && specialLast !== 11) { + suffix = 'st'; + } else if (last === 2 && specialLast !== 12) { + suffix = 'nd'; + } else if (last === 3 && specialLast !== 13) { + suffix = 'rd'; + } else { + suffix = 'th'; + } + return value + suffix; + } + + public static Clamp(n: number, min: number, max: number): number { + return Math.min(max, Math.max(min, n)); + } + + public static SortFunction(a: number, b: number): number { + return a - b; + } + + public static Sum(array: number[], key: any = undefined): number { + if (key) { + return array.reduce((a, b) => (isNaN(a[key]) ? 0 : a[key]) + (isNaN(b[key]) ? 0 : b[key]), 0); + } + return array.reduce((a, b) => (isNaN(a) ? 0 : a) + (isNaN(b) ? 0 : b), 0); + } + + public static FormatCurrency(v: number, zeroExpression: string = '0'): string { + + return ['', 0, null, undefined, NaN].includes(v) ? zeroExpression : formatCurrency(v, "en", "", "", `0.2`); + } + public static Round(num, precision) { + const factor = 10 ** precision; + return Math.round(num * factor) / factor; + } + + public static RoundCurrency(num) { + return this.Round(num, 2); + } + + public static VersionDiff(v1: string, v2: string) { + let versionDefine = [ + { index: 0, name: 'major' }, + { index: 1, name: 'minor' }, + { index: 2, name: 'patch' }, + { index: 3, name: 'build' } + ] + if (v1 && v2) { + let v1Versions = v1.split('.'); + let v2Versions = v2.split('.'); + + for (let i = 0; i < v1Versions.length; i++) { + if (v2Versions.length == i) return versionDefine[i]; + if (v1Versions[i] != v2Versions[i]) return versionDefine[i]; + } + } + return null; + } +} diff --git a/APP/src/utilities/object-utils.ts b/APP/src/utilities/object-utils.ts new file mode 100644 index 0000000..91b2c58 --- /dev/null +++ b/APP/src/utilities/object-utils.ts @@ -0,0 +1,220 @@ +import { Observable } from "rxjs"; + +const dateAndTimeRegex = new RegExp(/^(?\d{4}-\d{2}-\d{2})T(?\d{2}:\d{2}):((?\d{2}\.\d{0,6})|(?\d{2}))$/); + +export class ObjectUtils { + + private static ReviveDateTime(key: any, value: any): any { + if (typeof value === 'string') { + if (dateAndTimeRegex.test(value)) { + let newDate = new Date(value); + return newDate; + } + } + + return value; + } + + public static HasAnyData(obj: any, excludes: string[] = []) { + var hasData = false; + + for (const p in obj) { + if (false == excludes.includes(p) && Object.prototype.hasOwnProperty.call(obj, p)) { + const element = obj[p]; + if (element) { + if (typeof element !== 'object' || this.HasAnyData(element, excludes)) { + hasData = true; + break; + } + } + } + } + + return hasData; + } + public static Clone(obj: T, avoidCirculateRef = false): T { + if (avoidCirculateRef) { + return JSON.parse(this.stringify(obj), ObjectUtils.ReviveDateTime); + } + return JSON.parse(JSON.stringify(obj), ObjectUtils.ReviveDateTime); + } + + public static CopyValue(source: any, destination: any, excludes: string[] = ["id"], overwriting: boolean = true) { + + for (const p in source) { + if (false == excludes.includes(p) && Object.prototype.hasOwnProperty.call(source, p)) { + const element = source[p]; + + if (element && Array.isArray(element)) { + if ([null, undefined].includes(destination[p])) { + destination[p] = []; + for (let i = 0; i < element.length; i++) { + const cloneItem = {}; + this.CopyValue(element[i], cloneItem, excludes, true); + destination[p].push(cloneItem); + } + } else if (Array.isArray(destination[p])) { + + for (let i = 0; i < element.length; i++) { + const item = element[i]; + let destLength = destination[p].length; + if (i >= destLength) { + destination[p].push(this.Clone(source[p][i])); + } + this.CopyValue(item, destination[p][i], excludes, overwriting); + } + } + } + else if (element && typeof element.getMonth === 'function') { + //For angular will treat Date as object + try { + destination[p] = element; + } catch (error) { + console.log(`can\'t copy ${p}`, error); + } + } + else if (element && typeof element == 'object') { + let objectOverwriting = overwriting; + if ([null, undefined].includes(destination[p])) { + destination[p] = {}; + objectOverwriting = true; + } + try { + this.CopyValue(element, destination[p], excludes, objectOverwriting); + } catch (error) { + console.log(`can\'t copy ${p}`, error); + } + } else if (overwriting || [null, '', undefined].includes(destination[p])) { + try { + destination[p] = element; + } catch (error) { + console.log(`can\'t copy ${p}`, error); + } + } + } + } + } + public static ClearPkAndFk(source: any, excludes: string[] = [], clearExtra: string[] = []) { + + for (const p in source) { + const element = source[p]; + + if (element) { + if (clearExtra.includes(p)) { + source[p] = null; + } else if (typeof element == 'object') { + this.ClearPkAndFk(element, excludes, clearExtra); + } else if ( + (typeof element == 'string' && (p == 'id' || p.indexOf('Id') > -1) && + false == excludes.includes(p)) + ) { + source[p] = null; + } + } + } + } + public static ClearValue(source: any, excludes: string[] = ["id"]) { + for (const p in source) { + if (false == excludes.includes(p) && Object.prototype.hasOwnProperty.call(source, p)) { + const element = source[p]; + + if (element) { + if (typeof element == 'object') { + this.ClearValue(element, excludes); + } else if (typeof element == 'number') { + source[p] = 0; + } else { + source[p] = null; + } + } + } + } + } + /** + * return `true` if the comparison has any different value with source. + */ + public static CompareDiffValue(source: any, comparison: any, excludes: string[] = ["id"]) { + let isDifferent = false; + for (const p in source) { + if (false == excludes.includes(p) && + Object.prototype.hasOwnProperty.call(source, p)) { + const element = source[p]; + if (element && Array.isArray(element)) { + if (Array.isArray(comparison[p])) { + for (let i = 0; i < element.length; i++) { + const item = element[i]; + let destLength = comparison[p].length; + if (i >= destLength) { + return true; + } + isDifferent = this.CompareDiffValue(item, comparison[p][i], excludes); + if (isDifferent) return true; + } + } + } + else if (element && typeof element.getMonth === 'function') { + //For angular will treat Date as object + //TODO:Compare Date + //comparison[p] = element; + } + else if (element && typeof element == 'object') { + isDifferent = this.CompareDiffValue(element, comparison[p], excludes); + + } else { + isDifferent = comparison[p] != element; + } + + if (isDifferent) return true; + } + } + return false; + } + /** + * return `true` if the comparison has any different value with source. + */ + public static CompareDiffArrayContent(source: any[], comparison: any[], excludes: string[] = ["id"]) { + let isDifferent = false; + if (source && comparison) { + if (source.length == comparison.length) { + for (let i = 0; i < source.length; i++) { + const sourceItem = source[i]; + const comparisonItem = comparison[i]; + + isDifferent = this.CompareDiffValue(sourceItem, comparisonItem); + if (isDifferent) return true; + } + return false; + } + } + + return true; + } + public static nullCondition(source: T) { + if (source != null) return source; + } + + public static isNullOrUndefined(obj: any) { + return [null, undefined].includes(obj); + } + + public static isObservable(template: T | Observable): template is Observable { + return (template as Observable).subscribe !== undefined; + } + public static stringify(obj) { + let cache = []; + let str = JSON.stringify(obj, function (key, value) { + if (typeof value === "object" && value !== null) { + if (cache.indexOf(value) !== -1) { + // Circular reference found, discard key + return; + } + // Store value in our collection + cache.push(value); + } + return value; + }); + cache = null; // reset the cache + return str; + } + +} diff --git a/APP/src/utilities/storageKeys.ts b/APP/src/utilities/storageKeys.ts new file mode 100644 index 0000000..e12932c --- /dev/null +++ b/APP/src/utilities/storageKeys.ts @@ -0,0 +1,29 @@ +export class LocalStorageKeys { + //public static HOLIDAYS = "HOLIDAYS_FOR_DATE_PICKER"; + + public static NEW_TAB_PROFILE = "LoginPortal_newTabProfile"; + public static ESCROW_RECALL = "LoginPortal_escrowRecalls"; + public static SCREEN_HISTORY = "LoginPortal_screenHistory"; + public static LOGIN_ALL_ACCESS = "LoginPortal_allAccess"; + public static USER_PREFERENCE = "LoginPortal_userPreference"; + public static LAST_ESCROW_REFNDX = "LoginPortal_lastEscrowNo"; + public static COOKIE_ID = "LoginPortal_cookieId"; + public static USER_ACCESS = "LoginPortal_UserAccess"; + //public static DASHBOARD_WIDGETS_SETTING = "DASHBOARD_WIDGETS_SETTING"; +} +export class SessionStorageKeys { + public static SIDE_BAR_SEQ = "LoginPortal_SESSION_SCREEN_SEQ"; + public static ESCROW_TYPE = "LoginPortal_SESSION_ESCROW_TYPE"; + public static USER_PROFILE = "LoginPortal_USER_ACCESS" + public static ESCROW_REFNDX = "LoginPortal_refndx" + public static UI_DISPLAY_FOOTER_BAR = "LoginPortal_displayFooterBar" + public static CURRENT_SCREEN = "LoginPortal_screen" +} +export class CookieKeys { + public static SIDE_BAR_SEQ = "LoginPortal_SESSION_SCREEN_SEQ"; + public static ESCROW_TYPE = "LoginPortal_SESSION_ESCROW_TYPE"; + public static USER_PROFILE = "LoginPortal_USER_ACCESS" + public static ESCROW_REFNDX = "LoginPortal_refndx" + public static UI_DISPLAY_FOOTER_BAR = "LoginPortal_displayFooterBar" + public static CURRENT_SCREEN = "LoginPortal_screen" +} \ No newline at end of file diff --git a/APP/src/utilities/string-utils.ts b/APP/src/utilities/string-utils.ts new file mode 100644 index 0000000..8fa619f --- /dev/null +++ b/APP/src/utilities/string-utils.ts @@ -0,0 +1,171 @@ +import { FtTagType } from "../components/fancy-table/fancy-row-column.model"; + +export class StringUtils { + + static compare(aval: string, bval: string) { + return aval ? aval.localeCompare(bval) : -1; + } + // Sorting function for SemVer + // Returns 1 if a is greater, -1 if b is greater, 0 if equal + public static compareSemVer(a: string, b: string): number { + if (a === b) { + return 0; + } + + var a_components = a.split("."); + var b_components = b.split("."); + + var len = Math.min(a_components.length, b_components.length); + + // loop while the components are equal + for (var i = 0; i < len; i++) { + // A bigger than B + if (parseInt(a_components[i]) > parseInt(b_components[i])) { + return 1; + } + + // B bigger than A + if (parseInt(a_components[i]) < parseInt(b_components[i])) { + return -1; + } + } + + // If one's a prefix of the other, the longer one is greater. + if (a_components.length > b_components.length) { + return 1; + } + + if (a_components.length < b_components.length) { + return -1; + } + + // Otherwise they are the same. + return 0; + } + + public static isNullOrWhitespace(input: string): boolean { + return !input || !input.trim() || 'null' == input; + } + + public static getTrimmedValue(input: string, emptyFormat: string = ''): string { + if (input) { + input = input.trim(); + } + if (input) return input; + return emptyFormat; + } + + + public static firstIsVowel(s: string): boolean { + if (s) { + return ['a', 'e', 'i', 'o', 'u'].indexOf(s[0].toLowerCase()) !== -1 + } + } + + public static makeCommaSeparatedString(arr: string[], useOxfordComma: boolean) { + arr = arr.filter(s => s); + const listStart = arr.slice(0, -1).join(', '); + const listEnd = arr.slice(-1); + const conjunction = arr.length <= 1 ? '' : + useOxfordComma && arr.length > 2 ? ', and ' : ' and '; + + return [listStart, listEnd].join(conjunction); + } + + + + public static getFormattedTerm(input: string, emptyFormatter: string = 'Empty'): string { + if (false == this.isNullOrWhitespace(input)) { + return input.trim(); + } + return emptyFormatter; + } + public static camelToTitle(camelCase) { + // no side-effects + return camelCase + // inject space before the upper case letters + .replace(/([A-Z])/g, function (match) { + return " " + match; + }) + // replace first char with upper case + .replace(/^./, function (match) { + return match.toUpperCase(); + }); + } + + + /** + * status could be `info` , `primary` , `danger` , `warning` , `success` + */ + public static getHtmlBadge(text: string, badgeStatus: FtTagType) { + return ``; + + } + + public static getHtmlInnerText(htmlText: string) { + return htmlText ? htmlText.replace(/<[^>]+>/g, '') : ''; + + let HTML_INNERTEXT_REGEX = />((.|\n*?))<\//g; + + var match = HTML_INNERTEXT_REGEX.exec(htmlText); + if (match) { + return match[1]; + } else { + return htmlText; + } + } + + /** + * Try to parse city sate zip string like `Monrovia, CA 91016` to AddressInfo, if failed will return `null` instead. + */ + // public static tryParseCityStateZip(cityStateZip: string): AddressInfo { + + // let addressInfo = new AddressInfo(); + // var regex = /([\w\s]*),\s*([A-Z]{2})\s*(\d*-?\d*)/g; + // var match = regex.exec(cityStateZip); + // if (match) { + // addressInfo = new AddressInfo(); + // addressInfo.city = match[1]; + // addressInfo.state = match[2]; + // addressInfo.zip = match[3]; + // return addressInfo; + // } else { + // return null; + // } + // } + + // public static getCityStateZipString(addressInfo: AddressInfo): string { + // let result = ''; + // if (false == this.isNullOrWhitespace(addressInfo.city)) { + // result += `${addressInfo.city}, `; + // } + // if (false == this.isNullOrWhitespace(addressInfo.state)) { + // result += `${addressInfo.state} `; + // } + // if (false == this.isNullOrWhitespace(addressInfo.zip)) { + // result += addressInfo.zip; + // } + // return result; + // } + public static toUpperString(prop: any) { + if (prop) { + if (typeof prop === 'string') return prop.toUpperCase(); + prop.toString().toUpperCase(); + } else { + return ''; + } + } + public static truncateString(str: string, maxLength: number) { + if (str && str.length > maxLength) { + return str.slice(0, maxLength); + } else { + return str; + } + } + public static insertAt(str: string, insertValue: string, index: number) { + return [str.slice(0, index), insertValue, str.slice(index)].join(''); + } + public static replaceAll(str: string, find: string, replace: string) { + return str.replace(new RegExp(find, 'g'), replace); + } +} diff --git a/APP/src/utilities/textarea-utils.ts b/APP/src/utilities/textarea-utils.ts new file mode 100644 index 0000000..7cf4a81 --- /dev/null +++ b/APP/src/utilities/textarea-utils.ts @@ -0,0 +1,49 @@ + +export class TextareaUtils { + + public static autoExpand(field: HTMLTextAreaElement) { + // + if (field) { + // Reset field height + field.style.height = '0px'; + const computed = window.getComputedStyle(field); + // Calculate the height + var height = 0 + + parseInt(computed.getPropertyValue('border-top-width'), 10) + + field.scrollHeight + + parseInt(computed.getPropertyValue('border-bottom-width'), 10); + + field.style.height = height + 'px'; + } + } + + public static isEditingKeyPress(e: KeyboardEvent) { + + if ([46, 8, 9, 27, 13, 190].indexOf(e.keyCode) !== -1 || + // Allow: Ctrl+A + (e.keyCode === 65 && (e.ctrlKey || e.metaKey)) || + // Allow: Ctrl+C + (e.keyCode === 67 && (e.ctrlKey || e.metaKey)) || + // Allow: Ctrl+V + (e.keyCode === 86 && (e.ctrlKey || e.metaKey)) || + // Allow: Ctrl+X + (e.keyCode === 88 && (e.ctrlKey || e.metaKey)) || + // Allow: page up, page down, home, end, left, right + (e.keyCode >= 33 && e.keyCode <= 39)) { + // let it happen, don't do anything + return false; + } + + if (typeof e.which == "undefined") { + // This is IE, which only fires keypress events for printable keys + return true; + } else if (e.ctrlKey && e.key.toUpperCase() == 'V') { + return true; + } + else if (!e.ctrlKey && !e.metaKey && !e.altKey && e.code != 'Tab' && e.key != 'Control') { + return true; + } + + return false; + } +} diff --git a/APP/src/utilities/timer-utils.ts b/APP/src/utilities/timer-utils.ts new file mode 100644 index 0000000..1a1385e --- /dev/null +++ b/APP/src/utilities/timer-utils.ts @@ -0,0 +1,51 @@ +export class TimerUtils { + + // private static debounceTimers: DebounceTimer[]; + + // private static addDebounceTimer(key: string, debounceTime: number, callback: Function) { + // if (!this.debounceTimers) { + // this.debounceTimers = []; + // } + // let timerProfile = this.debounceTimers.find(t => t.key == key); + // if (timerProfile) { + // clearTimeout(timerProfile.timer); + // } else { + // timerProfile = new DebounceTimer(){ + + // } + // } + // } + +} + +export class DebounceTimer { + + constructor( + debounceTime: number, + callback: Function + ) { + //this.key = key + this.debounceTime = debounceTime; + this.callback = callback; + //this.resetTimer(); + } + + key: string; + debounceTime: number; + timer: any; + callback: Function; + resetTimer() { + if (this.timer) { + clearTimeout(this.timer); + } + this.timer = setTimeout(() => { + this.callback(); + this.timer = null; + }, this.debounceTime); + } + clearOut() { + if (this.timer) { + clearTimeout(this.timer); + } + } +} \ No newline at end of file diff --git a/APP/src/utilities/uuid-utils.ts b/APP/src/utilities/uuid-utils.ts new file mode 100644 index 0000000..196031a --- /dev/null +++ b/APP/src/utilities/uuid-utils.ts @@ -0,0 +1,18 @@ + +export class UuidUtils { + public static generate() { + var d = new Date().getTime();//Timestamp + var d2 = (performance && performance.now && (performance.now()*1000)) || 0;//Time in microseconds since page-load or 0 if unsupported + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { + var r = Math.random() * 16;//random number between 0 and 16 + if(d > 0){//Use timestamp until depleted + r = (d + r)%16 | 0; + d = Math.floor(d/16); + } else {//Use microseconds since page-load if supported + r = (d2 + r)%16 | 0; + d2 = Math.floor(d2/16); + } + return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16); + }); + } +} \ No newline at end of file diff --git a/APP/tsconfig.app.json b/APP/tsconfig.app.json new file mode 100644 index 0000000..bff9238 --- /dev/null +++ b/APP/tsconfig.app.json @@ -0,0 +1,20 @@ +/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ +/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/app", + "types": [ + "@angular/localize" + ] + }, + "files": [ + "src/main.ts" + ], + "include": [ + "src/**/*.d.ts" + ], + "exclude": [ + "src/**/*.spec.ts" + ] +} diff --git a/APP/tsconfig.json b/APP/tsconfig.json new file mode 100644 index 0000000..5921438 --- /dev/null +++ b/APP/tsconfig.json @@ -0,0 +1,31 @@ +/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ +/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ +{ + "compileOnSave": false, + "compilerOptions": { + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "skipLibCheck": true, + "isolatedModules": true, + "experimentalDecorators": true, + "importHelpers": true, + "target": "ES2022", + "module": "preserve" + }, + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": true, + "strictInputAccessModifiers": true, + "typeCheckHostBindings": true, + "strictTemplates": true + }, + "files": [], + "references": [ + { + "path": "./tsconfig.app.json" + } + ] +} diff --git a/APP/tsconfig.spec.json b/APP/tsconfig.spec.json new file mode 100644 index 0000000..04df34c --- /dev/null +++ b/APP/tsconfig.spec.json @@ -0,0 +1,14 @@ +/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ +/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/spec", + "types": [ + "jasmine" + ] + }, + "include": [ + "src/**/*.ts" + ] +}