From 73a282a9a129f38168338240f41c670eb4b82781 Mon Sep 17 00:00:00 2001 From: Henry Taeschner Date: Sat, 9 Nov 2024 09:56:15 +0100 Subject: [PATCH] fix: app setup files, fix various things --- package-lock.json | 336 +++++++++++++++--- package.json | 6 +- src/app/_th-mixins.scss | 10 + src/app/app.module.ts | 32 +- src/app/onecx-theme-remote.module.ts | 33 +- .../theme-detail/theme-detail.component.html | 31 +- .../theme-detail/theme-detail.component.scss | 3 +- .../theme-detail.component.spec.ts | 44 +-- .../theme-detail/theme-detail.component.ts | 73 ++-- .../theme-import/theme-import.component.html | 1 + .../theme-import.component.spec.ts | 47 +-- .../theme-import/theme-import.component.ts | 48 ++- .../theme-search/theme-search.component.html | 240 +++++++------ .../theme-search.component.spec.ts | 50 ++- .../theme-search/theme-search.component.ts | 23 +- src/assets/i18n/de.json | 17 +- src/assets/i18n/en.json | 17 +- webpack.config.js | 3 +- 18 files changed, 631 insertions(+), 383 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8d155a3..c252289 100644 --- a/package-lock.json +++ b/package-lock.json @@ -42,7 +42,7 @@ "ngx-color": "^9.0.0", "primeflex": "^3.3.1", "primeicons": "^7.0.0", - "primeng": "^17.18.10", + "primeng": "^17.18.11", "rxjs": "7.8.1", "tslib": "^2.7.0" }, @@ -60,7 +60,7 @@ "@angular/language-service": "^18.2.2", "@commitlint/cli": "^19.5.0", "@commitlint/config-conventional": "^19.5.0", - "@openapitools/openapi-generator-cli": "^2.13.4", + "@openapitools/openapi-generator-cli": "^2.15.3", "@schematics/angular": "^18.2.5", "@types/jasmine": "~5.1.4", "@types/node": "22.7.4", @@ -72,7 +72,7 @@ "eslint-plugin-import": "2.30.0", "eslint-plugin-prettier": "^5.2.1", "husky": "^9.1.6", - "jasmine-core": "~5.2.0", + "jasmine-core": "~5.4.0", "jasmine-spec-reporter": "^7.0.0", "karma": "^6.4.4", "karma-chrome-launcher": "^3.2.0", @@ -5546,9 +5546,9 @@ } }, "node_modules/@nestjs/axios": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@nestjs/axios/-/axios-3.0.3.tgz", - "integrity": "sha512-h6TCn3yJwD6OKqqqfmtRS5Zo4E46Ip2n+gK1sqwzNBC+qxQ9xpCu+ODVRFur6V3alHSCSBxb3nNtt73VEdluyA==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@nestjs/axios/-/axios-3.1.1.tgz", + "integrity": "sha512-ySoxrzqX80P1q6LKLKGcgyBd2utg4gbC+4FsJNpXYvILorMlxss/ECNogD9EXLCE4JS5exVFD5ez0nK5hXcNTQ==", "dev": true, "license": "MIT", "peerDependencies": { @@ -5558,9 +5558,9 @@ } }, "node_modules/@nestjs/common": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-10.4.3.tgz", - "integrity": "sha512-4hbLd3XIJubHSylYd/1WSi4VQvG68KM/ECYpMDqA3k3J1/T17SAg40sDoq3ZoO5OZgU0xuNyjuISdOTjs11qVg==", + "version": "10.4.6", + "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-10.4.6.tgz", + "integrity": "sha512-KkezkZvU9poWaNq4L+lNvx+386hpOxPJkfXBBeSMrcqBOx8kVr36TGN2uYkF4Ta4zNu1KbCjmZbc0rhHSg296g==", "dev": true, "license": "MIT", "dependencies": { @@ -5588,9 +5588,9 @@ } }, "node_modules/@nestjs/core": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.4.3.tgz", - "integrity": "sha512-6OQz+5C8mT8yRtfvE5pPCq+p6w5jDot+oQku1KzQ24ABn+lay1KGuJwcKZhdVNuselx+8xhdMxknZTA8wrGLIg==", + "version": "10.4.6", + "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.4.6.tgz", + "integrity": "sha512-zXVPxCNRfO6gAy0yvEDjUxE/8gfZICJFpsl2lZAUH31bPb6m+tXuhUq2mVCTEltyMYQ+DYtRe+fEYM2v152N1g==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -6374,37 +6374,37 @@ "peer": true }, "node_modules/@openapitools/openapi-generator-cli": { - "version": "2.13.9", - "resolved": "https://registry.npmjs.org/@openapitools/openapi-generator-cli/-/openapi-generator-cli-2.13.9.tgz", - "integrity": "sha512-GJaWGcHmLsvj/G1mRDytm9PTDwRGSYUDTf1uA/2FYxQAb5sq4nkZz1tD4Z7qDlZ3xTYSTw4Z8BQUdlsnrA8rcw==", + "version": "2.15.3", + "resolved": "https://registry.npmjs.org/@openapitools/openapi-generator-cli/-/openapi-generator-cli-2.15.3.tgz", + "integrity": "sha512-2UBnsDlMt36thhdXxisbA1qReVtbCaw+NCvXoslRXlaJBL4qkAmZUhNeDLNu3LCbwA2PASMWhJSqeLwgwMCitw==", "dev": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@nestjs/axios": "3.0.3", - "@nestjs/common": "10.4.3", - "@nestjs/core": "10.4.3", + "@nestjs/axios": "3.1.1", + "@nestjs/common": "10.4.6", + "@nestjs/core": "10.4.6", "@nuxtjs/opencollective": "0.3.2", - "axios": "1.7.4", + "axios": "1.7.7", "chalk": "4.1.2", "commander": "8.3.0", "compare-versions": "4.1.4", "concurrently": "6.5.1", "console.table": "0.10.0", "fs-extra": "10.1.0", - "glob": "7.2.3", - "https-proxy-agent": "7.0.4", + "glob": "9.3.5", "inquirer": "8.2.6", "lodash": "4.17.21", + "proxy-agent": "6.4.0", "reflect-metadata": "0.1.13", "rxjs": "7.8.1", - "tslib": "2.6.2" + "tslib": "2.8.1" }, "bin": { "openapi-generator-cli": "main.js" }, "engines": { - "node": ">=10.0.0" + "node": ">=16" }, "funding": { "type": "opencollective", @@ -6464,6 +6464,25 @@ "dev": true, "license": "MIT" }, + "node_modules/@openapitools/openapi-generator-cli/node_modules/glob": { + "version": "9.3.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.5.tgz", + "integrity": "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "minimatch": "^8.0.2", + "minipass": "^4.2.4", + "path-scurry": "^1.6.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@openapitools/openapi-generator-cli/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -6474,18 +6493,30 @@ "node": ">=8" } }, - "node_modules/@openapitools/openapi-generator-cli/node_modules/https-proxy-agent": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", - "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", + "node_modules/@openapitools/openapi-generator-cli/node_modules/minimatch": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.4.tgz", + "integrity": "sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "agent-base": "^7.0.2", - "debug": "4" + "brace-expansion": "^2.0.1" }, "engines": { - "node": ">= 14" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@openapitools/openapi-generator-cli/node_modules/minipass": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz", + "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=8" } }, "node_modules/@openapitools/openapi-generator-cli/node_modules/reflect-metadata": { @@ -6509,9 +6540,9 @@ } }, "node_modules/@openapitools/openapi-generator-cli/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "dev": true, "license": "0BSD" }, @@ -6887,6 +6918,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", + "dev": true, + "license": "MIT" + }, "node_modules/@tsconfig/node10": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", @@ -8190,6 +8228,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -8252,9 +8303,9 @@ } }, "node_modules/axios": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz", - "integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==", + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", "dev": true, "license": "MIT", "dependencies": { @@ -8396,6 +8447,16 @@ "node": "^4.5.0 || >= 5.9" } }, + "node_modules/basic-ftp": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", + "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/batch": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", @@ -10041,6 +10102,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/data-view-buffer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", @@ -10252,6 +10323,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -10900,6 +10986,39 @@ "node": ">=0.8.0" } }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/escodegen/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/eslint": { "version": "8.57.1", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", @@ -11472,6 +11591,20 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/esquery": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", @@ -12242,6 +12375,37 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-uri": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.3.tgz", + "integrity": "sha512-BzUrJBS9EcUb4cFol8r4W3v1cPsSyajLSthNkz5BxbpDcHN5tIrM10E2eNvfnvBn3DaT3DUgx0OpsBKkaOpanw==", + "dev": true, + "license": "MIT", + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4", + "fs-extra": "^11.2.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/get-uri/node_modules/fs-extra": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, "node_modules/git-raw-commits": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-4.0.0.tgz", @@ -13793,9 +13957,9 @@ } }, "node_modules/jasmine-core": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-5.2.0.tgz", - "integrity": "sha512-tSAtdrvWybZkQmmaIoDgnvHG8ORUNw5kEVlO5CvrXj02Jjr9TZrmjFq7FUiOUzJiOP2wLGYT6PgrQgQF4R1xiw==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-5.4.0.tgz", + "integrity": "sha512-T4fio3W++llLd7LGSGsioriDHgWyhoL6YTu4k37uwJLF7DzOzspz7mNxRoM3cQdLWtL/ebazQpIf/yZGJx/gzg==", "dev": true, "license": "MIT" }, @@ -15891,6 +16055,16 @@ "dev": true, "license": "MIT" }, + "node_modules/netmask": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", + "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/ngx-build-plus": { "version": "18.0.0", "resolved": "https://registry.npmjs.org/ngx-build-plus/-/ngx-build-plus-18.0.0.tgz", @@ -16696,6 +16870,40 @@ "node": ">= 4" } }, + "node_modules/pac-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.0.2.tgz", + "integrity": "sha512-BFi3vZnO9X5Qt6NRz7ZOaPja3ic0PhlsmCRYLOpN11+mWBCR6XJDqW5RF3j8jm4WGGQZtBA+bTfxYzeKW73eHg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.5", + "pac-resolver": "^7.0.1", + "socks-proxy-agent": "^8.0.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-resolver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "dev": true, + "license": "MIT", + "dependencies": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/package-json-from-dist": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", @@ -17997,9 +18205,9 @@ "license": "MIT" }, "node_modules/primeng": { - "version": "17.18.10", - "resolved": "https://registry.npmjs.org/primeng/-/primeng-17.18.10.tgz", - "integrity": "sha512-P3UskInOZ7qYICxSYvf0K8nUEb7DmndiXmyvLGU1wch+XcVWmVs4FZsWKNfdvK7TUdxxYj8WW44nodNV/epr3A==", + "version": "17.18.11", + "resolved": "https://registry.npmjs.org/primeng/-/primeng-17.18.11.tgz", + "integrity": "sha512-LzV0fFZmb3GdnaRqi1+GP+RPtW0a+jztL5pH1zRWY7+7pyQ0n1YNyTXzmqVcdks/CmoyjNhutWEmexwi6vFVeA==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -18084,6 +18292,36 @@ "node": ">= 0.10" } }, + "node_modules/proxy-agent": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", + "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.3", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.0.1", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -19325,18 +19563,6 @@ "node": ">= 18" } }, - "node_modules/sonarqube-scanner/node_modules/axios": { - "version": "1.7.7", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", - "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, "node_modules/sonarqube-scanner/node_modules/commander": { "version": "12.0.0", "resolved": "https://registry.npmjs.org/commander/-/commander-12.0.0.tgz", diff --git a/package.json b/package.json index b9d7b52..d6a21bc 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,7 @@ "ngx-color": "^9.0.0", "primeflex": "^3.3.1", "primeicons": "^7.0.0", - "primeng": "^17.18.10", + "primeng": "^17.18.11", "rxjs": "7.8.1", "tslib": "^2.7.0" }, @@ -86,7 +86,7 @@ "@angular/language-service": "^18.2.2", "@commitlint/cli": "^19.5.0", "@commitlint/config-conventional": "^19.5.0", - "@openapitools/openapi-generator-cli": "^2.13.4", + "@openapitools/openapi-generator-cli": "^2.15.3", "@schematics/angular": "^18.2.5", "@types/jasmine": "~5.1.4", "@types/node": "22.7.4", @@ -98,7 +98,7 @@ "eslint-plugin-import": "2.30.0", "eslint-plugin-prettier": "^5.2.1", "husky": "^9.1.6", - "jasmine-core": "~5.2.0", + "jasmine-core": "~5.4.0", "jasmine-spec-reporter": "^7.0.0", "karma": "^6.4.4", "karma-chrome-launcher": "^3.2.0", diff --git a/src/app/_th-mixins.scss b/src/app/_th-mixins.scss index a62323e..4a80f96 100644 --- a/src/app/_th-mixins.scss +++ b/src/app/_th-mixins.scss @@ -490,6 +490,16 @@ } } } + +@mixin tabview-fix-color-selected-tab { + :host ::ng-deep { + // correct the background color on inital selected tab + .p-tabview .p-tabview-nav li.p-highlight .p-tabview-nav-link[aria-selected='true'] { + background-color: rgba(var(--primary-color-rgb), 0.12); + } + } +} + /* ********************************************** * TABLE * **********************************************/ diff --git a/src/app/app.module.ts b/src/app/app.module.ts index a0cc613..987ce8c 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -1,4 +1,4 @@ -import { APP_INITIALIZER, CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA, NgModule } from '@angular/core' +import { APP_INITIALIZER, NgModule } from '@angular/core' import { CommonModule } from '@angular/common' import { HttpClient, provideHttpClient, withInterceptorsFromDi } from '@angular/common/http' import { RouterModule, Routes } from '@angular/router' @@ -6,33 +6,34 @@ import { BrowserModule } from '@angular/platform-browser' import { BrowserAnimationsModule } from '@angular/platform-browser/animations' import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core' -import { - APP_CONFIG, - AppStateService, - createTranslateLoader, - translateServiceInitializer, - PortalCoreModule, - UserService -} from '@onecx/portal-integration-angular' import { KeycloakAuthModule } from '@onecx/keycloak-auth' +import { createTranslateLoader } from '@onecx/angular-accelerator' +import { APP_CONFIG, AppStateService, UserService } from '@onecx/angular-integration-interface' +import { translateServiceInitializer, PortalCoreModule } from '@onecx/portal-integration-angular' +import { environment } from 'src/environments/environment' import { AppComponent } from './app.component' -import { environment } from '../environments/environment' -const routes: Routes = [{ path: '', pathMatch: 'full' }] +const routes: Routes = [ + { + path: '', + loadChildren: () => import('./theme/theme.module').then((m) => m.ThemeModule) + } +] + @NgModule({ bootstrap: [AppComponent], declarations: [AppComponent], imports: [ CommonModule, BrowserModule, - KeycloakAuthModule, BrowserAnimationsModule, + KeycloakAuthModule, + PortalCoreModule.forRoot('onecx-theme-ui'), RouterModule.forRoot(routes, { initialNavigation: 'enabledBlocking', enableTracing: true }), - PortalCoreModule.forRoot('onecx-theme-ui'), TranslateModule.forRoot({ isolate: true, loader: { @@ -51,11 +52,10 @@ const routes: Routes = [{ path: '', pathMatch: 'full' }] deps: [UserService, TranslateService] }, provideHttpClient(withInterceptorsFromDi()) - ], - schemas: [NO_ERRORS_SCHEMA, CUSTOM_ELEMENTS_SCHEMA] + ] }) export class AppModule { constructor() { - console.info('App Module constructor') + console.info('OneCX Theme Module constructor') } } diff --git a/src/app/onecx-theme-remote.module.ts b/src/app/onecx-theme-remote.module.ts index 5465fce..7ec0a66 100644 --- a/src/app/onecx-theme-remote.module.ts +++ b/src/app/onecx-theme-remote.module.ts @@ -1,26 +1,25 @@ +import { APP_INITIALIZER, DoBootstrap, Injector, NgModule } from '@angular/core' import { HttpClient, provideHttpClient, withInterceptorsFromDi } from '@angular/common/http' +import { RouterModule, Routes, Router } from '@angular/router' import { BrowserModule } from '@angular/platform-browser' -import { APP_INITIALIZER, DoBootstrap, Injector, NgModule } from '@angular/core' -import { Router, RouterModule, Routes } from '@angular/router' -import { MissingTranslationHandler, TranslateLoader, TranslateModule } from '@ngx-translate/core' +import { BrowserAnimationsModule } from '@angular/platform-browser/animations' +import { TranslateLoader, TranslateModule, MissingTranslationHandler } from '@ngx-translate/core' +import { AngularAuthModule } from '@onecx/angular-auth' +import { createTranslateLoader } from '@onecx/angular-accelerator' +import { createAppEntrypoint, initializeRouter, startsWith } from '@onecx/angular-webcomponents' +import { addInitializeModuleGuard, AppStateService, ConfigurationService } from '@onecx/angular-integration-interface' import { - AppStateService, - ConfigurationService, - createTranslateLoader, PortalApiConfiguration, PortalCoreModule, PortalMissingTranslationHandler } from '@onecx/portal-integration-angular' -import { addInitializeModuleGuard } from '@onecx/angular-integration-interface' -import { createAppEntrypoint, initializeRouter, startsWith } from '@onecx/angular-webcomponents' -import { AppEntrypointComponent } from './app-entrypoint.component' -import { AngularAuthModule } from '@onecx/angular-auth' -import { BrowserAnimationsModule } from '@angular/platform-browser/animations' -import { environment } from 'src/environments/environment' -import { Configuration } from './shared/generated' import { SLOT_SERVICE, SlotService } from '@onecx/angular-remote-components' +import { Configuration } from './shared/generated' +import { environment } from 'src/environments/environment' +import { AppEntrypointComponent } from './app-entrypoint.component' + function apiConfigProvider(configService: ConfigurationService, appStateService: AppStateService) { return new PortalApiConfiguration(Configuration, environment.apiPrefix, configService, appStateService) } @@ -35,8 +34,8 @@ const routes: Routes = [ declarations: [AppEntrypointComponent], imports: [ AngularAuthModule, - BrowserAnimationsModule, BrowserModule, + BrowserAnimationsModule, PortalCoreModule.forMicroFrontend(), RouterModule.forRoot(addInitializeModuleGuard(routes)), TranslateModule.forRoot({ @@ -49,20 +48,18 @@ const routes: Routes = [ missingTranslationHandler: { provide: MissingTranslationHandler, useClass: PortalMissingTranslationHandler } }) ], - exports: [], providers: [ ConfigurationService, + { provide: Configuration, useFactory: apiConfigProvider, deps: [ConfigurationService, AppStateService] }, { provide: APP_INITIALIZER, useFactory: initializeRouter, multi: true, deps: [Router, AppStateService] }, - { provide: Configuration, useFactory: apiConfigProvider, deps: [ConfigurationService, AppStateService] }, { provide: SLOT_SERVICE, useExisting: SlotService }, provideHttpClient(withInterceptorsFromDi()) - ], - schemas: [] + ] }) export class OneCXThemeModule implements DoBootstrap { constructor(private readonly injector: Injector) { diff --git a/src/app/theme/theme-detail/theme-detail.component.html b/src/app/theme/theme-detail/theme-detail.component.html index 5f688f0..a90a3e0 100644 --- a/src/app/theme/theme-detail/theme-detail.component.html +++ b/src/app/theme/theme-detail/theme-detail.component.html @@ -1,8 +1,7 @@ -
- -
-
+ -
- - + + + { @@ -92,9 +93,8 @@ describe('ThemeDetailComponent', () => { }) it('should create with provided id and get theme', async () => { - const name = 'themeName' const route = TestBed.inject(ActivatedRoute) - spyOn(route.snapshot.paramMap, 'get').and.returnValue(name) + spyOn(route.snapshot.paramMap, 'get').and.returnValue(theme.name!) translateService.use('de') // recreate component to test constructor @@ -103,13 +103,13 @@ describe('ThemeDetailComponent', () => { fixture.detectChanges() themesApiSpy.getThemeByName.and.returnValue(of({ resource: theme } as any)) - component.loading = true await component.ngOnInit() component.theme$?.subscribe((data) => { expect(data).toBe(theme) - expect(component.themeName).toBe(name) + expect(component.themeName).toBe(theme.name!) + expect(component.theme?.displayName).toBe(theme.displayName!) expect(component.dateFormat).toBe('medium') expect(themesApiSpy.getThemeByName).toHaveBeenCalled() }) @@ -194,38 +194,24 @@ describe('ThemeDetailComponent', () => { }) it('should display not found error', () => { - themesApiSpy.getThemeByName.and.returnValue( - throwError( - () => - new HttpErrorResponse({ - status: 404 - }) - ) - ) - component.exceptionKey = '' + themesApiSpy.getThemeByName.and.returnValue(throwError(() => new HttpErrorResponse({ status: 404 }))) + component.exceptionKey = undefined component.ngOnInit() component.theme$?.subscribe(() => { - expect(component.exceptionKey).toBe('THEME.NOT_FOUND') + expect(component.exceptionKey).toBe('EXCEPTIONS.HTTP_STATUS_404.THEME') }) }) - it('should display load error', () => { - themesApiSpy.getThemeByName.and.returnValue( - throwError( - () => - new HttpErrorResponse({ - status: 400 - }) - ) - ) - component.exceptionKey = '' + it('should display permission error', () => { + themesApiSpy.getThemeByName.and.returnValue(throwError(() => new HttpErrorResponse({ status: 403 }))) + component.exceptionKey = undefined component.ngOnInit() component.theme$?.subscribe(() => { - expect(component.exceptionKey).toBe('THEME.LOAD_ERROR') + expect(component.exceptionKey).toBe('EXCEPTIONS.HTTP_STATUS_403.THEME') }) }) @@ -358,10 +344,10 @@ describe('ThemeDetailComponent', () => { it('should display error on theme export fail', () => { themesApiSpy.exportThemes.and.returnValue(throwError(() => new Error())) - component.theme = { - name: 'themeName' - } + component.theme = theme + component.onExportTheme() + expect(msgServiceSpy.error).toHaveBeenCalledOnceWith({ summaryKey: 'ACTIONS.EXPORT.EXPORT_THEME_FAIL' }) }) diff --git a/src/app/theme/theme-detail/theme-detail.component.ts b/src/app/theme/theme-detail/theme-detail.component.ts index f5575fb..b439dcc 100644 --- a/src/app/theme/theme-detail/theme-detail.component.ts +++ b/src/app/theme/theme-detail/theme-detail.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit } from '@angular/core' +import { AfterViewInit, ChangeDetectorRef, Component, OnInit } from '@angular/core' import { Location } from '@angular/common' import { ActivatedRoute, Router } from '@angular/router' import { TranslateService } from '@ngx-translate/core' @@ -7,7 +7,7 @@ import FileSaver from 'file-saver' import { Action, PortalMessageService, UserService } from '@onecx/portal-integration-angular' -import { bffImageUrl, getCurrentDateTime } from 'src/app/shared/utils' +import { bffImageUrl, getCurrentDateTime, limitText } from 'src/app/shared/utils' import { ExportThemeRequest, ImagesInternalAPIService, @@ -20,19 +20,20 @@ import { templateUrl: './theme-detail.component.html', styleUrls: ['./theme-detail.component.scss'] }) -export class ThemeDetailComponent implements OnInit { +export class ThemeDetailComponent implements OnInit, AfterViewInit { public theme: Theme | undefined - public theme$: Observable | undefined - public themeName!: string + public theme$!: Observable + public themeName: string public themeDeleteVisible = false public showOperatorMessage = true // display initially only public loading = true + public exceptionKey: string | undefined = undefined public RefType = RefType public dateFormat = 'medium' // page header public actions$: Observable | undefined public headerImageUrl?: string - public exceptionKey = '' + public limitText = limitText constructor( private readonly user: UserService, @@ -42,37 +43,41 @@ export class ThemeDetailComponent implements OnInit { private readonly themeApi: ThemesAPIService, private readonly msgService: PortalMessageService, private readonly translate: TranslateService, - private readonly imageApi: ImagesInternalAPIService + private readonly imageApi: ImagesInternalAPIService, + private readonly cd: ChangeDetectorRef ) { this.themeName = this.route.snapshot.paramMap.get('name') || '' this.dateFormat = this.user.lang$.getValue() === 'de' ? 'dd.MM.yyyy HH:mm:ss' : 'medium' } ngOnInit(): void { + this.getTheme() + } + + ngAfterViewInit() { + this.cd.detectChanges() + } + + private getTheme() { this.loading = true this.theme$ = this.themeApi.getThemeByName({ name: this.themeName }).pipe( map((data) => { - this.preparePage() if (data.resource) this.theme = data.resource this.headerImageUrl = this.getImageUrl(this.theme, RefType.Logo) return data.resource }), catchError((err) => { - if (err.status === 404) this.exceptionKey = 'THEME.NOT_FOUND' - else this.exceptionKey = 'THEME.LOAD_ERROR' + this.exceptionKey = 'EXCEPTIONS.HTTP_STATUS_' + err.status + '.THEME' console.error('getThemeByName():', err) return of({} as Theme) }), finalize(() => { this.loading = false + this.prepareActionButtons() }) ) } - private preparePage() { - this.prepareActionButtons() - } - private prepareActionButtons(): void { this.actions$ = this.translate .get([ @@ -102,7 +107,9 @@ export class ThemeDetailComponent implements OnInit { actionCallback: () => this.onExportTheme(), icon: 'pi pi-download', show: 'always', - permission: 'THEME#EXPORT' + permission: 'THEME#EXPORT', + conditional: true, + showCondition: this.theme !== undefined }, { label: data['ACTIONS.EDIT.LABEL'], @@ -110,7 +117,9 @@ export class ThemeDetailComponent implements OnInit { actionCallback: () => this.router.navigate(['./edit'], { relativeTo: this.route }), icon: 'pi pi-pencil', show: 'always', - permission: 'THEME#EDIT' + permission: 'THEME#EDIT', + conditional: true, + showCondition: this.theme !== undefined }, { label: data['ACTIONS.DELETE.LABEL'], @@ -120,7 +129,7 @@ export class ThemeDetailComponent implements OnInit { show: 'asOverflow', permission: 'THEME#DELETE', conditional: true, - showCondition: !this.theme?.operator + showCondition: this.theme !== undefined && !this.theme?.operator } ] }) @@ -158,23 +167,19 @@ export class ThemeDetailComponent implements OnInit { onExportTheme(): void { if (this.theme?.name) { const exportThemeRequest: ExportThemeRequest = { names: [this.theme.name] } - this.themeApi - .exportThemes({ - exportThemeRequest - }) - .subscribe({ - next: (data) => { - const themeJSON = JSON.stringify(data, null, 2) - FileSaver.saveAs( - new Blob([themeJSON], { type: 'text/json' }), - `onecx-theme_${this.theme?.name}_${getCurrentDateTime()}.json` - ) - }, - error: (err) => { - console.log(err) - this.msgService.error({ summaryKey: 'ACTIONS.EXPORT.EXPORT_THEME_FAIL' }) - } - }) + this.themeApi.exportThemes({ exportThemeRequest }).subscribe({ + next: (data) => { + const themeJSON = JSON.stringify(data, null, 2) + FileSaver.saveAs( + new Blob([themeJSON], { type: 'text/json' }), + `onecx-theme_${this.theme?.name}_${getCurrentDateTime()}.json` + ) + }, + error: (err) => { + console.log(err) + this.msgService.error({ summaryKey: 'ACTIONS.EXPORT.EXPORT_THEME_FAIL' }) + } + }) } } diff --git a/src/app/theme/theme-import/theme-import.component.html b/src/app/theme/theme-import/theme-import.component.html index c86a749..f8c17b3 100644 --- a/src/app/theme/theme-import/theme-import.component.html +++ b/src/app/theme/theme-import/theme-import.component.html @@ -27,6 +27,7 @@
{ @@ -50,23 +50,18 @@ describe('ThemeImportComponent', () => { it('should create', () => { expect(component).toBeTruthy() - expect(component.httpHeaders.get('Content-Type')).toBe('application/json') - expect(component.themes).toEqual([]) }) it('should initialize themes and headers onInit', () => { - const themeArr = [ - { - name: 'theme1' - }, - { - name: 'theme2' - } + const themeArr: Theme[] = [ + { name: 'theme1', displayName: 'Theme-1' }, + { name: 'theme2', displayName: 'Theme-2' } ] - const themesResponse = { - stream: themeArr - } + const themesResponse: GetThemesResponse = { stream: themeArr } + themeApiSpy.getThemes.and.returnValue(of(themesResponse as any)) + + component.displayThemeImport = true component.ngOnInit() expect(component.themes).toEqual(themeArr) @@ -126,14 +121,11 @@ describe('ThemeImportComponent', () => { }) it('should indicate theme name existance if already present', async () => { - component.themes = [ - { - name: 'themeName' - } - ] + component.themes = [{ name: 'themeName', displayName: 'Theme-1' }] const themeSnapshot = JSON.stringify({ themes: { themeName: { + displayName: 'Theme-1', logoUrl: 'logo_url' } } @@ -146,7 +138,7 @@ describe('ThemeImportComponent', () => { expect(component.themeImportError).toBe(false) expect(component.themeSnapshot).toBeDefined() expect(component.themeNameExists).toBe(true) - // TODO: if error is visible + expect(component.displayNameExists).toBe(true) }) it('should emit displayThemeImportChange on import hide', () => { @@ -179,7 +171,7 @@ describe('ThemeImportComponent', () => { themeApiSpy.importThemes.and.returnValue( of( new HttpResponse({ - body: { id: 'id' } + body: { id: 'id', name: 'themeName', displayName: 'themeDisplayName' } }) ) ) @@ -189,7 +181,8 @@ describe('ThemeImportComponent', () => { created: 'created', themes: { ['theme']: { description: 'themeDescription' } } } - + component.themeName = 'themeName' + component.displayName = 'themeDisplayName' component.onThemeUpload() expect(msgServiceSpy.success).toHaveBeenCalledOnceWith({ summaryKey: 'THEME.IMPORT.IMPORT_THEME_SUCCESS' }) @@ -197,15 +190,11 @@ describe('ThemeImportComponent', () => { }) it('should return if no themes available', () => { - themeApiSpy.importThemes.and.returnValue( - of( - new HttpResponse({ - body: { id: 'id' } - }) - ) - ) + themeApiSpy.importThemes.and.returnValue(of(new HttpResponse({ body: { id: 'id' } }))) spyOn(component.uploadEmitter, 'emit') + component.themeName = 'themeName' + component.displayName = 'themeDisplayName' component.onThemeUpload() expect(component.uploadEmitter.emit).not.toHaveBeenCalled() @@ -219,6 +208,8 @@ describe('ThemeImportComponent', () => { themes: { ['theme']: { description: 'themeDescription' } } } + component.themeName = 'themeName' + component.displayName = 'themeDisplayName' component.onThemeUpload() expect(msgServiceSpy.error).toHaveBeenCalledOnceWith({ summaryKey: 'THEME.IMPORT.IMPORT_THEME_FAIL' }) diff --git a/src/app/theme/theme-import/theme-import.component.ts b/src/app/theme/theme-import/theme-import.component.ts index e38db63..8770af7 100644 --- a/src/app/theme/theme-import/theme-import.component.ts +++ b/src/app/theme/theme-import/theme-import.component.ts @@ -1,4 +1,13 @@ -import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core' +import { + AfterViewInit, + Component, + ChangeDetectorRef, + EventEmitter, + Input, + OnInit, + Output, + ViewChild +} from '@angular/core' import { HttpHeaders } from '@angular/common/http' import { TranslateService } from '@ngx-translate/core' import { ActivatedRoute, Router } from '@angular/router' @@ -13,14 +22,16 @@ import { FileSelectEvent } from 'primeng/fileupload' templateUrl: './theme-import.component.html', styleUrls: ['./theme-import.component.scss'] }) -export class ThemeImportComponent implements OnInit { +export class ThemeImportComponent implements OnInit, AfterViewInit { @Input() public displayThemeImport = false @Output() public displayThemeImportChange = new EventEmitter() @Output() public uploadEmitter = new EventEmitter() + @ViewChild('themeNameInput') themeNameInput!: HTMLInputElement + public themes!: Theme[] - public themeName = '' - public displayName: string | null = '' + public themeName: string | undefined = undefined + public displayName: string | undefined = undefined public themeNameExists = false public displayNameExists = false public themeImportError = false @@ -33,13 +44,20 @@ export class ThemeImportComponent implements OnInit { private readonly router: Router, private readonly themeApi: ThemesAPIService, public readonly translate: TranslateService, - private readonly msgService: PortalMessageService + private readonly msgService: PortalMessageService, + private readonly cd: ChangeDetectorRef ) {} ngOnInit(): void { - this.httpHeaders = new HttpHeaders() - this.httpHeaders = this.httpHeaders.set('Content-Type', 'application/json') - this.getThemes() + if (this.displayThemeImport) { + this.httpHeaders = new HttpHeaders() + this.httpHeaders = this.httpHeaders.set('Content-Type', 'application/json') + this.getThemes() + } + } + + ngAfterViewInit() { + this.cd.detectChanges() } public async onImportThemeSelect(event: FileSelectEvent): Promise { @@ -53,7 +71,7 @@ export class ThemeImportComponent implements OnInit { if (themeSnapshot.themes) { const key: string[] = Object.keys(themeSnapshot.themes) this.themeName = key[0] - this.displayName = themeSnapshot.themes[key[0]].displayName ?? null + this.displayName = themeSnapshot.themes[key[0]].displayName this.properties = themeSnapshot.themes[key[0]].properties } this.checkThemeExistence() @@ -68,8 +86,9 @@ export class ThemeImportComponent implements OnInit { } public checkThemeExistence() { - this.themeNameExists = this.themes.filter((theme) => theme.name === this.themeName).length > 0 - this.displayNameExists = this.themes.filter((theme) => theme.displayName === this.displayName).length > 0 + if (this.themeName) this.themeNameExists = this.themes.filter((theme) => theme.name === this.themeName).length > 0 + if (this.displayName) + this.displayNameExists = this.themes.filter((theme) => theme.displayName === this.displayName).length > 0 } public onImportThemeHide(): void { @@ -80,15 +99,18 @@ export class ThemeImportComponent implements OnInit { this.themeImportError = false } public onThemeUpload(): void { + if (!this.themeName || !this.displayName) return if (!this.themeSnapshot?.themes) return + // Import data preparation const key: string[] = Object.keys(this.themeSnapshot?.themes) - this.themeSnapshot.themes[key[0]].displayName = this.displayName ?? undefined + this.themeSnapshot.themes[key[0]].displayName = this.displayName if (key[0] !== this.themeName) { // save the theme properties to be reassigned on new key const themeProps = Object.getOwnPropertyDescriptor(this.themeSnapshot.themes, key[0]) - Object.defineProperty(this.themeSnapshot.themes, this.themeName, themeProps ?? {}) + Object.defineProperty(this.themeSnapshot.themes, this.themeName ?? '', themeProps ?? {}) delete this.themeSnapshot.themes[key[0]] } + // Import execution: upload this.themeApi .importThemes({ themeSnapshot: this.themeSnapshot diff --git a/src/app/theme/theme-search/theme-search.component.html b/src/app/theme/theme-search/theme-search.component.html index 23f7b3f..4566727 100644 --- a/src/app/theme/theme-search/theme-search.component.html +++ b/src/app/theme/theme-search/theme-search.component.html @@ -6,137 +6,149 @@ > - - - - - - - + + + + + + + + - - -
-
+ + +
+
+ +
+
+
+
+ {{ limitText(theme.displayName, 40) }} +
+ +
+
+
+
+ +
+
+
+
+ + + +
+
+
-
-
-
+ +
- {{ limitText(theme.displayName, 40) }} + {{ theme.displayName }}
-
-
-
-
- -
-
-
- - - - -
-
-
- - -
-
- {{ theme.displayName }} -
-
- {{ theme.name }} -
+ {{ theme.name }}
-
- - +
+ + diff --git a/src/app/theme/theme-search/theme-search.component.spec.ts b/src/app/theme/theme-search/theme-search.component.spec.ts index c6aaa45..a5ab041 100644 --- a/src/app/theme/theme-search/theme-search.component.spec.ts +++ b/src/app/theme/theme-search/theme-search.component.spec.ts @@ -8,14 +8,17 @@ import { TranslateTestingModule } from 'ngx-translate-testing' import { DataViewModule } from 'primeng/dataview' import { of } from 'rxjs' -import { Theme, ThemesAPIService } from 'src/app/shared/generated' +import { GetThemesResponse, ThemesAPIService } from 'src/app/shared/generated' import { ThemeSearchComponent } from './theme-search.component' describe('ThemeSearchComponent', () => { let component: ThemeSearchComponent let fixture: ComponentFixture - const themeApiSpy = jasmine.createSpyObj('ThemesAPIService', ['getThemes']) + //const themeApiSpy = jasmine.createSpyObj('ThemesAPIService', ['getThemes']) + const themeApiSpy = { + getThemes: jasmine.createSpy('getThemes').and.returnValue(of({})) + } beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ @@ -47,7 +50,7 @@ describe('ThemeSearchComponent', () => { expect(component).toBeTruthy() }) - it('should load themes and translations on initialization', async () => { + it('should load themes and translations on initialization', (done) => { const translateService = TestBed.inject(TranslateService) const actionsTranslations = { 'ACTIONS.CREATE.THEME': 'actionsCreateTheme', @@ -74,12 +77,21 @@ describe('ThemeSearchComponent', () => { { name: 'theme2', displayName: 'Theme 2' } ] } - const themesObservable = of(themesResponse as any) - themeApiSpy.getThemes.and.returnValue(themesObservable) - - await component.ngOnInit() - - expect(component.themes$).toEqual(themesObservable) + themeApiSpy.getThemes.and.returnValue(of(themesResponse as GetThemesResponse)) + + component.ngOnInit() + + component.themes$.subscribe({ + next: (result) => { + if (result) { + expect(result.length).toBe(2) + expect(result[0].name).toEqual('theme1') + expect(result[1].name).toEqual('theme2') + } + done() + }, + error: done.fail + }) let actions: any = [] component.actions$!.subscribe((act) => (actions = act)) @@ -153,24 +165,4 @@ describe('ThemeSearchComponent', () => { component.onImportThemeClick() expect(component.themeImportDialogVisible).toBe(true) }) - - it('should sort themes by display name ', () => { - const a: Theme = { - name: 'a', - displayName: 'a' - } - const b: Theme = { - name: 'b', - displayName: 'b' - } - const c: Theme = { - name: 'c', - displayName: 'c' - } - const themes = [b, c, a] - - themes.sort((x, y) => component.sortThemesByName(x, y)) - - expect(themes).toEqual([a, b, c]) - }) }) diff --git a/src/app/theme/theme-search/theme-search.component.ts b/src/app/theme/theme-search/theme-search.component.ts index 73bbd74..b4d63d2 100644 --- a/src/app/theme/theme-search/theme-search.component.ts +++ b/src/app/theme/theme-search/theme-search.component.ts @@ -1,12 +1,12 @@ import { Component, OnInit, ViewChild } from '@angular/core' import { ActivatedRoute, Router } from '@angular/router' import { TranslateService } from '@ngx-translate/core' -import { Observable, map } from 'rxjs' +import { catchError, finalize, map, Observable, of } from 'rxjs' import { DataView } from 'primeng/dataview' import { Action, DataViewControlTranslations } from '@onecx/portal-integration-angular' -import { GetThemesResponse, ImagesInternalAPIService, RefType, Theme, ThemesAPIService } from 'src/app/shared/generated' +import { ImagesInternalAPIService, RefType, Theme, ThemesAPIService } from 'src/app/shared/generated' import { limitText, bffImageUrl } from 'src/app/shared/utils' @Component({ @@ -14,8 +14,10 @@ import { limitText, bffImageUrl } from 'src/app/shared/utils' styleUrls: ['./theme-search.component.scss'] }) export class ThemeSearchComponent implements OnInit { - themes$!: Observable + public themes$!: Observable public actions$: Observable | undefined + public loading = false + public exceptionKey: string | undefined = undefined public viewMode: 'list' | 'grid' = 'grid' public filter: string | undefined public sortField = 'displayName' @@ -35,15 +37,24 @@ export class ThemeSearchComponent implements OnInit { ) {} ngOnInit(): void { - this.loadThemes() this.prepareTranslations() this.prepareActionButtons() + this.loadThemes() } public loadThemes(): void { - this.themes$ = this.themeApi.getThemes({}) + this.loading = true + this.themes$ = this.themeApi.getThemes({}).pipe( + map((data) => (data?.stream ? data.stream.sort(this.sortThemesByName) : [])), + catchError((err) => { + this.exceptionKey = 'EXCEPTIONS.HTTP_STATUS_' + err.status + '.WORKSPACES' + console.error('getThemes():', err) + return of([] as Theme[]) + }), + finalize(() => (this.loading = false)) + ) } - public sortThemesByName(a: Theme, b: Theme): number { + private sortThemesByName(a: Theme, b: Theme): number { return a.displayName!.toUpperCase().localeCompare(b.displayName!.toUpperCase()) } diff --git a/src/assets/i18n/de.json b/src/assets/i18n/de.json index 72da0ac..ba0d3c2 100644 --- a/src/assets/i18n/de.json +++ b/src/assets/i18n/de.json @@ -145,8 +145,6 @@ "DETAIL.AUTO_APPLY.TOOLTIP": "Wenn diese Option eingeschaltet ist, dann werden alle Änderungen (Schriftart, Farben) automatisch auf die aktuelle Seite angewendet.", "DETAIL.AUTO_APPLY_CURRENT_THEME": "Änderungen werden beim Speichern sofort angewendet, weil es das aktuell benutzte Theme ist", "DETAIL.THEME_SELECTOR": "Ein Theme als Vorlage wählen", - "LOAD_ERROR": "Theme konnte nicht geladen werden", - "NOT_FOUND": "Theme nicht gefunden", "NO_PROPERTIES": "Keine Farbwerte enthalten", "TABS": { "INTERN": "Intern", @@ -223,20 +221,19 @@ }, "EXCEPTIONS": { "HTTP_STATUS_0": { - "PORTALS": "Unbekanntes Problem beim Abrufen von Workspace-Daten - Bitte versuchen sie es noch einmal.", - "MENUS": "Unbekanntes Problem beim Abrufen von Menü-Daten - Bitte versuchen sie es noch einmal." + "THEME": "Unbekanntes Problem beim Abrufen des Themes - Bitte versuchen sie es noch einmal." + }, + "HTTP_STATUS_401": { + "THEME": "Sie sind nicht authorisiert um Themes zu sehen." }, "HTTP_STATUS_403": { - "PORTALS": "Sie haben keine Rechte um Workspaces zu sehen.", - "MENUS": "Sie haben keine Rechte um Menüs zu sehen." + "THEME": "Sie haben keine Rechte um Themes zu sehen." }, "HTTP_STATUS_404": { - "PORTALS": "Es konnten keine Workspaces gefunden werden.", - "MENUS": "Es konnten keine Menüs gefunden werden." + "THEME": "Theme nicht gefunden werden." }, "HTTP_STATUS_500": { - "PORTALS": "Unbekanntes Server-Problem beim Abrufen von Workspace-Daten - Bitte versuchen sie es noch einmal.", - "MENUS": "Unbekanntes Server-Problem beim Abrufen von Menü-Daten - Bitte versuchen sie es noch einmal." + "MENUS": "Unbekanntes Server-Problem beim Abrufen des Themes - Bitte versuchen sie es noch einmal." } } } diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index cea70b2..30a477a 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -145,8 +145,6 @@ "DETAIL.AUTO_APPLY.TOOLTIP": "If this option is switched on, then all changes (font, colors) are automatically applied to the current page.", "DETAIL.AUTO_APPLY_CURRENT_THEME": "Changes are applied immediately when saving because it is the Theme currently in use", "DETAIL.THEME_SELECTOR": "Select Theme as Template", - "LOAD_ERROR": "Failed to load Theme", - "NOT_FOUND": "Theme was not found", "NO_PROPERTIES": "No properties specified", "TABS": { "INTERN": "Internal", @@ -223,20 +221,19 @@ }, "EXCEPTIONS": { "HTTP_STATUS_0": { - "PORTALS": "Unknown problem retrieving Workspace data - please try again.", - "MENUS": "Unknown problem retrieving menu data - please try again." + "MENUS": "Unknown problem retrieving Theme data - please try again." + }, + "HTTP_STATUS_401": { + "MENUS": "You are not authorized to see Themes." }, "HTTP_STATUS_403": { - "PORTALS": "You have no permissions to see workspaces.", - "MENUS": "You have no permissions to see menus." + "MENUS": "You have no permissions to see Themes." }, "HTTP_STATUS_404": { - "PORTALS": "No Workspaces could be found.", - "MENUS": "No menus could be found." + "MENUS": "Theme could not be found." }, "HTTP_STATUS_500": { - "PORTALS": "Unknown server problem retrieving Workspace data - please try again.", - "MENUS": "Unknown server problem retrieving menu data - please try again." + "MENUS": "Unknown server problem retrieving the Theme - please try again." } } } diff --git a/webpack.config.js b/webpack.config.js index 6f5ca39..6154585 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -9,9 +9,10 @@ const config = withModuleFederationPlugin({ }, shared: share({ '@angular/core': { requiredVersion: 'auto' }, - '@angular/forms': { requiredVersion: 'auto', includeSecondaries: true, eager: false }, '@angular/common': { requiredVersion: 'auto', includeSecondaries: { skip: ['@angular/common/http/testing'] } }, '@angular/common/http': { requiredVersion: 'auto', includeSecondaries: true }, + '@angular/forms': { requiredVersion: 'auto', includeSecondaries: true }, + '@angular/platform-browser': { requiredVersion: 'auto', includeSecondaries: true }, '@angular/router': { requiredVersion: 'auto', includeSecondaries: true }, '@ngx-translate/core': { requiredVersion: 'auto' }, primeng: { requiredVersion: 'auto', includeSecondaries: true },