From f4eb700ab49148ca050fbbc823c43890a39bd316 Mon Sep 17 00:00:00 2001 From: Piet Ostendorp Date: Tue, 18 Nov 2025 23:00:18 +0100 Subject: [PATCH] Add zoom and device detection with warning component Introduces DeviceDetectionService and ZoomDetectionService to detect mobile devices and browser zoom level. Adds ZoomWarningComponent to display warnings for unsupported mobile devices and non-optimal browser zoom, and integrates it into the app layout. Updates routing to allow mobile access for the 'poc-model' route. --- src/app/app-module.ts | 2 + src/app/app-routing-module.ts | 2 +- src/app/app.html | 1 + src/app/device-detection.service.ts | 29 +++++ src/app/zoom-detection.service.ts | 33 ++++++ .../zoom-warning/zoom-warning.component.css | 14 +++ .../zoom-warning/zoom-warning.component.html | 81 ++++++++++++++ .../zoom-warning/zoom-warning.component.ts | 100 ++++++++++++++++++ 8 files changed, 261 insertions(+), 1 deletion(-) create mode 100644 src/app/device-detection.service.ts create mode 100644 src/app/zoom-detection.service.ts create mode 100644 src/app/zoom-warning/zoom-warning.component.css create mode 100644 src/app/zoom-warning/zoom-warning.component.html create mode 100644 src/app/zoom-warning/zoom-warning.component.ts diff --git a/src/app/app-module.ts b/src/app/app-module.ts index 2172c39..2794819 100644 --- a/src/app/app-module.ts +++ b/src/app/app-module.ts @@ -64,6 +64,7 @@ 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 { ZoomWarningComponent } from './zoom-warning/zoom-warning.component'; @NgModule({ @@ -107,6 +108,7 @@ import { TicketListComponent } from './ticket-list/ticket-list.component'; PurchaseFailedComponent, TicketSmallComponent, TicketListComponent, + ZoomWarningComponent, ], imports: [ AppRoutingModule, diff --git a/src/app/app-routing-module.ts b/src/app/app-routing-module.ts index 6f48c5e..e5692a4 100644 --- a/src/app/app-routing-module.ts +++ b/src/app/app-routing-module.ts @@ -12,7 +12,7 @@ import { AuthGuard } from './auth.guard'; 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 { diff --git a/src/app/app.html b/src/app/app.html index 67e7bd4..bc230ce 100644 --- a/src/app/app.html +++ b/src/app/app.html @@ -1 +1,2 @@ + diff --git a/src/app/device-detection.service.ts b/src/app/device-detection.service.ts new file mode 100644 index 0000000..c4035ea --- /dev/null +++ b/src/app/device-detection.service.ts @@ -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(); + } +} diff --git a/src/app/zoom-detection.service.ts b/src/app/zoom-detection.service.ts new file mode 100644 index 0000000..d9e3311 --- /dev/null +++ b/src/app/zoom-detection.service.ts @@ -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(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; + } +} diff --git a/src/app/zoom-warning/zoom-warning.component.css b/src/app/zoom-warning/zoom-warning.component.css new file mode 100644 index 0000000..21fcb9c --- /dev/null +++ b/src/app/zoom-warning/zoom-warning.component.css @@ -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; +} diff --git a/src/app/zoom-warning/zoom-warning.component.html b/src/app/zoom-warning/zoom-warning.component.html new file mode 100644 index 0000000..ea79125 --- /dev/null +++ b/src/app/zoom-warning/zoom-warning.component.html @@ -0,0 +1,81 @@ +@if (isOutOfRange && !isDismissed && !isMobile) { +
+ + +
+ + screenshot_monitor + + + warning + +
+ +

Browser-Zoom nicht optimal!

+

Ihr Browser-Zoom ist auf {{ currentZoomPercentage }}% eingestellt.
Für die beste Darstellung empfehlen wir 100%.

+ +
+
+

Browser-Zoom zurücksetzen:

+
+

+ Strg + / + + + + 0 + (Windows, Linux / Mac) +

+
+
+ +
+

Windows-Skalierung prüfen:

+
    +
  1. Öffnen Sie die Windows-Einstellungen
  2. +
  3. Navigieren Sie zu SystemBildschirm
  4. +
  5. Setzen Sie unter "Skalierung" den Wert auf 100%
  6. +
+
+
+
+} + +@if (showMobileWarning) { +
+
+ +

InfiniMotion

+
+
+
+
+
+ + screenshot_monitor + + + warning + +
+ +

Nur am PC verfügbar

+ +

+ Diese Anwendung ist für die Nutzung am Desktop-PC optimiert und kann auf mobilen Geräten nicht verwendet werden. +

+ +
+

Deine derzeitige URL:

+

+ {{ currentUrl }} +

+
+
+
+} diff --git a/src/app/zoom-warning/zoom-warning.component.ts b/src/app/zoom-warning/zoom-warning.component.ts new file mode 100644 index 0000000..8802167 --- /dev/null +++ b/src/app/zoom-warning/zoom-warning.component.ts @@ -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(); + 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(); + } +}