-
-
+
+
+
Minimum setzen
-
+
{{ currentSize }}px
-
+
-
+
- Minimum bestätigt: {{ currentSize }}px
-
-
+ Minimum bestätigt: {{ minSizeResult }}px
+
+
Maximum setzen
-
+
{{ currentSize }}px
-
+
-
+
- Maximum bestätigt: {{ currentSize }}px
-
+ Maximum bestätigt: {{ maxSizeResult }}px
+
Optimale Größe setzen
-
+
{{ currentSize }}px
-
+
-
+
+
+
+
+
+ Optimale Größe bestätigt: {{ comfortableSizeResult }}px
+
+
+
+
+
+ Vielen Dank!
+ Ergebnisse werden für die finale Übermittlung gespeichert...
diff --git a/src/app/components/test-suite/assessments/text-legibility-assessment/text-legibility-assessment.component.ts b/src/app/components/test-suite/assessments/text-legibility-assessment/text-legibility-assessment.component.ts
index dd64643..c30666b 100644
--- a/src/app/components/test-suite/assessments/text-legibility-assessment/text-legibility-assessment.component.ts
+++ b/src/app/components/test-suite/assessments/text-legibility-assessment/text-legibility-assessment.component.ts
@@ -1,16 +1,18 @@
import {
Component,
AfterViewInit,
+ OnDestroy,
ViewChild,
ElementRef,
ChangeDetectorRef,
CUSTOM_ELEMENTS_SCHEMA,
EventEmitter,
- Output
+ Output,
+ inject
} from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
-
+import { MetricsTrackerService } from '../../../../services/metrics-tracker.service';
import '../../../../../assets/scripts/model-viewer';
@Component({
@@ -21,97 +23,80 @@ import '../../../../../assets/scripts/model-viewer';
styleUrls: ['./text-legibility-assessment.component.css'],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
-export class TextLegibilityAssessmentComponent implements AfterViewInit {
+export class TextLegibilityAssessmentComponent implements AfterViewInit, OnDestroy {
@ViewChild('modelViewer') modelViewerRef!: ElementRef
;
@Output() testComplete = new EventEmitter();
- @Output() redoTest = new EventEmitter();
+ @Output() redoTest = new EventEmitter();
+ private metricsService = inject(MetricsTrackerService);
+
+ // --- Component State (Restored) ---
minSize = 2;
maxSize = 64;
currentSize = 16;
- comfortableSize = 16;
- phase:
- | 'min'
- | 'confirmedMin'
- | 'max'
- | 'confirmedMax'
- | 'comfortable'
- | 'confirmedComfort'
- | 'finished' = 'min';
+ minSizeResult: number | null = null;
+ maxSizeResult: number | null = null;
+ comfortableSizeResult: number | null = null;
+
+ phase: 'min' | 'confirmedMin' | 'max' | 'confirmedMax' | 'comfortable' | 'confirmedComfort' | 'finished' = 'min';
private offsetApplied = false;
- constructor(private cdr: ChangeDetectorRef) {}
+ constructor(private cdr: ChangeDetectorRef) {
+ this.logInteraction = this.logInteraction.bind(this);
+ }
ngAfterViewInit() {
- const mv = this.modelViewerRef.nativeElement;
- mv.setAttribute('scale', '0.25 0.25 0.25');
+ this.metricsService.startDeviceOrientationTracking();
+ this.metricsService.startArTracking(this.modelViewerRef.nativeElement);
+ }
- mv.addEventListener('ar-status', async (e: any) => {
- if (e.detail.status === 'session-started' && !this.offsetApplied) {
- await mv.updateComplete;
- const anchor = mv.getAnchor();
- if (!anchor.includes('not placed')) {
- const [x, y, z] = anchor.split(' ').map(parseFloat);
- mv.setAttribute('ar-anchor', `${x} ${y + 3.0} ${z}`);
- this.offsetApplied = true;
- }
- }
- });
+ public logInteraction(event: Event) {
+ this.metricsService.logInteraction(event);
}
decrease() {
- if (this.currentSize > this.minSize) {
- this.currentSize--;
- this.syncComfortable();
- }
+ if (this.currentSize > this.minSize) this.currentSize--;
}
increase() {
- if (this.currentSize < this.maxSize) {
- this.currentSize++;
- this.syncComfortable();
- }
- }
-
- private syncComfortable() {
- if (this.phase === 'comfortable') {
- this.comfortableSize = this.currentSize;
- }
+ if (this.currentSize < this.maxSize) this.currentSize++;
}
nextPhase() {
switch (this.phase) {
case 'min':
+ this.minSizeResult = this.currentSize;
this.phase = 'confirmedMin';
break;
case 'confirmedMin':
- this.phase = 'max';
this.currentSize = this.maxSize;
+ this.phase = 'max';
break;
case 'max':
+ this.maxSizeResult = this.currentSize;
this.phase = 'confirmedMax';
break;
case 'confirmedMax':
+ this.currentSize = Math.floor((this.minSizeResult! + this.maxSizeResult!) / 2);
this.phase = 'comfortable';
- this.currentSize = Math.floor((this.minSize + this.maxSize) / 2);
- this.comfortableSize = this.currentSize;
break;
case 'comfortable':
+ this.comfortableSizeResult = this.currentSize;
this.phase = 'confirmedComfort';
- this.comfortableSize = this.currentSize;
break;
case 'confirmedComfort':
- this.phase = 'finished';
- this.testComplete.emit();
+ this.finishAssessment();
break;
}
}
-
resetToMid() {
this.currentSize = Math.floor((this.minSize + this.maxSize) / 2);
this.phase = 'min';
+ this.minSizeResult = null;
+ this.maxSizeResult = null;
+ this.comfortableSizeResult = null;
}
retry() {
@@ -119,6 +104,28 @@ export class TextLegibilityAssessmentComponent implements AfterViewInit {
}
finishAssessment() {
+ this.phase = 'finished';
+
+ const finalResults = {
+ minReadableSize: this.minSizeResult,
+ maxReadableSize: this.maxSizeResult,
+ comfortableReadableSize: this.comfortableSizeResult
+ };
+
+ this.metricsService.logInteraction(new CustomEvent('test-results', {
+ detail: {
+ testName: 'TextLegibility',
+ results: finalResults
+ }
+ }));
+
+ console.log(finalResults);
+
this.testComplete.emit();
}
+
+
+ ngOnDestroy() {
+ this.metricsService.cleanup();
+ }
}
diff --git a/src/app/services/metrics-tracker.service.ts b/src/app/services/metrics-tracker.service.ts
new file mode 100644
index 0000000..5d6f69f
--- /dev/null
+++ b/src/app/services/metrics-tracker.service.ts
@@ -0,0 +1,168 @@
+import { HttpClient } from '@angular/common/http';
+import { inject, Injectable } from '@angular/core';
+import { interval, Subscription, tap } from 'rxjs';
+
+export interface InteractionEvent {
+ timestamp: number;
+ type: string;
+ elementId?: string;
+ elementTag?: string;
+ elementClasses?: string;
+ value?: any;
+}
+
+export interface DeviceOrientation {
+ timestamp: number;
+ alpha: number | null;
+ beta: number | null;
+ gamma: number | null;
+}
+
+export interface ArTrackingData {
+ timestamp: number;
+ anchor: string | null;
+ cameraOrbit: {
+ theta: number;
+ phi: number;
+ radius: number;
+ } | null;
+ cameraTarget: {
+ x: number;
+ y: number;
+ z: number;
+ } | null;
+}
+
+export interface MetricsLog {
+ interactions: InteractionEvent[];
+ deviceOrientations: DeviceOrientation[];
+ arData: ArTrackingData[];
+}
+
+@Injectable({
+ providedIn: 'root'
+})
+export class MetricsTrackerService {
+ private http = inject(HttpClient);
+ private serverUrl = '/api/log';
+
+ private metricsLog: MetricsLog = {
+ interactions: [],
+ deviceOrientations: [],
+ arData: []
+ };
+
+ private deviceOrientationSubscription: Subscription | null = null;
+ private arTrackingSubscription: Subscription | null = null;
+ private lastDeviceOrientation: DeviceOrientationEvent | null = null;
+
+ constructor() {
+ this.handleDeviceOrientation = this.handleDeviceOrientation.bind(this);
+ }
+
+ private handleDeviceOrientation(event: DeviceOrientationEvent): void {
+ this.lastDeviceOrientation = event;
+ }
+
+ // --- Public API ---
+ public logInteraction(event: Event): void {
+ if (event.target) {
+ const target = event.target as HTMLElement;
+ const interaction: InteractionEvent = {
+ timestamp: Date.now(),
+ type: event.type,
+ elementId: target.id || undefined,
+ elementTag: target.tagName,
+ elementClasses: target.className,
+ value: (target as any).value ?? undefined
+ };
+ this.metricsLog.interactions.push(interaction);
+ }
+ else if (event instanceof CustomEvent && event.detail) {
+ const interaction: InteractionEvent = {
+ timestamp: Date.now(),
+ type: event.type,
+ value: event.detail
+ };
+ this.metricsLog.interactions.push(interaction);
+ }
+ else {
+ console.warn("logInteraction called with an unknown event type:", event);
+ }
+ }
+
+
+ public startDeviceOrientationTracking(): void {
+ if (this.deviceOrientationSubscription || typeof window === 'undefined') return;
+
+ window.addEventListener('deviceorientation', this.handleDeviceOrientation);
+
+ this.deviceOrientationSubscription = interval(500).subscribe(() => {
+ if (this.lastDeviceOrientation) {
+ const orientation: DeviceOrientation = {
+ timestamp: this.lastDeviceOrientation.timeStamp,
+ alpha: this.lastDeviceOrientation.alpha,
+ beta: this.lastDeviceOrientation.beta,
+ gamma: this.lastDeviceOrientation.gamma
+ };
+ this.metricsLog.deviceOrientations.push(orientation);
+ }
+ });
+ }
+
+
+ public startArTracking(modelViewerElement: any): void {
+ if (this.arTrackingSubscription || !modelViewerElement) return;
+
+ this.arTrackingSubscription = interval(500).subscribe(() => {
+ if (!modelViewerElement.arActive) {
+ return;
+ }
+
+ const anchor = modelViewerElement.getAnchor ? modelViewerElement.getAnchor() : 'getAnchor not available';
+ const orbit = modelViewerElement.getCameraOrbit();
+ const target = modelViewerElement.getCameraTarget();
+
+ const arData: ArTrackingData = {
+ timestamp: Date.now(),
+ anchor: anchor.includes('not placed') ? null : anchor,
+ cameraOrbit: orbit ? { theta: orbit.theta, phi: orbit.phi, radius: orbit.radius } : null,
+ cameraTarget: target ? { x: target.x, y: target.y, z: target.z } : null,
+ };
+
+ this.metricsLog.arData.push(arData);
+ });
+ }
+
+ public sendMetricsToServer(testName: string, formData?: any) {
+ const payload = {
+ testName,
+ metricsLog: this.metricsLog,
+ ...(formData && { formData })
+ };
+ console.log(payload)
+ return this.http.post(this.serverUrl, payload).pipe(
+ tap({
+ next: (response) => {
+ console.log(`Metrics for '${testName}' sent successfully:`, response);
+ this.resetMetrics();
+ },
+ error: (err) => console.error(`Failed to send metrics for '${testName}':`, err)
+ })
+ );
+ }
+
+ public cleanup(): void {
+ if (typeof window !== 'undefined') {
+ window.removeEventListener('deviceorientation', this.handleDeviceOrientation);
+ }
+ this.deviceOrientationSubscription?.unsubscribe();
+ this.arTrackingSubscription?.unsubscribe();
+ this.deviceOrientationSubscription = null;
+ this.arTrackingSubscription = null;
+ }
+
+ public resetMetrics(): void {
+ this.metricsLog = { interactions: [], deviceOrientations: [], arData: [] };
+ }
+}
diff --git a/src/main.ts b/src/main.ts
index 681c68e..bd9a082 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -2,9 +2,11 @@ import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { provideRouter } from '@angular/router';
import { routes } from './app/app.routes';
+import { provideHttpClient } from '@angular/common/http';
bootstrapApplication(AppComponent, {
providers: [
- provideRouter(routes)
+ provideRouter(routes),
+ provideHttpClient()
]
-}).catch(err => console.error(err));
\ No newline at end of file
+}).catch(err => console.error(err));