Merge branch 'main' of git.infinimotion.de:infinimotion/frontend
This commit is contained in:
26
angular.json
26
angular.json
@@ -31,9 +31,7 @@
|
||||
"builder": "@angular/build:application",
|
||||
"options": {
|
||||
"browser": "src/main.ts",
|
||||
"polyfills": [
|
||||
"zone.js"
|
||||
],
|
||||
"polyfills": ["zone.js"],
|
||||
"tsConfig": "tsconfig.app.json",
|
||||
"inlineStyleLanguage": "css",
|
||||
"assets": [
|
||||
@@ -47,23 +45,20 @@
|
||||
"output": "assets"
|
||||
}
|
||||
],
|
||||
"styles": [
|
||||
"src/custom-theme.scss",
|
||||
"src/styles.css"
|
||||
]
|
||||
"styles": ["src/custom-theme.scss", "src/styles.css"]
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"budgets": [
|
||||
{
|
||||
"type": "initial",
|
||||
"maximumWarning": "500kB",
|
||||
"maximumError": "1MB"
|
||||
"maximumWarning": "5MB",
|
||||
"maximumError": "10MB"
|
||||
},
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
"maximumWarning": "4kB",
|
||||
"maximumError": "8kB"
|
||||
"maximumWarning": "100kB",
|
||||
"maximumError": "500kB"
|
||||
}
|
||||
],
|
||||
"outputHashing": "all"
|
||||
@@ -94,10 +89,7 @@
|
||||
"test": {
|
||||
"builder": "@angular/build:karma",
|
||||
"options": {
|
||||
"polyfills": [
|
||||
"zone.js",
|
||||
"zone.js/testing"
|
||||
],
|
||||
"polyfills": ["zone.js", "zone.js/testing"],
|
||||
"tsConfig": "tsconfig.spec.json",
|
||||
"inlineStyleLanguage": "css",
|
||||
"assets": [
|
||||
@@ -106,9 +98,7 @@
|
||||
"input": "public"
|
||||
}
|
||||
],
|
||||
"styles": [
|
||||
"src/styles.css"
|
||||
]
|
||||
"styles": ["src/styles.css"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
284
package-lock.json
generated
284
package-lock.json
generated
@@ -16,7 +16,7 @@
|
||||
"@angular/material": "^20.2.9",
|
||||
"@angular/platform-browser": "^20.3.0",
|
||||
"@angular/router": "^20.3.0",
|
||||
"@infinimotion/model-frontend": "^0.0.102",
|
||||
"@infinimotion/model-frontend": "^0.0.116",
|
||||
"@tailwindcss/postcss": "^4.1.14",
|
||||
"angularx-qrcode": "^20.0.0",
|
||||
"canvas-confetti": "^1.9.4",
|
||||
@@ -277,13 +277,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@angular-devkit/architect": {
|
||||
"version": "0.2003.9",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2003.9.tgz",
|
||||
"integrity": "sha512-p0GO2H8hiZjRHI9sm4tXTF3OpWaEnkqvB0GBGJfGp8RvpPfDA2t3j2NAUNtd75H+B0xdfyWLmNq9YJGpy6gznA==",
|
||||
"version": "0.2003.10",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2003.10.tgz",
|
||||
"integrity": "sha512-2SWetxJzS8gRX6OKQstkWx37VRvZVgcEBDLsDSaeTjpnwh81A+niZQjAVRdwL0NEt1Wixk/RxfeUuCmdyyHvhQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@angular-devkit/core": "20.3.9",
|
||||
"@angular-devkit/core": "20.3.10",
|
||||
"rxjs": "7.8.2"
|
||||
},
|
||||
"engines": {
|
||||
@@ -293,9 +293,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@angular-devkit/core": {
|
||||
"version": "20.3.9",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.3.9.tgz",
|
||||
"integrity": "sha512-bXsAGIUb4p60x548YmvnMvjwd3FwWz6re1uTM7dV0XH8nQn3XMhOQ3Q3sAckzJHxkDuaRhB3K/a4kupoOmVfTQ==",
|
||||
"version": "20.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.3.10.tgz",
|
||||
"integrity": "sha512-COOT2eVebDwHhwENk12VR6m0wjL8D7p0dncEHF15zaBt1IXEnVhGESjSrs5klnPnt5T55qCBKyCTaeK7i/cS8Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -321,13 +321,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@angular-devkit/schematics": {
|
||||
"version": "20.3.9",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-20.3.9.tgz",
|
||||
"integrity": "sha512-oaIjAKPmHMZBTC0met5M7dbXBeZnCNwmHacT/kBHNVBAz/NI95fuAfb2P0Jxt7gWdQXejDSxWp0tL+sZIyO0xw==",
|
||||
"version": "20.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-20.3.10.tgz",
|
||||
"integrity": "sha512-2N2WF9lj+kr3uCG4+vFadYCL5hAT4dxMgzwScSdOqSd0O+GZD0CzKbDzlfvWIWC/ZealC5Sh4dFEQaRfmy72xA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@angular-devkit/core": "20.3.9",
|
||||
"@angular-devkit/core": "20.3.10",
|
||||
"jsonc-parser": "3.3.1",
|
||||
"magic-string": "0.30.17",
|
||||
"ora": "8.2.0",
|
||||
@@ -340,14 +340,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/build": {
|
||||
"version": "20.3.9",
|
||||
"resolved": "https://registry.npmjs.org/@angular/build/-/build-20.3.9.tgz",
|
||||
"integrity": "sha512-Ulimvg6twPSCraaZECEmENfKBlD4M1yqeHlg6dCzFNM4xcwaGUnuG6O3cIQD59DaEvaG73ceM2y8ftYdxAwFow==",
|
||||
"version": "20.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@angular/build/-/build-20.3.10.tgz",
|
||||
"integrity": "sha512-nQrj1nMNZygYDilThc7hPrD6/NIWF/BOSgMfE4VkXQp8d0QronP3HFJ/h77MeoughMRFRhix0pqQSlXJQ2SGTQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ampproject/remapping": "2.3.0",
|
||||
"@angular-devkit/architect": "0.2003.9",
|
||||
"@angular-devkit/architect": "0.2003.10",
|
||||
"@babel/core": "7.28.3",
|
||||
"@babel/helper-annotate-as-pure": "7.27.3",
|
||||
"@babel/helper-split-export-declaration": "7.24.7",
|
||||
@@ -389,7 +389,7 @@
|
||||
"@angular/platform-browser": "^20.0.0",
|
||||
"@angular/platform-server": "^20.0.0",
|
||||
"@angular/service-worker": "^20.0.0",
|
||||
"@angular/ssr": "^20.3.9",
|
||||
"@angular/ssr": "^20.3.10",
|
||||
"karma": "^6.4.0",
|
||||
"less": "^4.2.0",
|
||||
"ng-packagr": "^20.0.0",
|
||||
@@ -439,11 +439,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/cdk": {
|
||||
"version": "20.2.12",
|
||||
"resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-20.2.12.tgz",
|
||||
"integrity": "sha512-hz8GtiMy3N9/e8407ZfrByHD5GEC4SkWtxyUknWuTM9P88AOie0jDZ6CfQg9gQ0OJX+6BAbJV3RpYZA1uzNUqA==",
|
||||
"version": "20.2.13",
|
||||
"resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-20.2.13.tgz",
|
||||
"integrity": "sha512-h1jTkCmJ/rEQQMkxgKFMCBOrMfjZEnppgdekNmSTerwdVp4vdosTDTzFH/kwiOGFeRClffmvqQ2XLG8mQOKOtA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"parse5": "^8.0.0",
|
||||
"tslib": "^2.3.0"
|
||||
@@ -455,19 +454,19 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/cli": {
|
||||
"version": "20.3.9",
|
||||
"resolved": "https://registry.npmjs.org/@angular/cli/-/cli-20.3.9.tgz",
|
||||
"integrity": "sha512-4eKpRDg96B20yrKJqjA24zgxYy1RiRd70FvF/KG1hqSowsWwtzydtEJ3VM6iFWS9t1D8truuVpKjMEnn1Y274A==",
|
||||
"version": "20.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@angular/cli/-/cli-20.3.10.tgz",
|
||||
"integrity": "sha512-CQzXScurBXSuMMn0jf6UYDItdggaM3bHYERKL4cUG1z5JqSozVFin1+TB1EjWYkddwdgC10R5xQurdMb+ahRNw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@angular-devkit/architect": "0.2003.9",
|
||||
"@angular-devkit/core": "20.3.9",
|
||||
"@angular-devkit/schematics": "20.3.9",
|
||||
"@angular-devkit/architect": "0.2003.10",
|
||||
"@angular-devkit/core": "20.3.10",
|
||||
"@angular-devkit/schematics": "20.3.10",
|
||||
"@inquirer/prompts": "7.8.2",
|
||||
"@listr2/prompt-adapter-inquirer": "3.0.1",
|
||||
"@modelcontextprotocol/sdk": "1.17.3",
|
||||
"@schematics/angular": "20.3.9",
|
||||
"@schematics/angular": "20.3.10",
|
||||
"@yarnpkg/lockfile": "1.1.0",
|
||||
"algoliasearch": "5.35.0",
|
||||
"ini": "5.0.0",
|
||||
@@ -490,11 +489,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/common": {
|
||||
"version": "20.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@angular/common/-/common-20.3.10.tgz",
|
||||
"integrity": "sha512-12fEzvKbEqjqy1fSk9DMYlJz6dF1MJVXuC5BB+oWWJpd+2lfh4xJ62pkvvLGAICI89hfM5n9Cy5kWnXwnqPZsA==",
|
||||
"version": "20.3.12",
|
||||
"resolved": "https://registry.npmjs.org/@angular/common/-/common-20.3.12.tgz",
|
||||
"integrity": "sha512-rFcDfe67ffrb435C6t2lc27WGbizeOcgce30tUhH0iezwEvU+kHHWezXXX6Ylx3TFgqGkhcxL0fliuFYrpM1Vw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
},
|
||||
@@ -502,16 +500,15 @@
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/core": "20.3.10",
|
||||
"@angular/core": "20.3.12",
|
||||
"rxjs": "^6.5.3 || ^7.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/compiler": {
|
||||
"version": "20.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-20.3.10.tgz",
|
||||
"integrity": "sha512-cW939Lr8GZjPSYfbQKIDNrUaHWmn2M+zBbERThfq5skLuY+xM60bJFv4NqBekfX6YqKLCY62ilUZlnImYIXaqA==",
|
||||
"version": "20.3.12",
|
||||
"resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-20.3.12.tgz",
|
||||
"integrity": "sha512-bGESKz97nWiEQ/sydTq/Lzv3zlLvDb8t0msLG5Xti7Ch1EdLddXS8d2D/zFsjiGbAUKVsT6RgPCLHYoi4ocbhA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
},
|
||||
@@ -520,12 +517,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/compiler-cli": {
|
||||
"version": "20.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-20.3.10.tgz",
|
||||
"integrity": "sha512-9BemvpFxA26yIVdu8ROffadMkEdlk/AQQ2Jb486w7RPkrvUQ0pbEJukhv9aryJvhbMopT66S5H/j4ipOUMzmzQ==",
|
||||
"version": "20.3.12",
|
||||
"resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-20.3.12.tgz",
|
||||
"integrity": "sha512-3SJkexqsydYjIs0iLiJr5AdwkvumpzvjJM6s76iaxXHkRll5k/vM0wqkXLlSIwieBrecO9D4J73lDLWDevXl5A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/core": "7.28.3",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14",
|
||||
@@ -544,7 +540,7 @@
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/compiler": "20.3.10",
|
||||
"@angular/compiler": "20.3.12",
|
||||
"typescript": ">=5.8 <6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
@@ -554,11 +550,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/core": {
|
||||
"version": "20.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@angular/core/-/core-20.3.10.tgz",
|
||||
"integrity": "sha512-g99Qe+NOVo72OLxowVF9NjCckswWYHmvO7MgeiZTDJbTjF9tXH96dMx7AWq76/GUinV10sNzDysVW16NoAbCRQ==",
|
||||
"version": "20.3.12",
|
||||
"resolved": "https://registry.npmjs.org/@angular/core/-/core-20.3.12.tgz",
|
||||
"integrity": "sha512-K7vibMr55a7+EsuDhkg4Pk+ELuMm12olllwqL/CiQUcHXZ9Zgc4KYGTUuxWB69qJCG90gdSZS7tm5Dx0wDcyjg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
},
|
||||
@@ -566,7 +561,7 @@
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/compiler": "20.3.10",
|
||||
"@angular/compiler": "20.3.12",
|
||||
"rxjs": "^6.5.3 || ^7.4.0",
|
||||
"zone.js": "~0.15.0"
|
||||
},
|
||||
@@ -580,11 +575,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/forms": {
|
||||
"version": "20.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@angular/forms/-/forms-20.3.10.tgz",
|
||||
"integrity": "sha512-9yWr51EUauTEINB745AaHwZNTHLpXIm4uxuykxzOg+g2QskEgVfH26uS8G2ogdNuwYpB8wnsXWr34qhM3qgOWw==",
|
||||
"version": "20.3.12",
|
||||
"resolved": "https://registry.npmjs.org/@angular/forms/-/forms-20.3.12.tgz",
|
||||
"integrity": "sha512-O0Jy8ScaN3qVipDfR4s0SIxGrz/+MbCdmR05ZYVWf1W5P3dvETKt9WNjX9fYYV47GdgSveyFjuCR2NvWlv94zA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
},
|
||||
@@ -592,22 +586,22 @@
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/common": "20.3.10",
|
||||
"@angular/core": "20.3.10",
|
||||
"@angular/platform-browser": "20.3.10",
|
||||
"@angular/common": "20.3.12",
|
||||
"@angular/core": "20.3.12",
|
||||
"@angular/platform-browser": "20.3.12",
|
||||
"rxjs": "^6.5.3 || ^7.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/material": {
|
||||
"version": "20.2.12",
|
||||
"resolved": "https://registry.npmjs.org/@angular/material/-/material-20.2.12.tgz",
|
||||
"integrity": "sha512-DVenIZmV87qhDBlI2Xv3Z+b+IFI1s4wcZsFrzDi1FBMxKLsltJwMHf4SAmuqY0Mm/2Vw7HEZlfE130TuqjG8Ig==",
|
||||
"version": "20.2.13",
|
||||
"resolved": "https://registry.npmjs.org/@angular/material/-/material-20.2.13.tgz",
|
||||
"integrity": "sha512-9pjp2mULOxojYzOO7qdqt/gSVLrpYBwsIM3K0fxp+mNEcJgNjIxvmRKx46LY9+v0yrPY9puoQvP/T2C+o1+xsw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/cdk": "20.2.12",
|
||||
"@angular/cdk": "20.2.13",
|
||||
"@angular/common": "^20.0.0 || ^21.0.0",
|
||||
"@angular/core": "^20.0.0 || ^21.0.0",
|
||||
"@angular/forms": "^20.0.0 || ^21.0.0",
|
||||
@@ -616,11 +610,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/platform-browser": {
|
||||
"version": "20.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-20.3.10.tgz",
|
||||
"integrity": "sha512-UV8CGoB5P3FmJciI3/I/n3L7C3NVgGh7bIlZ1BaB/qJDtv0Wq0rRAGwmT/Z3gwmrRtfHZWme7/CeQ2CYJmMyUQ==",
|
||||
"version": "20.3.12",
|
||||
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-20.3.12.tgz",
|
||||
"integrity": "sha512-14KQsXZyaQhbRwFz1W58CtbXQc9L+mfuHBgwQjQo99422Yk0ye5WVMb6DHH7dH671qFVqL0XL7zdOPBebaAnJQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
},
|
||||
@@ -628,9 +621,9 @@
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/animations": "20.3.10",
|
||||
"@angular/common": "20.3.10",
|
||||
"@angular/core": "20.3.10"
|
||||
"@angular/animations": "20.3.12",
|
||||
"@angular/common": "20.3.12",
|
||||
"@angular/core": "20.3.12"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@angular/animations": {
|
||||
@@ -639,9 +632,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/router": {
|
||||
"version": "20.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@angular/router/-/router-20.3.10.tgz",
|
||||
"integrity": "sha512-Z03cfH1jgQ7XMDJj4R8qAGqivcvhdG3wYBwaiN1K1ODBgPhbFKNeD4stKqYp7xBNtswmM2O2jMxrL/Djwju4Gg==",
|
||||
"version": "20.3.12",
|
||||
"resolved": "https://registry.npmjs.org/@angular/router/-/router-20.3.12.tgz",
|
||||
"integrity": "sha512-hUipb9JI/Euy3bdlhzkcWlw3cTyssPTVTDwSvyGxWO4i+UKATQYmxh8EDOrDYzFp6Aexiy0Hff/H8umdsn6ZdA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
@@ -650,9 +643,9 @@
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/common": "20.3.10",
|
||||
"@angular/core": "20.3.10",
|
||||
"@angular/platform-browser": "20.3.10",
|
||||
"@angular/common": "20.3.12",
|
||||
"@angular/core": "20.3.12",
|
||||
"@angular/platform-browser": "20.3.12",
|
||||
"rxjs": "^6.5.3 || ^7.4.0"
|
||||
}
|
||||
},
|
||||
@@ -687,7 +680,6 @@
|
||||
"integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@ampproject/remapping": "^2.2.0",
|
||||
"@babel/code-frame": "^7.27.1",
|
||||
@@ -1403,9 +1395,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@infinimotion/model-frontend": {
|
||||
"version": "0.0.102",
|
||||
"resolved": "https://git.infinimotion.de/api/packages/infinimotion/npm/%40infinimotion%2Fmodel-frontend/-/0.0.102/model-frontend-0.0.102.tgz",
|
||||
"integrity": "sha512-NJV9bSBubdOZ1GBIe9To3o/hh6AZscJcTyaZY2nGmMxH+GhtvO1AHmjhrQeRjwAKFiwZMEwEg4ktFiOAp3MTMQ==",
|
||||
"version": "0.0.116",
|
||||
"resolved": "https://git.infinimotion.de/api/packages/infinimotion/npm/%40infinimotion%2Fmodel-frontend/-/0.0.116/model-frontend-0.0.116.tgz",
|
||||
"integrity": "sha512-kGnZW1klIHzdL/44fOEUrDTVSkQSxErgHqwuSb6eQDLUW7Q9ZDL389LFR2haJCpqcYWIjsG2HjpvkJwrm2ctpA==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/@inquirer/ansi": {
|
||||
@@ -1419,14 +1411,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@inquirer/checkbox": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.3.1.tgz",
|
||||
"integrity": "sha512-rOcLotrptYIy59SGQhKlU0xBg1vvcVl2FdPIEclUvKHh0wo12OfGkId/01PIMJ/V+EimJ77t085YabgnQHBa5A==",
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.3.2.tgz",
|
||||
"integrity": "sha512-VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@inquirer/ansi": "^1.0.2",
|
||||
"@inquirer/core": "^10.3.1",
|
||||
"@inquirer/core": "^10.3.2",
|
||||
"@inquirer/figures": "^1.0.15",
|
||||
"@inquirer/type": "^3.0.10",
|
||||
"yoctocolors-cjs": "^2.1.3"
|
||||
@@ -1466,9 +1458,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@inquirer/core": {
|
||||
"version": "10.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.3.1.tgz",
|
||||
"integrity": "sha512-hzGKIkfomGFPgxKmnKEKeA+uCYBqC+TKtRx5LgyHRCrF6S2MliwRIjp3sUaWwVzMp7ZXVs8elB0Tfe682Rpg4w==",
|
||||
"version": "10.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.3.2.tgz",
|
||||
"integrity": "sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -1476,7 +1468,7 @@
|
||||
"@inquirer/figures": "^1.0.15",
|
||||
"@inquirer/type": "^3.0.10",
|
||||
"cli-width": "^4.1.0",
|
||||
"mute-stream": "^3.0.0",
|
||||
"mute-stream": "^2.0.0",
|
||||
"signal-exit": "^4.1.0",
|
||||
"wrap-ansi": "^6.2.0",
|
||||
"yoctocolors-cjs": "^2.1.3"
|
||||
@@ -1494,13 +1486,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@inquirer/editor": {
|
||||
"version": "4.2.22",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.22.tgz",
|
||||
"integrity": "sha512-8yYZ9TCbBKoBkzHtVNMF6PV1RJEUvMlhvmS3GxH4UvXMEHlS45jFyqFy0DU+K42jBs5slOaA78xGqqqWAx3u6A==",
|
||||
"version": "4.2.23",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.23.tgz",
|
||||
"integrity": "sha512-aLSROkEwirotxZ1pBaP8tugXRFCxW94gwrQLxXfrZsKkfjOYC1aRvAZuhpJOb5cu4IBTJdsCigUlf2iCOu4ZDQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@inquirer/core": "^10.3.1",
|
||||
"@inquirer/core": "^10.3.2",
|
||||
"@inquirer/external-editor": "^1.0.3",
|
||||
"@inquirer/type": "^3.0.10"
|
||||
},
|
||||
@@ -1517,13 +1509,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@inquirer/expand": {
|
||||
"version": "4.0.22",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.22.tgz",
|
||||
"integrity": "sha512-9XOjCjvioLjwlq4S4yXzhvBmAXj5tG+jvva0uqedEsQ9VD8kZ+YT7ap23i0bIXOtow+di4+u3i6u26nDqEfY4Q==",
|
||||
"version": "4.0.23",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.23.tgz",
|
||||
"integrity": "sha512-nRzdOyFYnpeYTTR2qFwEVmIWypzdAx/sIkCMeTNTcflFOovfqUk+HcFhQQVBftAh9gmGrpFj6QcGEqrDMDOiew==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@inquirer/core": "^10.3.1",
|
||||
"@inquirer/core": "^10.3.2",
|
||||
"@inquirer/type": "^3.0.10",
|
||||
"yoctocolors-cjs": "^2.1.3"
|
||||
},
|
||||
@@ -1572,13 +1564,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@inquirer/input": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.3.0.tgz",
|
||||
"integrity": "sha512-h4fgse5zeGsBSW3cRQqu9a99OXRdRsNCvHoBqVmz40cjYjYFzcfwD0KA96BHIPlT7rZw0IpiefQIqXrjbzjS4Q==",
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.3.1.tgz",
|
||||
"integrity": "sha512-kN0pAM4yPrLjJ1XJBjDxyfDduXOuQHrBB8aLDMueuwUGn+vNpF7Gq7TvyVxx8u4SHlFFj4trmj+a2cbpG4Jn1g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@inquirer/core": "^10.3.1",
|
||||
"@inquirer/core": "^10.3.2",
|
||||
"@inquirer/type": "^3.0.10"
|
||||
},
|
||||
"engines": {
|
||||
@@ -1594,13 +1586,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@inquirer/number": {
|
||||
"version": "3.0.22",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.22.tgz",
|
||||
"integrity": "sha512-oAdMJXz++fX58HsIEYmvuf5EdE8CfBHHXjoi9cTcQzgFoHGZE+8+Y3P38MlaRMeBvAVnkWtAxMUF6urL2zYsbg==",
|
||||
"version": "3.0.23",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.23.tgz",
|
||||
"integrity": "sha512-5Smv0OK7K0KUzUfYUXDXQc9jrf8OHo4ktlEayFlelCjwMXz0299Y8OrI+lj7i4gCBY15UObk76q0QtxjzFcFcg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@inquirer/core": "^10.3.1",
|
||||
"@inquirer/core": "^10.3.2",
|
||||
"@inquirer/type": "^3.0.10"
|
||||
},
|
||||
"engines": {
|
||||
@@ -1616,14 +1608,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@inquirer/password": {
|
||||
"version": "4.0.22",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.22.tgz",
|
||||
"integrity": "sha512-CbdqK1ioIr0Y3akx03k/+Twf+KSlHjn05hBL+rmubMll7PsDTGH0R4vfFkr+XrkB0FOHrjIwVP9crt49dgt+1g==",
|
||||
"version": "4.0.23",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.23.tgz",
|
||||
"integrity": "sha512-zREJHjhT5vJBMZX/IUbyI9zVtVfOLiTO66MrF/3GFZYZ7T4YILW5MSkEYHceSii/KtRk+4i3RE7E1CUXA2jHcA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@inquirer/ansi": "^1.0.2",
|
||||
"@inquirer/core": "^10.3.1",
|
||||
"@inquirer/core": "^10.3.2",
|
||||
"@inquirer/type": "^3.0.10"
|
||||
},
|
||||
"engines": {
|
||||
@@ -1644,7 +1636,6 @@
|
||||
"integrity": "sha512-nqhDw2ZcAUrKNPwhjinJny903bRhI0rQhiDz1LksjeRxqa36i3l75+4iXbOy0rlDpLJGxqtgoPavQjmmyS5UJw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@inquirer/checkbox": "^4.2.1",
|
||||
"@inquirer/confirm": "^5.1.14",
|
||||
@@ -1670,13 +1661,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@inquirer/rawlist": {
|
||||
"version": "4.1.10",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.1.10.tgz",
|
||||
"integrity": "sha512-Du4uidsgTMkoH5izgpfyauTL/ItVHOLsVdcY+wGeoGaG56BV+/JfmyoQGniyhegrDzXpfn3D+LFHaxMDRygcAw==",
|
||||
"version": "4.1.11",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.1.11.tgz",
|
||||
"integrity": "sha512-+LLQB8XGr3I5LZN/GuAHo+GpDJegQwuPARLChlMICNdwW7OwV2izlCSCxN6cqpL0sMXmbKbFcItJgdQq5EBXTw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@inquirer/core": "^10.3.1",
|
||||
"@inquirer/core": "^10.3.2",
|
||||
"@inquirer/type": "^3.0.10",
|
||||
"yoctocolors-cjs": "^2.1.3"
|
||||
},
|
||||
@@ -1693,13 +1684,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@inquirer/search": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.2.1.tgz",
|
||||
"integrity": "sha512-cKiuUvETublmTmaOneEermfG2tI9ABpb7fW/LqzZAnSv4ZaJnbEis05lOkiBuYX5hNdnX0Q9ryOQyrNidb55WA==",
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.2.2.tgz",
|
||||
"integrity": "sha512-p2bvRfENXCZdWF/U2BXvnSI9h+tuA8iNqtUKb9UWbmLYCRQxd8WkvwWvYn+3NgYaNwdUkHytJMGG4MMLucI1kA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@inquirer/core": "^10.3.1",
|
||||
"@inquirer/core": "^10.3.2",
|
||||
"@inquirer/figures": "^1.0.15",
|
||||
"@inquirer/type": "^3.0.10",
|
||||
"yoctocolors-cjs": "^2.1.3"
|
||||
@@ -1717,14 +1708,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@inquirer/select": {
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.4.1.tgz",
|
||||
"integrity": "sha512-E9hbLU4XsNe2SAOSsFrtYtYQDVi1mfbqJrPDvXKnGlnRiApBdWMJz7r3J2Ff38AqULkPUD3XjQMD4492TymD7Q==",
|
||||
"version": "4.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.4.2.tgz",
|
||||
"integrity": "sha512-l4xMuJo55MAe+N7Qr4rX90vypFwCajSakx59qe/tMaC1aEHWLyw68wF4o0A4SLAY4E0nd+Vt+EyskeDIqu1M6w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@inquirer/ansi": "^1.0.2",
|
||||
"@inquirer/core": "^10.3.1",
|
||||
"@inquirer/core": "^10.3.2",
|
||||
"@inquirer/figures": "^1.0.15",
|
||||
"@inquirer/type": "^3.0.10",
|
||||
"yoctocolors-cjs": "^2.1.3"
|
||||
@@ -3429,14 +3420,14 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@schematics/angular": {
|
||||
"version": "20.3.9",
|
||||
"resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-20.3.9.tgz",
|
||||
"integrity": "sha512-XkgTwGhhrx+MVi2+TFO32d6Es5Uezzx7Y7B/e2ulDlj08bizxQj+9wkeLt5+bR8JWODHpEntZn/Xd5WvXnODGA==",
|
||||
"version": "20.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-20.3.10.tgz",
|
||||
"integrity": "sha512-F9ntS2CElpoWlENf4b03nwdTcN9Ri0Nb4SAE/pfRw3In09h2UHxYyf1ex9jqQt70xltDg4wvyuc3mMs+JlSx9A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@angular-devkit/core": "20.3.9",
|
||||
"@angular-devkit/schematics": "20.3.9",
|
||||
"@angular-devkit/core": "20.3.10",
|
||||
"@angular-devkit/schematics": "20.3.10",
|
||||
"jsonc-parser": "3.3.1"
|
||||
},
|
||||
"engines": {
|
||||
@@ -3865,9 +3856,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/jasmine": {
|
||||
"version": "5.1.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-5.1.12.tgz",
|
||||
"integrity": "sha512-1BzPxNsFDLDfj9InVR3IeY0ZVf4o9XV+4mDqoCfyPkbsA7dYyKAPAb2co6wLFlHcvxPlt1wShm7zQdV7uTfLGA==",
|
||||
"version": "5.1.13",
|
||||
"resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-5.1.13.tgz",
|
||||
"integrity": "sha512-MYCcDkruFc92LeYZux5BC0dmqo2jk+M5UIZ4/oFnAPCXN9mCcQhLyj7F3/Za7rocVyt5YRr1MmqJqFlvQ9LVcg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
@@ -3877,7 +3868,6 @@
|
||||
"integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~7.16.0"
|
||||
}
|
||||
@@ -4097,9 +4087,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/baseline-browser-mapping": {
|
||||
"version": "2.8.26",
|
||||
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.26.tgz",
|
||||
"integrity": "sha512-73lC1ugzwoaWCLJ1LvOgrR5xsMLTqSKIEoMHVtL9E/HNk0PXtTM76ZIm84856/SF7Nv8mPZxKoBsgpm0tR1u1Q==",
|
||||
"version": "2.8.29",
|
||||
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.29.tgz",
|
||||
"integrity": "sha512-sXdt2elaVnhpDNRDz+1BDx1JQoJRuNk7oVlAlbGiFkLikHCAQiccexF/9e91zVi6RCgqspl04aP+6Cnl9zRLrA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
@@ -4224,7 +4214,6 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.8.25",
|
||||
"caniuse-lite": "^1.0.30001754",
|
||||
@@ -4412,9 +4401,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001754",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001754.tgz",
|
||||
"integrity": "sha512-x6OeBXueoAceOmotzx3PO4Zpt4rzpeIFsSr6AAePTZxSkXiYDUmpypEl7e2+8NCd9bD7bXjqyef8CJYPC1jfxg==",
|
||||
"version": "1.0.30001755",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001755.tgz",
|
||||
"integrity": "sha512-44V+Jm6ctPj7R52Na4TLi3Zri4dWUljJd+RDm+j8LtNCc/ihLCT+X1TzoOAkRETEWqjuLnh9581Tl80FvK7jVA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -4992,9 +4981,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.5.250",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.250.tgz",
|
||||
"integrity": "sha512-/5UMj9IiGDMOFBnN4i7/Ry5onJrAGSbOGo3s9FEKmwobGq6xw832ccET0CE3CkkMBZ8GJSlUIesZofpyurqDXw==",
|
||||
"version": "1.5.254",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.254.tgz",
|
||||
"integrity": "sha512-DcUsWpVhv9svsKRxnSCZ86SjD+sp32SGidNB37KpqXJncp1mfUgKbHvBomE89WJDbfVKw1mdv5+ikrvd43r+Bg==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
@@ -5353,7 +5342,6 @@
|
||||
"integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"accepts": "^2.0.0",
|
||||
"body-parser": "^2.2.0",
|
||||
@@ -6317,8 +6305,7 @@
|
||||
"resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-5.9.0.tgz",
|
||||
"integrity": "sha512-OMUvF1iI6+gSRYOhMrH4QYothVLN9C3EJ6wm4g7zLJlnaTl8zbaPOr0bTw70l7QxkoM7sVFOWo83u9B2Fe2Zng==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/jiti": {
|
||||
"version": "2.6.1",
|
||||
@@ -6412,7 +6399,6 @@
|
||||
"integrity": "sha512-LrtUxbdvt1gOpo3gxG+VAJlJAEMhbWlM4YrFQgql98FwF7+K8K12LYO4hnDdUkNjeztYrOXEMqgTajSWgmtI/w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@colors/colors": "1.5.0",
|
||||
"body-parser": "^1.19.0",
|
||||
@@ -7129,7 +7115,6 @@
|
||||
"integrity": "sha512-SL0JY3DaxylDuo/MecFeiC+7pedM0zia33zl0vcjgwcq1q1FWWF1To9EIauPbl8GbMCU0R2e0uJ8bZunhYKD2g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"cli-truncate": "^4.0.0",
|
||||
"colorette": "^2.0.20",
|
||||
@@ -7743,13 +7728,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/mute-stream": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-3.0.0.tgz",
|
||||
"integrity": "sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==",
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz",
|
||||
"integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": "^20.17.0 || >=22.9.0"
|
||||
"node": "^18.17.0 || >=20.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
@@ -8991,7 +8976,6 @@
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
|
||||
"integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
@@ -9048,7 +9032,6 @@
|
||||
"integrity": "sha512-9GUyuksjw70uNpb1MTYWsH9MQHOHY6kwfnkafC24+7aOMZn9+rVMBxRbLvw756mrBFbIsFg6Xw9IkR2Fnn3k+Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"chokidar": "^4.0.0",
|
||||
"immutable": "^5.0.2",
|
||||
@@ -9903,8 +9886,7 @@
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"license": "0BSD",
|
||||
"peer": true
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/tuf-js": {
|
||||
"version": "3.1.0",
|
||||
@@ -9942,7 +9924,6 @@
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
@@ -10129,7 +10110,6 @@
|
||||
"integrity": "sha512-uzcxnSDVjAopEUjljkWh8EIrg6tlzrjFUfMcR1EVsRDGwf/ccef0qQPRyOrROwhrTDaApueq+ja+KLPlzR/zdg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.25.0",
|
||||
"fdir": "^6.5.0",
|
||||
@@ -10532,7 +10512,6 @@
|
||||
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
@@ -10551,8 +10530,7 @@
|
||||
"version": "0.15.1",
|
||||
"resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.15.1.tgz",
|
||||
"integrity": "sha512-XE96n56IQpJM7NAoXswY3XRLcWFW83xe0BiAOeMD7K5k5xecOeul3Qcpx6GqEeeHNkW5DWL5zOyTbEfB4eti8w==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
"@angular/material": "^20.2.9",
|
||||
"@angular/platform-browser": "^20.3.0",
|
||||
"@angular/router": "^20.3.0",
|
||||
"@infinimotion/model-frontend": "^0.0.102",
|
||||
"@infinimotion/model-frontend": "^0.0.116",
|
||||
"@tailwindcss/postcss": "^4.1.14",
|
||||
"angularx-qrcode": "^20.0.0",
|
||||
"canvas-confetti": "^1.9.4",
|
||||
|
||||
@@ -23,6 +23,10 @@ import { MatDividerModule } from '@angular/material/divider';
|
||||
import { MatDialogClose, MatDialogTitle, MatDialogContent, MatDialogActions } from "@angular/material/dialog";
|
||||
import { MatStepperModule } from '@angular/material/stepper';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
import { MatBadgeModule } from '@angular/material/badge';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { MatPaginatorModule } from '@angular/material/paginator';
|
||||
import { MatTableModule } from '@angular/material/table';
|
||||
|
||||
import { HeaderComponent } from './header/header.component';
|
||||
import { HomeComponent } from './home/home.component';
|
||||
@@ -62,6 +66,8 @@ import { PurchaseSuccessComponent } from './purchase-success/purchase-success.co
|
||||
import { PurchaseFailedComponent } from './purchase-failed/purchase-failed.component';
|
||||
import { TicketSmallComponent } from './ticket-small/ticket-small.component';
|
||||
import { TicketListComponent } from './ticket-list/ticket-list.component';
|
||||
import { StatisticsComponent } from './statistics/statistics.component';
|
||||
import { ZoomWarningComponent } from './zoom-warning/zoom-warning.component';
|
||||
import { PricelistComponent } from './pricelist/pricelist.component';
|
||||
|
||||
|
||||
@@ -106,6 +112,8 @@ import { PricelistComponent } from './pricelist/pricelist.component';
|
||||
PurchaseFailedComponent,
|
||||
TicketSmallComponent,
|
||||
TicketListComponent,
|
||||
StatisticsComponent,
|
||||
ZoomWarningComponent,
|
||||
PricelistComponent,
|
||||
],
|
||||
imports: [
|
||||
@@ -135,6 +143,10 @@ import { PricelistComponent } from './pricelist/pricelist.component';
|
||||
NgxMaskDirective,
|
||||
NgxMaskPipe,
|
||||
QRCodeComponent,
|
||||
MatBadgeModule,
|
||||
MatTooltipModule,
|
||||
MatPaginatorModule,
|
||||
MatTableModule,
|
||||
],
|
||||
providers: [
|
||||
provideBrowserGlobalErrorListeners(),
|
||||
|
||||
@@ -8,12 +8,13 @@ import { ScheduleComponent } from './schedule/schedule.component';
|
||||
import { TheaterOverlayComponent} from './theater-overlay/theater-overlay.component';
|
||||
import { MovieImporterComponent } from './movie-importer/movie-importer.component';
|
||||
import { AuthGuard } from './auth.guard';
|
||||
import {StatisticsComponent} from './statistics/statistics.component';
|
||||
import { PricelistComponent } from './pricelist/pricelist.component';
|
||||
|
||||
const routes: Routes = [
|
||||
// Seiten ohne Layout
|
||||
{ path: 'landing', component: HomeComponent },
|
||||
{ path: 'poc-model', component: PocModelComponent },
|
||||
{ path: 'poc-model', component: PocModelComponent, data: { allowMobile: true } },
|
||||
|
||||
// Seiten mit MainLayout
|
||||
{
|
||||
@@ -28,7 +29,13 @@ const routes: Routes = [
|
||||
canActivate: [AuthGuard],
|
||||
data: { roles: ['admin'] }, // Array von erlaubten Rollen. Derzeit gäbe es 'admin' und 'employee'
|
||||
},
|
||||
{ path: 'selection/performance/:id', component: TheaterOverlayComponent},
|
||||
{ path: 'performance/:performanceId/checkout', component: TheaterOverlayComponent},
|
||||
{
|
||||
path: 'admin/statistics',
|
||||
component: StatisticsComponent,
|
||||
canActivate: [AuthGuard],
|
||||
data: { roles: ['admin'] }, // Array von erlaubten Rollen. Derzeit gäbe es 'admin' und 'employee'
|
||||
},
|
||||
{ path: 'prices', component: PricelistComponent },
|
||||
],
|
||||
},
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
<app-zoom-warning></app-zoom-warning>
|
||||
<router-outlet />
|
||||
|
||||
29
src/app/device-detection.service.ts
Normal file
29
src/app/device-detection.service.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class DeviceDetectionService {
|
||||
private _isMobile: boolean;
|
||||
|
||||
constructor() {
|
||||
this._isMobile = this.checkIfMobile();
|
||||
}
|
||||
|
||||
isMobile(): boolean {
|
||||
return this._isMobile;
|
||||
}
|
||||
|
||||
private checkIfMobile(): boolean {
|
||||
const userAgent = navigator.userAgent.toLowerCase();
|
||||
const isMobileUA = /android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(userAgent);
|
||||
const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
|
||||
const isSmallScreen = window.innerWidth < 768;
|
||||
|
||||
return isMobileUA || (isTouchDevice && isSmallScreen);
|
||||
}
|
||||
|
||||
recheckDevice(): void {
|
||||
this._isMobile = this.checkIfMobile();
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,13 @@
|
||||
import { Kinosaal, Sitzplatz, Vorstellung, Film, OmdbSearch, Bestellung, Eintrittskarte } from '@infinimotion/model-frontend';
|
||||
import {
|
||||
Kinosaal,
|
||||
Sitzplatz,
|
||||
Vorstellung,
|
||||
Film,
|
||||
OmdbSearch,
|
||||
Bestellung,
|
||||
Eintrittskarte,
|
||||
StatisticsFilm, StatisticsVorstellung
|
||||
} from '@infinimotion/model-frontend';
|
||||
import { HttpClient } from "@angular/common/http";
|
||||
import { inject, Injectable } from "@angular/core";
|
||||
import { Observable } from "rxjs";
|
||||
@@ -44,6 +53,12 @@ export class HttpService {
|
||||
}
|
||||
|
||||
|
||||
/* POST /api/order-transaction/create */
|
||||
saveAddOrder(req: {order:Bestellung, tickets:Eintrittskarte[]}): Observable<{order:Bestellung, tickets:Eintrittskarte[]}> {
|
||||
return this.http.post<{order: Bestellung, tickets: Eintrittskarte[]}>(`${this.baseUrl}order-transaction/create`, req);
|
||||
}
|
||||
|
||||
|
||||
/* Eintrittskarte APIs */
|
||||
|
||||
/* GET /api/eintrittskarte/{id} */
|
||||
@@ -155,7 +170,11 @@ export class HttpService {
|
||||
|
||||
/* GET /api/show-seats/{show} */
|
||||
getSeatsByShowId(show: number): Observable<{ seats: Sitzplatz[], reserved: Sitzplatz[], booked: Sitzplatz[] }> {
|
||||
return this.http.get<{seats:Sitzplatz[], reserved:Sitzplatz[], booked:Sitzplatz[]}>(`${this.baseUrl}show-seats/${show}`);
|
||||
return this.http.get<{
|
||||
seats: Sitzplatz[],
|
||||
reserved: Sitzplatz[],
|
||||
booked: Sitzplatz[]
|
||||
}>(`${this.baseUrl}show-seats/${show}`);
|
||||
}
|
||||
|
||||
|
||||
@@ -172,4 +191,17 @@ export class HttpService {
|
||||
importMovie(imdbId: string): Observable<Film> {
|
||||
return this.http.post<Film>(`${this.baseUrl}importer/import?id=${imdbId}`, {})
|
||||
}
|
||||
|
||||
|
||||
/* Statistics APIs */
|
||||
|
||||
/* GET /api/statistics/movies */
|
||||
getMovieStatistics(): Observable<StatisticsFilm[]> {
|
||||
return this.http.get<StatisticsFilm[]>(`${this.baseUrl}statistics/movies`)
|
||||
}
|
||||
|
||||
/* GET /api/statistics/shows */
|
||||
getShowStatistics(): Observable<StatisticsVorstellung[]> {
|
||||
return this.http.get<StatisticsVorstellung[]>(`${this.baseUrl}statistics/shows`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<div class="flex flex-row items-center justify-center gap-8 h-1/1">
|
||||
<div class="max-w-xl m-auto">
|
||||
<div class="flex items-center h-1/1 justify-center space-x-30">
|
||||
<div class="max-w-xl">
|
||||
<section class="felx flex-row">
|
||||
<div class="flex items-center">
|
||||
<h1 class="text-3xl font-bold">
|
||||
Willkommen bei
|
||||
</h1>
|
||||
<div class="bg-gradient-to-r from-indigo-500 to-pink-600 bg-clip-text text-transparent text-3xl font-bold">
|
||||
<div class="bg-linear-to-r from-indigo-500 to-pink-600 bg-clip-text text-transparent text-3xl font-bold">
|
||||
InfiniMotion
|
||||
</div>
|
||||
<h1 class="text-3xl font-bold">! 🎬</h1>
|
||||
@@ -41,7 +41,7 @@
|
||||
Wir haben uns bei Gestaltung und Stil bewusst an bestehenden Kinowebsites orientiert.
|
||||
Dabei handelt es sich um eine rein stilistische Anlehnung; diese Seite verfolgt keinerlei kommerzielle Zwecke und dient ausschließlich universitären Zwecken.
|
||||
Marken, Designs oder Funktionalitäten, die bekannten Anbietern ähneln, sind nicht als Kopie zum Wettbewerb gedacht, sondern als pragmatische Inspirationsquelle im Rahmen der Praxisarbeit.
|
||||
<a href="https://infinimotion.de" target="_blank" class="bg-gradient-to-r from-indigo-500 to-pink-600 bg-clip-text text-transparent">
|
||||
<a href="https://infinimotion.de" target="_blank" class="bg-linear-to-r from-indigo-500 to-pink-600 bg-clip-text text-transparent">
|
||||
https://infinimotion.de
|
||||
</a>
|
||||
wird zum Projektende offline genommen.
|
||||
@@ -52,7 +52,7 @@
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="max-w-md mr-60 mt-10">
|
||||
<div class="mt-10">
|
||||
<mat-vertical-stepper [linear]="false" [selectedIndex]="5" class="always-open-stepper">
|
||||
|
||||
<mat-step
|
||||
@@ -81,7 +81,7 @@
|
||||
|
||||
<mat-step
|
||||
[completed]="isCompleted(3)"
|
||||
[editable]="isEditable(3)">
|
||||
[editable]="true">
|
||||
<ng-template matStepLabel>
|
||||
<span>Sprint #3: Vorstellungstickets reservieren und buchen</span>
|
||||
</ng-template>
|
||||
@@ -104,5 +104,5 @@
|
||||
</mat-step>
|
||||
|
||||
</mat-vertical-stepper>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Component } from '@angular/core';
|
||||
styleUrl: './main.component.css'
|
||||
})
|
||||
export class MainComponent {
|
||||
currentSprint = 3;
|
||||
currentSprint = 4;
|
||||
|
||||
isCompleted(index: number): boolean {
|
||||
return index <= this.currentSprint;
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
@if ( icon() ) {
|
||||
<mat-icon style="font-size: 35px; width: 35px; height: 35px; opacity: 50%;">{{ icon() }}</mat-icon>
|
||||
}
|
||||
<p class="text-2xl font-medium pl-2 bg-gradient-to-r from-indigo-500 to-pink-600 bg-clip-text text-transparent">
|
||||
{{ title() }}
|
||||
<p class="text-2xl font-medium pl-2 bg-linear-to-r from-indigo-500 to-pink-600 bg-clip-text text-transparent">
|
||||
{{ label() }}
|
||||
</p>
|
||||
</div>
|
||||
@if ( searchBar() ) {
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Component, input, output } from '@angular/core';
|
||||
styleUrl: './menu-header.component.css'
|
||||
})
|
||||
export class MenuHeaderComponent {
|
||||
title = input.required<string>();
|
||||
label = input.required<string>();
|
||||
icon = input<string>();
|
||||
|
||||
searchBar = input<boolean>(false);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<app-menu-header title="Film aus IMDb importieren" icon="cloud_download"></app-menu-header>
|
||||
<app-menu-header label="Film aus IMDb importieren" icon="cloud_download"></app-menu-header>
|
||||
|
||||
<div class="w-6/10 m-auto my-20">
|
||||
<form class="movie-search-form w-full" (ngSubmit)="DoSubmit()">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<a [routerLink]="route" class="bg-gray-200 m-2 flex flex-col items-center justify-between rounded-md overflow-hidden text-xl shadow-lg transform transition-all duration-300 hover:scale-105">
|
||||
|
||||
<div class="bg-gradient-to-r from-indigo-500 to-pink-600 w-full text-center text-white font-medium rounded-t-md py-0.5 px-2">
|
||||
<div class="bg-linear-to-r from-indigo-500 to-pink-600 w-full text-center text-white font-medium rounded-t-md py-0.5 px-2">
|
||||
<p>{{ hall() }}</p>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ export class MoviePerformanceComponent implements OnInit {
|
||||
route: string = '';
|
||||
|
||||
ngOnInit() {
|
||||
this.route = `../selection/performance/${this.id()}`;
|
||||
this.route = `../performance/${this.id()}/checkout`;
|
||||
}
|
||||
|
||||
startTime = computed(() =>
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
<div class="flex items-center space-x-4">
|
||||
|
||||
@if (searchControl.value && searchControl.value.length > 0) {
|
||||
<button mat-icon-button #tooltip="matTooltip" matTooltip="Filter löschen" matTooltipPosition="above" class="w-11! h-11! opacity-50" (click)="searchControl.setValue('')">
|
||||
<mat-icon style="font-size: 25px; width: 25px; height: 25px;">filter_alt_off</mat-icon>
|
||||
</button>
|
||||
}
|
||||
|
||||
<form class="movie-search-form w-88">
|
||||
<mat-form-field class="w-full" subscriptSizing="dynamic">
|
||||
<mat-label>Film suchen</mat-label>
|
||||
<input class="w-full" type="text" matInput [formControl]="searchControl" [matAutocomplete]="auto" (click)="searchControl.setValue('')">
|
||||
|
||||
<!-- @if (searchControl.hasError('filmNotFound')) { -->
|
||||
<!-- <mat-error>Film existiert nicht</mat-error> -->
|
||||
<!-- } -->
|
||||
<input class="w-full" type="text" matInput [formControl]="searchControl" [matAutocomplete]="auto">
|
||||
|
||||
<mat-autocomplete autoActiveFirstOption #auto="matAutocomplete">
|
||||
@for (option of filteredOptions | async; track option) {
|
||||
@@ -14,3 +18,5 @@
|
||||
</mat-autocomplete>
|
||||
</mat-form-field>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -12,6 +12,7 @@ export class NavbarComponent {
|
||||
{label: 'Programm', path: '/schedule'},
|
||||
{label: 'Preise', path: '/prices'},
|
||||
{label: 'Film importieren', path: '/admin/movie-importer'},
|
||||
{label: 'Statistiken', path: '/admin/statistics'},
|
||||
]
|
||||
|
||||
private auth = inject(AuthService)
|
||||
|
||||
@@ -161,37 +161,31 @@ export class OrderComponent {
|
||||
|
||||
|
||||
submitOrder(order: Bestellung, seats: Sitzplatz[], performance: Vorstellung, mode: SubmissionMode) {
|
||||
this.httpService.addOrder(order).pipe(
|
||||
// Order erstellen
|
||||
switchMap(createdOrder => {
|
||||
|
||||
// Tickets parallel erstellen
|
||||
const ticketObservables = seats.map(seat => {
|
||||
const ticket = this.generateNewTicketObject(performance, seat, createdOrder);
|
||||
return this.httpService.addTicket(ticket);
|
||||
// Tickets anlegen
|
||||
const tickets = seats.map(seat => {
|
||||
return this.generateNewTicketObject(performance, seat, order);
|
||||
});
|
||||
|
||||
// Warten bis alles fertig sind
|
||||
return forkJoin(ticketObservables).pipe(
|
||||
tap(createdTickets => {
|
||||
// Transaktionssicher Sitzplatzbuchung
|
||||
this.httpService.saveAddOrder({order, tickets}).pipe(
|
||||
tap(createdOrderAndTickets => {
|
||||
// Success Handling
|
||||
if (mode === 'reservation') {
|
||||
this.orderState.set({
|
||||
status: 'reservation-success',
|
||||
order: createdOrder
|
||||
order: createdOrderAndTickets.order
|
||||
});
|
||||
} else {
|
||||
this.orderState.set({
|
||||
status: 'purchase-success',
|
||||
tickets: createdTickets
|
||||
tickets: createdOrderAndTickets.tickets
|
||||
});
|
||||
}
|
||||
|
||||
this.selectedSeatsService.commit();
|
||||
this.loadingService.hide();
|
||||
this.showConfetti();
|
||||
})
|
||||
);
|
||||
}),
|
||||
catchError(err => {
|
||||
// Error handling
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<h3 class="opacity-75">{{ getStartTimeString() }} • {{ performance().hall.name }}</h3>
|
||||
<h1 class="font-semibold mb-0.5">{{ movie().title }}</h1>
|
||||
<div class="flex items-center">
|
||||
<app-movie-rating [rating]="movie().rating" class="rounded-sm shadow-xs px-1 py-0.25 text-sm"></app-movie-rating>
|
||||
<app-movie-rating [rating]="movie().rating" class="rounded-sm shadow-xs px-1 py-px text-sm"></app-movie-rating>
|
||||
<app-movie-duration [duration]="movie().duration" [showIcon]="false" class="ml-1.5 opacity-75"></app-movie-duration>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,6 +6,6 @@
|
||||
<p class="text-center">Leider konnten Ihre Sitzplätze nicht gekauft werden. Dies kann passieren, wenn andere Nutzer zeitgleich versucht haben, dieselben Sitzplätze zu kaufen.</p>
|
||||
|
||||
<button mat-button matButton="filled" class="error-button mt-4 w-80">Erneut versuchen</button>
|
||||
<button routerLink="/schedule" mat-button matButton="outlined" color="accent" class="error-button w-80">Zurück zur Programmauswahl</button>
|
||||
<button routerLink="/schedule" mat-button matButton="outlined" color="accent" class="error-button w-80 mt-1">Zurück zur Programmauswahl</button>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
<h1 class="text-xl font-bold">Vielen Dank für Ihren Einkauf!</h1>
|
||||
<p class="text-center">Ihre Sitzplätze wurden erfolgreich gebucht.</p>
|
||||
|
||||
<app-ticket-list [tickets]="tickets()" class="w-8/10"></app-ticket-list>
|
||||
<app-ticket-list [tickets]="tickets()" class="w-8/10 my-4"></app-ticket-list>
|
||||
|
||||
<button mat-button disabled="true" matButton="filled" class="success-button w-80 mt-2">Tickets herunterladen</button>
|
||||
<button routerLink="/schedule" mat-button matButton="outlined" color="accent" class="success-button w-80">Zurück zur Programmauswahl</button>
|
||||
<button mat-button disabled="true" matButton="filled" class="success-button w-80 mt-4">Tickets herunterladen</button>
|
||||
<button routerLink="/schedule" mat-button matButton="outlined" color="accent" class="success-button w-80 mt-1">Zurück zur Programmauswahl</button>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
@@ -6,6 +6,6 @@
|
||||
<p class="text-center">Leider konnten Ihre Sitzplätze nicht reserviert werden. Dies kann passieren, wenn andere Nutzer gleichzeitig versucht haben, dieselben Sitzplätze zu reservieren.</p>
|
||||
|
||||
<button mat-button matButton="filled" class="error-button mt-4 w-80">Erneut versuchen</button>
|
||||
<button routerLink="/schedule" mat-button matButton="outlined" color="accent" class="error-button w-80">Zurück zur Programmauswahl</button>
|
||||
<button routerLink="/schedule" mat-button matButton="outlined" color="accent" class="error-button w-80 mt-1">Zurück zur Programmauswahl</button>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
<div class="bg-green-200 rounded-md shadow-sm w-full h-fit p-6 py-8 items-center justify-center flex flex-col space-y-2">
|
||||
<h1 class="text-xl font-bold">Reservierung erfolgreich!</h1>
|
||||
<!-- <p class="text-center">Ihre Sitzplätze wurden reserviert. </p> -->
|
||||
|
||||
<p class="text-center">Ihre Sitzplätze wurden erfolgreich reserviert. Bitte nennen sie den folgenden Code an der Kasse, um Ihre Reservierung in eine Buchung umzuwandeln.</p>
|
||||
<div class="bg-white text-5xl font-mono rounded-md shadow-sm w-fit h-fit p-4 py-2 my-4">
|
||||
<strong>{{ order().code }}</strong>
|
||||
</div>
|
||||
|
||||
<button routerLink="/schedule" mat-button matButton="filled" class="success-button mt-4 w-80">Zurück zur Programmauswahl</button>
|
||||
<button [disabled]="true" mat-button matButton="outlined" color="accent" class="success-button w-80">Tickets jetzt online bezahlen</button>
|
||||
<div class="text-green-500 cursor-pointer w-fit mt-1" (click)="cancelReservation()">
|
||||
<button [disabled]="true" mat-button matButton="filled" color="accent" class="success-button mt-2 w-80">Tickets jetzt online bezahlen</button>
|
||||
<button routerLink="/schedule" mat-button matButton="outlined" class="success-button mb-4 w-80 mt-1">Zurück zur Programmauswahl</button>
|
||||
<div class="text-green-500 cursor-pointer w-fit mt-2" (click)="cancelReservation()">
|
||||
Reservierung stornieren
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
@@ -5,3 +5,7 @@
|
||||
::ng-deep .mat-mdc-tab .mdc-tab-indicator__content--underline {
|
||||
border-color: #6366f1 !important; /* indigo-500 */
|
||||
}
|
||||
|
||||
.mat-badge-content {
|
||||
background: #dd2979;
|
||||
}
|
||||
|
||||
@@ -1,21 +1,26 @@
|
||||
<app-menu-header title="Programmübersicht" icon="event" [searchBar]="true" (movieSearchResult)="movieSearchResult = $event"></app-menu-header>
|
||||
<app-menu-header label="Programmübersicht" icon="event" [searchBar]="true" (movieSearchResult)="movieSearchResult = $event"></app-menu-header>
|
||||
|
||||
<mat-tab-group mat-stretch-tabs>
|
||||
@for (dateInfo of dates; track dateInfo.date; let i = $index) {
|
||||
<mat-tab [label]="dateInfo.label">
|
||||
<ng-template mat-tab-label>
|
||||
<span [matBadge]="getMovieCount(i)" matBadgeOverlap="false" [matBadgeHidden]="!isSearch() || getMovieCount(i) === 0" [class]="(isSearch() && getMovieCount(i) === 0)? 'text-gray-300' : ''">
|
||||
{{ dateInfo.label }}
|
||||
</span>
|
||||
</ng-template>
|
||||
@if (getMovieCount(i) > 0) {
|
||||
@if (hasSearchResults(i)) {
|
||||
@for (group of dateInfo.performances; track group.movie.id) {
|
||||
@if (group.movie.title.toLowerCase().includes(movieSearchResult.toLowerCase())) {
|
||||
<app-movie-schedule-info [movieGroup]="group"></app-movie-schedule-info>
|
||||
}
|
||||
}
|
||||
} @else {
|
||||
@if (isSearch()) {
|
||||
<app-movie-schedule-no-search-result [search]="movieSearchResult" [date]="dates[i].date"></app-movie-schedule-no-search-result>
|
||||
}
|
||||
} @else {
|
||||
<app-movie-schedule-empty></app-movie-schedule-empty>
|
||||
}
|
||||
}
|
||||
</mat-tab>
|
||||
}
|
||||
</mat-tab-group>
|
||||
|
||||
@@ -31,15 +31,7 @@ export class ScheduleComponent implements OnInit {
|
||||
this.loadPerformances(this.bookableDays);
|
||||
}
|
||||
|
||||
hasSearchResults(dateIndex: number): boolean {
|
||||
if (!this.movieSearchResult) return true;
|
||||
|
||||
return this.dates[dateIndex].performances.some(group =>
|
||||
group.movie.title.toLowerCase().includes(this.movieSearchResult.toLowerCase())
|
||||
);
|
||||
}
|
||||
|
||||
generateDates(bookableDays: number) {
|
||||
private generateDates(bookableDays: number) {
|
||||
const today = new Date();
|
||||
for (let i = 0; i < bookableDays; i++) {
|
||||
const date = new Date(today);
|
||||
@@ -58,7 +50,7 @@ export class ScheduleComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
loadPerformances(bookableDays: number) {
|
||||
private loadPerformances(bookableDays: number) {
|
||||
this.loading.show();
|
||||
const filter = this.generateDateFilter(bookableDays);
|
||||
this.http.getPerformacesByFilter(filter).pipe(
|
||||
@@ -89,8 +81,7 @@ private generateDateFilter(bookableDays: number): string[] {
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
assignPerformancesToDates() {
|
||||
private assignPerformancesToDates() {
|
||||
|
||||
// Gruppieren nach Datum
|
||||
const groupedByDate: { [key: string]: Vorstellung[] } = {};
|
||||
@@ -133,6 +124,22 @@ private generateDateFilter(bookableDays: number): string[] {
|
||||
}
|
||||
|
||||
getMovieCount(index: number): number {
|
||||
return this.dates[index].performances.length;
|
||||
if (!this.dates[index]?.performances) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const performances = this.dates[index].performances;
|
||||
|
||||
if (!this.isSearch()) {
|
||||
return performances.length;
|
||||
}
|
||||
|
||||
return performances.filter(group =>
|
||||
group.movie.title.toLowerCase().includes(this.movieSearchResult.toLowerCase())
|
||||
).length;
|
||||
}
|
||||
|
||||
isSearch(): boolean {
|
||||
return !!this.movieSearchResult && this.movieSearchResult.trim() !== '';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1,6 @@
|
||||
|
||||
.empty-seat-space {
|
||||
width: 30px;
|
||||
/* Keine Ahnung, wo die zusätzlichen 6.5px herkommen müssen. Wir sonst dünner angezeigt */
|
||||
height: 36.5px;
|
||||
/* height: 30px; */
|
||||
}
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
@for (entry of rowSeatList(); track $index) {
|
||||
<app-seat class="my-1" [state]="entry.state" [seat]="entry.seat"></app-seat>
|
||||
|
||||
@if (entry.seat != null && entry.state != null) {
|
||||
<app-seat class="my-1" [seat]="entry.seat" [state]="entry.state" ></app-seat>
|
||||
} @else {
|
||||
<div class="empty-seat-space my-1 mx-0.5"></div>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,5 +9,5 @@ import {TheaterSeatState} from '../model/theater-seat-state.model';
|
||||
styleUrl: './seat-row.component.css'
|
||||
})
|
||||
export class SeatRowComponent {
|
||||
rowSeatList = input.required<{ seat: Sitzplatz, state: TheaterSeatState }[]>();
|
||||
rowSeatList = input.required<{ seat: Sitzplatz | null, state: TheaterSeatState | null }[]>();
|
||||
}
|
||||
|
||||
0
src/app/statistics/statistics.component.css
Normal file
0
src/app/statistics/statistics.component.css
Normal file
85
src/app/statistics/statistics.component.html
Normal file
85
src/app/statistics/statistics.component.html
Normal file
@@ -0,0 +1,85 @@
|
||||
<app-menu-header label="Statistiken"></app-menu-header>
|
||||
|
||||
<div class="table-table-container">
|
||||
|
||||
<table mat-table [dataSource]="movies" class="example-table">
|
||||
|
||||
<ng-container matColumnDef="id">
|
||||
<th mat-header-cell *matHeaderCellDef>ID</th>
|
||||
<td mat-cell *matCellDef="let row">{{row.movieId}}</td>
|
||||
</ng-container>
|
||||
|
||||
|
||||
<ng-container matColumnDef="title">
|
||||
<th mat-header-cell *matHeaderCellDef>Titel</th>
|
||||
<td mat-cell *matCellDef="let row">{{row.movieTitle}}</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="earnings">
|
||||
<th mat-header-cell *matHeaderCellDef>
|
||||
Umsatz
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let row">{{(row.earnings/100).toFixed(2)}} €</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="tickets">
|
||||
<th mat-header-cell *matHeaderCellDef>
|
||||
Gebuchte Tickets
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let row">{{row.tickets}}</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="moviesDisplayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: moviesDisplayedColumns;"></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<mat-paginator [length]="movieResultsLength" [pageSize]="30" aria-label="Select page of GitHub search results"></mat-paginator>
|
||||
|
||||
|
||||
<div class="show-table-container">
|
||||
|
||||
<table mat-table [dataSource]="shows" class="example-table">
|
||||
|
||||
<ng-container matColumnDef="id">
|
||||
<th mat-header-cell *matHeaderCellDef>ID</th>
|
||||
<td mat-cell *matCellDef="let row">{{row.showId}}</td>
|
||||
</ng-container>
|
||||
|
||||
|
||||
<ng-container matColumnDef="hall">
|
||||
<th mat-header-cell *matHeaderCellDef>Kinosaal</th>
|
||||
<td mat-cell *matCellDef="let row">{{row.showHallName}}</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="movie_title">
|
||||
<th mat-header-cell *matHeaderCellDef>Film Name</th>
|
||||
<td mat-cell *matCellDef="let row">{{row.movieTitle}}</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="date">
|
||||
<th mat-header-cell *matHeaderCellDef>Datum</th>
|
||||
<td mat-cell *matCellDef="let row">{{formatDate(row.showStart)}}</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="earnings">
|
||||
<th mat-header-cell *matHeaderCellDef>
|
||||
Umsatz
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let row">{{(row.earnings/100).toFixed(2)}} €</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="tickets">
|
||||
<th mat-header-cell *matHeaderCellDef>
|
||||
Gebuchte Tickets
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let row">{{row.tickets}}</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="showsDisplayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: showsDisplayedColumns;"></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<mat-paginator [length]="showsResultLength" [pageSize]="30" aria-label="Select page of GitHub search results"></mat-paginator>
|
||||
|
||||
56
src/app/statistics/statistics.component.ts
Normal file
56
src/app/statistics/statistics.component.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import {Component, inject} from '@angular/core';
|
||||
import {HttpService} from '../http.service';
|
||||
import {
|
||||
StatisticsFilm,
|
||||
StatisticsVorstellung,
|
||||
} from '@infinimotion/model-frontend';
|
||||
import {LoadingService} from '../loading.service';
|
||||
import {firstValueFrom, forkJoin} from 'rxjs';
|
||||
|
||||
@Component({
|
||||
selector: 'app-statistics',
|
||||
standalone: false,
|
||||
templateUrl: './statistics.component.html',
|
||||
styleUrl: './statistics.component.css',
|
||||
})
|
||||
export class StatisticsComponent {
|
||||
private http = inject(HttpService);
|
||||
protected movies: StatisticsFilm[] = [];
|
||||
protected shows: StatisticsVorstellung[] = [];
|
||||
protected moviesDisplayedColumns: string[] = ['id', 'title', 'earnings', 'tickets'];
|
||||
protected showsDisplayedColumns: string[] = ['id', 'hall', 'movie_title', 'date', 'earnings', 'tickets'];
|
||||
protected movieResultsLength: number = 0;
|
||||
protected showsResultLength: number = 0;
|
||||
|
||||
private loading = inject(LoadingService);
|
||||
|
||||
ngOnInit(): void {
|
||||
this.loading.show()
|
||||
this.loadData().then();
|
||||
}
|
||||
|
||||
async loadData() {
|
||||
let movieRequest = this.http.getMovieStatistics();
|
||||
let showRequest = this.http.getShowStatistics();
|
||||
let movieResponse = await firstValueFrom(movieRequest);
|
||||
let showResponse = await firstValueFrom(showRequest);
|
||||
this.movies = movieResponse
|
||||
this.shows = showResponse
|
||||
if (this.movies.length / 30 < 1) {
|
||||
this.movieResultsLength = 1;
|
||||
} else {
|
||||
this.movieResultsLength = Math.ceil(this.movies.length / 30);
|
||||
}
|
||||
if (this.shows.length / 30 < 1) {
|
||||
this.showsResultLength = 1;
|
||||
} else {
|
||||
this.showsResultLength = Math.ceil(this.shows.length / 30);
|
||||
}
|
||||
this.loading.hide();
|
||||
}
|
||||
|
||||
formatDate(date: Date) {
|
||||
return new Date(date).toLocaleString("de");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,12 +3,12 @@
|
||||
Leinwand
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<div class="mb-5">
|
||||
@for (row of seatsPerRow(); track $index) {
|
||||
<div class="flex items-center justify-between">
|
||||
|
||||
<!-- Speaker -->
|
||||
<div class="shrink-0 pl-25">
|
||||
<div class="shrink-0 pl-20">
|
||||
@if ($index % 4 === 0) {
|
||||
<mat-icon class="material-symbols-outlined opacity-25" style="font-size: 30px; width: 30px; height: 30px">
|
||||
speaker
|
||||
@@ -25,7 +25,7 @@
|
||||
<app-seat-row class="flex justify-center" [rowSeatList]="row"></app-seat-row>
|
||||
|
||||
<!-- Speaker -->
|
||||
<div class="shrink-0 pr-25">
|
||||
<div class="shrink-0 pr-20">
|
||||
@if ($index % 4 === 0) {
|
||||
<mat-icon class="material-symbols-outlined opacity-25 mirrored" style="font-size: 30px; width: 30px; height: 30px">
|
||||
speaker
|
||||
|
||||
@@ -10,7 +10,7 @@ import {TheaterSeatState} from '../model/theater-seat-state.model';
|
||||
styleUrl: './theater-layout.component.css'
|
||||
})
|
||||
export class TheaterLayoutComponent {
|
||||
seatsPerRow = input.required<{ seat: Sitzplatz, state: TheaterSeatState }[][]>();
|
||||
seatsPerRow = input.required<{ seat: Sitzplatz | null, state: TheaterSeatState | null }[][]>();
|
||||
|
||||
protected selectedSeatsService = inject(SelectedSeatsService);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<app-menu-header title="Vorstellungstickets kaufen" icon="local_activity" [backToSchedule]="true"></app-menu-header>
|
||||
<app-menu-header label="Vorstellungstickets kaufen" icon="local_activity" [backToSchedule]="true"></app-menu-header>
|
||||
|
||||
<div class="flex justify-between h-100">
|
||||
<div class="flex h-fit">
|
||||
|
||||
<div class="w-7/10 p-10 h-188">
|
||||
<div class="w-7/10 p-10 h-fit">
|
||||
<div>
|
||||
@if (!performance && (loading.loading$ | async)){
|
||||
<div class="w-full h-full flex items-center justify-center mt-70">
|
||||
@@ -18,6 +18,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<app-order class="m-10 mr-20 w-3/10" [performance]="performance" [seatCategories]="seatCategories"></app-order>
|
||||
<app-order class="mt-10 mr-30 w-3/10" [performance]="performance" [seatCategories]="seatCategories"></app-order>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -25,7 +25,8 @@ export class TheaterOverlayComponent implements OnInit, OnDestroy {
|
||||
readonly loading = inject(LoadingService);
|
||||
|
||||
showId!: number;
|
||||
seatsPerRow = signal<{ seat: Sitzplatz, state: TheaterSeatState }[][]>([]);
|
||||
orderId?: string;
|
||||
seatsPerRow = signal<{ seat: Sitzplatz | null, state: TheaterSeatState | null }[][]>([]);
|
||||
performance: Vorstellung | undefined;
|
||||
seatCategories: Sitzkategorie[] = [];
|
||||
|
||||
@@ -33,7 +34,8 @@ export class TheaterOverlayComponent implements OnInit, OnDestroy {
|
||||
private isInitialLoad = signal(true);
|
||||
|
||||
ngOnInit() {
|
||||
this.showId = Number(this.route.snapshot.paramMap.get('id')!);
|
||||
this.showId = Number(this.route.snapshot.paramMap.get('performanceId')!);
|
||||
this.orderId = this.route.snapshot.queryParams['paramName'];
|
||||
this.selectedSeatService.clearSelection();
|
||||
this.selectedSeatService.setSeatSelectable(true);
|
||||
|
||||
@@ -90,12 +92,13 @@ export class TheaterOverlayComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
converter(resp: { seats: Sitzplatz[], reserved: Sitzplatz[], booked: Sitzplatz[] }): {
|
||||
seat: Sitzplatz,
|
||||
state: TheaterSeatState
|
||||
seat: Sitzplatz | null,
|
||||
state: TheaterSeatState | null
|
||||
}[][] {
|
||||
let rows: { seat: Sitzplatz, state: TheaterSeatState }[][] = [];
|
||||
let rows: { seat: Sitzplatz | null, state: TheaterSeatState | null }[][] = [];
|
||||
const categoryMap = new Map<number, Sitzkategorie>();
|
||||
|
||||
// Sitzplätze sammeln
|
||||
resp.seats.forEach(seat => {
|
||||
if (!rows[seat.row.position]) {
|
||||
rows[seat.row.position] = [];
|
||||
@@ -114,9 +117,50 @@ export class TheaterOverlayComponent implements OnInit, OnDestroy {
|
||||
|
||||
this.seatCategories = Array.from(categoryMap.values()).sort((a, b) => a.id - b.id);
|
||||
|
||||
rows = rows.filter(row => row.length > 0).sort((a, b) => a[0].seat.row.position - b[0].seat.row.position);
|
||||
rows.forEach(row => row.sort((a, b) => a.seat.position - b.seat.position));
|
||||
return rows;
|
||||
rows = rows.filter(row => row && row.length > 0).sort((a, b) => a[0].seat!.row.position - b[0].seat!.row.position);
|
||||
|
||||
if (rows.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Leere Plätze auffüllen
|
||||
const filledSeats: { seat: Sitzplatz | null, state: TheaterSeatState | null }[][] = [];
|
||||
|
||||
rows.forEach(row => {
|
||||
row.sort((a, b) => a.seat!.position - b.seat!.position)
|
||||
|
||||
const minPos = row[0].seat!.position;
|
||||
const maxPos = row[row.length - 1].seat!.position;
|
||||
const filledRow: { seat: Sitzplatz | null, state: TheaterSeatState | null }[] = [];
|
||||
|
||||
for (let pos = minPos; pos <= maxPos; pos++) {
|
||||
const existingSeat = row.find(s => s.seat!.position === pos);
|
||||
if (existingSeat) {
|
||||
filledRow.push(existingSeat);
|
||||
} else {
|
||||
filledRow.push({ seat: null, state: null });
|
||||
}
|
||||
}
|
||||
|
||||
filledSeats.push(filledRow);
|
||||
});
|
||||
|
||||
// Leere Reihen auffüllen
|
||||
const minRowPos = rows[0][0].seat!.row.position;
|
||||
const maxRowPos = rows[rows.length - 1][0].seat!.row.position;
|
||||
const filledRows: { seat: Sitzplatz | null, state: TheaterSeatState | null }[][] = [];
|
||||
|
||||
let processedIndex = 0;
|
||||
for (let rowPos = minRowPos; rowPos <= maxRowPos; rowPos++) {
|
||||
if (processedIndex < filledSeats.length && filledSeats[processedIndex][0].seat!.row.position === rowPos) {
|
||||
filledRows.push(filledSeats[processedIndex]);
|
||||
processedIndex++;
|
||||
} else {
|
||||
filledRows.push([{ seat: null, state: null }]);
|
||||
}
|
||||
}
|
||||
|
||||
return filledRows;
|
||||
}
|
||||
|
||||
refreshSeats(): void {
|
||||
|
||||
33
src/app/zoom-detection.service.ts
Normal file
33
src/app/zoom-detection.service.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { BehaviorSubject, fromEvent } from 'rxjs';
|
||||
import { debounceTime } from 'rxjs/operators';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class ZoomDetectionService {
|
||||
private zoomLevel$ = new BehaviorSubject<number>(this.getZoomLevel());
|
||||
|
||||
constructor() {
|
||||
// Zoom-Änderungen überwachen
|
||||
fromEvent(window, 'resize')
|
||||
.pipe(debounceTime(200))
|
||||
.subscribe(() => {
|
||||
this.zoomLevel$.next(this.getZoomLevel());
|
||||
});
|
||||
}
|
||||
|
||||
getZoomLevel(): number {
|
||||
const devicePixelRatio = window.devicePixelRatio || 1;
|
||||
return devicePixelRatio;
|
||||
}
|
||||
|
||||
getZoomLevel$() {
|
||||
return this.zoomLevel$.asObservable();
|
||||
}
|
||||
|
||||
isZoomOutOfRange(minZoom: number = 0.95, maxZoom: number = 1.05): boolean {
|
||||
const currentZoom = this.getZoomLevel();
|
||||
return currentZoom < minZoom || currentZoom > maxZoom;
|
||||
}
|
||||
}
|
||||
14
src/app/zoom-warning/zoom-warning.component.css
Normal file
14
src/app/zoom-warning/zoom-warning.component.css
Normal file
@@ -0,0 +1,14 @@
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px) scale(0.9);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.zoom-info-box {
|
||||
animation: slideIn 0.3s ease-out;
|
||||
}
|
||||
81
src/app/zoom-warning/zoom-warning.component.html
Normal file
81
src/app/zoom-warning/zoom-warning.component.html
Normal file
@@ -0,0 +1,81 @@
|
||||
@if (isOutOfRange && !isDismissed && !isMobile) {
|
||||
<div class="zoom-info-box fixed bottom-5 right-5 z-9999 w-[500px] bg-amber-300 border-4 border-dashed rounded-md shadow-lg p-6 py-8 items-center justify-center flex flex-col space-y-2 origin-bottom-right transition-all duration-300" [style.transform]="getCompensationTransform()">
|
||||
<button
|
||||
(click)="dismissWarning()"
|
||||
class="absolute top-2 right-2 w-10 h-10 flex items-center justify-center rounded-full hover:bg-amber-400 transition-colors"
|
||||
aria-label="Warnung schließen">
|
||||
<span class="text-3xl leading-none">×</span>
|
||||
</button>
|
||||
|
||||
<div class="relative mb-5">
|
||||
<mat-icon class="material-symbols-outlined" style="font-size: 100px; width: 100px; height: 100px">
|
||||
screenshot_monitor
|
||||
</mat-icon>
|
||||
<mat-icon class="material-symbols-outlined absolute top-[calc(50%-9px)] left-1/2 -translate-x-1/2 -translate-y-1/2" style="font-size: 30px; width: 30px; height: 30px">
|
||||
warning
|
||||
</mat-icon>
|
||||
</div>
|
||||
|
||||
<h1 class="text-xl font-bold">Browser-Zoom nicht optimal!</h1>
|
||||
<p class="text-center">Ihr Browser-Zoom ist auf <strong>{{ currentZoomPercentage }}%</strong> eingestellt.<br>Für die beste Darstellung empfehlen wir <strong>100%</strong>.</p>
|
||||
|
||||
<div class="mt-4 text-sm space-y-3 w-full">
|
||||
<div class="bg-white/50 rounded p-3">
|
||||
<p class="font-semibold mb-2">Browser-Zoom zurücksetzen:</p>
|
||||
<div class="space-y-1 ml-1">
|
||||
<p class="flex items-center gap-1.5">
|
||||
<kbd class="px-2 py-1 bg-white rounded shadow border border-gray-500 font-mono text-xs">Strg</kbd>
|
||||
<span>/</span>
|
||||
<kbd class="px-2 py-1 bg-white rounded shadow border border-gray-500 font-mono text-xs">⌘</kbd>
|
||||
<span>+</span>
|
||||
<kbd class="px-2 py-1 bg-white rounded shadow border border-gray-500 font-mono text-xs">0</kbd>
|
||||
<span class="px-1">(Windows, Linux / Mac)</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white/50 rounded p-3">
|
||||
<p class="font-semibold mb-2">Windows-Skalierung prüfen:</p>
|
||||
<ol class="list-decimal ml-6 space-y-1">
|
||||
<li>Öffnen Sie die <strong>Windows-Einstellungen</strong></li>
|
||||
<li>Navigieren Sie zu <strong>System</strong> → <strong>Bildschirm</strong></li>
|
||||
<li>Setzen Sie unter <strong>"Skalierung"</strong> den Wert auf <strong>100%</strong></li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (showMobileWarning) {
|
||||
<div class="header z-99999 px-8 pt-4 pb-3 relative bg-white text-center">
|
||||
<div class="flex items-center justify-center space-x-4 transition m-auto">
|
||||
<img src="assets/logo.png" class="h-10 w-10 transform scale-175 translate-y-px" />
|
||||
<h1 class="text-3xl font-semibold tracking-wide">InfiniMotion</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fixed inset-0 z-99998 bg-amber-300 flex items-center justify-center p-6">
|
||||
<div class="max-w-md w-full text-center space-y-6 mt-10">
|
||||
<div class="relative inline-block">
|
||||
<mat-icon class="material-symbols-outlined" style="font-size: 120px; width: 120px; height: 120px">
|
||||
screenshot_monitor
|
||||
</mat-icon>
|
||||
<mat-icon class="material-symbols-outlined absolute top-[calc(50%-11px)] left-1/2 -translate-x-1/2 -translate-y-1/2" style="font-size: 35px; width: 35px; height: 35px">
|
||||
warning
|
||||
</mat-icon>
|
||||
</div>
|
||||
|
||||
<h1 class="text-2xl font-bold text-gray-800">Nur am PC verfügbar</h1>
|
||||
|
||||
<p class="text-base text-gray-700">
|
||||
Diese Anwendung ist für die Nutzung am <strong>Desktop-PC</strong> optimiert und kann auf mobilen Geräten nicht verwendet werden.
|
||||
</p>
|
||||
|
||||
<div class="text-xs text-gray-600 mt-12">
|
||||
<p>Deine derzeitige URL:</p>
|
||||
<p class="font-mono bg-white/70 px-3 py-2 rounded mt-1 break-all">
|
||||
{{ currentUrl }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
100
src/app/zoom-warning/zoom-warning.component.ts
Normal file
100
src/app/zoom-warning/zoom-warning.component.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import { DeviceDetectionService } from './../device-detection.service';
|
||||
import { Component, HostListener, inject, OnDestroy, OnInit } from '@angular/core';
|
||||
import { filter, Subject, takeUntil } from 'rxjs';
|
||||
import { ZoomDetectionService } from '../zoom-detection.service';
|
||||
import { NavigationEnd, Router } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'app-zoom-warning',
|
||||
standalone: false,
|
||||
templateUrl: './zoom-warning.component.html',
|
||||
styleUrl: './zoom-warning.component.css',
|
||||
})
|
||||
export class ZoomWarningComponent implements OnInit, OnDestroy {
|
||||
currentZoomPercentage = 100;
|
||||
isOutOfRange = false;
|
||||
isDismissed = false;
|
||||
isMobile = false;
|
||||
showMobileWarning = false;
|
||||
currentUrl = '';
|
||||
|
||||
private destroy$ = new Subject<void>();
|
||||
private currentZoomLevel = 1;
|
||||
private lastZoomLevel = 0;
|
||||
|
||||
zoomDetectionService = inject(ZoomDetectionService);
|
||||
deviceDetectionService = inject(DeviceDetectionService)
|
||||
|
||||
constructor(private router: Router) {
|
||||
this.isMobile = this.deviceDetectionService.isMobile();
|
||||
this.currentUrl = window.location.href;
|
||||
this.checkIfShouldShowMobileWarning();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.router.events
|
||||
.pipe(
|
||||
filter(event => event instanceof NavigationEnd),
|
||||
takeUntil(this.destroy$)
|
||||
)
|
||||
.subscribe(() => {
|
||||
this.checkIfShouldShowMobileWarning();
|
||||
});
|
||||
|
||||
if (!this.isMobile) {
|
||||
this.updateZoomInfo();
|
||||
|
||||
this.zoomDetectionService.getZoomLevel$()
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe((newZoomLevel) => {
|
||||
if (Math.abs(newZoomLevel - this.lastZoomLevel) > 0.01) {
|
||||
this.lastZoomLevel = newZoomLevel;
|
||||
this.isDismissed = false;
|
||||
this.updateZoomInfo();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private checkIfShouldShowMobileWarning() {
|
||||
if (!this.isMobile) {
|
||||
this.showMobileWarning = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const currentRoute = this.router.routerState.root;
|
||||
const allowMobile = this.getRouteData(currentRoute, 'allowMobile');
|
||||
|
||||
this.showMobileWarning = !allowMobile;
|
||||
}
|
||||
|
||||
private getRouteData(route: any, key: string): any {
|
||||
while (route) {
|
||||
if (route.snapshot?.data?.[key] !== undefined) {
|
||||
return route.snapshot.data[key];
|
||||
}
|
||||
route = route.firstChild;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private updateZoomInfo() {
|
||||
this.currentZoomLevel = this.zoomDetectionService.getZoomLevel();
|
||||
this.currentZoomPercentage = Math.round(this.currentZoomLevel * 100);
|
||||
this.isOutOfRange = this.zoomDetectionService.isZoomOutOfRange();
|
||||
}
|
||||
|
||||
getCompensationTransform(): string {
|
||||
const scale = 1 / this.currentZoomLevel;
|
||||
return `scale(${scale})`;
|
||||
}
|
||||
|
||||
dismissWarning() {
|
||||
this.isDismissed = true;
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user