implement another usability test

master
MrPlatnum 2025-09-25 16:03:44 +02:00
parent eb69558504
commit b09823850b
14 changed files with 1666 additions and 69 deletions

Binary file not shown.

View File

@ -18,6 +18,10 @@ export const routes: Routes = [
path: 'completion', path: 'completion',
loadComponent: () => import('./components/completion/completion.component').then(c => c.CompletionComponent) loadComponent: () => import('./components/completion/completion.component').then(c => c.CompletionComponent)
}, },
{
path: 'test',
loadComponent: () => import('./components/test-suite/assessments/preivew/ceiling-placement-demonstrator.component').then(c => c.CeilingPlacementDemonstratorComponent)
},
{ {
path: '**', path: '**',
redirectTo: '/consent' redirectTo: '/consent'

View File

@ -17,8 +17,8 @@
<div class="space-y-4 text-gray-700"> <div class="space-y-4 text-gray-700">
<p> <p>
<strong>Zweck:</strong> Diese empirische Studie untersucht optimale Design-Parameter für <strong>Zweck:</strong> Diese Studie untersucht optimale Design-Parameter für
deckenbasierte Augmented-Reality-Anwendungen. deckenbasierte Augmented-Reality-Anwendungen. In dieser Simulation geht es darum ein Einkaufserlebnis für eine Deckenlampe zu simulieren um die Benutzbarkeit der Software abschätzen zu können.
</p> </p>
<div> <div>
@ -35,6 +35,7 @@
<p> <p>
<strong>Datenschutz:</strong> Alle erhobenen Daten werden anonymisiert und ausschließlich <strong>Datenschutz:</strong> Alle erhobenen Daten werden anonymisiert und ausschließlich
für wissenschaftliche Analysen verwendet. Es werden keine personenbezogenen Daten erfasst. für wissenschaftliche Analysen verwendet. Es werden keine personenbezogenen Daten erfasst.
Es werden keine Bilder oder Videos erfasst.
</p> </p>
<p> <p>

View File

@ -0,0 +1,142 @@
<div class="min-h-screen bg-gray-50 py-2 px-4">
<div class="max-w-3xl mx-auto bg-white rounded-lg shadow-lg p-8">
<div class="text-center mb-8">
<h2 class="text-3xl font-bold text-gray-900">Abschließender Fragebogen</h2>
<p class="text-gray-600 mt-2">Ihre Angaben helfen uns bei der wissenschaftlichen Auswertung der Studie.</p>
</div>
<form [formGroup]="feedbackForm" (ngSubmit)="onSubmit()">
<div class="mb-8">
<h3 class="text-xl font-semibold text-gray-800 mb-4 flex items-center">
<span class="bg-blue-500 text-white rounded-full w-6 h-6 flex items-center justify-center text-sm mr-3">2</span>
Demografische Angaben
</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label for="age-input" class="block text-sm font-medium text-gray-700 mb-2">Alter *</label>
<input id="age-input" type="number" formControlName="age" min="13" max="120" (change)="logInteraction($event)" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="Ihr Alter">
</div>
<div>
<label for="gender-select" class="block text-sm font-medium text-gray-700 mb-2">Geschlecht *</label>
<select id="gender-select" formControlName="gender" (change)="logInteraction($event)" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
<option [ngValue]="null" disabled>Bitte auswählen</option>
<option value="male">Männlich</option>
<option value="female">Weiblich</option>
<option value="non-binary">Divers</option>
<option value="prefer-not-to-say">Keine Angabe</option>
</select>
</div>
</div>
</div>
<div class="mb-8">
<h3 class="text-xl font-semibold text-gray-800 mb-4 flex items-center">
<span class="bg-blue-500 text-white rounded-full w-6 h-6 flex items-center justify-center text-sm mr-3">3</span>
Vorerfahrung mit AR/VR
</h3>
<div class="space-y-6">
<div class="p-4 border border-gray-200 rounded-lg">
<label class="block text-sm font-medium text-gray-800 mb-3">Erfahrung mit Augmented Reality (AR) *</label>
<div class="flex justify-between items-center">
<span class="text-xs text-gray-500 w-1/5 text-center">Keine<br>Erfahrung</span>
<div class="flex-grow flex justify-around">
<label *ngFor="let option of [1, 2, 3, 4, 5]" class="flex flex-col items-center cursor-pointer">
<input type="radio" formControlName="arExperience" [value]="option" (change)="logInteraction($event)" class="mb-1 focus:ring-blue-500 text-blue-600">
<span class="text-sm">{{ option }}</span>
</label>
</div>
<span class="text-xs text-gray-500 w-1/5 text-center">Experte</span>
</div>
</div>
<div class="p-4 border border-gray-200 rounded-lg">
<label class="block text-sm font-medium text-gray-800 mb-3">Erfahrung mit Virtual Reality (VR) *</label>
<div class="flex justify-between items-center">
<span class="text-xs text-gray-500 w-1/5 text-center">Keine<br>Erfahrung</span>
<div class="flex-grow flex justify-around">
<label *ngFor="let option of [1, 2, 3, 4, 5]" class="flex flex-col items-center cursor-pointer">
<input type="radio" formControlName="vrExperience" [value]="option" (change)="logInteraction($event)" class="mb-1 focus:ring-blue-500 text-blue-600">
<span class="text-sm">{{ option }}</span>
</label>
</div>
<span class="text-xs text-gray-500 w-1/5 text-center">Experte</span>
</div>
</div>
</div>
</div>
<div class="mb-8">
<h3 class="text-xl font-semibold text-gray-800 mb-4 flex items-center">
<span class="bg-blue-500 text-white rounded-full w-6 h-6 flex items-center justify-center text-sm mr-3">4</span>
Bewertung der Benutzerfreundlichkeit
</h3>
<p class="text-sm text-gray-600 mb-6">Bitte bewerten Sie die folgende Anwendung anhand der nachstehenden Aussagen auf einer Skala von 1 (stimme überhaupt nicht zu) bis 5 (stimme voll zu).</p>
<div class="space-y-1">
<div *ngFor="let item of usabilityItems; let i = index" class="p-1 border border-gray-200 rounded-lg">
<label class="block text-sm font-medium text-gray-800 mb-3">{{ i + 1 }}. {{ item.label }} *</label>
<div class="flex justify-between items-center text-center">
<span class="text-xs text-gray-500 w-1/5">Stimme überhaupt<br>nicht zu</span>
<div class="flex-grow flex justify-around">
<label *ngFor="let option of [1, 2, 3, 4, 5]" class="flex flex-col items-center cursor-pointer">
<input type="radio" [formControlName]="item.controlName" [value]="option" (change)="logInteraction($event)" class="mb-1 focus:ring-blue-500 text-blue-600">
<span class="text-sm">{{ option }}</span>
</label>
</div>
<span class="text-xs text-gray-500 w-1/5">Stimme<br>voll zu</span>
</div>
</div>
</div>
</div>
<div class="mb-8">
<h3 class="text-xl font-semibold text-gray-800 mb-4 flex items-center">
<span class="bg-blue-500 text-white rounded-full w-6 h-6 flex items-center justify-center text-sm mr-3">5</span>
Detailliertes Feedback zur Anwendung
</h3>
<p class="text-sm text-gray-600 mb-6">Bitte bewerten Sie die folgenden Aussagen auf einer Skala von 1 (stimme überhaupt nicht zu) bis 7 (stimme voll zu).</p>
<div class="space-y-4">
<div *ngFor="let item of harItems; let i = index" class="p-4 border border-gray-200 rounded-lg">
<label class="block text-sm font-medium text-gray-800 mb-3">{{ i + 1 }}. {{ item.label }} *</label>
<div class="flex justify-between items-center text-center">
<span class="text-xs text-gray-500 w-1/6">Stimme überhaupt<br>nicht zu</span>
<div class="flex-grow flex justify-around">
<label *ngFor="let option of [1, 2, 3, 4, 5, 6, 7]" class="flex flex-col items-center cursor-pointer">
<input type="radio" [formControlName]="item.controlName" [value]="option" (change)="logInteraction($event)" class="mb-1 focus:ring-blue-500 text-blue-600">
<span class="text-sm">{{ option }}</span>
</label>
</div>
<span class="text-xs text-gray-500 w-1/6">Stimme<br>voll zu</span>
</div>
</div>
</div>
</div>
<div class="mb-8">
<h3 class="text-xl font-semibold text-gray-800 mb-4 flex items-center">
<span class="bg-blue-500 text-white rounded-full w-6 h-6 flex items-center justify-center text-sm mr-3">6</span>
Abschließendes Feedback
</h3>
<div>
<label for="comments-textarea" class="block text-sm font-medium text-gray-700 mb-2">Anmerkungen und Verbesserungsvorschläge</label>
<textarea id="comments-textarea" formControlName="comments" rows="4" (change)="logInteraction($event)" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="Ihre Gedanken, Vorschläge oder aufgetretene Probleme..."></textarea>
</div>
</div>
<div class="text-center">
<button id="submit-button" type="submit" (click)="logInteraction($event)" [disabled]="!feedbackForm.valid || isSubmitting" class="bg-blue-600 hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed text-white font-bold py-3 px-8 rounded-lg text-lg transition-colors duration-200">
<span *ngIf="!isSubmitting">Fragebogen absenden</span>
<span *ngIf="isSubmitting">Wird übermittelt...</span>
</button>
<div *ngIf="!feedbackForm.valid && feedbackForm.touched" class="text-center mt-2">
<p class="text-red-500 text-sm">Bitte füllen Sie alle mit * markierten Pflichtfelder aus.</p>
</div>
</div>
</form>
<div *ngIf="isSubmitted" class="mt-8 p-4 bg-green-100 border border-green-400 text-green-700 rounded-md text-center">
<h4 class="font-semibold">Vielen Dank!</h4>
<p>Ihre Angaben wurden erfolgreich übermittelt. Die Studie ist hiermit abgeschlossen.</p>
</div>
</div>
</div>

View File

@ -0,0 +1,117 @@
import { Component, EventEmitter, inject, OnInit, Output } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ReactiveFormsModule, FormBuilder, FormGroup, Validators } from '@angular/forms';
import { MetricsTrackerService } from '../../../../services/metrics-tracker.service';
@Component({
selector: 'app-feedback',
standalone: true,
imports: [CommonModule, ReactiveFormsModule],
templateUrl: './feedback.component.html',
})
export class FeedbackComponent implements OnInit {
private metricsService = inject(MetricsTrackerService);
private formBuilder = inject(FormBuilder);
@Output() testComplete = new EventEmitter<void>();
feedbackForm!: FormGroup;
isSubmitting = false;
isSubmitted = false;
usabilityItems = [
{ label: 'Ich denke, dass ich diese AR-Anwendung gerne regelmäßig nutzen würde.', controlName: 'usability1' },
{ label: 'Ich empfinde diese AR-Anwendung als unnötig komplex.', controlName: 'usability2' },
{ label: 'Ich empfinde diese AR-Anwendung als einfach zu nutzen.', controlName: 'usability3' },
{ label: 'Ich denke, dass ich technischen Support brauchen würde, um diese AR-Anwendung zu nutzen.', controlName: 'usability4' },
{ label: 'Ich finde, dass die verschiedenen Funktionen dieser AR-Anwendung gut integriert sind.', controlName: 'usability5' },
{ label: 'Ich finde, dass es in dieser AR-Anwendung zu viele Inkonsistenzen gibt.', controlName: 'usability6' },
{ label: 'Ich kann mir vorstellen, dass die meisten Leute diese AR-Anwendung schnell zu beherrschen lernen.', controlName: 'usability7' },
{ label: 'Ich empfinde die Bedienung dieser AR-Anwendung als sehr umständlich.', controlName: 'usability8' },
{ label: 'Ich habe mich bei der Nutzung dieser AR-Anwendung sehr sicher gefühlt.', controlName: 'usability9' },
{ label: 'Ich musste eine Menge Dinge lernen, bevor ich mit dieser AR-Anwendung arbeiten konnte.', controlName: 'usability10' }
];
harItems = [
{ label: 'Ich finde, dass die Interaktion mit dieser Anwendung eine hohe körperliche Muskelanstrengung erfordert.', controlName: 'har1' },
{ label: 'Ich empfand die Nutzung der Anwendung als angenehm für meine Arme und Hände.', controlName: 'har2' },
{ label: 'Ich fand es schwierig, das Gerät während der Bedienung der Anwendung zu halten.', controlName: 'har3' },
{ label: 'Ich fand es einfach, Informationen über die Anwendung einzugeben.', controlName: 'har4' },
{ label: 'Ich hatte das Gefühl, dass mein Arm oder meine Hand nach der Nutzung der Anwendung müde wurde.', controlName: 'har5' },
{ label: 'Ich denke, die Anwendung ist einfach zu steuern.', controlName: 'har6' },
{ label: 'Ich hatte das Gefühl, dass ich irgendwann den Halt verlor und das Gerät fallen ließ.', controlName: 'har7' },
{ label: 'Ich denke, die Bedienung dieser Anwendung ist einfach und unkompliziert.', controlName: 'har8' },
{ label: 'Ich denke, dass die Interaktion mit dieser Anwendung eine hohe geistige Anstrengung erfordert.', controlName: 'har9' },
{ label: 'Ich fand die Menge der auf dem Bildschirm angezeigten Informationen angemessen.', controlName: 'har10' },
{ label: 'Ich fand die auf dem Bildschirm angezeigten Informationen schwer lesbar.', controlName: 'har11' },
{ label: 'Ich hatte das Gefühl, dass die Informationsanzeige schnell genug reagierte.', controlName: 'har12' },
{ label: 'Ich fand die auf dem Bildschirm angezeigten Informationen verwirrend.', controlName: 'har13' },
{ label: 'Ich fand die Wörter und Symbole auf dem Bildschirm leicht lesbar.', controlName: 'har14' },
{ label: 'Ich hatte das Gefühl, dass das Display zu stark flimmerte.', controlName: 'har15' },
{ label: 'Ich fand die auf dem Bildschirm angezeigten Informationen konsistent.', controlName: 'har16' }
];
constructor() {
this.logInteraction = this.logInteraction.bind(this);
}
ngOnInit() {
this.createForm();
}
logInteraction(event: Event) {
this.metricsService.logInteraction(event);
}
createForm() {
const formControls: { [key: string]: any } = {
// Demographics
age: [null, [Validators.required, Validators.min(13), Validators.max(120)]],
gender: [null, Validators.required],
// Experience
arExperience: [null, Validators.required],
vrExperience: [null, Validators.required],
// Final Comments
comments: [''],
};
// Usability items (1-5 scale)
this.usabilityItems.forEach(item => {
formControls[item.controlName] = [null, Validators.required];
});
// HAR items (1-7 scale)
this.harItems.forEach(item => {
formControls[item.controlName] = [null, Validators.required];
});
this.feedbackForm = this.formBuilder.group(formControls);
}
onSubmit() {
if (this.feedbackForm.valid) {
this.isSubmitting = true;
const formData = {
...this.feedbackForm.value,
submittedAt: new Date().toISOString()
};
this.metricsService.sendMetricsToServer(formData).subscribe({
next: () => {
this.isSubmitting = false;
this.isSubmitted = true;
setTimeout(() => this.testComplete.emit(), 3000);
},
error: (err) => {
console.error("FINAL SUBMISSION FAILED:", err);
this.isSubmitting = false;
}
});
} else {
this.feedbackForm.markAllAsTouched();
console.error("Form is invalid. Please fill out all required fields.");
}
}
}

View File

@ -0,0 +1,33 @@
.vertical-slider {
-webkit-appearance: none;
appearance: none;
width: 120px;
height: 8px;
background: #4a5568;
border-radius: 9999px;
outline: none;
cursor: pointer;
transform: rotate(-90deg);
}
.vertical-slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 24px;
height: 24px;
background: #ffffff;
border-radius: 50%;
border: 2px solid #e2e8f0;
}
.vertical-slider::-moz-range-thumb {
width: 24px;
height: 24px;
background: #ffffff;
border-radius: 50%;
border: 2px solid #e2e8f0;
}

View File

@ -0,0 +1,63 @@
<div class="w-full h-1/2 relative font-sans">
<div *ngIf="arHasBeenActive && !isArActive" class="w-full flex justify-center">
<button (click)="finishExperience()"
class="bg-green-600 text-white py-3 px-8 rounded-lg shadow-lg hover:bg-green-700 transition-colors">
Einkaufserlebnis beenden
</button>
</div>
<div class="w-full h-full relative font-sans mb-4">
<model-viewer #modelViewer class="absolute inset-0 w-full h-96" src="models/CeilingLamp.glb" ar camera-controls
ar-modes="webxr" ar-placement="ceiling" ar-interaction="true" camera-orbit="0deg 135deg 105%"
shadow-intensity="1" ar-scale="fixed" scale="0.1 0.1 0.1" alt="Virtuelles 3D-Modell einer Deckenleuchte">
<button slot="ar-button" (click)="logInteraction($event)"
class="absolute top-4 left-1/2 -translate-x-1/2 bg-blue-600 text-white py-2 px-6 rounded-lg shadow-md hover:bg-blue-700 transition-colors z-10">
Diese Lampe bei mir Zuhause anschauen
</button>
<div *ngIf="isArActive" class="absolute inset-0 z-20 pointer-events-none">
<div *ngIf="isInfoBoxVisible"
class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-black bg-opacity-80 p-6 rounded-xl text-white text-center shadow-lg transition-opacity duration-300 w-80 pointer-events-auto flex flex-col items-center">
<p class="mb-4">
Versuchen Sie die Lampe an der Decke zu plazieren und schauen Sie sich die Lampe an. Die Lampe
lässt sich mit den Fingern verschieben und mit dem Slider in der Höhe platzieren.
</p>
<button (click)="dismissInfoBox($event)"
class="bg-blue-600 text-white py-2 px-6 rounded-lg hover:bg-blue-700 transition-colors">
Verstanden
</button>
</div>
<div *ngIf="!isInfoBoxVisible && !isModelPlaced && !isCeilingAimed"
class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-black bg-opacity-70 p-4 rounded-xl text-white text-center transition-opacity duration-300 w-64 pointer-events-auto">
<h4 class="text-lg font-semibold">Decke anvisieren</h4>
<p class="mt-1 text-sm opacity-90">Richten Sie Ihr Gerät nach oben und schauen Sie sich um.</p>
</div>
<div *ngIf="isModelPlaced"
class="ar-controls absolute bottom-8 left-1/2 -translate-x-1/2 bg-black bg-opacity-70 p-5 rounded-xl flex flex-col items-center space-y-4 text-white z-10 pointer-events-auto w-80">
<div class="flex flex-col items-start w-full">
<label for="offset-slider" class="text-sm font-medium mb-1">Höhe anpassen</label>
<input id="offset-slider" type="range" min="-0.5" max="2" step="0.01" [ngModel]="verticalOffset"
(ngModelChange)="onOffsetChange($event)" (input)="logInteraction($event)"
class="w-full h-2 bg-gray-700 rounded-lg appearance-none cursor-pointer" />
<span class="text-xs mt-1 self-center">{{ verticalOffset >= 0 ? '+' : '' }}{{
verticalOffset.toFixed(2) }}m</span>
</div>
<div class="flex space-x-4 justify-center w-full">
<button id="reset-ceiling-btn" (click)="resetOffset($event)"
class="bg-gray-600 text-white py-2 px-4 rounded-lg flex-1">
Höhe zurücksetzen
</button>
</div>
</div>
</div>
</model-viewer>
</div>
</div>

View File

@ -0,0 +1,169 @@
import {
Component,
AfterViewInit,
OnDestroy,
ViewChild,
ElementRef,
CUSTOM_ELEMENTS_SCHEMA,
NgZone,
inject,
Output,
EventEmitter
} from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import '../../../../../assets/scripts/model-viewer.min.js';
import { MetricsTrackerService } from '../../../../services/metrics-tracker.service.js';
interface ModelViewerElement extends HTMLElement {
arVerticalOffset?: number;
setAttribute(name: string, value: string): void;
getAttribute(name: string): string | null;
removeAttribute(name: string): void;
getAnchor: () => Promise<string | null>;
activateAR: () => Promise<void>;
deactivateAR?: () => void;
}
@Component({
selector: 'app-ceiling-placement-demonstrator',
standalone: true,
imports: [CommonModule, FormsModule],
templateUrl: 'ceiling-placement-demonstrator.component.html',
styleUrl: 'ceiling-placement-demonstrator.component.css',
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class CeilingPlacementDemonstratorComponent implements AfterViewInit, OnDestroy {
@ViewChild('modelViewer') modelViewerRef!: ElementRef<ModelViewerElement>;
@Output() testComplete = new EventEmitter<void>();
private metricsService = inject(MetricsTrackerService);
private zone = inject(NgZone);
public isArActive = false;
public isModelPlaced = false;
public isCeilingAimed = false;
public arHasBeenActive = false;
public isInfoBoxVisible = true;
private hasStartedTracking = false;
public verticalOffset = 0;
private modelViewerElement!: ModelViewerElement;
constructor() {
this.logInteraction = this.logInteraction.bind(this);
}
ngAfterViewInit() {
this.modelViewerElement = this.modelViewerRef.nativeElement;
this.modelViewerElement.addEventListener('ar-status', this.handleArStatus);
this.modelViewerElement.addEventListener('ar-tracking', (ev: any) => {
this.zone.run(() => {
this.isCeilingAimed = ev?.detail?.status === 'tracking';
});
});
}
public logInteraction(event: Event) {
this.metricsService.logInteraction(event);
}
public async activateAr(event: MouseEvent) {
this.logInteraction(event);
try {
await this.modelViewerElement.activateAR();
} catch (error) {
console.error("Failed to activate AR:", error);
}
}
public dismissInfoBox(event: MouseEvent) {
this.isInfoBoxVisible = false;
this.logInteraction(event);
}
private pushVerticalOffset(value: number) {
if (!this.modelViewerElement) return;
(this.modelViewerElement as any).arVerticalOffset = value;
this.modelViewerElement.setAttribute('ar-vertical-offset', String(value));
}
public onOffsetChange(value: number) {
this.verticalOffset = value;
this.pushVerticalOffset(value);
}
public finishExperience() {
const finalPlacement = {
finalVerticalOffset: this.verticalOffset,
};
this.metricsService.logInteraction(new CustomEvent('test-results', {
detail: {
testName: 'CeilingPlacementDemonstrator',
results: finalPlacement
}
}));
this.testComplete.emit();
}
public resetOffset(event: Event) {
this.verticalOffset = 0;
this.pushVerticalOffset(0);
this.logInteraction(event);
}
private handleArStatus = (event: any) => {
this.zone.run(() => {
const status = event.detail?.status;
switch (status) {
case 'session-started':
this.isArActive = true;
this.arHasBeenActive = true;
this.isModelPlaced = false;
this.isInfoBoxVisible = true;
if (!this.hasStartedTracking) {
this.hasStartedTracking = true;
this.metricsService.startTracking(this.modelViewerElement, "ceiling_placement");
}
this.pushVerticalOffset(this.verticalOffset);
break;
case 'object-placed':
this.isModelPlaced = true;
this.isInfoBoxVisible = false;
this.pushVerticalOffset(this.verticalOffset);
break;
case 'not-presenting':
case 'failed':
this.isArActive = false;
break;
}
});
};
private resetArState() {
this.isArActive = false;
this.isModelPlaced = false;
this.isCeilingAimed = false;
this.isInfoBoxVisible = true;
this.verticalOffset = 0;
}
ngOnDestroy() {
if (this.modelViewerElement) {
this.modelViewerElement.removeEventListener('ar-status', this.handleArStatus);
}
if(this.hasStartedTracking) {
this.metricsService.stopTracking();
}
this.resetArState();
}
}

View File

@ -2,31 +2,24 @@
<!-- Header --> <!-- Header -->
<div class="mb-6"> <div class="mb-6">
<h1 class="text-3xl font-bold text-gray-800 mb-6">Augmented Reality Umfrage</h1>
<!-- Progress Section -->
<div class="mb-6">
<div class="flex justify-between items-center mb-2">
<span class="text-sm font-medium text-gray-700">Test {{ currentTest }} von {{ totalTests }}</span>
<span class="text-sm font-medium text-gray-700">{{ progress | number:'1.1-1' }}%</span>
</div>
<div class="w-full bg-gray-200 rounded-full h-2 overflow-hidden">
<div class="bg-gradient-to-r from-blue-600 to-blue-400 h-full rounded-full" [style.width.%]="progress"></div>
</div>
</div>
<!-- Status Display -->
<div *ngIf="currentTest < 4" class="bg-gray-50 border border-gray-200 rounded-lg p-4 mb-6 font-mono text-sm">
<div class="text-red-600">Bitte achten Sie darauf die Kamera auf eine Deckenfläche zu richten.</div>
</div>
</div>
<!-- Assessment Container --> <!-- Assessment Container -->
<div class="assessment-container" style="height: 70vh; min-height: 500px;"> <div class="assessment-container" class="h-full" style="min-height: 500px;">
<div *ngIf="currentTest === 1" class="w-full h-full flex flex-col">
<!-- Assessment 1 --> <div class="bg-gray-50 border border-gray-200 rounded-lg p-4 mb-4 font-mono text-sm">
<div class="text-black-600">
<h2 class="text-base mb-2">In dieser Simulation geht es darum ein Einkaufserlebnis in Augmented Reality für Objekte zu simulieren die an der Decke befestigt werden können. Wenn Sie mit der Plazierung zufriden sich können sie die AR Umgebung mit dem X schliesen und das Einkaufserlebnis beenden.</h2>
</div>
</div>
<div class="flex-1 min-h-0">
<app-ceiling-placement-demonstrator (testComplete)="onTestComplete()" (redoTest)="redoTest(1)"></app-ceiling-placement-demonstrator>
</div>
</div>
<!--
Assessment 1
<div *ngIf="currentTest === 1" class="w-full h-full flex flex-col"> <div *ngIf="currentTest === 1" class="w-full h-full flex flex-col">
<!-- Description -->
<div class="bg-gray-50 border border-gray-200 rounded-lg p-4 mb-4 font-mono text-sm"> <div class="bg-gray-50 border border-gray-200 rounded-lg p-4 mb-4 font-mono text-sm">
<div class="text-black-600"> <div class="text-black-600">
<h2 class="text-base mb-2">In diesem Test geht es darum, die Lesbarkeit von Texten in Augmented-Reality-Umgebungen (AR) im Kontext von Deckenflächen zu untersuchen.</h2> <h2 class="text-base mb-2">In diesem Test geht es darum, die Lesbarkeit von Texten in Augmented-Reality-Umgebungen (AR) im Kontext von Deckenflächen zu untersuchen.</h2>
@ -38,13 +31,11 @@
</div> </div>
</div> </div>
<!-- Component container -->
<div class="flex-1 min-h-0"> <div class="flex-1 min-h-0">
<app-text-legibility-assessment (testComplete)="onTestComplete()" (redoTest)="redoTest(1)"></app-text-legibility-assessment> <app-text-legibility-assessment (testComplete)="onTestComplete()" (redoTest)="redoTest(1)"></app-text-legibility-assessment>
</div> </div>
</div> </div>
<!-- Assessment 2 -->
<div *ngIf="currentTest === 2" class="w-full h-full flex flex-col"> <div *ngIf="currentTest === 2" class="w-full h-full flex flex-col">
<div class="bg-gray-50 border border-gray-200 rounded-lg p-4 mb-4 font-mono text-sm"> <div class="bg-gray-50 border border-gray-200 rounded-lg p-4 mb-4 font-mono text-sm">
<div class="text-black-600"> <div class="text-black-600">
@ -57,7 +48,6 @@
</div> </div>
</div> </div>
<!-- Assessment 3 -->
<div *ngIf="currentTest === 3" class="w-full h-full flex flex-col"> <div *ngIf="currentTest === 3" class="w-full h-full flex flex-col">
<div class="bg-gray-50 border border-gray-200 rounded-lg p-4 mb-4 font-mono text-sm"> <div class="bg-gray-50 border border-gray-200 rounded-lg p-4 mb-4 font-mono text-sm">
<div class="text-black-600"> <div class="text-black-600">
@ -72,21 +62,14 @@
<div class="flex-1 min-h-0"> <div class="flex-1 min-h-0">
<app-spatial-stability-assessment (testComplete)="onTestComplete()" (redoTest)="redoTest(3)"></app-spatial-stability-assessment> <app-spatial-stability-assessment (testComplete)="onTestComplete()" (redoTest)="redoTest(3)"></app-spatial-stability-assessment>
</div> </div>
</div> </div> -->
<!-- Assessment 4 --> <!-- Assessment 4 -->
<div *ngIf="currentTest === 4" class="w-full h-full overflow-auto"> <div *ngIf="currentTest === 2" class="w-full h-full overflow-auto">
<app-demographics-feedback (testComplete)="onTestComplete()" (redoTest)="redoTest(4)"></app-demographics-feedback> <app-feedback (testComplete)="onTestComplete()" (redoTest)="redoTest(4)"></app-feedback>
</div> </div>
</div> </div>
<!-- Skip Button -->
<div *ngIf="currentTest !== 4" class="flex justify-center mt-6 mb-4">
<button (click)="forceNextTest()"
class="bg-red-600 hover:bg-red-700 text-white font-bold py-3 px-6 rounded-lg shadow-lg text-sm">
Test überspringen
</button>
</div>
</div> </div>

View File

@ -7,6 +7,8 @@ import { SpatialStabilityAssessmentComponent } from './assessments/spatial-stabi
import { TextLegibilityAssessmentComponent } from './assessments/text-legibility-assessment/text-legibility-assessment.component'; import { TextLegibilityAssessmentComponent } from './assessments/text-legibility-assessment/text-legibility-assessment.component';
import { SpatialPositionAssessmentComponent } from './assessments/spatial-position-assessment/spatial-position-assessment.component'; import { SpatialPositionAssessmentComponent } from './assessments/spatial-position-assessment/spatial-position-assessment.component';
import { DemographicsFeedbackComponent } from './assessments/demographics-feedback/demographics-feedback.component'; import { DemographicsFeedbackComponent } from './assessments/demographics-feedback/demographics-feedback.component';
import { FeedbackComponent } from './assessments/feedback-component/feedback.component';
import { CeilingPlacementDemonstratorComponent } from "./assessments/preivew/ceiling-placement-demonstrator.component";
@Component({ @Component({
selector: 'app-test-suite', selector: 'app-test-suite',
@ -16,8 +18,8 @@ import { DemographicsFeedbackComponent } from './assessments/demographics-feedba
SpatialStabilityAssessmentComponent, SpatialStabilityAssessmentComponent,
TextLegibilityAssessmentComponent, TextLegibilityAssessmentComponent,
SpatialPositionAssessmentComponent, SpatialPositionAssessmentComponent,
DemographicsFeedbackComponent, CeilingPlacementDemonstratorComponent,
DemographicsFeedbackComponent FeedbackComponent
], ],
templateUrl: './test-suite.component.html', templateUrl: './test-suite.component.html',
styleUrls: ['./test-suite.component.css'] styleUrls: ['./test-suite.component.css']

View File

@ -153,7 +153,7 @@ export class MetricsTrackerService {
} }
console.log(`Starting tracking for test: ${testName}`); console.log(`Starting tracking for test: ${testName}`);
this.trackingSubscription = interval(500).subscribe(async () => { this.trackingSubscription = interval(100).subscribe(async () => {
if (!this.activeLog) return; if (!this.activeLog) return;
const timestamp = Date.now(); const timestamp = Date.now();

View File

@ -6,9 +6,9 @@ import { Router } from '@angular/router';
providedIn: 'root' providedIn: 'root'
}) })
export class TestProgressService { export class TestProgressService {
private totalTests = 4; private totalTests = 2;
private currentTestSubject = new BehaviorSubject<number>(1); private currentTestSubject = new BehaviorSubject<number>(1);
private progressSubject = new BehaviorSubject<number>(25); // (1/totalTests) * 100 private progressSubject = new BehaviorSubject<number>(50); // (1/totalTests) * 100
currentTest$ = this.currentTestSubject.asObservable(); currentTest$ = this.currentTestSubject.asObservable();
progress$ = this.progressSubject.asObservable(); progress$ = this.progressSubject.asObservable();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long