improve tracking logic
parent
9446e3b2de
commit
95d92cc4d9
|
|
@ -59,8 +59,6 @@ export class DemographicsFeedbackComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.createForm();
|
this.createForm();
|
||||||
// Start device orientation tracking for this component
|
|
||||||
this.metricsService.startDeviceOrientationTracking();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logInteraction(event: Event) {
|
logInteraction(event: Event) {
|
||||||
|
|
|
||||||
|
|
@ -41,8 +41,7 @@ export class SpatialPositionAssessmentComponent implements AfterViewInit, OnDest
|
||||||
|
|
||||||
ngAfterViewInit() {
|
ngAfterViewInit() {
|
||||||
const mv = this.modelViewerRef.nativeElement;
|
const mv = this.modelViewerRef.nativeElement;
|
||||||
this.metricsService.startDeviceOrientationTracking();
|
this.metricsService.startTracking(mv);
|
||||||
this.metricsService.startArTracking(mv);
|
|
||||||
mv.addEventListener('ar-status', (e: any) => {
|
mv.addEventListener('ar-status', (e: any) => {
|
||||||
if (e.detail.status === 'session-started') {
|
if (e.detail.status === 'session-started') {
|
||||||
setTimeout(() => this.captureAnchor(), 500);
|
setTimeout(() => this.captureAnchor(), 500);
|
||||||
|
|
|
||||||
|
|
@ -48,8 +48,7 @@ export class SpatialStabilityAssessmentComponent implements AfterViewInit, OnDes
|
||||||
ngAfterViewInit() {
|
ngAfterViewInit() {
|
||||||
const modelViewer = this.modelViewerRef.nativeElement;
|
const modelViewer = this.modelViewerRef.nativeElement;
|
||||||
|
|
||||||
this.metricsService.startDeviceOrientationTracking();
|
this.metricsService.startTracking(modelViewer);
|
||||||
this.metricsService.startArTracking(modelViewer);
|
|
||||||
|
|
||||||
modelViewer.addEventListener('ar-status', (event: any) => {
|
modelViewer.addEventListener('ar-status', (event: any) => {
|
||||||
if (event.detail.status === 'session-started' && !this.isModelPlaced) {
|
if (event.detail.status === 'session-started' && !this.isModelPlaced) {
|
||||||
|
|
@ -143,7 +142,7 @@ export class SpatialStabilityAssessmentComponent implements AfterViewInit, OnDes
|
||||||
if (wasCompleted) {
|
if (wasCompleted) {
|
||||||
this.currentPhase = 4;
|
this.currentPhase = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(wasCompleted) {
|
if(wasCompleted) {
|
||||||
this.testComplete.emit();
|
this.testComplete.emit();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,8 +48,8 @@ export class TextLegibilityAssessmentComponent implements AfterViewInit, OnDestr
|
||||||
}
|
}
|
||||||
|
|
||||||
ngAfterViewInit() {
|
ngAfterViewInit() {
|
||||||
this.metricsService.startDeviceOrientationTracking();
|
const mv = this.modelViewerRef.nativeElement;
|
||||||
this.metricsService.startArTracking(this.modelViewerRef.nativeElement);
|
this.metricsService.startTracking(mv);
|
||||||
}
|
}
|
||||||
|
|
||||||
public logInteraction(event: Event) {
|
public logInteraction(event: Event) {
|
||||||
|
|
|
||||||
|
|
@ -3,166 +3,155 @@ import { inject, Injectable } from '@angular/core';
|
||||||
import { interval, Subscription, tap } from 'rxjs';
|
import { interval, Subscription, tap } from 'rxjs';
|
||||||
|
|
||||||
export interface InteractionEvent {
|
export interface InteractionEvent {
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
type: string;
|
type: string;
|
||||||
elementId?: string;
|
elementId?: string;
|
||||||
elementTag?: string;
|
elementTag?: string;
|
||||||
elementClasses?: string;
|
elementClasses?: string;
|
||||||
value?: any;
|
value?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DeviceOrientation {
|
export interface DeviceOrientation {
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
alpha: number | null;
|
alpha: number | null;
|
||||||
beta: number | null;
|
beta: number | null;
|
||||||
gamma: number | null;
|
gamma: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ArTrackingData {
|
export interface ArTrackingData {
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
anchor: string | null;
|
anchor: string | null;
|
||||||
cameraOrbit: {
|
cameraOrbit: {
|
||||||
theta: number;
|
theta: number;
|
||||||
phi: number;
|
phi: number;
|
||||||
radius: number;
|
radius: number;
|
||||||
} | null;
|
} | null;
|
||||||
cameraTarget: {
|
cameraTarget: {
|
||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
z: number;
|
z: number;
|
||||||
} | null;
|
} | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MetricsLog {
|
export interface MetricsLog {
|
||||||
interactions: InteractionEvent[];
|
interactions: InteractionEvent[];
|
||||||
deviceOrientations: DeviceOrientation[];
|
deviceOrientations: DeviceOrientation[];
|
||||||
arData: ArTrackingData[];
|
arData: ArTrackingData[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class MetricsTrackerService {
|
export class MetricsTrackerService {
|
||||||
private http = inject(HttpClient);
|
private http = inject(HttpClient);
|
||||||
private serverUrl = '/api/log';
|
private serverUrl = '/api/log';
|
||||||
|
|
||||||
private metricsLog: MetricsLog = {
|
|
||||||
interactions: [],
|
|
||||||
deviceOrientations: [],
|
|
||||||
arData: []
|
|
||||||
};
|
|
||||||
|
|
||||||
private deviceOrientationSubscription: Subscription | null = null;
|
private metricsLog: MetricsLog = {
|
||||||
private arTrackingSubscription: Subscription | null = null;
|
interactions: [],
|
||||||
private lastDeviceOrientation: DeviceOrientationEvent | null = null;
|
deviceOrientations: [],
|
||||||
|
arData: []
|
||||||
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 {
|
private trackingSubscription: Subscription | null = null;
|
||||||
if (typeof window !== 'undefined') {
|
private lastDeviceOrientation: DeviceOrientationEvent | null = null;
|
||||||
window.removeEventListener('deviceorientation', this.handleDeviceOrientation);
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.deviceOrientationSubscription?.unsubscribe();
|
|
||||||
this.arTrackingSubscription?.unsubscribe();
|
|
||||||
this.deviceOrientationSubscription = null;
|
|
||||||
this.arTrackingSubscription = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public resetMetrics(): void {
|
|
||||||
this.metricsLog = { interactions: [], deviceOrientations: [], arData: [] };
|
public startTracking(modelViewerElement: any): void {
|
||||||
}
|
if (this.trackingSubscription || !modelViewerElement) return;
|
||||||
}
|
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
window.addEventListener('deviceorientation', this.handleDeviceOrientation);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.trackingSubscription = interval(500).subscribe(() => {
|
||||||
|
if (!modelViewerElement.arActive) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const timestamp = Date.now();
|
||||||
|
|
||||||
|
const anchor = modelViewerElement.getAnchor ? modelViewerElement.getAnchor() : 'getAnchor not available';
|
||||||
|
const orbit = modelViewerElement.getCameraOrbit ? modelViewerElement.getCameraOrbit() : null;
|
||||||
|
const target = modelViewerElement.getCameraTarget ? modelViewerElement.getCameraTarget() : null;
|
||||||
|
|
||||||
|
const arData: ArTrackingData = {
|
||||||
|
timestamp: timestamp,
|
||||||
|
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);
|
||||||
|
|
||||||
|
let orientation: DeviceOrientation | null = null;
|
||||||
|
if (this.lastDeviceOrientation) {
|
||||||
|
orientation = {
|
||||||
|
timestamp: timestamp,
|
||||||
|
alpha: this.lastDeviceOrientation.alpha,
|
||||||
|
beta: this.lastDeviceOrientation.beta,
|
||||||
|
gamma: this.lastDeviceOrientation.gamma
|
||||||
|
};
|
||||||
|
this.metricsLog.deviceOrientations.push(orientation);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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.trackingSubscription?.unsubscribe();
|
||||||
|
this.trackingSubscription = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public resetMetrics(): void {
|
||||||
|
this.metricsLog = { interactions: [], deviceOrientations: [], arData: [] };
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue