import { Component, AfterViewInit, OnDestroy, ViewChild, ElementRef, CUSTOM_ELEMENTS_SCHEMA, ChangeDetectorRef, EventEmitter, 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({ selector: 'app-spatial-stability-assessment', standalone: true, imports: [CommonModule, FormsModule], templateUrl: './spatial-stability-assessment.component.html', styleUrls: ['./spatial-stability-assessment.component.css'], schemas: [CUSTOM_ELEMENTS_SCHEMA] }) export class SpatialStabilityAssessmentComponent implements AfterViewInit, OnDestroy { // <-- Implement OnDestroy @ViewChild('modelViewer') modelViewerRef!: ElementRef; @Output() testComplete = new EventEmitter(); @Output() redoTest = new EventEmitter(); private metricsService = inject(MetricsTrackerService); public verticalOffset = 0; public currentPhase = 0; // 0: waiting, 1: adjust, 2: lock, 3: countdown, 4: complete public remainingTime = 20; public progressPercentage = 0; private initialArAnchor: { x: number, y: number, z: number } | null = null; private lockedPosition: { verticalOffset: number, anchor: string } | null = null; private countdownInterval: any = null; protected isModelPlaced = false; constructor(private cdr: ChangeDetectorRef) { this.logInteraction = this.logInteraction.bind(this); } ngAfterViewInit() { const modelViewer = this.modelViewerRef.nativeElement; this.metricsService.startDeviceOrientationTracking(); this.metricsService.startArTracking(modelViewer); modelViewer.addEventListener('ar-status', (event: any) => { if (event.detail.status === 'session-started' && !this.isModelPlaced) { setTimeout(() => this.getInitialAnchor(), 1000); } }); } public logInteraction(event: Event) { this.metricsService.logInteraction(event); } private async getInitialAnchor() { const modelViewer = this.modelViewerRef.nativeElement; const anchorString = modelViewer.getAnchor(); if (!anchorString.includes('not placed')) { const coords = anchorString.split(' ').map(parseFloat); this.initialArAnchor = { x: coords[0], y: coords[1], z: coords[2] }; this.isModelPlaced = true; this.currentPhase = 1; this.cdr.detectChanges(); } else { setTimeout(() => this.getInitialAnchor(), 500); } } onSliderInput() { if (this.initialArAnchor && this.currentPhase === 1) { const newY = this.initialArAnchor.y + this.verticalOffset; const anchor = `${this.initialArAnchor.x} ${newY} ${this.initialArAnchor.z}`; this.modelViewerRef.nativeElement.setAttribute('ar-anchor', anchor); } } lockPosition() { this.lockedPosition = { verticalOffset: this.verticalOffset, anchor: this.modelViewerRef.nativeElement.getAttribute('ar-anchor') }; this.currentPhase = 2; } adjustMore() { this.currentPhase = 1; } startCountdown() { this.currentPhase = 3; this.remainingTime = 20; this.progressPercentage = 0; this.countdownInterval = setInterval(() => { this.remainingTime--; this.progressPercentage = ((20 - this.remainingTime) / 20) * 100; if (this.remainingTime <= 0) { this.completeTest(true); } this.cdr.detectChanges(); }, 1000); } cancelTest() { if (this.countdownInterval) { clearInterval(this.countdownInterval); this.countdownInterval = null; } this.currentPhase = 2; this.metricsService.logInteraction(new CustomEvent('test-cancelled', { detail: { phase: 'countdown' }})); } private completeTest(wasCompleted: boolean) { if (this.countdownInterval) { clearInterval(this.countdownInterval); this.countdownInterval = null; } const finalResults = { testCompletedSuccessfully: wasCompleted, initialAnchor: this.initialArAnchor, lockedPosition: this.lockedPosition, }; this.metricsService.logInteraction(new CustomEvent('test-results', { detail: { testName: 'SpatialStability', results: finalResults } })); if (wasCompleted) { this.currentPhase = 4; } if(wasCompleted) { this.testComplete.emit(); } } restartTest() { this.currentPhase = 1; this.remainingTime = 20; this.progressPercentage = 0; this.lockedPosition = null; if (this.countdownInterval) { clearInterval(this.countdownInterval); this.countdownInterval = null; } this.metricsService.resetMetrics(); } finishAssessment() { this.testComplete.emit(); } ngOnDestroy() { if (this.countdownInterval) { clearInterval(this.countdownInterval); } this.metricsService.cleanup(); } }