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/shared/generated/.openapi-generator/FILES b/src/app/shared/generated/.openapi-generator/FILES index b4ea333..9ea2698 100644 --- a/src/app/shared/generated/.openapi-generator/FILES +++ b/src/app/shared/generated/.openapi-generator/FILES @@ -32,6 +32,5 @@ model/themeSnapshot.ts model/themeUpdateCreate.ts model/updateThemeRequest.ts model/updateThemeResponse.ts -model/workspace.ts param.ts variables.ts diff --git a/src/app/shared/generated/model/getThemeResponse.ts b/src/app/shared/generated/model/getThemeResponse.ts index 43d51a1..6b539dd 100644 --- a/src/app/shared/generated/model/getThemeResponse.ts +++ b/src/app/shared/generated/model/getThemeResponse.ts @@ -10,11 +10,9 @@ * Do not edit the class manually. */ import { Theme } from './theme'; -import { Workspace } from './workspace'; export interface GetThemeResponse { resource: Theme; - workspaces?: Array; } diff --git a/src/app/shared/generated/model/models.ts b/src/app/shared/generated/model/models.ts index 2db6cd6..303f033 100644 --- a/src/app/shared/generated/model/models.ts +++ b/src/app/shared/generated/model/models.ts @@ -20,4 +20,3 @@ export * from './themeSnapshot'; export * from './themeUpdateCreate'; export * from './updateThemeRequest'; export * from './updateThemeResponse'; -export * from './workspace'; diff --git a/src/app/shared/generated/model/workspace.ts b/src/app/shared/generated/model/workspace.ts deleted file mode 100644 index 74339b9..0000000 --- a/src/app/shared/generated/model/workspace.ts +++ /dev/null @@ -1,18 +0,0 @@ -/** - * onecx-theme-bff - * OneCx theme Bff - * - * The version of the OpenAPI document: 1.0 - * - * - * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). - * https://openapi-generator.tech - * Do not edit the class manually. - */ - - -export interface Workspace { - name?: string; - description?: string; -} - diff --git a/src/app/shared/utils.spec.ts b/src/app/shared/utils.spec.ts index f06c58a..44468e3 100644 --- a/src/app/shared/utils.spec.ts +++ b/src/app/shared/utils.spec.ts @@ -125,6 +125,24 @@ describe('util functions', () => { }) describe('bffImageUrl', () => { + it('should return a correct image path', () => { + const basePath = 'base' + const name = 'name' + + const preparedUrl = bffImageUrl(basePath, name, RefType.Logo) + + expect(preparedUrl).toBe('base/images/name/logo') + }) + + it('should return a path without base', () => { + const basePath = undefined + const name = 'name' + + const preparedUrl = bffImageUrl(basePath, name) + + expect(preparedUrl).toBe('/images/name/logo') + }) + it('should return empty string if no name is provided', () => { const basePath = 'base' const name = undefined diff --git a/src/app/shared/utils.ts b/src/app/shared/utils.ts index 33c7e02..c2cd237 100644 --- a/src/app/shared/utils.ts +++ b/src/app/shared/utils.ts @@ -44,8 +44,8 @@ export function prepareUrlPath(url?: string, path?: string): string { else if (url) return url else return '' } -export function bffImageUrl(basePath: string | undefined, name: string | undefined, refType: RefType): string { - return !name ? '' : basePath + '/images/' + name + '/' + refType +export function bffImageUrl(basePath: string | undefined, name: string | undefined, refType?: RefType): string { + return !name ? '' : (basePath ?? '') + '/images/' + name + '/' + (refType ?? RefType.Logo) } /** diff --git a/src/app/theme/theme-designer/theme-designer.component.html b/src/app/theme/theme-designer/theme-designer.component.html index 3da8d73..55d81c8 100644 --- a/src/app/theme/theme-designer/theme-designer.component.html +++ b/src/app/theme/theme-designer/theme-designer.component.html @@ -18,7 +18,7 @@ {{ 'THEME.DETAIL.AUTO_APPLY' | translate }} {{ 'THEME.GROUPS.BASE' | translate }}
-
+
{ let component: ThemeDesignerComponent let fixture: ComponentFixture + const validTheme = { + id: 'id', + name: 'themeName', + displayName: 'themeDisplayName', + description: 'desc', + logoUrl: 'logo_url', + faviconUrl: 'fav_url', + properties: { + font: { 'font-family': 'myFont' }, + general: { 'primary-color': 'rgb(0,0,0)' } + } + } const msgServiceSpy = jasmine.createSpyObj('PortalMessageService', ['success', 'error', 'info']) const themeServiceSpy = jasmine.createSpyObj('ThemeService', ['apply'], { @@ -124,7 +136,7 @@ describe('ThemeDesignerComponent', () => { initializeComponent() - expect(component.changeMode).toBe('NEW') + expect(component.changeMode).toBe('CREATE') }) it('should populate state and create forms', () => { @@ -134,7 +146,7 @@ describe('ThemeDesignerComponent', () => { initializeComponent() expect(component.themeName).toBe('themeName') - expect(component.themeIsCurrentUsedTheme).toBeFalse() + expect(component.autoApply).toBeFalse() expect(Object.keys(component.fontForm.controls).length).toBe(themeVariables.font.length) expect(Object.keys(component.generalForm.controls).length).toBe(themeVariables.general.length) expect(Object.keys(component.topbarForm.controls).length).toBe(themeVariables.topbar.length) @@ -161,9 +173,9 @@ describe('ThemeDesignerComponent', () => { const cancelAction = actions.filter( (a) => a.label === 'actionCancel' && a.title === 'actionTooltipsCancelAndClose' )[0] - spyOn(component, 'close') + spyOn(component, 'onClose') cancelAction.actionCallback() - expect(component['close']).toHaveBeenCalledTimes(1) + expect(component['onClose']).toHaveBeenCalledTimes(1) const saveAction = actions.filter((a) => a.label === 'actionsSave' && a.title === 'actionsTooltipsSave')[0] spyOn(component, 'onSaveTheme') @@ -221,40 +233,29 @@ describe('ThemeDesignerComponent', () => { })) }) - describe('after creation', () => { + describe('after component initialization', () => { beforeEach(() => { initializeComponent() }) - it('should create', () => { + it('should initialize component', () => { expect(component).toBeTruthy() }) it('should populate form with theme data in edit changeMode', () => { - const themeData = { - id: 'id', - description: 'desc', - logoUrl: 'logo_url', - faviconUrl: 'fav_url', - name: 'themeName', - properties: { - font: { 'font-family': 'myFont' }, - general: { 'primary-color': 'rgb(0,0,0)' } - } - } - const themeResponse = { resource: themeData } + const themeResponse = { resource: validTheme } themeApiSpy.getThemeByName.and.returnValue(of(themeResponse) as any) component.changeMode = 'EDIT' component.themeName = 'themeName' component.ngOnInit() - expect(component.theme).toBe(themeData) + expect(component.theme).toBe(validTheme) expect(themeApiSpy.getThemeByName).toHaveBeenCalledOnceWith({ name: 'themeName' }) - expect(component.basicForm.controls['name'].value).toBe(themeData.name) - expect(component.basicForm.controls['description'].value).toBe(themeData.description) - expect(component.basicForm.controls['logoUrl'].value).toBe(themeData.logoUrl) - expect(component.basicForm.controls['faviconUrl'].value).toBe(themeData.faviconUrl) + expect(component.basicForm.controls['name'].value).toBe(validTheme.name) + expect(component.basicForm.controls['description'].value).toBe(validTheme.description) + expect(component.basicForm.controls['logoUrl'].value).toBe(validTheme.logoUrl) + expect(component.basicForm.controls['faviconUrl'].value).toBe(validTheme.faviconUrl) expect(component.fontForm.controls['font-family'].value).toBe('myFont') expect(component.generalForm.controls['primary-color'].value).toBe('rgb(0,0,0)') expect(component.themeId).toBe('id') @@ -305,8 +306,8 @@ describe('ThemeDesignerComponent', () => { component.ngOnInit() expect(component.themeTemplates).toEqual([ - { label: 'myTheme', value: 'id2' }, - { label: 'theme1', value: 'id1' } + { label: 'themeDisplay1', value: 'id1' }, + { label: 'themeDisplay2', value: 'id2' } ]) }) @@ -324,15 +325,19 @@ describe('ThemeDesignerComponent', () => { }) }) - it('should display error when updating theme with invalid form', (done: DoneFn) => { + it('should display error when updating theme with invalid basic form', () => { + spyOnProperty(component.basicForm, 'invalid').and.returnValue(true) + + component.onSaveTheme() + + expect(msgServiceSpy.error).toHaveBeenCalledOnceWith({ summaryKey: 'VALIDATION.ERRORS.FORM_INVALID' }) + }) + it('should display error when updating theme with invalid property form', () => { spyOnProperty(component.propertiesForm, 'invalid').and.returnValue(true) - component.actions$?.subscribe((actions) => { - const updateThemeAction = actions[1] - updateThemeAction.actionCallback() - expect(msgServiceSpy.error).toHaveBeenCalledOnceWith({ summaryKey: 'VALIDATION.ERRORS.FORM_INVALID' }) - done() - }) + component.onSaveTheme() + + expect(msgServiceSpy.error).toHaveBeenCalledOnceWith({ summaryKey: 'VALIDATION.ERRORS.FORM_INVALID' }) }) it('should display error when theme data are not ready', (done: DoneFn) => { @@ -356,22 +361,12 @@ describe('ThemeDesignerComponent', () => { }) // on save - xit('should only update properties and base theme data and show success when updating theme call is successful', (done: DoneFn) => { + it('should only update properties and base theme data and show success when updating theme call is successful', () => { component.themeId = 'id' - const themeData = { - id: 'id', - description: 'desc', - logoUrl: 'logo_url', - faviconUrl: 'fav_url', - name: 'themeName', - properties: { - font: { 'font-family': 'myFont' }, - general: { 'primary-color': 'rgb(0,0,0)' } - } - } - const themeResponse = { resource: themeData } + const themeResponse = { resource: validTheme } themeApiSpy.getThemeByName.and.returnValue(of(themeResponse) as any) + // updating forms with different data component.fontForm.patchValue({ 'font-family': 'updatedFont' }) component.generalForm.patchValue({ 'primary-color': 'rgb(255,255,255)' }) const newBasicData = { @@ -387,55 +382,55 @@ describe('ThemeDesignerComponent', () => { themeApiSpy.updateTheme.and.returnValue(of({}) as any) - component.actions$?.subscribe((actions) => { - const updateThemeAction = actions[2] - updateThemeAction.actionCallback() - expect(msgServiceSpy.success).toHaveBeenCalledOnceWith({ summaryKey: 'ACTIONS.EDIT.MESSAGE.CHANGE_OK' }) - expect(themeApiSpy.updateTheme).toHaveBeenCalledTimes(1) - const updateArgs = themeApiSpy.updateTheme.calls.mostRecent().args[0] - expect(updateArgs.updateThemeRequest?.resource.name).toBe(newBasicData.name) - expect(updateArgs.updateThemeRequest?.resource.description).toBe(newBasicData.description) - expect(updateArgs.updateThemeRequest?.resource.logoUrl).toBe(newBasicData.logoUrl) - expect(updateArgs.updateThemeRequest?.resource.faviconUrl).toBe(newBasicData.faviconUrl) - expect(updateArgs.updateThemeRequest?.resource.properties).toEqual( - jasmine.objectContaining({ - font: jasmine.objectContaining({ 'font-family': 'updatedFont' }), - general: jasmine.objectContaining({ 'primary-color': 'rgb(255,255,255)' }) - }) - ) - done() - }) + component.changeMode = 'EDIT' + component.onSaveTheme() + + expect(msgServiceSpy.success).toHaveBeenCalledOnceWith({ summaryKey: 'ACTIONS.EDIT.MESSAGE.CHANGE_OK' }) + expect(themeApiSpy.updateTheme).toHaveBeenCalledTimes(1) + const updateArgs = themeApiSpy.updateTheme.calls.mostRecent().args[0] + expect(updateArgs.updateThemeRequest?.resource.name).toBe(newBasicData.name) + expect(updateArgs.updateThemeRequest?.resource.description).toBe(newBasicData.description) + expect(updateArgs.updateThemeRequest?.resource.logoUrl).toBe(newBasicData.logoUrl) + expect(updateArgs.updateThemeRequest?.resource.faviconUrl).toBe(newBasicData.faviconUrl) + expect(updateArgs.updateThemeRequest?.resource.properties).toEqual( + jasmine.objectContaining({ + font: jasmine.objectContaining({ 'font-family': 'updatedFont' }), + general: jasmine.objectContaining({ 'primary-color': 'rgb(255,255,255)' }) + }) + ) }) - xit('should apply changes when updating current theme is successful', (done: DoneFn) => { + it('should apply changes when updating current theme is successful', (done: DoneFn) => { component.themeId = 'id' - const themeData = { - id: 'id', - description: 'desc', - logoUrl: 'logo_url', - faviconUrl: 'fav_url', - name: 'themeName', - properties: { - font: { 'font-family': 'myFont' }, - general: { 'primary-color': 'rgb(0,0,0)' } - } - } - const themeResponse = { resource: themeData } + const themeResponse = { resource: validTheme } themeApiSpy.getThemeByName.and.returnValue(of(themeResponse) as any) - const updateThemeData = { resource: { id: 'updatedCallId' } } + + component.fontForm.patchValue({ 'font-family': 'updatedFont' }) + component.generalForm.patchValue({ 'primary-color': 'rgb(255,255,255)' }) + const newBasicData = { + name: 'updatedName', + displayName: 'updatedDisplayName', + description: 'updatedDesc', + logoUrl: 'updated_logo_url', + faviconUrl: 'updated_favicon_url' + } + component.basicForm.patchValue(newBasicData) + + const updateThemeData = { resource: validTheme } themeApiSpy.updateTheme.and.returnValue(of(updateThemeData) as any) - component.themeIsCurrentUsedTheme = true + component.autoApply = true + component.changeMode = 'EDIT' component.actions$?.subscribe((actions) => { - const updateThemeAction = actions[2] - updateThemeAction.actionCallback() + const saveThemeAction = actions[1] + saveThemeAction.actionCallback() expect(themeServiceSpy.apply).toHaveBeenCalledOnceWith(updateThemeData as any) done() }) }) - it('should display theme already exists message on theme save failure', () => { + it('should display theme already exists message on theme save as failure', () => { themeApiSpy.createTheme.and.returnValue( throwError(() => new HttpErrorResponse({ error: { errorCode: 'PERSIST_ENTITY_FAILED' } })) ) @@ -448,7 +443,7 @@ describe('ThemeDesignerComponent', () => { }) }) - it('should display error message on theme save failure', () => { + it('should display error message on theme save failure on creation', () => { const responseError = 'Error message' themeApiSpy.createTheme.and.returnValue(throwError(() => new HttpErrorResponse({ error: responseError }))) @@ -460,6 +455,25 @@ describe('ThemeDesignerComponent', () => { }) }) + it('should display error message on theme updating', () => { + component.themeId = validTheme.id + component.themeName = validTheme.name + const themeResponse = { resource: validTheme } + themeApiSpy.getThemeByName.and.returnValue(of(themeResponse) as any) + themeApiSpy.updateTheme.and.returnValue(throwError(() => new HttpErrorResponse({}))) + + component.changeMode = 'EDIT' + component.basicForm.patchValue(validTheme) + component.fontForm.patchValue(validTheme.properties.font) + component.generalForm.patchValue(validTheme.properties.general) + + component.onSaveTheme() + + expect(component.basicForm.valid).toBeTrue() + expect(component.propertiesForm.valid).toBeTrue() + expect(msgServiceSpy.error).toHaveBeenCalledOnceWith({ summaryKey: 'ACTIONS.EDIT.MESSAGE.CHANGE_NOK' }) + }) + it('should display success message and route correctly in edit changeMode', () => { const router = TestBed.inject(Router) spyOn(router, 'navigate') @@ -476,8 +490,8 @@ describe('ThemeDesignerComponent', () => { component.fontForm.patchValue({ 'font-family': 'newFont' }) component.generalForm.patchValue({ 'primary-color': 'rgb(255,255,255)' }) themeApiSpy.createTheme.and.returnValue(of({ resource: { name: 'myTheme' } }) as any) - component.changeMode = 'EDIT' + component.changeMode = 'EDIT' component.saveAsTheme('myTheme', 'myDisplayName') const createArgs = themeApiSpy.createTheme.calls.mostRecent().args[0] @@ -504,6 +518,34 @@ describe('ThemeDesignerComponent', () => { ) }) + it('should display success message in create changeMode', () => { + const router = TestBed.inject(Router) + spyOn(router, 'navigate') + + component.basicForm.patchValue(validTheme) + component.fontForm.patchValue(validTheme.properties.font) + component.generalForm.patchValue(validTheme.properties.general) + themeApiSpy.createTheme.and.returnValue(of({ resource: { name: 'myTheme' } }) as any) + + component.changeMode = 'CREATE' + component.onSaveTheme() + + const createArgs = themeApiSpy.createTheme.calls.mostRecent().args[0] + expect(createArgs.createThemeRequest?.resource).toEqual( + jasmine.objectContaining({ + name: validTheme.name, + displayName: validTheme.displayName, + description: validTheme.description, + logoUrl: validTheme.logoUrl, + faviconUrl: validTheme.faviconUrl, + properties: jasmine.objectContaining({ + font: jasmine.objectContaining(validTheme.properties.font), + general: jasmine.objectContaining(validTheme.properties.general) + }) + }) + ) + }) + it('should set faviconUrl and logoUrl to undefined if they already exist', () => { const router = TestBed.inject(Router) spyOn(router, 'navigate') @@ -560,7 +602,7 @@ describe('ThemeDesignerComponent', () => { ) }) - it('should display success message and route correctly in new changeMode', () => { + it('should display success message and route correctly in create changeMode', () => { const router = TestBed.inject(Router) spyOn(router, 'navigate') @@ -576,7 +618,7 @@ describe('ThemeDesignerComponent', () => { component.fontForm.patchValue({ 'font-family': 'newFont' }) component.generalForm.patchValue({ 'primary-color': 'rgb(255,255,255)' }) themeApiSpy.createTheme.and.returnValue(of({ resource: { name: 'myTheme' } }) as any) - component.changeMode = 'NEW' + component.changeMode = 'CREATE' component.saveAsTheme('myTheme', 'myDisplayName') @@ -595,29 +637,27 @@ describe('ThemeDesignerComponent', () => { }) }) - it('should use form theme name in save as dialog while in NEW changeMode', () => { + it('should use form theme name in save as dialog while in create changeMode', () => { component.saveAsThemeName = jasmine.createSpyObj('ElementRef', [], { nativeElement: { value: '' } }) component.saveAsThemeDisplayName = jasmine.createSpyObj('ElementRef', [], { nativeElement: { value: '' } }) component.basicForm.controls['name'].setValue('newThemeName') component.basicForm.controls['displayName'].setValue('newThemeDisplayName') - component.changeMode = 'NEW' + component.changeMode = 'CREATE' component.onShowSaveAsDialog() - expect(component.saveAsThemeName?.nativeElement.value).toBe('Copy of newThemeName') - expect(component.saveAsThemeDisplayName?.nativeElement.value).toBe('Copy of newThemeDisplayName') + expect(component.saveAsThemeName?.nativeElement.value).toBe(component.copyOfPrefix + 'newThemeName') + expect(component.saveAsThemeDisplayName?.nativeElement.value).toBe(component.copyOfPrefix + 'newThemeDisplayName') }) it('should use COPY_OF + form theme name in save as dialog while in EDIT changeMode', () => { - const translateService = TestBed.inject(TranslateService) - spyOn(translateService, 'instant').and.returnValue('copy_of: ') component.saveAsThemeName = jasmine.createSpyObj('ElementRef', [], { nativeElement: { value: '' } }) component.basicForm.controls['name'].setValue('newThemeName') component.changeMode = 'EDIT' component.onShowSaveAsDialog() - expect(component.saveAsThemeName?.nativeElement.value).toBe('copy_of: newThemeName') + expect(component.saveAsThemeName?.nativeElement.value).toBe(component.copyOfPrefix + 'newThemeName') }) it('should not upload a file if currThemeName is empty', () => { @@ -830,10 +870,8 @@ describe('ThemeDesignerComponent', () => { { label: 'myTheme', value: 'id2' } ] - component.themeTemplateSelectedId = 'id2' - const translationData = { - 'GENERAL.COPY_OF': 'generalCopyOf', + 'GENERAL.COPY_OF': 'Copy of ', 'THEME.TEMPLATE.CONFIRMATION.HEADER': 'themeTemplateConfirmationHeader', 'THEME.TEMPLATE.CONFIRMATION.MESSAGE': '{{ITEM}} themeTemplateConfirmationMessage', 'ACTIONS.CONFIRMATION.YES': 'actionsConfirmationYes', @@ -844,7 +882,7 @@ describe('ThemeDesignerComponent', () => { const confirmdialog: ConfirmDialog = fixture.debugElement.query(By.css('p-confirmdialog')).componentInstance - component.onThemeTemplateDropdownChange() + component.onThemeTemplateDropdownChange({ value: 'id2' }) fixture.detectChanges() expect(confirmdialog.confirmation).toEqual( @@ -863,12 +901,10 @@ describe('ThemeDesignerComponent', () => { { label: 'myTheme', value: 'id2' } ] - component.themeTemplateSelectedId = 'id2' - const confirmdialog: ConfirmDialog = fixture.debugElement.query(By.css('p-confirmdialog')).componentInstance const reject = spyOn(confirmdialog, 'reject').and.callThrough() - component.onThemeTemplateDropdownChange() + component.onThemeTemplateDropdownChange({ value: 'id2' }) fixture.detectChanges() component = fixture.componentInstance @@ -876,7 +912,6 @@ describe('ThemeDesignerComponent', () => { cancelBtn.click() expect(reject).toHaveBeenCalled() - expect(component.themeTemplateSelectedId).toBe('') }) it('should populate only properties with template data on confirmation accept and EDIT changeMode', () => { @@ -885,10 +920,10 @@ describe('ThemeDesignerComponent', () => { { label: 'myTheme', value: 'id2' } ] - component.themeTemplateSelectedId = 'id2' + component.onThemeTemplateDropdownChange({ value: 'id2' }) const translationData = { - 'GENERAL.COPY_OF': 'generalCopyOf', + 'GENERAL.COPY_OF': 'Copy of ', 'THEME.TEMPLATE.CONFIRMATION.HEADER': 'themeTemplateConfirmationHeader', 'THEME.TEMPLATE.CONFIRMATION.MESSAGE': '{{ITEM}} themeTemplateConfirmationMessage', 'ACTIONS.CONFIRMATION.YES': 'actionsConfirmationYes', @@ -927,7 +962,7 @@ describe('ThemeDesignerComponent', () => { const confirmdialog: ConfirmDialog = fixture.debugElement.query(By.css('p-confirmdialog')).componentInstance const accept = spyOn(confirmdialog, 'accept').and.callThrough() - component.onThemeTemplateDropdownChange() + component.onThemeTemplateDropdownChange({ value: 'id2' }) fixture.detectChanges() const acceptBtn = fixture.debugElement.nativeElement.querySelector('.p-confirm-dialog-accept') @@ -950,10 +985,9 @@ describe('ThemeDesignerComponent', () => { { label: 'theme1', value: 'id1' }, { label: 'myTheme', value: 'id2' } ] - component.themeTemplateSelectedId = 'id2' const translationData = { - 'GENERAL.COPY_OF': 'generalCopyOf: ', + 'GENERAL.COPY_OF': 'Copy of ', 'THEME.TEMPLATE.CONFIRMATION.HEADER': 'themeTemplateConfirmationHeader', 'THEME.TEMPLATE.CONFIRMATION.MESSAGE': '{{ITEM}} themeTemplateConfirmationMessage', 'ACTIONS.CONFIRMATION.YES': 'actionsConfirmationYes', @@ -962,7 +996,7 @@ describe('ThemeDesignerComponent', () => { const translateService = TestBed.inject(TranslateService) spyOn(translateService, 'get').and.returnValue(of(translationData)) - component.changeMode = 'NEW' + component.changeMode = 'CREATE' const basicFormBeforeFetch = { name: 'n', displayName: 'n', @@ -979,12 +1013,8 @@ describe('ThemeDesignerComponent', () => { faviconUrl: 'fetchedFavUrl', logoUrl: 'fetchedLogoUrl', properties: { - font: { - 'font-family': 'fetchedFont' - }, - general: { - 'primary-color': 'rgb(255,255,255)' - } + font: { 'font-family': 'fetchedFont' }, + general: { 'primary-color': 'rgb(255,255,255)' } } } const fetchedThemeResponse = { @@ -995,7 +1025,7 @@ describe('ThemeDesignerComponent', () => { const confirmdialog: ConfirmDialog = fixture.debugElement.query(By.css('p-confirmdialog')).componentInstance const accept = spyOn(confirmdialog, 'accept').and.callThrough() - component.onThemeTemplateDropdownChange() + component.onThemeTemplateDropdownChange({ value: 'id2' }) fixture.detectChanges() const acceptBtn = fixture.debugElement.nativeElement.querySelector('.p-confirm-dialog-accept') @@ -1003,7 +1033,7 @@ describe('ThemeDesignerComponent', () => { expect(accept).toHaveBeenCalled() expect(component.basicForm.value).toEqual({ - name: 'generalCopyOf: fetchedName', + name: 'Copy of fetchedName', displayName: 'fetchedNamedisplay', description: 'fetchedDesc', faviconUrl: 'fetchedFavUrl', @@ -1033,36 +1063,4 @@ describe('ThemeDesignerComponent', () => { expect(component.getImageUrl(theme, RefType.Logo)).toBe(theme.logoUrl) expect(component.getImageUrl(theme, RefType.Favicon)).toBe(theme.faviconUrl) }) - - it('should get display name', () => { - const themeData = { - id: 'id', - description: 'desc', - logoUrl: 'logo_url', - faviconUrl: 'fav_url', - name: 'themeName', - properties: { - font: { - 'font-family': 'myFont' - }, - general: { - 'primary-color': 'rgb(0,0,0)' - } - } - } - const themeResponse = { - resource: themeData - } - themeApiSpy.getThemeByName.and.returnValue(of(themeResponse) as any) - component.changeMode = 'EDIT' - component.themeName = 'themeName' - - component.ngOnInit() - - expect(component.theme).toBe(themeData) - expect(themeApiSpy.getThemeByName).toHaveBeenCalledOnceWith({ name: 'themeName' }) - const res = component.getDisplayName() - - expect(res).toBe('themeName') - }) }) diff --git a/src/app/theme/theme-designer/theme-designer.component.ts b/src/app/theme/theme-designer/theme-designer.component.ts index 21bf524..e57796f 100644 --- a/src/app/theme/theme-designer/theme-designer.component.ts +++ b/src/app/theme/theme-designer/theme-designer.component.ts @@ -36,17 +36,16 @@ export class ThemeDesignerComponent implements OnInit { public theme: Theme | undefined public themeId: string | undefined public themeName: string | null + public copyOfPrefix: string | undefined public themeVars = themeVariables public themeTemplates!: SelectItem[] - public themeTemplateSelectedId = '' - public themeIsCurrentUsedTheme = false - public bffImagePath = '' + public bffImagePath: string | undefined public fetchingLogoUrl?: string public fetchingFaviconUrl?: string public imageLogoUrlExists = false public imageFaviconUrlExists = false - public changeMode: 'EDIT' | 'NEW' = 'NEW' + public changeMode: 'EDIT' | 'CREATE' = 'CREATE' public autoApply = false public saveAsNewPopupDisplay = false public displayFileTypeErrorLogo = false @@ -75,9 +74,9 @@ export class ThemeDesignerComponent implements OnInit { private readonly confirmation: ConfirmationService, private readonly msgService: PortalMessageService ) { - this.changeMode = route.snapshot.paramMap.has('name') ? 'EDIT' : 'NEW' + this.changeMode = route.snapshot.paramMap.has('name') ? 'EDIT' : 'CREATE' this.themeName = route.snapshot.paramMap.get('name') - this.bffImagePath = this.imageApi.configuration.basePath! + this.bffImagePath = this.imageApi.configuration.basePath this.prepareActionButtons() this.fontForm = new FormGroup({}) @@ -148,13 +147,12 @@ export class ThemeDesignerComponent implements OnInit { this.themeApi.getThemeByName({ name: this.themeName }) ]).subscribe(([currentTheme, data]) => { this.theme = data.resource + this.themeId = this.theme.id this.basicForm.patchValue(this.theme) this.basicForm.controls['name'].disable() this.propertiesForm.reset() this.propertiesForm.patchValue(this.theme.properties ?? {}) - this.themeId = this.theme.id - this.themeIsCurrentUsedTheme = this.theme.name === currentTheme.name - this.autoApply = this.themeIsCurrentUsedTheme + this.autoApply = this.theme.name === currentTheme.name // images this.fetchingLogoUrl = this.getImageUrl(this.theme, RefType.Logo) this.fetchingFaviconUrl = this.getImageUrl(this.theme, RefType.Favicon) @@ -178,6 +176,7 @@ export class ThemeDesignerComponent implements OnInit { private prepareActionButtons(): void { this.actions$ = this.translate .get([ + 'GENERAL.COPY_OF', 'ACTIONS.CANCEL', 'ACTIONS.TOOLTIPS.CANCEL_AND_CLOSE', 'ACTIONS.SAVE', @@ -187,11 +186,12 @@ export class ThemeDesignerComponent implements OnInit { ]) .pipe( map((data) => { + this.copyOfPrefix = data['GENERAL.COPY_OF'] return [ { label: data['ACTIONS.CANCEL'], title: data['ACTIONS.TOOLTIPS.CANCEL_AND_CLOSE'], - actionCallback: () => this.close(), + actionCallback: () => this.onClose(), icon: 'pi pi-times', show: 'always', permission: 'THEME#VIEW' @@ -203,7 +203,7 @@ export class ThemeDesignerComponent implements OnInit { icon: 'pi pi-save', show: 'always', conditional: true, - showCondition: this.changeMode === 'EDIT' || this.changeMode === 'NEW', + showCondition: this.changeMode === 'EDIT' || this.changeMode === 'CREATE', permission: this.changeMode === 'EDIT' ? 'THEME#EDIT' : 'THEME#CREATE' }, { @@ -228,7 +228,7 @@ export class ThemeDesignerComponent implements OnInit { this.themeTemplates = [ ...data.stream .map((theme) => ({ - label: theme.name, + label: theme.displayName, value: theme.id })) .sort(dropDownSortItemsByLabel) @@ -237,12 +237,12 @@ export class ThemeDesignerComponent implements OnInit { }) } - public onThemeTemplateDropdownChange(): void { - const themeName = dropDownGetLabelByValue(this.themeTemplates, this.themeTemplateSelectedId) - this.confirmTemplateTheme(themeName) + public onThemeTemplateDropdownChange(ev: any): void { + const themeName = dropDownGetLabelByValue(this.themeTemplates, ev.value) + this.confirmTemplateTheme(themeName, ev.value) } - private confirmTemplateTheme(themeName: string) { + private confirmTemplateTheme(name: string, id: string) { this.translate .get([ 'GENERAL.COPY_OF', @@ -253,37 +253,29 @@ export class ThemeDesignerComponent implements OnInit { ]) .pipe( map((data) => { - this.confirmUseThemeAsTemplate( - themeName, - data, - () => { - this.getThemeById(this.themeTemplateSelectedId).subscribe((result) => { - if (this.changeMode === 'NEW') { - this.basicForm.controls['name'].setValue(data['GENERAL.COPY_OF'] + result.resource.name) - this.basicForm.controls['displayName'].setValue(result.resource.displayName) - this.basicForm.controls['description'].setValue(result.resource.description) - this.basicForm.controls['logoUrl'].setValue(result.resource.logoUrl) - this.basicForm.controls['faviconUrl'].setValue(result.resource.faviconUrl) - this.fetchingLogoUrl = this.getImageUrl(result.resource, RefType.Logo) - this.fetchingFaviconUrl = this.getImageUrl(result.resource, RefType.Favicon) - } - if (result.resource.properties) { - this.propertiesForm.reset() - this.propertiesForm.patchValue(result.resource.properties) - } - }) - }, - () => { - // on reject - this.themeTemplateSelectedId = '' - } - ) + this.confirmUseThemeAsTemplate(name, data, () => { + this.getThemeById(id).subscribe((result) => { + if (this.changeMode === 'CREATE') { + this.basicForm.controls['name'].setValue(data['GENERAL.COPY_OF'] + result.resource.name) + this.basicForm.controls['displayName'].setValue(result.resource.displayName) + this.basicForm.controls['description'].setValue(result.resource.description) + this.basicForm.controls['logoUrl'].setValue(result.resource.logoUrl) + this.basicForm.controls['faviconUrl'].setValue(result.resource.faviconUrl) + this.fetchingLogoUrl = this.getImageUrl(result.resource, RefType.Logo) + this.fetchingFaviconUrl = this.getImageUrl(result.resource, RefType.Favicon) + } + if (result.resource.properties) { + this.propertiesForm.reset() + this.propertiesForm.patchValue(result.resource.properties) + } + }) + }) }) ) .subscribe() } - private confirmUseThemeAsTemplate(themeName: string, data: any, onConfirm: () => void, onReject: () => void): void { + private confirmUseThemeAsTemplate(themeName: string, data: any, onConfirm: () => void): void { this.confirmation.confirm({ icon: 'pi pi-question-circle', defaultFocus: 'reject', @@ -292,12 +284,11 @@ export class ThemeDesignerComponent implements OnInit { message: data['THEME.TEMPLATE.CONFIRMATION.MESSAGE'].replace('{{ITEM}}', themeName), acceptLabel: data['ACTIONS.CONFIRMATION.YES'], rejectLabel: data['ACTIONS.CONFIRMATION.NO'], - accept: () => onConfirm(), - reject: () => onReject() + accept: () => onConfirm() }) } - private close(): void { + private onClose(): void { this.router.navigate(['./..'], { relativeTo: this.route }) } @@ -308,7 +299,7 @@ export class ThemeDesignerComponent implements OnInit { } const newTheme: ThemeUpdateCreate = { ...this.basicForm.value } newTheme.properties = this.propertiesForm.value - if (this.changeMode === 'NEW') this.createTheme(newTheme) + if (this.changeMode === 'CREATE') this.createTheme(newTheme) if (this.changeMode === 'EDIT') this.updateTheme() } @@ -327,24 +318,14 @@ export class ThemeDesignerComponent implements OnInit { this.saveAsNewPopupDisplay = true } public onShowSaveAsDialog(): void { - const basicFormName = this.basicForm.controls['name'].value ?? '' - const basicFormDisplayName = this.basicForm.controls['displayName'].value ?? '' - const translatedCopyOf = this.translate.instant('GENERAL.COPY_OF') - this.updateSaveAsElement(this.saveAsThemeName, translatedCopyOf + basicFormName) - this.updateSaveAsElement(this.saveAsThemeDisplayName, translatedCopyOf + basicFormDisplayName) - } - private updateSaveAsElement(saveAsElement: ElementRef | undefined, newValue: string): void { - if (saveAsElement) { - saveAsElement.nativeElement.value = newValue - } + const basicFormName = this.basicForm.controls['name'].value + const basicFormDisplayName = this.basicForm.controls['displayName'].value + this.saveAsThemeName!.nativeElement.value = this.copyOfPrefix + basicFormName + this.saveAsThemeDisplayName!.nativeElement.value = this.copyOfPrefix + basicFormDisplayName } // EDIT private updateTheme(): void { - if (this.propertiesForm.invalid) { - this.msgService.error({ summaryKey: 'ACTIONS.EDIT.MESSAGE.CHANGE_NOK' }) - return - } this.themeApi .getThemeByName({ name: this.themeName! }) .pipe( @@ -372,7 +353,7 @@ export class ThemeDesignerComponent implements OnInit { next: (data: UpdateThemeResponse) => { this.msgService.success({ summaryKey: 'ACTIONS.EDIT.MESSAGE.CHANGE_OK' }) // apply theme changes immediately if it is the theme of the current portal - if (this.themeIsCurrentUsedTheme) { + if (this.autoApply) { this.themeService.apply(data as object) } }, @@ -382,7 +363,7 @@ export class ThemeDesignerComponent implements OnInit { }) } - // NEW + // CREATE private createTheme(theme: ThemeUpdateCreate): void { this.themeApi.createTheme({ createThemeRequest: { resource: theme } }).subscribe({ next: (data) => { @@ -410,10 +391,6 @@ export class ThemeDesignerComponent implements OnInit { return this.themeApi.getThemeById({ id: id }) } - getDisplayName() { - return this.basicForm.controls['name'].value - } - // Applying Styles private updateCssVar(varName: string, value: string): void { document.documentElement.style.setProperty(`--${varName}`, value) diff --git a/src/app/theme/theme-detail/theme-detail.component.html b/src/app/theme/theme-detail/theme-detail.component.html index 5f688f0..b97f854 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 @@ -
- -
-
+ -
- - + + + - -
-            {{ theme?.properties | json }}
-          
-
+
{{ theme?.properties | json }}
+ { @@ -25,18 +27,11 @@ describe('ThemeDetailComponent', () => { let fixture: ComponentFixture let translateService: TranslateService - const mockUserService = { - lang$: { - getValue: jasmine.createSpy('getValue').and.returnValue('en') - } - } + const mockUserService = { lang$: { getValue: jasmine.createSpy('getValue').and.returnValue('en') } } const msgServiceSpy = jasmine.createSpyObj('PortalMessageService', ['success', 'error']) const locationSpy = jasmine.createSpyObj('Location', ['back']) - const configServiceSpy = { - getProperty: jasmine.createSpy('getProperty').and.returnValue('123'), - lang: 'en' - } + const configServiceSpy = { getProperty: jasmine.createSpy('getProperty').and.returnValue('123'), lang: 'en' } const themesApiSpy = jasmine.createSpyObj('ThemesAPIService', [ 'getThemeByName', 'deleteTheme', @@ -92,9 +87,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,15 +97,21 @@ 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() + component.actions$!.subscribe((actions) => { + expect(actions.length).toBe(4) + if (actions.length > 0) { + expect(actions[3].showCondition).toBeTrue() + } + }) }) }) @@ -140,31 +140,31 @@ describe('ThemeDetailComponent', () => { it('should load theme and action translations on successful call', async () => { spyOn(component, 'onClose') spyOn(component, 'onExportTheme') + component.themeName = 'dummy' component.themeDeleteVisible = false - let actions: any = [] - component.actions$!.subscribe((act) => (actions = act)) - - actions[0].actionCallback() - actions[1].actionCallback() - actions[2].actionCallback() - actions[3].actionCallback() - - expect(actions.length).toBe(4) - expect(component.onClose).toHaveBeenCalled() - expect(component.onExportTheme).toHaveBeenCalled() - expect(component.themeDeleteVisible).toBeTrue() + component.actions$!.subscribe((actions) => { + expect(actions.length).toBe(4) + if (actions.length > 0) { + actions[0].actionCallback() + actions[1].actionCallback() + actions[2].actionCallback() + actions[3].actionCallback() + expect(component.onClose).toHaveBeenCalled() + expect(component.onExportTheme).toHaveBeenCalled() + expect(component.themeDeleteVisible).toBeTrue() + } + }) }) - it('should load prepare object details on successfull call', async () => { + it('should load prepare translations on successfull call', async () => { const themeResponse = { resource: { name: 'themeName', displayName: 'Theme', creationDate: 'myCreDate', modificationDate: 'myModDate' - }, - workspaces: [{ name: 'workspace1' }, { name: 'workspace2' }] + } } themesApiSpy.getThemeByName.and.returnValue(of(themeResponse) as any) @@ -181,51 +181,42 @@ describe('ThemeDetailComponent', () => { 'ACTIONS.DELETE.THEME_MESSAGE': '{{ITEM}} actionDeleteThemeMessage' } const generalTranslations = { - 'DETAIL.CREATION_DATE': 'detailCreationDate', - 'DETAIL.TOOLTIPS.CREATION_DATE': 'detailTooltipsCreationDate', - 'DETAIL.MODIFICATION_DATE': 'detailModificationDate', - 'DETAIL.TOOLTIPS.MODIFICATION_DATE': 'detailTooltipsModificationDate', - 'THEME.WORKSPACES': 'themeWorkspaces', - 'THEME.TOOLTIPS.WORKSPACES': 'themeTooltipsWorkspaces' + 'INTERNAL.CREATION_DATE': 'detailCreationDate', + 'INTERNAL.TOOLTIPS.CREATION_DATE': 'detailTooltipsCreationDate', + 'INTERNAL.MODIFICATION_DATE': 'detailModificationDate', + 'INTERNAL.TOOLTIPS.MODIFICATION_DATE': 'detailTooltipsModificationDate' } spyOn(translateService, 'get').and.returnValues(of(actionsTranslations), of(generalTranslations)) await component.ngOnInit() }) - it('should display not found error', () => { - themesApiSpy.getThemeByName.and.returnValue( - throwError( - () => - new HttpErrorResponse({ - status: 404 - }) - ) - ) - component.exceptionKey = '' - + it('should display not found error and limited header actions', () => { + themesApiSpy.getThemeByName.and.returnValue(throwError(() => new HttpErrorResponse({ status: 404 }))) + component.exceptionKey = undefined + component.themeName = 'dummy' component.ngOnInit() + component.actions$!.subscribe((actions) => { + expect(actions.length).toBe(4) + if (actions.length > 0) { + expect(actions[3].showCondition).toBeFalse() // hide delete action + } + }) + 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') }) }) @@ -241,8 +232,7 @@ describe('ThemeDetailComponent', () => { resource: { name: 'themeName', logoUrl: 'logo123.png' - }, - workspaces: [] + } } themesApiSpy.getThemeByName.and.returnValue(of(themeResponse) as any) @@ -259,8 +249,7 @@ describe('ThemeDetailComponent', () => { resource: { name: 'themeName', logoUrl: url - }, - workspaces: [] + } } themesApiSpy.getThemeByName.and.returnValue(of(themeResponse) as any) await component.ngOnInit() @@ -358,10 +347,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..560bb80 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 | null 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 actions$: Observable = of([]) 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.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.prepareActionButtons() + if (!this.themeName) return 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) + this.prepareActionButtons() 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 - }) + finalize(() => (this.loading = false)) ) } - 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.error(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,15 +121,13 @@ 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: { - logoUrl: 'logo_url' + displayName: 'Theme-1', + logoUrl: 'logo_url', + properties: {} } } }) @@ -146,7 +139,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 +172,7 @@ describe('ThemeImportComponent', () => { themeApiSpy.importThemes.and.returnValue( of( new HttpResponse({ - body: { id: 'id' } + body: { id: 'id', name: 'themeName', displayName: 'themeDisplayName' } }) ) ) @@ -189,6 +182,9 @@ describe('ThemeImportComponent', () => { created: 'created', themes: { ['theme']: { description: 'themeDescription' } } } + component.themeName = 'themeName' + component.displayName = 'themeDisplayName' + component.properties = {} component.onThemeUpload() @@ -197,15 +193,24 @@ 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.properties = {} + component.onThemeUpload() + + expect(component.themeNameExists).toBe(false) + expect(component.displayNameExists).toBe(false) + expect(component.uploadEmitter.emit).not.toHaveBeenCalled() + }) + + it('should prevent upload if form is not ready', () => { + themeApiSpy.importThemes.and.returnValue(of(new HttpResponse({ body: { id: 'id' } }))) spyOn(component.uploadEmitter, 'emit') + component.themeName = 'themeName' component.onThemeUpload() expect(component.uploadEmitter.emit).not.toHaveBeenCalled() @@ -219,6 +224,9 @@ describe('ThemeImportComponent', () => { themes: { ['theme']: { description: 'themeDescription' } } } + component.themeName = 'themeName' + component.displayName = 'themeDisplayName' + component.properties = {} 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..ab40a6d 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,11 @@ 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 + this.themeNameExists = false + this.displayNameExists = false + 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 +101,18 @@ export class ThemeImportComponent implements OnInit { this.themeImportError = false } public onThemeUpload(): void { + if (!this.themeName || !this.displayName || !this.properties) 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..9c98fa2 100644 --- a/src/app/theme/theme-search/theme-search.component.spec.ts +++ b/src/app/theme/theme-search/theme-search.component.spec.ts @@ -5,17 +5,20 @@ import { provideHttpClientTesting } from '@angular/common/http/testing' import { provideRouter, Router } from '@angular/router' import { TranslateService } from '@ngx-translate/core' import { TranslateTestingModule } from 'ngx-translate-testing' +import { of, throwError } from 'rxjs' 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)) @@ -94,6 +106,40 @@ describe('ThemeSearchComponent', () => { expect(component.onImportThemeClick).toHaveBeenCalledTimes(1) }) + it('should search themes without results', (done) => { + themeApiSpy.getThemes.and.returnValue(of({ stream: [] } as GetThemesResponse)) + + component.ngOnInit() + + component.themes$.subscribe({ + next: (result) => { + if (result) { + expect(result.length).toBe(0) + } + done() + }, + error: done.fail + }) + }) + + it('should search themes but display error if API call fails', (done) => { + const err = { status: 403 } + themeApiSpy.getThemes.and.returnValue(throwError(() => err)) + + component.ngOnInit() + + component.themes$.subscribe({ + next: (result) => { + if (result) { + expect(result.length).toBe(0) + expect(component.exceptionKey).toEqual('EXCEPTIONS.HTTP_STATUS_403.THEMES') + } + done() + }, + error: done.fail + }) + }) + it('should get the logo url: theme undefined', () => { const result = component.getLogoUrl(undefined) @@ -153,24 +199,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..75c7f1b 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 + '.THEMES' + 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/api/openapi-bff.yaml b/src/assets/api/openapi-bff.yaml index 71afaa4..d7f3a82 100644 --- a/src/assets/api/openapi-bff.yaml +++ b/src/assets/api/openapi-bff.yaml @@ -1,695 +1,684 @@ --- openapi: 3.0.3 info: - title: onecx-theme-bff - description: OneCx theme Bff - version: '1.0' + title: onecx-theme-bff + description: OneCx theme Bff + version: '1.0' servers: - - url: http://onecx-theme-bff:8080/ + - url: http://onecx-theme-bff:8080/ tags: - - name: theme - - name: imagesInternal + - name: theme + - name: imagesInternal paths: - /themes: - get: - x-onecx: - permissions: - themes: - - read - tags: - - themes - description: Find all themes - operationId: getThemes - parameters: - - $ref: '#/components/parameters/pageNumber' - - $ref: '#/components/parameters/pageSize' - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/GetThemesResponse' - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/ProblemDetailResponse' - '404': - description: Not found - post: - x-onecx: - permissions: - themes: - - write - tags: - - themes - description: Create theme - operationId: createTheme - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/CreateThemeRequest' - responses: - '201': - description: Created - content: - application/json: - schema: - $ref: '#/components/schemas/CreateThemeResponse' - '204': - description: No Content - '400': - description: Bad Request - content: - application/json: - schema: - $ref: '#/components/schemas/ProblemDetailResponse' - '404': - description: Not Found - /themes/{id}: - get: - x-onecx: - permissions: - themes: - - read - tags: - - themes - description: Find theme by id - operationId: getThemeById - parameters: - - $ref: '#/components/parameters/id' - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/GetThemeResponse' - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/ProblemDetailResponse' - '404': - description: Not Found - put: - x-onecx: - permissions: - themes: - - write - tags: - - themes - description: Update theme - operationId: updateTheme - parameters: - - $ref: '#/components/parameters/id' - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/UpdateThemeRequest' - responses: - '200': - description: Ok - content: - application/json: - schema: - $ref: '#/components/schemas/UpdateThemeResponse' - '204': - description: No Content - '400': - description: Bad Request - content: - application/json: - schema: - $ref: '#/components/schemas/ProblemDetailResponse' - '404': - description: Not Found - delete: - x-onecx: - permissions: - themes: - - delete - tags: - - themes - description: Delete theme - operationId: deleteTheme - parameters: - - $ref: '#/components/parameters/id' - responses: - '204': - description: No Content - '400': - description: Bad Request - content: - application/json: - schema: - $ref: '#/components/schemas/ProblemDetailResponse' - /themes/name/{name}: - get: - x-onecx: - permissions: - themes: - - read - tags: - - themes - description: Find theme by name including workspace - operationId: getThemeByName - parameters: - - $ref: '#/components/parameters/name' - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/GetThemeResponse' - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/ProblemDetailResponse' - '404': - description: Not Found - /themes/search: - post: - x-onecx: - permissions: - themes: - - read - tags: - - themes - description: Search themes by criteria - operationId: searchThemes - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/SearchThemeRequest' - responses: - '200': - description: Ok - content: - application/json: - schema: - $ref: '#/components/schemas/SearchThemeResponse' - '204': - description: No Content - '400': - description: Bad Request - content: - application/json: - schema: - $ref: '#/components/schemas/ProblemDetailResponse' - '404': - description: Not Found - /themes/export: - post: - x-onecx: - permissions: - themes: - - read - tags: - - themes - description: Export list of themes - operationId: exportThemes - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/ExportThemeRequest' - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/ThemeSnapshot' - '404': - description: No themes founds - /themes/import: - post: - x-onecx: - permissions: - themes: - - write - tags: - - themes - description: Import themes - operationId: importThemes - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/ThemeSnapshot' - responses: - '200': - description: Import result - content: - application/json: - schema: - $ref: '#/components/schemas/ImportThemeResponse' - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/ProblemDetailResponse' - /images/{refId}/{refType}: - post: - x-onecx: - permissions: - themes: - - write - tags: - - imagesInternal - description: Upload Images - parameters: - - name: refId - in: path - required: true - schema: - type: string - - name: refType - in: path - required: true - schema: - $ref: '#/components/schemas/RefType' - operationId: uploadImage - requestBody: - required: true - content: - image/*: - schema: - type: string - format: binary - responses: - '201': - description: CREATED - content: - application/json: - schema: - $ref: '#/components/schemas/ImageInfo' - '400': - description: Bad Request - get: - x-onecx: - permissions: - themes: - - read - tags: - - imagesInternal - description: Get Image by id - operationId: getImage - parameters: - - name: refId - in: path - required: true - schema: - type: string - - name: refType - in: path - required: true - schema: - $ref: '#/components/schemas/RefType' - responses: - '200': - description: OK - content: - image/*: - schema: - type: string - format: binary - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/ProblemDetailResponse' - put: - x-onecx: - permissions: - themes: - - write - tags: - - imagesInternal - description: update Images - operationId: updateImage - parameters: - - name: refId - in: path - required: true - schema: - type: string - - name: refType - in: path - required: true - schema: - $ref: '#/components/schemas/RefType' - requestBody: - required: true - content: - image/*: - schema: - type: string - format: binary - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/ImageInfo' - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/ProblemDetailResponse' -components: - schemas: - RefType: - type: string - enum: [logo, favicon] - Theme: - type: object - properties: - modificationCount: - format: int32 - type: integer - creationDate: - $ref: '#/components/schemas/OffsetDateTime' - creationUser: - type: string - modificationDate: - $ref: '#/components/schemas/OffsetDateTime' - modificationUser: - type: string - id: - type: string - name: - minLength: 2 - type: string - displayName: - minLength: 2 - type: string - cssFile: - type: string - description: - type: string - assetsUrl: - type: string - logoUrl: - type: string - faviconUrl: - type: string - previewImageUrl: - type: string - assetsUpdateDate: - type: string - properties: - type: object - operator: - type: boolean - ThemeUpdateCreate: - type: object - properties: - modificationCount: - format: int32 - type: integer - creationDate: - $ref: '#/components/schemas/OffsetDateTime' - creationUser: - type: string - modificationDate: - $ref: '#/components/schemas/OffsetDateTime' - modificationUser: - type: string - name: - minLength: 2 - type: string - displayName: - minLength: 2 - type: string - cssFile: - type: string - description: - type: string - assetsUrl: - type: string - logoUrl: - type: string - faviconUrl: - type: string - previewImageUrl: - type: string - assetsUpdateDate: - type: string - properties: - type: object - - GetThemeResponse: - required: - - resource - type: object - properties: - resource: - $ref: '#/components/schemas/Theme' - workspaces: - type: array - items: - $ref: '#/components/schemas/Workspace' - GetThemesResponse: - allOf: # Combines the PagingResponse and the Resources - - $ref: '#/components/schemas/PagingResponse' - - type: object - properties: - stream: - type: array - items: - $ref: '#/components/schemas/Theme' - CreateThemeResponse: - required: - - resource - type: object - properties: - resource: - $ref: '#/components/schemas/Theme' - UpdateThemeResponse: - required: - - resource - type: object - properties: - resource: - $ref: '#/components/schemas/Theme' - OffsetDateTime: - format: date-time - type: string - example: 2022-03-10T12:15:50-04:00 - ProblemDetailResponse: - type: object - properties: - errorCode: - type: string - detail: - type: string - params: - type: array - items: - $ref: '#/components/schemas/ProblemDetailParam' - invalidParams: - type: array - items: - $ref: '#/components/schemas/ProblemDetailInvalidParam' - ProblemDetailParam: - type: object - properties: - key: - type: string - value: - type: string - ProblemDetailInvalidParam: - type: object - properties: - name: - type: string - message: - type: string - CreateThemeRequest: - required: - - resource - type: object - properties: - resource: - $ref: '#/components/schemas/ThemeUpdateCreate' - UpdateThemeRequest: - required: - - resource - type: object - properties: - resource: - $ref: '#/components/schemas/ThemeUpdateCreate' - SearchThemeRequest: - type: object - properties: - name: - type: string - displayName: - type: string - pageNumber: - type: integer - format: int32 - default: 0 - pageSize: - type: integer - format: int32 - default: 100 - maximum: 1000 - SearchThemeResponse: - allOf: - - $ref: '#/components/schemas/PagingResponse' - - type: object - properties: - stream: - type: array - items: - $ref: '#/components/schemas/Theme' - PagingResponse: - type: object - properties: - totalElements: - format: int64 - type: integer - number: - format: int32 - type: integer - size: - format: int32 - type: integer - totalPages: - format: int64 - type: integer - Workspace: - type: object - properties: - name: - type: string - description: - type: string - ExportThemeRequest: - type: object - properties: - names: - type: array - uniqueItems: true - items: - type: string - ThemeSnapshot: - type: object - properties: - id: - type: string - minLength: 10 - description: ID of the request - created: - $ref: '#/components/schemas/OffsetDateTime' - themes: - type: object - nullable: false - additionalProperties: - $ref: '#/components/schemas/EximTheme' - ImportThemeResponse: - type: object - properties: - id: - type: string - minLength: 10 - description: ID of the request - themes: - additionalProperties: - $ref: '#/components/schemas/ImportThemeResponseStatus' - ImportThemeResponseStatus: - type: string - enum: - - UPDATE - - CREATED - - SKIP - EximTheme: - type: object - properties: - displayName: - type: string - cssFile: - type: string - description: - type: string - assetsUrl: - type: string - logoUrl: - type: string - faviconUrl: - type: string - previewImageUrl: - type: string - assetsUpdateDate: - type: string - properties: - type: object - images: - $ref: '#/components/schemas/Images' - Images: - type: object - nullable: false - additionalProperties: - $ref: '#/components/schemas/Image' - Image: - type: object - properties: - imageData: - type: string - format: byte - mimeType: - type: string - ImageInfo: - type: object - properties: - id: - type: string - parameters: - pageNumber: - in: query - name: pageNumber - required: false + /themes: + get: + x-onecx: + permissions: + themes: + - read + tags: + - themes + description: Find all themes + operationId: getThemes + parameters: + - $ref: '#/components/parameters/pageNumber' + - $ref: '#/components/parameters/pageSize' + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/GetThemesResponse' + "400": + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetailResponse' + "404": + description: Not found + post: + x-onecx: + permissions: + themes: + - write + tags: + - themes + description: Create theme + operationId: createTheme + requestBody: + content: + application/json: schema: - type: integer - format: int32 - default: 0 - pageSize: - in: query - name: pageSize - required: false + $ref: '#/components/schemas/CreateThemeRequest' + responses: + "201": + description: Created + content: + application/json: + schema: + $ref: '#/components/schemas/CreateThemeResponse' + "204": + description: No Content + "400": + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetailResponse' + "404": + description: Not Found + /themes/{id}: + get: + x-onecx: + permissions: + themes: + - read + tags: + - themes + description: Find theme by id + operationId: getThemeById + parameters: + - $ref: '#/components/parameters/id' + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/GetThemeResponse' + "400": + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetailResponse' + "404": + description: Not Found + put: + x-onecx: + permissions: + themes: + - write + tags: + - themes + description: Update theme + operationId: updateTheme + parameters: + - $ref: '#/components/parameters/id' + requestBody: + content: + application/json: schema: - type: integer - format: int32 - default: 100 - maximum: 1000 - id: + $ref: '#/components/schemas/UpdateThemeRequest' + responses: + "200": + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateThemeResponse' + "204": + description: No Content + "400": + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetailResponse' + "404": + description: Not Found + delete: + x-onecx: + permissions: + themes: + - delete + tags: + - themes + description: Delete theme + operationId: deleteTheme + parameters: + - $ref: '#/components/parameters/id' + responses: + "204": + description: No Content + "400": + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetailResponse' + /themes/name/{name}: + get: + x-onecx: + permissions: + themes: + - read + tags: + - themes + description: Find theme by name including workspace + operationId: getThemeByName + parameters: + - $ref: '#/components/parameters/name' + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/GetThemeResponse' + "400": + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetailResponse' + "404": + description: Not Found + /themes/search: + post: + x-onecx: + permissions: + themes: + - read + tags: + - themes + description: Search themes by criteria + operationId: searchThemes + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/SearchThemeRequest' + responses: + "200": + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/SearchThemeResponse' + "204": + description: No Content + "400": + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetailResponse' + "404": + description: Not Found + /themes/export: + post: + x-onecx: + permissions: + themes: + - read + tags: + - themes + description: Export list of themes + operationId: exportThemes + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExportThemeRequest' + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ThemeSnapshot' + "404": + description: No themes founds + /themes/import: + post: + x-onecx: + permissions: + themes: + - write + tags: + - themes + description: Import themes + operationId: importThemes + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ThemeSnapshot' + responses: + "200": + description: Import result + content: + application/json: + schema: + $ref: '#/components/schemas/ImportThemeResponse' + "400": + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetailResponse' + /images/{refId}/{refType}: + post: + x-onecx: + permissions: + themes: + - write + tags: + - imagesInternal + description: Upload Images + parameters: + - name: refId in: path - name: id required: true schema: - type: string - name: + type: string + - name: refType + in: path + required: true + schema: + $ref: "#/components/schemas/RefType" + operationId: uploadImage + requestBody: + required: true + content: + image/*: + schema: + type: string + format: binary + responses: + "201": + description: CREATED + content: + application/json: + schema: + $ref: '#/components/schemas/ImageInfo' + "400": + description: Bad Request + get: + x-onecx: + permissions: + themes: + - read + tags: + - imagesInternal + description: Get Image by id + operationId: getImage + parameters: + - name: refId + in: path + required: true + schema: + type: string + - name: refType in: path - name: name required: true schema: + $ref: "#/components/schemas/RefType" + responses: + "200": + description: OK + content: + image/*: + schema: type: string + format: binary + "400": + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetailResponse' + put: + x-onecx: + permissions: + themes: + - write + tags: + - imagesInternal + description: update Images + operationId: updateImage + parameters: + - name: refId + in: path + required: true + schema: + type: string + - name: refType + in: path + required: true + schema: + $ref: "#/components/schemas/RefType" + requestBody: + required: true + content: + image/*: + schema: + type: string + format: binary + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ImageInfo' + "400": + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemDetailResponse' +components: + schemas: + RefType: + type: string + enum: [ logo, favicon ] + Theme: + type: object + properties: + modificationCount: + format: int32 + type: integer + creationDate: + $ref: '#/components/schemas/OffsetDateTime' + creationUser: + type: string + modificationDate: + $ref: '#/components/schemas/OffsetDateTime' + modificationUser: + type: string + id: + type: string + name: + minLength: 2 + type: string + displayName: + minLength: 2 + type: string + cssFile: + type: string + description: + type: string + assetsUrl: + type: string + logoUrl: + type: string + faviconUrl: + type: string + previewImageUrl: + type: string + assetsUpdateDate: + type: string + properties: + type: object + operator: + type: boolean + ThemeUpdateCreate: + type: object + properties: + modificationCount: + format: int32 + type: integer + creationDate: + $ref: '#/components/schemas/OffsetDateTime' + creationUser: + type: string + modificationDate: + $ref: '#/components/schemas/OffsetDateTime' + modificationUser: + type: string + name: + minLength: 2 + type: string + displayName: + minLength: 2 + type: string + cssFile: + type: string + description: + type: string + assetsUrl: + type: string + logoUrl: + type: string + faviconUrl: + type: string + previewImageUrl: + type: string + assetsUpdateDate: + type: string + properties: + type: object + + GetThemeResponse: + required: + - resource + type: object + properties: + resource: + $ref: '#/components/schemas/Theme' + GetThemesResponse: + allOf: # Combines the PagingResponse and the Resources + - $ref: '#/components/schemas/PagingResponse' + - type: object + properties: + stream: + type: array + items: + $ref: '#/components/schemas/Theme' + CreateThemeResponse: + required: + - resource + type: object + properties: + resource: + $ref: '#/components/schemas/Theme' + UpdateThemeResponse: + required: + - resource + type: object + properties: + resource: + $ref: '#/components/schemas/Theme' + OffsetDateTime: + format: date-time + type: string + example: 2022-03-10T12:15:50-04:00 + ProblemDetailResponse: + type: object + properties: + errorCode: + type: string + detail: + type: string + params: + type: array + items: + $ref: '#/components/schemas/ProblemDetailParam' + invalidParams: + type: array + items: + $ref: '#/components/schemas/ProblemDetailInvalidParam' + ProblemDetailParam: + type: object + properties: + key: + type: string + value: + type: string + ProblemDetailInvalidParam: + type: object + properties: + name: + type: string + message: + type: string + CreateThemeRequest: + required: + - resource + type: object + properties: + resource: + $ref: '#/components/schemas/ThemeUpdateCreate' + UpdateThemeRequest: + required: + - resource + type: object + properties: + resource: + $ref: '#/components/schemas/ThemeUpdateCreate' + SearchThemeRequest: + type: object + properties: + name: + type: string + displayName: + type: string + pageNumber: + type: integer + format: int32 + default: 0 + pageSize: + type: integer + format: int32 + default: 100 + maximum: 1000 + SearchThemeResponse: + allOf: + - $ref: '#/components/schemas/PagingResponse' + - type: object + properties: + stream: + type: array + items: + $ref: '#/components/schemas/Theme' + PagingResponse: + type: object + properties: + totalElements: + format: int64 + type: integer + number: + format: int32 + type: integer + size: + format: int32 + type: integer + totalPages: + format: int64 + type: integer + ExportThemeRequest: + type: object + properties: + names: + type: array + uniqueItems: true + items: + type: string + ThemeSnapshot: + type: object + properties: + id: + type: string + minLength: 10 + description: ID of the request + created: + $ref: '#/components/schemas/OffsetDateTime' + themes: + type: object + nullable: false + additionalProperties: + $ref: '#/components/schemas/EximTheme' + ImportThemeResponse: + type: object + properties: + id: + type: string + minLength: 10 + description: ID of the request + themes: + additionalProperties: + $ref: '#/components/schemas/ImportThemeResponseStatus' + ImportThemeResponseStatus: + type: string + enum: + - UPDATE + - CREATED + - SKIP + EximTheme: + type: object + properties: + displayName: + type: string + cssFile: + type: string + description: + type: string + assetsUrl: + type: string + logoUrl: + type: string + faviconUrl: + type: string + previewImageUrl: + type: string + assetsUpdateDate: + type: string + properties: + type: object + images: + $ref: '#/components/schemas/Images' + Images: + type: object + nullable: false + additionalProperties: + $ref: '#/components/schemas/Image' + Image: + type: object + properties: + imageData: + type: string + format: byte + mimeType: + type: string + ImageInfo: + type: object + properties: + id: + type: string + parameters: + pageNumber: + in: query + name: pageNumber + required: false + schema: + type: integer + format: int32 + default: 0 + pageSize: + in: query + name: pageSize + required: false + schema: + type: integer + format: int32 + default: 100 + maximum: 1000 + id: + in: path + name: id + required: true + schema: + type: string + name: + in: path + name: name + required: true + schema: + type: string diff --git a/src/assets/i18n/de.json b/src/assets/i18n/de.json index 72da0ac..15c8702 100644 --- a/src/assets/i18n/de.json +++ b/src/assets/i18n/de.json @@ -53,7 +53,7 @@ "NEXT.TOOLTIP": "Nächste Seite", "BACK.TOOLTIP": "Vorherige Seite", "DETAILS.TOOLTIP": "Anzeige Details", - "NEW.TOOLTIP": "Erstelle neuen Eintrag" + "CREATE.TOOLTIP": "Erstelle neuen Eintrag" }, "SEARCH": { "ERROR": "Daten nicht abrufbar", @@ -139,14 +139,12 @@ "SEARCH.HEADER": "Theme Verwaltung", "SEARCH.SUBHEADER": "Erstellung und Bearbeitung von Themes", "DESIGNER.EDIT.SUBHEADER": "Theme Details ändern", - "DESIGNER.NEW.SUBHEADER": "Ein neues Theme erstellen", + "DESIGNER.CREATE.SUBHEADER": "Ein neues Theme erstellen", "DETAIL.SUBHEADER": "Theme Details", "DETAIL.AUTO_APPLY": "Änderungen automatisch anwenden", "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", @@ -186,11 +184,6 @@ "VERSION": "Version", "DESCRIPTION": "Beschreibung", "CSS_FILE": "CSS Datei", - "CREATION_DATE": "Erstellt am", - "CREATION_USER": "Erstellt von", - "MODIFICATION_DATE": "Geändert am", - "MODIFICATION_USER": "Geändert von", - "WORKSPACES": "Verwendet in Workspaces", "TOOLTIPS": { "ID": "Eindeutige technische ID des Themes", "NAME": "Eindeutiger Name des Themes, der nicht geändert werden kann. Wird als Referenz auf das Theme benutzt.", @@ -198,15 +191,10 @@ "FONT_FAMILY": "Wähle die Schriftart", "FONT_SIZE": "Wähle die Schriftgröße", "ITEM_COLOR": "Wähle eine Farbe", - "ITEM_COLOR_PICKER": "Wähle eine Farbe mit Farbwähler", + "ITEM_COLOR_PICKER": "Wähle eine Farbe mit dem Farbwähler", "VERSION": "Version des Themes", "DESCRIPTION": "Beschreibung des Themes", - "WORKSPACES": "Workspaces, die das Theme benutzen", - "CSS_FILE": "CSS Datei", - "CREATION_DATE": "Erstellt am", - "CREATION_USER": "Erstellt von", - "MODIFICATION_DATE": "Geändert am", - "MODIFICATION_USER": "Geändert von" + "CSS_FILE": "CSS Datei" } }, "VALIDATION": { @@ -223,20 +211,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..dc4db8e 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -53,7 +53,7 @@ "NEXT.TOOLTIP": "Next page", "BACK.TOOLTIP": "Previous page", "DETAILS.TOOLTIP": "Display details", - "NEW.TOOLTIP": "Create new entry" + "CREATE.TOOLTIP": "Create new entry" }, "SEARCH": { "ERROR": "Data not available", @@ -139,14 +139,12 @@ "SEARCH.HEADER": "Theme Management", "SEARCH.SUBHEADER": "Creation and editing of Themes", "DESIGNER.EDIT.SUBHEADER": "Change Theme Details", - "DESIGNER.NEW.SUBHEADER": "Create a new Theme", + "DESIGNER.CREATE.SUBHEADER": "Create a new Theme", "DETAIL.SUBHEADER": "Theme Details", "DETAIL.AUTO_APPLY": "Auto apply changes", "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", @@ -186,11 +184,6 @@ "VERSION": "Version", "DESCRIPTION": "Description", "CSS_FILE": "CSS File", - "CREATION_DATE": "Created on", - "CREATION_USER": "Created by", - "MODIFICATION_DATE": "Changed on", - "MODIFICATION_USER": "Changed by", - "WORKSPACES": "Used in Workspaces", "TOOLTIPS": { "ID": "Unique technical ID of the Theme", "NAME": "Unique name of the Theme, cannot be changed. Used as reference to the Theme.", @@ -198,15 +191,10 @@ "FONT_FAMILY": "Select Font family", "FONT_SIZE": "Select Font size", "ITEM_COLOR": "Select a Color", - "ITEM_COLOR_PICKER": "Select a color with color picker", + "ITEM_COLOR_PICKER": "Select a color with the color picker", "VERSION": "Version of the Theme", "DESCRIPTION": "Description of the Theme", - "WORKSPACES": "Workspaces using the Theme", - "CSS_FILE": "CSS file name", - "CREATION_DATE": "Timestamp the Theme was created", - "CREATION_USER": "Name of the user which created the Theme", - "MODIFICATION_DATE": "Timestamp the Theme was latest modified", - "MODIFICATION_USER": "Name of the user which changed the Theme last time" + "CSS_FILE": "CSS file name" } }, "VALIDATION": { @@ -223,20 +211,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 },