Move to standalone components

This commit is contained in:
Jim Martens 2023-08-12 15:50:47 +02:00
parent e5285688b9
commit 7f7c285295
22 changed files with 771 additions and 226 deletions

595
package-lock.json generated
View File

@ -37,7 +37,9 @@
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.1.0",
"typescript": "~5.1.6"
"typescript": "~5.1.6",
"webpack-bundle-analyzer": "^4.9.0",
"webpack-cli": "^5.1.4"
}
},
"node_modules/@ampproject/remapping": {
@ -3909,6 +3911,12 @@
"node": ">=14"
}
},
"node_modules/@polka/url": {
"version": "1.0.0-next.21",
"resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.21.tgz",
"integrity": "sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==",
"dev": true
},
"node_modules/@schematics/angular": {
"version": "16.1.4",
"resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-16.1.4.tgz",
@ -4350,6 +4358,50 @@
"@xtuc/long": "4.2.2"
}
},
"node_modules/@webpack-cli/configtest": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz",
"integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==",
"dev": true,
"engines": {
"node": ">=14.15.0"
},
"peerDependencies": {
"webpack": "5.x.x",
"webpack-cli": "5.x.x"
}
},
"node_modules/@webpack-cli/info": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz",
"integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==",
"dev": true,
"engines": {
"node": ">=14.15.0"
},
"peerDependencies": {
"webpack": "5.x.x",
"webpack-cli": "5.x.x"
}
},
"node_modules/@webpack-cli/serve": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz",
"integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==",
"dev": true,
"engines": {
"node": ">=14.15.0"
},
"peerDependencies": {
"webpack": "5.x.x",
"webpack-cli": "5.x.x"
},
"peerDependenciesMeta": {
"webpack-dev-server": {
"optional": true
}
}
},
"node_modules/@xtuc/ieee754": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
@ -4414,6 +4466,15 @@
"acorn": "^8"
}
},
"node_modules/acorn-walk": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz",
"integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==",
"dev": true,
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/adjust-sourcemap-loader": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-4.0.0.tgz",
@ -5863,6 +5924,12 @@
"url": "https://github.com/fb55/domutils?sponsor=1"
}
},
"node_modules/duplexer": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz",
"integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==",
"dev": true
},
"node_modules/eastasianwidth": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
@ -5998,6 +6065,18 @@
"node": ">=6"
}
},
"node_modules/envinfo": {
"version": "7.10.0",
"resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.10.0.tgz",
"integrity": "sha512-ZtUjZO6l5mwTHvc1L9+1q5p/R3wTopcfqMW8r5t8SJSKqeVI/LtajORwRFEKpEFuekjD0VBjwu1HMxL4UalIRw==",
"dev": true,
"bin": {
"envinfo": "dist/cli.js"
},
"engines": {
"node": ">=4"
}
},
"node_modules/err-code": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz",
@ -6421,6 +6500,15 @@
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
"dev": true
},
"node_modules/fastest-levenshtein": {
"version": "1.0.16",
"resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz",
"integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==",
"dev": true,
"engines": {
"node": ">= 4.9.1"
}
},
"node_modules/fastq": {
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz",
@ -6844,6 +6932,21 @@
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"dev": true
},
"node_modules/gzip-size": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz",
"integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==",
"dev": true,
"dependencies": {
"duplexer": "^0.1.2"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/handle-thing": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz",
@ -7257,6 +7360,25 @@
"node": ">=4"
}
},
"node_modules/import-local": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz",
"integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==",
"dev": true,
"dependencies": {
"pkg-dir": "^4.2.0",
"resolve-cwd": "^3.0.0"
},
"bin": {
"import-local-fixture": "fixtures/cli.js"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/imurmurhash": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
@ -7396,6 +7518,15 @@
"node": ">=8"
}
},
"node_modules/interpret": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz",
"integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==",
"dev": true,
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/ip": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz",
@ -9358,6 +9489,15 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/opener": {
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz",
"integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==",
"dev": true,
"bin": {
"opener": "bin/opener-bin.js"
}
},
"node_modules/ora": {
"version": "5.4.1",
"resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz",
@ -10146,6 +10286,18 @@
"node": ">=8.10.0"
}
},
"node_modules/rechoir": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz",
"integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==",
"dev": true,
"dependencies": {
"resolve": "^1.20.0"
},
"engines": {
"node": ">= 10.13.0"
}
},
"node_modules/reflect-metadata": {
"version": "0.1.13",
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz",
@ -10270,6 +10422,18 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/resolve-cwd": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz",
"integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==",
"dev": true,
"dependencies": {
"resolve-from": "^5.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/resolve-from": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
@ -10894,6 +11058,20 @@
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
}
},
"node_modules/sirv": {
"version": "1.0.19",
"resolved": "https://registry.npmjs.org/sirv/-/sirv-1.0.19.tgz",
"integrity": "sha512-JuLThK3TnZG1TAKDwNIqNq6QA2afLOCcm+iE8D1Kj3GA40pSPsxQjjJl0J8X3tsR7T+CP1GavpzLwYkgVLWrZQ==",
"dev": true,
"dependencies": {
"@polka/url": "^1.0.0-next.20",
"mrmime": "^1.0.0",
"totalist": "^1.0.0"
},
"engines": {
"node": ">= 10"
}
},
"node_modules/slash": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz",
@ -11558,6 +11736,15 @@
"node": ">=0.6"
}
},
"node_modules/totalist": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/totalist/-/totalist-1.1.0.tgz",
"integrity": "sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g==",
"dev": true,
"engines": {
"node": ">=6"
}
},
"node_modules/tree-kill": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
@ -11960,6 +12147,184 @@
}
}
},
"node_modules/webpack-bundle-analyzer": {
"version": "4.9.0",
"resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.9.0.tgz",
"integrity": "sha512-+bXGmO1LyiNx0i9enBu3H8mv42sj/BJWhZNFwjz92tVnBa9J3JMGo2an2IXlEleoDOPn/Hofl5hr/xCpObUDtw==",
"dev": true,
"dependencies": {
"@discoveryjs/json-ext": "0.5.7",
"acorn": "^8.0.4",
"acorn-walk": "^8.0.0",
"chalk": "^4.1.0",
"commander": "^7.2.0",
"gzip-size": "^6.0.0",
"lodash": "^4.17.20",
"opener": "^1.5.2",
"sirv": "^1.0.7",
"ws": "^7.3.1"
},
"bin": {
"webpack-bundle-analyzer": "lib/bin/analyzer.js"
},
"engines": {
"node": ">= 10.13.0"
}
},
"node_modules/webpack-bundle-analyzer/node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/webpack-bundle-analyzer/node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"dev": true,
"dependencies": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/webpack-bundle-analyzer/node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/webpack-bundle-analyzer/node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"node_modules/webpack-bundle-analyzer/node_modules/commander": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
"integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
"dev": true,
"engines": {
"node": ">= 10"
}
},
"node_modules/webpack-bundle-analyzer/node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/webpack-bundle-analyzer/node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"dependencies": {
"has-flag": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/webpack-bundle-analyzer/node_modules/ws": {
"version": "7.5.9",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz",
"integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==",
"dev": true,
"engines": {
"node": ">=8.3.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": "^5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/webpack-cli": {
"version": "5.1.4",
"resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz",
"integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==",
"dev": true,
"dependencies": {
"@discoveryjs/json-ext": "^0.5.0",
"@webpack-cli/configtest": "^2.1.1",
"@webpack-cli/info": "^2.0.2",
"@webpack-cli/serve": "^2.0.5",
"colorette": "^2.0.14",
"commander": "^10.0.1",
"cross-spawn": "^7.0.3",
"envinfo": "^7.7.3",
"fastest-levenshtein": "^1.0.12",
"import-local": "^3.0.2",
"interpret": "^3.1.1",
"rechoir": "^0.8.0",
"webpack-merge": "^5.7.3"
},
"bin": {
"webpack-cli": "bin/cli.js"
},
"engines": {
"node": ">=14.15.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
},
"peerDependencies": {
"webpack": "5.x.x"
},
"peerDependenciesMeta": {
"@webpack-cli/generators": {
"optional": true
},
"webpack-bundle-analyzer": {
"optional": true
},
"webpack-dev-server": {
"optional": true
}
}
},
"node_modules/webpack-cli/node_modules/commander": {
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz",
"integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==",
"dev": true,
"engines": {
"node": ">=14"
}
},
"node_modules/webpack-dev-middleware": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-6.1.1.tgz",
@ -15224,6 +15589,12 @@
"dev": true,
"optional": true
},
"@polka/url": {
"version": "1.0.0-next.21",
"resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.21.tgz",
"integrity": "sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==",
"dev": true
},
"@schematics/angular": {
"version": "16.1.4",
"resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-16.1.4.tgz",
@ -15640,6 +16011,27 @@
"@xtuc/long": "4.2.2"
}
},
"@webpack-cli/configtest": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz",
"integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==",
"dev": true,
"requires": {}
},
"@webpack-cli/info": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz",
"integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==",
"dev": true,
"requires": {}
},
"@webpack-cli/serve": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz",
"integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==",
"dev": true,
"requires": {}
},
"@xtuc/ieee754": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
@ -15693,6 +16085,12 @@
"dev": true,
"requires": {}
},
"acorn-walk": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz",
"integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==",
"dev": true
},
"adjust-sourcemap-loader": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-4.0.0.tgz",
@ -16764,6 +17162,12 @@
"domhandler": "^5.0.3"
}
},
"duplexer": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz",
"integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==",
"dev": true
},
"eastasianwidth": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
@ -16874,6 +17278,12 @@
"integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==",
"dev": true
},
"envinfo": {
"version": "7.10.0",
"resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.10.0.tgz",
"integrity": "sha512-ZtUjZO6l5mwTHvc1L9+1q5p/R3wTopcfqMW8r5t8SJSKqeVI/LtajORwRFEKpEFuekjD0VBjwu1HMxL4UalIRw==",
"dev": true
},
"err-code": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz",
@ -17222,6 +17632,12 @@
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
"dev": true
},
"fastest-levenshtein": {
"version": "1.0.16",
"resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz",
"integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==",
"dev": true
},
"fastq": {
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz",
@ -17533,6 +17949,15 @@
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"dev": true
},
"gzip-size": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz",
"integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==",
"dev": true,
"requires": {
"duplexer": "^0.1.2"
}
},
"handle-thing": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz",
@ -17841,6 +18266,16 @@
}
}
},
"import-local": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz",
"integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==",
"dev": true,
"requires": {
"pkg-dir": "^4.2.0",
"resolve-cwd": "^3.0.0"
}
},
"imurmurhash": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
@ -17949,6 +18384,12 @@
}
}
},
"interpret": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz",
"integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==",
"dev": true
},
"ip": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz",
@ -19439,6 +19880,12 @@
"is-wsl": "^2.2.0"
}
},
"opener": {
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz",
"integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==",
"dev": true
},
"ora": {
"version": "5.4.1",
"resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz",
@ -19998,6 +20445,15 @@
"picomatch": "^2.2.1"
}
},
"rechoir": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz",
"integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==",
"dev": true,
"requires": {
"resolve": "^1.20.0"
}
},
"reflect-metadata": {
"version": "0.1.13",
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz",
@ -20100,6 +20556,15 @@
"supports-preserve-symlinks-flag": "^1.0.0"
}
},
"resolve-cwd": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz",
"integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==",
"dev": true,
"requires": {
"resolve-from": "^5.0.0"
}
},
"resolve-from": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
@ -20559,6 +21024,17 @@
"make-fetch-happen": "^11.0.1"
}
},
"sirv": {
"version": "1.0.19",
"resolved": "https://registry.npmjs.org/sirv/-/sirv-1.0.19.tgz",
"integrity": "sha512-JuLThK3TnZG1TAKDwNIqNq6QA2afLOCcm+iE8D1Kj3GA40pSPsxQjjJl0J8X3tsR7T+CP1GavpzLwYkgVLWrZQ==",
"dev": true,
"requires": {
"@polka/url": "^1.0.0-next.20",
"mrmime": "^1.0.0",
"totalist": "^1.0.0"
}
},
"slash": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz",
@ -21067,6 +21543,12 @@
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
"dev": true
},
"totalist": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/totalist/-/totalist-1.1.0.tgz",
"integrity": "sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g==",
"dev": true
},
"tree-kill": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
@ -21359,6 +21841,117 @@
}
}
},
"webpack-bundle-analyzer": {
"version": "4.9.0",
"resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.9.0.tgz",
"integrity": "sha512-+bXGmO1LyiNx0i9enBu3H8mv42sj/BJWhZNFwjz92tVnBa9J3JMGo2an2IXlEleoDOPn/Hofl5hr/xCpObUDtw==",
"dev": true,
"requires": {
"@discoveryjs/json-ext": "0.5.7",
"acorn": "^8.0.4",
"acorn-walk": "^8.0.0",
"chalk": "^4.1.0",
"commander": "^7.2.0",
"gzip-size": "^6.0.0",
"lodash": "^4.17.20",
"opener": "^1.5.2",
"sirv": "^1.0.7",
"ws": "^7.3.1"
},
"dependencies": {
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"requires": {
"color-convert": "^2.0.1"
}
},
"chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"dev": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"commander": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
"integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
"dev": true
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"requires": {
"has-flag": "^4.0.0"
}
},
"ws": {
"version": "7.5.9",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz",
"integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==",
"dev": true,
"requires": {}
}
}
},
"webpack-cli": {
"version": "5.1.4",
"resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz",
"integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==",
"dev": true,
"requires": {
"@discoveryjs/json-ext": "^0.5.0",
"@webpack-cli/configtest": "^2.1.1",
"@webpack-cli/info": "^2.0.2",
"@webpack-cli/serve": "^2.0.5",
"colorette": "^2.0.14",
"commander": "^10.0.1",
"cross-spawn": "^7.0.3",
"envinfo": "^7.7.3",
"fastest-levenshtein": "^1.0.12",
"import-local": "^3.0.2",
"interpret": "^3.1.1",
"rechoir": "^0.8.0",
"webpack-merge": "^5.7.3"
},
"dependencies": {
"commander": {
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz",
"integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==",
"dev": true
}
}
},
"webpack-dev-middleware": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-6.1.1.tgz",

View File

@ -5,7 +5,10 @@
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"buildProduction": "ng build -c production",
"build:stats": "ng build --stats-json",
"build:production": "ng build -c production",
"build:production:stats": "ng build -c production --stats-json",
"analyze": "webpack-bundle-analyzer dist/wahlrecht-frontend/de/stats.json",
"watch": "ng build --watch --configuration development",
"test": "ng test",
"extract-i18n": "ng extract-i18n --output-path src/locale"
@ -41,6 +44,8 @@
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.1.0",
"typescript": "~5.1.6"
"typescript": "~5.1.6",
"webpack-bundle-analyzer": "^4.9.0",
"webpack-cli": "^5.1.4"
}
}

View File

@ -1,26 +0,0 @@
import {NgModule} from '@angular/core';
import {RouterModule, Routes} from '@angular/router';
import {DashboardComponent} from "./dashboard/dashboard.component";
import {PermissionDeniedComponent} from "./permission-denied/permission-denied.component";
const routes: Routes = [
{
path: 'elections',
loadChildren: () => import("./elections/elections.module").then(mod => mod.ElectionsModule)
},
{
path: 'permission-denied',
component: PermissionDeniedComponent
},
{
path: '',
component: DashboardComponent,
pathMatch: 'full'
}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }

View File

@ -1,3 +1,3 @@
z<app-navigation>
<app-navigation>
<router-outlet></router-outlet>
</app-navigation>

View File

@ -1,9 +1,13 @@
import {Component} from '@angular/core';
import {NavigationComponent} from "./navigation/navigation.component";
import {RouterModule} from "@angular/router";
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
styleUrls: ['./app.component.scss'],
standalone: true,
imports: [NavigationComponent, RouterModule]
})
export class AppComponent {
}

View File

@ -1,82 +0,0 @@
import {APP_INITIALIZER, NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {AppRoutingModule} from './app-routing.module';
import {AppComponent} from './app.component';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {DashboardComponent} from './dashboard/dashboard.component';
import {MatToolbarModule} from "@angular/material/toolbar";
import {MatButtonModule} from "@angular/material/button";
import {MatGridListModule} from "@angular/material/grid-list";
import {MatIconModule} from "@angular/material/icon";
import {MatListModule} from "@angular/material/list";
import {KeycloakAngularModule, KeycloakService} from "keycloak-angular";
import {PermissionDeniedComponent} from './permission-denied/permission-denied.component';
import {HttpClientModule} from "@angular/common/http";
import {environment} from "../environments/environment";
import {NavigationComponent} from './navigation/navigation.component';
import {MatSidenavModule} from '@angular/material/sidenav';
import {MatMenuModule} from "@angular/material/menu";
import {MatTableModule} from "@angular/material/table";
import {StoreModule} from '@ngrx/store';
import {EffectsModule} from '@ngrx/effects';
function initializeKeycloak(keycloak: KeycloakService) {
return () =>
keycloak.init({
config: {
url: environment.keycloakURL,
realm: environment.realm,
clientId: environment.clientId,
},
initOptions: {
onLoad: 'check-sso',
silentCheckSsoRedirectUri:
window.location.origin + '/assets/silent-check-sso.html',
flow: "standard"
},
shouldAddToken: (request) => {
const {url} = request;
return url.startsWith(environment.backendURL);
},
loadUserProfileAtStartUp: true
});
}
@NgModule({
declarations: [
AppComponent,
DashboardComponent,
PermissionDeniedComponent,
NavigationComponent
],
imports: [
BrowserModule,
AppRoutingModule,
BrowserAnimationsModule,
MatToolbarModule,
MatButtonModule,
MatGridListModule,
MatIconModule,
MatToolbarModule,
MatListModule,
KeycloakAngularModule,
HttpClientModule,
MatSidenavModule,
MatMenuModule,
MatTableModule,
StoreModule.forRoot({}, {}),
EffectsModule.forRoot([])
],
providers: [
{
provide: APP_INITIALIZER,
useFactory: initializeKeycloak,
multi: true,
deps: [KeycloakService],
},
],
bootstrap: [AppComponent]
})
export class AppModule {
}

26
src/app/app.routes.ts Normal file
View File

@ -0,0 +1,26 @@
import {Route} from "@angular/router";
import {provideState} from "@ngrx/store";
import {electionsReducers, featureStateName} from "./elections/store";
import {provideEffects} from "@ngrx/effects";
import {ElectionsEffects} from "./elections/store/elections.effects";
export const ROOT_ROUTES: Route[] = [
{
path: 'elections',
providers: [
provideState(featureStateName, electionsReducers),
provideEffects([ElectionsEffects]),
],
loadChildren: () => import("./elections/elections.routes").then(mod => mod.ELECTION_ROUTES)
},
{
path: 'permission-denied',
loadComponent: () => import("./permission-denied/permission-denied.component")
.then(mod => mod.PermissionDeniedComponent)
},
{
path: '',
loadComponent: () => import("./dashboard/dashboard.component").then(mod => mod.DashboardComponent),
pathMatch: 'full'
}
];

View File

@ -35,7 +35,7 @@ export class AppAuthGuard extends KeycloakAuthGuard {
// Allow the user to proceed if all the required roles are present.
granted = requiredRoles.every((role) => this.roles.includes(role));
// Routing user into permission denied view if don't have necessary roles.
// Routing user into permission denied view if they don't have necessary roles.
if (!granted) {
await this.router.navigate(['permission-denied']);
}

View File

@ -1,15 +1,11 @@
import {Component, OnInit} from '@angular/core';
import {Component} from '@angular/core';
@Component({
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.scss']
styleUrls: ['./dashboard.component.scss'],
standalone: true,
})
export class DashboardComponent implements OnInit {
export class DashboardComponent {
constructor() {
}
ngOnInit() {
}
}

View File

@ -1,7 +1,7 @@
import {AfterViewInit, Component, Input, ViewChild} from '@angular/core';
import {ElectedCandidates} from "../model/elected-candidates";
import {MatSort, Sort} from "@angular/material/sort";
import {MatTableDataSource} from "@angular/material/table";
import {MatSort, MatSortModule, Sort} from "@angular/material/sort";
import {MatTableDataSource, MatTableModule} from "@angular/material/table";
import {LiveAnnouncer} from "@angular/cdk/a11y";
import {ElectionSource} from "../model/election-source";
@ -19,7 +19,9 @@ interface PresentedCandidate {
@Component({
selector: 'app-elected-candidates',
templateUrl: './elected-candidates.component.html',
styleUrls: ['./elected-candidates.component.scss']
styleUrls: ['./elected-candidates.component.scss'],
imports: [MatTableModule, MatSortModule],
standalone: true
})
export class ElectedCandidatesComponent implements AfterViewInit {
@ViewChild(MatSort) sort: MatSort | null = null;

View File

@ -16,6 +16,9 @@ import {
import {DEFAULT_RESULT, ElectionResult} from "../model/election-result";
import {FormElectionResult} from "../model/form-election-result";
import {DEFAULT_ELECTED, ElectedCandidates} from "../model/elected-candidates";
import {MatProgressSpinnerModule} from "@angular/material/progress-spinner";
import {AsyncPipe, NgIf} from "@angular/common";
import {ElectionComponent} from "../election/election.component";
export interface ModifiedElectionResult {
electionName: string;
@ -38,7 +41,9 @@ export interface ViewModel {
@Component({
selector: 'app-election-container',
templateUrl: './election-container.component.html',
styleUrls: ['./election-container.component.scss']
styleUrls: ['./election-container.component.scss'],
imports: [MatProgressSpinnerModule, AsyncPipe, ElectionComponent, NgIf],
standalone: true
})
export class ElectionContainerComponent implements OnInit {
election$: Observable<Election>;

View File

@ -1,7 +1,10 @@
import {Component, Input} from '@angular/core';
import {Party} from "../model/party";
import {ElectedCandidates} from "../model/elected-candidates";
import {ViewModel} from "../elected-candidates/elected-candidates.component";
import {ElectedCandidatesComponent, ViewModel} from "../elected-candidates/elected-candidates.component";
import {MatTabsModule} from "@angular/material/tabs";
import {SeatAllocationComponent} from "../seat-allocation/seat-allocation.component";
import {NgForOf} from "@angular/common";
export interface PartySeats {
party: string;
@ -11,7 +14,9 @@ export interface PartySeats {
@Component({
selector: 'app-election-result',
templateUrl: './election-result.component.html',
styleUrls: ['./election-result.component.scss']
styleUrls: ['./election-result.component.scss'],
imports: [MatTabsModule, SeatAllocationComponent, ElectedCandidatesComponent, NgForOf],
standalone: true
})
export class ElectionResultComponent {

View File

@ -1,10 +1,10 @@
import {Component, EventEmitter, Input, OnInit, Output, ViewChild} from '@angular/core';
import {DEFAULT_ELECTION, Election} from "../model/election";
import {KeyValue} from "@angular/common";
import {KeyValue, KeyValuePipe, NgFor, NgIf} from "@angular/common";
import {Party} from "../model/party";
import {VotingResult} from "../model/voting-result";
import {Constituency} from "../model/constituency";
import {FormBuilder, FormGroup} from '@angular/forms';
import {FormBuilder, FormGroup, ReactiveFormsModule} from '@angular/forms';
import {
DEFAULT_MODIFIED_RESULT,
ModifiedElectionResult,
@ -14,12 +14,21 @@ import {ElectionResult} from "../model/election-result";
import {FormElectionResult} from "../model/form-election-result";
import {ElectedCandidates} from "../model/elected-candidates";
import {mapConstituencyResultsForm, mapOverallResultsForm} from "../store/elections.reducer";
import {debounceTime, distinctUntilChanged, filter} from "rxjs";
import {debounceTime, distinctUntilChanged} from "rxjs";
import {MatTabsModule} from "@angular/material/tabs";
import {MatProgressSpinnerModule} from "@angular/material/progress-spinner";
import {MatButtonModule} from "@angular/material/button";
import {MatFormFieldModule} from "@angular/material/form-field";
import {MatInputModule} from "@angular/material/input";
import {ElectionResultComponent} from "../election-result/election-result.component";
@Component({
selector: 'app-election',
templateUrl: './election.component.html',
styleUrls: ['./election.component.scss']
styleUrls: ['./election.component.scss'],
imports: [MatTabsModule, MatProgressSpinnerModule, MatButtonModule, ReactiveFormsModule, MatFormFieldModule,
MatInputModule, KeyValuePipe, ElectionResultComponent, NgFor, NgIf],
standalone: true
})
export class ElectionComponent implements OnInit {
form: FormGroup;
@ -143,12 +152,12 @@ export class ElectionComponent implements OnInit {
private updateForm(electionResult: ModifiedElectionResult) {
for (const votingResult of electionResult.overallResults) {
const formGroup = this.overallResults.get(votingResult.partyAbbreviation);
formGroup?.get('votesOnNomination')?.setValue(votingResult.votesOnNomination, { emitEvent: false });
formGroup?.get('votesThroughHealing')?.setValue(votingResult.votesThroughHealing, { emitEvent: false });
formGroup?.get('votesOnNomination')?.setValue(votingResult.votesOnNomination, {emitEvent: false});
formGroup?.get('votesThroughHealing')?.setValue(votingResult.votesThroughHealing, {emitEvent: false});
const votesPerPosition = formGroup?.get('votesPerPosition');
const {map, entries} = this.buildVotesPerPosition(votingResult);
for (const number of entries) {
votesPerPosition?.get(number)?.setValue(map.get(number) || 0, { emitEvent: false });
votesPerPosition?.get(number)?.setValue(map.get(number) || 0, {emitEvent: false});
}
}
for (const constituencyNumber of electionResult.constituencyResults.keys()) {
@ -164,7 +173,7 @@ export class ElectionComponent implements OnInit {
const formGroup = constituencyFormGroup?.get(votingResult.partyAbbreviation);
const votesPerPosition = formGroup?.get('votesPerPosition');
for (const number in votingResult.votesPerPosition) {
votesPerPosition?.get(number)?.setValue(votingResult.votesPerPosition[number], { emitEvent: false });
votesPerPosition?.get(number)?.setValue(votingResult.votesPerPosition[number], {emitEvent: false});
}
}
}

View File

@ -1,57 +0,0 @@
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {LandingPageComponent} from "./landingpage/landing-page.component";
import {StoreModule} from "@ngrx/store";
import {ElectionsEffects} from "./store/elections.effects";
import {EffectsModule} from "@ngrx/effects";
import {electionsReducers, featureStateName} from "./store";
import {ElectionsRoutingModule} from "./elections-routing.module";
import {MatTableModule} from "@angular/material/table";
import {MatProgressSpinnerModule} from "@angular/material/progress-spinner";
import {ElectionComponent} from './election/election.component';
import {MatFormFieldModule} from "@angular/material/form-field";
import {MatTabsModule} from "@angular/material/tabs";
import {MatInputModule} from "@angular/material/input";
import {FormsModule, ReactiveFormsModule} from "@angular/forms";
import {ElectionContainerComponent} from './election-container/election-container.component';
import {MatButtonModule} from "@angular/material/button";
import {ElectionResultComponent} from './election-result/election-result.component';
import {MatSortModule} from "@angular/material/sort";
import {MatStepperModule} from "@angular/material/stepper";
import {SeatAllocationComponent} from './seat-allocation/seat-allocation.component';
import {MatIconModule} from "@angular/material/icon";
import {ElectedCandidatesComponent} from './elected-candidates/elected-candidates.component';
@NgModule({
declarations: [
LandingPageComponent,
ElectionComponent,
ElectionContainerComponent,
ElectionResultComponent,
SeatAllocationComponent,
ElectedCandidatesComponent
],
imports: [
CommonModule,
ElectionsRoutingModule,
StoreModule.forFeature(featureStateName, electionsReducers),
EffectsModule.forFeature([ElectionsEffects]),
MatTableModule,
MatProgressSpinnerModule,
MatFormFieldModule,
MatTabsModule,
MatInputModule,
FormsModule,
ReactiveFormsModule,
MatButtonModule,
MatSortModule,
MatStepperModule,
MatIconModule,
],
exports: [
LandingPageComponent
]
})
export class ElectionsModule {
}

View File

@ -1,13 +1,12 @@
import {NgModule} from '@angular/core';
import {RouterModule, Routes} from "@angular/router";
import {Route} from "@angular/router";
import {LandingPageComponent} from "./landingpage/landing-page.component";
import {AppAuthGuard} from "../auth/auth.guard";
import {ElectionContainerComponent} from "./election-container/election-container.component";
const routes: Routes = [
export const ELECTION_ROUTES: Route[] = [
{
path: '',
component: LandingPageComponent,
loadComponent: () => import('./landingpage/landing-page.component').then(mod => mod.LandingPageComponent),
canActivate: [AppAuthGuard],
},
{
@ -17,10 +16,3 @@ const routes: Routes = [
resolve: []
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class ElectionsRoutingModule {
}

View File

@ -4,11 +4,17 @@ import {Store} from "@ngrx/store";
import {allElections, ElectionsState} from "../store";
import {loadAllElectionsAction} from "../store/elections.actions";
import {Election} from "../model/election";
import {MatTableModule} from "@angular/material/table";
import {RouterLink} from "@angular/router";
import {DatePipe, NgIf} from "@angular/common";
import {MatProgressSpinnerModule} from "@angular/material/progress-spinner";
@Component({
selector: 'app-elections',
selector: 'app-landing-page',
templateUrl: './landing-page.component.html',
styleUrls: ['./landing-page.component.scss']
styleUrls: ['./landing-page.component.scss'],
imports: [MatTableModule, RouterLink, DatePipe, MatProgressSpinnerModule, NgIf],
standalone: true
})
export class LandingPageComponent implements OnInit, OnDestroy {
private store: Store<ElectionsState> = inject(Store<ElectionsState>);

View File

@ -1,6 +1,6 @@
import {AfterViewInit, Component, Input, ViewChild} from '@angular/core';
import {MatSort, Sort} from "@angular/material/sort";
import {MatTableDataSource} from "@angular/material/table";
import {MatSort, MatSortModule, Sort} from "@angular/material/sort";
import {MatTableDataSource, MatTableModule} from "@angular/material/table";
import {PartySeats} from "../election-result/election-result.component";
import {ElectedCandidates} from "../model/elected-candidates";
import {Party} from "../model/party";
@ -9,7 +9,9 @@ import {LiveAnnouncer} from "@angular/cdk/a11y";
@Component({
selector: 'app-seat-allocation',
templateUrl: './seat-allocation.component.html',
styleUrls: ['./seat-allocation.component.scss']
styleUrls: ['./seat-allocation.component.scss'],
imports: [MatTableModule, MatSortModule],
standalone: true
})
export class SeatAllocationComponent implements AfterViewInit {
@ViewChild(MatSort) sort: MatSort|null = null;

View File

@ -1,12 +1,33 @@
import {Component} from '@angular/core';
import {KeycloakService} from "keycloak-angular";
import {KeycloakAngularModule, KeycloakService} from "keycloak-angular";
import {from, of, switchMap} from "rxjs";
import {ActivatedRoute} from "@angular/router";
import {ActivatedRoute, RouterModule} from "@angular/router";
import {MatSidenavModule} from "@angular/material/sidenav";
import {MatToolbarModule} from "@angular/material/toolbar";
import {MatListModule} from "@angular/material/list";
import {MatButtonModule} from "@angular/material/button";
import {MatIconModule} from "@angular/material/icon";
import {AsyncPipe, NgIf} from "@angular/common";
import {provideHttpClient} from "@angular/common/http";
@Component({
selector: 'app-navigation',
templateUrl: './navigation.component.html',
styleUrls: ['./navigation.component.scss']
styleUrls: ['./navigation.component.scss'],
imports: [RouterModule,
KeycloakAngularModule,
MatSidenavModule,
MatToolbarModule,
MatListModule,
MatButtonModule,
MatIconModule,
NgIf,
AsyncPipe,
],
providers: [
],
standalone: true
})
export class NavigationComponent {
loggedUserName$;
@ -14,7 +35,7 @@ export class NavigationComponent {
url: string;
constructor(private keycloakService: KeycloakService,
private route: ActivatedRoute) {
route: ActivatedRoute) {
this.url = route.snapshot.url.join('');
this.isLoggedIn$ = from(this.keycloakService.isLoggedIn());
this.loggedUserName$ = this.isLoggedIn$.pipe(

View File

@ -3,7 +3,8 @@ import {Component} from '@angular/core';
@Component({
selector: 'app-permission-denied',
templateUrl: './permission-denied.component.html',
styleUrls: ['./permission-denied.component.scss']
styleUrls: ['./permission-denied.component.scss'],
standalone: true
})
export class PermissionDeniedComponent {

View File

@ -1,7 +0,0 @@
import {ElectionsState} from "../elections/store";
import {MessagesState} from "../messages/store";
export interface AppState {
electionsFeature: ElectionsState;
messagesFeature: MessagesState;
}

View File

View File

@ -1,9 +1,59 @@
/// <reference types="@angular/localize" />
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {bootstrapApplication} from "@angular/platform-browser";
import {AppComponent} from "./app/app.component";
import {APP_INITIALIZER} from "@angular/core";
import {KeycloakBearerInterceptor, KeycloakService} from "keycloak-angular";
import {environment} from "./environments/environment";
import {provideRouter} from "@angular/router";
import {provideStore} from "@ngrx/store";
import {provideEffects} from "@ngrx/effects";
import {provideAnimations} from "@angular/platform-browser/animations";
import {HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi} from "@angular/common/http";
import {ROOT_ROUTES} from "./app/app.routes";
import {AppModule} from './app/app.module';
function initializeKeycloak(keycloak: KeycloakService) {
return () =>
keycloak.init({
config: {
url: environment.keycloakURL,
realm: environment.realm,
clientId: environment.clientId,
},
initOptions: {
onLoad: 'check-sso',
silentCheckSsoRedirectUri:
window.location.origin + '/assets/silent-check-sso.html',
flow: "standard"
},
shouldAddToken: (request) => {
const {url} = request;
return url.startsWith(environment.backendURL);
},
loadUserProfileAtStartUp: true
});
}
platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.error(err));
bootstrapApplication(AppComponent, {
providers: [
{
provide: APP_INITIALIZER,
useFactory: initializeKeycloak,
multi: true,
deps: [KeycloakService],
},
provideRouter(ROOT_ROUTES),
provideStore(),
provideEffects(),
provideAnimations(),
provideHttpClient(
withInterceptorsFromDi()
),
KeycloakService,
{
provide: HTTP_INTERCEPTORS,
useClass: KeycloakBearerInterceptor,
multi: true
}
]
}).catch(err => console.error(err));