diff --git a/front/app/.eslintrc.json b/front/app/.eslintrc.json new file mode 100644 index 00000000..1e1f03f4 --- /dev/null +++ b/front/app/.eslintrc.json @@ -0,0 +1,84 @@ +{ + "root": true, + "ignorePatterns": ["dist"], + "overrides": [ + { + "files": ["*.ts"], + "parserOptions": { + "project": ["tsconfig.json", "e2e/tsconfig.json"], + "createDefaultProgram": true + }, + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended", + "plugin:@angular-eslint/recommended", + "plugin:@angular-eslint/template/process-inline-templates" + ], + "rules": { + "dot-notation": ["warn"], + "max-len": [ + "warn", + { + "code": 100, + "ignoreComments": true, + "ignoreStrings": true, + "ignoreTemplateLiterals": true + } + ], + "object-shorthand": [ + "warn", + "always", + { + "avoidQuotes": true + } + ], + "quote-props": ["warn", "consistent-as-needed"], + "quotes": [ + "warn", + "single", + { + "allowTemplateLiterals": true + } + ], + "semi": ["warn", "always"], + "@typescript-eslint/explicit-module-boundary-types": "off", + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-empty-function": "off", + "@typescript-eslint/no-non-null-assertion": "off", + "@typescript-eslint/no-unused-vars": "off", + "@angular-eslint/component-selector": [ + "error", + { + "type": "element", + "style": "kebab-case" + } + ], + "@angular-eslint/directive-selector": [ + "error", + { + "type": "attribute", + "style": "camelCase" + } + ], + "@angular-eslint/no-empty-lifecycle-method": "off" + } + }, + { + "files": ["*.html"], + "extends": ["plugin:@angular-eslint/template/recommended"], + "rules": {} + }, + { + "files": ["*.js"], + "parserOptions": { + "ecmaVersion": 11 + }, + "env": { + "node": true, + "amd": true + }, + "extends": ["eslint:recommended"], + "rules": {} + } + ] +} diff --git a/front/app/.prettierignore b/front/app/.prettierignore new file mode 100644 index 00000000..03eef2fc --- /dev/null +++ b/front/app/.prettierignore @@ -0,0 +1,3 @@ +# add files you wish to ignore here +dist/ +node_modules/ diff --git a/front/app/.prettierrc b/front/app/.prettierrc new file mode 100644 index 00000000..621831f0 --- /dev/null +++ b/front/app/.prettierrc @@ -0,0 +1,17 @@ +{ + "arrowParens": "avoid", + "bracketSpacing": true, + "htmlWhitespaceSensitivity": "css", + "insertPragma": false, + "jsxBracketSameLine": false, + "jsxSingleQuote": false, + "printWidth": 100, + "proseWrap": "preserve", + "quoteProps": "consistent", + "requirePragma": false, + "semi": true, + "singleQuote": true, + "tabWidth": 2, + "trailingComma": "es5", + "useTabs": false +} diff --git a/front/app/.stylelintrc b/front/app/.stylelintrc new file mode 100644 index 00000000..020b3d2a --- /dev/null +++ b/front/app/.stylelintrc @@ -0,0 +1,39 @@ +{ + "ignoreFiles": [ + "dist/**", + "schematics/**" + ], + "extends": [ + "stylelint-config-standard", + "stylelint-config-recommended-scss", + "stylelint-config-rational-order" + ], + "plugins": [ + "stylelint-order" + ], + "rules": { + "alpha-value-notation": null, + "annotation-no-unknown": null, + "at-rule-empty-line-before": null, + "color-function-notation": "legacy", + "function-no-unknown": null, + "no-descending-specificity": null, + "no-empty-source": null, + "number-leading-zero": "never", + "selector-pseudo-element-no-unknown": [ + true, + { + "ignorePseudoElements": [ + "ng-deep" + ] + } + ], + "selector-class-pattern": null, + "selector-type-no-unknown": null, + "string-quotes": "single", + "value-keyword-case": null, + "scss/at-extend-no-missing-placeholder": null, + "scss/comment-no-empty": null, + "scss/operator-no-unspaced": null + } +} diff --git a/front/app/.vscode/extensions.json b/front/app/.vscode/extensions.json index 77b37457..9023dc68 100644 --- a/front/app/.vscode/extensions.json +++ b/front/app/.vscode/extensions.json @@ -1,4 +1,17 @@ -{ - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846 - "recommendations": ["angular.ng-template"] -} +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. + // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp + + // List of extensions which should be recommended for users of this workspace. + "recommendations": [ + "angular.ng-template", + "cyrilletuzi.angular-schematics", + "apk27.ngx-translate-lookup", + "esbenp.prettier-vscode", + "stylelint.vscode-stylelint", + "syler.sass-indented", + "editorconfig.editorconfig", + "box-of-hats.quick-material-icons", + "mrmlnc.vscode-scss" + ] +} diff --git a/front/app/.vscode/settings.json b/front/app/.vscode/settings.json new file mode 100644 index 00000000..b137fcb9 --- /dev/null +++ b/front/app/.vscode/settings.json @@ -0,0 +1,20 @@ +{ + "editor.rulers": [100], + "html.format.wrapLineLength": 100, + "html.format.wrapAttributes": "preserve-aligned", + "[javascript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[typescript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[jsonc]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "editor.codeActionsOnSave": { + "source.fixAll.eslint": true, + "source.fixAll.stylelint": true + }, + "ngx-translate.lookup.resourcesType": "json", + "ngx-translate.lookup.resourcesPath": "src/assets/i18n/zh-CN.json", +} diff --git a/front/app/LICENSE b/front/app/LICENSE new file mode 100644 index 00000000..8297943e --- /dev/null +++ b/front/app/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Zongbin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/front/app/angular.json b/front/app/angular.json index 00c1c0e2..06b8dc95 100644 --- a/front/app/angular.json +++ b/front/app/angular.json @@ -25,7 +25,7 @@ "src/assets" ], "styles": [ - "@angular/material/prebuilt-themes/deeppurple-amber.css", + "src/styles.scss", "src/styles.css" ], "scripts": [] @@ -36,12 +36,12 @@ { "type": "initial", "maximumWarning": "500kb", - "maximumError": "1mb" + "maximumError": "4mb" }, { "type": "anyComponentStyle", "maximumWarning": "2kb", - "maximumError": "4kb" + "maximumError": "16kb" } ], "outputHashing": "all" @@ -65,6 +65,9 @@ }, "development": { "browserTarget": "app:build:development" + }, + "options": { + "proxyConfig": "proxy.config.js" } }, "defaultConfiguration": "development" @@ -88,11 +91,20 @@ "src/assets" ], "styles": [ - "@angular/material/prebuilt-themes/deeppurple-amber.css", + "src/styles.scss", "src/styles.css" ], "scripts": [] } + }, + "lint": { + "builder": "@angular-eslint/builder:lint", + "options": { + "lintFilePatterns": [ + "src/**/*.ts", + "src/**/*.html" + ] + } } } } diff --git a/front/app/package-lock.json b/front/app/package-lock.json index 38b9dde9..d0f7d771 100644 --- a/front/app/package-lock.json +++ b/front/app/package-lock.json @@ -9,30 +9,60 @@ "version": "0.0.0", "dependencies": { "@angular/animations": "^15.0.0", - "@angular/cdk": "^15.0.4", + "@angular/cdk": "~15.0.3", "@angular/common": "^15.0.0", "@angular/compiler": "^15.0.0", "@angular/core": "^15.0.0", "@angular/forms": "^15.0.0", - "@angular/material": "^15.0.4", + "@angular/material": "~15.0.3", + "@angular/material-moment-adapter": "~15.0.3", "@angular/platform-browser": "^15.0.0", "@angular/platform-browser-dynamic": "^15.0.0", "@angular/router": "^15.0.0", + "@ng-matero/extensions": "^15.0.1", + "@ng-matero/extensions-moment-adapter": "^15.0.0", + "@ngx-formly/core": "^6.0.4", + "@ngx-formly/material": "^6.0.4", + "@ngx-translate/core": "^14.0.0", + "@ngx-translate/http-loader": "^7.0.0", + "moment": "^2.29.4", + "ng-matero": "^15.1.1", + "ngx-permissions": "^14.0.0", + "ngx-progressbar": "^9.0.0", + "ngx-toastr": "^16.0.1", + "photoviewer": "^3.6.6", "rxjs": "~7.5.0", + "screenfull": "^6.0.2", "tslib": "^2.3.0", "zone.js": "~0.12.0" }, "devDependencies": { "@angular-devkit/build-angular": "^15.0.5", + "@angular-eslint/builder": "^15.1.0", + "@angular-eslint/eslint-plugin": "^15.1.0", + "@angular-eslint/eslint-plugin-template": "^15.1.0", + "@angular-eslint/schematics": "^15.1.0", + "@angular-eslint/template-parser": "^15.1.0", "@angular/cli": "~15.0.5", "@angular/compiler-cli": "^15.0.0", "@types/jasmine": "~4.3.0", + "@typescript-eslint/eslint-plugin": "^5.46.0", + "@typescript-eslint/parser": "^5.46.0", + "eslint": "^8.30.0", "jasmine-core": "~4.5.0", "karma": "~6.4.0", "karma-chrome-launcher": "~3.1.0", "karma-coverage": "~2.2.0", "karma-jasmine": "~5.1.0", "karma-jasmine-html-reporter": "~2.0.0", + "parse5": "^7.1.2", + "prettier": "^2.8.1", + "stylelint": "^14.16.0", + "stylelint-config-rational-order": "^0.1.2", + "stylelint-config-recommended-scss": "^8.0.0", + "stylelint-config-standard": "^29.0.0", + "stylelint-order": "^5.0.0", + "stylelint-scss": "^4.3.0", "typescript": "~4.8.2" } }, @@ -326,6 +356,141 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true }, + "node_modules/@angular-eslint/builder": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/builder/-/builder-15.1.0.tgz", + "integrity": "sha512-MoPeJv4a1wSoFj8fVA01hFb+QQke2t74CSVuc6o4EqkWI0tYMM1Wg19fPtTZnj4spkGA82j2mf/tazKGRe/nrw==", + "dev": true, + "peerDependencies": { + "eslint": "^7.20.0 || ^8.0.0", + "typescript": "*" + } + }, + "node_modules/@angular-eslint/bundled-angular-compiler": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-15.1.0.tgz", + "integrity": "sha512-zcOx+PnYuVDIG3wd/JVzCYdEUarKGtgIcN4iU9ZF+BVk5e8i9cbD3U8U3EDJKbrrokbFl9GBBJMCOa6XYTGJwQ==", + "dev": true + }, + "node_modules/@angular-eslint/eslint-plugin": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin/-/eslint-plugin-15.1.0.tgz", + "integrity": "sha512-3RRDnxaCEI5DdKq3hipXvrxctPPssrUXnNbgczJRIJ3cssr4ndobCSNqUSepA6vWj5mWe7w+nnh4vgfhZ5keig==", + "dev": true, + "dependencies": { + "@angular-eslint/utils": "15.1.0", + "@typescript-eslint/utils": "5.44.0" + }, + "peerDependencies": { + "eslint": "^7.20.0 || ^8.0.0", + "typescript": "*" + } + }, + "node_modules/@angular-eslint/eslint-plugin-template": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin-template/-/eslint-plugin-template-15.1.0.tgz", + "integrity": "sha512-WofUNiLcO/oprnzswkF+u1PC6ulmqB/m7fNKMMnbExMYuK1P38gjp59FW7E+2Ivz+A4/8a5xV+U+cy3oRgh4NQ==", + "dev": true, + "dependencies": { + "@angular-eslint/bundled-angular-compiler": "15.1.0", + "@angular-eslint/utils": "15.1.0", + "@typescript-eslint/type-utils": "5.44.0", + "@typescript-eslint/utils": "5.44.0", + "aria-query": "5.1.3", + "axobject-query": "3.1.1" + }, + "peerDependencies": { + "eslint": "^7.20.0 || ^8.0.0", + "typescript": "*" + } + }, + "node_modules/@angular-eslint/schematics": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/schematics/-/schematics-15.1.0.tgz", + "integrity": "sha512-BJm7FFVCad8TV8Gtwq+FbgtLGvjJDlpt5Rne1hCd4nCr8vlQZxSWVwnTHRkAs+qd5dYn3p7bGcKZxEZzeVkWjA==", + "dev": true, + "dependencies": { + "@angular-eslint/eslint-plugin": "15.1.0", + "@angular-eslint/eslint-plugin-template": "15.1.0", + "ignore": "5.2.0", + "strip-json-comments": "3.1.1", + "tmp": "0.2.1" + }, + "peerDependencies": { + "@angular/cli": ">= 15.0.0 < 16.0.0" + } + }, + "node_modules/@angular-eslint/schematics/node_modules/ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/@angular-eslint/schematics/node_modules/tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, + "dependencies": { + "rimraf": "^3.0.0" + }, + "engines": { + "node": ">=8.17.0" + } + }, + "node_modules/@angular-eslint/template-parser": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/template-parser/-/template-parser-15.1.0.tgz", + "integrity": "sha512-ctcA7OAV1wwFByW1te3uZwzySuIRlo8NblG5yUtgU5BXt3nXwIDwoSr3tvI2dRHobNHcXVQcOFVzyOdXD/vsIg==", + "dev": true, + "dependencies": { + "@angular-eslint/bundled-angular-compiler": "15.1.0", + "eslint-scope": "^7.0.0" + }, + "peerDependencies": { + "eslint": "^7.20.0 || ^8.0.0", + "typescript": "*" + } + }, + "node_modules/@angular-eslint/template-parser/node_modules/eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@angular-eslint/template-parser/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/@angular-eslint/utils": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/utils/-/utils-15.1.0.tgz", + "integrity": "sha512-Vt59o7wq3UOgHSCrOaHg0SgxgbAGhG0ofNQwd7sLqNP2/w/90dWY2jwWXIVSuZ+BmfVj3wgNi3KujbSWJP1cfg==", + "dev": true, + "dependencies": { + "@angular-eslint/bundled-angular-compiler": "15.1.0", + "@typescript-eslint/utils": "5.44.0" + }, + "peerDependencies": { + "eslint": "^7.20.0 || ^8.0.0", + "typescript": "*" + } + }, "node_modules/@angular/animations": { "version": "15.0.4", "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-15.0.4.tgz", @@ -356,30 +521,6 @@ "rxjs": "^6.5.3 || ^7.4.0" } }, - "node_modules/@angular/cdk/node_modules/entities": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz", - "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==", - "optional": true, - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/@angular/cdk/node_modules/parse5": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", - "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", - "optional": true, - "dependencies": { - "entities": "^4.4.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, "node_modules/@angular/cli": { "version": "15.0.5", "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-15.0.5.tgz", @@ -586,6 +727,19 @@ "rxjs": "^6.5.3 || ^7.4.0" } }, + "node_modules/@angular/material-moment-adapter": { + "version": "15.0.4", + "resolved": "https://registry.npmjs.org/@angular/material-moment-adapter/-/material-moment-adapter-15.0.4.tgz", + "integrity": "sha512-F1wVCGpplytmuQJrLjghnIZDVzs47n5w8WD5hJTGW5/dD4Bz1AIJE2G2rKGvqiV010ditHvjR6Hf8Ggo0ZIBpg==", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/core": "^15.0.0 || ^16.0.0", + "@angular/material": "15.0.4", + "moment": "^2.18.1" + } + }, "node_modules/@angular/platform-browser": { "version": "15.0.4", "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-15.0.4.tgz", @@ -2392,6 +2546,31 @@ "node": ">=0.1.90" } }, + "node_modules/@csstools/selector-specificity": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.0.2.tgz", + "integrity": "sha512-IkpVW/ehM1hWKln4fCA3NzJU8KwD+kIOvPZA4cqxoJHtE21CCzjyp+Kxbu0i5I4tBNOlXPL9mjwnWlL0VEG4Fg==", + "dev": true, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2", + "postcss-selector-parser": "^6.0.10" + } + }, + "node_modules/@ctrl/tinycolor": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.5.0.tgz", + "integrity": "sha512-tlJpwF40DEQcfR/QF+wNMVyGMaO9FQp6Z1Wahj4Gk3CJQYHwA2xVG7iKDFdW6zuxZY9XWOpGcfNCTsX4McOsOg==", + "engines": { + "node": ">=10" + } + }, "node_modules/@discoveryjs/json-ext": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", @@ -2433,12 +2612,179 @@ "node": ">=12" } }, + "node_modules/@eslint/eslintrc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", + "integrity": "sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.4.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/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, + "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/@eslint/eslintrc/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.19.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz", + "integrity": "sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@eslint/eslintrc/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 + }, + "node_modules/@eslint/eslintrc/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, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/eslintrc/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@gar/promisify": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", "dev": true }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", + "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/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, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -3289,6 +3635,75 @@ "tslib": "^2.1.0" } }, + "node_modules/@mrmlnc/readdir-enhanced": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz", + "integrity": "sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g==", + "dev": true, + "dependencies": { + "call-me-maybe": "^1.0.1", + "glob-to-regexp": "^0.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@mrmlnc/readdir-enhanced/node_modules/glob-to-regexp": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz", + "integrity": "sha512-Iozmtbqv0noj0uDDqoL0zNq0VBEfK2YFoMAZoxJe4cwphvLR+JskfF30QhXHOR4m3KrE6NLRYw+U9MRXvifyig==", + "dev": true + }, + "node_modules/@ng-matero/extensions": { + "version": "15.0.1", + "resolved": "https://registry.npmjs.org/@ng-matero/extensions/-/extensions-15.0.1.tgz", + "integrity": "sha512-MuI+s48H0UEoij1WprcDLhY+FG21FxsXhhNWzcXaktP0ARQenG4PGtYx24blCHYA4wFhXLnGczr8rcf6MLRLJQ==", + "dependencies": { + "@ng-select/ng-select": "^10.0.0", + "ngx-color": "^8.0.0", + "photoviewer": "^3.6.0", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@angular/animations": ">=15.0.0", + "@angular/cdk": ">=15.0.0", + "@angular/common": ">=15.0.0", + "@angular/core": ">=15.0.0", + "@angular/material": ">=15.0.0" + } + }, + "node_modules/@ng-matero/extensions-moment-adapter": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/@ng-matero/extensions-moment-adapter/-/extensions-moment-adapter-15.0.0.tgz", + "integrity": "sha512-NOxAb7vDxQjUznxt8HRSE6STF48sIoL9DgsKpaT+G4yli+pZUPUorMEDPDX760g7z653Sj3v2XmiTCOEr510gw==", + "dependencies": { + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@angular/core": ">=15.0.0", + "@angular/material": ">=15.0.0", + "@angular/material-moment-adapter": ">=15.0.0", + "@ng-matero/extensions": ">=15.0.0", + "moment": "^2.29.4" + } + }, + "node_modules/@ng-select/ng-select": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/@ng-select/ng-select/-/ng-select-10.0.3.tgz", + "integrity": "sha512-Ma8pzKMI5TWnsKgOiONQLGeUeTko9gI6AtqpMMOVhrCktjtUSo9h5N17WomHRGtba9D7QviTZcR7UBhKOPwZ7g==", + "dependencies": { + "tslib": "^2.3.1" + }, + "engines": { + "node": ">= 12.20.0", + "npm": ">= 6.0.0" + }, + "peerDependencies": { + "@angular/common": "<16.0.0", + "@angular/core": "<16.0.0", + "@angular/forms": "<16.0.0" + } + }, "node_modules/@ngtools/webpack": { "version": "15.0.5", "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-15.0.5.tgz", @@ -3305,6 +3720,55 @@ "webpack": "^5.54.0" } }, + "node_modules/@ngx-formly/core": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@ngx-formly/core/-/core-6.0.4.tgz", + "integrity": "sha512-GH+uIwNivI7EmqIRcGwyWa+CXPBY3ihWi05foMsYV7ghADe2Ap6mHaYdKoejMItLoWz79CrV2pYfwrnjlE3KOQ==", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@angular/forms": ">=13.2.0", + "rxjs": "^6.5.3 || ^7.0.0" + } + }, + "node_modules/@ngx-formly/material": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@ngx-formly/material/-/material-6.0.4.tgz", + "integrity": "sha512-eJjLcR7Kueqg9KfyaPEjQADaF5qB4M4TrNgY0SJ6SWnJjE1hWQWcLvwKxo/7EX93ClD/1Z3ytZMRk1homPK8HA==", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@angular/material": ">=13.0.0", + "@ngx-formly/core": "6.0.4" + } + }, + "node_modules/@ngx-translate/core": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@ngx-translate/core/-/core-14.0.0.tgz", + "integrity": "sha512-UevdwNCXMRCdJv//0kC8h2eSfmi02r29xeE8E9gJ1Al4D4jEJ7eiLPdjslTMc21oJNGguqqWeEVjf64SFtvw2w==", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/core": ">=13.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@ngx-translate/http-loader": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@ngx-translate/http-loader/-/http-loader-7.0.0.tgz", + "integrity": "sha512-j+NpXXlcGVdyUNyY/qsJrqqeAdJdizCd+GKh3usXExSqy1aE9866jlAIL+xrfDU4w+LiMoma5pgE4emvFebZmA==", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/common": ">=13.0.0", + "@ngx-translate/core": ">=14.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -3659,6 +4123,16 @@ "@types/range-parser": "*" } }, + "node_modules/@types/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==", + "dev": true, + "dependencies": { + "@types/minimatch": "*", + "@types/node": "*" + } + }, "node_modules/@types/http-proxy": { "version": "1.17.9", "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.9.tgz", @@ -3686,12 +4160,30 @@ "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==", "dev": true }, + "node_modules/@types/minimatch": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", + "dev": true + }, + "node_modules/@types/minimist": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", + "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==", + "dev": true + }, "node_modules/@types/node": { "version": "18.11.18", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz", "integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==", "dev": true }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", + "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", + "dev": true + }, "node_modules/@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", @@ -3716,6 +4208,12 @@ "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", "dev": true }, + "node_modules/@types/semver": { + "version": "7.3.13", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", + "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", + "dev": true + }, "node_modules/@types/serve-index": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.1.tgz", @@ -3744,6 +4242,33 @@ "@types/node": "*" } }, + "node_modules/@types/unist": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz", + "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==", + "dev": true + }, + "node_modules/@types/vfile": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/vfile/-/vfile-3.0.2.tgz", + "integrity": "sha512-b3nLFGaGkJ9rzOcuXRfHkZMdjsawuDD0ENL9fzTophtBg8FJHSGbH7daXkEpcwy3v7Xol3pAvsmlYyFhR4pqJw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/unist": "*", + "@types/vfile-message": "*" + } + }, + "node_modules/@types/vfile-message": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/vfile-message/-/vfile-message-2.0.0.tgz", + "integrity": "sha512-GpTIuDpb9u4zIO165fUy9+fXcULdD8HFRNli04GehoMVbeNq7D6OBnqSmg3lxZnC+UvgUhEWKxdKiwYUkGltIw==", + "deprecated": "This is a stub types definition. vfile-message provides its own type definitions, so you do not need this installed.", + "dev": true, + "dependencies": { + "vfile-message": "*" + } + }, "node_modules/@types/ws": { "version": "8.5.4", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.4.tgz", @@ -3753,6 +4278,464 @@ "@types/node": "*" } }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.48.0.tgz", + "integrity": "sha512-SVLafp0NXpoJY7ut6VFVUU9I+YeFsDzeQwtK0WZ+xbRN3mtxJ08je+6Oi2N89qDn087COdO0u3blKZNv9VetRQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.48.0", + "@typescript-eslint/type-utils": "5.48.0", + "@typescript-eslint/utils": "5.48.0", + "debug": "^4.3.4", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "regexpp": "^3.2.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/type-utils": { + "version": "5.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.48.0.tgz", + "integrity": "sha512-vbtPO5sJyFjtHkGlGK4Sthmta0Bbls4Onv0bEqOGm7hP9h8UpRsHJwsrCiWtCUndTRNQO/qe6Ijz9rnT/DB+7g==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "5.48.0", + "@typescript-eslint/utils": "5.48.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils": { + "version": "5.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.48.0.tgz", + "integrity": "sha512-x2jrMcPaMfsHRRIkL+x96++xdzvrdBCnYRd5QiW5Wgo1OB4kDYPbC1XjWP/TNqlfK93K/lUL92erq5zPLgFScQ==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.48.0", + "@typescript-eslint/types": "5.48.0", + "@typescript-eslint/typescript-estree": "5.48.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "5.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.48.0.tgz", + "integrity": "sha512-1mxNA8qfgxX8kBvRDIHEzrRGrKHQfQlbW6iHyfHYS0Q4X1af+S6mkLNtgCOsGVl8+/LUPrqdHMssAemkrQ01qg==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.48.0", + "@typescript-eslint/types": "5.48.0", + "@typescript-eslint/typescript-estree": "5.48.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.48.0.tgz", + "integrity": "sha512-0AA4LviDtVtZqlyUQnZMVHydDATpD9SAX/RC5qh6cBd3xmyWvmXYF+WT1oOmxkeMnWDlUVTwdODeucUnjz3gow==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.48.0", + "@typescript-eslint/visitor-keys": "5.48.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "5.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.44.0.tgz", + "integrity": "sha512-A1u0Yo5wZxkXPQ7/noGkRhV4J9opcymcr31XQtOzcc5nO/IHN2E2TPMECKWYpM3e6olWEM63fq/BaL1wEYnt/w==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "5.44.0", + "@typescript-eslint/utils": "5.44.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": { + "version": "5.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.44.0.tgz", + "integrity": "sha512-Tp+zDnHmGk4qKR1l+Y1rBvpjpm5tGXX339eAlRBDg+kgZkz9Bw+pqi4dyseOZMsGuSH69fYfPJCBKBrbPCxYFQ==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": { + "version": "5.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.44.0.tgz", + "integrity": "sha512-M6Jr+RM7M5zeRj2maSfsZK2660HKAJawv4Ud0xT+yauyvgrsHu276VtXlKDFnEmhG+nVEd0fYZNXGoAgxwDWJw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.44.0", + "@typescript-eslint/visitor-keys": "5.44.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": { + "version": "5.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.44.0.tgz", + "integrity": "sha512-a48tLG8/4m62gPFbJ27FxwCOqPKxsb8KC3HkmYoq2As/4YyjQl1jDbRr1s63+g4FS/iIehjmN3L5UjmKva1HzQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.44.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.48.0.tgz", + "integrity": "sha512-UTe67B0Ypius0fnEE518NB2N8gGutIlTojeTg4nt0GQvikReVkurqxd2LvYa9q9M5MQ6rtpNyWTBxdscw40Xhw==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.48.0.tgz", + "integrity": "sha512-7pjd94vvIjI1zTz6aq/5wwE/YrfIyEPLtGJmRfyNR9NYIW+rOvzzUv3Cmq2hRKpvt6e9vpvPUQ7puzX7VSmsEw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.48.0", + "@typescript-eslint/visitor-keys": "5.48.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.44.0.tgz", + "integrity": "sha512-fMzA8LLQ189gaBjS0MZszw5HBdZgVwxVFShCO3QN+ws3GlPkcy9YuS3U4wkT6su0w+Byjq3mS3uamy9HE4Yfjw==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.44.0", + "@typescript-eslint/types": "5.44.0", + "@typescript-eslint/typescript-estree": "5.44.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/scope-manager": { + "version": "5.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.44.0.tgz", + "integrity": "sha512-2pKml57KusI0LAhgLKae9kwWeITZ7IsZs77YxyNyIVOwQ1kToyXRaJLl+uDEXzMN5hnobKUOo2gKntK9H1YL8g==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.44.0", + "@typescript-eslint/visitor-keys": "5.44.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": { + "version": "5.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.44.0.tgz", + "integrity": "sha512-Tp+zDnHmGk4qKR1l+Y1rBvpjpm5tGXX339eAlRBDg+kgZkz9Bw+pqi4dyseOZMsGuSH69fYfPJCBKBrbPCxYFQ==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": { + "version": "5.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.44.0.tgz", + "integrity": "sha512-M6Jr+RM7M5zeRj2maSfsZK2660HKAJawv4Ud0xT+yauyvgrsHu276VtXlKDFnEmhG+nVEd0fYZNXGoAgxwDWJw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.44.0", + "@typescript-eslint/visitor-keys": "5.44.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/visitor-keys": { + "version": "5.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.44.0.tgz", + "integrity": "sha512-a48tLG8/4m62gPFbJ27FxwCOqPKxsb8KC3HkmYoq2As/4YyjQl1jDbRr1s63+g4FS/iIehjmN3L5UjmKva1HzQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.44.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.48.0.tgz", + "integrity": "sha512-5motVPz5EgxQ0bHjut3chzBkJ3Z3sheYVcSwS5BpHZpLqSptSmELNtGixmgj65+rIfhvtQTz5i9OP2vtzdDH7Q==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.48.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@webassemblyjs/ast": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", @@ -3963,6 +4946,15 @@ "acorn": "^8" } }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, "node_modules/adjust-sourcemap-loader": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-4.0.0.tgz", @@ -4181,12 +5173,123 @@ "sprintf-js": "~1.0.2" } }, + "node_modules/aria-query": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", + "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", + "dev": true, + "dependencies": { + "deep-equal": "^2.0.5" + } + }, + "node_modules/arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-find-index": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", + "integrity": "sha512-M1HQyIXcBGtVywBt8WVdim+lrNaK7VHp99Qt5pSNziXznKHViIBbXWtfRTpEFpF/c4FdfxNAsCCwPp5phBYJtw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/array-flatten": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", "dev": true }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true, + "bin": { + "atob": "bin/atob.js" + }, + "engines": { + "node": ">= 4.5.0" + } + }, "node_modules/autoprefixer": { "version": "10.4.13", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.13.tgz", @@ -4220,6 +5323,27 @@ "postcss": "^8.1.0" } }, + "node_modules/available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axobject-query": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.1.1.tgz", + "integrity": "sha512-goKlv8DZrK9hUh975fnHzhNIO4jUnFCfv/dszV5VwUGDFjI6vQ2VwoyjYjYNEbBE8AH87TduWP5uyDR1D+Iteg==", + "dev": true, + "dependencies": { + "deep-equal": "^2.0.5" + } + }, "node_modules/babel-loader": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.0.tgz", @@ -4301,12 +5425,52 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/bail": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz", + "integrity": "sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "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==", "dev": true }, + "node_modules/base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "dependencies": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -4558,6 +5722,26 @@ "node": ">=12" } }, + "node_modules/cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "dependencies": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", @@ -4571,6 +5755,45 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/call-me-maybe": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", + "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==", + "dev": true + }, + "node_modules/caller-callsite": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", + "integrity": "sha512-JuG3qI4QOftFsZyOn1qq87fq5grLIyk1JYd5lJmdA+fG7aQ9pA/i3JIJGcO3q0MrRcHlOt1U+ZeHW8Dq9axALQ==", + "dev": true, + "dependencies": { + "callsites": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/caller-callsite/node_modules/callsites": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "integrity": "sha512-ksWePWBloaWPxJYQ8TL0JHvtci6G5QTKwQ95RcWAa/lzoAKuAOflGdAK92hpHXjkwb8zLxoLNUoNYZgVsaJzvQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/caller-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", + "integrity": "sha512-MCL3sf6nCSXOwCTzvPKhN18TU7AHTvdtam8DAogxcrJ8Rjfbbg7Lgng64H9Iy+vUV6VGFClN/TyxBkAebLRR4A==", + "dev": true, + "dependencies": { + "caller-callsite": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -4589,6 +5812,23 @@ "node": ">=6" } }, + "node_modules/camelcase-keys": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", + "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "map-obj": "^4.0.0", + "quick-lru": "^4.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001442", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001442.tgz", @@ -4605,6 +5845,16 @@ } ] }, + "node_modules/ccount": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-1.1.0.tgz", + "integrity": "sha512-vlNK021QdI7PNeiUh/lKkC/mNHHfV0m/Ad5JoI0TYtlBnJAslM/JIkm/tGC88bkLIwO6OQ5uV6ztS6kVAtCDlg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -4619,6 +5869,46 @@ "node": ">=4" } }, + "node_modules/character-entities": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", + "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-1.1.4.tgz", + "integrity": "sha512-HRcDxZuZqMx3/a+qrzxdBKBPUpxWEq9xw2OPZ3a/174ihfrQKVsFhqtthBInFy1zZ9GgZyFXOatNujm8M+El3g==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", + "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", + "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chardet": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", @@ -4670,6 +5960,110 @@ "node": ">=6.0" } }, + "node_modules/class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "dependencies": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/is-accessor-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "node_modules/class-utils/node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/is-data-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", @@ -4749,6 +6143,42 @@ "node": ">=6" } }, + "node_modules/clone-regexp": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/clone-regexp/-/clone-regexp-1.0.1.tgz", + "integrity": "sha512-Fcij9IwRW27XedRIJnSOEupS7RVcXtObJXbcUOX93UCLqqOdRpkvzKywOOSizmEK/Is3S/RHX9dLdfo6R1Q1mw==", + "dev": true, + "dependencies": { + "is-regexp": "^1.0.0", + "is-supported-regexp-flag": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/collapse-white-space": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-1.0.6.tgz", + "integrity": "sha512-jEovNnrhMuqyCcjfEJA56v0Xq8SkIoPKDyaHahwo3POf4qcSXqMYuwNcOTzp74vTsR9Tn08z4MxWqAhcekogkQ==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw==", + "dev": true, + "dependencies": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -4773,6 +6203,12 @@ "color-support": "bin.js" } }, + "node_modules/colord": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", + "dev": true + }, "node_modules/colorette": { "version": "2.0.19", "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", @@ -4791,6 +6227,12 @@ "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", "dev": true }, + "node_modules/component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true + }, "node_modules/compressible": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", @@ -4956,6 +6398,15 @@ "url": "https://github.com/sponsors/mesqueeb" } }, + "node_modules/copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/copy-webpack-plugin": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz", @@ -5112,6 +6563,12 @@ "node": ">=8" } }, + "node_modules/critters/node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + }, "node_modules/critters/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -5153,6 +6610,15 @@ "node": ">= 8" } }, + "node_modules/css-functions-list": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.1.0.tgz", + "integrity": "sha512-/9lCvYZaUbBGvYUgYGFJ4dcYiyqdhSjG7IPVluoV8A1ILjkF7ilmhp1OGUz8n+nmBcu0RNrQAzgD8B6FJbrt2w==", + "dev": true, + "engines": { + "node": ">=12.22" + } + }, "node_modules/css-loader": { "version": "6.7.3", "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.3.tgz", @@ -5219,6 +6685,18 @@ "node": ">=4" } }, + "node_modules/currently-unhandled": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", + "integrity": "sha512-/fITjgjGU50vjQ4FH6eUoYu+iUoUKIXws2hL15JJpIR+BbTxaXQsMuuyjtNh2WqsSBS5nsaZHFsFecyw5CCAng==", + "dev": true, + "dependencies": { + "array-find-index": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/custom-event": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", @@ -5251,6 +6729,89 @@ } } }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decamelize-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz", + "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==", + "dev": true, + "dependencies": { + "decamelize": "^1.1.0", + "map-obj": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decamelize-keys/node_modules/map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decode-uri-component": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/deep-equal": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.0.tgz", + "integrity": "sha512-RdpzE0Hv4lhowpIUKKMJfeH6C1pXdtT1/it80ubgWqwI3qpuxUBpC1S4hnHg+zjnuOoDkzUtUCEEkG+XG5l3Mw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "es-get-iterator": "^1.1.2", + "get-intrinsic": "^1.1.3", + "is-arguments": "^1.1.1", + "is-array-buffer": "^3.0.1", + "is-date-object": "^1.0.5", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "isarray": "^2.0.5", + "object-is": "^1.1.5", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "side-channel": "^1.0.4", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.9" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/deep-equal/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, "node_modules/default-gateway": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", @@ -5284,6 +6845,35 @@ "node": ">=8" } }, + "node_modules/define-properties": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", + "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", + "dev": true, + "dependencies": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", @@ -5360,6 +6950,18 @@ "node": ">=6" } }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/dom-serialize": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", @@ -5413,6 +7015,11 @@ "url": "https://github.com/fb55/domhandler?sponsor=1" } }, + "node_modules/domq.js": { + "version": "0.6.7", + "resolved": "https://registry.npmjs.org/domq.js/-/domq.js-0.6.7.tgz", + "integrity": "sha512-WwRGORo/eYGf7v7YXZ3M6x/PEoxCsP3D0my7pnAVwtbfsKRvW6qioSdlLsy1MFzfwN1TM9oO9QJcvkE8ERYmlg==" + }, "node_modules/domutils": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", @@ -5427,6 +7034,18 @@ "url": "https://github.com/fb55/domutils?sponsor=1" } }, + "node_modules/dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "dev": true, + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -5581,6 +7200,31 @@ "is-arrayish": "^0.2.1" } }, + "node_modules/es-get-iterator": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.2.tgz", + "integrity": "sha512-+DTO8GYwbMCwbywjimwZMHp8AuYXOS2JZFWoi2AlPOS3ebnII9w/NLpNZtA7A0YLaVDw+O7KFCeoIV7OPvM7hQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.0", + "has-symbols": "^1.0.1", + "is-arguments": "^1.1.0", + "is-map": "^2.0.2", + "is-set": "^2.0.2", + "is-string": "^1.0.5", + "isarray": "^2.0.5" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-get-iterator/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, "node_modules/es-module-lexer": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", @@ -5981,6 +7625,62 @@ "node": ">=0.8.0" } }, + "node_modules/eslint": { + "version": "8.31.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.31.0.tgz", + "integrity": "sha512-0tQQEVdmPZ1UtUKXjX7EMm9BlgJ08G90IhWh0PKDCb3ZLsgAOHI8fYSIzYVZej92zsgq+ft0FGsxhJ3xo2tbuA==", + "dev": true, + "dependencies": { + "@eslint/eslintrc": "^1.4.1", + "@humanwhocodes/config-array": "^0.11.8", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.4.0", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-sdsl": "^4.1.4", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", @@ -5994,6 +7694,325 @@ "node": ">=8.0.0" } }, + "node_modules/eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint/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, + "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/eslint/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/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==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eslint/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==", + "dev": true + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.19.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz", + "integrity": "sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/eslint/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 + }, + "node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/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, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/espree": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", + "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==", + "dev": true, + "dependencies": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", @@ -6007,6 +8026,27 @@ "node": ">=4" } }, + "node_modules/esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, "node_modules/esrecurse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", @@ -6099,6 +8139,161 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, + "node_modules/execall": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execall/-/execall-1.0.0.tgz", + "integrity": "sha512-/J0Q8CvOvlAdpvhfkD/WnTQ4H1eU0exze2nFGPj/RSC7jpQ0NkKe2r28T5eMkhEEs+fzepMZNy1kVRKNlC04nQ==", + "dev": true, + "dependencies": { + "clone-regexp": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==", + "dev": true, + "dependencies": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/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, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/expand-brackets/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/is-accessor-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "node_modules/expand-brackets/node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/is-data-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, "node_modules/express": { "version": "4.18.2", "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", @@ -6204,6 +8399,19 @@ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "dev": true }, + "node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "dev": true, + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/external-editor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", @@ -6218,6 +8426,58 @@ "node": ">=4" } }, + "node_modules/extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "dependencies": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -6246,6 +8506,21 @@ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, + "engines": { + "node": ">= 4.9.1" + } + }, "node_modules/fastq": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", @@ -6282,6 +8557,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -6369,6 +8656,19 @@ "node": ">=8" } }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, "node_modules/flatted": { "version": "3.2.7", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", @@ -6395,6 +8695,24 @@ } } }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -6417,6 +8735,18 @@ "url": "https://www.patreon.com/infusion" } }, + "node_modules/fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==", + "dev": true, + "dependencies": { + "map-cache": "^0.2.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", @@ -6484,6 +8814,15 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/gauge": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", @@ -6544,6 +8883,15 @@ "node": ">=8.0.0" } }, + "node_modules/get-stdin": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", + "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", @@ -6556,6 +8904,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/glob": { "version": "8.0.3", "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", @@ -6593,6 +8950,38 @@ "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", "dev": true }, + "node_modules/global-modules": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", + "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", + "dev": true, + "dependencies": { + "global-prefix": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/global-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", + "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", + "dev": true, + "dependencies": { + "ini": "^1.3.5", + "kind-of": "^6.0.2", + "which": "^1.3.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/global-prefix/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, "node_modules/globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", @@ -6621,18 +9010,66 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/globjoin": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/globjoin/-/globjoin-0.1.4.tgz", + "integrity": "sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==", + "dev": true + }, + "node_modules/gonzales-pe": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/gonzales-pe/-/gonzales-pe-4.3.0.tgz", + "integrity": "sha512-otgSPpUmdWJ43VXyiNgEYE4luzHCL2pz4wQ0OnDluC6Eg4Ko3Vexy/SrSynglw/eR+OhkzmqFCZa/OFa/RgAOQ==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "gonzales": "bin/gonzales.js" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/graceful-fs": { "version": "4.2.10", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", "dev": true }, + "node_modules/grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, "node_modules/handle-thing": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", "dev": true }, + "node_modules/hard-rejection": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", + "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -6645,6 +9082,15 @@ "node": ">= 0.4.0" } }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -6654,6 +9100,18 @@ "node": ">=4" } }, + "node_modules/has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-symbols": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", @@ -6666,12 +9124,96 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-unicode": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", "dev": true }, + "node_modules/has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw==", + "dev": true, + "dependencies": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ==", + "dev": true, + "dependencies": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values/node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "node_modules/has-values/node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values/node_modules/is-number/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values/node_modules/kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/hdr-histogram-js": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/hdr-histogram-js/-/hdr-histogram-js-2.0.3.tgz", @@ -6764,6 +9306,94 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, + "node_modules/html-tags": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.2.0.tgz", + "integrity": "sha512-vy7ClnArOZwCnqZgvv+ddgHgJiAFXe3Ge9ML5/mBctVJoUoYPCdxVucOywjDARn6CVoh3dRSFdPHy2sX80L0Wg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/htmlparser2": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", + "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", + "dev": true, + "dependencies": { + "domelementtype": "^1.3.1", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "inherits": "^2.0.1", + "readable-stream": "^3.1.1" + } + }, + "node_modules/htmlparser2/node_modules/dom-serializer": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", + "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", + "dev": true, + "dependencies": { + "domelementtype": "^2.0.1", + "entities": "^2.0.0" + } + }, + "node_modules/htmlparser2/node_modules/dom-serializer/node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/htmlparser2/node_modules/dom-serializer/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/htmlparser2/node_modules/domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", + "dev": true + }, + "node_modules/htmlparser2/node_modules/domhandler": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", + "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "dev": true, + "dependencies": { + "domelementtype": "1" + } + }, + "node_modules/htmlparser2/node_modules/domutils": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", + "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", + "dev": true, + "dependencies": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", + "dev": true + }, "node_modules/http-cache-semantics": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", @@ -6999,6 +9629,15 @@ "node": ">=4" } }, + "node_modules/import-lazy": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz", + "integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -7017,6 +9656,12 @@ "node": ">=8" } }, + "node_modules/indexes-of": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz", + "integrity": "sha512-bup+4tap3Hympa+JBJUG7XuOsdNQ6fxt0MHyXMKuLBKn0OqsTfvUxkUrroEX1+B2VsSHvCjiIcZVxRtYa4nllA==", + "dev": true + }, "node_modules/infer-owner": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", @@ -7159,12 +9804,99 @@ "node": ">= 10" } }, + "node_modules/is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-alphabetical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", + "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumeric": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-alphanumeric/-/is-alphanumeric-1.0.0.tgz", + "integrity": "sha512-ZmRL7++ZkcMOfDuWZuMJyIVLr2keE1o/DeNWh1EmgqGhUcV+9BIVsx0BcSBOHTZqzjs4+dISzr2KAeBEWGgXeA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-alphanumerical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", + "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", + "dev": true, + "dependencies": { + "is-alphabetical": "^1.0.0", + "is-decimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.1.tgz", + "integrity": "sha512-ASfLknmY8Xa2XtB4wmbz13Wu202baeA18cJBCeCy0wXUHZF0IPyVEXqKEcd+t2fNSLLL1vC6k7lxZEojNbISXQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-typed-array": "^1.1.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "dev": true }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -7177,6 +9909,57 @@ "node": ">=8" } }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "engines": { + "node": ">=4" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-core-module": { "version": "2.11.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", @@ -7189,6 +9972,66 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-decimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", + "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-directory": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", + "integrity": "sha512-yVChGzahRFvbkscn2MlwGismPO12i9+znNruC5gVEntG3qu0xQMzsGg/JFbrsqDOHtHFPci+V5aP5T9I+yeKqw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-docker": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", @@ -7204,6 +10047,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -7234,6 +10089,16 @@ "node": ">=0.10.0" } }, + "node_modules/is-hexadecimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", + "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-interactive": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", @@ -7249,6 +10114,15 @@ "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", "dev": true }, + "node_modules/is-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", + "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -7258,6 +10132,39 @@ "node": ">=0.12.0" } }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/is-plain-obj": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", @@ -7282,6 +10189,52 @@ "node": ">=0.10.0" } }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", + "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-set": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", + "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -7294,6 +10247,64 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-supported-regexp-flag": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-supported-regexp-flag/-/is-supported-regexp-flag-1.0.1.tgz", + "integrity": "sha512-3vcJecUUrpgCqc/ca0aWeNu64UGgxcvO60K/Fkr1N6RSvfGCTU60UKN68JDmKokgba0rFFJs12EnzOQa14ubKQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", + "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-unicode-supported": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", @@ -7306,12 +10317,63 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-weakmap": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", + "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", + "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-what": { "version": "3.14.1", "resolved": "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz", "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==", "dev": true }, + "node_modules/is-whitespace-character": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-whitespace-character/-/is-whitespace-character-1.0.4.tgz", + "integrity": "sha512-SDweEzfIZM0SJV0EUga669UTKlmL0Pq8Lno0QDQsPnvECB3IM2aP0gdx5TrU0A01MAPfViaZiI2V1QMZLaKK5w==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-word-character": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-word-character/-/is-word-character-1.0.4.tgz", + "integrity": "sha512-5SMO8RVennx3nZrqtKwCGyyetPE9VDba5ugvKLaD4KopPG5kR4mQ7tNt/r7feL5yt5h3lpuBbIUmCOG2eSzXHA==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-wsl": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", @@ -7506,6 +10568,16 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/js-sdsl": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.2.0.tgz", + "integrity": "sha512-dyBIzQBDkCqCu+0upx25Y2jGdbTGxE9fshMsCdK0ViOongpV+n5tXRcZY9v7CaVQ79AGS9KA1KHtojxiM7aXSQ==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -7537,6 +10609,12 @@ "node": ">=4" } }, + "node_modules/json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", @@ -7549,6 +10627,12 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -7825,6 +10909,12 @@ "node": ">= 8" } }, + "node_modules/known-css-properties": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.26.0.tgz", + "integrity": "sha512-5FZRzrZzNTBruuurWpvZnvP9pum+fe0HcK8z/ooo+U+Hmp4vtbyp1/QDsqmufirXy4egGzbaH/y2uCZf+6W5Kg==", + "dev": true + }, "node_modules/less": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/less/-/less-4.1.3.tgz", @@ -7918,6 +11008,28 @@ "node": ">=0.10.0" } }, + "node_modules/leven": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", + "integrity": "sha512-nvVPLpIHUxCUoRLrFqTgSxXJ614d8AgQoWl7zPe/2VadE8+1dpU3LBhowRuBAcuwruWtOdD8oYC9jDNJjXDPyA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/license-webpack-plugin": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/license-webpack-plugin/-/license-webpack-plugin-4.0.2.tgz", @@ -7941,6 +11053,43 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "dev": true }, + "node_modules/load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/load-json-file/node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "dev": true, + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/load-json-file/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/loader-runner": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", @@ -7983,6 +11132,18 @@ "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", "dev": true }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", + "dev": true + }, "node_modules/log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -8085,6 +11246,29 @@ "node": ">=8.0" } }, + "node_modules/longest-streak": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-2.0.4.tgz", + "integrity": "sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/loud-rejection": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", + "integrity": "sha512-RPNliZOFkqFumDhvYqOaNY4Uz9oJM2K9tC6JWsJJsNdhuONW4LQHRBpb0qf4pJApVffI5N39SwzWZJuEhfd7eQ==", + "dev": true, + "dependencies": { + "currently-unhandled": "^0.4.1", + "signal-exit": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -8256,6 +11440,83 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, + "node_modules/map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/map-obj": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", + "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==", + "dev": true, + "dependencies": { + "object-visit": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/markdown-escapes": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/markdown-escapes/-/markdown-escapes-1.0.4.tgz", + "integrity": "sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/markdown-table": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-1.1.3.tgz", + "integrity": "sha512-1RUZVgQlpJSPWYbFSpmudq5nHY1doEIv89gBtF0s4gW1GF2XorxcA/70M5vq7rLv0a6mhOUccRsqkwhwLCIQ2Q==", + "dev": true + }, + "node_modules/material-colors": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/material-colors/-/material-colors-1.2.6.tgz", + "integrity": "sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg==" + }, + "node_modules/mathml-tag-names": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz", + "integrity": "sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/mdast-util-compact": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mdast-util-compact/-/mdast-util-compact-1.0.4.tgz", + "integrity": "sha512-3YDMQHI5vRiS2uygEFYaqckibpJtKq5Sj2c8JioeOQBU6INpKbdWzfyLqFFnDwEcEnRFIdMsguzs5pC1Jp4Isg==", + "dev": true, + "dependencies": { + "unist-util-visit": "^1.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -8277,6 +11538,98 @@ "node": ">= 4.0.0" } }, + "node_modules/meow": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz", + "integrity": "sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==", + "dev": true, + "dependencies": { + "@types/minimist": "^1.2.0", + "camelcase-keys": "^6.2.2", + "decamelize": "^1.2.0", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.1.0", + "minimist-options": "4.1.0", + "normalize-package-data": "^3.0.0", + "read-pkg-up": "^7.0.1", + "redent": "^3.0.0", + "trim-newlines": "^3.0.0", + "type-fest": "^0.18.0", + "yargs-parser": "^20.2.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/meow/node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/meow/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/meow/node_modules/normalize-package-data": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", + "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^4.0.1", + "is-core-module": "^2.5.0", + "semver": "^7.3.4", + "validate-npm-package-license": "^3.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/meow/node_modules/type-fest": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", + "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/meow/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 + }, + "node_modules/meow/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, + "engines": { + "node": ">=10" + } + }, "node_modules/merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", @@ -8362,6 +11715,15 @@ "node": ">=6" } }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/mini-css-extract-plugin": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.6.1.tgz", @@ -8408,6 +11770,29 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minimist-options": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", + "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", + "dev": true, + "dependencies": { + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0", + "kind-of": "^6.0.3" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/minimist-options/node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/minipass": { "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", @@ -8520,6 +11905,19 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, + "node_modules/mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "dev": true, + "dependencies": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/mkdirp": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", @@ -8532,6 +11930,14 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/moment": { + "version": "2.29.4", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", + "engines": { + "node": "*" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -8569,6 +11975,40 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dev": true, + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, "node_modules/needle": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/needle/-/needle-3.2.0.tgz", @@ -8625,6 +12065,66 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, + "node_modules/ng-matero": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/ng-matero/-/ng-matero-15.1.1.tgz", + "integrity": "sha512-wCyFN1dgsRUWTRw6czEc6uSHikbray4fdiwX2B0BmtqZ2sxxAJ5SkO6h0oehpar4i4hoUcFE50AReSnBdQ4wDw==" + }, + "node_modules/ngx-color": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/ngx-color/-/ngx-color-8.0.3.tgz", + "integrity": "sha512-tuLP+uIoDEu2m0bh711kb2P1M1bh/oIrOn8mJd9mb8xGL2v+OcokcxPmVvWRn0avMG1lXL53CjSlWXGkdV4CDA==", + "dependencies": { + "@ctrl/tinycolor": "^3.4.1", + "material-colors": "^1.2.6", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/common": ">=14.0.0-0", + "@angular/core": ">=14.0.0-0" + } + }, + "node_modules/ngx-permissions": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/ngx-permissions/-/ngx-permissions-14.0.0.tgz", + "integrity": "sha512-jkqTwfomNdSOWsMyMJ2SJcxTge/1HQVuQMe+fm5//HbuT4xc1NjMinAb1W8Y+HHgY90sEam17ndDCxhfhSyTNQ==", + "dependencies": { + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@angular/core": ">=13 || >16", + "@angular/router": ">=13 || >16", + "rxjs": ">=7 || >9", + "zone.js": ">=0.11.4 || >0.12" + } + }, + "node_modules/ngx-progressbar": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/ngx-progressbar/-/ngx-progressbar-9.0.0.tgz", + "integrity": "sha512-QaDxKY3KNlOdzPgdVzoUZVemZIM4YHmn3XrzbyQHWUaD1Gd6fr3BOJ+tTMKEfrJzjFQte/lszAKCaTrHY0g95w==", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@angular/common": ">=13.0.0", + "@angular/core": ">=13.0.0", + "@angular/router": ">=13.0.0", + "rxjs": ">=6.0.0" + } + }, + "node_modules/ngx-toastr": { + "version": "16.0.2", + "resolved": "https://registry.npmjs.org/ngx-toastr/-/ngx-toastr-16.0.2.tgz", + "integrity": "sha512-J6SueNCaGwm/gpXdsG56UzMEAcuayYWEW6NmIrNoe5iP7lOUohg4xYXWipkbMH9wGWmLPD9gU8AufUVWMplCvg==", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/common": ">=14.0.0-0", + "@angular/core": ">=14.0.0-0", + "@angular/platform-browser": ">=14.0.0-0" + } + }, "node_modules/nice-napi": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz", @@ -8824,6 +12324,12 @@ "node": ">=0.10.0" } }, + "node_modules/normalize-selector": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/normalize-selector/-/normalize-selector-0.2.0.tgz", + "integrity": "sha512-dxvWdI8gw6eAvk9BlPffgEoGfM7AdijoCwOEJge3e3ulT2XLgmU7KvvxprOaCu05Q1uGRHmOhHe1r6emZoKyFw==", + "dev": true + }, "node_modules/npm-bundled": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-3.0.0.tgz", @@ -9132,6 +12638,12 @@ "url": "https://github.com/fb55/nth-check?sponsor=1" } }, + "node_modules/num2fraction": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", + "integrity": "sha512-Y1wZESM7VUThYY+4W+X4ySH2maqcA+p7UR+w8VWNWVAd6lwuXXWz/w/Cz43J/dI2I+PS6wD5N+bJUF+gjWvIqg==", + "dev": true + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -9141,6 +12653,97 @@ "node": ">=0.10.0" } }, + "node_modules/object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ==", + "dev": true, + "dependencies": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "node_modules/object-copy/node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/is-descriptor/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-inspect": { "version": "1.12.2", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", @@ -9150,6 +12753,73 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/object-is": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", + "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA==", + "dev": true, + "dependencies": { + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/obuf": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", @@ -9218,6 +12888,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/ora": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", @@ -9499,6 +13186,20 @@ "node": ">=6" } }, + "node_modules/parse-entities": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-1.2.2.tgz", + "integrity": "sha512-NzfpbxW/NPrzZ/yYSoQxyqUZMZXIdCfE0OIN4ESsnptHJECoUk3FZktxNuzQf4tjt5UEopnxpYJbvYuxIFDdsg==", + "dev": true, + "dependencies": { + "character-entities": "^1.0.0", + "character-entities-legacy": "^1.0.0", + "character-reference-invalid": "^1.0.0", + "is-alphanumerical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-hexadecimal": "^1.0.0" + } + }, "node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -9527,10 +13228,16 @@ } }, "node_modules/parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", - "dev": true + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "devOptional": true, + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } }, "node_modules/parse5-html-rewriting-stream": { "version": "6.0.1", @@ -9542,6 +13249,12 @@ "parse5-sax-parser": "^6.0.1" } }, + "node_modules/parse5-html-rewriting-stream/node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + }, "node_modules/parse5-htmlparser2-tree-adapter": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", @@ -9551,6 +13264,12 @@ "parse5": "^6.0.1" } }, + "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + }, "node_modules/parse5-sax-parser": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/parse5-sax-parser/-/parse5-sax-parser-6.0.1.tgz", @@ -9560,6 +13279,24 @@ "parse5": "^6.0.1" } }, + "node_modules/parse5-sax-parser/node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + }, + "node_modules/parse5/node_modules/entities": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz", + "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==", + "devOptional": true, + "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", @@ -9569,6 +13306,21 @@ "node": ">= 0.8" } }, + "node_modules/pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha512-ALzNPpyNq9AqXMBjeymIjFDAkAFH06mHJH/cSBHAgU0s4vfpBn6b2nf8tiRLvagKD8RbTpq2FKTBg7cl9l3c7Q==", + "dev": true + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -9617,6 +13369,14 @@ "node": ">=8" } }, + "node_modules/photoviewer": { + "version": "3.6.6", + "resolved": "https://registry.npmjs.org/photoviewer/-/photoviewer-3.6.6.tgz", + "integrity": "sha512-TYuxoEdlVkIngVnoCEO+CWjeTO/F4TOPDv9ic4zbVU8ZXMiDggUzMj7jv50Kl0n1Yks72hleujPjAfGmkl7n9w==", + "dependencies": { + "domq.js": "^0.6.7" + } + }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -9640,7 +13400,6 @@ "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", "dev": true, - "optional": true, "engines": { "node": ">=6" } @@ -9671,6 +13430,15 @@ "node": ">=8" } }, + "node_modules/posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/postcss": { "version": "8.4.19", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.19.tgz", @@ -9695,6 +13463,76 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/postcss-html": { + "version": "0.36.0", + "resolved": "https://registry.npmjs.org/postcss-html/-/postcss-html-0.36.0.tgz", + "integrity": "sha512-HeiOxGcuwID0AFsNAL0ox3mW6MHH5cstWN1Z3Y+n6H+g12ih7LHdYxWwEA/QmrebctLjo79xz9ouK3MroHwOJw==", + "dev": true, + "dependencies": { + "htmlparser2": "^3.10.0" + }, + "peerDependencies": { + "postcss": ">=5.0.0", + "postcss-syntax": ">=0.36.0" + } + }, + "node_modules/postcss-jsx": { + "version": "0.36.4", + "resolved": "https://registry.npmjs.org/postcss-jsx/-/postcss-jsx-0.36.4.tgz", + "integrity": "sha512-jwO/7qWUvYuWYnpOb0+4bIIgJt7003pgU3P6nETBLaOyBXuTD55ho21xnals5nBrlpTIFodyd3/jBi6UO3dHvA==", + "dev": true, + "dependencies": { + "@babel/core": ">=7.2.2" + }, + "peerDependencies": { + "postcss": ">=5.0.0", + "postcss-syntax": ">=0.36.0" + } + }, + "node_modules/postcss-less": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/postcss-less/-/postcss-less-3.1.4.tgz", + "integrity": "sha512-7TvleQWNM2QLcHqvudt3VYjULVB49uiW6XzEUFmvwHzvsOEF5MwBrIXZDJQvJNFGjJQTzSzZnDoCJ8h/ljyGXA==", + "dev": true, + "dependencies": { + "postcss": "^7.0.14" + }, + "engines": { + "node": ">=6.14.4" + } + }, + "node_modules/postcss-less/node_modules/picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "dev": true + }, + "node_modules/postcss-less/node_modules/postcss": { + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", + "dev": true, + "dependencies": { + "picocolors": "^0.2.1", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + } + }, + "node_modules/postcss-less/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, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/postcss-loader": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.0.1.tgz", @@ -9717,6 +13555,26 @@ "webpack": "^5.0.0" } }, + "node_modules/postcss-markdown": { + "version": "0.36.0", + "resolved": "https://registry.npmjs.org/postcss-markdown/-/postcss-markdown-0.36.0.tgz", + "integrity": "sha512-rl7fs1r/LNSB2bWRhyZ+lM/0bwKv9fhl38/06gF6mKMo/NPnp55+K1dSTosSVjFZc0e1ppBlu+WT91ba0PMBfQ==", + "dev": true, + "dependencies": { + "remark": "^10.0.1", + "unist-util-find-all-after": "^1.0.2" + }, + "peerDependencies": { + "postcss": ">=5.0.0", + "postcss-syntax": ">=0.36.0" + } + }, + "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 + }, "node_modules/postcss-modules-extract-imports": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", @@ -9776,6 +13634,151 @@ "postcss": "^8.1.0" } }, + "node_modules/postcss-reporter": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-reporter/-/postcss-reporter-6.0.1.tgz", + "integrity": "sha512-LpmQjfRWyabc+fRygxZjpRxfhRf9u/fdlKf4VHG4TSPbV2XNsuISzYW1KL+1aQzx53CAppa1bKG4APIB/DOXXw==", + "dev": true, + "dependencies": { + "chalk": "^2.4.1", + "lodash": "^4.17.11", + "log-symbols": "^2.2.0", + "postcss": "^7.0.7" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/postcss-reporter/node_modules/log-symbols": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", + "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", + "dev": true, + "dependencies": { + "chalk": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-reporter/node_modules/picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "dev": true + }, + "node_modules/postcss-reporter/node_modules/postcss": { + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", + "dev": true, + "dependencies": { + "picocolors": "^0.2.1", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + } + }, + "node_modules/postcss-reporter/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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postcss-resolve-nested-selector": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.1.tgz", + "integrity": "sha512-HvExULSwLqHLgUy1rl3ANIqCsvMS0WHss2UOsXhXnQaZ9VCc2oBvIpXrl00IUFT5ZDITME0o6oiXeiHr2SAIfw==", + "dev": true + }, + "node_modules/postcss-safe-parser": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-6.0.0.tgz", + "integrity": "sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==", + "dev": true, + "engines": { + "node": ">=12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.3.3" + } + }, + "node_modules/postcss-sass": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/postcss-sass/-/postcss-sass-0.3.5.tgz", + "integrity": "sha512-B5z2Kob4xBxFjcufFnhQ2HqJQ2y/Zs/ic5EZbCywCkxKd756Q40cIQ/veRDwSrw1BF6+4wUgmpm0sBASqVi65A==", + "dev": true, + "dependencies": { + "gonzales-pe": "^4.2.3", + "postcss": "^7.0.1" + } + }, + "node_modules/postcss-sass/node_modules/picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "dev": true + }, + "node_modules/postcss-sass/node_modules/postcss": { + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", + "dev": true, + "dependencies": { + "picocolors": "^0.2.1", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + } + }, + "node_modules/postcss-sass/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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postcss-scss": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-4.0.6.tgz", + "integrity": "sha512-rLDPhJY4z/i4nVFZ27j9GqLxj1pwxE80eAzUNRMXtcpipFYIeowerzBgG3yJhMtObGEXidtIgbUpQ3eLDsf5OQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss-scss" + } + ], + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.4.19" + } + }, "node_modules/postcss-selector-parser": { "version": "6.0.11", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.11.tgz", @@ -9789,12 +13792,54 @@ "node": ">=4" } }, + "node_modules/postcss-sorting": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-sorting/-/postcss-sorting-7.0.1.tgz", + "integrity": "sha512-iLBFYz6VRYyLJEJsBJ8M3TCqNcckVzz4wFounSc5Oez35ogE/X+aoC5fFu103Ot7NyvjU3/xqIXn93Gp3kJk4g==", + "dev": true, + "peerDependencies": { + "postcss": "^8.3.9" + } + }, + "node_modules/postcss-syntax": { + "version": "0.36.2", + "resolved": "https://registry.npmjs.org/postcss-syntax/-/postcss-syntax-0.36.2.tgz", + "integrity": "sha512-nBRg/i7E3SOHWxF3PpF5WnJM/jQ1YpY9000OaVXlAQj6Zp/kIqJxEDWIZ67tAd7NLuk7zqN4yqe9nc0oNAOs1w==", + "dev": true, + "peerDependencies": { + "postcss": ">=5.0.0" + } + }, "node_modules/postcss-value-parser": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "dev": true }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.2.tgz", + "integrity": "sha512-BtRV9BcncDyI2tsuS19zzhzoxD8Dh8LiCx7j7tHzrkz8GFXAexeWFdi22mjE1d16dftH2qNaytVxqiRTGlMfpw==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/pretty-bytes": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", @@ -9923,6 +13968,15 @@ } ] }, + "node_modules/quick-lru": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", + "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -10002,6 +14056,83 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "dev": true, + "dependencies": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "dev": true, + "dependencies": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg/node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "node_modules/read-pkg/node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/read-pkg/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/read-pkg/node_modules/type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/readable-stream": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", @@ -10028,6 +14159,19 @@ "node": ">=8.10.0" } }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/reflect-metadata": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", @@ -10067,12 +14211,54 @@ "@babel/runtime": "^7.8.4" } }, + "node_modules/regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "dependencies": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/regex-parser": { "version": "2.2.11", "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.2.11.tgz", "integrity": "sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q==", "dev": true }, + "node_modules/regexp.prototype.flags": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", + "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, "node_modules/regexpu-core": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.2.2.tgz", @@ -10117,6 +14303,89 @@ "jsesc": "bin/jsesc" } }, + "node_modules/remark": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/remark/-/remark-10.0.1.tgz", + "integrity": "sha512-E6lMuoLIy2TyiokHprMjcWNJ5UxfGQjaMSMhV+f4idM625UjjK4j798+gPs5mfjzDE6vL0oFKVeZM6gZVSVrzQ==", + "dev": true, + "dependencies": { + "remark-parse": "^6.0.0", + "remark-stringify": "^6.0.0", + "unified": "^7.0.0" + } + }, + "node_modules/remark-parse": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-6.0.3.tgz", + "integrity": "sha512-QbDXWN4HfKTUC0hHa4teU463KclLAnwpn/FBn87j9cKYJWWawbiLgMfP2Q4XwhxxuuuOxHlw+pSN0OKuJwyVvg==", + "dev": true, + "dependencies": { + "collapse-white-space": "^1.0.2", + "is-alphabetical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-whitespace-character": "^1.0.0", + "is-word-character": "^1.0.0", + "markdown-escapes": "^1.0.0", + "parse-entities": "^1.1.0", + "repeat-string": "^1.5.4", + "state-toggle": "^1.0.0", + "trim": "0.0.1", + "trim-trailing-lines": "^1.0.0", + "unherit": "^1.0.4", + "unist-util-remove-position": "^1.0.0", + "vfile-location": "^2.0.0", + "xtend": "^4.0.1" + } + }, + "node_modules/remark-stringify": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-6.0.4.tgz", + "integrity": "sha512-eRWGdEPMVudijE/psbIDNcnJLRVx3xhfuEsTDGgH4GsFF91dVhw5nhmnBppafJ7+NWINW6C7ZwWbi30ImJzqWg==", + "dev": true, + "dependencies": { + "ccount": "^1.0.0", + "is-alphanumeric": "^1.0.0", + "is-decimal": "^1.0.0", + "is-whitespace-character": "^1.0.0", + "longest-streak": "^2.0.1", + "markdown-escapes": "^1.0.0", + "markdown-table": "^1.1.0", + "mdast-util-compact": "^1.0.0", + "parse-entities": "^1.0.2", + "repeat-string": "^1.5.4", + "state-toggle": "^1.0.0", + "stringify-entities": "^1.0.1", + "unherit": "^1.0.4", + "xtend": "^4.0.1" + } + }, + "node_modules/repeat-element": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", + "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/replace-ext": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", + "integrity": "sha512-vuNYXC7gG7IeVNBC1xUllqCcZKRbJoSPOBhnTEcAIiKCsbuef6zO3F0Rve3isPMMoNoQRWjQwbAgAjHUHniyEA==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -10167,6 +14436,13 @@ "node": ">=8" } }, + "node_modules/resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==", + "deprecated": "https://github.com/lydell/resolve-url#deprecated", + "dev": true + }, "node_modules/resolve-url-loader": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-5.0.0.tgz", @@ -10219,6 +14495,15 @@ "node": ">=8" } }, + "node_modules/ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true, + "engines": { + "node": ">=0.12" + } + }, "node_modules/retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", @@ -10361,6 +14646,15 @@ } ] }, + "node_modules/safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==", + "dev": true, + "dependencies": { + "ret": "~0.1.10" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -10453,6 +14747,17 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/screenfull": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/screenfull/-/screenfull-6.0.2.tgz", + "integrity": "sha512-AQdy8s4WhNvUZ6P8F6PB21tSPIYKniic+Ogx0AacBMjKP1GUHN2E9URxQHtCusiwxudnCKkdy4GrHXPPJSkCCw==", + "engines": { + "node": "^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", @@ -10669,6 +14974,42 @@ "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", "dev": true }, + "node_modules/set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "dev": true, + "dependencies": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/set-value/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/set-value/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -10740,6 +15081,56 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/slice-ansi/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==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/slice-ansi/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==", + "dev": true + }, "node_modules/smart-buffer": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", @@ -10750,6 +15141,215 @@ "npm": ">= 3.0.0" } }, + "node_modules/snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "dependencies": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "dependencies": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "dependencies": { + "kind-of": "^3.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-util/node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "node_modules/snapdragon-util/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/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, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/snapdragon/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/is-accessor-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "node_modules/snapdragon/node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/is-data-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/snapdragon/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/socket.io": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.5.4.tgz", @@ -10876,6 +15476,20 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-resolve": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", + "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", + "deprecated": "See https://github.com/lydell/source-map-resolve#deprecated", + "dev": true, + "dependencies": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.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", @@ -10895,6 +15509,13 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-url": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", + "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", + "deprecated": "See https://github.com/lydell/source-map-url#deprecated", + "dev": true + }, "node_modules/sourcemap-codec": { "version": "1.4.8", "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", @@ -10964,6 +15585,27 @@ "wbuf": "^1.7.3" } }, + "node_modules/specificity": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/specificity/-/specificity-0.4.1.tgz", + "integrity": "sha512-1klA3Gi5PD1Wv9Q0wUoOQN1IWAuPu0D1U03ThXTr0cJ20+/iq2tHSDnK7Kk/0LXJ1ztUB2/1Os0wKmfyNgUQfg==", + "dev": true, + "bin": { + "specificity": "bin/specificity" + } + }, + "node_modules/split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "dependencies": { + "extend-shallow": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -11000,6 +15642,118 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, + "node_modules/state-toggle": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/state-toggle/-/state-toggle-1.0.3.tgz", + "integrity": "sha512-d/5Z4/2iiCnHw6Xzghyhb+GcmF89bxwgXG60wjIiZaxnymbyOmI8Hk4VqHXiVVp6u2ysaskFfXg3ekCj4WNftQ==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==", + "dev": true, + "dependencies": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/is-accessor-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "node_modules/static-extend/node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/is-data-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/statuses": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", @@ -11046,6 +15800,18 @@ "node": ">=8" } }, + "node_modules/stringify-entities": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-1.3.2.tgz", + "integrity": "sha512-nrBAQClJAPN2p+uGCVJRPIPakKeKWZ9GtBCmormE7pWOSlHat7+x5A8gx85M7HM5Dt0BP3pP5RhVW77WdbJJ3A==", + "dev": true, + "dependencies": { + "character-entities-html4": "^1.0.0", + "character-entities-legacy": "^1.0.0", + "is-alphanumerical": "^1.0.0", + "is-hexadecimal": "^1.0.0" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -11058,6 +15824,15 @@ "node": ">=8" } }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/strip-final-newline": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", @@ -11067,6 +15842,1256 @@ "node": ">=6" } }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/style-search": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/style-search/-/style-search-0.1.0.tgz", + "integrity": "sha512-Dj1Okke1C3uKKwQcetra4jSuk0DqbzbYtXipzFlFMZtowbF1x7BKJwB9AayVMyFARvU8EDrZdcax4At/452cAg==", + "dev": true + }, + "node_modules/stylelint": { + "version": "14.16.1", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-14.16.1.tgz", + "integrity": "sha512-ErlzR/T3hhbV+a925/gbfc3f3Fep9/bnspMiJPorfGEmcBbXdS+oo6LrVtoUZ/w9fqD6o6k7PtUlCOsCRdjX/A==", + "dev": true, + "dependencies": { + "@csstools/selector-specificity": "^2.0.2", + "balanced-match": "^2.0.0", + "colord": "^2.9.3", + "cosmiconfig": "^7.1.0", + "css-functions-list": "^3.1.0", + "debug": "^4.3.4", + "fast-glob": "^3.2.12", + "fastest-levenshtein": "^1.0.16", + "file-entry-cache": "^6.0.1", + "global-modules": "^2.0.0", + "globby": "^11.1.0", + "globjoin": "^0.1.4", + "html-tags": "^3.2.0", + "ignore": "^5.2.1", + "import-lazy": "^4.0.0", + "imurmurhash": "^0.1.4", + "is-plain-object": "^5.0.0", + "known-css-properties": "^0.26.0", + "mathml-tag-names": "^2.1.3", + "meow": "^9.0.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.19", + "postcss-media-query-parser": "^0.2.3", + "postcss-resolve-nested-selector": "^0.1.1", + "postcss-safe-parser": "^6.0.0", + "postcss-selector-parser": "^6.0.11", + "postcss-value-parser": "^4.2.0", + "resolve-from": "^5.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "style-search": "^0.1.0", + "supports-hyperlinks": "^2.3.0", + "svg-tags": "^1.0.0", + "table": "^6.8.1", + "v8-compile-cache": "^2.3.0", + "write-file-atomic": "^4.0.2" + }, + "bin": { + "stylelint": "bin/stylelint.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/stylelint" + } + }, + "node_modules/stylelint-config-rational-order": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/stylelint-config-rational-order/-/stylelint-config-rational-order-0.1.2.tgz", + "integrity": "sha512-Qo7ZQaihCwTqijfZg4sbdQQHtugOX/B1/fYh018EiDZHW+lkqH9uHOnsDwDPGZrYJuB6CoyI7MZh2ecw2dOkew==", + "dev": true, + "dependencies": { + "stylelint": "^9.10.1", + "stylelint-order": "^2.2.1" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/@nodelib/fs.stat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz", + "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/stylelint-config-rational-order/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, + "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/stylelint-config-rational-order/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==", + "dev": true, + "dependencies": { + "array-uniq": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/autoprefixer": { + "version": "9.8.8", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.8.8.tgz", + "integrity": "sha512-eM9d/swFopRt5gdJ7jrpCwgvEMIayITpojhkkSMRsFHYuH5bkSQ4p/9qTEHtmNudUZh22Tehu7I6CxAW0IXTKA==", + "dev": true, + "dependencies": { + "browserslist": "^4.12.0", + "caniuse-lite": "^1.0.30001109", + "normalize-range": "^0.1.2", + "num2fraction": "^1.2.2", + "picocolors": "^0.2.1", + "postcss": "^7.0.32", + "postcss-value-parser": "^4.1.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "funding": { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "dependencies": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/braces/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha512-FxAv7HpHrXbh3aPo4o2qxHay2lkLY3x5Mw3KeE4KQE8ysVfziWeRZDwcjauvwBSGEC/nXUPzZy8zeh4HokqOnw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/camelcase-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-4.2.0.tgz", + "integrity": "sha512-Ej37YKYbFUI8QiYlvj9YHb6/Z60dZyPJW0Cs8sFilMbd2lP0bw3ylAq9yJkK4lcTA2dID5fG8LjmJYbO7kWb7Q==", + "dev": true, + "dependencies": { + "camelcase": "^4.1.0", + "map-obj": "^2.0.0", + "quick-lru": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/cosmiconfig": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", + "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "dev": true, + "dependencies": { + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.13.1", + "parse-json": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/dir-glob": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz", + "integrity": "sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==", + "dev": true, + "dependencies": { + "path-type": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "node_modules/stylelint-config-rational-order/node_modules/fast-glob": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-2.2.7.tgz", + "integrity": "sha512-g1KuQwHOZAmOZMuBtHdxDtju+T2RT8jgCC9aANsbpdiDDTSnjgfuVsIBNKbUeJI3oKMRExcfNDtJl4OhbffMsw==", + "dev": true, + "dependencies": { + "@mrmlnc/readdir-enhanced": "^2.2.1", + "@nodelib/fs.stat": "^1.1.2", + "glob-parent": "^3.1.0", + "is-glob": "^4.0.0", + "merge2": "^1.2.3", + "micromatch": "^3.1.10" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/file-entry-cache": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-4.0.0.tgz", + "integrity": "sha512-AVSwsnbV8vH/UVbvgEhf3saVQXORNv0ZzSkvkhQIaia5Tia+JhGTaa/ePUSVoPHQyGayQNmYfkzFi3WZV5zcpA==", + "dev": true, + "dependencies": { + "flat-cache": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", + "dev": true, + "dependencies": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/fill-range/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", + "dev": true, + "dependencies": { + "locate-path": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/flat-cache": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", + "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "dev": true, + "dependencies": { + "flatted": "^2.0.0", + "rimraf": "2.6.3", + "write": "1.0.3" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/flatted": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", + "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", + "dev": true + }, + "node_modules/stylelint-config-rational-order/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "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/stylelint-config-rational-order/node_modules/glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA==", + "dev": true, + "dependencies": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/glob-parent/node_modules/is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/globby": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-9.2.0.tgz", + "integrity": "sha512-ollPHROa5mcxDEkwg6bPt3QbEf4pDQSNtd6JPL1YvOvAo/7/0VAm9TccUeoTmarjPw4pfUthSCqcyfNB1I3ZSg==", + "dev": true, + "dependencies": { + "@types/glob": "^7.1.1", + "array-union": "^1.0.2", + "dir-glob": "^2.2.2", + "fast-glob": "^2.2.6", + "glob": "^7.1.3", + "ignore": "^4.0.3", + "pify": "^4.0.1", + "slash": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/globby/node_modules/ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "node_modules/stylelint-config-rational-order/node_modules/html-tags": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-2.0.0.tgz", + "integrity": "sha512-+Il6N8cCo2wB/Vd3gqy/8TZhTD3QvcVeQLCnZiGkGCH3JP28IgGAY41giccp2W4R3jfyJPAP318FQTa1yU7K7g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==", + "dev": true, + "dependencies": { + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/import-fresh/node_modules/resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/import-lazy": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-3.1.0.tgz", + "integrity": "sha512-8/gvXvX2JMn0F+CDlSC4l6kOmVaLOO3XLkksI7CI3Ud95KDYJuYur2b9P/PUt/i/pDAMd/DulQsNbbbmRRsDIQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/indent-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", + "integrity": "sha512-BYqTHXTGUIvg7t1r4sJNKcbDZkL92nkXA8YtRpbjFHRHGDL/NtUeiBJMeE60kIFN/Mg8ESaWQvftaYMGJzQZCQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "node_modules/stylelint-config-rational-order/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/is-number/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stylelint-config-rational-order/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 + }, + "node_modules/stylelint-config-rational-order/node_modules/known-css-properties": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.11.0.tgz", + "integrity": "sha512-bEZlJzXo5V/ApNNa5z375mJC6Nrz4vG43UgcSCrg2OHC+yuB6j0iDSrY7RQ/+PRofFB03wNIIt9iXIVLr4wc7w==", + "dev": true + }, + "node_modules/stylelint-config-rational-order/node_modules/locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", + "dev": true, + "dependencies": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/log-symbols": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", + "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", + "dev": true, + "dependencies": { + "chalk": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/map-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-2.0.0.tgz", + "integrity": "sha512-TzQSV2DiMYgoF5RycneKVUzIa9bQsj/B3tTgsE3dOGqlzHnGIDaC7XBE7grnA+8kZPnfqSGFe95VHc2oc0VFUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/meow": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-5.0.0.tgz", + "integrity": "sha512-CbTqYU17ABaLefO8vCU153ZZlprKYWDljcndKKDCFcYQITzWCXZAVk4QMFZPgvzrnUQ3uItnIE/LoUOwrT15Ig==", + "dev": true, + "dependencies": { + "camelcase-keys": "^4.0.0", + "decamelize-keys": "^1.0.0", + "loud-rejection": "^1.0.0", + "minimist-options": "^3.0.1", + "normalize-package-data": "^2.3.4", + "read-pkg-up": "^3.0.0", + "redent": "^2.0.0", + "trim-newlines": "^2.0.0", + "yargs-parser": "^10.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stylelint-config-rational-order/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, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/minimist-options": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-3.0.2.tgz", + "integrity": "sha512-FyBrT/d0d4+uiZRbqznPXqw3IpZZG3gl3wKWiX784FycUKVwBt0uLBFkQrtE4tZOrgo78nZp2jnKz3L65T5LdQ==", + "dev": true, + "dependencies": { + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "dependencies": { + "p-try": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", + "dev": true, + "dependencies": { + "p-limit": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "dev": true, + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/path-type/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "dev": true + }, + "node_modules/stylelint-config-rational-order/node_modules/postcss": { + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", + "dev": true, + "dependencies": { + "picocolors": "^0.2.1", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/postcss-safe-parser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-4.0.2.tgz", + "integrity": "sha512-Uw6ekxSWNLCPesSv/cmqf2bY/77z11O7jZGPax3ycZMFU/oi2DMH9i89AdHc1tRwFg/arFoEwX0IS3LCUxJh1g==", + "dev": true, + "dependencies": { + "postcss": "^7.0.26" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/postcss-scss": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-2.1.1.tgz", + "integrity": "sha512-jQmGnj0hSGLd9RscFw9LyuSVAa5Bl1/KBPqG1NQw9w8ND55nY4ZEsdlVuYJvLPpV+y0nwTV5v/4rHPzZRihQbA==", + "dev": true, + "dependencies": { + "postcss": "^7.0.6" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/postcss-selector-parser": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", + "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", + "dev": true, + "dependencies": { + "dot-prop": "^5.2.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/postcss-sorting": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-sorting/-/postcss-sorting-4.1.0.tgz", + "integrity": "sha512-r4T2oQd1giURJdHQ/RMb72dKZCuLOdWx2B/XhXN1Y1ZdnwXsKH896Qz6vD4tFy9xSjpKNYhlZoJmWyhH/7JUQw==", + "dev": true, + "dependencies": { + "lodash": "^4.17.4", + "postcss": "^7.0.0" + }, + "engines": { + "node": ">=6.14.3" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/quick-lru": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-1.1.0.tgz", + "integrity": "sha512-tRS7sTgyxMXtLum8L65daJnHUhfDUgboRdcWW2bR9vBfrj2+O5HSMbQOJfJJjIVSPFqbBCF37FpwWXGitDc5tA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==", + "dev": true, + "dependencies": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/read-pkg-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", + "integrity": "sha512-YFzFrVvpC6frF1sz8psoHDBGF7fLPc+llq/8NB43oagqWkx8ar5zYtsTORtOjw9W2RHLpWP+zTWwBvf1bCmcSw==", + "dev": true, + "dependencies": { + "find-up": "^2.0.0", + "read-pkg": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/redent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-2.0.0.tgz", + "integrity": "sha512-XNwrTx77JQCEMXTeb8movBKuK75MgH0RZkujNuDKCezemx/voapl9i2gCSi8WWm8+ox5ycJi1gxF22fR7c0Ciw==", + "dev": true, + "dependencies": { + "indent-string": "^3.0.0", + "strip-indent": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/stylelint-config-rational-order/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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/strip-indent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz", + "integrity": "sha512-RsSNPLpq6YUL7QYy44RnPVTn/lcVZtb48Uof3X5JLbF4zD/Gs7ZFDv2HWol+leoQN2mT86LAzSshGfkTlSOpsA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/stylelint": { + "version": "9.10.1", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-9.10.1.tgz", + "integrity": "sha512-9UiHxZhOAHEgeQ7oLGwrwoDR8vclBKlSX7r4fH0iuu0SfPwFaLkb1c7Q2j1cqg9P7IDXeAV2TvQML/fRQzGBBQ==", + "dev": true, + "dependencies": { + "autoprefixer": "^9.0.0", + "balanced-match": "^1.0.0", + "chalk": "^2.4.1", + "cosmiconfig": "^5.0.0", + "debug": "^4.0.0", + "execall": "^1.0.0", + "file-entry-cache": "^4.0.0", + "get-stdin": "^6.0.0", + "global-modules": "^2.0.0", + "globby": "^9.0.0", + "globjoin": "^0.1.4", + "html-tags": "^2.0.0", + "ignore": "^5.0.4", + "import-lazy": "^3.1.0", + "imurmurhash": "^0.1.4", + "known-css-properties": "^0.11.0", + "leven": "^2.1.0", + "lodash": "^4.17.4", + "log-symbols": "^2.0.0", + "mathml-tag-names": "^2.0.1", + "meow": "^5.0.0", + "micromatch": "^3.1.10", + "normalize-selector": "^0.2.0", + "pify": "^4.0.0", + "postcss": "^7.0.13", + "postcss-html": "^0.36.0", + "postcss-jsx": "^0.36.0", + "postcss-less": "^3.1.0", + "postcss-markdown": "^0.36.0", + "postcss-media-query-parser": "^0.2.3", + "postcss-reporter": "^6.0.0", + "postcss-resolve-nested-selector": "^0.1.1", + "postcss-safe-parser": "^4.0.0", + "postcss-sass": "^0.3.5", + "postcss-scss": "^2.0.0", + "postcss-selector-parser": "^3.1.0", + "postcss-syntax": "^0.36.2", + "postcss-value-parser": "^3.3.0", + "resolve-from": "^4.0.0", + "signal-exit": "^3.0.2", + "slash": "^2.0.0", + "specificity": "^0.4.1", + "string-width": "^3.0.0", + "style-search": "^0.1.0", + "sugarss": "^2.0.0", + "svg-tags": "^1.0.0", + "table": "^5.0.0" + }, + "bin": { + "stylelint": "bin/stylelint.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/stylelint-order": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/stylelint-order/-/stylelint-order-2.2.1.tgz", + "integrity": "sha512-019KBV9j8qp1MfBjJuotse6MgaZqGVtXMc91GU9MsS9Feb+jYUvUU3Z8XiClqPdqJZQ0ryXQJGg3U3PcEjXwfg==", + "dev": true, + "dependencies": { + "lodash": "^4.17.10", + "postcss": "^7.0.2", + "postcss-sorting": "^4.1.0" + }, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "stylelint": "^9.10.1 || ^10.0.0" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/stylelint/node_modules/postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + }, + "node_modules/stylelint-config-rational-order/node_modules/table": { + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", + "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", + "dev": true, + "dependencies": { + "ajv": "^6.10.2", + "lodash": "^4.17.14", + "slice-ansi": "^2.1.0", + "string-width": "^3.0.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", + "dev": true, + "dependencies": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/trim-newlines": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-2.0.0.tgz", + "integrity": "sha512-MTBWv3jhVjTU7XR3IQHllbiJs8sc75a80OEhB6or/q7pLTWgQ0bMGQXXYQSrSuXe6WiKWDZ5txXY5P59a/coVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/yargs-parser": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz", + "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", + "dev": true, + "dependencies": { + "camelcase": "^4.1.0" + } + }, + "node_modules/stylelint-config-recommended": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-9.0.0.tgz", + "integrity": "sha512-9YQSrJq4NvvRuTbzDsWX3rrFOzOlYBmZP+o513BJN/yfEmGSr0AxdvrWs0P/ilSpVV/wisamAHu5XSk8Rcf4CQ==", + "dev": true, + "peerDependencies": { + "stylelint": "^14.10.0" + } + }, + "node_modules/stylelint-config-recommended-scss": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended-scss/-/stylelint-config-recommended-scss-8.0.0.tgz", + "integrity": "sha512-BxjxEzRaZoQb7Iinc3p92GS6zRdRAkIuEu2ZFLTxJK2e1AIcCb5B5MXY9KOXdGTnYFZ+KKx6R4Fv9zU6CtMYPQ==", + "dev": true, + "dependencies": { + "postcss-scss": "^4.0.2", + "stylelint-config-recommended": "^9.0.0", + "stylelint-scss": "^4.0.0" + }, + "peerDependencies": { + "postcss": "^8.3.3", + "stylelint": "^14.10.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + } + } + }, + "node_modules/stylelint-config-standard": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-standard/-/stylelint-config-standard-29.0.0.tgz", + "integrity": "sha512-uy8tZLbfq6ZrXy4JKu3W+7lYLgRQBxYTUUB88vPgQ+ZzAxdrvcaSUW9hOMNLYBnwH+9Kkj19M2DHdZ4gKwI7tg==", + "dev": true, + "dependencies": { + "stylelint-config-recommended": "^9.0.0" + }, + "peerDependencies": { + "stylelint": "^14.14.0" + } + }, + "node_modules/stylelint-order": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/stylelint-order/-/stylelint-order-5.0.0.tgz", + "integrity": "sha512-OWQ7pmicXufDw5BlRqzdz3fkGKJPgLyDwD1rFY3AIEfIH/LQY38Vu/85v8/up0I+VPiuGRwbc2Hg3zLAsJaiyw==", + "dev": true, + "dependencies": { + "postcss": "^8.3.11", + "postcss-sorting": "^7.0.1" + }, + "peerDependencies": { + "stylelint": "^14.0.0" + } + }, + "node_modules/stylelint-scss": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-4.3.0.tgz", + "integrity": "sha512-GvSaKCA3tipzZHoz+nNO7S02ZqOsdBzMiCx9poSmLlb3tdJlGddEX/8QzCOD8O7GQan9bjsvLMsO5xiw6IhhIQ==", + "dev": true, + "dependencies": { + "lodash": "^4.17.21", + "postcss-media-query-parser": "^0.2.3", + "postcss-resolve-nested-selector": "^0.1.1", + "postcss-selector-parser": "^6.0.6", + "postcss-value-parser": "^4.1.0" + }, + "peerDependencies": { + "stylelint": "^14.5.1" + } + }, + "node_modules/stylelint/node_modules/balanced-match": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-2.0.0.tgz", + "integrity": "sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==", + "dev": true + }, + "node_modules/stylelint/node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stylelint/node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stylelint/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/sugarss": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/sugarss/-/sugarss-2.0.0.tgz", + "integrity": "sha512-WfxjozUk0UVA4jm+U1d736AUpzSrNsQcIbyOkoE364GrtWmIrFdk5lksEupgWMD4VaT/0kVx1dobpiDumSgmJQ==", + "dev": true, + "dependencies": { + "postcss": "^7.0.2" + } + }, + "node_modules/sugarss/node_modules/picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "dev": true + }, + "node_modules/sugarss/node_modules/postcss": { + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", + "dev": true, + "dependencies": { + "picocolors": "^0.2.1", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + } + }, + "node_modules/sugarss/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, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -11079,6 +17104,40 @@ "node": ">=4" } }, + "node_modules/supports-hyperlinks": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", + "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-hyperlinks/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-hyperlinks/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, + "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", @@ -11091,6 +17150,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/svg-tags": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz", + "integrity": "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==", + "dev": true + }, "node_modules/symbol-observable": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", @@ -11100,6 +17165,22 @@ "node": ">=0.10" } }, + "node_modules/table": { + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz", + "integrity": "sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==", + "dev": true, + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -11352,6 +17433,51 @@ "node": ">=4" } }, + "node_modules/to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-object-path/node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "node_modules/to-object-path/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "dependencies": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -11382,11 +17508,79 @@ "tree-kill": "cli.js" } }, + "node_modules/trim": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz", + "integrity": "sha512-YzQV+TZg4AxpKxaTHK3c3D+kRDCGVEE7LemdlQZoQXn0iennk10RsIoY6ikzAqJTc9Xjl9C1/waHom/J86ziAQ==", + "dev": true + }, + "node_modules/trim-newlines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", + "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/trim-trailing-lines": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/trim-trailing-lines/-/trim-trailing-lines-1.1.4.tgz", + "integrity": "sha512-rjUWSqnfTNrjbB9NQWfPMH/xRK1deHeGsHoVfpxJ++XeYXE0d6B1En37AHfw3jtfTU7dzMzZL2jjpe8Qb5gLIQ==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/trough/-/trough-1.0.5.tgz", + "integrity": "sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/tslib": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/type-fest": { "version": "0.21.3", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", @@ -11450,6 +17644,20 @@ "node": "*" } }, + "node_modules/unherit": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/unherit/-/unherit-1.1.3.tgz", + "integrity": "sha512-Ft16BJcnapDKp0+J/rqFC3Rrk6Y/Ng4nzsC028k2jdDII/rdZ7Wd3pPT/6+vIIxRagwRc9K0IUX0Ra4fKvw+WQ==", + "dev": true, + "dependencies": { + "inherits": "^2.0.0", + "xtend": "^4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", @@ -11490,6 +17698,61 @@ "node": ">=4" } }, + "node_modules/unified": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/unified/-/unified-7.1.0.tgz", + "integrity": "sha512-lbk82UOIGuCEsZhPj8rNAkXSDXd6p0QLzIuSsCdxrqnqU56St4eyOB+AlXsVgVeRmetPTYydIuvFfpDIed8mqw==", + "dev": true, + "dependencies": { + "@types/unist": "^2.0.0", + "@types/vfile": "^3.0.0", + "bail": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^1.1.0", + "trough": "^1.0.0", + "vfile": "^3.0.0", + "x-is-string": "^0.1.0" + } + }, + "node_modules/unified/node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "dev": true, + "dependencies": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/union-value/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/uniq": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", + "integrity": "sha512-Gw+zz50YNKPDKXs+9d+aKAjVwpjNwqzvNpLigIruT4HA9lMZNdMqs9x07kKHB/L9WRzqp4+DlTU5s4wG2esdoA==", + "dev": true + }, "node_modules/unique-filename": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", @@ -11514,6 +17777,62 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/unist-util-find-all-after": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/unist-util-find-all-after/-/unist-util-find-all-after-1.0.5.tgz", + "integrity": "sha512-lWgIc3rrTMTlK1Y0hEuL+k+ApzFk78h+lsaa2gHf63Gp5Ww+mt11huDniuaoq1H+XMK2lIIjjPkncxXcDp3QDw==", + "dev": true, + "dependencies": { + "unist-util-is": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-3.0.0.tgz", + "integrity": "sha512-sVZZX3+kspVNmLWBPAB6r+7D9ZgAFPNWm66f7YNb420RlQSbn+n8rG8dGZSkrER7ZIXGQYNm5pqC3v3HopH24A==", + "dev": true + }, + "node_modules/unist-util-remove-position": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-1.1.4.tgz", + "integrity": "sha512-tLqd653ArxJIPnKII6LMZwH+mb5q+n/GtXQZo6S6csPRs5zB0u79Yw8ouR3wTw8wxvdJFhpP6Y7jorWdCgLO0A==", + "dev": true, + "dependencies": { + "unist-util-visit": "^1.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-1.1.2.tgz", + "integrity": "sha512-pNCVrk64LZv1kElr0N1wPiHEUoXNVFERp+mlTg/s9R5Lwg87f9bM/3sQB99w+N9D/qnM9ar3+AKDBwo/gm/iQQ==", + "dev": true + }, + "node_modules/unist-util-visit": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-1.4.1.tgz", + "integrity": "sha512-AvGNk7Bb//EmJZyhtRUnNMEpId/AZ5Ph/KUpTI09WHQuDZHKovQ1oEv3mfmKpWKtoMzyMC4GLBm1Zy5k12fjIw==", + "dev": true, + "dependencies": { + "unist-util-visit-parents": "^2.0.0" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-2.1.2.tgz", + "integrity": "sha512-DyN5vD4NE3aSeB+PXYNKxzGsfocxp6asDc2XXE3b0ekO2BaRUpBicbbUygfSvYfUz1IkmjFR1YF7dPklraMZ2g==", + "dev": true, + "dependencies": { + "unist-util-is": "^3.0.0" + } + }, "node_modules/universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -11532,6 +17851,54 @@ "node": ">= 0.8" } }, + "node_modules/unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==", + "dev": true, + "dependencies": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==", + "dev": true, + "dependencies": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-value/node_modules/isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==", + "dev": true, + "dependencies": { + "isarray": "1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/update-browserslist-db": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", @@ -11567,6 +17934,22 @@ "punycode": "^2.1.0" } }, + "node_modules/urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==", + "deprecated": "Please see https://github.com/lydell/urix#deprecated", + "dev": true + }, + "node_modules/use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -11591,6 +17974,12 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "dev": true + }, "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", @@ -11622,6 +18011,64 @@ "node": ">= 0.8" } }, + "node_modules/vfile": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-3.0.1.tgz", + "integrity": "sha512-y7Y3gH9BsUSdD4KzHsuMaCzRjglXN0W2EcMf0gpvu6+SbsGhMje7xDc8AEoeXy6mIwCKMI6BkjMsRjzQbhMEjQ==", + "dev": true, + "dependencies": { + "is-buffer": "^2.0.0", + "replace-ext": "1.0.0", + "unist-util-stringify-position": "^1.0.0", + "vfile-message": "^1.0.0" + } + }, + "node_modules/vfile-location": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-2.0.6.tgz", + "integrity": "sha512-sSFdyCP3G6Ka0CEmN83A2YCMKIieHx0EDaj5IDP4g1pa5ZJ4FJDvpO0WODLxo4LUX4oe52gmSCK7Jw4SBghqxA==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.3.tgz", + "integrity": "sha512-0yaU+rj2gKAyEk12ffdSbBfjnnj+b1zqTBv3OQCTn8yEB02bsPizwdBPrLJjHnK+cU9EMMcUnNv938XcZIkmdA==", + "dev": true, + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-stringify-position": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message/node_modules/unist-util-stringify-position": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.2.tgz", + "integrity": "sha512-7A6eiDCs9UtjcwZOcCpM4aPII3bAAGv13E96IkawkOAW0OhH+yRxtY0lzo8KiHpzEMfH7Q+FizUmwp8Iqy5EWg==", + "dev": true, + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile/node_modules/vfile-message": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-1.1.1.tgz", + "integrity": "sha512-1WmsopSGhWt5laNir+633LszXvZ+Z/lxveBf6yhGsqnQIhlhzooZae7zV6YVM1Sdkw68dtAW3ow0pOdPANugvA==", + "dev": true, + "dependencies": { + "unist-util-stringify-position": "^1.1.1" + } + }, "node_modules/void-elements": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", @@ -11935,6 +18382,57 @@ "which": "bin/which" } }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", + "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", + "dev": true, + "dependencies": { + "is-map": "^2.0.1", + "is-set": "^2.0.1", + "is-weakmap": "^2.0.1", + "is-weakset": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", + "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/wide-align": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", @@ -11950,6 +18448,15 @@ "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", "dev": true }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -12006,6 +18513,31 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, + "node_modules/write": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", + "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", + "dev": true, + "dependencies": { + "mkdirp": "^0.5.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, "node_modules/ws": { "version": "8.2.3", "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", @@ -12027,6 +18559,21 @@ } } }, + "node_modules/x-is-string": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/x-is-string/-/x-is-string-0.1.0.tgz", + "integrity": "sha512-GojqklwG8gpzOVEVki5KudKNoq7MbbjYZCbyWzEz7tyPA7eleiE0+ePwOWQQRb5fm86rD3S8Tc0tSFf3AOv50w==", + "dev": true + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "engines": { + "node": ">=0.4" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -12078,6 +18625,18 @@ "node": ">=12" } }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/zone.js": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.12.0.tgz", diff --git a/front/app/package.json b/front/app/package.json index 67c9b726..89e35be2 100644 --- a/front/app/package.json +++ b/front/app/package.json @@ -6,35 +6,70 @@ "start": "ng serve", "build": "ng build", "watch": "ng build --watch --configuration development", - "test": "ng test" + "test": "ng test", + "build:prod": "ng build --prod", + "lint": "npm run lint:ts && npm run lint:scss", + "lint:ts": "eslint \"src/**/*.ts\" --fix", + "lint:scss": "stylelint \"src/**/*.scss\" --fix", + "hmr": "ng serve --hmr --disable-host-check" }, "private": true, "dependencies": { "@angular/animations": "^15.0.0", - "@angular/cdk": "^15.0.4", + "@angular/cdk": "~15.0.3", "@angular/common": "^15.0.0", "@angular/compiler": "^15.0.0", "@angular/core": "^15.0.0", "@angular/forms": "^15.0.0", - "@angular/material": "^15.0.4", + "@angular/material": "~15.0.3", + "@angular/material-moment-adapter": "~15.0.3", "@angular/platform-browser": "^15.0.0", "@angular/platform-browser-dynamic": "^15.0.0", "@angular/router": "^15.0.0", + "@ng-matero/extensions": "^15.0.1", + "@ng-matero/extensions-moment-adapter": "^15.0.0", + "@ngx-formly/core": "^6.0.4", + "@ngx-formly/material": "^6.0.4", + "@ngx-translate/core": "^14.0.0", + "@ngx-translate/http-loader": "^7.0.0", + "moment": "^2.29.4", + "ng-matero": "^15.1.1", + "ngx-permissions": "^14.0.0", + "ngx-progressbar": "^9.0.0", + "ngx-toastr": "^16.0.1", + "photoviewer": "^3.6.6", "rxjs": "~7.5.0", + "screenfull": "^6.0.2", "tslib": "^2.3.0", "zone.js": "~0.12.0" }, "devDependencies": { "@angular-devkit/build-angular": "^15.0.5", + "@angular-eslint/builder": "^15.1.0", + "@angular-eslint/eslint-plugin": "^15.1.0", + "@angular-eslint/eslint-plugin-template": "^15.1.0", + "@angular-eslint/schematics": "^15.1.0", + "@angular-eslint/template-parser": "^15.1.0", "@angular/cli": "~15.0.5", "@angular/compiler-cli": "^15.0.0", "@types/jasmine": "~4.3.0", + "@typescript-eslint/eslint-plugin": "^5.46.0", + "@typescript-eslint/parser": "^5.46.0", + "eslint": "^8.30.0", "jasmine-core": "~4.5.0", "karma": "~6.4.0", "karma-chrome-launcher": "~3.1.0", "karma-coverage": "~2.2.0", "karma-jasmine": "~5.1.0", "karma-jasmine-html-reporter": "~2.0.0", + "parse5": "^7.1.2", + "prettier": "^2.8.1", + "stylelint": "^14.16.0", + "stylelint-config-rational-order": "^0.1.2", + "stylelint-config-recommended-scss": "^8.0.0", + "stylelint-config-standard": "^29.0.0", + "stylelint-order": "^5.0.0", + "stylelint-scss": "^4.3.0", "typescript": "~4.8.2" } } \ No newline at end of file diff --git a/front/app/proxy.config.js b/front/app/proxy.config.js new file mode 100644 index 00000000..3e1f7cf1 --- /dev/null +++ b/front/app/proxy.config.js @@ -0,0 +1,22 @@ +// https://angular.io/guide/build#proxying-to-a-backend-server + +const PROXY_CONFIG = { + '/users/**': { + target: 'https://api.github.com', + changeOrigin: true, + secure: false, + logLevel: 'debug', + // onProxyReq: (proxyReq, req, res) => { + // const cookieMap = { + // SID: '', + // }; + // let cookie = ''; + // for (const key of Object.keys(cookieMap)) { + // cookie += `${key}=${cookieMap[key]}; `; + // } + // proxyReq.setHeader('cookie', cookie); + // }, + }, +}; + +module.exports = PROXY_CONFIG; diff --git a/front/app/src/app/app-routing.module.ts b/front/app/src/app/app-routing.module.ts deleted file mode 100644 index 02972627..00000000 --- a/front/app/src/app/app-routing.module.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { NgModule } from '@angular/core'; -import { RouterModule, Routes } from '@angular/router'; - -const routes: Routes = []; - -@NgModule({ - imports: [RouterModule.forRoot(routes)], - exports: [RouterModule] -}) -export class AppRoutingModule { } diff --git a/front/app/src/app/app.component.html b/front/app/src/app/app.component.html deleted file mode 100644 index e32be577..00000000 --- a/front/app/src/app/app.component.html +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/front/app/src/app/app.component.spec.ts b/front/app/src/app/app.component.spec.ts deleted file mode 100644 index d5efb8d5..00000000 --- a/front/app/src/app/app.component.spec.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { TestBed } from '@angular/core/testing'; -import { RouterTestingModule } from '@angular/router/testing'; -import { AppComponent } from './app.component'; - -describe('AppComponent', () => { - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [ - RouterTestingModule - ], - declarations: [ - AppComponent - ], - }).compileComponents(); - }); - - it('should create the app', () => { - const fixture = TestBed.createComponent(AppComponent); - const app = fixture.componentInstance; - expect(app).toBeTruthy(); - }); - - it(`should have as title 'app'`, () => { - const fixture = TestBed.createComponent(AppComponent); - const app = fixture.componentInstance; - expect(app.title).toEqual('app'); - }); - - it('should render title', () => { - const fixture = TestBed.createComponent(AppComponent); - fixture.detectChanges(); - const compiled = fixture.nativeElement as HTMLElement; - expect(compiled.querySelector('.content span')?.textContent).toContain('app app is running!'); - }); -}); diff --git a/front/app/src/app/app.component.ts b/front/app/src/app/app.component.ts index 7b0f6728..01bb15da 100644 --- a/front/app/src/app/app.component.ts +++ b/front/app/src/app/app.component.ts @@ -1,10 +1,16 @@ -import { Component } from '@angular/core'; +import { Component, OnInit, AfterViewInit } from '@angular/core'; +import { PreloaderService } from '@core'; @Component({ selector: 'app-root', - templateUrl: './app.component.html', - styleUrls: ['./app.component.css'] + template: '', }) -export class AppComponent { - title = 'app'; +export class AppComponent implements OnInit, AfterViewInit { + constructor(private preloader: PreloaderService) {} + + ngOnInit() {} + + ngAfterViewInit() { + this.preloader.hide(); + } } diff --git a/front/app/src/app/app.module.ts b/front/app/src/app/app.module.ts index d03631cb..c0bedb1e 100644 --- a/front/app/src/app/app.module.ts +++ b/front/app/src/app/app.module.ts @@ -1,23 +1,58 @@ import { NgModule } from '@angular/core'; +import { HttpClientModule, HttpClient } from '@angular/common/http'; import { BrowserModule } from '@angular/platform-browser'; -import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; -import { AuthComponent } from './auth/auth.component'; + +import { CoreModule } from '@core/core.module'; +import { ThemeModule } from '@theme/theme.module'; +import { SharedModule } from '@shared/shared.module'; +import { RoutesModule } from './routes/routes.module'; +import { FormlyConfigModule } from './formly-config.module'; +import { NgxPermissionsModule } from 'ngx-permissions'; +import { ToastrModule } from 'ngx-toastr'; +import { TranslateModule, TranslateLoader } from '@ngx-translate/core'; +import { TranslateHttpLoader } from '@ngx-translate/http-loader'; + +import { environment } from '@env/environment'; +import { BASE_URL, httpInterceptorProviders, appInitializerProviders } from '@core'; + +// Required for AOT compilation +export function TranslateHttpLoaderFactory(http: HttpClient) { + return new TranslateHttpLoader(http, './assets/i18n/', '.json'); +} + +import { LoginService } from '@core/authentication/login.service'; +import { FakeLoginService } from './fake-login.service'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; @NgModule({ - declarations: [ - AppComponent, - AuthComponent - - ], + declarations: [AppComponent], imports: [ BrowserModule, - AppRoutingModule, - BrowserAnimationsModule + HttpClientModule, + CoreModule, + ThemeModule, + RoutesModule, + SharedModule, + FormlyConfigModule.forRoot(), + NgxPermissionsModule.forRoot(), + ToastrModule.forRoot(), + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useFactory: TranslateHttpLoaderFactory, + deps: [HttpClient], + }, + }), + BrowserAnimationsModule, ], - providers: [], - bootstrap: [AppComponent] + providers: [ + { provide: BASE_URL, useValue: environment.baseUrl }, + { provide: LoginService, useClass: FakeLoginService }, // <= Remove it in the real APP + httpInterceptorProviders, + appInitializerProviders, + ], + bootstrap: [AppComponent], }) -export class AppModule { } +export class AppModule {} diff --git a/front/app/src/app/auth/auth.component.html b/front/app/src/app/auth/auth.component.html deleted file mode 100644 index f66eb695..00000000 --- a/front/app/src/app/auth/auth.component.html +++ /dev/null @@ -1 +0,0 @@ -

auth works!

diff --git a/front/app/src/app/auth/auth.component.spec.ts b/front/app/src/app/auth/auth.component.spec.ts deleted file mode 100644 index ce785350..00000000 --- a/front/app/src/app/auth/auth.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { AuthComponent } from './auth.component'; - -describe('AuthComponent', () => { - let component: AuthComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ AuthComponent ] - }) - .compileComponents(); - - fixture = TestBed.createComponent(AuthComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/front/app/src/app/auth/auth.component.ts b/front/app/src/app/auth/auth.component.ts deleted file mode 100644 index c45c6b7f..00000000 --- a/front/app/src/app/auth/auth.component.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Component } from '@angular/core'; - -@Component({ - selector: 'app-auth', - templateUrl: './auth.component.html', - styleUrls: ['./auth.component.css'] -}) -export class AuthComponent { - -} diff --git a/front/app/src/app/core/authentication/README.md b/front/app/src/app/core/authentication/README.md new file mode 100644 index 00000000..2517328e --- /dev/null +++ b/front/app/src/app/core/authentication/README.md @@ -0,0 +1,17 @@ +# Authentication + +1. Modify the token key at `token.service` to another name such as `TOKEN` or `your-app-token`. By default set to `ng-matero-token`. + +2. Replace the APIs at `login.service` with your owns. + + - `/auth/login` Login + - `/auth/refresh` Refresh + - `/auth/logout` Logout + - `/me` Get user information + - `/me/menu` Get user menu + +3. If you have modified the login url (defaults to `auth/login`), you should correct it in the following files. + + - `auth.guard.ts` + - `error-interceptor.ts` + - `token-interceptor.ts` diff --git a/front/app/src/app/core/authentication/auth.guard.spec.ts b/front/app/src/app/core/authentication/auth.guard.spec.ts new file mode 100644 index 00000000..d068838c --- /dev/null +++ b/front/app/src/app/core/authentication/auth.guard.spec.ts @@ -0,0 +1,56 @@ +import { TestBed } from '@angular/core/testing'; + +import { Router } from '@angular/router'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { Component } from '@angular/core'; +import { RouterTestingModule } from '@angular/router/testing'; +import { LocalStorageService, MemoryStorageService } from '@shared/services/storage.service'; +import { TokenService, AuthGuard, AuthService } from '@core/authentication'; + +@Component({ template: '' }) +class DummyComponent {} + +describe('AuthGuard', () => { + const route: any = {}; + const state: any = {}; + let router: Router; + let authGuard: AuthGuard; + let authService: AuthService; + let tokenService: TokenService; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ + HttpClientTestingModule, + RouterTestingModule.withRoutes([ + { path: 'dashboard', component: DummyComponent, canActivate: [AuthGuard] }, + { path: 'auth/login', component: DummyComponent }, + ]), + ], + declarations: [DummyComponent], + providers: [{ provide: LocalStorageService, useClass: MemoryStorageService }], + }); + TestBed.createComponent(DummyComponent); + + router = TestBed.inject(Router); + authGuard = TestBed.inject(AuthGuard); + authService = TestBed.inject(AuthService); + tokenService = TestBed.inject(TokenService); + }); + + it('should be created', () => { + expect(authGuard).toBeTruthy(); + }); + + it('should be authenticated', () => { + tokenService.set({ access_token: 'token', token_type: 'bearer' }); + + expect(authGuard.canActivate(route, state)).toBeTrue(); + }); + + it('should redirect to /auth/login when authenticate failed', () => { + spyOn(authService, 'check').and.returnValue(false); + + expect(authGuard.canActivate(route, state)).toEqual(router.parseUrl('/auth/login')); + }); +}); diff --git a/front/app/src/app/core/authentication/auth.guard.ts b/front/app/src/app/core/authentication/auth.guard.ts new file mode 100644 index 00000000..491dd640 --- /dev/null +++ b/front/app/src/app/core/authentication/auth.guard.ts @@ -0,0 +1,32 @@ +import { Injectable } from '@angular/core'; +import { + ActivatedRouteSnapshot, + CanActivate, + CanActivateChild, + Router, + RouterStateSnapshot, + UrlTree, +} from '@angular/router'; +import { AuthService } from './auth.service'; + +@Injectable({ + providedIn: 'root', +}) +export class AuthGuard implements CanActivate, CanActivateChild { + constructor(private auth: AuthService, private router: Router) {} + + canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { + return this.authenticate(); + } + + canActivateChild( + childRoute: ActivatedRouteSnapshot, + state: RouterStateSnapshot + ): boolean | UrlTree { + return this.authenticate(); + } + + private authenticate(): boolean | UrlTree { + return this.auth.check() ? true : this.router.parseUrl('/auth/login'); + } +} diff --git a/front/app/src/app/core/authentication/auth.service.spec.ts b/front/app/src/app/core/authentication/auth.service.spec.ts new file mode 100644 index 00000000..81df5a01 --- /dev/null +++ b/front/app/src/app/core/authentication/auth.service.spec.ts @@ -0,0 +1,133 @@ +import { fakeAsync, TestBed, tick } from '@angular/core/testing'; + +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; +import { Observable } from 'rxjs'; +import { skip } from 'rxjs/operators'; +import { HttpRequest } from '@angular/common/http'; +import { LocalStorageService, MemoryStorageService } from '@shared/services/storage.service'; +import { AuthService, LoginService, TokenService, User } from '@core/authentication'; + +describe('AuthService', () => { + let authService: AuthService; + let loginService: LoginService; + let tokenService: TokenService; + let httpMock: HttpTestingController; + let user$: Observable; + const email = 'foo@bar.com'; + const token = { access_token: 'token', token_type: 'bearer' }; + const user = { id: 1, email }; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + providers: [{ provide: LocalStorageService, useClass: MemoryStorageService }], + }); + loginService = TestBed.inject(LoginService); + authService = TestBed.inject(AuthService); + tokenService = TestBed.inject(TokenService); + httpMock = TestBed.inject(HttpTestingController); + + user$ = authService.user(); + authService.change().subscribe(user => { + expect(user).toBeInstanceOf(Object); + }); + }); + + afterEach(() => httpMock.verify()); + + it('should be created', () => { + expect(authService).toBeTruthy(); + }); + + it('should log in failed', () => { + authService.login(email, 'password', false).subscribe(isLogin => expect(isLogin).toBeFalse()); + httpMock.expectOne('/auth/login').flush({}); + + expect(authService.check()).toBeFalse(); + }); + + it('should log in successful and get user info', () => { + user$.pipe(skip(1)).subscribe(currentUser => expect(currentUser.id).toEqual(user.id)); + authService.login(email, 'password', false).subscribe(isLogin => expect(isLogin).toBeTrue()); + httpMock.expectOne('/auth/login').flush(token); + + expect(authService.check()).toBeTrue(); + httpMock.expectOne('/me').flush(user); + }); + + it('should log out failed when user is not login', () => { + spyOn(loginService, 'logout').and.callThrough(); + expect(authService.check()).toBeFalse(); + + authService.logout().subscribe(); + httpMock.expectOne('/auth/logout'); + + expect(authService.check()).toBeFalse(); + expect(loginService.logout).toHaveBeenCalled(); + }); + + it('should log out successful when user is login', () => { + tokenService.set(token); + expect(authService.check()).toBeTrue(); + httpMock.expectOne('/me').flush(user); + + user$.pipe(skip(1)).subscribe(currentUser => expect(currentUser.id).toBeUndefined()); + authService.logout().subscribe(); + httpMock.expectOne('/auth/logout').flush({}); + + expect(authService.check()).toBeFalse(); + }); + + it('should refresh token when access_token is valid', fakeAsync(() => { + tokenService.set(Object.assign({ expires_in: 5 }, token)); + expect(authService.check()).toBeTrue(); + httpMock.expectOne('/me').flush(user); + const match = (req: HttpRequest) => req.url === '/auth/refresh' && !req.body.refresh_token; + + tick(4000); + expect(authService.check()).toBeTrue(); + httpMock.match(match)[0].flush(token); + + expect(authService.check()).toBeTrue(); + httpMock.expectNone('/me'); + tokenService.ngOnDestroy(); + })); + + it('should refresh token when access_token is invalid and refresh_token is valid', fakeAsync(() => { + tokenService.set(Object.assign({ expires_in: 5, refresh_token: 'foo' }, token)); + const match = (req: HttpRequest) => + req.url === '/auth/refresh' && req.body.refresh_token === 'foo'; + + expect(authService.check()).toBeTrue(); + httpMock.expectOne('/me').flush(user); + tick(10000); + expect(authService.check()).toBeFalse(); + httpMock.match(match)[0].flush(token); + + expect(authService.check()).toBeTrue(); + httpMock.expectNone('/me'); + tokenService.ngOnDestroy(); + })); + + it('it should clear token when access_token is invalid and refresh token response is 401', fakeAsync(() => { + spyOn(tokenService, 'set').and.callThrough(); + tokenService.set(Object.assign({ expires_in: 5, refresh_token: 'foo' }, token)); + const match = (req: HttpRequest) => + req.url === '/auth/refresh' && req.body.refresh_token === 'foo'; + + tick(10000); + expect(authService.check()).toBeFalse(); + httpMock.expectOne('/me').flush({}); + httpMock.match(match)[0].flush({}, { status: 401, statusText: 'Unauthorized' }); + + expect(authService.check()).toBeFalse(); + expect(tokenService.set).toHaveBeenCalledWith(undefined); + tokenService.ngOnDestroy(); + })); + + it('it only call http request once when on change subscribe twice', () => { + authService.change().subscribe(); + tokenService.set(token); + httpMock.expectOne('/me').flush({}); + }); +}); diff --git a/front/app/src/app/core/authentication/auth.service.ts b/front/app/src/app/core/authentication/auth.service.ts new file mode 100644 index 00000000..1769588d --- /dev/null +++ b/front/app/src/app/core/authentication/auth.service.ts @@ -0,0 +1,79 @@ +import { Injectable } from '@angular/core'; +import { BehaviorSubject, iif, merge, of } from 'rxjs'; +import { catchError, map, share, switchMap, tap } from 'rxjs/operators'; +import { TokenService } from './token.service'; +import { LoginService } from './login.service'; +import { filterObject, isEmptyObject } from './helpers'; +import { User } from './interface'; + +@Injectable({ + providedIn: 'root', +}) +export class AuthService { + private user$ = new BehaviorSubject({}); + private change$ = merge( + this.tokenService.change(), + this.tokenService.refresh().pipe(switchMap(() => this.refresh())) + ).pipe( + switchMap(() => this.assignUser()), + share() + ); + + constructor(private loginService: LoginService, private tokenService: TokenService) {} + + init() { + return new Promise(resolve => this.change$.subscribe(() => resolve())); + } + + change() { + return this.change$; + } + + check() { + return this.tokenService.valid(); + } + + login(username: string, password: string, rememberMe = false) { + return this.loginService.login(username, password, rememberMe).pipe( + tap(token => this.tokenService.set(token)), + map(() => this.check()) + ); + } + + refresh() { + return this.loginService + .refresh(filterObject({ refresh_token: this.tokenService.getRefreshToken() })) + .pipe( + catchError(() => of(undefined)), + tap(token => this.tokenService.set(token)), + map(() => this.check()) + ); + } + + logout() { + return this.loginService.logout().pipe( + tap(() => this.tokenService.clear()), + map(() => !this.check()) + ); + } + + user() { + return this.user$.pipe(share()); + } + + menu() { + return iif(() => this.check(), this.loginService.menu(), of([])); + } + + private assignUser() { + if (!this.check()) { + return of({}).pipe(tap(user => this.user$.next(user))); + } + + if (!isEmptyObject(this.user$.getValue())) { + return of(this.user$.getValue()); + } + + return this.loginService.me().pipe(tap(user => this.user$.next(user))); + } +} diff --git a/front/app/src/app/core/authentication/helpers.ts b/front/app/src/app/core/authentication/helpers.ts new file mode 100644 index 00000000..a6f2aeff --- /dev/null +++ b/front/app/src/app/core/authentication/helpers.ts @@ -0,0 +1,57 @@ +import { fromByteArray, toByteArray } from 'base64-js'; + +export class Base64 { + static encode(plainText: string): string { + return fromByteArray(pack(plainText)).replace(/[+/=]/g, m => { + return { '+': '-', '/': '_', '=': '' }[m] as string; + }); + } + + static decode(b64: string): string { + b64 = b64.replace(/[-_]/g, m => { + return { '-': '+', '_': '/' }[m] as string; + }); + while (b64.length % 4) { + b64 += '='; + } + + return unpack(toByteArray(b64)); + } +} + +export function pack(str: string) { + const bytes: any = []; + for (let i = 0; i < str.length; i++) { + bytes.push(...[str.charCodeAt(i)]); + } + + return bytes; +} + +export function unpack(byteArray: any) { + return String.fromCharCode(...byteArray); +} + +export const base64 = { encode: Base64.encode, decode: Base64.decode }; + +export function capitalize(text: string): string { + return text.substring(0, 1).toUpperCase() + text.substring(1, text.length).toLowerCase(); +} + +export function currentTimestamp(): number { + return Math.ceil(new Date().getTime() / 1000); +} + +export function timeLeft(expiredAt: number): number { + return Math.max(0, expiredAt - currentTimestamp()); +} + +export function filterObject>(obj: T) { + return Object.fromEntries( + Object.entries(obj).filter(([, value]) => value !== undefined && value !== null) + ); +} + +export function isEmptyObject(obj: Record) { + return Object.keys(obj).length === 0; +} diff --git a/front/app/src/app/core/authentication/index.ts b/front/app/src/app/core/authentication/index.ts new file mode 100644 index 00000000..e3bb2056 --- /dev/null +++ b/front/app/src/app/core/authentication/index.ts @@ -0,0 +1,9 @@ +export * from './interface'; +export * from './auth.guard'; +export * from './auth.service'; +export * from './token-factory.service'; +export * from './token.service'; +export * from './token'; +export * from './login.service'; +export * from './user'; +export * from './helpers'; diff --git a/front/app/src/app/core/authentication/interface.ts b/front/app/src/app/core/authentication/interface.ts new file mode 100644 index 00000000..ce50657a --- /dev/null +++ b/front/app/src/app/core/authentication/interface.ts @@ -0,0 +1,20 @@ +export interface User { + [prop: string]: any; + + id?: number | string | null; + name?: string; + email?: string; + avatar?: string; + roles?: any[]; + permissions?: any[]; +} + +export interface Token { + [prop: string]: any; + + access_token: string; + token_type?: string; + expires_in?: number; + exp?: number; + refresh_token?: string; +} diff --git a/front/app/src/app/core/authentication/login.service.ts b/front/app/src/app/core/authentication/login.service.ts new file mode 100644 index 00000000..4d3a7658 --- /dev/null +++ b/front/app/src/app/core/authentication/login.service.ts @@ -0,0 +1,32 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Token, User } from './interface'; +import { Menu } from '@core'; +import { map } from 'rxjs/operators'; + +@Injectable({ + providedIn: 'root', +}) +export class LoginService { + constructor(protected http: HttpClient) {} + + login(username: string, password: string, rememberMe = false) { + return this.http.post('/auth/login', { username, password, rememberMe }); + } + + refresh(params: Record) { + return this.http.post('/auth/refresh', params); + } + + logout() { + return this.http.post('/auth/logout', {}); + } + + me() { + return this.http.get('/me'); + } + + menu() { + return this.http.get<{ menu: Menu[] }>('/me/menu').pipe(map(res => res.menu)); + } +} diff --git a/front/app/src/app/core/authentication/token-factory.service.ts b/front/app/src/app/core/authentication/token-factory.service.ts new file mode 100644 index 00000000..99203297 --- /dev/null +++ b/front/app/src/app/core/authentication/token-factory.service.ts @@ -0,0 +1,20 @@ +import { Injectable } from '@angular/core'; +import { Token } from './interface'; +import { SimpleToken, JwtToken, BaseToken } from './token'; + +@Injectable({ + providedIn: 'root', +}) +export class TokenFactory { + create(attributes: Token): BaseToken | undefined { + if (!attributes.access_token) { + return undefined; + } + + if (JwtToken.is(attributes.access_token)) { + return new JwtToken(attributes); + } + + return new SimpleToken(attributes); + } +} diff --git a/front/app/src/app/core/authentication/token.service.spec.ts b/front/app/src/app/core/authentication/token.service.spec.ts new file mode 100644 index 00000000..2e424e5a --- /dev/null +++ b/front/app/src/app/core/authentication/token.service.spec.ts @@ -0,0 +1,52 @@ +import { TestBed } from '@angular/core/testing'; +import { tap } from 'rxjs/operators'; +import { MemoryStorageService, LocalStorageService } from '@shared/services/storage.service'; +import { TokenService, currentTimestamp, TokenFactory, SimpleToken } from '@core/authentication'; + +describe('TokenService', () => { + let tokenService: TokenService; + let tokenFactory: TokenFactory; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [{ provide: LocalStorageService, useClass: MemoryStorageService }], + }); + tokenService = TestBed.inject(TokenService); + tokenFactory = TestBed.inject(TokenFactory); + }); + + it('should be created', () => { + expect(tokenService).toBeTruthy(); + }); + + it('should get authorization header value', () => { + tokenService.set({ access_token: 'token', token_type: 'bearer' }); + + expect(tokenService.getBearerToken()).toEqual('Bearer token'); + }); + + it('cannot get authorization header value', () => { + tokenService.set({ access_token: '', token_type: 'bearer' }); + + expect(tokenService.getBearerToken()).toBe(''); + }); + + it('should not has exp when token has expires_in', () => { + tokenService.set({ access_token: 'token', token_type: 'bearer' }); + + tokenService + .change() + .pipe(tap(token => expect(token!.exp).toBeUndefined())) + .subscribe(); + }); + + it('should has exp when token has expires_in', () => { + const expiresIn = 3600; + tokenService.set({ access_token: 'token', token_type: 'bearer', expires_in: expiresIn }); + + tokenService + .change() + .pipe(tap(token => expect(token!.exp).toEqual(currentTimestamp() + expiresIn))) + .subscribe(); + }); +}); diff --git a/front/app/src/app/core/authentication/token.service.ts b/front/app/src/app/core/authentication/token.service.ts new file mode 100644 index 00000000..c78bb3d8 --- /dev/null +++ b/front/app/src/app/core/authentication/token.service.ts @@ -0,0 +1,99 @@ +import { Injectable, OnDestroy } from '@angular/core'; +import { BehaviorSubject, Observable, Subject, Subscription, timer } from 'rxjs'; +import { share } from 'rxjs/operators'; +import { LocalStorageService } from '@shared'; +import { Token } from './interface'; +import { BaseToken } from './token'; +import { TokenFactory } from './token-factory.service'; +import { currentTimestamp, filterObject } from './helpers'; + +@Injectable({ + providedIn: 'root', +}) +export class TokenService implements OnDestroy { + private key = 'ng-matero-token'; + + private change$ = new BehaviorSubject(undefined); + private refresh$ = new Subject(); + private timer$?: Subscription; + + private _token?: BaseToken; + + constructor(private store: LocalStorageService, private factory: TokenFactory) {} + + private get token(): BaseToken | undefined { + if (!this._token) { + this._token = this.factory.create(this.store.get(this.key)); + } + + return this._token; + } + + change(): Observable { + return this.change$.pipe(share()); + } + + refresh(): Observable { + this.buildRefresh(); + + return this.refresh$.pipe(share()); + } + + set(token?: Token): TokenService { + this.save(token); + + return this; + } + + clear(): void { + this.save(); + } + + valid(): boolean { + return this.token?.valid() ?? false; + } + + getBearerToken(): string { + return this.token?.getBearerToken() ?? ''; + } + + getRefreshToken(): string | void { + return this.token?.refresh_token; + } + + ngOnDestroy(): void { + this.clearRefresh(); + } + + private save(token?: Token): void { + this._token = undefined; + + if (!token) { + this.store.remove(this.key); + } else { + const value = Object.assign({ access_token: '', token_type: 'Bearer' }, token, { + exp: token.expires_in ? currentTimestamp() + token.expires_in : null, + }); + this.store.set(this.key, filterObject(value)); + } + + this.change$.next(this.token); + this.buildRefresh(); + } + + private buildRefresh() { + this.clearRefresh(); + + if (this.token?.needRefresh()) { + this.timer$ = timer(this.token.getRefreshTime() * 1000).subscribe(() => { + this.refresh$.next(this.token); + }); + } + } + + private clearRefresh() { + if (this.timer$ && !this.timer$.closed) { + this.timer$.unsubscribe(); + } + } +} diff --git a/front/app/src/app/core/authentication/token.spec.ts b/front/app/src/app/core/authentication/token.spec.ts new file mode 100644 index 00000000..2918699c --- /dev/null +++ b/front/app/src/app/core/authentication/token.spec.ts @@ -0,0 +1,41 @@ +import { base64, currentTimestamp, JwtToken } from '@core/authentication'; + +describe('Token', () => { + describe('JwtToken', () => { + function generateToken(params: any, typ = 'JWT') { + return [ + base64.encode(JSON.stringify({ typ, alg: 'HS256' })), + base64.encode(JSON.stringify(params)), + base64.encode('ng-matero'), + ].join('.'); + } + + const exp = currentTimestamp() + 3600; + const token = new JwtToken({ + access_token: generateToken({ exp }, 'at+JWT'), + token_type: 'Bearer', + }); + + it('test access_token is JWT', () => { + expect(JwtToken.is(token.access_token)).toBeTrue(); + }); + + it('test bearer token', function () { + expect(token.getBearerToken()).toBe(`Bearer ${token.access_token}`); + }); + + it('test payload has exp attribute', () => { + expect(token.exp).toEqual(exp); + }); + + it('test payload does not has exp attribute', () => { + expect(token.exp).toEqual(exp); + }); + + it('test does not has exp attribute', () => { + const token = new JwtToken({ access_token: generateToken({}), token_type: 'Bearer' }); + + expect(token.exp).toBeUndefined(); + }); + }); +}); diff --git a/front/app/src/app/core/authentication/token.ts b/front/app/src/app/core/authentication/token.ts new file mode 100644 index 00000000..f622cf89 --- /dev/null +++ b/front/app/src/app/core/authentication/token.ts @@ -0,0 +1,87 @@ +import { base64, capitalize, currentTimestamp, timeLeft } from './helpers'; +import { Token } from './interface'; + +export abstract class BaseToken { + constructor(protected attributes: Token) {} + + get access_token(): string { + return this.attributes.access_token; + } + + get refresh_token(): string | void { + return this.attributes.refresh_token; + } + + get token_type(): string { + return this.attributes.token_type ?? 'bearer'; + } + + get exp(): number | void { + return this.attributes.exp; + } + + valid(): boolean { + return this.hasAccessToken() && !this.isExpired(); + } + + getBearerToken(): string { + return this.access_token + ? [capitalize(this.token_type), this.access_token].join(' ').trim() + : ''; + } + + needRefresh(): boolean { + return this.exp !== undefined && this.exp >= 0; + } + + getRefreshTime(): number { + return timeLeft((this.exp ?? 0) - 5); + } + + private hasAccessToken(): boolean { + return !!this.access_token; + } + + private isExpired(): boolean { + return this.exp !== undefined && this.exp - currentTimestamp() <= 0; + } +} + +export class SimpleToken extends BaseToken {} + +export class JwtToken extends SimpleToken { + private _payload?: { exp?: number | void }; + + static is(accessToken: string): boolean { + try { + const [_header] = accessToken.split('.'); + const header = JSON.parse(base64.decode(_header)); + + return header.typ.toUpperCase().includes('JWT'); + } catch (e) { + return false; + } + } + + get exp(): number | void { + return this.payload?.exp; + } + + private get payload(): { exp?: number | void } { + if (!this.access_token) { + return {}; + } + + if (this._payload) { + return this._payload; + } + + const [, payload] = this.access_token.split('.'); + const data = JSON.parse(base64.decode(payload)); + if (!data.exp) { + data.exp = this.attributes.exp; + } + + return (this._payload = data); + } +} diff --git a/front/app/src/app/core/authentication/user.ts b/front/app/src/app/core/authentication/user.ts new file mode 100644 index 00000000..332504aa --- /dev/null +++ b/front/app/src/app/core/authentication/user.ts @@ -0,0 +1,14 @@ +import { User } from './interface'; + +export const admin: User = { + id: 1, + name: 'Zongbin', + email: 'nzb329@163.com', + avatar: './assets/images/avatar.jpg', +}; + +export const guest: User = { + name: 'unknown', + email: 'unknown', + avatar: './assets/images/avatar-default.jpg', +}; diff --git a/front/app/src/app/core/bootstrap/README.md b/front/app/src/app/core/bootstrap/README.md new file mode 100644 index 00000000..c769e407 --- /dev/null +++ b/front/app/src/app/core/bootstrap/README.md @@ -0,0 +1,3 @@ +# Bootstrap + +The services in this folder should be singletons and used for sharing data and functionality. diff --git a/front/app/src/app/core/bootstrap/menu.service.ts b/front/app/src/app/core/bootstrap/menu.service.ts new file mode 100644 index 00000000..148caf23 --- /dev/null +++ b/front/app/src/app/core/bootstrap/menu.service.ts @@ -0,0 +1,150 @@ +import { Injectable } from '@angular/core'; +import { BehaviorSubject, Observable } from 'rxjs'; +import { share } from 'rxjs/operators'; + +export interface MenuTag { + color: string; // background color + value: string; +} + +export interface MenuPermissions { + only?: string | string[]; + except?: string | string[]; +} + +export interface MenuChildrenItem { + route: string; + name: string; + type: 'link' | 'sub' | 'extLink' | 'extTabLink'; + children?: MenuChildrenItem[]; + permissions?: MenuPermissions; +} + +export interface Menu { + route: string; + name: string; + type: 'link' | 'sub' | 'extLink' | 'extTabLink'; + icon: string; + label?: MenuTag; + badge?: MenuTag; + children?: MenuChildrenItem[]; + permissions?: MenuPermissions; +} + +@Injectable({ + providedIn: 'root', +}) +export class MenuService { + private menu$: BehaviorSubject = new BehaviorSubject([]); + + /** Get all the menu data. */ + getAll(): Observable { + return this.menu$.asObservable(); + } + + /** Observe the change of menu data. */ + change(): Observable { + return this.menu$.pipe(share()); + } + + /** Initialize the menu data. */ + set(menu: Menu[]): Observable { + this.menu$.next(menu); + return this.menu$.asObservable(); + } + + /** Add one item to the menu data. */ + add(menu: Menu) { + const tmpMenu = this.menu$.value; + tmpMenu.push(menu); + this.menu$.next(tmpMenu); + } + + /** Reset the menu data. */ + reset() { + this.menu$.next([]); + } + + /** Delete empty values and rebuild route. */ + buildRoute(routeArr: string[]): string { + let route = ''; + routeArr.forEach(item => { + if (item && item.trim()) { + route += '/' + item.replace(/^\/+|\/+$/g, ''); + } + }); + return route; + } + + /** Get the menu item name based on current route. */ + getItemName(routeArr: string[]): string { + return this.getLevel(routeArr)[routeArr.length - 1]; + } + + // Whether is a leaf menu + private isLeafItem(item: MenuChildrenItem): boolean { + const cond0 = item.route === undefined; + const cond1 = item.children === undefined; + const cond2 = !cond1 && item.children?.length === 0; + return cond0 || cond1 || cond2; + } + + // Deep clone object could be jsonized + private deepClone(obj: any): any { + return JSON.parse(JSON.stringify(obj)); + } + + // Whether two objects could be jsonized equal + private isJsonObjEqual(obj0: any, obj1: any): boolean { + return JSON.stringify(obj0) === JSON.stringify(obj1); + } + + // Whether routeArr equals realRouteArr (after remove empty route element) + private isRouteEqual(routeArr: Array, realRouteArr: Array): boolean { + realRouteArr = this.deepClone(realRouteArr); + realRouteArr = realRouteArr.filter(r => r !== ''); + return this.isJsonObjEqual(routeArr, realRouteArr); + } + + /** Get the menu level. */ + getLevel(routeArr: string[]): string[] { + let tmpArr: any[] = []; + this.menu$.value.forEach(item => { + // Breadth-first traverse + let unhandledLayer = [{ item, parentNamePathList: [], realRouteArr: [] }]; + while (unhandledLayer.length > 0) { + let nextUnhandledLayer: any[] = []; + for (const ele of unhandledLayer) { + const eachItem = ele.item; + const currentNamePathList = this.deepClone(ele.parentNamePathList).concat(eachItem.name); + const currentRealRouteArr = this.deepClone(ele.realRouteArr).concat(eachItem.route); + // Compare the full Array for expandable + if (this.isRouteEqual(routeArr, currentRealRouteArr)) { + tmpArr = currentNamePathList; + break; + } + if (!this.isLeafItem(eachItem)) { + const wrappedChildren = eachItem.children?.map(child => ({ + item: child, + parentNamePathList: currentNamePathList, + realRouteArr: currentRealRouteArr, + })); + nextUnhandledLayer = nextUnhandledLayer.concat(wrappedChildren); + } + } + unhandledLayer = nextUnhandledLayer; + } + }); + return tmpArr; + } + + /** Add namespace for translation. */ + addNamespace(menu: Menu[] | MenuChildrenItem[], namespace: string) { + menu.forEach(menuItem => { + menuItem.name = `${namespace}.${menuItem.name}`; + if (menuItem.children && menuItem.children.length > 0) { + this.addNamespace(menuItem.children, menuItem.name); + } + }); + } +} diff --git a/front/app/src/app/core/bootstrap/preloader.service.ts b/front/app/src/app/core/bootstrap/preloader.service.ts new file mode 100644 index 00000000..d19e8823 --- /dev/null +++ b/front/app/src/app/core/bootstrap/preloader.service.ts @@ -0,0 +1,28 @@ +import { Inject, Injectable } from '@angular/core'; +import { DOCUMENT } from '@angular/common'; + +@Injectable({ + providedIn: 'root', +}) +export class PreloaderService { + private selector = 'globalLoader'; + + constructor(@Inject(DOCUMENT) private document: Document) {} + + private getElement() { + return this.document.getElementById(this.selector); + } + + hide() { + const el = this.getElement(); + if (el) { + el.addEventListener('transitionend', () => { + el.className = 'global-loader-hidden'; + }); + + if (!el.classList.contains('global-loader-hidden')) { + el.className += ' global-loader-fade-in'; + } + } + } +} diff --git a/front/app/src/app/core/bootstrap/sanctum.service.spec.ts b/front/app/src/app/core/bootstrap/sanctum.service.spec.ts new file mode 100644 index 00000000..30415504 --- /dev/null +++ b/front/app/src/app/core/bootstrap/sanctum.service.spec.ts @@ -0,0 +1,78 @@ +import { TestBed } from '@angular/core/testing'; + +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; +import { HttpClient } from '@angular/common/http'; +import { BASE_URL } from '../interceptors/base-url-interceptor'; +import { SANCTUM_PREFIX, SanctumService } from '@core'; + +describe('SanctumService', () => { + let httpMock: HttpTestingController; + let http: HttpClient; + let sanctumService: SanctumService; + + const setBaseUrlAndSanctumPrefix = (baseUrl: string | null, sanctumPrefix: string | null) => { + TestBed.overrideProvider(BASE_URL, { useValue: baseUrl }); + TestBed.overrideProvider(SANCTUM_PREFIX, { useValue: sanctumPrefix }); + + httpMock = TestBed.inject(HttpTestingController); + http = TestBed.inject(HttpClient); + sanctumService = TestBed.inject(SanctumService); + }; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + providers: [ + { provide: BASE_URL, useValue: null }, + { provide: SANCTUM_PREFIX, useValue: null }, + SanctumService, + ], + }); + }); + + afterEach(() => httpMock.verify()); + + it('should get csrf cookie once', done => { + setBaseUrlAndSanctumPrefix(null, null); + + sanctumService.load().then(data => { + expect(data).toEqual({ cookie: true }); + done(); + }); + + httpMock.expectOne('/sanctum/csrf-cookie').flush({ cookie: true }); + }); + + it('should get csrf cookie with base url', done => { + setBaseUrlAndSanctumPrefix('http://foo.bar/api', ''); + + sanctumService.load().then((data: any) => { + expect(data).toEqual({ cookie: true }); + done(); + }); + + httpMock.expectOne('http://foo.bar/sanctum/csrf-cookie').flush({ cookie: true }); + }); + + it('should get csrf cookie with sanctum prefix', done => { + setBaseUrlAndSanctumPrefix(null, '/foobar/'); + + sanctumService.load().then((data: any) => { + expect(data).toEqual({ cookie: true }); + done(); + }); + + httpMock.expectOne('/foobar/csrf-cookie').flush({ cookie: true }); + }); + + it('should get csrf cookie with base url and sanctum prefix', done => { + setBaseUrlAndSanctumPrefix('http://foo.bar/api/', '/foobar'); + + sanctumService.load().then((data: any) => { + expect(data).toEqual({ cookie: true }); + done(); + }); + + httpMock.expectOne('http://foo.bar/foobar/csrf-cookie').flush({ cookie: true }); + }); +}); diff --git a/front/app/src/app/core/bootstrap/sanctum.service.ts b/front/app/src/app/core/bootstrap/sanctum.service.ts new file mode 100644 index 00000000..24b50015 --- /dev/null +++ b/front/app/src/app/core/bootstrap/sanctum.service.ts @@ -0,0 +1,38 @@ +import { Inject, Injectable, InjectionToken, Optional } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { BASE_URL } from '../interceptors/base-url-interceptor'; + +export const SANCTUM_PREFIX = new InjectionToken('SANCTUM_PREFIX'); + +@Injectable({ + providedIn: 'root', +}) +export class SanctumService { + constructor( + private http: HttpClient, + @Optional() @Inject(BASE_URL) private baseUrl?: string, + @Optional() @Inject(SANCTUM_PREFIX) private prefix?: string + ) {} + + load(): Promise { + return new Promise(resolve => this.toObservable().subscribe(resolve)); + } + + toObservable(): Observable { + return this.http.get(this.getUrl()); + } + + private getUrl(): string { + const prefix = this.prefix || 'sanctum'; + const path = `/${prefix.replace(/^\/|\/$/g, '')}/csrf-cookie`; + + if (!this.baseUrl) { + return path; + } + + const url = new URL(this.baseUrl); + + return url.origin + path; + } +} diff --git a/front/app/src/app/core/bootstrap/settings.service.ts b/front/app/src/app/core/bootstrap/settings.service.ts new file mode 100644 index 00000000..c377ba6a --- /dev/null +++ b/front/app/src/app/core/bootstrap/settings.service.ts @@ -0,0 +1,34 @@ +import { Injectable } from '@angular/core'; +import { BehaviorSubject, Observable } from 'rxjs'; +import { AppSettings, defaults } from '../settings'; + +@Injectable({ + providedIn: 'root', +}) +export class SettingsService { + get notify(): Observable> { + return this.notify$.asObservable(); + } + + private notify$ = new BehaviorSubject>({}); + + getOptions() { + return this.options; + } + + setOptions(options: AppSettings) { + this.options = Object.assign(defaults, options); + this.notify$.next(this.options); + } + + private options = defaults; + + getLanguage() { + return this.options.language; + } + + setLanguage(lang: string) { + this.options.language = lang; + this.notify$.next({ lang }); + } +} diff --git a/front/app/src/app/core/bootstrap/startup.service.spec.ts b/front/app/src/app/core/bootstrap/startup.service.spec.ts new file mode 100644 index 00000000..d2c1149f --- /dev/null +++ b/front/app/src/app/core/bootstrap/startup.service.spec.ts @@ -0,0 +1,87 @@ +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; +import { TestBed } from '@angular/core/testing'; +import { NgxPermissionsModule, NgxPermissionsService, NgxRolesService } from 'ngx-permissions'; +import { LocalStorageService, MemoryStorageService } from '@shared/services/storage.service'; +import { admin, TokenService } from '@core/authentication'; +import { MenuService } from '@core/bootstrap/menu.service'; +import { StartupService } from '@core/bootstrap/startup.service'; + +describe('StartupService', () => { + let httpMock: HttpTestingController; + let startup: StartupService; + let tokenService: TokenService; + let menuService: MenuService; + let mockPermissionsService: NgxPermissionsService; + let mockRolesService: NgxRolesService; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule, NgxPermissionsModule.forRoot()], + providers: [ + { + provide: LocalStorageService, + useClass: MemoryStorageService, + }, + { + provide: NgxPermissionsService, + useValue: { + loadPermissions: (permissions: string[]) => void 0, + }, + }, + { + provide: NgxRolesService, + useValue: { + flushRoles: () => void 0, + addRoles: (params: { ADMIN: string[] }) => void 0, + }, + }, + StartupService, + ], + }); + httpMock = TestBed.inject(HttpTestingController); + startup = TestBed.inject(StartupService); + tokenService = TestBed.inject(TokenService); + menuService = TestBed.inject(MenuService); + mockPermissionsService = TestBed.inject(NgxPermissionsService); + mockRolesService = TestBed.inject(NgxRolesService); + }); + + afterEach(() => httpMock.verify()); + + it('should load menu when token changed and token valid', async () => { + const menuData = { menu: [] }; + const permissions = ['canAdd', 'canDelete', 'canEdit', 'canRead']; + spyOn(menuService, 'addNamespace'); + spyOn(menuService, 'set'); + spyOn(mockPermissionsService, 'loadPermissions'); + spyOn(mockRolesService, 'flushRoles'); + spyOn(mockRolesService, 'addRoles'); + + await startup.load(); + + tokenService.set({ access_token: 'token', token_type: 'bearer' }); + + httpMock.expectOne('/me').flush(admin); + httpMock.expectOne('/me/menu').flush(menuData); + + expect(menuService.addNamespace).toHaveBeenCalledWith(menuData.menu, 'menu'); + expect(menuService.set).toHaveBeenCalledWith(menuData.menu); + expect(mockPermissionsService.loadPermissions).toHaveBeenCalledWith(permissions); + expect(mockRolesService.flushRoles).toHaveBeenCalledWith(); + expect(mockRolesService.addRoles).toHaveBeenCalledWith({ ADMIN: permissions }); + }); + + it('should clear menu when token changed and token invalid', async () => { + spyOn(menuService, 'addNamespace'); + spyOn(menuService, 'set'); + + await startup.load(); + + tokenService.set({ access_token: '', token_type: 'bearer' }); + + httpMock.expectNone('/me/menu'); + + expect(menuService.addNamespace).toHaveBeenCalledWith([], 'menu'); + expect(menuService.set).toHaveBeenCalledWith([]); + }); +}); diff --git a/front/app/src/app/core/bootstrap/startup.service.ts b/front/app/src/app/core/bootstrap/startup.service.ts new file mode 100644 index 00000000..8d94431e --- /dev/null +++ b/front/app/src/app/core/bootstrap/startup.service.ts @@ -0,0 +1,53 @@ +import { Injectable } from '@angular/core'; +import { switchMap, tap } from 'rxjs/operators'; +import { NgxPermissionsService, NgxRolesService } from 'ngx-permissions'; +import { AuthService, User } from '@core/authentication'; +import { Menu, MenuService } from './menu.service'; + +@Injectable({ + providedIn: 'root', +}) +export class StartupService { + constructor( + private authService: AuthService, + private menuService: MenuService, + private permissonsService: NgxPermissionsService, + private rolesService: NgxRolesService + ) {} + + /** + * Load the application only after get the menu or other essential informations + * such as permissions and roles. + */ + load() { + return new Promise((resolve, reject) => { + this.authService + .change() + .pipe( + tap(user => this.setPermissions(user)), + switchMap(() => this.authService.menu()), + tap(menu => this.setMenu(menu)) + ) + .subscribe( + () => resolve(), + () => resolve() + ); + }); + } + + private setMenu(menu: Menu[]) { + this.menuService.addNamespace(menu, 'menu'); + this.menuService.set(menu); + } + + private setPermissions(user: User) { + // In a real app, you should get permissions and roles from the user information. + const permissions = ['canAdd', 'canDelete', 'canEdit', 'canRead']; + this.permissonsService.loadPermissions(permissions); + this.rolesService.flushRoles(); + this.rolesService.addRoles({ ADMIN: permissions }); + + // Tips: Alternatively you can add permissions with role at the same time. + // this.rolesService.addRolesWithPermissions({ ADMIN: permissions }); + } +} diff --git a/front/app/src/app/core/bootstrap/translate-lang.service.ts b/front/app/src/app/core/bootstrap/translate-lang.service.ts new file mode 100644 index 00000000..ec00790f --- /dev/null +++ b/front/app/src/app/core/bootstrap/translate-lang.service.ts @@ -0,0 +1,33 @@ +import { Injectable, Injector } from '@angular/core'; +import { LOCATION_INITIALIZED } from '@angular/common'; +import { TranslateService } from '@ngx-translate/core'; +import { SettingsService } from './settings.service'; + +@Injectable({ + providedIn: 'root', +}) +export class TranslateLangService { + constructor( + private injector: Injector, + private translate: TranslateService, + private settings: SettingsService + ) {} + + load() { + return new Promise(resolve => { + const locationInitialized = this.injector.get(LOCATION_INITIALIZED, Promise.resolve()); + locationInitialized.then(() => { + const browserLang = navigator.language; + const defaultLang = browserLang.match(/en-US|zh-CN|zh-TW/) ? browserLang : 'en-US'; + + this.settings.setLanguage(defaultLang); + this.translate.setDefaultLang(defaultLang); + this.translate.use(defaultLang).subscribe( + () => console.log(`Successfully initialized '${defaultLang}' language.'`), + () => console.error(`Problem with '${defaultLang}' language initialization.'`), + () => resolve() + ); + }); + }); + } +} diff --git a/front/app/src/app/core/core.module.ts b/front/app/src/app/core/core.module.ts new file mode 100644 index 00000000..a8900f24 --- /dev/null +++ b/front/app/src/app/core/core.module.ts @@ -0,0 +1,13 @@ +import { NgModule, Optional, SkipSelf } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { throwIfAlreadyLoaded } from './module-import-guard'; + +@NgModule({ + declarations: [], + imports: [CommonModule], +}) +export class CoreModule { + constructor(@Optional() @SkipSelf() parentModule: CoreModule) { + throwIfAlreadyLoaded(parentModule, 'CoreModule'); + } +} diff --git a/front/app/src/app/core/index.ts b/front/app/src/app/core/index.ts new file mode 100644 index 00000000..acffc0f3 --- /dev/null +++ b/front/app/src/app/core/index.ts @@ -0,0 +1,16 @@ +export * from './settings'; +export * from './initializers'; + +// Bootstrap +export * from './bootstrap/menu.service'; +export * from './bootstrap/settings.service'; +export * from './bootstrap/startup.service'; +export * from './bootstrap/preloader.service'; +export * from './bootstrap/translate-lang.service'; +export * from './bootstrap/sanctum.service'; + +// Interceptors +export * from './interceptors'; + +// Authentication +export * from './authentication'; diff --git a/front/app/src/app/core/initializers.ts b/front/app/src/app/core/initializers.ts new file mode 100644 index 00000000..9fcd75ad --- /dev/null +++ b/front/app/src/app/core/initializers.ts @@ -0,0 +1,37 @@ +import { APP_INITIALIZER } from '@angular/core'; + +// import { SanctumService } from './bootstrap/sanctum.service'; +// export function SanctumServiceFactory(sanctumService: SanctumService) { +// return () => sanctumService.load(); +// } + +import { TranslateLangService } from './bootstrap/translate-lang.service'; +export function TranslateLangServiceFactory(translateLangService: TranslateLangService) { + return () => translateLangService.load(); +} + +import { StartupService } from './bootstrap/startup.service'; +export function StartupServiceFactory(startupService: StartupService) { + return () => startupService.load(); +} + +export const appInitializerProviders = [ + // { + // provide: APP_INITIALIZER, + // useFactory: SanctumServiceFactory, + // deps: [SanctumService], + // multi: true, + // }, + { + provide: APP_INITIALIZER, + useFactory: TranslateLangServiceFactory, + deps: [TranslateLangService], + multi: true, + }, + { + provide: APP_INITIALIZER, + useFactory: StartupServiceFactory, + deps: [StartupService], + multi: true, + }, +]; diff --git a/front/app/src/app/core/interceptors/README.md b/front/app/src/app/core/interceptors/README.md new file mode 100644 index 00000000..82e99862 --- /dev/null +++ b/front/app/src/app/core/interceptors/README.md @@ -0,0 +1,3 @@ +# Interceptors + +https://angular.io/guide/http#intercepting-requests-and-responses diff --git a/front/app/src/app/core/interceptors/base-url-interceptor.spec.ts b/front/app/src/app/core/interceptors/base-url-interceptor.spec.ts new file mode 100644 index 00000000..8509e256 --- /dev/null +++ b/front/app/src/app/core/interceptors/base-url-interceptor.spec.ts @@ -0,0 +1,47 @@ +import { TestBed } from '@angular/core/testing'; + +import { HTTP_INTERCEPTORS, HttpClient } from '@angular/common/http'; +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; +import { BASE_URL, BaseUrlInterceptor } from './base-url-interceptor'; + +describe('BaseUrlInterceptor', () => { + let httpMock: HttpTestingController; + let http: HttpClient; + const baseUrl = 'https://foo.bar'; + + const setBaseUrl = (url: string | null) => { + TestBed.overrideProvider(BASE_URL, { useValue: url }); + httpMock = TestBed.inject(HttpTestingController); + http = TestBed.inject(HttpClient); + }; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + providers: [ + { provide: BASE_URL, useValue: null }, + { provide: HTTP_INTERCEPTORS, useClass: BaseUrlInterceptor, multi: true }, + ], + }); + }); + + afterEach(() => httpMock.verify()); + + it('should not prepend base url when base url is empty', () => { + setBaseUrl(null); + + http.get('/me').subscribe(data => expect(data).toEqual({ success: true })); + + httpMock.expectOne('/me').flush({ success: true }); + }); + + it('should prepend base url when request url does not has http scheme', () => { + setBaseUrl(baseUrl); + + http.get('./me').subscribe(data => expect(data).toEqual({ success: true })); + httpMock.expectOne(baseUrl + '/me').flush({ success: true }); + + http.get('').subscribe(data => expect(data).toEqual({ success: true })); + httpMock.expectOne(baseUrl).flush({ success: true }); + }); +}); diff --git a/front/app/src/app/core/interceptors/base-url-interceptor.ts b/front/app/src/app/core/interceptors/base-url-interceptor.ts new file mode 100644 index 00000000..fc94c962 --- /dev/null +++ b/front/app/src/app/core/interceptors/base-url-interceptor.ts @@ -0,0 +1,24 @@ +import { Inject, Injectable, InjectionToken, Optional } from '@angular/core'; +import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http'; +import { Observable } from 'rxjs'; + +export const BASE_URL = new InjectionToken('BASE_URL'); + +@Injectable() +export class BaseUrlInterceptor implements HttpInterceptor { + private hasScheme = (url: string) => this.baseUrl && new RegExp('^http(s)?://', 'i').test(url); + + constructor(@Optional() @Inject(BASE_URL) private baseUrl?: string) {} + + intercept(request: HttpRequest, next: HttpHandler): Observable> { + return this.hasScheme(request.url) === false + ? next.handle(request.clone({ url: this.prependBaseUrl(request.url) })) + : next.handle(request); + } + + private prependBaseUrl(url: string) { + return [this.baseUrl?.replace(/\/$/g, ''), url.replace(/^\.?\//, '')] + .filter(val => val) + .join('/'); + } +} diff --git a/front/app/src/app/core/interceptors/default-interceptor.ts b/front/app/src/app/core/interceptors/default-interceptor.ts new file mode 100644 index 00000000..ad0238e4 --- /dev/null +++ b/front/app/src/app/core/interceptors/default-interceptor.ts @@ -0,0 +1,41 @@ +import { Injectable } from '@angular/core'; +import { + HttpEvent, + HttpHandler, + HttpInterceptor, + HttpRequest, + HttpResponse, +} from '@angular/common/http'; +import { Observable, of, throwError } from 'rxjs'; +import { mergeMap } from 'rxjs/operators'; + +import { ToastrService } from 'ngx-toastr'; + +@Injectable() +export class DefaultInterceptor implements HttpInterceptor { + constructor(private toast: ToastrService) {} + + intercept(req: HttpRequest, next: HttpHandler): Observable> { + if (!req.url.includes('/api/')) { + return next.handle(req); + } + + return next.handle(req).pipe(mergeMap((event: HttpEvent) => this.handleOkReq(event))); + } + + private handleOkReq(event: HttpEvent): Observable { + if (event instanceof HttpResponse) { + const body: any = event.body; + // failure: { code: **, msg: 'failure' } + // success: { code: 0, msg: 'success', data: {} } + if (body && 'code' in body && body.code !== 0) { + if (body.msg) { + this.toast.error(body.msg); + } + return throwError([]); + } + } + // Pass down event if everything is OK + return of(event); + } +} diff --git a/front/app/src/app/core/interceptors/error-interceptor.spec.ts b/front/app/src/app/core/interceptors/error-interceptor.spec.ts new file mode 100644 index 00000000..7f2a52a6 --- /dev/null +++ b/front/app/src/app/core/interceptors/error-interceptor.spec.ts @@ -0,0 +1,75 @@ +import { TestBed } from '@angular/core/testing'; + +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; +import { HTTP_INTERCEPTORS, HttpClient } from '@angular/common/http'; +import { RouterTestingModule } from '@angular/router/testing'; +import { Router } from '@angular/router'; +import { ToastrModule, ToastrService } from 'ngx-toastr'; +import { ErrorInterceptor } from './error-interceptor'; + +describe('ErrorInterceptor', () => { + let httpMock: HttpTestingController; + let http: HttpClient; + let router: Router; + let toast: ToastrService; + const emptyFn = () => {}; + + function assertStatus(status: number, statusText: string) { + spyOn(router, 'navigateByUrl'); + + http.get('/me').subscribe(emptyFn, emptyFn, emptyFn); + + httpMock.expectOne('/me').flush({}, { status, statusText }); + + expect(router.navigateByUrl).toHaveBeenCalledWith(`/${status}`, { + skipLocationChange: true, + }); + } + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule, RouterTestingModule, ToastrModule.forRoot()], + providers: [{ provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true }], + }); + + httpMock = TestBed.inject(HttpTestingController); + http = TestBed.inject(HttpClient); + router = TestBed.inject(Router); + toast = TestBed.inject(ToastrService); + }); + + afterEach(() => httpMock.verify()); + + it('should handle status code 401', () => { + spyOn(router, 'navigateByUrl'); + spyOn(toast, 'error'); + + http.get('/me').subscribe(emptyFn, emptyFn, emptyFn); + httpMock.expectOne('/me').flush({}, { status: 401, statusText: 'Unauthorized' }); + + expect(toast.error).toHaveBeenCalledWith('401 Unauthorized'); + expect(router.navigateByUrl).toHaveBeenCalledWith('/auth/login'); + }); + + it('should handle status code 403', () => { + assertStatus(403, 'Forbidden'); + }); + + it('should handle status code 404', () => { + assertStatus(404, 'Not Found'); + }); + + it('should handle status code 500', () => { + assertStatus(500, 'Internal Server Error'); + }); + + it('should handle others status code', () => { + spyOn(toast, 'error'); + + http.get('/me').subscribe(emptyFn, emptyFn, emptyFn); + + httpMock.expectOne('/me').flush({}, { status: 504, statusText: 'Gateway Timeout' }); + + expect(toast.error).toHaveBeenCalledWith('504 Gateway Timeout'); + }); +}); diff --git a/front/app/src/app/core/interceptors/error-interceptor.ts b/front/app/src/app/core/interceptors/error-interceptor.ts new file mode 100644 index 00000000..d05531e1 --- /dev/null +++ b/front/app/src/app/core/interceptors/error-interceptor.ts @@ -0,0 +1,60 @@ +import { Injectable } from '@angular/core'; +import { + HttpErrorResponse, + HttpEvent, + HttpHandler, + HttpInterceptor, + HttpRequest, +} from '@angular/common/http'; +import { Router } from '@angular/router'; +import { Observable, throwError } from 'rxjs'; +import { catchError } from 'rxjs/operators'; +import { ToastrService } from 'ngx-toastr'; + +export enum STATUS { + UNAUTHORIZED = 401, + FORBIDDEN = 403, + NOT_FOUND = 404, + INTERNAL_SERVER_ERROR = 500, +} + +@Injectable() +export class ErrorInterceptor implements HttpInterceptor { + private errorPages = [STATUS.FORBIDDEN, STATUS.NOT_FOUND, STATUS.INTERNAL_SERVER_ERROR]; + + private getMessage = (error: HttpErrorResponse) => { + if (error.error?.message) { + return error.error.message; + } + + if (error.error?.msg) { + return error.error.msg; + } + + return `${error.status} ${error.statusText}`; + }; + + constructor(private router: Router, private toast: ToastrService) {} + + intercept(request: HttpRequest, next: HttpHandler): Observable> { + return next + .handle(request) + .pipe(catchError((error: HttpErrorResponse) => this.handleError(error))); + } + + private handleError(error: HttpErrorResponse) { + if (this.errorPages.includes(error.status)) { + this.router.navigateByUrl(`/${error.status}`, { + skipLocationChange: true, + }); + } else { + console.error('ERROR', error); + this.toast.error(this.getMessage(error)); + if (error.status === STATUS.UNAUTHORIZED) { + this.router.navigateByUrl('/auth/login'); + } + } + + return throwError(error); + } +} diff --git a/front/app/src/app/core/interceptors/index.ts b/front/app/src/app/core/interceptors/index.ts new file mode 100644 index 00000000..7c098c5c --- /dev/null +++ b/front/app/src/app/core/interceptors/index.ts @@ -0,0 +1,31 @@ +import { HTTP_INTERCEPTORS } from '@angular/common/http'; + +import { NoopInterceptor } from './noop-interceptor'; +// import { SanctumInterceptor } from './sanctum-interceptor'; +import { BaseUrlInterceptor } from './base-url-interceptor'; +import { SettingsInterceptor } from './settings-interceptor'; +import { TokenInterceptor } from './token-interceptor'; +import { DefaultInterceptor } from './default-interceptor'; +import { ErrorInterceptor } from './error-interceptor'; +import { LoggingInterceptor } from './logging-interceptor'; + +export * from './noop-interceptor'; +// export * from './sanctum-interceptor'; +export * from './base-url-interceptor'; +export * from './settings-interceptor'; +export * from './token-interceptor'; +export * from './default-interceptor'; +export * from './error-interceptor'; +export * from './logging-interceptor'; + +/** Http interceptor providers in outside-in order */ +export const httpInterceptorProviders = [ + { provide: HTTP_INTERCEPTORS, useClass: NoopInterceptor, multi: true }, + // { provide: HTTP_INTERCEPTORS, useClass: SanctumInterceptor, multi: true }, + { provide: HTTP_INTERCEPTORS, useClass: BaseUrlInterceptor, multi: true }, + { provide: HTTP_INTERCEPTORS, useClass: SettingsInterceptor, multi: true }, + { provide: HTTP_INTERCEPTORS, useClass: TokenInterceptor, multi: true }, + { provide: HTTP_INTERCEPTORS, useClass: DefaultInterceptor, multi: true }, + { provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true }, + { provide: HTTP_INTERCEPTORS, useClass: LoggingInterceptor, multi: true }, +]; diff --git a/front/app/src/app/core/interceptors/logging-interceptor.ts b/front/app/src/app/core/interceptors/logging-interceptor.ts new file mode 100644 index 00000000..887c7ad1 --- /dev/null +++ b/front/app/src/app/core/interceptors/logging-interceptor.ts @@ -0,0 +1,30 @@ +import { Injectable } from '@angular/core'; +import { HttpInterceptor, HttpHandler, HttpRequest, HttpResponse } from '@angular/common/http'; +import { finalize, tap } from 'rxjs/operators'; +import { MessageService } from '@shared'; + +@Injectable() +export class LoggingInterceptor implements HttpInterceptor { + constructor(private messenger: MessageService) {} + + intercept(req: HttpRequest, next: HttpHandler) { + const started = Date.now(); + let ok: string; + + // extend server response observable with logging + return next.handle(req).pipe( + tap( + // Succeeds when there is a response; ignore other events + event => (ok = event instanceof HttpResponse ? 'succeeded' : ''), + // Operation failed; error is an HttpErrorResponse + error => (ok = 'failed') + ), + // Log when response observable either completes or errors + finalize(() => { + const elapsed = Date.now() - started; + const msg = `${req.method} "${req.urlWithParams}" ${ok} in ${elapsed} ms.`; + this.messenger.add(msg); + }) + ); + } +} diff --git a/front/app/src/app/core/interceptors/noop-interceptor.ts b/front/app/src/app/core/interceptors/noop-interceptor.ts new file mode 100644 index 00000000..94430207 --- /dev/null +++ b/front/app/src/app/core/interceptors/noop-interceptor.ts @@ -0,0 +1,10 @@ +import { Injectable } from '@angular/core'; +import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest } from '@angular/common/http'; +import { Observable } from 'rxjs'; + +@Injectable() +export class NoopInterceptor implements HttpInterceptor { + intercept(req: HttpRequest, next: HttpHandler): Observable> { + return next.handle(req); + } +} diff --git a/front/app/src/app/core/interceptors/sanctum-interceptor.spec.ts b/front/app/src/app/core/interceptors/sanctum-interceptor.spec.ts new file mode 100644 index 00000000..65fb9617 --- /dev/null +++ b/front/app/src/app/core/interceptors/sanctum-interceptor.spec.ts @@ -0,0 +1,98 @@ +import { TestBed } from '@angular/core/testing'; + +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; +import { HTTP_INTERCEPTORS, HttpClient } from '@angular/common/http'; +import { switchMap } from 'rxjs/operators'; +import { SanctumInterceptor } from './sanctum-interceptor'; +import { BASE_URL } from './base-url-interceptor'; +import { SANCTUM_PREFIX } from '@core/bootstrap/sanctum.service'; + +describe('SanctumInterceptor', () => { + let httpMock: HttpTestingController; + let http: HttpClient; + + const setBaseUrlAndSanctumPrefix = (baseUrl: string | null, sanctumPrefix: string | null) => { + TestBed.overrideProvider(BASE_URL, { useValue: baseUrl }); + TestBed.overrideProvider(SANCTUM_PREFIX, { useValue: sanctumPrefix }); + + httpMock = TestBed.inject(HttpTestingController); + http = TestBed.inject(HttpClient); + }; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + providers: [ + { provide: BASE_URL, useValue: null }, + { provide: SANCTUM_PREFIX, useValue: null }, + { provide: HTTP_INTERCEPTORS, useClass: SanctumInterceptor, multi: true }, + ], + }); + }); + + afterEach(() => httpMock.verify()); + + it('should get csrf cookie once', () => { + setBaseUrlAndSanctumPrefix(null, null); + + http + .post('/auth/login', { + username: 'foo', + password: 'bar', + }) + .pipe(switchMap(() => http.get('/me'))) + .subscribe(data => expect(data).toEqual({ me: true })); + + httpMock.expectOne('/sanctum/csrf-cookie').flush({ cookie: true }); + httpMock.expectOne('/auth/login').flush({ login: true }); + httpMock.expectOne('/me').flush({ me: true }); + }); + + it('should get csrf cookie with base url', () => { + setBaseUrlAndSanctumPrefix('https://foo.bar/api', null); + + http + .post('/auth/login', { + username: 'foo', + password: 'bar', + }) + .pipe(switchMap(() => http.get('/me'))) + .subscribe(data => expect(data).toEqual({ me: true })); + + httpMock.expectOne('https://foo.bar/sanctum/csrf-cookie').flush({ cookie: true }); + httpMock.expectOne('/auth/login').flush({ login: true }); + httpMock.expectOne('/me').flush({ me: true }); + }); + + it('should get csrf cookie with sanctum prefix', () => { + setBaseUrlAndSanctumPrefix(null, 'foobar'); + + http + .post('/auth/login', { + username: 'foo', + password: 'bar', + }) + .pipe(switchMap(() => http.get('/me'))) + .subscribe(data => expect(data).toEqual({ me: true })); + + httpMock.expectOne('/foobar/csrf-cookie').flush({ cookie: true }); + httpMock.expectOne('/auth/login').flush({ login: true }); + httpMock.expectOne('/me').flush({ me: true }); + }); + + it('should get csrf cookie with base url and sanctum prefix', () => { + setBaseUrlAndSanctumPrefix('https://foo.bar/api', 'foobar'); + + http + .post('/auth/login', { + username: 'foo', + password: 'bar', + }) + .pipe(switchMap(() => http.get('/me'))) + .subscribe(data => expect(data).toEqual({ me: true })); + + httpMock.expectOne('https://foo.bar/foobar/csrf-cookie').flush({ cookie: true }); + httpMock.expectOne('/auth/login').flush({ login: true }); + httpMock.expectOne('/me').flush({ me: true }); + }); +}); diff --git a/front/app/src/app/core/interceptors/sanctum-interceptor.ts b/front/app/src/app/core/interceptors/sanctum-interceptor.ts new file mode 100644 index 00000000..f4dec383 --- /dev/null +++ b/front/app/src/app/core/interceptors/sanctum-interceptor.ts @@ -0,0 +1,22 @@ +import { Injectable } from '@angular/core'; +import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { switchMap } from 'rxjs/operators'; +import { SanctumService } from '@core'; + +@Injectable() +export class SanctumInterceptor implements HttpInterceptor { + private ready = false; + + constructor(private sanctum: SanctumService) {} + + intercept(request: HttpRequest, next: HttpHandler): Observable> { + if (!this.ready) { + this.ready = true; + + return this.sanctum.toObservable().pipe(switchMap(() => next.handle(request))); + } + + return next.handle(request); + } +} diff --git a/front/app/src/app/core/interceptors/settings-interceptor.spec.ts b/front/app/src/app/core/interceptors/settings-interceptor.spec.ts new file mode 100644 index 00000000..5fcf0ec9 --- /dev/null +++ b/front/app/src/app/core/interceptors/settings-interceptor.spec.ts @@ -0,0 +1,33 @@ +import { TestBed } from '@angular/core/testing'; + +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; +import { HTTP_INTERCEPTORS, HttpClient } from '@angular/common/http'; +import { SettingsService } from '@core/bootstrap/settings.service'; +import { SettingsInterceptor } from './settings-interceptor'; + +describe('SettingsInterceptor', () => { + let httpMock: HttpTestingController; + let http: HttpClient; + let settings: SettingsService; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + providers: [{ provide: HTTP_INTERCEPTORS, useClass: SettingsInterceptor, multi: true }], + }); + + httpMock = TestBed.inject(HttpTestingController); + http = TestBed.inject(HttpClient); + settings = TestBed.inject(SettingsService); + }); + + it('should set accept language', () => { + settings.setLanguage('zh-TW'); + + http.get('/me').subscribe(); + const testRequest = httpMock.expectOne('/me'); + testRequest.flush({ me: true }); + + expect(testRequest.request.headers.get('Accept-Language')).toEqual('zh-TW'); + }); +}); diff --git a/front/app/src/app/core/interceptors/settings-interceptor.ts b/front/app/src/app/core/interceptors/settings-interceptor.ts new file mode 100644 index 00000000..36c1b93b --- /dev/null +++ b/front/app/src/app/core/interceptors/settings-interceptor.ts @@ -0,0 +1,17 @@ +import { Injectable } from '@angular/core'; +import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { SettingsService } from '@core'; + +@Injectable() +export class SettingsInterceptor implements HttpInterceptor { + constructor(private settings: SettingsService) {} + + intercept(request: HttpRequest, next: HttpHandler): Observable> { + return next.handle( + request.clone({ + headers: request.headers.append('Accept-Language', this.settings.getLanguage()), + }) + ); + } +} diff --git a/front/app/src/app/core/interceptors/token-interceptor.spec.ts b/front/app/src/app/core/interceptors/token-interceptor.spec.ts new file mode 100644 index 00000000..f9763830 --- /dev/null +++ b/front/app/src/app/core/interceptors/token-interceptor.spec.ts @@ -0,0 +1,116 @@ +import { TestBed } from '@angular/core/testing'; + +import { TokenInterceptor } from './token-interceptor'; +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; +import { HTTP_INTERCEPTORS, HttpClient } from '@angular/common/http'; +import { STATUS } from 'angular-in-memory-web-api'; +import { RouterTestingModule } from '@angular/router/testing'; +import { Router } from '@angular/router'; +import { LocalStorageService, MemoryStorageService } from '@shared/services/storage.service'; +import { TokenService, User } from '@core/authentication'; +import { BASE_URL } from './base-url-interceptor'; + +describe('TokenInterceptor', () => { + let httpMock: HttpTestingController; + let http: HttpClient; + let router: Router; + let tokenService: TokenService; + const emptyFn = () => {}; + const baseUrl = 'https://foo.bar'; + const user: User = { id: 1, email: 'foo@bar.com' }; + + function init(url: string, access_token: string) { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule, RouterTestingModule], + providers: [ + { provide: LocalStorageService, useClass: MemoryStorageService }, + { provide: BASE_URL, useValue: url }, + { provide: HTTP_INTERCEPTORS, useClass: TokenInterceptor, multi: true }, + ], + }); + + httpMock = TestBed.inject(HttpTestingController); + http = TestBed.inject(HttpClient); + router = TestBed.inject(Router); + tokenService = TestBed.inject(TokenService).set({ access_token, token_type: 'bearer' }); + } + + function mockRequest(url: string, body?: any, headers?: any) { + http.get(url).subscribe(emptyFn, emptyFn, emptyFn); + const testRequest = httpMock.expectOne(url); + testRequest.flush(body ?? {}, headers ?? {}); + + return testRequest; + } + + afterEach(() => httpMock.verify()); + + it('should append token when url does not has http scheme', () => { + init('', 'token'); + + const headers = mockRequest('/me', user).request.headers; + + expect(headers.get('Authorization')).toEqual('Bearer token'); + }); + + it('should append token when url does not has http and base url not empty', () => { + init(baseUrl, 'token'); + + const headers = mockRequest('/me', user).request.headers; + + expect(headers.get('Authorization')).toEqual('Bearer token'); + }); + + it('should append token when url include base url', () => { + init(baseUrl, 'token'); + + const headers = mockRequest(`${baseUrl}/me`, user).request.headers; + + expect(headers.get('Authorization')).toEqual('Bearer token'); + }); + + it('should not append token when url not include baseUrl', () => { + init(baseUrl, 'token'); + + const headers = mockRequest('https://api.github.com', { success: true }).request.headers; + + expect(headers.has('Authorization')).toBeFalse(); + }); + + it('should not append token when base url is empty and url is not same site', () => { + init('', 'token'); + + const headers = mockRequest('https://api.github.com', { success: true }).request.headers; + + expect(headers.has('Authorization')).toBeFalse(); + }); + + it('should clear token when response status is unauthorized', () => { + init('', 'token'); + spyOn(tokenService, 'clear'); + + mockRequest('/me', {}, { status: STATUS.UNAUTHORIZED, statusText: 'Unauthorized' }); + + expect(tokenService.clear).toHaveBeenCalled(); + }); + + it('should navigate /auth/login when api url is /auth/logout and token is valid', () => { + init('', 'token'); + const navigateByUrl = spyOn(router, 'navigateByUrl'); + navigateByUrl.and.returnValue(Promise.resolve(true)); + + mockRequest('/auth/logout'); + + expect(navigateByUrl).toHaveBeenCalledWith('/auth/login'); + }); + + it('should navigate /auth/login when api url is /auth/logout and token is invalid', () => { + init('', ''); + const navigateByUrl = spyOn(router, 'navigateByUrl'); + navigateByUrl.and.returnValue(Promise.resolve(true)); + + mockRequest('/auth/logout'); + + expect(navigateByUrl).toHaveBeenCalledWith('/auth/login'); + }); +}); diff --git a/front/app/src/app/core/interceptors/token-interceptor.ts b/front/app/src/app/core/interceptors/token-interceptor.ts new file mode 100644 index 00000000..1e8c730e --- /dev/null +++ b/front/app/src/app/core/interceptors/token-interceptor.ts @@ -0,0 +1,71 @@ +import { Inject, Injectable, Optional } from '@angular/core'; +import { + HttpErrorResponse, + HttpEvent, + HttpHandler, + HttpInterceptor, + HttpRequest, +} from '@angular/common/http'; +import { Router } from '@angular/router'; +import { Observable, throwError } from 'rxjs'; +import { catchError, tap } from 'rxjs/operators'; +import { TokenService } from '@core/authentication'; +import { BASE_URL } from './base-url-interceptor'; + +@Injectable() +export class TokenInterceptor implements HttpInterceptor { + private hasHttpScheme = (url: string) => new RegExp('^http(s)?://', 'i').test(url); + + constructor( + private tokenService: TokenService, + private router: Router, + @Optional() @Inject(BASE_URL) private baseUrl?: string + ) {} + + intercept(request: HttpRequest, next: HttpHandler): Observable> { + const handler = () => { + if (request.url.includes('/auth/logout')) { + this.router.navigateByUrl('/auth/login'); + } + + if (this.router.url.includes('/auth/login')) { + this.router.navigateByUrl('/dashboard'); + } + }; + + if (this.tokenService.valid() && this.shouldAppendToken(request.url)) { + return next + .handle( + request.clone({ + headers: request.headers.append('Authorization', this.tokenService.getBearerToken()), + withCredentials: true, + }) + ) + .pipe( + catchError((error: HttpErrorResponse) => { + if (error.status === 401) { + this.tokenService.clear(); + } + return throwError(error); + }), + tap(() => handler()) + ); + } + + return next.handle(request).pipe(tap(() => handler())); + } + + private shouldAppendToken(url: string) { + return !this.hasHttpScheme(url) || this.includeBaseUrl(url); + } + + private includeBaseUrl(url: string) { + if (!this.baseUrl) { + return false; + } + + const baseUrl = this.baseUrl.replace(/\/$/, ''); + + return new RegExp(`^${baseUrl}`, 'i').test(url); + } +} diff --git a/front/app/src/app/core/module-import-guard.ts b/front/app/src/app/core/module-import-guard.ts new file mode 100644 index 00000000..eb87efcf --- /dev/null +++ b/front/app/src/app/core/module-import-guard.ts @@ -0,0 +1,7 @@ +export function throwIfAlreadyLoaded(parentModule: any, moduleName: string) { + if (parentModule) { + throw new Error( + `${moduleName} has already been loaded. Import Core modules in the AppModule only.` + ); + } +} diff --git a/front/app/src/app/core/settings.ts b/front/app/src/app/core/settings.ts new file mode 100644 index 00000000..ab739602 --- /dev/null +++ b/front/app/src/app/core/settings.ts @@ -0,0 +1,23 @@ +export interface AppSettings { + navPos: 'side' | 'top'; + theme: 'light' | 'dark' | 'auto'; + dir: 'ltr' | 'rtl'; + showHeader: boolean; + headerPos: 'fixed' | 'static' | 'above'; + showUserPanel: boolean; + sidenavOpened: boolean; + sidenavCollapsed: boolean; + language: string; +} + +export const defaults: AppSettings = { + navPos: 'side', + theme: 'auto', + dir: 'ltr', + showHeader: true, + headerPos: 'fixed', + showUserPanel: true, + sidenavOpened: true, + sidenavCollapsed: false, + language: 'en-US', +}; diff --git a/front/app/src/app/fake-login.service.ts b/front/app/src/app/fake-login.service.ts new file mode 100644 index 00000000..b64d613e --- /dev/null +++ b/front/app/src/app/fake-login.service.ts @@ -0,0 +1,34 @@ +import { Injectable } from '@angular/core'; +import { of } from 'rxjs'; +import { admin, LoginService, Menu } from '@core'; +import { map } from 'rxjs/operators'; + +/** + * You should delete this file in the real APP. + */ +@Injectable() +export class FakeLoginService extends LoginService { + private token = { access_token: 'MW56YjMyOUAxNjMuY29tWm9uZ2Jpbg==', token_type: 'bearer' }; + + login() { + return of(this.token); + } + + refresh() { + return of(this.token); + } + + logout() { + return of({}); + } + + me() { + return of(admin); + } + + menu() { + return this.http + .get<{ menu: Menu[] }>('assets/data/menu.json?_t=' + Date.now()) + .pipe(map(res => res.menu)); + } +} diff --git a/front/app/src/app/formly-config.module.ts b/front/app/src/app/formly-config.module.ts new file mode 100644 index 00000000..52eaedd9 --- /dev/null +++ b/front/app/src/app/formly-config.module.ts @@ -0,0 +1,53 @@ +import { NgModule, ModuleWithProviders, Provider } from '@angular/core'; +import { SharedModule } from './shared/shared.module'; + +import { FormlyModule } from '@ngx-formly/core'; +import { FormlyFieldComboboxComponent } from './formly-templates'; +import { FormlyWrapperCardComponent, FormlyWrapperDivComponent } from './formly-wrappers'; +import { FormlyValidations } from './formly-validations'; + +/** + * Formly global configuration + */ +const formlyModuleProviders = FormlyModule.forRoot({ + types: [ + { + name: 'combobox', + component: FormlyFieldComboboxComponent, + wrappers: ['form-field'], + }, + ], + wrappers: [ + { + name: 'card', + component: FormlyWrapperCardComponent, + }, + { + name: 'div', + component: FormlyWrapperDivComponent, + }, + ], + validationMessages: [], +}).providers as Provider[]; + +@NgModule({ + imports: [SharedModule], + declarations: [ + FormlyFieldComboboxComponent, + FormlyWrapperCardComponent, + FormlyWrapperDivComponent, + ], + providers: [FormlyValidations], +}) +export class FormlyConfigModule { + constructor(formlyValidations: FormlyValidations) { + formlyValidations.init(); + } + + static forRoot(): ModuleWithProviders { + return { + ngModule: FormlyConfigModule, + providers: [formlyModuleProviders], + }; + } +} diff --git a/front/app/src/app/formly-templates.ts b/front/app/src/app/formly-templates.ts new file mode 100644 index 00000000..db6b2cf7 --- /dev/null +++ b/front/app/src/app/formly-templates.ts @@ -0,0 +1,45 @@ +import { ViewChild, ChangeDetectionStrategy, Component } from '@angular/core'; +import { FieldType } from '@ngx-formly/material/form-field'; +import { MtxSelect } from '@ng-matero/extensions/select'; +import { FieldTypeConfig } from '@ngx-formly/core'; + +/** + * This is just an example. + */ +@Component({ + selector: 'formly-field-combobox', + template: ` + `, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class FormlyFieldComboboxComponent extends FieldType { + @ViewChild('select', { static: true }) select!: MtxSelect; + + get bindLabel() { + return typeof this.props.labelProp === 'string' ? this.props.labelProp : ''; + } + + get bindValue() { + return typeof this.props.valueProp === 'string' ? this.props.valueProp : undefined; + } + + // The original `onContainerClick` has been covered up in FieldType, so we should redefine it. + onContainerClick(event: MouseEvent) { + const target = event.target as HTMLElement; + if (/mat-form-field|mtx-select/g.test(target.parentElement?.classList[0] || '')) { + this.select.focus(); + this.select.open(); + } + } +} diff --git a/front/app/src/app/formly-validations.ts b/front/app/src/app/formly-validations.ts new file mode 100644 index 00000000..195e38f0 --- /dev/null +++ b/front/app/src/app/formly-validations.ts @@ -0,0 +1,53 @@ +import { Injectable } from '@angular/core'; +import { FormlyFieldConfig, FormlyConfig } from '@ngx-formly/core'; +import { TranslateService } from '@ngx-translate/core'; + +@Injectable() +export class FormlyValidations { + constructor(private translate: TranslateService, private formlyConfig: FormlyConfig) {} + + init(): void { + // message without params + this.formlyConfig.addValidatorMessage('required', (_err, _field) => + this.translate.stream('validations.required') + ); + + // message with params + this.formlyConfig.addValidatorMessage('minLength', (err, field) => + this.minLengthValidationMessage(err, field, this.translate) + ); + this.formlyConfig.addValidatorMessage('maxLength', (err, field) => + this.maxLengthValidationMessage(err, field, this.translate) + ); + this.formlyConfig.addValidatorMessage('min', (err, field) => + this.minValidationMessage(err, field, this.translate) + ); + this.formlyConfig.addValidatorMessage('max', (err, field) => + this.maxValidationMessage(err, field, this.translate) + ); + } + + private minLengthValidationMessage( + err: any, + field: FormlyFieldConfig, + translate: TranslateService + ) { + return translate.stream('validations.minlength', { number: field.props?.minLength }); + } + + private maxLengthValidationMessage( + err: any, + field: FormlyFieldConfig, + translate: TranslateService + ) { + return translate.stream('validations.maxlength', { number: field.props?.maxLength }); + } + + private minValidationMessage(err: any, field: FormlyFieldConfig, translate: TranslateService) { + return translate.stream('validations.min', { number: field.props?.min }); + } + + private maxValidationMessage(err: any, field: FormlyFieldConfig, translate: TranslateService) { + return translate.stream('validations.max', { number: field.props?.max }); + } +} diff --git a/front/app/src/app/formly-wrappers.ts b/front/app/src/app/formly-wrappers.ts new file mode 100644 index 00000000..6125c4e9 --- /dev/null +++ b/front/app/src/app/formly-wrappers.ts @@ -0,0 +1,29 @@ +import { Component } from '@angular/core'; +import { FieldWrapper } from '@ngx-formly/core'; + +/** + * This is just an example. + */ +@Component({ + selector: 'formly-wrapper-card', + template: ` +
+

Its time to party

+

{{ props.label }}

+
+ +
+
+ `, +}) +export class FormlyWrapperCardComponent extends FieldWrapper {} + +@Component({ + selector: 'formly-wrapper-div', + template: ` +
+ +
+ `, +}) +export class FormlyWrapperDivComponent extends FieldWrapper {} diff --git a/front/app/src/app/material-extensions.module.ts b/front/app/src/app/material-extensions.module.ts new file mode 100644 index 00000000..9baa9281 --- /dev/null +++ b/front/app/src/app/material-extensions.module.ts @@ -0,0 +1,66 @@ +import { NgModule } from '@angular/core'; + +import { MtxAlertModule } from '@ng-matero/extensions/alert'; +import { MtxButtonModule } from '@ng-matero/extensions/button'; +import { MtxCheckboxGroupModule } from '@ng-matero/extensions/checkbox-group'; +import { MtxColorpickerModule } from '@ng-matero/extensions/colorpicker'; +import { MtxDatetimepickerModule } from '@ng-matero/extensions/datetimepicker'; +import { MtxDialogModule } from '@ng-matero/extensions/dialog'; +import { MtxDrawerModule } from '@ng-matero/extensions/drawer'; +import { MtxGridModule } from '@ng-matero/extensions/grid'; +import { MtxLoaderModule } from '@ng-matero/extensions/loader'; +import { MtxPopoverModule } from '@ng-matero/extensions/popover'; +import { MtxProgressModule } from '@ng-matero/extensions/progress'; +import { MtxSelectModule } from '@ng-matero/extensions/select'; +import { MtxSliderModule } from '@ng-matero/extensions/slider'; +import { MtxSplitModule } from '@ng-matero/extensions/split'; +import { MtxTooltipModule } from '@ng-matero/extensions/tooltip'; +import { MTX_DATETIME_FORMATS } from '@ng-matero/extensions/core'; +import { MtxMomentDatetimeModule } from '@ng-matero/extensions-moment-adapter'; + +@NgModule({ + exports: [ + MtxAlertModule, + MtxButtonModule, + MtxCheckboxGroupModule, + MtxColorpickerModule, + MtxDatetimepickerModule, + MtxDialogModule, + MtxDrawerModule, + MtxGridModule, + MtxLoaderModule, + MtxPopoverModule, + MtxProgressModule, + MtxSelectModule, + MtxSliderModule, + MtxSplitModule, + MtxTooltipModule, + MtxMomentDatetimeModule, // <= You can import the other adapter you need (luxon, date-fns) + ], + providers: [ + { + provide: MTX_DATETIME_FORMATS, + useValue: { + parse: { + dateInput: 'YYYY-MM-DD', + yearInput: 'YYYY', + monthInput: 'MMMM', + datetimeInput: 'YYYY-MM-DD HH:mm', + timeInput: 'HH:mm', + }, + display: { + dateInput: 'YYYY-MM-DD', + yearInput: 'YYYY', + monthInput: 'MMMM', + datetimeInput: 'YYYY-MM-DD HH:mm', + timeInput: 'HH:mm', + monthYearLabel: 'YYYY MMMM', + dateA11yLabel: 'LL', + monthYearA11yLabel: 'MMMM YYYY', + popupHeaderDateLabel: 'MMM DD, ddd', + }, + }, + }, + ], +}) +export class MaterialExtensionsModule {} diff --git a/front/app/src/app/material.module.ts b/front/app/src/app/material.module.ts new file mode 100644 index 00000000..39155df7 --- /dev/null +++ b/front/app/src/app/material.module.ts @@ -0,0 +1,117 @@ +import { NgModule } from '@angular/core'; + +import { MatAutocompleteModule } from '@angular/material/autocomplete'; +import { MatBadgeModule } from '@angular/material/badge'; +import { MatBottomSheetModule } from '@angular/material/bottom-sheet'; +import { MatButtonModule } from '@angular/material/button'; +import { MatButtonToggleModule } from '@angular/material/button-toggle'; +import { MatCardModule } from '@angular/material/card'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatChipsModule } from '@angular/material/chips'; +import { MatRippleModule, MAT_DATE_FORMATS, MAT_DATE_LOCALE } from '@angular/material/core'; +import { MatDatepickerModule } from '@angular/material/datepicker'; +import { + MatDialogConfig, + MatDialogModule, + MAT_DIALOG_DEFAULT_OPTIONS, +} from '@angular/material/dialog'; +import { MatDividerModule } from '@angular/material/divider'; +import { MatExpansionModule } from '@angular/material/expansion'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatGridListModule } from '@angular/material/grid-list'; +import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; +import { MatListModule } from '@angular/material/list'; +import { MatMenuModule } from '@angular/material/menu'; +import { MatPaginatorIntl, MatPaginatorModule } from '@angular/material/paginator'; +import { MatProgressBarModule } from '@angular/material/progress-bar'; +import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; +import { MatRadioModule } from '@angular/material/radio'; +import { MatSelectModule } from '@angular/material/select'; +import { MatSidenavModule } from '@angular/material/sidenav'; +import { MatSlideToggleModule } from '@angular/material/slide-toggle'; +import { MatSliderModule } from '@angular/material/slider'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { MatSortModule } from '@angular/material/sort'; +import { MatStepperModule } from '@angular/material/stepper'; +import { MatTableModule } from '@angular/material/table'; +import { MatTabsModule } from '@angular/material/tabs'; +import { MatToolbarModule } from '@angular/material/toolbar'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { MatTreeModule } from '@angular/material/tree'; +import { MatMomentDateModule } from '@angular/material-moment-adapter'; + +import { PaginatorI18nService } from '@shared/services/paginator-i18n.service'; + +@NgModule({ + exports: [ + MatAutocompleteModule, + MatBadgeModule, + MatBottomSheetModule, + MatButtonModule, + MatButtonToggleModule, + MatCardModule, + MatCheckboxModule, + MatChipsModule, + MatStepperModule, + MatDatepickerModule, + MatMomentDateModule, + MatDialogModule, + MatDividerModule, + MatExpansionModule, + MatFormFieldModule, + MatGridListModule, + MatIconModule, + MatInputModule, + MatListModule, + MatMenuModule, + MatPaginatorModule, + MatProgressBarModule, + MatProgressSpinnerModule, + MatRadioModule, + MatRippleModule, + MatSelectModule, + MatSidenavModule, + MatSliderModule, + MatSlideToggleModule, + MatSnackBarModule, + MatSortModule, + MatTableModule, + MatTabsModule, + MatToolbarModule, + MatTooltipModule, + MatTreeModule, + ], + providers: [ + { + provide: MatPaginatorIntl, + deps: [PaginatorI18nService], + useFactory: (paginatorI18nSrv: PaginatorI18nService) => paginatorI18nSrv.getPaginatorIntl(), + }, + { + provide: MAT_DIALOG_DEFAULT_OPTIONS, + useValue: { + ...new MatDialogConfig(), + }, + }, + { + provide: MAT_DATE_LOCALE, + useFactory: () => navigator.language, // <= This will be overrided by runtime setting + }, + { + provide: MAT_DATE_FORMATS, + useValue: { + parse: { + dateInput: 'YYYY-MM-DD', + }, + display: { + dateInput: 'YYYY-MM-DD', + monthYearLabel: 'YYYY MMM', + dateA11yLabel: 'LL', + monthYearA11yLabel: 'YYYY MMM', + }, + }, + }, + ], +}) +export class MaterialModule {} diff --git a/front/app/src/app/routes/dashboard/dashboard.component.html b/front/app/src/app/routes/dashboard/dashboard.component.html new file mode 100644 index 00000000..f2649e73 --- /dev/null +++ b/front/app/src/app/routes/dashboard/dashboard.component.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/front/app/src/app/auth/auth.component.css b/front/app/src/app/routes/dashboard/dashboard.component.scss similarity index 100% rename from front/app/src/app/auth/auth.component.css rename to front/app/src/app/routes/dashboard/dashboard.component.scss diff --git a/front/app/src/app/routes/dashboard/dashboard.component.ts b/front/app/src/app/routes/dashboard/dashboard.component.ts new file mode 100644 index 00000000..51790451 --- /dev/null +++ b/front/app/src/app/routes/dashboard/dashboard.component.ts @@ -0,0 +1,13 @@ +import { Component, OnInit, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; + +@Component({ + selector: 'app-dashboard', + templateUrl: './dashboard.component.html', + styleUrls: ['./dashboard.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class DashboardComponent implements OnInit { + constructor(private cdr: ChangeDetectorRef) {} + + ngOnInit() {} +} diff --git a/front/app/src/app/routes/routes-routing.module.ts b/front/app/src/app/routes/routes-routing.module.ts new file mode 100644 index 00000000..896ba3aa --- /dev/null +++ b/front/app/src/app/routes/routes-routing.module.ts @@ -0,0 +1,48 @@ +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; +import { environment } from '@env/environment'; + +import { AdminLayoutComponent } from '../theme/admin-layout/admin-layout.component'; +import { AuthLayoutComponent } from '../theme/auth-layout/auth-layout.component'; +import { DashboardComponent } from './dashboard/dashboard.component'; +import { LoginComponent } from './sessions/login/login.component'; +import { RegisterComponent } from './sessions/register/register.component'; +import { Error403Component } from './sessions/403.component'; +import { Error404Component } from './sessions/404.component'; +import { Error500Component } from './sessions/500.component'; +import { AuthGuard } from '@core'; + +const routes: Routes = [ + { + path: '', + component: AdminLayoutComponent, + canActivate: [AuthGuard], + canActivateChild: [AuthGuard], + children: [ + { path: '', redirectTo: 'dashboard', pathMatch: 'full' }, + { path: 'dashboard', component: DashboardComponent }, + { path: '403', component: Error403Component }, + { path: '404', component: Error404Component }, + { path: '500', component: Error500Component }, + ], + }, + { + path: 'auth', + component: AuthLayoutComponent, + children: [ + { path: 'login', component: LoginComponent }, + { path: 'register', component: RegisterComponent }, + ], + }, + { path: '**', redirectTo: 'dashboard' }, +]; + +@NgModule({ + imports: [ + RouterModule.forRoot(routes, { + useHash: environment.useHash, + }), + ], + exports: [RouterModule], +}) +export class RoutesRoutingModule {} diff --git a/front/app/src/app/routes/routes.module.ts b/front/app/src/app/routes/routes.module.ts new file mode 100644 index 00000000..b2f943af --- /dev/null +++ b/front/app/src/app/routes/routes.module.ts @@ -0,0 +1,26 @@ +import { NgModule } from '@angular/core'; +import { SharedModule } from '@shared/shared.module'; +import { RoutesRoutingModule } from './routes-routing.module'; + +import { DashboardComponent } from './dashboard/dashboard.component'; +import { LoginComponent } from './sessions/login/login.component'; +import { RegisterComponent } from './sessions/register/register.component'; +import { Error403Component } from './sessions/403.component'; +import { Error404Component } from './sessions/404.component'; +import { Error500Component } from './sessions/500.component'; + +const COMPONENTS: any[] = [ + DashboardComponent, + LoginComponent, + RegisterComponent, + Error403Component, + Error404Component, + Error500Component, +]; +const COMPONENTS_DYNAMIC: any[] = []; + +@NgModule({ + imports: [SharedModule, RoutesRoutingModule], + declarations: [...COMPONENTS, ...COMPONENTS_DYNAMIC], +}) +export class RoutesModule {} diff --git a/front/app/src/app/routes/sessions/403.component.ts b/front/app/src/app/routes/sessions/403.component.ts new file mode 100644 index 00000000..81f76f6c --- /dev/null +++ b/front/app/src/app/routes/sessions/403.component.ts @@ -0,0 +1,13 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-error-403', + template: ` + + `, +}) +export class Error403Component {} diff --git a/front/app/src/app/routes/sessions/404.component.ts b/front/app/src/app/routes/sessions/404.component.ts new file mode 100644 index 00000000..5d1fecd6 --- /dev/null +++ b/front/app/src/app/routes/sessions/404.component.ts @@ -0,0 +1,13 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-error-404', + template: ` + + `, +}) +export class Error404Component {} diff --git a/front/app/src/app/routes/sessions/500.component.ts b/front/app/src/app/routes/sessions/500.component.ts new file mode 100644 index 00000000..024edf4f --- /dev/null +++ b/front/app/src/app/routes/sessions/500.component.ts @@ -0,0 +1,14 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-error-500', + template: ` + + + `, +}) +export class Error500Component {} diff --git a/front/app/src/app/routes/sessions/login/login.component.html b/front/app/src/app/routes/sessions/login/login.component.html new file mode 100644 index 00000000..8a7fc1dc --- /dev/null +++ b/front/app/src/app/routes/sessions/login/login.component.html @@ -0,0 +1,45 @@ +
+ + + {{'login.title' | translate}}! + + + +
+ + {{'login.username' | translate}}: ng-matero + + + {{'login.please_enter' | translate}} + ng-matero + + {{ username.errors?.remote }} + + + + + {{'login.password' | translate}}: ng-matero + + + {{'login.please_enter' | translate}} + ng-matero + + {{ password.errors?.remote }} + + + + {{'login.remember_me' | translate}} + + + + +
{{'login.have_no_account' | translate}}? + {{'login.create_one' | translate}} +
+
+
+
+
diff --git a/front/app/src/app/routes/sessions/login/login.component.scss b/front/app/src/app/routes/sessions/login/login.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/front/app/src/app/routes/sessions/login/login.component.ts b/front/app/src/app/routes/sessions/login/login.component.ts new file mode 100644 index 00000000..679f97c8 --- /dev/null +++ b/front/app/src/app/routes/sessions/login/login.component.ts @@ -0,0 +1,58 @@ +import { Component } from '@angular/core'; +import { FormBuilder, Validators } from '@angular/forms'; +import { Router } from '@angular/router'; +import { HttpErrorResponse } from '@angular/common/http'; +import { filter } from 'rxjs/operators'; +import { AuthService } from '@core/authentication'; + +@Component({ + selector: 'app-login', + templateUrl: './login.component.html', + styleUrls: ['./login.component.scss'], +}) +export class LoginComponent { + isSubmitting = false; + + loginForm = this.fb.nonNullable.group({ + username: ['ng-matero', [Validators.required]], + password: ['ng-matero', [Validators.required]], + rememberMe: [false], + }); + + constructor(private fb: FormBuilder, private router: Router, private auth: AuthService) {} + + get username() { + return this.loginForm.get('username')!; + } + + get password() { + return this.loginForm.get('password')!; + } + + get rememberMe() { + return this.loginForm.get('rememberMe')!; + } + + login() { + this.isSubmitting = true; + + this.auth + .login(this.username.value, this.password.value, this.rememberMe.value) + .pipe(filter(authenticated => authenticated)) + .subscribe( + () => this.router.navigateByUrl('/'), + (errorRes: HttpErrorResponse) => { + if (errorRes.status === 422) { + const form = this.loginForm; + const errors = errorRes.error.errors; + Object.keys(errors).forEach(key => { + form.get(key === 'email' ? 'username' : key)?.setErrors({ + remote: errors[key][0], + }); + }); + } + this.isSubmitting = false; + } + ); + } +} diff --git a/front/app/src/app/routes/sessions/register/register.component.html b/front/app/src/app/routes/sessions/register/register.component.html new file mode 100644 index 00000000..64f61c75 --- /dev/null +++ b/front/app/src/app/routes/sessions/register/register.component.html @@ -0,0 +1,52 @@ +
+ + + + {{'register.welcome' | translate}},
+ {{'register.title' | translate}} +
+
+ + +
+ + {{'login.username' | translate}} + + + {{'validations.required' | translate}} + + + + + {{'login.password' | translate}} + + + {{'validations.required' | translate}} + + + + + {{'register.confirm_password' | translate}} + + + {{'validations.required' | translate}} + + + {{'validations.inconsistent'}} + + + + {{'register.agree' | translate}} + + + +
{{'register.have_an_account' | translate}}? + {{'login.login' | translate}} +
+
+
+
+
diff --git a/front/app/src/app/routes/sessions/register/register.component.scss b/front/app/src/app/routes/sessions/register/register.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/front/app/src/app/routes/sessions/register/register.component.ts b/front/app/src/app/routes/sessions/register/register.component.ts new file mode 100644 index 00000000..99db87ba --- /dev/null +++ b/front/app/src/app/routes/sessions/register/register.component.ts @@ -0,0 +1,39 @@ +import { Component } from '@angular/core'; +import { FormBuilder, Validators, AbstractControl } from '@angular/forms'; + +@Component({ + selector: 'app-register', + templateUrl: './register.component.html', + styleUrls: ['./register.component.scss'], +}) +export class RegisterComponent { + registerForm = this.fb.nonNullable.group( + { + username: ['', [Validators.required]], + password: ['', [Validators.required]], + confirmPassword: ['', [Validators.required]], + }, + { + validators: [this.matchValidator('password', 'confirmPassword')], + } + ); + + constructor(private fb: FormBuilder) {} + + matchValidator(source: string, target: string) { + return (control: AbstractControl) => { + const sourceControl = control.get(source)!; + const targetControl = control.get(target)!; + if (targetControl.errors && !targetControl.errors.mismatch) { + return null; + } + if (sourceControl.value !== targetControl.value) { + targetControl.setErrors({ mismatch: true }); + return { mismatch: true }; + } else { + targetControl.setErrors(null); + return null; + } + }; + } +} diff --git a/front/app/src/app/shared/components/breadcrumb/breadcrumb.component.html b/front/app/src/app/shared/components/breadcrumb/breadcrumb.component.html new file mode 100644 index 00000000..27c3068b --- /dev/null +++ b/front/app/src/app/shared/components/breadcrumb/breadcrumb.component.html @@ -0,0 +1,12 @@ + diff --git a/front/app/src/app/shared/components/breadcrumb/breadcrumb.component.scss b/front/app/src/app/shared/components/breadcrumb/breadcrumb.component.scss new file mode 100644 index 00000000..39f0958e --- /dev/null +++ b/front/app/src/app/shared/components/breadcrumb/breadcrumb.component.scss @@ -0,0 +1,33 @@ +.matero-breadcrumb { + display: flex; + flex-wrap: wrap; + margin-bottom: 1rem; + padding: 0; + font-size: .875rem; + list-style: none; +} + +.matero-breadcrumb-item { + line-height: 18px; + text-transform: capitalize; + + > * { + vertical-align: middle; + } + + > a.link { + color: currentColor; + + &:hover { + color: currentColor; + text-decoration: underline; + } + } + + > .chevron { + width: 18px; + height: 18px; + font-size: 18px; + user-select: none; + } +} diff --git a/front/app/src/app/shared/components/breadcrumb/breadcrumb.component.ts b/front/app/src/app/shared/components/breadcrumb/breadcrumb.component.ts new file mode 100644 index 00000000..bbc59972 --- /dev/null +++ b/front/app/src/app/shared/components/breadcrumb/breadcrumb.component.ts @@ -0,0 +1,33 @@ +import { Component, OnInit, ViewEncapsulation, Input } from '@angular/core'; +import { Router } from '@angular/router'; +import { MenuService } from '@core/bootstrap/menu.service'; + +@Component({ + selector: 'breadcrumb', + templateUrl: './breadcrumb.component.html', + styleUrls: ['./breadcrumb.component.scss'], + encapsulation: ViewEncapsulation.None, +}) +export class BreadcrumbComponent implements OnInit { + @Input() nav: string[] = []; + + constructor(private router: Router, private menu: MenuService) {} + + ngOnInit() { + this.nav = Array.isArray(this.nav) ? this.nav : []; + + if (this.nav.length === 0) { + this.genBreadcrumb(); + } + } + + trackByNavlink(index: number, navLink: string): string { + return navLink; + } + + genBreadcrumb() { + const routes = this.router.url.slice(1).split('/'); + this.nav = this.menu.getLevel(routes); + this.nav.unshift('home'); + } +} diff --git a/front/app/src/app/shared/components/error-code/_error-code-theme.scss b/front/app/src/app/shared/components/error-code/_error-code-theme.scss new file mode 100644 index 00000000..3971559a --- /dev/null +++ b/front/app/src/app/shared/components/error-code/_error-code-theme.scss @@ -0,0 +1,11 @@ +@use 'sass:map'; +@use '@angular/material' as mat; + +@mixin theme($theme) { + $background: map.get($theme, background); + $foreground: map.get($theme, foreground); + + .matero-error-code { + color: mat.get-color-from-palette($foreground, text); + } +} diff --git a/front/app/src/app/shared/components/error-code/_long-shadow.scss b/front/app/src/app/shared/components/error-code/_long-shadow.scss new file mode 100644 index 00000000..24dac9a8 --- /dev/null +++ b/front/app/src/app/shared/components/error-code/_long-shadow.scss @@ -0,0 +1,55 @@ +// Long Shadow +// +// https://codepen.io/c_fitzmaurice/pen/ZYJeRY + +@use 'sass:color'; +@use 'sass:list'; +@use 'sass:meta'; +@use 'sass:map'; +@use 'sass:math'; + +@function long-shadow($direction, $length, $color, $fade: false, $shadow-count: 100) { + $shadows: (); + $conversion-map: ( + to top: 180deg, + to top right: 135deg, + to right top: 135deg, + to right: 90deg, + to bottom right: 45deg, + to right bottom: 45deg, + to bottom: 0deg, + to bottom left: 315deg, + to left bottom: 315deg, + to left: 270deg, + to left top: 225deg, + to top left: 225deg + ); + + @if map-has-key($conversion-map, $direction) { + $direction: map.get($conversion-map, $direction); + } + + @for $i from 1 through $shadow-count { + $current-step: math.div($i * $length, $shadow-count); + $current-color: if( + not $fade, + $color, + if( + meta.type-of($fade) == 'color', + color.mix($fade, $color, math.div($i, $shadow-count) * 100%), + color.rgba($color, 1 - math.div($i, $shadow-count)) + ) + ); + + $shadows: list.append( + $shadows, + (math.sin(0deg + $direction) * $current-step) + (math.cos(0deg + $direction) * $current-step) + 0 + $current-color, + 'comma' + ); + } + + @return $shadows; +} diff --git a/front/app/src/app/shared/components/error-code/error-code.component.html b/front/app/src/app/shared/components/error-code/error-code.component.html new file mode 100644 index 00000000..76b02749 --- /dev/null +++ b/front/app/src/app/shared/components/error-code/error-code.component.html @@ -0,0 +1,6 @@ +
+
{{code}}
+
{{title}}
+
{{message}}
+ +
\ No newline at end of file diff --git a/front/app/src/app/shared/components/error-code/error-code.component.scss b/front/app/src/app/shared/components/error-code/error-code.component.scss new file mode 100644 index 00000000..440ac0c1 --- /dev/null +++ b/front/app/src/app/shared/components/error-code/error-code.component.scss @@ -0,0 +1,32 @@ +@use 'long-shadow'; + +.matero-error-wrap { + text-align: center; +} + +.matero-error-code { + padding: 20px 0; + font-size: 150px; + text-shadow: + long-shadow.long-shadow( + $direction: 45deg, + $length: 60px, + $color: rgba(0, 0, 0, .03), + $fade: rgba(0, 0, 0, .0015), + $shadow-count: 20 + ); +} + +.matero-error-title { + margin: 0 0 16px; + font-weight: 500; + font-size: 20px; + line-height: 32px; +} + +.matero-error-message { + margin: 0 0 16px; + font-weight: 400; + font-size: 16px; + line-height: 28px; +} diff --git a/front/app/src/app/shared/components/error-code/error-code.component.ts b/front/app/src/app/shared/components/error-code/error-code.component.ts new file mode 100644 index 00000000..51cdce54 --- /dev/null +++ b/front/app/src/app/shared/components/error-code/error-code.component.ts @@ -0,0 +1,13 @@ +import { Component, ViewEncapsulation, Input } from '@angular/core'; + +@Component({ + selector: 'error-code', + templateUrl: './error-code.component.html', + styleUrls: ['./error-code.component.scss'], + encapsulation: ViewEncapsulation.None, +}) +export class ErrorCodeComponent { + @Input() code = ''; + @Input() title = ''; + @Input() message = ''; +} diff --git a/front/app/src/app/shared/components/page-header/page-header.component.html b/front/app/src/app/shared/components/page-header/page-header.component.html new file mode 100644 index 00000000..94d9cb98 --- /dev/null +++ b/front/app/src/app/shared/components/page-header/page-header.component.html @@ -0,0 +1,4 @@ +
+

{{title | translate}} {{subtitle}}

+ +
diff --git a/front/app/src/app/shared/components/page-header/page-header.component.scss b/front/app/src/app/shared/components/page-header/page-header.component.scss new file mode 100644 index 00000000..5c7fe177 --- /dev/null +++ b/front/app/src/app/shared/components/page-header/page-header.component.scss @@ -0,0 +1,18 @@ +.matero-page-header { + display: block; + margin: -16px -16px 16px; + padding: 16px; + color: #fff; + background-color: #3f51b5; + + .matero-breadcrumb { + margin-top: 8px; + margin-bottom: 0; + } +} + +.matero-page-title { + margin: 0; + font-weight: 400; + font-size: 24px; +} diff --git a/front/app/src/app/shared/components/page-header/page-header.component.ts b/front/app/src/app/shared/components/page-header/page-header.component.ts new file mode 100644 index 00000000..bf27e9e2 --- /dev/null +++ b/front/app/src/app/shared/components/page-header/page-header.component.ts @@ -0,0 +1,46 @@ +import { Component, OnInit, ViewEncapsulation, Input, HostBinding } from '@angular/core'; +import { MenuService } from '@core/bootstrap/menu.service'; +import { Router } from '@angular/router'; +import { BooleanInput, coerceBooleanProperty } from '@angular/cdk/coercion'; + +@Component({ + selector: 'page-header', + templateUrl: './page-header.component.html', + styleUrls: ['./page-header.component.scss'], + encapsulation: ViewEncapsulation.None, +}) +export class PageHeaderComponent implements OnInit { + @HostBinding('class') class = 'matero-page-header'; + + @Input() title = ''; + @Input() subtitle = ''; + @Input() nav: string[] = []; + @Input() + get hideBreadcrumb() { + return this._hideBreadCrumb; + } + set hideBreadcrumb(value: boolean) { + this._hideBreadCrumb = coerceBooleanProperty(value); + } + private _hideBreadCrumb = false; + + constructor(private router: Router, private menu: MenuService) {} + + ngOnInit() { + this.nav = Array.isArray(this.nav) ? this.nav : []; + + if (this.nav.length === 0) { + this.genBreadcrumb(); + } + + this.title = this.title || this.nav[this.nav.length - 1]; + } + + genBreadcrumb() { + const routes = this.router.url.slice(1).split('/'); + this.nav = this.menu.getLevel(routes); + this.nav.unshift('home'); + } + + static ngAcceptInputType_hideBreadcrumb: BooleanInput; +} diff --git a/front/app/src/app/shared/directives/disable-control.directive.ts b/front/app/src/app/shared/directives/disable-control.directive.ts new file mode 100644 index 00000000..a55ee2d9 --- /dev/null +++ b/front/app/src/app/shared/directives/disable-control.directive.ts @@ -0,0 +1,14 @@ +import { NgControl } from '@angular/forms'; +import { Directive, Input, SkipSelf, Optional } from '@angular/core'; + +@Directive({ + selector: '[disableControl]', +}) +export class DisableControlDirective { + @Input() set disableControl(condition: boolean) { + const action = condition ? 'disable' : 'enable'; + this.ngControl.control?.[action](); + } + + constructor(@Optional() @SkipSelf() private ngControl: NgControl) {} +} diff --git a/front/app/src/app/shared/in-mem/in-mem-data.service.spec.ts b/front/app/src/app/shared/in-mem/in-mem-data.service.spec.ts new file mode 100644 index 00000000..3d497005 --- /dev/null +++ b/front/app/src/app/shared/in-mem/in-mem-data.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { InMemDataService } from './in-mem-data.service'; + +describe('InMemDataService', () => { + let service: InMemDataService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(InMemDataService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/front/app/src/app/shared/in-mem/in-mem-data.service.ts b/front/app/src/app/shared/in-mem/in-mem-data.service.ts new file mode 100644 index 00000000..75933714 --- /dev/null +++ b/front/app/src/app/shared/in-mem/in-mem-data.service.ts @@ -0,0 +1,209 @@ +import { Injectable } from '@angular/core'; +import { HttpRequest } from '@angular/common/http'; +import { InMemoryDbService, RequestInfo, STATUS } from 'angular-in-memory-web-api'; +import { from, Observable } from 'rxjs'; +import { ajax } from 'rxjs/ajax'; +import { find, map, switchMap } from 'rxjs/operators'; +import { environment } from '@env/environment'; +import { base64, currentTimestamp, filterObject, User } from '@core/authentication'; + +class JWT { + generate(user: User) { + const expiresIn = 3600; + const refreshTokenExpiresIn = 86400; + + return filterObject({ + access_token: this.createToken(user, expiresIn), + token_type: 'bearer', + expires_in: user.refresh_token ? expiresIn : undefined, + refresh_token: user.refresh_token ? this.createToken(user, refreshTokenExpiresIn) : undefined, + }); + } + + getUser(req: HttpRequest) { + let token = ''; + + if (req.body?.refresh_token) { + token = req.body.refresh_token; + } else if (req.headers.has('Authorization')) { + const authorization = req.headers.get('Authorization'); + const result = (authorization as string).split(' '); + token = result[1]; + } + + try { + const now = new Date(); + const data = JWT.parseToken(token); + + return JWT.isExpired(data, now) ? null : data.user; + } catch (e) { + return null; + } + } + + createToken(user: User, expiresIn = 0) { + const exp = user.refresh_token ? currentTimestamp() + expiresIn : undefined; + + return [ + base64.encode(JSON.stringify({ typ: 'JWT', alg: 'HS256' })), + base64.encode(JSON.stringify(filterObject(Object.assign({ exp, user })))), + base64.encode('ng-matero'), + ].join('.'); + } + + private static parseToken(accessToken: string) { + const [, payload] = accessToken.split('.'); + + return JSON.parse(base64.decode(payload)); + } + + private static isExpired(data: any, now: Date) { + const expiresIn = new Date(); + expiresIn.setTime(data.exp * 1000); + const diff = this.dateToSeconds(expiresIn) - this.dateToSeconds(now); + + return diff <= 0; + } + + private static dateToSeconds(date: Date) { + return Math.ceil(date.getTime() / 1000); + } +} + +const jwt = new JWT(); + +function is(reqInfo: RequestInfo, path: string) { + if (environment.baseUrl) { + return false; + } + + return new RegExp(`${path}(/)?$`, 'i').test(reqInfo.req.url); +} + +@Injectable({ + providedIn: 'root', +}) +export class InMemDataService implements InMemoryDbService { + private users: User[] = [ + { + id: 1, + username: 'ng-matero', + password: 'ng-matero', + name: 'Zongbin', + email: 'nzb329@163.com', + avatar: './assets/images/avatar.jpg', + }, + { + id: 2, + username: 'recca0120', + password: 'password', + name: 'recca0120', + email: 'recca0120@gmail.com', + avatar: './assets/images/avatars/avatar-10.jpg', + refresh_token: true, + }, + ]; + + createDb( + reqInfo?: RequestInfo + ): + | Record + | Observable> + | Promise> { + return { users: this.users }; + } + + get(reqInfo: RequestInfo) { + const { headers, url } = reqInfo; + + if (is(reqInfo, 'sanctum/csrf-cookie')) { + const response = { headers, url, status: STATUS.NO_CONTENT, body: {} }; + + return reqInfo.utils.createResponse$(() => response); + } + + if (is(reqInfo, 'me/menu')) { + return ajax('assets/data/menu.json?_t=' + Date.now()).pipe( + map((response: any) => { + return { headers, url, status: STATUS.OK, body: { menu: response.response.menu } }; + }), + switchMap(response => reqInfo.utils.createResponse$(() => response)) + ); + } + + if (is(reqInfo, 'me')) { + const user = jwt.getUser(reqInfo.req as HttpRequest); + const result = user + ? { status: STATUS.OK, body: user } + : { status: STATUS.UNAUTHORIZED, body: {} }; + const response = Object.assign({ headers, url }, result); + + return reqInfo.utils.createResponse$(() => response); + } + + return; + } + + post(reqInfo: RequestInfo) { + if (is(reqInfo, 'auth/login')) { + return this.login(reqInfo); + } + + if (is(reqInfo, 'auth/refresh')) { + return this.refresh(reqInfo); + } + + if (is(reqInfo, 'auth/logout')) { + return this.logout(reqInfo); + } + + return; + } + + private login(reqInfo: RequestInfo) { + const { headers, url } = reqInfo; + const req = reqInfo.req as HttpRequest; + const { username, password } = req.body; + + return from(this.users).pipe( + find(user => user.username === username || user.email === username), + map(user => { + if (!user) { + return { headers, url, status: STATUS.UNAUTHORIZED, body: {} }; + } + + if (user.password !== password) { + const result = { + status: STATUS.UNPROCESSABLE_ENTRY, + error: { errors: { password: ['The provided password is incorrect.'] } }, + }; + + return Object.assign({ headers, url }, result); + } + + const currentUser = Object.assign({}, user); + delete currentUser.password; + return { headers, url, status: STATUS.OK, body: jwt.generate(currentUser) }; + }), + switchMap(response => reqInfo.utils.createResponse$(() => response)) + ); + } + + private refresh(reqInfo: RequestInfo) { + const { headers, url } = reqInfo; + const user = jwt.getUser(reqInfo.req as HttpRequest); + const result = user + ? { status: STATUS.OK, body: jwt.generate(user) } + : { status: STATUS.UNAUTHORIZED, body: {} }; + const response = Object.assign({ headers, url }, result); + + return reqInfo.utils.createResponse$(() => response); + } + + private logout(reqInfo: RequestInfo) { + const { headers, url } = reqInfo; + const response = { headers, url, status: STATUS.OK, body: {} }; + + return reqInfo.utils.createResponse$(() => response); + } +} diff --git a/front/app/src/app/shared/index.ts b/front/app/src/app/shared/index.ts new file mode 100644 index 00000000..b1382ae7 --- /dev/null +++ b/front/app/src/app/shared/index.ts @@ -0,0 +1,12 @@ +// Module +export * from './shared.module'; + +// Services +export * from './services/directionality.service'; +export * from './services/message.service'; +export * from './services/storage.service'; +export * from './services/paginator-i18n.service'; + +// Utils +export * from './utils/colors'; +export * from './utils/icons'; diff --git a/front/app/src/app/shared/pipes/safe-url.pipe.ts b/front/app/src/app/shared/pipes/safe-url.pipe.ts new file mode 100644 index 00000000..3869d9a9 --- /dev/null +++ b/front/app/src/app/shared/pipes/safe-url.pipe.ts @@ -0,0 +1,10 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import { DomSanitizer } from '@angular/platform-browser'; + +@Pipe({ name: 'safeUrl' }) +export class SafeUrlPipe implements PipeTransform { + constructor(private sanitizer: DomSanitizer) {} + transform(url: string) { + return this.sanitizer.bypassSecurityTrustResourceUrl(url); + } +} diff --git a/front/app/src/app/shared/pipes/to-observable.pipe.ts b/front/app/src/app/shared/pipes/to-observable.pipe.ts new file mode 100644 index 00000000..eaed0de6 --- /dev/null +++ b/front/app/src/app/shared/pipes/to-observable.pipe.ts @@ -0,0 +1,9 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import { Observable, of, isObservable } from 'rxjs'; + +@Pipe({ name: 'toObservable' }) +export class ToObservablePipe implements PipeTransform { + transform(value: Observable | unknown): Observable { + return isObservable(value) ? value : of(value); + } +} diff --git a/front/app/src/app/shared/services/directionality.service.ts b/front/app/src/app/shared/services/directionality.service.ts new file mode 100644 index 00000000..8b0e9d1b --- /dev/null +++ b/front/app/src/app/shared/services/directionality.service.ts @@ -0,0 +1,22 @@ +import { Direction, Directionality } from '@angular/cdk/bidi'; +import { EventEmitter, Injectable, OnDestroy } from '@angular/core'; + +@Injectable({ + providedIn: 'root', +}) +export class AppDirectionality implements Directionality, OnDestroy { + readonly change = new EventEmitter(); + + get value(): Direction { + return this._value; + } + set value(value: Direction) { + this._value = value; + this.change.next(value); + } + private _value: Direction = 'ltr'; + + ngOnDestroy() { + this.change.complete(); + } +} diff --git a/front/app/src/app/shared/services/message.service.ts b/front/app/src/app/shared/services/message.service.ts new file mode 100644 index 00000000..d72412e1 --- /dev/null +++ b/front/app/src/app/shared/services/message.service.ts @@ -0,0 +1,16 @@ +import { Injectable } from '@angular/core'; + +@Injectable({ + providedIn: 'root', +}) +export class MessageService { + messages: string[] = []; + + add(message: string) { + this.messages.push(message); + } + + clear() { + this.messages = []; + } +} diff --git a/front/app/src/app/shared/services/paginator-i18n.service.ts b/front/app/src/app/shared/services/paginator-i18n.service.ts new file mode 100644 index 00000000..a579a35d --- /dev/null +++ b/front/app/src/app/shared/services/paginator-i18n.service.ts @@ -0,0 +1,45 @@ +import { Injectable } from '@angular/core'; +import { MatPaginatorIntl } from '@angular/material/paginator'; +import { LangChangeEvent, TranslateService } from '@ngx-translate/core'; + +@Injectable({ + providedIn: 'root', +}) +export class PaginatorI18nService { + paginatorIntl = new MatPaginatorIntl(); + + constructor(private translate: TranslateService) { + this.translate.onLangChange.subscribe((event: LangChangeEvent) => this.getPaginatorIntl()); + } + + getPaginatorIntl() { + this.paginatorIntl.itemsPerPageLabel = this.translate.instant('paginator.items_per_page_label'); + this.paginatorIntl.previousPageLabel = this.translate.instant('paginator.previous_page_label'); + this.paginatorIntl.nextPageLabel = this.translate.instant('paginator.next_page_label'); + this.paginatorIntl.firstPageLabel = this.translate.instant('paginator.first_page_label'); + this.paginatorIntl.lastPageLabel = this.translate.instant('paginator.last_page_label'); + this.paginatorIntl.getRangeLabel = this.getRangeLabel.bind(this); + + this.paginatorIntl.changes.next(); + + return this.paginatorIntl; + } + + private getRangeLabel(page: number, pageSize: number, length: number): string { + if (length === 0 || pageSize === 0) { + return this.translate.instant('paginator.range_page_label_1', { length }); + } + length = Math.max(length, 0); + + const startIndex = page * pageSize; + // If the start index exceeds the list length, do not try and fix the end index to the end. + const endIndex = + startIndex < length ? Math.min(startIndex + pageSize, length) : startIndex + pageSize; + + return this.translate.instant('paginator.range_page_label_2', { + startIndex: startIndex + 1, + endIndex, + length, + }); + } +} diff --git a/front/app/src/app/shared/services/storage.service.ts b/front/app/src/app/shared/services/storage.service.ts new file mode 100644 index 00000000..66ac4b53 --- /dev/null +++ b/front/app/src/app/shared/services/storage.service.ts @@ -0,0 +1,53 @@ +import { Injectable } from '@angular/core'; + +@Injectable({ + providedIn: 'root', +}) +export class LocalStorageService { + get(key: string) { + return JSON.parse(localStorage.getItem(key) || '{}') || {}; + } + + set(key: string, value: any): boolean { + localStorage.setItem(key, JSON.stringify(value)); + + return true; + } + + has(key: string): boolean { + return !!localStorage.getItem(key); + } + + remove(key: string) { + localStorage.removeItem(key); + } + + clear() { + localStorage.clear(); + } +} + +export class MemoryStorageService { + private store: { [k: string]: string } = {}; + + get(key: string) { + return JSON.parse(this.store[key] || '{}') || {}; + } + + set(key: string, value: any): boolean { + this.store[key] = JSON.stringify(value); + return true; + } + + has(key: string): boolean { + return !!this.store[key]; + } + + remove(key: string) { + delete this.store[key]; + } + + clear() { + this.store = {}; + } +} diff --git a/front/app/src/app/shared/shared.module.ts b/front/app/src/app/shared/shared.module.ts new file mode 100644 index 00000000..a3d9c8a5 --- /dev/null +++ b/front/app/src/app/shared/shared.module.ts @@ -0,0 +1,51 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { RouterModule } from '@angular/router'; +import { ReactiveFormsModule, FormsModule } from '@angular/forms'; + +import { MaterialModule } from '../material.module'; +import { MaterialExtensionsModule } from '../material-extensions.module'; + +import { FormlyModule } from '@ngx-formly/core'; +import { FormlyMaterialModule } from '@ngx-formly/material'; +import { NgProgressModule } from 'ngx-progressbar'; +import { NgProgressHttpModule } from 'ngx-progressbar/http'; +import { NgProgressRouterModule } from 'ngx-progressbar/router'; +import { NgxPermissionsModule } from 'ngx-permissions'; +import { ToastrModule } from 'ngx-toastr'; +import { TranslateModule } from '@ngx-translate/core'; + +import { BreadcrumbComponent } from './components/breadcrumb/breadcrumb.component'; +import { PageHeaderComponent } from './components/page-header/page-header.component'; +import { ErrorCodeComponent } from './components/error-code/error-code.component'; +import { DisableControlDirective } from './directives/disable-control.directive'; +import { SafeUrlPipe } from './pipes/safe-url.pipe'; +import { ToObservablePipe } from './pipes/to-observable.pipe'; + +const MODULES: any[] = [ + CommonModule, + RouterModule, + ReactiveFormsModule, + FormsModule, + MaterialModule, + MaterialExtensionsModule, + FormlyModule, + FormlyMaterialModule, + NgProgressModule, + NgProgressRouterModule, + NgProgressHttpModule, + NgxPermissionsModule, + ToastrModule, + TranslateModule, +]; +const COMPONENTS: any[] = [BreadcrumbComponent, PageHeaderComponent, ErrorCodeComponent]; +const COMPONENTS_DYNAMIC: any[] = []; +const DIRECTIVES: any[] = [DisableControlDirective]; +const PIPES: any[] = [SafeUrlPipe, ToObservablePipe]; + +@NgModule({ + imports: [...MODULES], + exports: [...MODULES, ...COMPONENTS, ...DIRECTIVES, ...PIPES], + declarations: [...COMPONENTS, ...COMPONENTS_DYNAMIC, ...DIRECTIVES, ...PIPES], +}) +export class SharedModule {} diff --git a/front/app/src/app/shared/utils/colors.ts b/front/app/src/app/shared/utils/colors.ts new file mode 100644 index 00000000..64bcbda8 --- /dev/null +++ b/front/app/src/app/shared/utils/colors.ts @@ -0,0 +1,610 @@ +export const MAT_COLORS = { + 'red': { + 50: '#FFEBEE', + 100: '#FFCDD2', + 200: '#EF9A9A', + 300: '#E57373', + 400: '#EF5350', + 500: '#F44336', + 600: '#E53935', + 700: '#D32F2F', + 800: '#C62828', + 900: '#B71C1C', + A100: '#FF8A80', + A200: '#FF5252', + A400: '#FF1744', + A700: '#D50000', + contrast: { + 50: 'dark', + 100: 'dark', + 200: 'dark', + 300: 'dark', + 400: 'light', + 500: 'light', + 600: 'light', + 700: 'light', + 800: 'light', + 900: 'light', + A100: 'dark', + A200: 'light', + A400: 'light', + A700: 'light', + }, + }, + 'pink': { + 50: '#FCE4EC', + 100: '#F8BBD0', + 200: '#F48FB1', + 300: '#F06292', + 400: '#EC407A', + 500: '#E91E63', + 600: '#D81B60', + 700: '#C2185B', + 800: '#AD1457', + 900: '#880E4F', + A100: '#FF80AB', + A200: '#FF4081', + A400: '#F50057', + A700: '#C51162', + contrast: { + 50: 'dark', + 100: 'dark', + 200: 'dark', + 300: 'dark', + 400: 'light', + 500: 'light', + 600: 'light', + 700: 'light', + 800: 'light', + 900: 'light', + A100: 'dark', + A200: 'light', + A400: 'light', + A700: 'light', + }, + }, + 'purple': { + 50: '#F3E5F5', + 100: '#E1BEE7', + 200: '#CE93D8', + 300: '#BA68C8', + 400: '#AB47BC', + 500: '#9C27B0', + 600: '#8E24AA', + 700: '#7B1FA2', + 800: '#6A1B9A', + 900: '#4A148C', + A100: '#EA80FC', + A200: '#E040FB', + A400: '#D500F9', + A700: '#AA00FF', + contrast: { + 50: 'dark', + 100: 'dark', + 200: 'dark', + 300: 'light', + 400: 'light', + 500: 'light', + 600: 'light', + 700: 'light', + 800: 'light', + 900: 'light', + A100: 'dark', + A200: 'light', + A400: 'light', + A700: 'light', + }, + }, + 'deep-purple': { + 50: '#EDE7F6', + 100: '#D1C4E9', + 200: '#B39DDB', + 300: '#9575CD', + 400: '#7E57C2', + 500: '#673AB7', + 600: '#5E35B1', + 700: '#512DA8', + 800: '#4527A0', + 900: '#311B92', + A100: '#B388FF', + A200: '#7C4DFF', + A400: '#651FFF', + A700: '#6200EA', + contrast: { + 50: 'dark', + 100: 'dark', + 200: 'dark', + 300: 'light', + 400: 'light', + 500: 'light', + 600: 'light', + 700: 'light', + 800: 'light', + 900: 'light', + A100: 'dark', + A200: 'light', + A400: 'light', + A700: 'light', + }, + }, + 'indigo': { + 50: '#E8EAF6', + 100: '#C5CAE9', + 200: '#9FA8DA', + 300: '#7986CB', + 400: '#5C6BC0', + 500: '#3F51B5', + 600: '#3949AB', + 700: '#303F9F', + 800: '#283593', + 900: '#1A237E', + A100: '#8C9EFF', + A200: '#536DFE', + A400: '#3D5AFE', + A700: '#304FFE', + contrast: { + 50: 'dark', + 100: 'dark', + 200: 'dark', + 300: 'light', + 400: 'light', + 500: 'light', + 600: 'light', + 700: 'light', + 800: 'light', + 900: 'light', + A100: 'dark', + A200: 'light', + A400: 'light', + A700: 'light', + }, + }, + 'blue': { + 50: '#E3F2FD', + 100: '#BBDEFB', + 200: '#90CAF9', + 300: '#64B5F6', + 400: '#42A5F5', + 500: '#2196F3', + 600: '#1E88E5', + 700: '#1976D2', + 800: '#1565C0', + 900: '#0D47A1', + A100: '#82B1FF', + A200: '#448AFF', + A400: '#2979FF', + A700: '#2962FF', + contrast: { + 50: 'dark', + 100: 'dark', + 200: 'dark', + 300: 'dark', + 400: 'dark', + 500: 'dark', + 600: 'light', + 700: 'light', + 800: 'light', + 900: 'light', + A100: 'dark', + A200: 'light', + A400: 'light', + A700: 'light', + }, + }, + 'light-blue': { + 50: '#E1F5FE', + 100: '#B3E5FC', + 200: '#81D4FA', + 300: '#4FC3F7', + 400: '#29B6F6', + 500: '#03A9F4', + 600: '#039BE5', + 700: '#0288D1', + 800: '#0277BD', + 900: '#01579B', + A100: '#80D8FF', + A200: '#40C4FF', + A400: '#00B0FF', + A700: '#0091EA', + contrast: { + 50: 'dark', + 100: 'dark', + 200: 'dark', + 300: 'dark', + 400: 'dark', + 500: 'dark', + 600: 'dark', + 700: 'light', + 800: 'light', + 900: 'light', + A100: 'dark', + A200: 'dark', + A400: 'dark', + A700: 'light', + }, + }, + 'cyan': { + 50: '#E0F7FA', + 100: '#B2EBF2', + 200: '#80DEEA', + 300: '#4DD0E1', + 400: '#26C6DA', + 500: '#00BCD4', + 600: '#00ACC1', + 700: '#0097A7', + 800: '#00838F', + 900: '#006064', + A100: '#84FFFF', + A200: '#18FFFF', + A400: '#00E5FF', + A700: '#00B8D4', + contrast: { + 50: 'dark', + 100: 'dark', + 200: 'dark', + 300: 'dark', + 400: 'dark', + 500: 'dark', + 600: 'dark', + 700: 'light', + 800: 'light', + 900: 'light', + A100: 'dark', + A200: 'dark', + A400: 'dark', + A700: 'dark', + }, + }, + 'teal': { + 50: '#E0F2F1', + 100: '#B2DFDB', + 200: '#80CBC4', + 300: '#4DB6AC', + 400: '#26A69A', + 500: '#009688', + 600: '#00897B', + 700: '#00796B', + 800: '#00695C', + 900: '#004D40', + A100: '#A7FFEB', + A200: '#64FFDA', + A400: '#1DE9B6', + A700: '#00BFA5', + contrast: { + 50: 'dark', + 100: 'dark', + 200: 'dark', + 300: 'dark', + 400: 'dark', + 500: 'light', + 600: 'light', + 700: 'light', + 800: 'light', + 900: 'light', + A100: 'dark', + A200: 'dark', + A400: 'dark', + A700: 'dark', + }, + }, + 'green': { + 50: '#E8F5E9', + 100: '#C8E6C9', + 200: '#A5D6A7', + 300: '#81C784', + 400: '#66BB6A', + 500: '#4CAF50', + 600: '#43A047', + 700: '#388E3C', + 800: '#2E7D32', + 900: '#1B5E20', + A100: '#B9F6CA', + A200: '#69F0AE', + A400: '#00E676', + A700: '#00C853', + contrast: { + 50: 'dark', + 100: 'dark', + 200: 'dark', + 300: 'dark', + 400: 'dark', + 500: 'dark', + 600: 'light', + 700: 'light', + 800: 'light', + 900: 'light', + A100: 'dark', + A200: 'dark', + A400: 'dark', + A700: 'dark', + }, + }, + 'light-green': { + 50: '#F1F8E9', + 100: '#DCEDC8', + 200: '#C5E1A5', + 300: '#AED581', + 400: '#9CCC65', + 500: '#8BC34A', + 600: '#7CB342', + 700: '#689F38', + 800: '#558B2F', + 900: '#33691E', + A100: '#CCFF90', + A200: '#B2FF59', + A400: '#76FF03', + A700: '#64DD17', + contrast: { + 50: 'dark', + 100: 'dark', + 200: 'dark', + 300: 'dark', + 400: 'dark', + 500: 'dark', + 600: 'dark', + 700: 'dark', + 800: 'light', + 900: 'light', + A100: 'dark', + A200: 'dark', + A400: 'dark', + A700: 'dark', + }, + }, + 'lime': { + 50: '#F9FBE7', + 100: '#F0F4C3', + 200: '#E6EE9C', + 300: '#DCE775', + 400: '#D4E157', + 500: '#CDDC39', + 600: '#C0CA33', + 700: '#AFB42B', + 800: '#9E9D24', + 900: '#827717', + A100: '#F4FF81', + A200: '#EEFF41', + A400: '#C6FF00', + A700: '#AEEA00', + contrast: { + 50: 'dark', + 100: 'dark', + 200: 'dark', + 300: 'dark', + 400: 'dark', + 500: 'dark', + 600: 'dark', + 700: 'dark', + 800: 'dark', + 900: 'light', + A100: 'dark', + A200: 'dark', + A400: 'dark', + A700: 'dark', + }, + }, + 'yellow': { + 50: '#FFFDE7', + 100: '#FFF9C4', + 200: '#FFF59D', + 300: '#FFF176', + 400: '#FFEE58', + 500: '#FFEB3B', + 600: '#FDD835', + 700: '#FBC02D', + 800: '#F9A825', + 900: '#F57F17', + A100: '#FFFF8D', + A200: '#FFFF00', + A400: '#FFEA00', + A700: '#FFD600', + contrast: { + 50: 'dark', + 100: 'dark', + 200: 'dark', + 300: 'dark', + 400: 'dark', + 500: 'dark', + 600: 'dark', + 700: 'dark', + 800: 'dark', + 900: 'dark', + A100: 'dark', + A200: 'dark', + A400: 'dark', + A700: 'dark', + }, + }, + 'amber': { + 50: '#FFF8E1', + 100: '#FFECB3', + 200: '#FFE082', + 300: '#FFD54F', + 400: '#FFCA28', + 500: '#FFC107', + 600: '#FFB300', + 700: '#FFA000', + 800: '#FF8F00', + 900: '#FF6F00', + A100: '#FFE57F', + A200: '#FFD740', + A400: '#FFC400', + A700: '#FFAB00', + contrast: { + 50: 'dark', + 100: 'dark', + 200: 'dark', + 300: 'dark', + 400: 'dark', + 500: 'dark', + 600: 'dark', + 700: 'dark', + 800: 'dark', + 900: 'dark', + A100: 'dark', + A200: 'dark', + A400: 'dark', + A700: 'dark', + }, + }, + 'orange': { + 50: '#FFF3E0', + 100: '#FFE0B2', + 200: '#FFCC80', + 300: '#FFB74D', + 400: '#FFA726', + 500: '#FF9800', + 600: '#FB8C00', + 700: '#F57C00', + 800: '#EF6C00', + 900: '#E65100', + A100: '#FFD180', + A200: '#FFAB40', + A400: '#FF9100', + A700: '#FF6D00', + contrast: { + 50: 'dark', + 100: 'dark', + 200: 'dark', + 300: 'dark', + 400: 'dark', + 500: 'dark', + 600: 'dark', + 700: 'dark', + 800: 'dark', + 900: 'light', + A100: 'dark', + A200: 'dark', + A400: 'dark', + A700: 'dark', + }, + }, + 'deep-orange': { + 50: '#FBE9E7', + 100: '#FFCCBC', + 200: '#FFAB91', + 300: '#FF8A65', + 400: '#FF7043', + 500: '#FF5722', + 600: '#F4511E', + 700: '#E64A19', + 800: '#D84315', + 900: '#BF360C', + A100: '#FF9E80', + A200: '#FF6E40', + A400: '#FF3D00', + A700: '#DD2C00', + contrast: { + 50: 'dark', + 100: 'dark', + 200: 'dark', + 300: 'dark', + 400: 'dark', + 500: 'dark', + 600: 'dark', + 700: 'dark', + 800: 'dark', + 900: 'light', + A100: 'dark', + A200: 'dark', + A400: 'dark', + A700: 'dark', + }, + }, + 'brown': { + 50: '#EFEBE9', + 100: '#D7CCC8', + 200: '#BCAAA4', + 300: '#A1887F', + 400: '#8D6E63', + 500: '#795548', + 600: '#6D4C41', + 700: '#5D4037', + 800: '#4E342E', + 900: '#3E2723', + A100: '#D7CCC8', + A200: '#BCAAA4', + A400: '#8D6E63', + A700: '#5D4037', + contrast: { + 50: 'dark', + 100: 'dark', + 200: 'dark', + 300: 'light', + 400: 'light', + 500: 'light', + 600: 'light', + 700: 'light', + 800: 'light', + 900: 'light', + A100: 'dark', + A200: 'dark', + A400: 'light', + A700: 'light', + }, + }, + 'gray': { + 50: '#FAFAFA', + 100: '#F5F5F5', + 200: '#EEEEEE', + 300: '#E0E0E0', + 400: '#BDBDBD', + 500: '#9E9E9E', + 600: '#757575', + 700: '#616161', + 800: '#424242', + 900: '#212121', + A100: '#FFFFFF', + A200: ' #EEEEEE', + A400: '#BDBDBD', + A700: '#616161', + contrast: { + 50: 'dark', + 100: 'dark', + 200: 'dark', + 300: 'dark', + 400: 'dark', + 500: 'dark', + 600: 'light', + 700: 'light', + 800: 'light', + 900: 'light', + A100: 'dark', + A200: 'dark', + A400: 'dark', + A700: 'light', + }, + }, + 'blue-gray': { + 50: '#ECEFF1', + 100: '#CFD8DC', + 200: '#B0BEC5', + 300: '#90A4AE', + 400: '#78909C', + 500: '#607D8B', + 600: '#546E7A', + 700: '#455A64', + 800: '#37474F', + 900: '#263238', + A100: '#CFD8DC', + A200: '#B0BEC5', + A400: '#78909C', + A700: '#455A64', + contrast: { + 50: 'dark', + 100: 'dark', + 200: 'dark', + 300: 'dark', + 400: 'light', + 500: 'light', + 600: 'light', + 700: 'light', + 800: 'light', + 900: 'light', + A100: 'dark', + A200: 'dark', + A400: 'light', + A700: 'light', + }, + }, +}; diff --git a/front/app/src/app/shared/utils/icons.ts b/front/app/src/app/shared/utils/icons.ts new file mode 100644 index 00000000..afee22f1 --- /dev/null +++ b/front/app/src/app/shared/utils/icons.ts @@ -0,0 +1,1072 @@ +export const MAT_ICONS = { + action: [ + '3d_rotation', + 'accessibility', + 'accessibility_new', + 'accessible', + 'accessible_forward', + 'account_balance', + 'account_balance_wallet', + 'account_box', + 'account_circle', + 'add_shopping_cart', + 'alarm', + 'alarm_add', + 'alarm_off', + 'alarm_on', + 'all_inbox', + 'all_out', + 'android', + 'announcement', + 'arrow_right_alt', + 'aspect_ratio', + 'assessment', + 'assignment', + 'assignment_ind', + 'assignment_late', + 'assignment_return', + 'assignment_returned', + 'assignment_turned_in', + 'autorenew', + 'backup', + 'book', + 'bookmark', + 'bookmark_border', + 'bookmarks', + 'bug_report', + 'build', + 'cached', + 'calendar_today', + 'calendar_view_day', + 'camera_enhance', + 'card_giftcard', + 'card_membership', + 'card_travel', + 'change_history', + 'check_circle', + 'check_circle_outline', + 'chrome_reader_mode', + 'class', + 'code', + 'commute', + 'compare_arrows', + 'contact_support', + 'copyright', + 'credit_card', + 'dashboard', + 'date_range', + 'delete', + 'delete_forever', + 'delete_outline', + 'description', + 'dns', + 'done', + 'done_all', + 'done_outline', + 'donut_large', + 'donut_small', + 'drag_indicator', + 'eject', + 'euro_symbol', + 'event', + 'event_seat', + 'exit_to_app', + 'explore', + 'explore_off', + 'extension', + 'face', + 'favorite', + 'favorite_border', + 'feedback', + 'find_in_page', + 'find_replace', + 'fingerprint', + 'flight_land', + 'flight_takeoff', + 'flip_to_back', + 'flip_to_front', + 'g_translate', + 'gavel', + 'get_app', + 'gif', + 'grade', + 'group_work', + 'help', + 'help_outline', + 'highlight_off', + 'history', + 'home', + 'horizontal_split', + 'hourglass_empty', + 'hourglass_full', + 'http', + 'https', + 'important_devices', + 'info', + 'input', + 'invert_colors', + 'label', + 'label_important', + 'label_off', + 'language', + 'launch', + 'line_style', + 'line_weight', + 'list', + 'lock', + 'lock_open', + 'loyalty', + 'markunread_mailbox', + 'maximize', + 'minimize', + 'motorcycle', + 'note_add', + 'offline_bolt', + 'offline_pin', + 'opacity', + 'open_in_browser', + 'open_in_new', + 'open_with', + 'pageview', + 'pan_tool', + 'payment', + 'perm_camera_mic', + 'perm_contact_calendar', + 'perm_data_setting', + 'perm_device_information', + 'perm_identity', + 'perm_media', + 'perm_phone_msg', + 'perm_scan_wifi', + 'pets', + 'picture_in_picture', + 'picture_in_picture_alt', + 'play_for_work', + 'polymer', + 'power_settings_new', + 'pregnant_woman', + 'print', + 'query_builder', + 'question_answer', + 'receipt', + 'record_voice_over', + 'redeem', + 'remove_shopping_cart', + 'reorder', + 'report_problem', + 'restore', + 'restore_from_trash', + 'restore_page', + 'room', + 'rounded_corner', + 'rowing', + 'schedule', + 'search', + 'settings', + 'settings_applications', + 'settings_backup_restore', + 'settings_bluetooth', + 'settings_brightness', + 'settings_cell', + 'settings_ethernet', + 'settings_input_antenna', + 'settings_input_component', + 'settings_input_composite', + 'settings_input_hdmi', + 'settings_input_svideo', + 'settings_overscan', + 'settings_phone', + 'settings_power', + 'settings_remote', + 'settings_voice', + 'shop', + 'shop_two', + 'shopping_basket', + 'shopping_cart', + 'speaker_notes', + 'speaker_notes_off', + 'spellcheck', + 'star_rate', + 'stars', + 'store', + 'subject', + 'supervised_user_circle', + 'supervisor_account', + 'swap_horiz', + 'swap_horizontal_circle', + 'swap_vert', + 'swap_vertical_circle', + 'tab', + 'tab_unselected', + 'text_rotate_up', + 'text_rotate_vertical', + 'text_rotation_down', + 'text_rotation_none', + 'theaters', + 'thumb_down', + 'thumb_up', + 'thumbs_up_down', + 'timeline', + 'toc', + 'today', + 'toll', + 'touch_app', + 'track_changes', + 'translate', + 'trending_down', + 'trending_flat', + 'trending_up', + 'turned_in', + 'turned_in_not', + 'update', + 'verified_user', + 'vertical_split', + 'view_agenda', + 'view_array', + 'view_carousel', + 'view_column', + 'view_day', + 'view_headline', + 'view_list', + 'view_module', + 'view_quilt', + 'view_stream', + 'view_week', + 'visibility', + 'visibility_off', + 'voice_over_off', + 'watch_later', + 'work', + 'work_off', + 'work_outline', + 'youtube_searched_for', + 'zoom_in', + 'zoom_out', + ], + alert: ['add_alert', 'error', 'error_outline', 'notification_important', 'warning'], + av: [ + '4k', + 'add_to_queue', + 'airplay', + 'album', + 'art_track', + 'av_timer', + 'branding_watermark', + 'call_to_action', + 'closed_caption', + 'control_camera', + 'equalizer', + 'explicit', + 'fast_forward', + 'fast_rewind', + 'featured_play_list', + 'featured_video', + 'fiber_dvr', + 'fiber_manual_record', + 'fiber_new', + 'fiber_pin', + 'fiber_smart_record', + 'forward_10', + 'forward_30', + 'forward_5', + 'games', + 'hd', + 'hearing', + 'high_quality', + 'library_add', + 'library_books', + 'library_music', + 'loop', + 'mic', + 'mic_none', + 'mic_off', + 'missed_video_call', + 'movie', + 'music_video', + 'new_releases', + 'not_interested', + 'note', + 'pause', + 'pause_circle_filled', + 'pause_circle_outline', + 'play_arrow', + 'play_circle_filled', + 'play_circle_filled_white', + 'play_circle_outline', + 'playlist_add', + 'playlist_add_check', + 'playlist_play', + 'queue', + 'queue_music', + 'queue_play_next', + 'radio', + 'recent_actors', + 'remove_from_queue', + 'repeat', + 'repeat_one', + 'replay', + 'replay_10', + 'replay_30', + 'replay_5', + 'shuffle', + 'skip_next', + 'skip_previous', + 'slow_motion_video', + 'snooze', + 'sort_by_alpha', + 'stop', + 'subscriptions', + 'subtitles', + 'surround_sound', + 'video_call', + 'video_label', + 'video_library', + 'videocam', + 'videocam_off', + 'volume_down', + 'volume_mute', + 'volume_off', + 'volume_up', + 'web', + 'web_asset', + ], + communication: [ + 'alternate_email', + 'business', + 'call', + 'call_end', + 'call_made', + 'call_merge', + 'call_missed', + 'call_missed_outgoing', + 'call_received', + 'call_split', + 'cancel_presentation', + 'cell_wifi', + 'chat', + 'chat_bubble', + 'chat_bubble_outline', + 'clear_all', + 'comment', + 'contact_mail', + 'contact_phone', + 'contacts', + 'desktop_access_disabled', + 'dialer_sip', + 'dialpad', + 'domain_disabled', + 'duo', + 'email', + 'forum', + 'import_contacts', + 'import_export', + 'invert_colors_off', + 'list_alt', + 'live_help', + 'location_off', + 'location_on', + 'mail_outline', + 'message', + 'mobile_screen_share', + 'no_sim', + 'pause_presentation', + 'person_add_disabled', + 'phone', + 'phonelink_erase', + 'phonelink_lock', + 'phonelink_ring', + 'phonelink_setup', + 'portable_wifi_off', + 'present_to_all', + 'print_disabled', + 'ring_volume', + 'rss_feed', + 'screen_share', + 'sentiment_satisfied_alt', + 'speaker_phone', + 'stay_current_landscape', + 'stay_current_portrait', + 'stay_primary_landscape', + 'stay_primary_portrait', + 'stop_screen_share', + 'swap_calls', + 'textsms', + 'unsubscribe', + 'voicemail', + 'vpn_key', + ], + content: [ + 'add', + 'add_box', + 'add_circle', + 'add_circle_outline', + 'archive', + 'backspace', + 'ballot', + 'block', + 'clear', + 'create', + 'delete_sweep', + 'drafts', + 'file_copy', + 'filter_list', + 'flag', + 'font_download', + 'forward', + 'gesture', + 'how_to_reg', + 'how_to_vote', + 'inbox', + 'link', + 'link_off', + 'low_priority', + 'mail', + 'markunread', + 'move_to_inbox', + 'next_week', + 'outlined_flag', + 'redo', + 'remove', + 'remove_circle', + 'remove_circle_outline', + 'reply', + 'reply_all', + 'report', + 'report_off', + 'save', + 'save_alt', + 'select_all', + 'send', + 'sort', + 'text_format', + 'unarchive', + 'undo', + 'waves', + 'weekend', + 'where_to_vote', + ], + device: [ + 'access_alarm', + 'access_alarms', + 'access_time', + 'add_alarm', + 'add_to_home_screen', + 'airplanemode_active', + 'airplanemode_inactive', + // 'battery_20', + // 'battery_30', + // 'battery_50', + // 'battery_60', + // 'battery_80', + // 'battery_90', + 'battery_alert', + // 'battery_charging_20', + // 'battery_charging_30', + // 'battery_charging_50', + // 'battery_charging_60', + // 'battery_charging_80', + // 'battery_charging_90', + 'battery_charging_full', + 'battery_full', + 'battery_std', + 'battery_unknown', + 'bluetooth', + 'bluetooth_connected', + 'bluetooth_disabled', + 'bluetooth_searching', + 'brightness_auto', + 'brightness_high', + 'brightness_low', + 'brightness_medium', + 'data_usage', + 'developer_mode', + 'devices', + 'dvr', + 'gps_fixed', + 'gps_not_fixed', + 'gps_off', + 'graphic_eq', + 'location_disabled', + 'location_searching', + 'mobile_friendly', + 'mobile_off', + // 'network_cell', + // 'network_wifi', + 'nfc', + 'screen_lock_landscape', + 'screen_lock_portrait', + 'screen_lock_rotation', + 'screen_rotation', + 'sd_storage', + 'settings_system_daydream', + // 'signal_cellular_0_bar', + // 'signal_cellular_1_bar', + // 'signal_cellular_2_bar', + // 'signal_cellular_3_bar', + // 'signal_cellular_4_bar', + 'signal_cellular_alt', + // 'signal_cellular_connected_no_internet_0_bar', + // 'signal_cellular_connected_no_internet_1_bar', + // 'signal_cellular_connected_no_internet_2_bar', + // 'signal_cellular_connected_no_internet_3_bar', + 'signal_cellular_connected_no_internet_4_bar', + 'signal_cellular_no_sim', + 'signal_cellular_null', + 'signal_cellular_off', + // 'signal_wifi_0_bar', + // 'signal_wifi_1_bar', + // 'signal_wifi_1_bar_lock', + // 'signal_wifi_2_bar', + // 'signal_wifi_2_bar_lock', + // 'signal_wifi_3_bar', + // 'signal_wifi_3_bar_lock', + 'signal_wifi_4_bar', + 'signal_wifi_4_bar_lock', + 'signal_wifi_off', + 'storage', + 'usb', + 'wallpaper', + 'widgets', + 'wifi_lock', + 'wifi_tethering', + ], + editor: [ + 'add_comment', + 'attach_file', + 'attach_money', + 'bar_chart', + 'border_all', + 'border_bottom', + 'border_clear', + 'border_color', + 'border_horizontal', + 'border_inner', + 'border_left', + 'border_outer', + 'border_right', + 'border_style', + 'border_top', + 'border_vertical', + 'bubble_chart', + 'drag_handle', + 'format_align_center', + 'format_align_justify', + 'format_align_left', + 'format_align_right', + 'format_bold', + 'format_clear', + 'format_color_fill', + 'format_color_reset', + 'format_color_text', + 'format_indent_decrease', + 'format_indent_increase', + 'format_italic', + 'format_line_spacing', + 'format_list_bulleted', + 'format_list_numbered', + 'format_list_numbered_rtl', + 'format_paint', + 'format_quote', + 'format_shapes', + 'format_size', + 'format_strikethrough', + 'format_textdirection_l_to_r', + 'format_textdirection_r_to_l', + 'format_underlined', + 'functions', + 'highlight', + 'insert_chart', + 'insert_chart_outlined', + 'insert_comment', + 'insert_drive_file', + 'insert_emoticon', + 'insert_invitation', + 'insert_link', + 'insert_photo', + 'linear_scale', + 'merge_type', + 'mode_comment', + 'monetization_on', + 'money_off', + 'multiline_chart', + 'notes', + 'pie_chart', + 'publish', + 'scatter_plot', + 'score', + 'short_text', + 'show_chart', + 'space_bar', + 'strikethrough_s', + 'table_chart', + 'text_fields', + 'title', + 'vertical_align_bottom', + 'vertical_align_center', + 'vertical_align_top', + 'wrap_text', + ], + file: [ + 'attachment', + 'cloud', + 'cloud_circle', + 'cloud_done', + 'cloud_download', + 'cloud_off', + 'cloud_queue', + 'cloud_upload', + 'create_new_folder', + 'folder', + 'folder_open', + 'folder_shared', + ], + hardware: [ + 'cast', + 'cast_connected', + 'cast_for_education', + 'computer', + 'desktop_mac', + 'desktop_windows', + 'developer_board', + 'device_hub', + 'device_unknown', + 'devices_other', + 'dock', + 'gamepad', + 'headset', + 'headset_mic', + 'keyboard', + 'keyboard_arrow_down', + 'keyboard_arrow_left', + 'keyboard_arrow_right', + 'keyboard_arrow_up', + 'keyboard_backspace', + 'keyboard_capslock', + 'keyboard_hide', + 'keyboard_return', + 'keyboard_tab', + 'keyboard_voice', + 'laptop', + 'laptop_chromebook', + 'laptop_mac', + 'laptop_windows', + 'memory', + 'mouse', + 'phone_android', + 'phone_iphone', + 'phonelink', + 'phonelink_off', + 'power_input', + 'router', + 'scanner', + 'security', + 'sim_card', + 'smartphone', + 'speaker', + 'speaker_group', + 'tablet', + 'tablet_android', + 'tablet_mac', + 'toys', + 'tv', + 'videogame_asset', + 'watch', + ], + image: [ + 'add_a_photo', + 'add_photo_alternate', + 'add_to_photos', + 'adjust', + 'assistant', + 'assistant_photo', + 'audiotrack', + 'blur_circular', + 'blur_linear', + 'blur_off', + 'blur_on', + 'brightness_1', + 'brightness_2', + 'brightness_3', + 'brightness_4', + 'brightness_5', + 'brightness_6', + 'brightness_7', + 'broken_image', + 'brush', + 'burst_mode', + 'camera', + 'camera_alt', + 'camera_front', + 'camera_rear', + 'camera_roll', + 'center_focus_strong', + 'center_focus_weak', + 'collections', + 'collections_bookmark', + 'color_lens', + 'colorize', + 'compare', + 'control_point', + 'control_point_duplicate', + 'crop', + 'crop_16_9', + 'crop_3_2', + 'crop_5_4', + 'crop_7_5', + 'crop_din', + 'crop_free', + 'crop_landscape', + 'crop_original', + 'crop_portrait', + 'crop_rotate', + 'crop_square', + 'dehaze', + 'details', + 'edit', + 'exposure', + 'exposure_neg_1', + 'exposure_neg_2', + 'exposure_plus_1', + 'exposure_plus_2', + 'exposure_zero', + 'filter', + 'filter_1', + 'filter_2', + 'filter_3', + 'filter_4', + 'filter_5', + 'filter_6', + 'filter_7', + 'filter_8', + 'filter_9', + 'filter_9_plus', + 'filter_b_and_w', + 'filter_center_focus', + 'filter_drama', + 'filter_frames', + 'filter_hdr', + 'filter_none', + 'filter_tilt_shift', + 'filter_vintage', + 'flare', + 'flash_auto', + 'flash_off', + 'flash_on', + 'flip', + 'gradient', + 'grain', + 'grid_off', + 'grid_on', + 'hdr_off', + 'hdr_on', + 'hdr_strong', + 'hdr_weak', + 'healing', + 'image', + 'image_aspect_ratio', + 'image_search', + 'iso', + 'landscape', + 'leak_add', + 'leak_remove', + 'lens', + 'linked_camera', + 'looks', + 'looks_3', + 'looks_4', + 'looks_5', + 'looks_6', + 'looks_one', + 'looks_two', + 'loupe', + 'monochrome_photos', + 'movie_creation', + 'movie_filter', + 'music_note', + 'music_off', + 'nature', + 'nature_people', + 'navigate_before', + 'navigate_next', + 'palette', + 'panorama', + 'panorama_fish_eye', + 'panorama_horizontal', + 'panorama_vertical', + 'panorama_wide_angle', + 'photo', + 'photo_album', + 'photo_camera', + 'photo_filter', + 'photo_library', + 'photo_size_select_actual', + 'photo_size_select_large', + 'photo_size_select_small', + 'picture_as_pdf', + 'portrait', + 'remove_red_eye', + 'rotate_90_degrees_ccw', + 'rotate_left', + 'rotate_right', + 'shutter_speed', + 'slideshow', + 'straighten', + 'style', + 'switch_camera', + 'switch_video', + 'tag_faces', + 'texture', + 'timelapse', + 'timer', + 'timer_10', + 'timer_3', + 'timer_off', + 'tonality', + 'transform', + 'tune', + 'view_comfy', + 'view_compact', + 'vignette', + 'wb_auto', + 'wb_cloudy', + 'wb_incandescent', + 'wb_iridescent', + 'wb_sunny', + ], + maps: [ + '360', + 'add_location', + 'atm', + 'beenhere', + 'category', + 'compass_calibration', + 'departure_board', + 'directions', + 'directions_bike', + 'directions_boat', + 'directions_bus', + 'directions_car', + 'directions_railway', + 'directions_run', + 'directions_subway', + 'directions_transit', + 'directions_walk', + 'edit_attributes', + 'edit_location', + 'ev_station', + 'fastfood', + 'flight', + 'hotel', + 'layers', + 'layers_clear', + 'local_activity', + 'local_airport', + 'local_atm', + 'local_bar', + 'local_cafe', + 'local_car_wash', + 'local_convenience_store', + 'local_dining', + 'local_drink', + 'local_florist', + 'local_gas_station', + 'local_grocery_store', + 'local_hospital', + 'local_hotel', + 'local_laundry_service', + 'local_library', + 'local_mall', + 'local_movies', + 'local_offer', + 'local_parking', + 'local_pharmacy', + 'local_phone', + 'local_pizza', + 'local_play', + 'local_post_office', + 'local_printshop', + 'local_see', + 'local_shipping', + 'local_taxi', + 'map', + 'money', + 'my_location', + 'navigation', + 'near_me', + 'not_listed_location', + 'person_pin', + 'person_pin_circle', + 'pin_drop', + 'place', + 'rate_review', + 'restaurant', + 'restaurant_menu', + 'satellite', + 'store_mall_directory', + 'streetview', + 'subway', + 'terrain', + 'traffic', + 'train', + 'tram', + 'transfer_within_a_station', + 'transit_enterexit', + 'trip_origin', + 'zoom_out_map', + ], + navigation: [ + 'apps', + 'arrow_back', + 'arrow_back_ios', + 'arrow_downward', + 'arrow_drop_down', + 'arrow_drop_down_circle', + 'arrow_drop_up', + 'arrow_forward', + 'arrow_forward_ios', + 'arrow_left', + 'arrow_right', + 'arrow_upward', + 'cancel', + 'check', + 'chevron_left', + 'chevron_right', + 'close', + 'expand_less', + 'expand_more', + 'first_page', + 'fullscreen', + 'fullscreen_exit', + 'last_page', + 'menu', + 'more_horiz', + 'more_vert', + 'refresh', + 'subdirectory_arrow_left', + 'subdirectory_arrow_right', + 'unfold_less', + 'unfold_more', + ], + notification: [ + 'adb', + 'airline_seat_flat', + 'airline_seat_flat_angled', + 'airline_seat_individual_suite', + 'airline_seat_legroom_extra', + 'airline_seat_legroom_normal', + 'airline_seat_legroom_reduced', + 'airline_seat_recline_extra', + 'airline_seat_recline_normal', + 'bluetooth_audio', + 'confirmation_number', + 'disc_full', + 'drive_eta', + 'enhanced_encryption', + 'event_available', + 'event_busy', + 'event_note', + 'folder_special', + 'live_tv', + 'mms', + 'more', + 'network_check', + 'network_locked', + 'no_encryption', + 'ondemand_video', + 'personal_video', + 'phone_bluetooth_speaker', + 'phone_callback', + 'phone_forwarded', + 'phone_in_talk', + 'phone_locked', + 'phone_missed', + 'phone_paused', + 'power', + 'power_off', + 'priority_high', + 'sd_card', + 'sms', + 'sms_failed', + 'sync', + 'sync_disabled', + 'sync_problem', + 'system_update', + 'tap_and_play', + 'time_to_leave', + 'tv_off', + 'vibration', + 'voice_chat', + 'vpn_lock', + 'wc', + 'wifi', + 'wifi_off', + ], + places: [ + 'ac_unit', + 'airport_shuttle', + 'all_inclusive', + 'beach_access', + 'business_center', + 'casino', + 'child_care', + 'child_friendly', + 'fitness_center', + 'free_breakfast', + 'golf_course', + 'hot_tub', + 'kitchen', + 'meeting_room', + 'no_meeting_room', + 'pool', + 'room_service', + 'rv_hookup', + 'smoke_free', + 'smoking_rooms', + 'spa', + ], + social: [ + 'cake', + 'domain', + 'group', + 'group_add', + 'location_city', + 'mood', + 'mood_bad', + 'notifications', + 'notifications_active', + 'notifications_none', + 'notifications_off', + 'notifications_paused', + 'pages', + 'party_mode', + 'people', + 'people_outline', + 'person', + 'person_add', + 'person_outline', + 'plus_one', + 'poll', + 'public', + 'school', + 'sentiment_dissatisfied', + 'sentiment_satisfied', + 'sentiment_very_dissatisfied', + 'sentiment_very_satisfied', + 'share', + 'thumb_down_alt', + 'thumb_up_alt', + 'whatshot', + ], + toggle: [ + 'check_box', + 'check_box_outline_blank', + 'indeterminate_check_box', + 'radio_button_checked', + 'radio_button_unchecked', + 'star', + 'star_border', + 'star_half', + 'toggle_off', + 'toggle_on', + ], +}; diff --git a/front/app/src/app/theme/admin-layout/admin-layout.component.html b/front/app/src/app/theme/admin-layout/admin-layout.component.html new file mode 100644 index 00000000..3114f76f --- /dev/null +++ b/front/app/src/app/theme/admin-layout/admin-layout.component.html @@ -0,0 +1,34 @@ + +
+ + + + + + + + + + + + + + + + +
+ +
+
+
+
diff --git a/front/app/src/app/theme/admin-layout/admin-layout.component.scss b/front/app/src/app/theme/admin-layout/admin-layout.component.scss new file mode 100644 index 00000000..dae9889f --- /dev/null +++ b/front/app/src/app/theme/admin-layout/admin-layout.component.scss @@ -0,0 +1,160 @@ +@use '@angular/material' as mat; +@use '../style/variables'; +@use '../style/transitions'; +@use '../style/breakpoints'; + +.matero-container-wrap, +.matero-container { + height: 100%; +} + +.matero-content { + position: relative; + padding: variables.$gutter; +} + +.matero-sidenav { + position: absolute; + overflow-x: hidden; + transition: transitions.swift-ease-out(width); // Only set width property + + @include mat.elevation(2); + + &.mat-drawer-side { + border-width: 0; + + [dir='rtl'] & { + border-width: 0; + } + } +} + +// Layout control +.matero-header-above { + .matero-container { + height: calc(100% - #{variables.$toolbar-height-desktop}) !important; + } + + .matero-sidebar-main { + height: 100% !important; + } +} + +// Layout control +.matero-sidenav-collapsed, +.matero-sidenav-collapsed-fix { + .matero-sidenav { + width: variables.$sidenav-collapsed-width; + + .menu-name, + .menu-label, + .menu-badge, + .menu-caret, + .matero-user-panel-name, + .matero-user-panel-email, + .matero-user-panel-icons { + opacity: 0; + } + + .matero-user-panel-avatar { + transform: scale(.5); + } + + &:hover { + width: variables.$sidenav-width; + + .menu-name, + .menu-label, + .menu-badge, + .menu-caret, + .matero-user-panel-name, + .matero-user-panel-email, + .matero-user-panel-icons { + opacity: 1; + } + + .matero-user-panel-avatar { + transform: scale(1); + } + } + } +} + +// Layout control +.matero-sidenav-collapsed { + .matero-content-wrap { + margin-left: variables.$sidenav-collapsed-width !important; + + [dir='rtl'] & { + margin-right: variables.$sidenav-collapsed-width !important; + margin-left: auto !important; + } + } + + &[dir='rtl'] .matero-content-wrap { + margin-right: variables.$sidenav-collapsed-width !important; + margin-left: auto !important; + } +} + +// Layout control +.matero-navbar-top { + .matero-topmenu { + top: 0; + } + + .matero-branding { + margin-left: 16px; + + [dir='rtl'] & { + margin-right: 16px; + margin-left: auto; + } + } +} + +// Layout control +.matero-header-fixed { + .matero-header { + position: sticky; + top: 0; + } + + .matero-topmenu { + top: variables.$topmenu-sticky-position-desktop; + + @include breakpoints.bp-lt(small) { + & { + top: variables.$topmenu-sticky-position-mobile; + } + } + } + + &.matero-navbar-side { + .matero-toolbar { + border-bottom: unset; + + @include mat.elevation(2); + } + } +} + +// Fix the init content width +.matero-content-width-fix { + .matero-content-wrap { + margin-left: variables.$sidenav-width !important; + + [dir='rtl'] & { + margin-right: variables.$sidenav-width !important; + margin-left: auto !important; + } + } +} + +// Colorful +.matero-header-white { + .matero-toolbar, + .matero-topmenu { + background-color: white; + } +} diff --git a/front/app/src/app/theme/admin-layout/admin-layout.component.ts b/front/app/src/app/theme/admin-layout/admin-layout.component.ts new file mode 100644 index 00000000..8704c5fa --- /dev/null +++ b/front/app/src/app/theme/admin-layout/admin-layout.component.ts @@ -0,0 +1,163 @@ +import { + Component, + OnDestroy, + ViewChild, + HostBinding, + Inject, + Optional, + ViewEncapsulation, +} from '@angular/core'; +import { DOCUMENT } from '@angular/common'; +import { NavigationEnd, Router } from '@angular/router'; +import { Subscription } from 'rxjs'; +import { filter } from 'rxjs/operators'; +import { BreakpointObserver, MediaMatcher } from '@angular/cdk/layout'; +import { Directionality } from '@angular/cdk/bidi'; +import { MatSidenav, MatSidenavContent } from '@angular/material/sidenav'; + +import { SettingsService, AppSettings } from '@core'; +import { AppDirectionality } from '@shared'; + +const MOBILE_MEDIAQUERY = 'screen and (max-width: 599px)'; +const TABLET_MEDIAQUERY = 'screen and (min-width: 600px) and (max-width: 959px)'; +const MONITOR_MEDIAQUERY = 'screen and (min-width: 960px)'; + +@Component({ + selector: 'app-admin-layout', + templateUrl: './admin-layout.component.html', + styleUrls: ['./admin-layout.component.scss'], + encapsulation: ViewEncapsulation.None, +}) +export class AdminLayoutComponent implements OnDestroy { + @ViewChild('sidenav', { static: true }) sidenav!: MatSidenav; + @ViewChild('content', { static: true }) content!: MatSidenavContent; + + options = this.settings.getOptions(); + + private layoutChangesSubscription = Subscription.EMPTY; + + get isOver(): boolean { + return this.isMobileScreen; + } + + private isMobileScreen = false; + + @HostBinding('class.matero-content-width-fix') get contentWidthFix() { + return ( + this.isContentWidthFixed && + this.options.navPos === 'side' && + this.options.sidenavOpened && + !this.isOver + ); + } + + private isContentWidthFixed = true; + + @HostBinding('class.matero-sidenav-collapsed-fix') get collapsedWidthFix() { + return ( + this.isCollapsedWidthFixed && + (this.options.navPos === 'top' || (this.options.sidenavOpened && this.isOver)) + ); + } + + private isCollapsedWidthFixed = false; + + private htmlElement!: HTMLHtmlElement; + + constructor( + private router: Router, + private mediaMatcher: MediaMatcher, + private breakpointObserver: BreakpointObserver, + private settings: SettingsService, + @Optional() @Inject(DOCUMENT) private document: Document, + @Inject(Directionality) public dir: AppDirectionality + ) { + this.dir.value = this.options.dir; + this.document.body.dir = this.dir.value; + + this.htmlElement = this.document.querySelector('html')!; + + this.layoutChangesSubscription = this.breakpointObserver + .observe([MOBILE_MEDIAQUERY, TABLET_MEDIAQUERY, MONITOR_MEDIAQUERY]) + .subscribe(state => { + // SidenavOpened must be reset true when layout changes + this.options.sidenavOpened = true; + + this.isMobileScreen = state.breakpoints[MOBILE_MEDIAQUERY]; + this.options.sidenavCollapsed = state.breakpoints[TABLET_MEDIAQUERY]; + this.isContentWidthFixed = state.breakpoints[MONITOR_MEDIAQUERY]; + }); + + this.router.events.pipe(filter(event => event instanceof NavigationEnd)).subscribe(e => { + if (this.isOver) { + this.sidenav.close(); + } + this.content.scrollTo({ top: 0 }); + }); + + if (this.options.theme === 'auto') { + this.setAutoTheme(); + } + + // Initialize project theme with options + this.receiveOptions(this.options); + } + + ngOnDestroy() { + this.layoutChangesSubscription.unsubscribe(); + } + + toggleCollapsed() { + this.isContentWidthFixed = false; + this.options.sidenavCollapsed = !this.options.sidenavCollapsed; + this.resetCollapsedState(); + } + + // TODO: Trigger when transition end + resetCollapsedState(timer = 400) { + setTimeout(() => this.settings.setOptions(this.options), timer); + } + + onSidenavClosedStart() { + this.isContentWidthFixed = false; + } + + onSidenavOpenedChange(isOpened: boolean) { + this.isCollapsedWidthFixed = !this.isOver; + this.options.sidenavOpened = isOpened; + this.settings.setOptions(this.options); + } + + setAutoTheme() { + // Check whether the browser support `prefers-color-scheme` + if (this.mediaMatcher.matchMedia('(prefers-color-scheme)').media !== 'not all') { + const isSystemDark = this.mediaMatcher.matchMedia('(prefers-color-scheme: dark)').matches; + // Set theme to dark if `prefers-color-scheme` is dark. Otherwise, set it to light. + this.options.theme = isSystemDark ? 'dark' : 'light'; + } else { + // If the browser does not support `prefers-color-scheme`, set the default to light. + this.options.theme = 'light'; + } + } + + // Demo purposes only + + receiveOptions(options: AppSettings): void { + this.options = options; + this.toggleDarkTheme(options); + this.toggleDirection(options); + } + + toggleDarkTheme(options: AppSettings) { + if (options.theme === 'dark') { + this.htmlElement.classList.add('theme-dark'); + } else { + this.htmlElement.classList.remove('theme-dark'); + } + } + + toggleDirection(options: AppSettings) { + this.dir.value = options.dir; + this.document.body.dir = this.dir.value; + } +} diff --git a/front/app/src/app/theme/auth-layout/auth-layout.component.html b/front/app/src/app/theme/auth-layout/auth-layout.component.html new file mode 100644 index 00000000..94d80c77 --- /dev/null +++ b/front/app/src/app/theme/auth-layout/auth-layout.component.html @@ -0,0 +1,3 @@ +
+ +
diff --git a/front/app/src/app/theme/auth-layout/auth-layout.component.scss b/front/app/src/app/theme/auth-layout/auth-layout.component.scss new file mode 100644 index 00000000..8085a917 --- /dev/null +++ b/front/app/src/app/theme/auth-layout/auth-layout.component.scss @@ -0,0 +1,26 @@ +.matero-auth-container { + position: relative; + display: flex; + justify-content: center; + min-height: 100%; + padding: 16px; + background-color: #212121; + background-image: + linear-gradient( + 45deg, + rgba(255, 255, 255, .05) 25%, + transparent 25%, + transparent 75%, + rgba(255, 255, 255, .05) 75%, + rgba(255, 255, 255, .05) + ), + linear-gradient( + -45deg, + rgba(255, 255, 255, .05) 25%, + transparent 25%, + transparent 75%, + rgba(255, 255, 255, .05) 75%, + rgba(255, 255, 255, .05) + ); + background-size: 60px 60px; +} diff --git a/front/app/src/app/theme/auth-layout/auth-layout.component.ts b/front/app/src/app/theme/auth-layout/auth-layout.component.ts new file mode 100644 index 00000000..bec0a8f1 --- /dev/null +++ b/front/app/src/app/theme/auth-layout/auth-layout.component.ts @@ -0,0 +1,9 @@ +import { Component, ViewEncapsulation } from '@angular/core'; + +@Component({ + selector: 'app-auth-layout', + templateUrl: './auth-layout.component.html', + styleUrls: ['./auth-layout.component.scss'], + encapsulation: ViewEncapsulation.None, +}) +export class AuthLayoutComponent {} diff --git a/front/app/src/app/theme/header/_header-theme.scss b/front/app/src/app/theme/header/_header-theme.scss new file mode 100644 index 00000000..41e98c5f --- /dev/null +++ b/front/app/src/app/theme/header/_header-theme.scss @@ -0,0 +1,11 @@ +@use 'sass:map'; +@use '@angular/material' as mat; + +@mixin theme($theme) { + $background: map.get($theme, background); + $foreground: map.get($theme, foreground); + + .matero-toolbar { + border-bottom: 1px solid mat.get-color-from-palette($foreground, divider); + } +} diff --git a/front/app/src/app/theme/header/header.component.html b/front/app/src/app/theme/header/header.component.html new file mode 100644 index 00000000..34c00332 --- /dev/null +++ b/front/app/src/app/theme/header/header.component.html @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + diff --git a/front/app/src/app/theme/header/header.component.scss b/front/app/src/app/theme/header/header.component.scss new file mode 100644 index 00000000..5a5a2e91 --- /dev/null +++ b/front/app/src/app/theme/header/header.component.scss @@ -0,0 +1,8 @@ +.matero-header { + position: relative; + z-index: 200; + + .matero-toolbar { + padding: 0 8px; + } +} diff --git a/front/app/src/app/theme/header/header.component.ts b/front/app/src/app/theme/header/header.component.ts new file mode 100644 index 00000000..04858b31 --- /dev/null +++ b/front/app/src/app/theme/header/header.component.ts @@ -0,0 +1,33 @@ +import { + Component, + Output, + EventEmitter, + Input, + ChangeDetectionStrategy, + ViewEncapsulation, + HostBinding, +} from '@angular/core'; +import screenfull from 'screenfull'; + +@Component({ + selector: 'app-header', + templateUrl: './header.component.html', + styleUrls: ['./header.component.scss'], + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class HeaderComponent { + @HostBinding('class') class = 'matero-header'; + + @Input() showToggle = true; + @Input() showBranding = false; + + @Output() toggleSidenav = new EventEmitter(); + @Output() toggleSidenavNotice = new EventEmitter(); + + toggleFullscreen() { + if (screenfull.isEnabled) { + screenfull.toggle(); + } + } +} diff --git a/front/app/src/app/theme/sidebar-notice/sidebar-notice.component.html b/front/app/src/app/theme/sidebar-notice/sidebar-notice.component.html new file mode 100644 index 00000000..d4ee52cf --- /dev/null +++ b/front/app/src/app/theme/sidebar-notice/sidebar-notice.component.html @@ -0,0 +1,4 @@ + + Content 1 + Content 2 + diff --git a/front/app/src/app/theme/sidebar-notice/sidebar-notice.component.scss b/front/app/src/app/theme/sidebar-notice/sidebar-notice.component.scss new file mode 100644 index 00000000..c15cbe89 --- /dev/null +++ b/front/app/src/app/theme/sidebar-notice/sidebar-notice.component.scss @@ -0,0 +1,5 @@ +:host ::ng-deep { + .mat-mdc-tab { + min-width: 160px; + } +} diff --git a/front/app/src/app/theme/sidebar-notice/sidebar-notice.component.ts b/front/app/src/app/theme/sidebar-notice/sidebar-notice.component.ts new file mode 100644 index 00000000..d91c2c53 --- /dev/null +++ b/front/app/src/app/theme/sidebar-notice/sidebar-notice.component.ts @@ -0,0 +1,8 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-sidebar-notice', + templateUrl: './sidebar-notice.component.html', + styleUrls: ['./sidebar-notice.component.scss'], +}) +export class SidebarNoticeComponent {} diff --git a/front/app/src/app/theme/sidebar/_sidebar-theme.scss b/front/app/src/app/theme/sidebar/_sidebar-theme.scss new file mode 100644 index 00000000..2507134e --- /dev/null +++ b/front/app/src/app/theme/sidebar/_sidebar-theme.scss @@ -0,0 +1,17 @@ +@use 'sass:map'; +@use '@angular/material' as mat; + +@mixin theme($theme) { + $background: map.get($theme, background); + $foreground: map.get($theme, foreground); + + .matero-sidebar-header { + color: mat.get-color-from-palette($foreground, text); + background: mat.get-color-from-palette($background, dialog); + border-bottom: 1px solid mat.get-color-from-palette($foreground, divider); + } + + .matero-user-panel { + border-bottom: 1px solid mat.get-color-from-palette($foreground, divider); + } +} diff --git a/front/app/src/app/theme/sidebar/sidebar.component.html b/front/app/src/app/theme/sidebar/sidebar.component.html new file mode 100644 index 00000000..055275d3 --- /dev/null +++ b/front/app/src/app/theme/sidebar/sidebar.component.html @@ -0,0 +1,13 @@ +
+ + + + +
+ +
+ + +
diff --git a/front/app/src/app/theme/sidebar/sidebar.component.scss b/front/app/src/app/theme/sidebar/sidebar.component.scss new file mode 100644 index 00000000..83de6756 --- /dev/null +++ b/front/app/src/app/theme/sidebar/sidebar.component.scss @@ -0,0 +1,37 @@ +@use '../style/variables'; +@use '../style/breakpoints'; + +.matero-sidebar-header { + position: relative; + top: 0; + left: 0; + z-index: 1; + display: flex; + align-items: center; + justify-content: space-between; + height: variables.$toolbar-height-desktop; + padding: 0 8px; + overflow: hidden; + + @include breakpoints.bp-lt(small) { + & { + height: variables.$toolbar-height-mobile; + } + } + + // Colorful + .matero-header-white & { + background-color: white; + } +} + +.matero-sidebar-main { + height: calc(100% - #{variables.$toolbar-height-desktop}); + overflow: auto; + + @include breakpoints.bp-lt(small) { + & { + height: calc(100% - #{variables.$toolbar-height-mobile}); + } + } +} diff --git a/front/app/src/app/theme/sidebar/sidebar.component.ts b/front/app/src/app/theme/sidebar/sidebar.component.ts new file mode 100644 index 00000000..e681f657 --- /dev/null +++ b/front/app/src/app/theme/sidebar/sidebar.component.ts @@ -0,0 +1,16 @@ +import { Component, Output, EventEmitter, Input, ViewEncapsulation } from '@angular/core'; + +@Component({ + selector: 'app-sidebar', + templateUrl: './sidebar.component.html', + styleUrls: ['./sidebar.component.scss'], + encapsulation: ViewEncapsulation.None, +}) +export class SidebarComponent { + @Input() showToggle = true; + @Input() showUser = true; + @Input() showHeader = true; + @Input() toggleChecked = false; + + @Output() toggleCollapsed = new EventEmitter(); +} diff --git a/front/app/src/app/theme/sidebar/user-panel.component.scss b/front/app/src/app/theme/sidebar/user-panel.component.scss new file mode 100644 index 00000000..5c439358 --- /dev/null +++ b/front/app/src/app/theme/sidebar/user-panel.component.scss @@ -0,0 +1,48 @@ +@use '../style/transitions'; + +.matero-user-panel { + display: flex; + flex-direction: column; + align-items: center; + padding: 12px 0; +} + +// Set default width and height can avoid flashing before avatar image loaded. +.matero-user-panel-avatar { + width: 64px; + height: 64px; + margin-bottom: 8px; + border-radius: 50rem; + transition: transitions.swift-ease-out(transform); +} + +.matero-user-panel-name, +.matero-user-panel-email { + margin-top: 0; + margin-bottom: 4px; + font-weight: normal; +} + +.matero-user-panel-name, +.matero-user-panel-email, +.matero-user-panel-icons { + opacity: 1; + transition: transitions.swift-ease-out(opacity); +} + +.matero-user-panel-icons { + white-space: nowrap; + + .mat-mdc-button-base, + .mat-mdc-button-touch-target { + width: 32px; + height: 32px; + } + + .mat-mdc-button-base { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0; + } +} diff --git a/front/app/src/app/theme/sidebar/user-panel.component.ts b/front/app/src/app/theme/sidebar/user-panel.component.ts new file mode 100644 index 00000000..ab859b53 --- /dev/null +++ b/front/app/src/app/theme/sidebar/user-panel.component.ts @@ -0,0 +1,40 @@ +import { Component, OnInit, ViewEncapsulation } from '@angular/core'; +import { Router } from '@angular/router'; +import { AuthService, User } from '@core/authentication'; + +@Component({ + selector: 'app-user-panel', + template: ` +
+ avatar +

{{ user.name }}

+ +
+ + + +
+
+ `, + styleUrls: ['./user-panel.component.scss'], + encapsulation: ViewEncapsulation.None, +}) +export class UserPanelComponent implements OnInit { + user!: User; + + constructor(private router: Router, private auth: AuthService) {} + + ngOnInit(): void { + this.auth.user().subscribe(user => (this.user = user)); + } + + logout() { + this.auth.logout().subscribe(() => this.router.navigateByUrl('/auth/login')); + } +} diff --git a/front/app/src/app/theme/sidemenu/_sidemenu-theme.scss b/front/app/src/app/theme/sidemenu/_sidemenu-theme.scss new file mode 100644 index 00000000..c184cbba --- /dev/null +++ b/front/app/src/app/theme/sidemenu/_sidemenu-theme.scss @@ -0,0 +1,64 @@ +@use 'sass:color'; +@use 'sass:map'; +@use '@angular/material' as mat; + +@mixin theme($theme) { + $is-dark-theme: map.get($theme, is-dark); + $primary: map.get($theme, primary); + $accent: map.get($theme, accent); + $background: map.get($theme, background); + $foreground: map.get($theme, foreground); + + .matero-sidemenu { + > .menu-item { + > .menu-heading { + color: mat.get-color-from-palette($foreground, text); + + &:hover, + &:focus { + background: mat.get-color-from-palette($background, hover); + } + } + + &.active { + > .menu-heading { + color: + if( + $is-dark-theme, + color.adjust(mat.get-color-from-palette($primary), $lightness: 25%), + mat.get-color-from-palette($accent) + ); + background-color: if($is-dark-theme, rgba(mat.get-color-from-palette($primary), .15), transparent); + + &:hover, + &:focus { + background: mat.get-color-from-palette($background, hover); + } + } + } + + &.expanded { + background: mat.get-color-from-palette($background, hover); + } + } + + &.submenu { + > .menu-item { + &.active { + > .menu-heading { + background-color: transparent; + + &:hover, + &:focus { + background: mat.get-color-from-palette($background, hover); + } + } + } + + &.expanded { + background: transparent; + } + } + } + } +} diff --git a/front/app/src/app/theme/sidemenu/nav-accordion-item.directive.ts b/front/app/src/app/theme/sidemenu/nav-accordion-item.directive.ts new file mode 100644 index 00000000..c0bf016e --- /dev/null +++ b/front/app/src/app/theme/sidemenu/nav-accordion-item.directive.ts @@ -0,0 +1,43 @@ +import { Directive, HostBinding, Inject, Input, OnInit, OnDestroy } from '@angular/core'; +import { NavAccordionDirective } from './nav-accordion.directive'; + +@Directive({ + selector: '[navAccordionItem]', +}) +export class NavAccordionItemDirective implements OnInit, OnDestroy { + protected nav: NavAccordionDirective; + protected isExpanded = false; + + @Input() route = ''; + @Input() type: 'link' | 'sub' | 'extLink' | 'extTabLink' = 'link'; + + @HostBinding('class.expanded') + @Input() + get expanded() { + return this.isExpanded; + } + set expanded(value: boolean) { + // Only sub menu can be expanded + this.isExpanded = this.type === 'sub' && value; + + if (value) { + this.nav.closeOtherLinks(this); + } + } + + constructor(@Inject(NavAccordionDirective) nav: NavAccordionDirective) { + this.nav = nav; + } + + ngOnInit() { + this.nav.addLink(this); + } + + ngOnDestroy() { + this.nav.removeLink(this); + } + + toggle() { + this.expanded = !this.expanded; + } +} diff --git a/front/app/src/app/theme/sidemenu/nav-accordion-toggle.directive.ts b/front/app/src/app/theme/sidemenu/nav-accordion-toggle.directive.ts new file mode 100644 index 00000000..8c2e9156 --- /dev/null +++ b/front/app/src/app/theme/sidemenu/nav-accordion-toggle.directive.ts @@ -0,0 +1,18 @@ +import { Directive, HostListener, Inject } from '@angular/core'; +import { NavAccordionItemDirective } from './nav-accordion-item.directive'; + +@Directive({ + selector: '[navAccordionToggle]', +}) +export class NavAccordionToggleDirective { + protected navLink: NavAccordionItemDirective; + + constructor(@Inject(NavAccordionItemDirective) navLink: NavAccordionItemDirective) { + this.navLink = navLink; + } + + @HostListener('click', ['$event']) + onClick() { + this.navLink.toggle(); + } +} diff --git a/front/app/src/app/theme/sidemenu/nav-accordion.directive.ts b/front/app/src/app/theme/sidemenu/nav-accordion.directive.ts new file mode 100644 index 00000000..a6a2538b --- /dev/null +++ b/front/app/src/app/theme/sidemenu/nav-accordion.directive.ts @@ -0,0 +1,53 @@ +import { Directive } from '@angular/core'; +import { NavigationEnd, Router } from '@angular/router'; +import { MenuService } from '@core'; +import { filter } from 'rxjs/operators'; +import { NavAccordionItemDirective } from './nav-accordion-item.directive'; + +@Directive({ + selector: '[navAccordion]', +}) +export class NavAccordionDirective { + protected navLinks: NavAccordionItemDirective[] = []; + + constructor(private router: Router, private menu: MenuService) { + this.router.events + .pipe(filter(event => event instanceof NavigationEnd)) + .subscribe(() => this.checkOpenLinks()); + + // Fix opening status for async menu data + this.menu.change().subscribe(() => { + setTimeout(() => this.checkOpenLinks()); + }); + } + + addLink(link: NavAccordionItemDirective) { + this.navLinks.push(link); + } + + removeLink(link: NavAccordionItemDirective) { + const index = this.navLinks.indexOf(link); + if (index !== -1) { + this.navLinks.splice(index, 1); + } + } + + closeOtherLinks(openLink: NavAccordionItemDirective) { + this.navLinks.forEach(link => { + if (link !== openLink) { + link.expanded = false; + } + }); + } + + checkOpenLinks() { + this.navLinks.forEach(link => { + if (link.route) { + if (this.router.url.split('/').includes(link.route)) { + link.expanded = true; + this.closeOtherLinks(link); + } + } + }); + } +} diff --git a/front/app/src/app/theme/sidemenu/sidemenu.component.html b/front/app/src/app/theme/sidemenu/sidemenu.component.html new file mode 100644 index 00000000..fdf52aa7 --- /dev/null +++ b/front/app/src/app/theme/sidemenu/sidemenu.component.html @@ -0,0 +1,70 @@ + + + + + + + + + {{item.icon}} + {{item.name | translate}} + + {{item.label.value}} + + + + {{item.badge.value}} + + + {{item.type==='sub' ? 'arrow_drop_down' : 'launch'}} + + diff --git a/front/app/src/app/theme/sidemenu/sidemenu.component.scss b/front/app/src/app/theme/sidemenu/sidemenu.component.scss new file mode 100644 index 00000000..1f1bcc35 --- /dev/null +++ b/front/app/src/app/theme/sidemenu/sidemenu.component.scss @@ -0,0 +1,125 @@ +@use '../style/variables'; +@use '../style/transitions'; +@use '../style/badge'; + +.matero-sidemenu { + width: variables.$sidenav-width; + margin: 0; + padding: 0; + list-style: none; + + .menu-item { + display: block; + height: auto; + padding: 0; + + &.expanded { + > .submenu { + max-height: 2000px; + visibility: visible; + } + + > .menu-toggle > .menu-caret { + transform: rotate(-180deg); + } + } + } + + &.submenu { + max-height: 0; + padding-top: 0; + overflow: hidden; + transform: translateZ(0) !important; + visibility: hidden; + transition: transitions.fast-out-slow(max-height), transitions.fast-out-slow(visibility); + } + + .menu-heading { + display: flex; + flex-direction: row; + align-items: center; + width: 100%; + height: 48px; + padding: 0 16px; + font-size: inherit; + text-decoration: none; + background-color: transparent; + border: none; + outline: none; + cursor: pointer; + } + + .mat-icon.menu-icon { + width: 18px; + height: 18px; + margin-right: 16px; + font-size: 18px; + line-height: 18px; + + [dir='rtl'] & { + margin-right: auto; + margin-left: 16px; + } + } + + .mat-icon.menu-caret { + display: block; + text-align: center; + transition: transitions.fast-out-slow(transform); + } + + .menu-name, + .menu-label, + .menu-badge { + transition: transitions.swift-ease-out(opacity); + } + + .menu-label, + .menu-badge { + @include badge.badge(); + } + + .menu-badge { + border-radius: 50rem; + } + + .menu-spacer { + flex-grow: 1; + } + + &.level-0 > li > .menu-heading > .menu-name { + margin-right: 5px; + + [dir='rtl'] & { + margin-right: auto; + margin-left: 5px; + } + } + + &.level-1 > li > .menu-heading { + padding-left: 50px; + + [dir='rtl'] & { + padding-right: 50px; + padding-left: 16px; + } + } + + &.level-2 > li > .menu-heading { + padding-left: 64px; + + [dir='rtl'] & { + padding-right: 64px; + padding-left: 16px; + } + } + + &.level-2 [class^='level-'] > li > .menu-heading { + padding-left: 80px; + + [dir='rtl'] & { + padding-right: 80px; + padding-left: 16px; + } + } +} diff --git a/front/app/src/app/theme/sidemenu/sidemenu.component.ts b/front/app/src/app/theme/sidemenu/sidemenu.component.ts new file mode 100644 index 00000000..96fe29b6 --- /dev/null +++ b/front/app/src/app/theme/sidemenu/sidemenu.component.ts @@ -0,0 +1,19 @@ +import { Component, Input, ViewEncapsulation } from '@angular/core'; +import { MenuService } from '@core'; + +@Component({ + selector: 'app-sidemenu', + templateUrl: './sidemenu.component.html', + styleUrls: ['./sidemenu.component.scss'], + encapsulation: ViewEncapsulation.None, +}) +export class SidemenuComponent { + // Note: Ripple effect make page flashing on mobile + @Input() ripple = false; + + menu$ = this.menu.getAll(); + + buildRoute = this.menu.buildRoute; + + constructor(private menu: MenuService) {} +} diff --git a/front/app/src/app/theme/style.scss b/front/app/src/app/theme/style.scss new file mode 100644 index 00000000..63b0a6bc --- /dev/null +++ b/front/app/src/app/theme/style.scss @@ -0,0 +1,3 @@ +@use 'style/reboot'; +@use 'style/misc'; +@use 'style/colors'; diff --git a/front/app/src/app/theme/style/_badge.scss b/front/app/src/app/theme/style/_badge.scss new file mode 100644 index 00000000..18f2c600 --- /dev/null +++ b/front/app/src/app/theme/style/_badge.scss @@ -0,0 +1,14 @@ +@mixin badge() { + display: inline-block; + min-width: 18px; + padding: .35em .65em; + color: #fff; + font-weight: 700; + font-size: .75em; + line-height: 1; + white-space: nowrap; + text-align: center; + vertical-align: baseline; + background-color: #757575; + border-radius: 4px; +} diff --git a/front/app/src/app/theme/style/_breakpoints.scss b/front/app/src/app/theme/style/_breakpoints.scss new file mode 100644 index 00000000..f4225d2d --- /dev/null +++ b/front/app/src/app/theme/style/_breakpoints.scss @@ -0,0 +1,36 @@ +@use 'sass:map'; +@use 'variables'; + +@function bp($name, $breakpoints: variables.$breakpoints) { + $min: map.get($breakpoints, $name); + + @return $min; +} + +// Media of at least the minimum breakpoint width. +@mixin bp-gt($name, $breakpoints: variables.$breakpoints) { + $min: bp($name, $breakpoints); + + @if $min { + @media (min-width: $min) { + @content; + } + } + @else { + @content; + } +} + +// Media of at most the maximum breakpoint width. +@mixin bp-lt($name, $breakpoints: variables.$breakpoints) { + $max: bp($name, $breakpoints) - 1px; + + @if $max { + @media (max-width: $max) { + @content; + } + } + @else { + @content; + } +} diff --git a/front/app/src/app/theme/style/_colors.scss b/front/app/src/app/theme/style/_colors.scss new file mode 100644 index 00000000..d69577fa --- /dev/null +++ b/front/app/src/app/theme/style/_colors.scss @@ -0,0 +1,40 @@ +// Material color helpers including text color and background color +@use 'variables'; + +@mixin generate-colors($prefix, $property) { + @each $name, $value in variables.$mat-colors { + // If the value is a map, continue to each + @if type-of($value) == 'map' { + @each $hue, $color in $value { + @if ($hue != 'contrast') { + .#{$prefix + '-' + $name + '-' + $hue} { + #{$property}: $color !important; + } + } + } + } + + @if type-of($value) == 'color' { + .#{$prefix + '-' + $name} { + #{$property}: $value !important; + } + } + } + + // alias + @for $i from 1 through 9 { + .#{$prefix + '-grey-' + $i * 100} { + @extend .#{$prefix + '-gray-' + $i * 100}; + } + + .#{$prefix + '-blue-grey-' + $i * 100} { + @extend .#{$prefix + '-blue-gray-' + $i * 100}; + } + } +} + +// Generate text color helpers +@include generate-colors('text', 'color'); + +// Generate background color helpers +@include generate-colors('bg', 'background-color'); diff --git a/front/app/src/app/theme/style/_misc.scss b/front/app/src/app/theme/style/_misc.scss new file mode 100644 index 00000000..ebba0360 --- /dev/null +++ b/front/app/src/app/theme/style/_misc.scss @@ -0,0 +1,25 @@ +@use 'variables'; +@use 'badge'; +@use 'scrollbar'; + +.badge { + @include badge.badge(); +} + +.scrollbar-thin { + @include scrollbar.thin(); +} + +.scrollbar-none { + @include scrollbar.none(); +} + +@media (max-width: 720px) { + .hide-small { + display: none !important; + } + + .show-small { + display: block !important; + } +} diff --git a/front/app/src/app/theme/style/_reboot-theme.scss b/front/app/src/app/theme/style/_reboot-theme.scss new file mode 100644 index 00000000..982bd910 --- /dev/null +++ b/front/app/src/app/theme/style/_reboot-theme.scss @@ -0,0 +1,27 @@ +@use 'sass:color'; +@use 'sass:map'; +@use '@angular/material' as mat; + +@mixin theme($theme) { + $primary: map.get($theme, primary); + $foreground: map.get($theme, foreground); + $is-dark-theme: map.get($theme, is-dark); + + @if $is-dark-theme { + :root { + color-scheme: dark; + } + } + + a:not(.mat-mdc-button-base) { + color: mat.get-color-from-palette($primary); + + &:hover { + color: color.adjust(mat.get-color-from-palette($primary), $lightness: 10%); + } + } + + code { + background-color: rgba(mat.get-color-from-palette($foreground, secondary-text), .03); + } +} diff --git a/front/app/src/app/theme/style/_reboot.scss b/front/app/src/app/theme/style/_reboot.scss new file mode 100644 index 00000000..5a990356 --- /dev/null +++ b/front/app/src/app/theme/style/_reboot.scss @@ -0,0 +1,48 @@ +@use 'variables'; + +body { + margin: 0; + font-family: variables.$font-family-base; + line-height: 1.5; + -webkit-font-smoothing: antialiased; + text-size-adjust: 100%; + -webkit-tap-highlight-color: transparent; + -webkit-touch-callout: none; +} + +html, +body { + position: relative; // 1 + height: 100%; + overflow: auto; // 2 +} + +*, +::after, +::before { + box-sizing: border-box; +} + +dl, +ol, +ul { + margin-top: 0; + margin-bottom: 1rem; +} + +code, +kbd, +pre, +samp { + font-family: variables.$font-family-monospace; +} + +code { + padding: 3px; + font-size: 90%; + word-break: break-word; +} + +a { + text-decoration: none; +} diff --git a/front/app/src/app/theme/style/_rtl.scss b/front/app/src/app/theme/style/_rtl.scss new file mode 100644 index 00000000..406f7297 --- /dev/null +++ b/front/app/src/app/theme/style/_rtl.scss @@ -0,0 +1,5 @@ +@mixin rtl { + @at-root [dir='rtl'] #{&} { + @content; + } +} diff --git a/front/app/src/app/theme/style/_scrollbar.scss b/front/app/src/app/theme/style/_scrollbar.scss new file mode 100644 index 00000000..a9cc68c7 --- /dev/null +++ b/front/app/src/app/theme/style/_scrollbar.scss @@ -0,0 +1,44 @@ +@mixin thin() { + &::-webkit-scrollbar { + width: 4px; + height: 4px; + } + + &::-webkit-scrollbar-track, + &::-webkit-scrollbar-thumb { + border-color: transparent; + border-style: solid; + border-width: 0; + border-radius: 50rem; + border-image: initial; + } + + &::-webkit-scrollbar-track { + box-shadow: rgba(0, 0, 0, .2) 1px 1px 5px inset; + } + + &::-webkit-scrollbar-thumb { + min-height: 20px; + background-color: rgba(0, 0, 0, .2); + } + + &::-webkit-scrollbar-corner { + background: transparent; + } + + & { + scrollbar-width: thin; + } +} + +@mixin none { + &::-webkit-scrollbar { + width: 0; + height: 0; + } + + & { + -ms-overflow-style: none; + scrollbar-width: none; + } +} diff --git a/front/app/src/app/theme/style/_transitions.scss b/front/app/src/app/theme/style/_transitions.scss new file mode 100644 index 00000000..bec053b5 --- /dev/null +++ b/front/app/src/app/theme/style/_transitions.scss @@ -0,0 +1,15 @@ +@use 'variables'; + +@function swift-ease-out($property) { + // The Material default animation curves. + $transition: $property variables.$swift-ease-out-duration variables.$swift-ease-out-timing-function; + + @return $transition; +} + +@function fast-out-slow($property) { + // The Material default animation curves. + $transition: $property 225ms cubic-bezier(.4, 0, .2, 1); + + @return $transition; +} diff --git a/front/app/src/app/theme/style/_variables.scss b/front/app/src/app/theme/style/_variables.scss new file mode 100644 index 00000000..2c939697 --- /dev/null +++ b/front/app/src/app/theme/style/_variables.scss @@ -0,0 +1,66 @@ +@use '@angular/material' as mat; + +// Layout +$gutter: 16px !default; + +// Sidenav +$sidenav-width: 240px !default; +$sidenav-collapsed-width: 50px !default; +$sidenav-width-mobile: 280px !default; + +// Toolbar +$toolbar-height-desktop: 64px !default; +$toolbar-height-mobile: 56px !default; + +// Topmenu +$topmenu-sticky-position-desktop: $toolbar-height-desktop !default; +$topmenu-sticky-position-mobile: $toolbar-height-mobile !default; + +// Typography +$font-family-sans-serif: 'Roboto', 'Helvetica Neue Light', 'Helvetica Neue', Helvetica, Arial, + 'Lucida Grande', sans-serif !default; +$font-family-monospace: 'Roboto Mono', monospace !default; +$font-family-base: $font-family-sans-serif !default; + +// Breakpoints +// +// Define the minimum dimensions at which your layout will change, +// adapting to different screen sizes, for use in media queries. +$breakpoints: ( + xsmall: 0, + small: 600px, + medium: 960px, + large: 1280px, + xlarge: 1920px +) !default; + +// Material colors +$mat-colors: ( + red: mat.$red-palette, + pink: mat.$pink-palette, + purple: mat.$purple-palette, + deep-purple: mat.$deep-purple-palette, + indigo: mat.$indigo-palette, + blue: mat.$blue-palette, + light-blue: mat.$light-blue-palette, + cyan: mat.$cyan-palette, + teal: mat.$teal-palette, + green: mat.$green-palette, + light-green: mat.$light-green-palette, + lime: mat.$lime-palette, + yellow: mat.$yellow-palette, + amber: mat.$amber-palette, + orange: mat.$orange-palette, + deep-orange: mat.$deep-orange-palette, + brown: mat.$brown-palette, + gray: mat.$gray-palette, + blue-gray: mat.$blue-gray-palette, + white: white, + black: black, + light: white, + dark: rgba(black, .87), +) !default; + +// The material default animation curves +$swift-ease-out-duration: 400ms !default; +$swift-ease-out-timing-function: cubic-bezier(.25, .8, .25, 1) !default; diff --git a/front/app/src/app/theme/theme.module.ts b/front/app/src/app/theme/theme.module.ts new file mode 100644 index 00000000..76c5d2bc --- /dev/null +++ b/front/app/src/app/theme/theme.module.ts @@ -0,0 +1,46 @@ +import { NgModule } from '@angular/core'; +import { SharedModule } from '@shared/shared.module'; + +import { AdminLayoutComponent } from './admin-layout/admin-layout.component'; +import { AuthLayoutComponent } from './auth-layout/auth-layout.component'; + +import { SidebarComponent } from './sidebar/sidebar.component'; +import { UserPanelComponent } from './sidebar/user-panel.component'; +import { SidemenuComponent } from './sidemenu/sidemenu.component'; +import { NavAccordionDirective } from './sidemenu/nav-accordion.directive'; +import { NavAccordionItemDirective } from './sidemenu/nav-accordion-item.directive'; +import { NavAccordionToggleDirective } from './sidemenu/nav-accordion-toggle.directive'; +import { SidebarNoticeComponent } from './sidebar-notice/sidebar-notice.component'; + +import { TopmenuComponent } from './topmenu/topmenu.component'; +import { TopmenuPanelComponent } from './topmenu/topmenu-panel.component'; + +import { HeaderComponent } from './header/header.component'; + +import { BrandingComponent } from './widgets/branding.component'; +import { NotificationComponent } from './widgets/notification.component'; +import { TranslateComponent } from './widgets/translate.component'; +import { UserComponent } from './widgets/user.component'; + +@NgModule({ + declarations: [ + AdminLayoutComponent, + AuthLayoutComponent, + SidebarComponent, + UserPanelComponent, + SidemenuComponent, + NavAccordionDirective, + NavAccordionItemDirective, + NavAccordionToggleDirective, + SidebarNoticeComponent, + TopmenuComponent, + TopmenuPanelComponent, + HeaderComponent, + BrandingComponent, + NotificationComponent, + TranslateComponent, + UserComponent, + ], + imports: [SharedModule], +}) +export class ThemeModule {} diff --git a/front/app/src/app/theme/topmenu/_topmenu-theme.scss b/front/app/src/app/theme/topmenu/_topmenu-theme.scss new file mode 100644 index 00000000..f93cf2a1 --- /dev/null +++ b/front/app/src/app/theme/topmenu/_topmenu-theme.scss @@ -0,0 +1,32 @@ +@use 'sass:map'; +@use '@angular/material' as mat; + +@mixin theme($theme) { + $is-dark-theme: map.get($theme, is-dark); + $primary: map.get($theme, primary); + $accent: map.get($theme, accent); + $background: mat.get-color-from-palette($theme, background); + $foreground: mat.get-color-from-palette($theme, foreground); + + .matero-topmenu { + background: mat.get-color-from-palette($background, app-bar); + + .mat-mdc-button { + color: mat.get-color-from-palette($foreground, text); + + &.active { + background-color: mat.get-color-from-palette($background, focused-button); + } + } + } + + .matero-topmenu-panel { + .mat-mdc-menu-item { + color: mat.get-color-from-palette($foreground, text); + + &.active > .mdc-list-item__primary-text { + color: mat.get-color-from-palette($accent); + } + } + } +} diff --git a/front/app/src/app/theme/topmenu/topmenu-panel.component.html b/front/app/src/app/theme/topmenu/topmenu-panel.component.html new file mode 100644 index 00000000..af7de4b2 --- /dev/null +++ b/front/app/src/app/theme/topmenu/topmenu-panel.component.html @@ -0,0 +1,38 @@ + + + + + + {{item.name | translate}} + + + + {{item.name | translate}} + launch + + + + {{item.name | translate}} + launch + + + + + + + diff --git a/front/app/src/app/theme/topmenu/topmenu-panel.component.ts b/front/app/src/app/theme/topmenu/topmenu-panel.component.ts new file mode 100644 index 00000000..023b6ea9 --- /dev/null +++ b/front/app/src/app/theme/topmenu/topmenu-panel.component.ts @@ -0,0 +1,82 @@ +import { + Component, + ViewChild, + Input, + Output, + EventEmitter, + OnInit, + OnDestroy, +} from '@angular/core'; +import { MatMenu } from '@angular/material/menu'; +import { NavigationEnd, Router, RouterLinkActive } from '@angular/router'; +import { MenuChildrenItem, MenuService } from '@core'; +import { Subscription } from 'rxjs'; +import { filter } from 'rxjs/operators'; +import { TopmenuState } from './topmenu.component'; + +@Component({ + selector: 'app-topmenu-panel', + templateUrl: './topmenu-panel.component.html', +}) +export class TopmenuPanelComponent implements OnInit, OnDestroy { + @ViewChild(MatMenu, { static: true }) menuPanel!: MatMenu; + + @Input() items: MenuChildrenItem[] = []; + @Input() parentRoute: string[] = []; + @Input() level = 1; + @Output() routeChange = new EventEmitter(); + + menuStates: TopmenuState[] = []; + + buildRoute = this.menu.buildRoute; + + private routerSubscription = Subscription.EMPTY; + + constructor(private menu: MenuService, private router: Router) {} + + ngOnInit() { + this.items.forEach(item => { + this.menuStates.push({ active: this.checkRoute(item), route: item.route }); + }); + } + + ngOnDestroy() { + this.routerSubscription.unsubscribe(); + } + + checkRoute(item: MenuChildrenItem) { + if (!item.route) { + return this.checkChildRoute(item.children); + } else { + return this.router.url.split('/').includes(item.route); + } + } + + checkChildRoute(menuItems: MenuChildrenItem[] = []) { + return menuItems.some(child => { + if (this.router.url.split('/').includes(child.route)) { + return true; + } + if (!child.route && child.children) { + this.checkChildRoute(child.children); + } + return false; + }); + } + + onRouterLinkClick(rla: RouterLinkActive) { + this.routeChange.emit(rla); + } + + onRouteChange(rla: RouterLinkActive, index: number) { + this.routeChange.emit(rla); + + this.routerSubscription.unsubscribe(); + this.routerSubscription = this.router.events + .pipe(filter(event => event instanceof NavigationEnd)) + .subscribe(e => { + this.menuStates.forEach(item => (item.active = false)); + setTimeout(() => (this.menuStates[index].active = rla.isActive)); + }); + } +} diff --git a/front/app/src/app/theme/topmenu/topmenu.component.html b/front/app/src/app/theme/topmenu/topmenu.component.html new file mode 100644 index 00000000..50bc01af --- /dev/null +++ b/front/app/src/app/theme/topmenu/topmenu.component.html @@ -0,0 +1,53 @@ + + + + + {{item.icon}} + {{item.name | translate}} + + {{item.label.value}} + + + {{item.badge.value}} + + + {{item.type==='sub' ? 'arrow_drop_down' : 'launch'}} + + diff --git a/front/app/src/app/theme/topmenu/topmenu.component.scss b/front/app/src/app/theme/topmenu/topmenu.component.scss new file mode 100644 index 00000000..a227cb1a --- /dev/null +++ b/front/app/src/app/theme/topmenu/topmenu.component.scss @@ -0,0 +1,77 @@ +@use '@angular/material' as mat; + +.matero-topmenu { + position: sticky; + z-index: 200; + display: block; + padding: 8px; + + @include mat.elevation(2); + + .mat-mdc-button-base { + padding: 0 16px; + white-space: nowrap; + } + + .menu-icon, + .menu-caret, + .menu-name { + vertical-align: middle; + } + + .mat-icon.menu-icon { + width: 18px; + height: 18px; + margin-right: 8px; + font-size: 18px; + line-height: 18px; + + [dir='rtl'] & { + margin-right: auto; + margin-left: 8px; + } + } + + .mat-icon.menu-caret { + margin-right: -8px; + + [dir='rtl'] & { + margin-right: auto; + margin-left: -8px; + } + } + + .menu-label, + .menu-badge { + margin-left: 8px; + font-size: 12px; + + [dir='rtl'] & { + margin-right: 8px; + margin-left: auto; + } + } + + .menu-badge { + border-radius: 50rem; + } + + .mat-tab-nav-bar, + .mat-tab-header { + border-bottom: none; + } +} + +.matero-topmenu-panel { + .mat-menu-item { + .menu-name { + margin-right: 8px; + vertical-align: middle; + + [dir='rtl'] & { + margin-right: auto; + margin-left: 8px; + } + } + } +} diff --git a/front/app/src/app/theme/topmenu/topmenu.component.ts b/front/app/src/app/theme/topmenu/topmenu.component.ts new file mode 100644 index 00000000..ce08e6c4 --- /dev/null +++ b/front/app/src/app/theme/topmenu/topmenu.component.ts @@ -0,0 +1,57 @@ +import { Component, HostBinding, OnDestroy, ViewEncapsulation } from '@angular/core'; +import { NavigationEnd, Router, RouterLinkActive } from '@angular/router'; +import { Menu, MenuService } from '@core'; +import { Subscription } from 'rxjs'; +import { filter } from 'rxjs/operators'; + +export interface TopmenuState { + active: boolean; + route: string; +} + +@Component({ + selector: 'app-topmenu', + templateUrl: './topmenu.component.html', + styleUrls: ['./topmenu.component.scss'], + encapsulation: ViewEncapsulation.None, +}) +export class TopmenuComponent implements OnDestroy { + @HostBinding('class') class = 'matero-topmenu'; + + menu$ = this.menu.getAll(); + + buildRoute = this.menu.buildRoute; + + menuList: Menu[] = []; + menuStates: TopmenuState[] = []; + + private menuSubscription = Subscription.EMPTY; + private routerSubscription = Subscription.EMPTY; + + constructor(private menu: MenuService, private router: Router) { + this.menuSubscription = this.menu$.subscribe(res => { + this.menuList = res; + this.menuList.forEach(item => { + this.menuStates.push({ + active: this.router.url.split('/').includes(item.route), + route: item.route, + }); + }); + }); + } + + ngOnDestroy() { + this.menuSubscription.unsubscribe(); + this.routerSubscription.unsubscribe(); + } + + onRouteChange(rla: RouterLinkActive, index: number) { + this.routerSubscription.unsubscribe(); + this.routerSubscription = this.router.events + .pipe(filter(event => event instanceof NavigationEnd)) + .subscribe(e => { + this.menuStates.forEach(item => (item.active = false)); + setTimeout(() => (this.menuStates[index].active = rla.isActive)); + }); + } +} diff --git a/front/app/src/app/theme/widgets/branding.component.ts b/front/app/src/app/theme/widgets/branding.component.ts new file mode 100644 index 00000000..b1d1ad62 --- /dev/null +++ b/front/app/src/app/theme/widgets/branding.component.ts @@ -0,0 +1,20 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-branding', + template: ` + + + MATERO + + `, + styles: [ + ` + .brand-logo { + width: 30px; + height: 30px; + } + `, + ], +}) +export class BrandingComponent {} diff --git a/front/app/src/app/theme/widgets/github.component.ts b/front/app/src/app/theme/widgets/github.component.ts new file mode 100644 index 00000000..f4fa3536 --- /dev/null +++ b/front/app/src/app/theme/widgets/github.component.ts @@ -0,0 +1,26 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-github-button', + template: ` + + + + + + `, +}) +export class GithubButtonComponent {} diff --git a/front/app/src/app/theme/widgets/notification.component.ts b/front/app/src/app/theme/widgets/notification.component.ts new file mode 100644 index 00000000..45648f49 --- /dev/null +++ b/front/app/src/app/theme/widgets/notification.component.ts @@ -0,0 +1,22 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-notification', + template: ` + + + + + + info + {{ message }} + + + + `, +}) +export class NotificationComponent { + messages = ['Server Error Reports', 'Server Error Reports', 'Server Error Reports']; +} diff --git a/front/app/src/app/theme/widgets/translate.component.ts b/front/app/src/app/theme/widgets/translate.component.ts new file mode 100644 index 00000000..da7ac3a6 --- /dev/null +++ b/front/app/src/app/theme/widgets/translate.component.ts @@ -0,0 +1,34 @@ +import { Component } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import { SettingsService } from '@core'; + +@Component({ + selector: 'app-translate', + template: ` + + + + + + `, +}) +export class TranslateComponent { + langs = { + 'en-US': 'English', + 'zh-CN': '中文简体', + 'zh-TW': '中文繁体', + }; + + constructor(private translate: TranslateService, private settings: SettingsService) { + translate.addLangs(['en-US', 'zh-CN', 'zh-TW']); + } + + useLanguage(language: string) { + this.translate.use(language); + this.settings.setLanguage(language); + } +} diff --git a/front/app/src/app/theme/widgets/user.component.ts b/front/app/src/app/theme/widgets/user.component.ts new file mode 100644 index 00000000..60dca4d5 --- /dev/null +++ b/front/app/src/app/theme/widgets/user.component.ts @@ -0,0 +1,56 @@ +import { ChangeDetectorRef, Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; +import { debounceTime, tap } from 'rxjs/operators'; +import { AuthService, User } from '@core/authentication'; + +@Component({ + selector: 'app-user', + template: ` + + + + + + + + `, + styles: [ + ` + .avatar { + width: 24px; + height: 24px; + } + `, + ], +}) +export class UserComponent implements OnInit { + user!: User; + + constructor(private router: Router, private auth: AuthService, private cdr: ChangeDetectorRef) {} + + ngOnInit(): void { + this.auth + .user() + .pipe( + tap(user => (this.user = user)), + debounceTime(10) + ) + .subscribe(() => this.cdr.detectChanges()); + } + + logout() { + this.auth.logout().subscribe(() => this.router.navigateByUrl('/auth/login')); + } +} diff --git a/front/app/src/assets/data/dashboard.json b/front/app/src/assets/data/dashboard.json new file mode 100644 index 00000000..93b5aca9 --- /dev/null +++ b/front/app/src/assets/data/dashboard.json @@ -0,0 +1,28 @@ +{ + "dashboard": [ + { + "type": "Total Sales", + "amount": 180200, + "progress": 50, + "date": 1427207139000 + }, + { + "type": "Revenue", + "amount": 70205, + "progress": 70, + "date": 1427412725000 + }, + { + "type": "Traffic", + "amount": 1291922, + "progress": 80, + "date": 1427546580000 + }, + { + "type": "New User", + "amount": 1922, + "progress": 40, + "date": 1427891640000 + } + ] +} diff --git a/front/app/src/assets/data/menu.json b/front/app/src/assets/data/menu.json new file mode 100644 index 00000000..7e1bcf3c --- /dev/null +++ b/front/app/src/assets/data/menu.json @@ -0,0 +1,37 @@ +{ + "menu": [ + { + "route": "dashboard", + "name": "dashboard", + "type": "link", + "icon": "dashboard", + "badge": { + "color": "red-500", + "value": "5" + } + }, + { + "route": "/", + "name": "sessions", + "type": "sub", + "icon": "question_answer", + "children": [ + { + "route": "403", + "name": "403", + "type": "link" + }, + { + "route": "404", + "name": "404", + "type": "link" + }, + { + "route": "500", + "name": "500", + "type": "link" + } + ] + } + ] +} diff --git a/front/app/src/assets/fonts/Material_Icons.css b/front/app/src/assets/fonts/Material_Icons.css new file mode 100644 index 00000000..95f3add8 --- /dev/null +++ b/front/app/src/assets/fonts/Material_Icons.css @@ -0,0 +1,23 @@ +@font-face { + font-family: 'Material Icons'; + font-style: normal; + font-weight: 400; + font-display: block; + src: url(./flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.woff2) format('woff2'); +} + +.material-icons { + font-family: 'Material Icons'; + font-weight: normal; + font-style: normal; + font-size: 24px; + line-height: 1; + letter-spacing: normal; + text-transform: none; + display: inline-block; + white-space: nowrap; + word-wrap: normal; + direction: ltr; + font-feature-settings: 'liga'; + -webkit-font-smoothing: antialiased; +} diff --git a/front/app/src/assets/fonts/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.woff2 b/front/app/src/assets/fonts/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.woff2 new file mode 100644 index 00000000..34cdd2af Binary files /dev/null and b/front/app/src/assets/fonts/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.woff2 differ diff --git a/front/app/src/assets/i18n/en-US.json b/front/app/src/assets/i18n/en-US.json new file mode 100644 index 00000000..46887c96 --- /dev/null +++ b/front/app/src/assets/i18n/en-US.json @@ -0,0 +1,145 @@ +{ + "menu": { + "dashboard": "Dashboard", + "design": "Design", + "design.colors": "Color System", + "design.icons": "Material Icons", + "material": "Material", + "material.form-controls": "Form Controls", + "material.form-controls.autocomplete": "Autocomplete", + "material.form-controls.checkbox": "Checkbox", + "material.form-controls.datepicker": "Datepicker", + "material.form-controls.form-field": "Form Field", + "material.form-controls.input": "Input", + "material.form-controls.radio": "Radio", + "material.form-controls.select": "Select", + "material.form-controls.slider": "Slider", + "material.form-controls.slide-toggle": "Slide Toggle", + "material.navigation": "Navigation", + "material.navigation.menu": "Menu", + "material.navigation.sidenav": "Sidenav", + "material.navigation.toolbar": "Toolbar", + "material.layout": "Layout", + "material.layout.card": "Card", + "material.layout.divider": "Divider", + "material.layout.expansion": "Expansion Panel", + "material.layout.grid-list": "Grid List", + "material.layout.list": "List", + "material.layout.stepper": "Stepper", + "material.layout.tab": "Tab", + "material.layout.tree": "Tree", + "material.buttons-indicators": "Buttons & Indicators", + "material.buttons-indicators.button": "Buttons", + "material.buttons-indicators.button-toggle": "Button Toggle", + "material.buttons-indicators.badge": "Badge", + "material.buttons-indicators.chips": "Chips", + "material.buttons-indicators.icon": "Icon", + "material.buttons-indicators.progress-spinner": "Progress Spinner", + "material.buttons-indicators.progress-bar": "Progress Bar", + "material.buttons-indicators.ripple": "Ripple", + "material.popups-modals": "Popups & Modals", + "material.popups-modals.bottom-sheet": "Bottom Sheet", + "material.popups-modals.dialog": "Dialog", + "material.popups-modals.snackbar": "Snackbar", + "material.popups-modals.tooltip": "Tooltip", + "material.data-table": "Data Table", + "material.data-table.paginator": "Paginator", + "material.data-table.sort": "Sort", + "material.data-table.table": "Table", + "media": "Media", + "media.gallery": "Gallery", + "forms": "Forms", + "forms.form-elements": "Form Elements", + "forms.dynamic-form": "Dynamic Form", + "forms.select": "Select", + "forms.datetime": "Date Time", + "tables": "Tables", + "tables.kitchen-sink": "Kitchen Sink", + "tables.remote-data": "Remote Data", + "profile": "Profile", + "profile.overview": "Overview", + "profile.settings": "Settings", + "extensions": "Extensions", + "sessions": "Sessions", + "sessions.403": "403", + "sessions.404": "404", + "sessions.500": "500", + "utilities": "Utilities", + "utilities.css-grid": "CSS Grid", + "utilities.css-helpers": "CSS Helpers", + "menu-level": "Menu Level", + "menu-level.level-1-1": "Level 1.1", + "menu-level.level-1-2": "Level 1.2", + "menu-level.level-1-1.level-2-1": "Level 2.1", + "menu-level.level-1-1.level-2-2": "Level 2.2", + "menu-level.level-1-1.level-2-1.level-3-1": "Level 3.1", + "menu-level.level-1-1.level-2-1.level-3-1.level-4-1": "Level 4.1", + "permissions": "Permissions", + "permissions.role-switching": "Role Switching", + "permissions.route-guard": "Route Guard", + "permissions.test": "Permission Test" + }, + "validations": { + "required": "This field is required", + "minlength": "Should have atleast {{number}} characters", + "maxlength": "This value should be less than {{number}} characters", + "min": "This value should be more than {{number}}", + "max": "This value should be less than {{number}}", + "exist": "The {{value}} has exists", + "inconsistent": "Inconsistent with {{value}}", + "invalid_email": "Invalid email" + }, + "user": { + "profile": "profile", + "settings": "settings", + "logout": "logout" + }, + "login": { + "title": "Welcome Back", + "username": "Username", + "password": "Password", + "remember_me": "Remember Me", + "login": "Login", + "have_no_account": "Don't have an account", + "create_one": "Click here to create one", + "please_enter": "Please enter" + }, + "register": { + "welcome": "Welcome", + "title": "It only takes a few seconds to create your account", + "confirm_password": "Confirm Password", + "agree": "I have read and agree to the terms of service", + "have_an_account": "Already have an account", + "register": "Create account" + }, + "paginator": { + "items_per_page_label": "Items per page:", + "next_page_label": "Next page", + "previous_page_label": "Previous page", + "first_page_label": "First page", + "last_page_label": "Last page", + "range_page_label_1": "no record", + "range_page_label_2": "{{startIndex}} - {{endIndex}} of {{length}}" + }, + "table_kitchen_sink": { + "position": "Position", + "name": "Name", + "weight": "Weight", + "symbol": "Symbol", + "gender": "Gender", + "mobile": "Mobile", + "tele": "Telephone", + "birthday": "Birthday", + "city": "City", + "address": "Address", + "website": "Website", + "company": "Company", + "email": "Email", + "operation": "Operation", + "edit": "Edit", + "delete": "Delete", + "confirm_delete": "Confirm delete?", + "ok": "Ok", + "close": "Close" + } +} diff --git a/front/app/src/assets/i18n/zh-CN.json b/front/app/src/assets/i18n/zh-CN.json new file mode 100644 index 00000000..4dc1d60a --- /dev/null +++ b/front/app/src/assets/i18n/zh-CN.json @@ -0,0 +1,145 @@ +{ + "menu": { + "dashboard": "数据大盘", + "design": "设计", + "design.colors": "颜色系统", + "design.icons": "Material 图标库", + "material": "Material", + "material.form-controls": "表单控件", + "material.form-controls.autocomplete": "自动完成", + "material.form-controls.checkbox": "检查框", + "material.form-controls.datepicker": "日期选择器", + "material.form-controls.form-field": "表单字段", + "material.form-controls.input": "输入框", + "material.form-controls.radio": "单选按钮", + "material.form-controls.select": "选择框", + "material.form-controls.slider": "滑竿", + "material.form-controls.slide-toggle": "滑块开关", + "material.navigation": "导航", + "material.navigation.menu": "菜单", + "material.navigation.sidenav": "侧边栏", + "material.navigation.toolbar": "工具栏", + "material.layout": "布局", + "material.layout.card": "卡片", + "material.layout.divider": "分割器", + "material.layout.expansion": "可展开面板", + "material.layout.grid-list": "网格列表", + "material.layout.list": "列表", + "material.layout.stepper": "步进器", + "material.layout.tab": "选项卡", + "material.layout.tree": "树", + "material.buttons-indicators": "按钮与指示器", + "material.buttons-indicators.button": "按钮", + "material.buttons-indicators.button-toggle": "开关按钮", + "material.buttons-indicators.badge": "徽章", + "material.buttons-indicators.chips": "芯片", + "material.buttons-indicators.icon": "图标", + "material.buttons-indicators.progress-spinner": "进度圈", + "material.buttons-indicators.progress-bar": "进度条", + "material.buttons-indicators.ripple": "水波", + "material.popups-modals": "弹框与模态框", + "material.popups-modals.bottom-sheet": "底部操作表", + "material.popups-modals.dialog": "对话框", + "material.popups-modals.snackbar": "快餐栏", + "material.popups-modals.tooltip": "提示框", + "material.data-table": "数据表", + "material.data-table.paginator": "分页器", + "material.data-table.sort": "排序头", + "material.data-table.table": "表格", + "media": "媒体", + "media.gallery": "图片画廊", + "forms": "表单", + "forms.form-elements": "表单元素", + "forms.dynamic-form": "动态表单", + "forms.select": "选择框", + "forms.datetime": "日期时间", + "tables": "表格", + "tables.kitchen-sink": "基础演示", + "tables.remote-data": "远程数据", + "profile": "个人信息", + "profile.overview": "概述", + "profile.settings": "设置", + "extensions": "扩展组件库", + "sessions": "会话", + "sessions.403": "403", + "sessions.404": "404", + "sessions.500": "500", + "utilities": "辅助工具", + "utilities.css-grid": "CSS 栅格", + "utilities.css-helpers": "CSS 辅助类", + "menu-level": "菜单层级", + "menu-level.level-1-1": "层级 1.1", + "menu-level.level-1-2": "层级 1.2", + "menu-level.level-1-1.level-2-1": "层级 2.1", + "menu-level.level-1-1.level-2-2": "层级 2.2", + "menu-level.level-1-1.level-2-1.level-3-1": "层级 3.1", + "menu-level.level-1-1.level-2-1.level-3-1.level-4-1": "层级 4.1", + "permissions": "权限管理", + "permissions.role-switching": "切换角色", + "permissions.route-guard": "路由守卫", + "permissions.test": "权限测试" + }, + "validations": { + "required": "该字段必填", + "minlength": "必须大于 {{number}} 个字符", + "maxlength": "必须小于 {{number}} 个字符", + "min": "值必须大于 {{number}}", + "max": "值必须小于 {{number}}", + "exist": "{{value}} 已存在", + "inconsistent": "与 {{value}} 不一致", + "invalid_email": "邮箱格式不正确" + }, + "user": { + "profile": "个人信息", + "settings": "偏好设置", + "logout": "退出" + }, + "login": { + "title": "欢迎回来", + "username": "用户名", + "password": "密码", + "remember_me": "记住我", + "login": "登入", + "have_no_account": "没有账号", + "create_one": "点此创建一个", + "please_enter": "请输入" + }, + "register": { + "welcome": "欢迎", + "title": "创建您的帐户只需几秒钟", + "confirm_password": "确认密码", + "agree": "我已阅读并同意服务条款", + "have_an_account": "已经有帐号了", + "register": "创建账号" + }, + "paginator": { + "items_per_page_label": "每页共:", + "next_page_label": "下一页", + "previous_page_label": "前一页", + "first_page_label": "首页", + "last_page_label": "尾页", + "range_page_label_1": "无数据", + "range_page_label_2": "第 {{startIndex}} - {{endIndex}} 行,共 {{length}} 行" + }, + "table_kitchen_sink": { + "position": "序号", + "name": "姓名", + "weight": "体重", + "symbol": "代号", + "gender": "性别", + "mobile": "手机号", + "tele": "固话", + "birthday": "出生日期", + "city": "城市", + "address": "家庭地址", + "company": "公司", + "website": "网址", + "email": "邮箱", + "operation": "操作", + "edit": "编辑", + "delete": "删除", + "confirm_delete": "确认删除?", + "ok": "确定", + "close": "关闭" + } +} diff --git a/front/app/src/assets/i18n/zh-TW.json b/front/app/src/assets/i18n/zh-TW.json new file mode 100644 index 00000000..fcc2eb54 --- /dev/null +++ b/front/app/src/assets/i18n/zh-TW.json @@ -0,0 +1,145 @@ +{ + "menu": { + "dashboard": "數據大盤", + "design": "設計", + "design.colors": "颜色系统", + "design.icons": "Material 圖標庫", + "material": "Material", + "material.form-controls": "表單控件", + "material.form-controls.autocomplete": "自動完成", + "material.form-controls.checkbox": "檢查框", + "material.form-controls.datepicker": "日期選擇器", + "material.form-controls.form-field": "表單字段", + "material.form-controls.input": "輸入框", + "material.form-controls.radio": "單選按鈕", + "material.form-controls.select": "選擇框", + "material.form-controls.slider": "滑竿", + "material.form-controls.slide-toggle": "滑塊開關", + "material.navigation": "導航", + "material.navigation.menu": "菜單", + "material.navigation.sidenav": "側邊欄", + "material.navigation.toolbar": "工具欄", + "material.layout": "布局", + "material.layout.card": "卡片", + "material.layout.divider": "分割器", + "material.layout.expansion": "可展開面板", + "material.layout.grid-list": "網格列表", + "material.layout.list": "列表", + "material.layout.stepper": "步進器", + "material.layout.tab": "選項卡", + "material.layout.tree": "樹", + "material.buttons-indicators": "按鈕與指示器", + "material.buttons-indicators.button": "按鈕", + "material.buttons-indicators.button-toggle": "開關按鈕", + "material.buttons-indicators.badge": "徽章", + "material.buttons-indicators.chips": "芯片", + "material.buttons-indicators.icon": "圖標", + "material.buttons-indicators.progress-spinner": "進度圈", + "material.buttons-indicators.progress-bar": "進度條", + "material.buttons-indicators.ripple": "水波", + "material.popups-modals": "彈框與模態框", + "material.popups-modals.bottom-sheet": "底部操作表", + "material.popups-modals.dialog": "對話框", + "material.popups-modals.snackbar": "快餐欄", + "material.popups-modals.tooltip": "提示框", + "material.data-table": "數據表", + "material.data-table.paginator": "分頁器", + "material.data-table.sort": "排序頭", + "material.data-table.table": "表格", + "media": "媒體", + "media.gallery": "圖片畫廊", + "forms": "表單", + "forms.form-elements": "表單元素", + "forms.dynamic-form": "動態表單", + "forms.select": "選擇框", + "forms.datetime": "日期時間", + "tables": "表格", + "tables.kitchen-sink": "基礎演示", + "tables.remote-data": "遠程數據", + "profile": "個人信息", + "profile.overview": "概述", + "profile.settings": "設置", + "extensions": "擴展組件庫", + "sessions": "會話", + "sessions.403": "403", + "sessions.404": "404", + "sessions.500": "500", + "utilities": "輔助工具", + "utilities.css-grid": "CSS 柵格", + "utilities.css-helpers": "CSS 輔助類", + "menu-level": "菜單層級", + "menu-level.level-1-1": "層級 1.1", + "menu-level.level-1-2": "層級 1.2", + "menu-level.level-1-1.level-2-1": "層級 2.1", + "menu-level.level-1-1.level-2-2": "層級 2.2", + "menu-level.level-1-1.level-2-1.level-3-1": "層級 3.1", + "menu-level.level-1-1.level-2-1.level-3-1.level-4-1": "層級 4.1", + "permissions": "權限管理", + "permissions.role-switching": "切換角色", + "permissions.route-guard": "路由守衛", + "permissions.test": "權限測試" + }, + "validations": { + "required": "該字段必填", + "minlength": "必須大于 {{number}} 個字符", + "maxlength": "必須小于 {{number}} 個字符", + "min": "值必须大于 {{number}}", + "max": "值必須小于 {{number}}", + "exist": "{{value}} 已存在", + "inconsistent": "與 {{value}} 不壹致", + "invalid_email": "郵箱格式不正確" + }, + "user": { + "profile": "個人信息", + "settings": "偏好設置", + "logout": "退出" + }, + "login": { + "title": "歡迎回來", + "username": "用戶名", + "password": "密碼", + "remember_me": "記住我", + "login": "登入", + "have_no_account": "沒有賬號", + "create_one": "點此創建壹個", + "please_enter": "請輸入" + }, + "register": { + "welcome": "歡迎", + "title": "創建您的帳戶只需幾秒鐘", + "confirm_password": "確認密碼", + "agree": "我已閱讀並同意服務條款", + "have_an_account": "已經有帳號了", + "register": "創建賬號" + }, + "paginator": { + "items_per_page_label": "每頁共:", + "next_page_label": "下壹頁", + "previous_page_label": "前壹頁", + "first_page_label": "首頁", + "last_page_label": "尾頁", + "range_page_label_1": "無數據", + "range_page_label_2": "第 {{startIndex}} - {{endIndex}} 行,共 {{length}} 行" + }, + "table_kitchen_sink": { + "position": "序號", + "name": "姓名", + "weight": "體重", + "symbol": "代號", + "gender": "性别", + "mobile": "手機號", + "tele": "固話", + "birthday": "出生日期", + "city": "城市", + "address": "家庭地址", + "company": "公司", + "website": "網址", + "email": "郵箱", + "operation": "操作", + "edit": "編輯", + "delete": "删除", + "confirm_delete": "確認刪除?", + "ok": "確定", + "close": "關閉" + } +} diff --git a/front/app/src/assets/images/avatar-default.jpg b/front/app/src/assets/images/avatar-default.jpg new file mode 100644 index 00000000..0ead43e1 Binary files /dev/null and b/front/app/src/assets/images/avatar-default.jpg differ diff --git a/front/app/src/assets/images/avatar.jpg b/front/app/src/assets/images/avatar.jpg new file mode 100644 index 00000000..36c56811 Binary files /dev/null and b/front/app/src/assets/images/avatar.jpg differ diff --git a/front/app/src/assets/images/matero.png b/front/app/src/assets/images/matero.png new file mode 100644 index 00000000..3c3f799d Binary files /dev/null and b/front/app/src/assets/images/matero.png differ diff --git a/front/app/src/environments/environment.prod.ts b/front/app/src/environments/environment.prod.ts new file mode 100644 index 00000000..b1e84509 --- /dev/null +++ b/front/app/src/environments/environment.prod.ts @@ -0,0 +1,5 @@ +export const environment = { + production: true, + baseUrl: '', + useHash: false, +}; diff --git a/front/app/src/environments/environment.ts b/front/app/src/environments/environment.ts new file mode 100644 index 00000000..ab0f6159 --- /dev/null +++ b/front/app/src/environments/environment.ts @@ -0,0 +1,18 @@ +// This file can be replaced during build by using the `fileReplacements` array. +// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. +// The list of file replacements can be found in `angular.json`. + +export const environment = { + production: false, + baseUrl: '', + useHash: false, +}; + +/* + * For easier debugging in development mode, you can import the following file + * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. + * + * This import should be commented out in production mode because it will have a negative impact + * on performance if an error is thrown. + */ +// import 'zone.js/plugins/zone-error'; // Included with Angular CLI. diff --git a/front/app/src/index.html b/front/app/src/index.html index 48121633..01327d0d 100644 --- a/front/app/src/index.html +++ b/front/app/src/index.html @@ -7,10 +7,73 @@ - + + - + +

LOADING

diff --git a/front/app/src/styles.css b/front/app/src/styles.css index 7e7239a2..90d4ee00 100644 --- a/front/app/src/styles.css +++ b/front/app/src/styles.css @@ -1,4 +1 @@ /* You can add global styles to this file, and also import other style files */ - -html, body { height: 100%; } -body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; } diff --git a/front/app/src/styles.scss b/front/app/src/styles.scss new file mode 100644 index 00000000..f3d3af70 --- /dev/null +++ b/front/app/src/styles.scss @@ -0,0 +1,7 @@ +/* You can add global styles to this file, and also import other style files */ +@use 'app/theme/style'; +@use 'styles/themes'; +@use 'styles/custom'; +@use 'styles/grid'; +@use 'styles/helpers'; +@use 'styles/plugins'; diff --git a/front/app/src/styles/_app-theme.scss b/front/app/src/styles/_app-theme.scss new file mode 100644 index 00000000..99b73bad --- /dev/null +++ b/front/app/src/styles/_app-theme.scss @@ -0,0 +1,28 @@ +@use '@angular/material' as mat; +@use '@ng-matero/extensions' as mtx; + +@use '../app/theme/style/reboot-theme'; +@use '../app/theme/header/header-theme'; +@use '../app/theme/sidebar/sidebar-theme'; +@use '../app/theme/sidemenu/sidemenu-theme'; +@use '../app/theme/topmenu/topmenu-theme'; +@use '../app/shared/components/error-code/error-code-theme'; + +@use './custom/table-theme'; + +@include mat.core(); + +// Styles for the app that are based on the current theme. +@mixin theme($theme) { + @include mat.all-component-themes($theme); + @include mtx.all-component-themes($theme); + + @include reboot-theme.theme($theme); + @include header-theme.theme($theme); + @include sidebar-theme.theme($theme); + @include sidemenu-theme.theme($theme); + @include topmenu-theme.theme($theme); + @include error-code-theme.theme($theme); + + @include table-theme.theme($theme); +} diff --git a/front/app/src/styles/_custom.scss b/front/app/src/styles/_custom.scss new file mode 100644 index 00000000..a79b8075 --- /dev/null +++ b/front/app/src/styles/_custom.scss @@ -0,0 +1,2 @@ +@use 'custom/material'; +@use 'custom/table'; diff --git a/front/app/src/styles/_grid.scss b/front/app/src/styles/_grid.scss new file mode 100644 index 00000000..867a3ea8 --- /dev/null +++ b/front/app/src/styles/_grid.scss @@ -0,0 +1 @@ +@use 'grid/grid'; diff --git a/front/app/src/styles/_helpers.scss b/front/app/src/styles/_helpers.scss new file mode 100644 index 00000000..5fbeb09f --- /dev/null +++ b/front/app/src/styles/_helpers.scss @@ -0,0 +1 @@ +@use 'helpers/api'; diff --git a/front/app/src/styles/_plugins.scss b/front/app/src/styles/_plugins.scss new file mode 100644 index 00000000..58e3224b --- /dev/null +++ b/front/app/src/styles/_plugins.scss @@ -0,0 +1,2 @@ +@use 'plugins/ngx-formly'; +@use 'plugins/ngx-toastr'; diff --git a/front/app/src/styles/_themes.scss b/front/app/src/styles/_themes.scss new file mode 100644 index 00000000..2197dc91 --- /dev/null +++ b/front/app/src/styles/_themes.scss @@ -0,0 +1,46 @@ +@use '@angular/material' as mat; +@use 'app-theme'; + +// Define the palettes for your theme using the Material Design palettes available in palette.scss +// (imported above). For each palette, you can optionally specify a default, lighter, and darker +// hue. Available color palettes: https://material.io/design/color/ + +// +// Light theme +// +$primary: mat.define-palette(mat.$indigo-palette); +$accent: mat.define-palette(mat.$pink-palette, A200, A100, A400); +$theme: mat.define-light-theme( + ( + color: ( + primary: $primary, + accent: $accent + ), + typography: mat.define-typography-config(), + density: 0 + ) +); + +@include app-theme.theme($theme); + +// +// Dark theme +// +.theme-dark { + color-scheme: dark; + + $primary: mat.define-palette(mat.$blue-palette); + $accent: mat.define-palette(mat.$amber-palette, A200, A100, A400); + $theme: mat.define-dark-theme( + ( + color: ( + primary: $primary, + accent: $accent + ), + typography: null, + density: null + ) + ); + + @include app-theme.theme($theme); +} diff --git a/front/app/src/styles/custom/_material.scss b/front/app/src/styles/custom/_material.scss new file mode 100644 index 00000000..dd3f6e00 --- /dev/null +++ b/front/app/src/styles/custom/_material.scss @@ -0,0 +1,18 @@ +/** Override Material styles */ + +@use '../../app/theme/style/variables'; + +.mat-mdc-card { + margin-bottom: variables.$gutter; +} + +.mat-mdc-accordion { + display: block; + margin-bottom: variables.$gutter; +} + +.form-field-full { + .mat-mdc-form-field { + width: 100%; + } +} diff --git a/front/app/src/styles/custom/_mixins.scss b/front/app/src/styles/custom/_mixins.scss new file mode 100644 index 00000000..691683c4 --- /dev/null +++ b/front/app/src/styles/custom/_mixins.scss @@ -0,0 +1,5 @@ +@mixin dark { + @at-root .theme-dark #{&} { + @content; + } +} diff --git a/front/app/src/styles/custom/_table-theme.scss b/front/app/src/styles/custom/_table-theme.scss new file mode 100644 index 00000000..149211a1 --- /dev/null +++ b/front/app/src/styles/custom/_table-theme.scss @@ -0,0 +1,40 @@ +@use 'sass:color'; +@use 'sass:map'; +@use '@angular/material' as mat; + +// Example for custom theming +@mixin theme($theme) { + $is-dark-theme: map.get($theme, is-dark); + $background: map.get($theme, 'background'); + $foreground: map.get($theme, 'foreground'); + + $row-hover-bg: mat.get-color-from-palette(mat.$indigo-palette, 50); + $row-odd-bg: mat.get-color-from-palette(mat.$gray-palette, 100); + + $row-hover-bg-dark: mat.get-color-from-palette(mat.$blue-gray-palette, 900); + $row-odd-bg-dark: color.adjust(mat.get-color-from-palette(mat.$gray-palette, 900), $lightness: 10%); + + mtx-grid.mtx-grid { + .mat-table { + &.mat-table-striped { + .mat-row-odd { + background-color: if($is-dark-theme, $row-odd-bg-dark, $row-odd-bg); + } + } + + &.mat-table-hover { + .mat-row { + &:hover { + background-color: if($is-dark-theme, $row-hover-bg-dark, $row-hover-bg); + } + } + } + + .mat-row { + &.selected { + background-color: if($is-dark-theme, $row-hover-bg-dark, $row-hover-bg); + } + } + } + } +} diff --git a/front/app/src/styles/custom/_table.scss b/front/app/src/styles/custom/_table.scss new file mode 100644 index 00000000..a9e7c66f --- /dev/null +++ b/front/app/src/styles/custom/_table.scss @@ -0,0 +1,7 @@ +/** Custom table style not with theme */ + +mtx-grid.mtx-grid { + .mat-header-cell { + white-space: nowrap; + } +} diff --git a/front/app/src/styles/grid/_grid.scss b/front/app/src/styles/grid/_grid.scss new file mode 100644 index 00000000..db4bc5c4 --- /dev/null +++ b/front/app/src/styles/grid/_grid.scss @@ -0,0 +1,40 @@ +@use 'variables'; +@use 'mixins'; + +.row { + display: flex; + flex-wrap: wrap; + margin-right: variables.$grid-gutter * -.5; + margin-left: variables.$grid-gutter * -.5; +} + +.no-gutters { + margin-right: 0; + margin-left: 0; + + > .col, + > [class*='col-'] { + padding-right: 0; + padding-left: 0; + } +} + +@include mixins.make-grid-columns(); + +// xsmall: 0, +// small: 600px +// medium: 960px +// large: 1280px +// xlarge: 1920px + +@each $breakpoint, $infix in variables.$breakpoint-infixs { + @if ($breakpoint== 'xsmall') { + @include mixins.loop-grid-columns(variables.$grid-columns, $infix); + } + + @else { + @include mixins.bp-gt($breakpoint) { + @include mixins.loop-grid-columns(variables.$grid-columns, $infix); + } + } +} diff --git a/front/app/src/styles/grid/_mixins.scss b/front/app/src/styles/grid/_mixins.scss new file mode 100644 index 00000000..dde3026f --- /dev/null +++ b/front/app/src/styles/grid/_mixins.scss @@ -0,0 +1,79 @@ +@use 'sass:map'; +@use 'sass:math'; +@use 'variables'; + +@function bp($name, $breakpoints: variables.$breakpoints) { + $min: map.get($breakpoints, $name); + + @return $min; +} + +// Media of at least the minimum breakpoint width. +@mixin bp-gt($name, $breakpoints: variables.$breakpoints) { + $min: bp($name, $breakpoints); + + @if $min { + @media (min-width: $min) { + @content; + } + } + @else { + @content; + } +} + +// Media of at most the maximum breakpoint width. +@mixin bp-lt($name, $breakpoints: variables.$breakpoints) { + $max: bp($name, $breakpoints) - 1px; + + @if $max { + @media (max-width: $max) { + @content; + } + } + @else { + @content; + } +} + +@mixin make-grid-columns($i: 1, $list: '.col') { + @each $breakpoint, $infix in variables.$breakpoint-infixs { + $infix: if($infix == '', '', '-#{$infix}'); + + @if ($infix != '') { + $list: '#{$list}, .col#{$infix}'; + } + + @for $i from 1 through variables.$grid-columns { + $list: '#{$list}, .col#{$infix}-#{$i}'; + } + } + + #{$list} { + position: relative; + width: 100%; + padding-right: variables.$grid-gutter * .5; + padding-left: variables.$grid-gutter * .5; + } +} + +@mixin loop-grid-columns($columns: $grid-columns, $breakpoint-infix: '') { + $infix: if($breakpoint-infix == '', '', '-#{$breakpoint-infix}'); + + .col#{$infix} { + flex-basis: 0; + flex-grow: 1; + max-width: 100%; + } + + @for $i from 1 through $columns { + .col#{$infix}-#{$i} { + flex: 0 0 math.percentage(math.div($i, $columns)); + max-width: math.percentage(math.div($i, $columns)); + } + + .offset#{$infix}-#{$i} { + margin-left: math.percentage(math.div($i, $columns)); + } + } +} diff --git a/front/app/src/styles/grid/_variables.scss b/front/app/src/styles/grid/_variables.scss new file mode 100644 index 00000000..3f353eed --- /dev/null +++ b/front/app/src/styles/grid/_variables.scss @@ -0,0 +1,18 @@ +$grid-columns: 12 !default; +$grid-gutter: 16px !default; + +$breakpoints: ( + xsmall: 0, + small: 600px, + medium: 960px, + large: 1280px, + xlarge: 1920px +) !default; + +$breakpoint-infixs: ( + xsmall: '', + small: 'sm', + medium: 'md', + large: 'lg', + xlarge: 'xl' +) !default; diff --git a/front/app/src/styles/helpers/_alignment.scss b/front/app/src/styles/helpers/_alignment.scss new file mode 100644 index 00000000..701fd25f --- /dev/null +++ b/front/app/src/styles/helpers/_alignment.scss @@ -0,0 +1,13 @@ +@use 'sass:map'; +@use 'variables' as *; + +$utilities: map.merge( + ( + 'vertical-align': ( + property: vertical-align, + class: align, + values: top middle bottom + ) + ), + $utilities +); diff --git a/front/app/src/styles/helpers/_api.scss b/front/app/src/styles/helpers/_api.scss new file mode 100644 index 00000000..edff6cf5 --- /dev/null +++ b/front/app/src/styles/helpers/_api.scss @@ -0,0 +1,47 @@ +@use 'sass:map'; +@use 'sass:meta'; +@use 'sass:list'; + +@use 'variables' as *; + +@use 'display'; +@use 'flexbox'; +@use 'position'; +@use 'sizing'; +@use 'spacing'; +@use 'border'; +@use 'rounded'; +@use 'text'; +@use 'alignment'; +@use 'overflow'; +@use 'cursor'; +@use 'image'; +@use 'icon'; + +// Generate Helpers +@each $key, $utility in $utilities { + $values: map.get($utility, values); + + // If the values are a list or string, convert it into a map + @if meta.type-of($values) == 'string' or meta.type-of(list.nth($values, 1)) != 'list' { + $values: list.zip($values, $values); + } + + $properties: map.get($utility, property); + $property-class-prefix: map.get($utility, class); + + // Utility class maybe empty, (e.g. with position class) + $property-class-prefix-hyphen: if( + $property-class-prefix == '', + $property-class-prefix, + $property-class-prefix + '-' + ); + + @each $class-modifier, $value in $values { + .#{$property-class-prefix-hyphen + $class-modifier} { + @each $property in $properties { + #{$property}: $value !important; + } + } + } +} diff --git a/front/app/src/styles/helpers/_border.scss b/front/app/src/styles/helpers/_border.scss new file mode 100644 index 00000000..798bd76b --- /dev/null +++ b/front/app/src/styles/helpers/_border.scss @@ -0,0 +1,33 @@ +@use 'sass:map'; +@use 'variables' as *; + +$utilities: map.merge( + ( + 'border': ( + property: border, + class: b, + values: $borders + ), + 'border-top': ( + property: border-top, + class: b-t, + values: $borders + ), + 'border-bottom': ( + property: border-bottom, + class: b-b, + values: $borders + ), + 'border-right': ( + property: border-right, + class: b-r, + values: $borders + ), + 'border-left': ( + property: border-left, + class: b-l, + values: $borders + ) + ), + $utilities +); diff --git a/front/app/src/styles/helpers/_cursor.scss b/front/app/src/styles/helpers/_cursor.scss new file mode 100644 index 00000000..30fde211 --- /dev/null +++ b/front/app/src/styles/helpers/_cursor.scss @@ -0,0 +1,13 @@ +@use 'sass:map'; +@use 'variables' as *; + +$utilities: map.merge( + ( + 'cursor': ( + property: cursor, + class: cursor, + values: default pointer text move copy not-allowed + ) + ), + $utilities +); diff --git a/front/app/src/styles/helpers/_display.scss b/front/app/src/styles/helpers/_display.scss new file mode 100644 index 00000000..116c24c9 --- /dev/null +++ b/front/app/src/styles/helpers/_display.scss @@ -0,0 +1,13 @@ +@use 'sass:map'; +@use 'variables' as *; + +$utilities: map.merge( + ( + 'display': ( + property: display, + class: d, + values: block inline-block inline flex inline-flex none + ) + ), + $utilities +); diff --git a/front/app/src/styles/helpers/_flexbox.scss b/front/app/src/styles/helpers/_flexbox.scss new file mode 100644 index 00000000..7e043d86 --- /dev/null +++ b/front/app/src/styles/helpers/_flexbox.scss @@ -0,0 +1,90 @@ +@use 'sass:map'; +@use 'variables' as *; + +$utilities: map.merge( + ( + 'flex': ( + property: flex, + class: flex, + values: (fill: 1 1 auto) + ), + 'flex-direction': ( + property: flex-direction, + class: flex, + values: ( + row: row, + row-reverse: row-reverse, + col: column, + col-reverse: column-reverse + ) + ), + 'flex-grow': ( + property: flex-grow, + class: flex, + values: ( + grow-0: 0, + grow-1: 1, + ) + ), + 'flex-shrink': ( + property: flex-shrink, + class: flex, + values: ( + shrink-0: 0, + shrink-1: 1, + ) + ), + 'flex-wrap': ( + property: flex-wrap, + class: flex, + values: wrap nowrap wrap-reverse + ), + 'justify-content': ( + property: justify-content, + class: justify-content, + values: ( + start: flex-start, + end: flex-end, + center: center, + between: space-between, + around: space-around, + ) + ), + 'align-content': ( + property: align-content, + class: align-content, + values: ( + start: flex-start, + end: flex-end, + center: center, + between: space-between, + around: space-around, + stretch: stretch, + ) + ), + 'align-items': ( + property: align-items, + class: align-items, + values: ( + start: flex-start, + end: flex-end, + center: center, + baseline: baseline, + stretch: stretch, + ) + ), + 'align-self': ( + property: align-self, + class: align-self, + values: ( + auto: auto, + start: flex-start, + end: flex-end, + center: center, + baseline: baseline, + stretch: stretch, + ) + ), + ), + $utilities +); diff --git a/front/app/src/styles/helpers/_functions.scss b/front/app/src/styles/helpers/_functions.scss new file mode 100644 index 00000000..c97b3ed9 --- /dev/null +++ b/front/app/src/styles/helpers/_functions.scss @@ -0,0 +1,14 @@ +@use 'sass:map'; + +// It makes the value negative. +@function negativify-map($map) { + $result: (); + + @each $key, $value in $map { + @if $key !=0 { + $result: map.merge($result, ('-' + $key: (-$value))); + } + } + + @return $result; +} diff --git a/front/app/src/styles/helpers/_icon.scss b/front/app/src/styles/helpers/_icon.scss new file mode 100644 index 00000000..64f094dd --- /dev/null +++ b/front/app/src/styles/helpers/_icon.scss @@ -0,0 +1,18 @@ +@use 'variables' as *; +@use 'mixins' as *; + +.icon-18 { + @include icon(18); +} + +.icon-24 { + @include icon(24); +} + +.icon-36 { + @include icon(36); +} + +.icon-48 { + @include icon(48); +} diff --git a/front/app/src/styles/helpers/_image.scss b/front/app/src/styles/helpers/_image.scss new file mode 100644 index 00000000..3b8f816e --- /dev/null +++ b/front/app/src/styles/helpers/_image.scss @@ -0,0 +1,13 @@ +@use 'sass:map'; +@use 'variables' as *; + +$utilities: map.merge( + ( + 'object-fit': ( + property: object-fit, + class: object, + values: contain cover fill none scale-down + ) + ), + $utilities +); diff --git a/front/app/src/styles/helpers/_mixins.scss b/front/app/src/styles/helpers/_mixins.scss new file mode 100644 index 00000000..5630b9c4 --- /dev/null +++ b/front/app/src/styles/helpers/_mixins.scss @@ -0,0 +1,6 @@ +@mixin icon($size) { + width: $size * 1px !important; + height: $size * 1px !important; + font-size: $size * 1px !important; + line-height: $size * 1px !important; +} diff --git a/front/app/src/styles/helpers/_overflow.scss b/front/app/src/styles/helpers/_overflow.scss new file mode 100644 index 00000000..f3dcefae --- /dev/null +++ b/front/app/src/styles/helpers/_overflow.scss @@ -0,0 +1,23 @@ +@use 'sass:map'; +@use 'variables' as *; + +$utilities: map.merge( + ( + 'overflow': ( + property: overflow, + class: overflow, + values: auto hidden visible scroll + ), + 'overflow-x': ( + property: overflow-x, + class: overflow-x, + values: auto hidden visible scroll + ), + 'overflow-y': ( + property: overflow-y, + class: overflow-y, + values: auto hidden visible scroll + ) + ), + $utilities +); diff --git a/front/app/src/styles/helpers/_position.scss b/front/app/src/styles/helpers/_position.scss new file mode 100644 index 00000000..6c535cee --- /dev/null +++ b/front/app/src/styles/helpers/_position.scss @@ -0,0 +1,13 @@ +@use 'sass:map'; +@use 'variables' as *; + +$utilities: map.merge( + ( + 'position': ( + property: position, + class: '', + values: static fixed absolute relative sticky + ) + ), + $utilities +); diff --git a/front/app/src/styles/helpers/_rounded.scss b/front/app/src/styles/helpers/_rounded.scss new file mode 100644 index 00000000..7d20992c --- /dev/null +++ b/front/app/src/styles/helpers/_rounded.scss @@ -0,0 +1,33 @@ +@use 'sass:map'; +@use 'variables' as *; + +$utilities: map.merge( + ( + 'border-radius': ( + property: border-radius, + class: r, + values: $radius + ), + 'border-top-left-radius': ( + property: border-top-left-radius, + class: r-t-l, + values: $radius + ), + 'border-top-right-radius': ( + property: border-top-right-radius, + class: r-t-r, + values: $radius + ), + 'border-bottom-right-radius': ( + property: border-bottom-right-radius, + class: r-b-r, + values: $radius + ), + 'border-bottom-left-radius': ( + property: border-bottom-left-radius, + class: r-b-l, + values: $radius + ) + ), + $utilities +); diff --git a/front/app/src/styles/helpers/_sizing.scss b/front/app/src/styles/helpers/_sizing.scss new file mode 100644 index 00000000..fd59d66b --- /dev/null +++ b/front/app/src/styles/helpers/_sizing.scss @@ -0,0 +1,18 @@ +@use 'sass:map'; +@use 'variables' as *; + +$utilities: map.merge( + ( + 'width': ( + property: width, + class: w, + values: $sizes + ), + 'height': ( + property: height, + class: h, + values: $sizes + ) + ), + $utilities +); diff --git a/front/app/src/styles/helpers/_spacing.scss b/front/app/src/styles/helpers/_spacing.scss new file mode 100644 index 00000000..9c853387 --- /dev/null +++ b/front/app/src/styles/helpers/_spacing.scss @@ -0,0 +1,113 @@ +@use 'sass:map'; +@use 'variables' as *; + +$utilities: map.merge( + ( + 'margin': ( + property: margin, + class: m, + values: map.merge($spacers, (auto: auto)) + ), + 'margin-x': ( + property: margin-left margin-right, + class: m-x, + values: map.merge($spacers, (auto: auto)) + ), + 'margin-y': ( + property: margin-top margin-bottom, + class: m-y, + values: map.merge($spacers, (auto: auto)) + ), + 'margin-top': ( + property: margin-top, + class: m-t, + values: map.merge($spacers, (auto: auto)) + ), + 'margin-right': ( + property: margin-right, + class: m-r, + values: map.merge($spacers, (auto: auto)) + ), + 'margin-bottom': ( + property: margin-bottom, + class: m-b, + values: map.merge($spacers, (auto: auto)) + ), + 'margin-left': ( + property: margin-left, + class: m-l, + values: map.merge($spacers, (auto: auto)) + ), + 'negative-margin': ( + property: margin, + class: m, + values: $negative-spacers + ), + 'negative-margin-x': ( + property: margin-left margin-right, + class: m-x, + values: $negative-spacers + ), + 'negative-margin-y': ( + property: margin-top margin-bottom, + class: m-y, + values: $negative-spacers + ), + 'negative-margin-top': ( + property: margin-top, + class: m-t, + values: $negative-spacers + ), + 'negative-margin-right': ( + property: margin-right, + class: m-r, + values: $negative-spacers + ), + 'negative-margin-bottom': ( + property: margin-bottom, + class: m-b, + values: $negative-spacers + ), + 'negative-margin-left': ( + property: margin-left, + class: m-l, + values: $negative-spacers + ), + 'padding': ( + property: padding, + class: p, + values: $spacers + ), + 'padding-x': ( + property: padding-left padding-right, + class: p-x, + values: $spacers + ), + 'padding-y': ( + property: padding-top padding-bottom, + class: p-y, + values: $spacers + ), + 'padding-top': ( + property: padding-top, + class: p-t, + values: $spacers + ), + 'padding-right': ( + property: padding-right, + class: p-r, + values: $spacers + ), + 'padding-bottom': ( + property: padding-bottom, + class: p-b, + values: $spacers + ), + 'padding-left': ( + property: padding-left, + class: p-l, + values: $spacers + ) + ), + $utilities +); diff --git a/front/app/src/styles/helpers/_text.scss b/front/app/src/styles/helpers/_text.scss new file mode 100644 index 00000000..fbe56a26 --- /dev/null +++ b/front/app/src/styles/helpers/_text.scss @@ -0,0 +1,49 @@ +@use 'sass:map'; +@use 'variables' as *; + +$utilities: map.merge( + ( + 'font-size': ( + property: font-size, + class: f-s, + values: $font-sizes + ), + 'font-weight': ( + property: font-weight, + class: f-w, + values: $font-wieghts + ), + 'text-transform': ( + property: text-transform, + class: text, + values: capitalize uppercase lowercase + ), + 'text-align': ( + property: text-align, + class: text, + values: center right left + ), + 'white-space': ( + property: white-space, + class: text, + values: ( + wrap: normal, + nowrap: nowrap + ) + ), + 'text-overflow': ( + property: text-overflow, + class: text, + values: ellipsis + ), + 'color': ( + property: color, + class: text, + values: ( + reset: inherit, + current: currentColor + ) + ) + ), + $utilities +); diff --git a/front/app/src/styles/helpers/_variables.scss b/front/app/src/styles/helpers/_variables.scss new file mode 100644 index 00000000..de67e7c1 --- /dev/null +++ b/front/app/src/styles/helpers/_variables.scss @@ -0,0 +1,83 @@ +@use 'functions' as *; + +// Utility Map + +$utilities: () !default; + +// Spacing + +$spacer: 16px !default; +$spacers: ( + 0: 0, + 2: $spacer * .125, + 4: $spacer * .25, + 8: $spacer * .5, + 12: $spacer * .75, + 16: $spacer, + 24: $spacer * 1.5, + 32: $spacer * 2, + 48: $spacer * 3 +) !default; +$negative-spacers: negativify-map($spacers) !default; + +// Border + +$border-color: rgba(0, 0, 0, .12) !default; +$borders: ( + 0: 0, + 1: 1px solid $border-color, + 2: 2px solid $border-color, + 4: 4px solid $border-color, + 8: 8px solid $border-color +) !default; + +// Border radius + +$radius-base: 4px !default; +$radius: ( + 0: 0, + 4: $radius-base, + 8: $radius-base * 2, + 12: $radius-base * 3, + 16: $radius-base * 4, + full: 9999px +) !default; + +// Text + +$font-wieghts: ( + 100: 100, + 200: 200, + 300: 300, + 400: 400, + 500: 500, + 600: 600, + 700: 700, + 800: 800, + 900: 900 +) !default; + +$font-sizes: ( + 0: 0, + 10: 10px, + 12: 12px, + 14: 14px, + 16: 16px, + 18: 18px, + 20: 20px +) !default; + +// Sizing + +$sizes: ( + 0: 0, + 20: 20%, + 25: 25%, + 40: 40%, + 50: 50%, + 60: 60%, + 75: 75%, + 80: 80%, + full: 100%, + auto: auto +) !default; diff --git a/front/app/src/styles/plugins/_ngx-formly.scss b/front/app/src/styles/plugins/_ngx-formly.scss new file mode 100644 index 00000000..75754301 --- /dev/null +++ b/front/app/src/styles/plugins/_ngx-formly.scss @@ -0,0 +1,3 @@ +formly-wrapper-mat-form-field .mat-mdc-form-field { + width: 100%; +} diff --git a/front/app/src/styles/plugins/_ngx-toastr.scss b/front/app/src/styles/plugins/_ngx-toastr.scss new file mode 100644 index 00000000..8d737f72 --- /dev/null +++ b/front/app/src/styles/plugins/_ngx-toastr.scss @@ -0,0 +1,52 @@ +@use '@angular/material' as mat; +@use 'ngx-toastr/toastr'; + +.toast-container { + .ngx-toastr { + min-height: 48px; + padding: 14px 16px; + color: rgba(255, 255, 255, .7); + background-color: #323232; + border-radius: 4px; + + @include mat.elevation(6); + + &:hover { + @include mat.elevation(10); + } + } + + .toast-success, + .toast-info, + .toast-warning, + .toast-error { + padding-left: 50px; + color: #fff; + } + + .toast-info { + background-color: mat.get-color-from-palette(mat.$blue-palette, 500); + } + + .toast-success { + background-color: mat.get-color-from-palette(mat.$green-palette, 500); + } + + .toast-warning { + background-color: mat.get-color-from-palette(mat.$orange-palette, 500); + } + + .toast-error { + background-color: mat.get-color-from-palette(mat.$red-palette, 500); + } + + .toast-close-button { + font-size: inherit; + text-shadow: 0 1px 0 rgba(0, 0, 0, .25); + + &:hover { + color: inherit; + opacity: .6; + } + } +} diff --git a/front/app/src/styles/plugins/_photoviewer.scss b/front/app/src/styles/plugins/_photoviewer.scss new file mode 100644 index 00000000..bdeb7ac1 --- /dev/null +++ b/front/app/src/styles/plugins/_photoviewer.scss @@ -0,0 +1,3 @@ +// The photoviewer styles have been imported from the `@ng-matero/extensions/theming` +// so that you don't need to import it again. +// @use '~photoviewer/dist/photoviewer.css'; diff --git a/front/app/src/typings.d.ts b/front/app/src/typings.d.ts new file mode 100644 index 00000000..84c8d73c --- /dev/null +++ b/front/app/src/typings.d.ts @@ -0,0 +1 @@ +declare const ApexCharts: any; diff --git a/front/app/tsconfig.app.json b/front/app/tsconfig.app.json index 374cc9d2..c3317bcd 100644 --- a/front/app/tsconfig.app.json +++ b/front/app/tsconfig.app.json @@ -1,14 +1,9 @@ -/* To learn more about this file see: https://angular.io/config/tsconfig. */ { "extends": "./tsconfig.json", "compilerOptions": { "outDir": "./out-tsc/app", - "types": [] + "types": ["node"] }, - "files": [ - "src/main.ts" - ], - "include": [ - "src/**/*.d.ts" - ] + "files": ["src/main.ts"], + "include": ["src/**/*.d.ts"] } diff --git a/front/app/tsconfig.json b/front/app/tsconfig.json index ed966d43..1b2310ef 100644 --- a/front/app/tsconfig.json +++ b/front/app/tsconfig.json @@ -1,28 +1,36 @@ -/* To learn more about this file see: https://angular.io/config/tsconfig. */ { "compileOnSave": false, "compilerOptions": { - "baseUrl": "./", + "baseUrl": "src", + "downlevelIteration": true, "outDir": "./dist/out-tsc", "forceConsistentCasingInFileNames": true, "strict": true, - "noImplicitOverride": true, - "noPropertyAccessFromIndexSignature": true, "noImplicitReturns": true, "noFallthroughCasesInSwitch": true, "sourceMap": true, "declaration": false, - "downlevelIteration": true, - "experimentalDecorators": true, + "module": "es2020", "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, "importHelpers": true, "target": "ES2022", - "module": "ES2022", - "useDefineForClassFields": false, - "lib": [ - "ES2022", - "dom" - ] + "typeRoots": ["node_modules/@types"], + "lib": ["es2019", "dom"], + "paths": { + "@core": ["app/core"], + "@core/*": ["app/core/*"], + "@shared": ["app/shared"], + "@shared/*": ["app/shared/*"], + "@theme": ["app/theme"], + "@theme/*": ["app/theme/*"], + "@testing": ["testing"], + "@testing/*": ["testing/*"], + "@env": ["environments"], + "@env/*": ["environments/*"] + }, + "useDefineForClassFields": false }, "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, diff --git a/front/app/tsconfig.spec.json b/front/app/tsconfig.spec.json index be7e9da7..1030a83c 100644 --- a/front/app/tsconfig.spec.json +++ b/front/app/tsconfig.spec.json @@ -1,14 +1,9 @@ -/* To learn more about this file see: https://angular.io/config/tsconfig. */ { "extends": "./tsconfig.json", "compilerOptions": { "outDir": "./out-tsc/spec", - "types": [ - "jasmine" - ] + "types": ["jasmine", "node"] }, - "include": [ - "src/**/*.spec.ts", - "src/**/*.d.ts" - ] + "files": ["src/test.ts"], + "include": ["src/**/*.spec.ts", "src/**/*.d.ts"] }