polishing
parent
95d92cc4d9
commit
5b1daac97b
|
|
@ -18,6 +18,7 @@
|
|||
"@angular/router": "^18.0.0",
|
||||
"rxjs": "~7.8.0",
|
||||
"tslib": "^2.3.0",
|
||||
"uuid": "^13.0.0",
|
||||
"zone.js": "~0.14.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
@ -25,6 +26,7 @@
|
|||
"@angular/cli": "^18.0.6",
|
||||
"@angular/compiler-cli": "^18.0.0",
|
||||
"@types/jasmine": "~5.1.0",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"jasmine-core": "~5.1.0",
|
||||
"karma": "~6.4.0",
|
||||
|
|
@ -4627,6 +4629,13 @@
|
|||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/uuid": {
|
||||
"version": "10.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz",
|
||||
"integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/wrap-ansi": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz",
|
||||
|
|
@ -12380,6 +12389,16 @@
|
|||
"websocket-driver": "^0.7.4"
|
||||
}
|
||||
},
|
||||
"node_modules/sockjs/node_modules/uuid": {
|
||||
"version": "8.3.2",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"uuid": "dist/bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/socks": {
|
||||
"version": "2.8.7",
|
||||
"resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz",
|
||||
|
|
@ -13410,13 +13429,16 @@
|
|||
}
|
||||
},
|
||||
"node_modules/uuid": {
|
||||
"version": "8.3.2",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
|
||||
"dev": true,
|
||||
"version": "13.0.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz",
|
||||
"integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==",
|
||||
"funding": [
|
||||
"https://github.com/sponsors/broofa",
|
||||
"https://github.com/sponsors/ctavan"
|
||||
],
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"uuid": "dist/bin/uuid"
|
||||
"uuid": "dist-node/bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/validate-npm-package-license": {
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
"@angular/router": "^18.0.0",
|
||||
"rxjs": "~7.8.0",
|
||||
"tslib": "^2.3.0",
|
||||
"uuid": "^13.0.0",
|
||||
"zone.js": "~0.14.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
@ -27,6 +28,7 @@
|
|||
"@angular/cli": "^18.0.6",
|
||||
"@angular/compiler-cli": "^18.0.0",
|
||||
"@types/jasmine": "~5.1.0",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"jasmine-core": "~5.1.0",
|
||||
"karma": "~6.4.0",
|
||||
|
|
|
|||
|
|
@ -1,153 +1,31 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
import { Component } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Router } from '@angular/router';
|
||||
import { ArLoggerService } from '../../services/ar-logger.service';
|
||||
import { DataExportService } from '../../services/data-export.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-completion',
|
||||
standalone: true,
|
||||
imports: [CommonModule],
|
||||
template: `
|
||||
<div class="max-w-4xl mx-auto p-5 bg-white rounded-lg shadow-lg mt-8">
|
||||
<div class="text-center mb-8">
|
||||
<div class="w-16 h-16 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<svg class="w-8 h-8 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<h2 class="text-3xl font-bold text-gray-800 mb-2">Assessment Suite Completed Successfully</h2>
|
||||
<p class="text-gray-600 max-w-2xl mx-auto">
|
||||
Thank you for your participation in this research study. Your data has been successfully
|
||||
collected and is ready for download.
|
||||
</p>
|
||||
<div class="max-w-2xl mx-auto p-8 bg-white rounded-lg shadow-lg mt-10 text-center">
|
||||
|
||||
<div class="w-20 h-20 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-5">
|
||||
<svg class="w-12 h-12 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<!-- Results Summary -->
|
||||
<div class="bg-gray-50 rounded-lg p-6 mb-6">
|
||||
<h3 class="text-lg font-semibold text-gray-800 mb-4">Session Summary</h3>
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div class="text-center">
|
||||
<div class="text-2xl font-bold text-blue-600">{{completedTests}}</div>
|
||||
<div class="text-sm text-gray-600">Tests Completed</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<div class="text-2xl font-bold text-green-600">{{totalInteractions}}</div>
|
||||
<div class="text-sm text-gray-600">Total Interactions</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<div class="text-2xl font-bold text-purple-600">{{sessionDuration}}</div>
|
||||
<div class="text-sm text-gray-600">Session Duration</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h2 class="text-3xl font-bold text-gray-900 mb-3">
|
||||
Umfrage abgeschlossen
|
||||
</h2>
|
||||
|
||||
<p class="text-gray-600 text-lg max-w-md mx-auto">
|
||||
Ihre Teilnahme war erfolgreich. Vielen Dank für Ihren Beitrag zu unserer Forschung.
|
||||
</p>
|
||||
|
||||
<!-- Download Section -->
|
||||
<div class="text-center mb-6">
|
||||
<button
|
||||
(click)="downloadResults()"
|
||||
class="inline-flex items-center px-6 py-3 bg-blue-600 text-white font-medium
|
||||
rounded-lg hover:bg-blue-700 transition-colors shadow-lg">
|
||||
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z">
|
||||
</path>
|
||||
</svg>
|
||||
Download Results as CSV
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Data Preview -->
|
||||
<div class="bg-gray-50 border border-gray-200 rounded-lg p-4">
|
||||
<h4 class="text-sm font-semibold text-gray-700 mb-2">Data Preview:</h4>
|
||||
<pre class="text-xs text-gray-600 font-mono overflow-auto max-h-80"
|
||||
[innerHTML]="dataPreview">
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
<!-- Navigation -->
|
||||
<div class="text-center mt-8">
|
||||
<button
|
||||
(click)="restartAssessment()"
|
||||
class="px-4 py-2 text-blue-600 border border-blue-600 rounded-lg
|
||||
hover:bg-blue-50 transition-colors mr-4">
|
||||
Start New Assessment
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
styles: []
|
||||
})
|
||||
export class CompletionComponent implements OnInit {
|
||||
completedTests = 0;
|
||||
totalInteractions = 0;
|
||||
sessionDuration = '0m 0s';
|
||||
dataPreview = '';
|
||||
|
||||
constructor(
|
||||
private router: Router,
|
||||
private logger: ArLoggerService,
|
||||
private dataExportService: DataExportService
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.loadSessionSummary();
|
||||
this.generateDataPreview();
|
||||
}
|
||||
|
||||
private loadSessionSummary(): void {
|
||||
const testData = this.logger.getTestData();
|
||||
|
||||
// Count completed tests
|
||||
this.completedTests = Object.values(testData.results)
|
||||
.filter((result: any) => result.completedSuccessfully).length;
|
||||
|
||||
// Count total interactions
|
||||
this.totalInteractions = Object.values(testData.results)
|
||||
.reduce((total: number, result: any) => {
|
||||
return total + (result.interactionCount || 0);
|
||||
}, 0);
|
||||
|
||||
// Calculate session duration
|
||||
const duration = Date.now() - new Date(testData.device.timestamp).getTime();
|
||||
const minutes = Math.floor(duration / 60000);
|
||||
const seconds = Math.floor((duration % 60000) / 1000);
|
||||
this.sessionDuration = `${minutes}m ${seconds}s`;
|
||||
}
|
||||
|
||||
private generateDataPreview(): void {
|
||||
const testData = this.logger.getTestData();
|
||||
const preview = JSON.stringify(testData, null, 2);
|
||||
|
||||
// Truncate for display
|
||||
if (preview.length > 2000) {
|
||||
this.dataPreview = preview.substring(0, 2000) + '...\n\n[Preview truncated - complete data available in CSV export]';
|
||||
} else {
|
||||
this.dataPreview = preview;
|
||||
}
|
||||
}
|
||||
|
||||
downloadResults(): void {
|
||||
const csvData = this.logger.exportToCSV();
|
||||
const csvContent = csvData.map(row => row.join(',')).join('\n');
|
||||
|
||||
const blob = new Blob([csvContent], { type: 'text/csv' });
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
|
||||
a.setAttribute('hidden', '');
|
||||
a.setAttribute('href', url);
|
||||
a.setAttribute('download', `ceiling_ar_assessment_${this.logger.getTestData().device.sessionId}.csv`);
|
||||
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
|
||||
window.URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
restartAssessment(): void {
|
||||
// Reset services and navigate back to consent
|
||||
this.router.navigate(['/consent']);
|
||||
}
|
||||
}
|
||||
export class CompletionComponent {
|
||||
constructor() { }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,62 +1,75 @@
|
|||
<div class="max-w-4xl mx-auto p-5 bg-white rounded-lg shadow-lg mt-8">
|
||||
<div class="bg-yellow-50 border border-yellow-200 p-6 rounded-lg mb-6">
|
||||
<h2 class="text-2xl font-semibold text-gray-800 mb-4">
|
||||
Einverständniserklärung
|
||||
</h2>
|
||||
|
||||
<div class="space-y-4 text-gray-700">
|
||||
<p>
|
||||
<strong>Zweck:</strong> Diese empirische Studie untersucht optimale Design-Parameter für
|
||||
deckenbasierte Augmented-Reality-Anwendungen.
|
||||
</p>
|
||||
<div class="max-w-4xl mx-auto p-5 mt-8">
|
||||
|
||||
<!-- iOS Incompatibility Message -->
|
||||
<div *ngIf="isIosDevice" class="bg-red-100 border-l-4 border-red-500 text-red-700 p-6 rounded-lg shadow-lg text-center">
|
||||
<h2 class="text-xl font-bold mb-2">Inkompatibles Gerät</h2>
|
||||
<p class="text-base">
|
||||
Danke, dass Sie an dieser Studie teilnehmen wollen. Leider ist die Studie nur für Android-Geräte verfügbar.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Standard Consent Form (shown only on non-iOS devices) -->
|
||||
<div *ngIf="!isIosDevice" class="bg-white rounded-lg shadow-lg">
|
||||
<div class="bg-yellow-50 border border-yellow-200 p-6 rounded-lg mb-6">
|
||||
<h2 class="text-2xl font-semibold text-gray-800 mb-4">
|
||||
Einverständniserklärung
|
||||
</h2>
|
||||
|
||||
<div>
|
||||
<p class="font-semibold mb-2">Datenerhebungsprotokoll:</p>
|
||||
<ul class="list-disc list-inside space-y-1 ml-4">
|
||||
<li>Gerätespezifikationen (Display-Abmessungen, Browser-Version, Betriebssystem)</li>
|
||||
<li>WebXR-Interaktionsmetriken und Bewegungsverfolgungsdaten</li>
|
||||
<li>Parameteranpassungsmuster und Timing</li>
|
||||
<li>Geräteorientierung und Betrachtungswinkel-Parameter</li>
|
||||
<li>Erste Eingabeerkennung und natürliche Gestenanalyse</li>
|
||||
<li>Optionale demografische Informationen</li>
|
||||
</ul>
|
||||
<div class="space-y-4 text-gray-700">
|
||||
<p>
|
||||
<strong>Zweck:</strong> Diese empirische Studie untersucht optimale Design-Parameter für
|
||||
deckenbasierte Augmented-Reality-Anwendungen.
|
||||
</p>
|
||||
|
||||
<div>
|
||||
<p class="font-semibold mb-2">Datenerhebungsprotokoll:</p>
|
||||
<ul class="list-disc list-inside space-y-1 ml-4">
|
||||
<li>Gerätespezifikationen (Display-Abmessungen, Browser-Version, Betriebssystem)</li>
|
||||
<li>WebXR-Interaktionsmetriken und Bewegungsverfolgungsdaten</li>
|
||||
<li>Parameteranpassungsmuster und Timing</li>
|
||||
<li>Geräteorientierung und Betrachtungswinkel-Parameter</li>
|
||||
<li>Erste Eingabeerkennung und natürliche Gestenanalyse</li>
|
||||
<li>Optionale demografische Informationen</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
<strong>Datenschutz:</strong> Alle erhobenen Daten werden anonymisiert und ausschließlich
|
||||
für wissenschaftliche Analysen verwendet. Es werden keine personenbezogenen Daten erfasst.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<strong>Freiwillige Teilnahme:</strong> Die Teilnahme ist vollständig freiwillig.
|
||||
Sie können jederzeit ohne Konsequenzen zurücktreten.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
<strong>Datenschutz:</strong> Alle erhobenen Daten werden anonymisiert und ausschließlich
|
||||
für wissenschaftliche Analysen verwendet. Es werden keine personenbezogenen Daten erfasst.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<strong>Freiwillige Teilnahme:</strong> Die Teilnahme ist vollständig freiwillig.
|
||||
Sie können jederzeit ohne Konsequenzen zurücktreten.
|
||||
</p>
|
||||
<div class="mt-6">
|
||||
<label class="flex items-start space-x-3 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="mt-1 h-4 w-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500"
|
||||
[checked]="consentGiven"
|
||||
(change)="onConsentChange($event)"
|
||||
>
|
||||
<span class="text-gray-700">
|
||||
Ich stimme der Datenerhebung zu und erkläre mich bereit, an dieser Studie teilzunehmen.
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6">
|
||||
<label class="flex items-start space-x-3 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="mt-1 h-4 w-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500"
|
||||
[checked]="consentGiven"
|
||||
(change)="onConsentChange($event)"
|
||||
>
|
||||
<span class="text-gray-700">
|
||||
Ich stimme der Datenerhebung zu und erkläre mich bereit, an dieser Studie teilzunehmen.
|
||||
</span>
|
||||
</label>
|
||||
<div class="text-center">
|
||||
<button
|
||||
(click)="startTestSuite()"
|
||||
[disabled]="!consentGiven"
|
||||
class="px-7 py-3 bg-blue-600 text-white font-medium rounded-md hover:bg-blue-700
|
||||
disabled:bg-gray-400 disabled:cursor-not-allowed transition-all duration-200
|
||||
transform hover:scale-105 disabled:hover:scale-100"
|
||||
>
|
||||
Umfrage starten
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-center">
|
||||
<button
|
||||
(click)="startTestSuite()"
|
||||
[disabled]="!consentGiven"
|
||||
class="px-7 py-3 bg-blue-600 text-white font-medium rounded-md hover:bg-blue-700
|
||||
disabled:bg-gray-400 disabled:cursor-not-allowed transition-all duration-200
|
||||
transform hover:scale-105 disabled:hover:scale-100"
|
||||
>
|
||||
Umfrage starten
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Component } from '@angular/core';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
|
|
@ -11,23 +11,38 @@ import { ArLoggerService } from '../../services/ar-logger.service';
|
|||
templateUrl: './consent.component.html',
|
||||
styleUrls: ['./consent.component.css']
|
||||
})
|
||||
export class ConsentComponent {
|
||||
export class ConsentComponent implements OnInit {
|
||||
consentGiven = false;
|
||||
isIosDevice = false;
|
||||
|
||||
constructor(
|
||||
private router: Router,
|
||||
private logger: ArLoggerService
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.isIosDevice = this.isIOS();
|
||||
|
||||
if (this.isIosDevice) {
|
||||
console.log("iOS device detected. Study will be disabled.");
|
||||
}
|
||||
}
|
||||
|
||||
isIOS(): boolean {
|
||||
const win = window as any;
|
||||
return /iPad|iPhone|iPod/.test(navigator.userAgent) && !win.MSStream;
|
||||
}
|
||||
|
||||
|
||||
onConsentChange(event: Event): void {
|
||||
const target = event.target as HTMLInputElement;
|
||||
this.consentGiven = target.checked;
|
||||
}
|
||||
|
||||
startTestSuite(): void {
|
||||
if (this.consentGiven) {
|
||||
if (this.consentGiven && !this.isIosDevice) {
|
||||
this.logger.initializeSession();
|
||||
this.router.navigate(['/test-suite']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,8 @@ import '../../../../../assets/scripts/model-viewer';
|
|||
styleUrls: ['./spatial-position-assessment.component.css'],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
})
|
||||
export class SpatialPositionAssessmentComponent implements AfterViewInit, OnDestroy { // <-- Implement OnDestroy
|
||||
export class SpatialPositionAssessmentComponent implements AfterViewInit, OnDestroy {
|
||||
|
||||
@ViewChild('modelViewer') modelViewerRef!: ElementRef<any>;
|
||||
@Output() testComplete = new EventEmitter<void>();
|
||||
@Output() redoTest = new EventEmitter<number>();
|
||||
|
|
@ -41,10 +42,10 @@ export class SpatialPositionAssessmentComponent implements AfterViewInit, OnDest
|
|||
|
||||
ngAfterViewInit() {
|
||||
const mv = this.modelViewerRef.nativeElement;
|
||||
this.metricsService.startTracking(mv);
|
||||
mv.addEventListener('ar-status', (e: any) => {
|
||||
if (e.detail.status === 'session-started') {
|
||||
setTimeout(() => this.captureAnchor(), 500);
|
||||
this.metricsService.startTracking(mv);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,13 +46,11 @@ export class SpatialStabilityAssessmentComponent implements AfterViewInit, OnDes
|
|||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
const modelViewer = this.modelViewerRef.nativeElement;
|
||||
|
||||
this.metricsService.startTracking(modelViewer);
|
||||
|
||||
modelViewer.addEventListener('ar-status', (event: any) => {
|
||||
const mv = this.modelViewerRef.nativeElement;
|
||||
mv.addEventListener('ar-status', (event: any) => {
|
||||
if (event.detail.status === 'session-started' && !this.isModelPlaced) {
|
||||
setTimeout(() => this.getInitialAnchor(), 1000);
|
||||
this.metricsService.startTracking(mv);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ export class TextLegibilityAssessmentComponent implements AfterViewInit, OnDestr
|
|||
|
||||
private metricsService = inject(MetricsTrackerService);
|
||||
|
||||
// --- Component State (Restored) ---
|
||||
minSize = 2;
|
||||
maxSize = 64;
|
||||
currentSize = 16;
|
||||
|
|
@ -49,7 +48,11 @@ export class TextLegibilityAssessmentComponent implements AfterViewInit, OnDestr
|
|||
|
||||
ngAfterViewInit() {
|
||||
const mv = this.modelViewerRef.nativeElement;
|
||||
this.metricsService.startTracking(mv);
|
||||
mv.addEventListener('ar-status', (event: any) => {
|
||||
if (event.detail.status === 'session-started') {
|
||||
this.metricsService.startTracking(mv);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public logInteraction(event: Event) {
|
||||
|
|
@ -105,7 +108,7 @@ export class TextLegibilityAssessmentComponent implements AfterViewInit, OnDestr
|
|||
|
||||
finishAssessment() {
|
||||
this.phase = 'finished';
|
||||
|
||||
|
||||
const finalResults = {
|
||||
minReadableSize: this.minSizeResult,
|
||||
maxReadableSize: this.maxSizeResult,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import { HttpClient } from '@angular/common/http';
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { interval, Subscription, tap } from 'rxjs';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
// --- Data Interfaces ---
|
||||
export interface InteractionEvent {
|
||||
timestamp: number;
|
||||
type: string;
|
||||
|
|
@ -46,6 +48,8 @@ export class MetricsTrackerService {
|
|||
private http = inject(HttpClient);
|
||||
private serverUrl = '/api/log';
|
||||
|
||||
private deviceId: string;
|
||||
|
||||
private metricsLog: MetricsLog = {
|
||||
interactions: [],
|
||||
deviceOrientations: [],
|
||||
|
|
@ -55,10 +59,24 @@ export class MetricsTrackerService {
|
|||
private trackingSubscription: Subscription | null = null;
|
||||
private lastDeviceOrientation: DeviceOrientationEvent | null = null;
|
||||
|
||||
constructor() {
|
||||
this.handleDeviceOrientation = this.handleDeviceOrientation.bind(this);
|
||||
|
||||
const storedId = localStorage.getItem('device-uuid');
|
||||
if (storedId) {
|
||||
this.deviceId = storedId;
|
||||
} else {
|
||||
this.deviceId = uuidv4();
|
||||
localStorage.setItem('device-uuid', this.deviceId);
|
||||
}
|
||||
console.log('Device ID for this session:', this.deviceId);
|
||||
}
|
||||
|
||||
private handleDeviceOrientation(event: DeviceOrientationEvent): void {
|
||||
this.lastDeviceOrientation = event;
|
||||
}
|
||||
// --- Public API ---
|
||||
|
||||
|
||||
public logInteraction(event: Event): void {
|
||||
if (event.target) {
|
||||
const target = event.target as HTMLElement;
|
||||
|
|
@ -71,21 +89,18 @@ export class MetricsTrackerService {
|
|||
value: (target as any).value ?? undefined
|
||||
};
|
||||
this.metricsLog.interactions.push(interaction);
|
||||
}
|
||||
else if (event instanceof CustomEvent && event.detail) {
|
||||
} 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 {
|
||||
} else {
|
||||
console.warn("logInteraction called with an unknown event type:", event);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public startTracking(modelViewerElement: any): void {
|
||||
if (this.trackingSubscription || !modelViewerElement) return;
|
||||
|
||||
|
|
@ -112,9 +127,8 @@ export class MetricsTrackerService {
|
|||
};
|
||||
this.metricsLog.arData.push(arData);
|
||||
|
||||
let orientation: DeviceOrientation | null = null;
|
||||
if (this.lastDeviceOrientation) {
|
||||
orientation = {
|
||||
const orientation: DeviceOrientation = {
|
||||
timestamp: timestamp,
|
||||
alpha: this.lastDeviceOrientation.alpha,
|
||||
beta: this.lastDeviceOrientation.beta,
|
||||
|
|
@ -128,10 +142,11 @@ export class MetricsTrackerService {
|
|||
public sendMetricsToServer(testName: string, formData?: any) {
|
||||
const payload = {
|
||||
testName,
|
||||
deviceId: this.deviceId,
|
||||
metricsLog: this.metricsLog,
|
||||
...(formData && { formData })
|
||||
};
|
||||
console.log(payload)
|
||||
console.log("Sending final payload:", payload);
|
||||
return this.http.post(this.serverUrl, payload).pipe(
|
||||
tap({
|
||||
next: (response) => {
|
||||
|
|
@ -149,9 +164,10 @@ export class MetricsTrackerService {
|
|||
}
|
||||
this.trackingSubscription?.unsubscribe();
|
||||
this.trackingSubscription = null;
|
||||
this.lastDeviceOrientation = null;
|
||||
}
|
||||
|
||||
public resetMetrics(): void {
|
||||
this.metricsLog = { interactions: [], deviceOrientations: [], arData: [] };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue