initial setup
commit
ff12bb40ad
|
|
@ -0,0 +1,16 @@
|
|||
# Editor configuration, see https://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.ts]
|
||||
quote_type = single
|
||||
|
||||
[*.md]
|
||||
max_line_length = off
|
||||
trim_trailing_whitespace = false
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files.
|
||||
|
||||
# Compiled output
|
||||
/dist
|
||||
/tmp
|
||||
/out-tsc
|
||||
/bazel-out
|
||||
|
||||
# Node
|
||||
/node_modules
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
|
||||
# IDEs and editors
|
||||
.idea/
|
||||
.project
|
||||
.classpath
|
||||
.c9/
|
||||
*.launch
|
||||
.settings/
|
||||
*.sublime-workspace
|
||||
|
||||
# Visual Studio Code
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
.history/*
|
||||
|
||||
# Miscellaneous
|
||||
/.angular/cache
|
||||
.sass-cache/
|
||||
/connect.lock
|
||||
/coverage
|
||||
/libpeerconnection.log
|
||||
testem.log
|
||||
/typings
|
||||
|
||||
# System files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
|
||||
"recommendations": ["angular.ng-template"]
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "ng serve",
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"preLaunchTask": "npm: start",
|
||||
"url": "http://localhost:4200/"
|
||||
},
|
||||
{
|
||||
"name": "ng test",
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"preLaunchTask": "npm: test",
|
||||
"url": "http://localhost:9876/debug.html"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "start",
|
||||
"isBackground": true,
|
||||
"problemMatcher": {
|
||||
"owner": "typescript",
|
||||
"pattern": "$tsc",
|
||||
"background": {
|
||||
"activeOnStart": true,
|
||||
"beginsPattern": {
|
||||
"regexp": "(.*?)"
|
||||
},
|
||||
"endsPattern": {
|
||||
"regexp": "bundle generation complete"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "test",
|
||||
"isBackground": true,
|
||||
"problemMatcher": {
|
||||
"owner": "typescript",
|
||||
"pattern": "$tsc",
|
||||
"background": {
|
||||
"activeOnStart": true,
|
||||
"beginsPattern": {
|
||||
"regexp": "(.*?)"
|
||||
},
|
||||
"endsPattern": {
|
||||
"regexp": "bundle generation complete"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
# CeilingArAssessment
|
||||
|
||||
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 18.0.6.
|
||||
|
||||
## Development server
|
||||
|
||||
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.
|
||||
|
||||
## Code scaffolding
|
||||
|
||||
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
|
||||
|
||||
## Build
|
||||
|
||||
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.
|
||||
|
||||
## Running unit tests
|
||||
|
||||
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
|
||||
|
||||
## Running end-to-end tests
|
||||
|
||||
Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.
|
||||
|
||||
## Further help
|
||||
|
||||
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page.
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
{
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"version": 1,
|
||||
"newProjectRoot": "projects",
|
||||
"projects": {
|
||||
"ceiling-ar-assessment": {
|
||||
"projectType": "application",
|
||||
"schematics": {},
|
||||
"root": "",
|
||||
"sourceRoot": "src",
|
||||
"prefix": "app",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:application",
|
||||
"options": {
|
||||
"outputPath": "dist/ceiling-ar-assessment",
|
||||
"index": "src/index.html",
|
||||
"browser": "src/main.ts",
|
||||
"polyfills": [
|
||||
"zone.js"
|
||||
],
|
||||
"tsConfig": "tsconfig.app.json",
|
||||
"assets": [
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "public"
|
||||
}
|
||||
],
|
||||
"styles": [
|
||||
"src/styles.css"
|
||||
],
|
||||
"scripts": [
|
||||
]
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"budgets": [
|
||||
{
|
||||
"type": "initial",
|
||||
"maximumWarning": "500kB",
|
||||
"maximumError": "1MB"
|
||||
},
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
"maximumWarning": "2kB",
|
||||
"maximumError": "4kB"
|
||||
}
|
||||
],
|
||||
"outputHashing": "all"
|
||||
},
|
||||
"development": {
|
||||
"optimization": false,
|
||||
"extractLicenses": false,
|
||||
"sourceMap": true
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "production"
|
||||
},
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"configurations": {
|
||||
"production": {
|
||||
"buildTarget": "ceiling-ar-assessment:build:production"
|
||||
},
|
||||
"development": {
|
||||
"buildTarget": "ceiling-ar-assessment:build:development"
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "development"
|
||||
},
|
||||
"extract-i18n": {
|
||||
"builder": "@angular-devkit/build-angular:extract-i18n"
|
||||
},
|
||||
"test": {
|
||||
"builder": "@angular-devkit/build-angular:karma",
|
||||
"options": {
|
||||
"polyfills": [
|
||||
"zone.js",
|
||||
"zone.js/testing"
|
||||
],
|
||||
"tsConfig": "tsconfig.spec.json",
|
||||
"assets": [
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "public"
|
||||
}
|
||||
],
|
||||
"styles": [
|
||||
"src/styles.css"
|
||||
],
|
||||
"scripts": [
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,41 @@
|
|||
{
|
||||
"name": "ceiling-ar-assessment",
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "ng serve",
|
||||
"build": "ng build",
|
||||
"watch": "ng build --watch --configuration development",
|
||||
"test": "ng test"
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "^18.0.0",
|
||||
"@angular/common": "^18.0.0",
|
||||
"@angular/compiler": "^18.0.0",
|
||||
"@angular/core": "^18.0.0",
|
||||
"@angular/forms": "^18.0.0",
|
||||
"@angular/platform-browser": "^18.0.0",
|
||||
"@angular/platform-browser-dynamic": "^18.0.0",
|
||||
"@angular/router": "^18.0.0",
|
||||
"rxjs": "~7.8.0",
|
||||
"tslib": "^2.3.0",
|
||||
"zone.js": "~0.14.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "^18.0.6",
|
||||
"@angular/cli": "^18.0.6",
|
||||
"@angular/compiler-cli": "^18.0.0",
|
||||
"@types/jasmine": "~5.1.0",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"jasmine-core": "~5.1.0",
|
||||
"karma": "~6.4.0",
|
||||
"karma-chrome-launcher": "~3.2.0",
|
||||
"karma-coverage": "~2.2.0",
|
||||
"karma-jasmine": "~5.1.0",
|
||||
"karma-jasmine-html-reporter": "~2.1.0",
|
||||
"postcss": "^8.5.6",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"typescript": "~5.4.2"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
|
|
@ -0,0 +1,8 @@
|
|||
<div class="container mx-auto p-8">
|
||||
<h1 class="text-4xl font-bold text-blue-600 mb-4">
|
||||
Ceiling-AR Assessment Suite
|
||||
</h1>
|
||||
<app-text-legibility-assessment>
|
||||
|
||||
</app-text-legibility-assessment>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
import { TestBed } from '@angular/core/testing';
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
describe('AppComponent', () => {
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [AppComponent],
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
it('should create the app', () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.componentInstance;
|
||||
expect(app).toBeTruthy();
|
||||
});
|
||||
|
||||
it(`should have the 'ceiling-ar-assessment' title`, () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.componentInstance;
|
||||
expect(app.title).toEqual('ceiling-ar-assessment');
|
||||
});
|
||||
|
||||
it('should render title', () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
fixture.detectChanges();
|
||||
const compiled = fixture.nativeElement as HTMLElement;
|
||||
expect(compiled.querySelector('h1')?.textContent).toContain('Hello, ceiling-ar-assessment');
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
import { Component } from '@angular/core';
|
||||
import { RouterOutlet } from '@angular/router';
|
||||
import { TextLegibilityAssessmentComponent } from "./components/test-suite/assessments/text-legibility-assessment/text-legibility-assessment.component";
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
standalone: true,
|
||||
imports: [RouterOutlet, TextLegibilityAssessmentComponent],
|
||||
templateUrl: './app.component.html',
|
||||
styleUrl: './app.component.css'
|
||||
})
|
||||
export class AppComponent {
|
||||
title = 'ceiling-ar-assessment';
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
|
||||
import { provideRouter } from '@angular/router';
|
||||
|
||||
import { routes } from './app.routes';
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes)]
|
||||
};
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
import { Routes } from '@angular/router';
|
||||
|
||||
export const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
redirectTo: '/consent',
|
||||
pathMatch: 'full'
|
||||
},
|
||||
{
|
||||
path: 'consent',
|
||||
loadComponent: () => import('./components/consent/consent.component').then(c => c.ConsentComponent)
|
||||
},
|
||||
{
|
||||
path: 'test-suite',
|
||||
loadComponent: () => import('./components/test-suite/test-suite.component').then(c => c.TestSuiteComponent)
|
||||
},
|
||||
{
|
||||
path: 'completion',
|
||||
loadComponent: () => import('./components/completion/completion.component').then(c => c.CompletionComponent)
|
||||
},
|
||||
{
|
||||
path: '**',
|
||||
redirectTo: '/consent'
|
||||
}
|
||||
];
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { ConsentComponent } from './components/consent/consent.component';
|
||||
import { TestSuiteComponent } from './components/test-suite/test-suite.component';
|
||||
import { CompletionComponent } from './components/completion/completion.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: '', redirectTo: '/consent', pathMatch: 'full' },
|
||||
{ path: 'consent', component: ConsentComponent },
|
||||
{ path: 'test-suite', component: TestSuiteComponent },
|
||||
{ path: 'completion', component: CompletionComponent },
|
||||
{ path: '**', redirectTo: '/consent' }
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forRoot(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class AppRoutingModule { }
|
||||
|
|
@ -0,0 +1,153 @@
|
|||
import { Component, OnInit } 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>
|
||||
|
||||
<!-- 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>
|
||||
|
||||
<!-- 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']);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
<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">
|
||||
Informed Consent - Ceiling-AR Design Parameter Assessment
|
||||
</h2>
|
||||
|
||||
<div class="space-y-4 text-gray-700">
|
||||
<p>
|
||||
<strong>Purpose:</strong> This empirical study investigates optimal design parameters for
|
||||
ceiling-based augmented reality applications within a Design Science Research framework
|
||||
for academic research purposes.
|
||||
</p>
|
||||
|
||||
<div>
|
||||
<p class="font-semibold mb-2">Data Collection Protocol:</p>
|
||||
<ul class="list-disc list-inside space-y-1 ml-4">
|
||||
<li>Device specifications (display dimensions, browser version, operating system)</li>
|
||||
<li>WebXR interaction metrics and motion tracking data</li>
|
||||
<li>Parameter adjustment patterns and timing</li>
|
||||
<li>Device orientation and viewing angle parameters</li>
|
||||
<li>First input detection and natural gesture analysis</li>
|
||||
<li>Optional demographic information (age)</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
<strong>Data Protection:</strong> All collected data will be anonymized and used
|
||||
exclusively for scientific analysis. No personally identifiable information is recorded.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<strong>Voluntary Participation:</strong> Participation is entirely voluntary.
|
||||
You may withdraw at any time without consequence.
|
||||
</p>
|
||||
</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">
|
||||
I consent to data collection and agree to participate in this research study.
|
||||
</span>
|
||||
</label>
|
||||
</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"
|
||||
>
|
||||
Initialize Assessment Suite
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
import { Component } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { ArLoggerService } from '../../services/ar-logger.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-consent',
|
||||
standalone: true,
|
||||
imports: [CommonModule, FormsModule],
|
||||
templateUrl: './consent.component.html',
|
||||
styleUrls: ['./consent.component.css']
|
||||
})
|
||||
export class ConsentComponent {
|
||||
consentGiven = false;
|
||||
|
||||
constructor(
|
||||
private router: Router,
|
||||
private logger: ArLoggerService
|
||||
) {}
|
||||
|
||||
onConsentChange(event: Event): void {
|
||||
const target = event.target as HTMLInputElement;
|
||||
this.consentGiven = target.checked;
|
||||
}
|
||||
|
||||
startTestSuite(): void {
|
||||
if (this.consentGiven) {
|
||||
this.logger.initializeSession();
|
||||
this.router.navigate(['/test-suite']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
<p>progress-bar works!</p>
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ProgressBarComponent } from './progress-bar.component';
|
||||
|
||||
describe('ProgressBarComponent', () => {
|
||||
let component: ProgressBarComponent;
|
||||
let fixture: ComponentFixture<ProgressBarComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [ProgressBarComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(ProgressBarComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-progress-bar',
|
||||
standalone: true,
|
||||
imports: [],
|
||||
templateUrl: './progress-bar.component.html',
|
||||
styleUrl: './progress-bar.component.css'
|
||||
})
|
||||
export class ProgressBarComponent {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
<p>status-display works!</p>
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { StatusDisplayComponent } from './status-display.component';
|
||||
|
||||
describe('StatusDisplayComponent', () => {
|
||||
let component: StatusDisplayComponent;
|
||||
let fixture: ComponentFixture<StatusDisplayComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [StatusDisplayComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(StatusDisplayComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-status-display',
|
||||
standalone: true,
|
||||
imports: [],
|
||||
templateUrl: './status-display.component.html',
|
||||
styleUrl: './status-display.component.css'
|
||||
})
|
||||
export class StatusDisplayComponent {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
<p>vr-slider works!</p>
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { VrSliderComponent } from './vr-slider.component';
|
||||
|
||||
describe('VrSliderComponent', () => {
|
||||
let component: VrSliderComponent;
|
||||
let fixture: ComponentFixture<VrSliderComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [VrSliderComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(VrSliderComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
import { Component, Input, Output, EventEmitter } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
|
||||
@Component({
|
||||
selector: 'app-vr-slider',
|
||||
standalone: true,
|
||||
imports: [CommonModule, FormsModule],
|
||||
template: `
|
||||
<div class="vr-slider absolute bottom-32 left-1/2 transform -translate-x-1/2
|
||||
bg-black bg-opacity-80 text-white p-5 rounded-lg backdrop-blur-md"
|
||||
[style.width.px]="width">
|
||||
|
||||
<div class="mb-3 text-center">{{ label }}</div>
|
||||
|
||||
<input
|
||||
type="range"
|
||||
[min]="min"
|
||||
[max]="max"
|
||||
[step]="step"
|
||||
[value]="value"
|
||||
(input)="onValueChange($event)"
|
||||
(beforexrselect)="preventXRSelect($event)"
|
||||
class="w-full h-2 bg-white bg-opacity-30 rounded-lg appearance-none cursor-pointer">
|
||||
|
||||
<div class="text-center mt-2">{{ displayValue }}</div>
|
||||
|
||||
<div *ngIf="showButtons" class="flex justify-center gap-2 mt-4">
|
||||
<button
|
||||
*ngFor="let button of buttons"
|
||||
(click)="onButtonClick(button.action)"
|
||||
class="bg-blue-600 bg-opacity-90 text-white px-4 py-2 rounded-full
|
||||
hover:bg-blue-700 transition-colors text-sm">
|
||||
{{ button.label }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
styles: [`
|
||||
input[type="range"]::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background: #4285f4;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
input[type="range"]::-moz-range-thumb {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background: #4285f4;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
}
|
||||
|
||||
input[type="range"]:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
input[type="range"]:focus::-webkit-slider-thumb {
|
||||
box-shadow: 0 0 0 3px rgba(66, 133, 244, 0.3);
|
||||
}
|
||||
`]
|
||||
})
|
||||
export class VrSliderComponent {
|
||||
@Input() label = '';
|
||||
@Input() min = 0;
|
||||
@Input() max = 100;
|
||||
@Input() step = 1;
|
||||
@Input() value = 50;
|
||||
@Input() unit = '';
|
||||
@Input() width = 300;
|
||||
@Input() showButtons = false;
|
||||
@Input() buttons: Array<{label: string, action: string}> = [];
|
||||
|
||||
@Output() valueChange = new EventEmitter<number>();
|
||||
@Output() buttonClick = new EventEmitter<string>();
|
||||
|
||||
get displayValue(): string {
|
||||
return `${this.value}${this.unit}`;
|
||||
}
|
||||
|
||||
onValueChange(event: Event): void {
|
||||
const target = event.target as HTMLInputElement;
|
||||
const newValue = parseFloat(target.value);
|
||||
this.value = newValue;
|
||||
this.valueChange.emit(newValue);
|
||||
}
|
||||
|
||||
onButtonClick(action: string): void {
|
||||
this.buttonClick.emit(action);
|
||||
}
|
||||
|
||||
preventXRSelect(event: Event): void {
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
<p>demographics-assessment works!</p>
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { DemographicsAssessmentComponent } from './demographics-assessment.component';
|
||||
|
||||
describe('DemographicsAssessmentComponent', () => {
|
||||
let component: DemographicsAssessmentComponent;
|
||||
let fixture: ComponentFixture<DemographicsAssessmentComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [DemographicsAssessmentComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(DemographicsAssessmentComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-demographics-assessment',
|
||||
standalone: true,
|
||||
imports: [],
|
||||
templateUrl: './demographics-assessment.component.html',
|
||||
styleUrl: './demographics-assessment.component.css'
|
||||
})
|
||||
export class DemographicsAssessmentComponent {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
<p>ergonomic-assessment works!</p>
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ErgonomicAssessmentComponent } from './ergonomic-assessment.component';
|
||||
|
||||
describe('ErgonomicAssessmentComponent', () => {
|
||||
let component: ErgonomicAssessmentComponent;
|
||||
let fixture: ComponentFixture<ErgonomicAssessmentComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [ErgonomicAssessmentComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(ErgonomicAssessmentComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-ergonomic-assessment',
|
||||
standalone: true,
|
||||
imports: [],
|
||||
templateUrl: './ergonomic-assessment.component.html',
|
||||
styleUrl: './ergonomic-assessment.component.css'
|
||||
})
|
||||
export class ErgonomicAssessmentComponent {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,149 @@
|
|||
import { Component, Output, EventEmitter, OnInit, OnDestroy } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { ArLoggerService } from '../../../../services/ar-logger.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-first-input-assessment',
|
||||
standalone: true,
|
||||
imports: [CommonModule],
|
||||
templateUrl: './first-input-assessment.component.html',
|
||||
styleUrls: ['./first-input-assessment.component.css']
|
||||
})
|
||||
export class FirstInputAssessmentComponent implements OnInit, OnDestroy {
|
||||
@Output() testComplete = new EventEmitter<void>();
|
||||
@Output() redoTest = new EventEmitter<void>();
|
||||
|
||||
showManualComplete = false;
|
||||
showRedoButton = false;
|
||||
firstInputDetected = false;
|
||||
sessionStartTime: number = 0;
|
||||
xrSession: any = null;
|
||||
|
||||
constructor(private logger: ArLoggerService) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.logger.updateStatus('Assessment 1 initialized - Ready to begin first input detection');
|
||||
|
||||
setTimeout(() => {
|
||||
this.showManualComplete = true;
|
||||
this.showRedoButton = true;
|
||||
}, 30000);
|
||||
|
||||
this.initializeWebXRTracking();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.exitWebXRSession();
|
||||
}
|
||||
|
||||
private initializeWebXRTracking(): void {
|
||||
const modelViewer = document.getElementById('first-input-model') as any;
|
||||
|
||||
if (modelViewer) {
|
||||
modelViewer.addEventListener('ar-status', (event: any) => {
|
||||
if (event.detail.status === 'session-started') {
|
||||
this.xrSession = modelViewer.model?.webXRCamera?.session;
|
||||
this.sessionStartTime = performance.now();
|
||||
this.firstInputDetected = false;
|
||||
|
||||
console.log('WebXR session started for first input test');
|
||||
this.logger.updateStatus('WebXR active - Waiting for first input');
|
||||
|
||||
this.setupFirstInputTracking();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private setupFirstInputTracking(): void {
|
||||
if (!this.xrSession) return;
|
||||
|
||||
const onXRFrame = (time: number, frame: any) => {
|
||||
if (!this.xrSession || this.firstInputDetected) return;
|
||||
|
||||
const inputSources = this.xrSession.inputSources;
|
||||
|
||||
for (let inputSource of inputSources) {
|
||||
if (inputSource.gamepad) {
|
||||
inputSource.gamepad.buttons.forEach((button: any, index: number) => {
|
||||
if (button.pressed && !this.firstInputDetected) {
|
||||
this.handleFirstInput('button', { buttonIndex: index, inputSource: inputSource.handedness });
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
inputSource.gamepad.axes.forEach((axis: any, index: number) => {
|
||||
if (Math.abs(axis) > 0.1 && !this.firstInputDetected) {
|
||||
this.handleFirstInput('axis', { axisIndex: index, value: axis, inputSource: inputSource.handedness });
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (this.xrSession && !this.firstInputDetected) {
|
||||
this.xrSession.requestAnimationFrame(onXRFrame);
|
||||
}
|
||||
};
|
||||
|
||||
this.xrSession.requestAnimationFrame(onXRFrame);
|
||||
}
|
||||
|
||||
private handleFirstInput(inputType: string, inputData: any): void {
|
||||
if (this.firstInputDetected) return;
|
||||
|
||||
this.firstInputDetected = true;
|
||||
const endTime = performance.now();
|
||||
const sessionDuration = endTime - this.sessionStartTime;
|
||||
|
||||
const testData = {
|
||||
duration: sessionDuration,
|
||||
firstInputType: inputType,
|
||||
firstInputData: inputData,
|
||||
sessionDuration: sessionDuration,
|
||||
completedSuccessfully: true
|
||||
};
|
||||
|
||||
this.logger.markTestComplete('firstInputTest');
|
||||
|
||||
console.log('First input detected:', inputType, inputData);
|
||||
this.logger.updateStatus(`First input detected: ${inputType} - Auto completing test`);
|
||||
|
||||
setTimeout(() => {
|
||||
this.exitWebXRSession();
|
||||
this.completeTest();
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
manualCompleteFirstTest(): void {
|
||||
if (!this.firstInputDetected) {
|
||||
this.logger.markTestComplete('firstInputTest');
|
||||
}
|
||||
this.completeTest();
|
||||
}
|
||||
|
||||
private completeTest(): void {
|
||||
this.showManualComplete = true;
|
||||
this.showRedoButton = true;
|
||||
this.testComplete.emit();
|
||||
}
|
||||
|
||||
onRedoTest(): void {
|
||||
this.firstInputDetected = false;
|
||||
this.showManualComplete = false;
|
||||
this.showRedoButton = false;
|
||||
this.exitWebXRSession();
|
||||
this.redoTest.emit();
|
||||
}
|
||||
|
||||
private exitWebXRSession(): void {
|
||||
if (this.xrSession) {
|
||||
this.xrSession.end().then(() => {
|
||||
console.log('WebXR session ended');
|
||||
this.xrSession = null;
|
||||
}).catch((err: any) => {
|
||||
console.error('Error ending WebXR session:', err);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
<p>rotation-speed-assessment works!</p>
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { RotationSpeedAssessmentComponent } from './rotation-speed-assessment.component';
|
||||
|
||||
describe('RotationSpeedAssessmentComponent', () => {
|
||||
let component: RotationSpeedAssessmentComponent;
|
||||
let fixture: ComponentFixture<RotationSpeedAssessmentComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [RotationSpeedAssessmentComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(RotationSpeedAssessmentComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
import { Component, Output, EventEmitter, OnInit, OnDestroy } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { ArLoggerService } from '../../../../services/ar-logger.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-rotation-speed-assessment',
|
||||
standalone: true,
|
||||
imports: [CommonModule, FormsModule],
|
||||
template: ``})
|
||||
export class RotationSpeedAssessmentComponent implements OnInit, OnDestroy {
|
||||
@Output() testComplete = new EventEmitter<void>();
|
||||
@Output() redoTest = new EventEmitter<void>();
|
||||
|
||||
rotationSpeed = 50;
|
||||
showConfirmButton = false;
|
||||
showRedoButton = false;
|
||||
xrSession: any = null;
|
||||
|
||||
constructor(private logger: ArLoggerService) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.logger.updateStatus('Assessment 2 initialized - Ready to test rotation speed');
|
||||
this.initializeWebXRTracking();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.exitWebXRSession();
|
||||
}
|
||||
|
||||
private initializeWebXRTracking(): void {
|
||||
const modelViewer = document.getElementById('speed-model') as any;
|
||||
|
||||
if (modelViewer) {
|
||||
modelViewer.addEventListener('ar-status', (event: any) => {
|
||||
if (event.detail.status === 'session-started') {
|
||||
this.xrSession = modelViewer.model?.webXRCamera?.session;
|
||||
this.logger.updateStatus('WebXR active - Adjust rotation speed with slider');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onSpeedChange(event: Event): void {
|
||||
const target = event.target as HTMLInputElement;
|
||||
this.rotationSpeed = parseInt(target.value);
|
||||
|
||||
this.logger.logParameterAdjustment('speed', 'rotationSpeed', this.rotationSpeed);
|
||||
|
||||
this.updateSpeedModel(this.rotationSpeed);
|
||||
}
|
||||
|
||||
private updateSpeedModel(speed: number): void {
|
||||
const modelViewer = document.getElementById('speed-model') as any;
|
||||
if (modelViewer) {
|
||||
modelViewer.style.setProperty('--auto-rotate-delay', `${101 - speed}0ms`);
|
||||
}
|
||||
}
|
||||
|
||||
confirmSpeedSetting(): void {
|
||||
this.logger.markTestComplete('speedTest');
|
||||
this.showConfirmButton = true;
|
||||
this.showRedoButton = true;
|
||||
this.logger.updateStatus(`Speed confirmed at ${this.rotationSpeed}%`);
|
||||
}
|
||||
|
||||
protected completeTest(): void {
|
||||
this.exitWebXRSession();
|
||||
this.testComplete.emit();
|
||||
}
|
||||
|
||||
onRedoTest(): void {
|
||||
this.rotationSpeed = 50;
|
||||
this.showConfirmButton = false;
|
||||
this.showRedoButton = false;
|
||||
this.exitWebXRSession();
|
||||
this.redoTest.emit();
|
||||
}
|
||||
|
||||
private exitWebXRSession(): void {
|
||||
if (this.xrSession) {
|
||||
this.xrSession.end().then(() => {
|
||||
console.log('WebXR session ended');
|
||||
this.xrSession = null;
|
||||
}).catch((err: any) => {
|
||||
console.error('Error ending WebXR session:', err);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
<p>scale-reference-assessment works!</p>
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ScaleReferenceAssessmentComponent } from './scale-reference-assessment.component';
|
||||
|
||||
describe('ScaleReferenceAssessmentComponent', () => {
|
||||
let component: ScaleReferenceAssessmentComponent;
|
||||
let fixture: ComponentFixture<ScaleReferenceAssessmentComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [ScaleReferenceAssessmentComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(ScaleReferenceAssessmentComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-scale-reference-assessment',
|
||||
standalone: true,
|
||||
imports: [],
|
||||
templateUrl: './scale-reference-assessment.component.html',
|
||||
styleUrl: './scale-reference-assessment.component.css'
|
||||
})
|
||||
export class ScaleReferenceAssessmentComponent {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
<div class="relative w-full h-screen">
|
||||
<model-viewer
|
||||
#modelViewer
|
||||
src="https://modelviewer.dev/shared-assets/models/Astronaut.glb"
|
||||
ar
|
||||
ar-modes="webxr"
|
||||
camera-controls
|
||||
interaction-prompt="none"
|
||||
class="w-full h-full"
|
||||
>
|
||||
<button slot="ar-button" class="bg-blue-500 text-white font-bold py-2 px-4 rounded-lg absolute bottom-4 right-4">
|
||||
Enter WebXR
|
||||
</button>
|
||||
|
||||
<!-- UI container for sliders, visible only in WebXR mode -->
|
||||
<div
|
||||
*ngIf="isInWebXR"
|
||||
class="absolute bottom-24 left-1/2 -translate-x-1/2 flex flex-col items-center space-y-6 bg-black bg-opacity-60 p-5 rounded-xl"
|
||||
>
|
||||
<!-- Scale Slider -->
|
||||
<div class="flex flex-col items-center text-white w-64">
|
||||
<label for="scale-slider" class="text-sm font-medium mb-2">Scale</label>
|
||||
<input
|
||||
id="scale-slider"
|
||||
type="range"
|
||||
min="0.5"
|
||||
max="2.5"
|
||||
step="0.01"
|
||||
[(ngModel)]="scale"
|
||||
(input)="updateModelTransform()"
|
||||
class="w-full h-2 bg-gray-700 rounded-lg appearance-none cursor-pointer"
|
||||
/>
|
||||
<span class="text-xs mt-1">{{ scale.toFixed(2) }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Vertical Position Slider -->
|
||||
<div class="flex flex-col items-center text-white w-64">
|
||||
<label for="offset-slider" class="text-sm font-medium mb-2">Vertical Position</label>
|
||||
<input
|
||||
id="offset-slider"
|
||||
type="range"
|
||||
min="-1"
|
||||
max="1"
|
||||
step="0.01"
|
||||
[(ngModel)]="verticalOffset"
|
||||
(input)="updateModelTransform()"
|
||||
class="w-full h-2 bg-gray-700 rounded-lg appearance-none cursor-pointer"
|
||||
/>
|
||||
<span class="text-xs mt-1">{{ verticalOffset.toFixed(2) }}m</span>
|
||||
</div>
|
||||
</div>
|
||||
</model-viewer>
|
||||
</div>
|
||||
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { SpatialPositionAssessmentComponent } from './spatial-position-assessment.component';
|
||||
|
||||
describe('SpatialPositionAssessmentComponent', () => {
|
||||
let component: SpatialPositionAssessmentComponent;
|
||||
let fixture: ComponentFixture<SpatialPositionAssessmentComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [SpatialPositionAssessmentComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(SpatialPositionAssessmentComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
import { Component, AfterViewInit, ElementRef, ViewChild, ChangeDetectorRef, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
|
||||
@Component({
|
||||
selector: 'app-spatial-position-assessment',
|
||||
standalone: true,
|
||||
imports: [CommonModule, FormsModule],
|
||||
templateUrl: './spatial-position-assessment.component.html',
|
||||
styleUrl: './spatial-position-assessment.component.css',
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
})
|
||||
export class SpatialPositionAssessmentComponent implements AfterViewInit {
|
||||
@ViewChild('modelViewer') modelViewerRef!: ElementRef<any>;
|
||||
|
||||
isInWebXR = false;
|
||||
scale = 1;
|
||||
verticalOffset = 0;
|
||||
|
||||
constructor(private cdr: ChangeDetectorRef) {}
|
||||
|
||||
ngAfterViewInit() {
|
||||
const modelViewerElement = this.modelViewerRef.nativeElement;
|
||||
|
||||
modelViewerElement.addEventListener('enter-vr', () => {
|
||||
this.isInWebXR = true;
|
||||
modelViewerElement.cameraControls = false;
|
||||
this.cdr.detectChanges();
|
||||
});
|
||||
|
||||
modelViewerElement.addEventListener('exit-vr', () => {
|
||||
this.isInWebXR = false;
|
||||
modelViewerElement.cameraControls = true;
|
||||
this.resetModelTransform();
|
||||
this.cdr.detectChanges();
|
||||
});
|
||||
}
|
||||
|
||||
updateModelTransform() {
|
||||
const model = this.modelViewerRef.nativeElement.model;
|
||||
if (model) {
|
||||
model.scale.set(this.scale, this.scale, this.scale);
|
||||
model.position.y = this.verticalOffset;
|
||||
}
|
||||
}
|
||||
|
||||
resetModelTransform() {
|
||||
this.scale = 1;
|
||||
this.verticalOffset = 0;
|
||||
this.updateModelTransform();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
|
||||
.annotation {
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
<div class="w-full h-screen relative">
|
||||
<div class="absolute top-0 left-0 p-6 z-10 bg-white bg-opacity-80 rounded-br-lg">
|
||||
<h3 class="text-lg font-bold">Assessment: Text Legibility Optimization</h3>
|
||||
<p class="text-sm mt-2"><strong>Objective:</strong> Determine optimal text scaling for ceiling-AR applications.</p>
|
||||
<p class="text-sm"><strong>Procedure:</strong> Adjust text size using the WebXR slider until it is clearly legible.</p>
|
||||
</div>
|
||||
|
||||
<model-viewer
|
||||
#modelViewer
|
||||
id="text-model"
|
||||
src="https://modelviewer.dev/shared-assets/models/Astronaut.glb"
|
||||
ar
|
||||
ar-modes="webxr"
|
||||
ar-placement="ceiling"
|
||||
camera-orbit="0deg 75deg 2m"
|
||||
reveal="manual"
|
||||
class="w-full h-full"
|
||||
>
|
||||
<button slot="ar-button" class="bg-blue-500 text-white font-bold py-2 px-4 rounded-lg absolute bottom-4 right-4 z-10">
|
||||
Enter WebXR - Text Size Test
|
||||
</button>
|
||||
|
||||
<!-- Hotspot with annotation text -->
|
||||
<button class="hotspot" slot="hotspot-text" data-position="0.2 0.1 0.1" data-normal="0 1 0">
|
||||
<div
|
||||
class="annotation"
|
||||
[ngStyle]="{'font-size.px': textSize}"
|
||||
style="background: rgba(255,255,255,0.9); padding: 10px; border-radius: 5px; color: black; width: 250px;"
|
||||
>
|
||||
This sample text represents typical information display requirements for ceiling-AR applications.
|
||||
Text must remain legible despite challenging viewing angles inherent to overhead displays.
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<!-- UI container for the text size slider, visible only in WebXR -->
|
||||
<div
|
||||
*ngIf="isInWebXR"
|
||||
class="absolute bottom-24 left-1/2 -translate-x-1/2 flex flex-col items-center space-y-4 bg-black bg-opacity-60 p-5 rounded-xl"
|
||||
>
|
||||
<div class="flex flex-col items-center text-white w-64">
|
||||
<label for="text-size-slider" class="text-sm font-medium mb-2">Text Size: {{ textSize }}px</label>
|
||||
<input
|
||||
id="text-size-slider"
|
||||
type="range"
|
||||
min="10"
|
||||
max="32"
|
||||
step="1"
|
||||
[(ngModel)]="textSize"
|
||||
(input)="updateTextSize()"
|
||||
class="w-full h-2 bg-gray-700 rounded-lg appearance-none cursor-pointer"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</model-viewer>
|
||||
</div>
|
||||
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { TextLegibilityAssessmentComponent } from './text-legibility-assessment.component';
|
||||
|
||||
describe('TextLegibilityAssessmentComponent', () => {
|
||||
let component: TextLegibilityAssessmentComponent;
|
||||
let fixture: ComponentFixture<TextLegibilityAssessmentComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [TextLegibilityAssessmentComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(TextLegibilityAssessmentComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
import { Component, AfterViewInit, ViewChild, ElementRef, ChangeDetectorRef, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
|
||||
@Component({
|
||||
selector: 'app-text-legibility-assessment',
|
||||
standalone: true,
|
||||
imports: [CommonModule, FormsModule],
|
||||
templateUrl: './text-legibility-assessment.component.html',
|
||||
styleUrl: './text-legibility-assessment.component.css',
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
})
|
||||
export class TextLegibilityAssessmentComponent implements AfterViewInit {
|
||||
@ViewChild('modelViewer') modelViewerRef!: ElementRef<any>;
|
||||
|
||||
isInWebXR = false;
|
||||
textSize = 16;
|
||||
|
||||
constructor(private cdr: ChangeDetectorRef) {}
|
||||
|
||||
ngAfterViewInit() {
|
||||
const modelViewerElement = this.modelViewerRef.nativeElement;
|
||||
|
||||
modelViewerElement.addEventListener('enter-vr', () => {
|
||||
this.isInWebXR = true;
|
||||
this.cdr.detectChanges();
|
||||
});
|
||||
|
||||
modelViewerElement.addEventListener('exit-vr', () => {
|
||||
this.isInWebXR = false;
|
||||
this.resetTextSize();
|
||||
this.cdr.detectChanges();
|
||||
});
|
||||
}
|
||||
|
||||
updateTextSize() {
|
||||
}
|
||||
|
||||
resetTextSize() {
|
||||
this.textSize = 16;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
<div class="max-w-4xl mx-auto p-5 bg-white rounded-lg shadow-lg mt-8">
|
||||
<h1 class="text-3xl font-bold text-gray-800 mb-6">Ceiling-AR Design Parameter Assessment Suite</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">
|
||||
Assessment {{ currentTest }} of {{ totalTests }}
|
||||
</span>
|
||||
<span class="text-sm font-medium text-gray-700">
|
||||
{{ progress | number:'1.1-1' }}%
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Progress Bar -->
|
||||
<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 transition-all duration-300 ease-out"
|
||||
[style.width.%]="progress">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Status Display -->
|
||||
<div class="bg-gray-50 border border-gray-200 rounded-lg p-4 mb-6 font-mono text-sm">
|
||||
<div class="text-gray-600">
|
||||
Status: {{ status }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Assessment Components Container -->
|
||||
<div class="assessment-container">
|
||||
|
||||
<!-- Assessment 1: First Input Detection -->
|
||||
<div *ngIf="currentTest === 1">
|
||||
<app-first-input-assessment
|
||||
(testComplete)="onTestComplete()"
|
||||
(redoTest)="redoTest(1)">
|
||||
</app-first-input-assessment>
|
||||
</div>
|
||||
|
||||
<!-- Assessment 2: Rotation Speed Optimization -->
|
||||
<div *ngIf="currentTest === 2">
|
||||
<app-rotation-speed-assessment
|
||||
(testComplete)="onTestComplete()"
|
||||
(redoTest)="redoTest(2)">
|
||||
</app-rotation-speed-assessment>
|
||||
</div>
|
||||
|
||||
<!-- Assessment 3: Ergonomic Position Optimization -->
|
||||
<div *ngIf="currentTest === 3">
|
||||
<app-ergonomic-assessment
|
||||
(testComplete)="onTestComplete()"
|
||||
(redoTest)="redoTest(3)">
|
||||
</app-ergonomic-assessment>
|
||||
</div>
|
||||
|
||||
<!-- Assessment 4: Text Legibility Optimization -->
|
||||
<div *ngIf="currentTest === 4">
|
||||
<app-text-legibility-assessment
|
||||
(testComplete)="onTestComplete()"
|
||||
(redoTest)="redoTest(4)">
|
||||
</app-text-legibility-assessment>
|
||||
</div>
|
||||
|
||||
<!-- Assessment 5: Scale Reference Calibration -->
|
||||
<div *ngIf="currentTest === 5">
|
||||
<app-scale-reference-assessment
|
||||
(testComplete)="onTestComplete()"
|
||||
(redoTest)="redoTest(5)">
|
||||
</app-scale-reference-assessment>
|
||||
</div>
|
||||
|
||||
<!-- Assessment 6: Spatial Positioning Optimization -->
|
||||
<div *ngIf="currentTest === 6">
|
||||
<app-spatial-position-assessment
|
||||
(testComplete)="onTestComplete()"
|
||||
(redoTest)="redoTest(6)">
|
||||
</app-spatial-position-assessment>
|
||||
</div>
|
||||
|
||||
<!-- Assessment 7: Demographics and Completion -->
|
||||
<div *ngIf="currentTest === 7">
|
||||
<app-demographics-assessment
|
||||
(testComplete)="onTestComplete()"
|
||||
(redoTest)="redoTest(7)">
|
||||
</app-demographics-assessment>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Subject, takeUntil } from 'rxjs';
|
||||
import { TestProgressService } from '../../services/test-progress.service';
|
||||
import { ArLoggerService } from '../../services/ar-logger.service';
|
||||
|
||||
import { RotationSpeedAssessmentComponent } from './assessments/rotation-speed-assessment/rotation-speed-assessment.component';
|
||||
import { ErgonomicAssessmentComponent } from './assessments/ergonomic-assessment/ergonomic-assessment.component';
|
||||
import { TextLegibilityAssessmentComponent } from './assessments/text-legibility-assessment/text-legibility-assessment.component';
|
||||
import { ScaleReferenceAssessmentComponent } from './assessments/scale-reference-assessment/scale-reference-assessment.component';
|
||||
import { SpatialPositionAssessmentComponent } from './assessments/spatial-position-assessment/spatial-position-assessment.component';
|
||||
import { DemographicsAssessmentComponent } from './assessments/demographics-assessment/demographics-assessment.component';
|
||||
import { FirstInputAssessmentComponent } from './assessments/first-input-assesment/first-input-assesment.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-test-suite',
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule,
|
||||
FirstInputAssessmentComponent,
|
||||
RotationSpeedAssessmentComponent,
|
||||
ErgonomicAssessmentComponent,
|
||||
TextLegibilityAssessmentComponent,
|
||||
ScaleReferenceAssessmentComponent,
|
||||
SpatialPositionAssessmentComponent,
|
||||
DemographicsAssessmentComponent
|
||||
],
|
||||
templateUrl: './test-suite.component.html',
|
||||
styleUrls: ['./test-suite.component.css']
|
||||
})
|
||||
export class TestSuiteComponent implements OnInit, OnDestroy {
|
||||
currentTest = 1;
|
||||
totalTests = 7;
|
||||
progress = 14.3;
|
||||
status = 'Ready to begin assessments';
|
||||
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
constructor(
|
||||
private progressService: TestProgressService,
|
||||
private logger: ArLoggerService
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.progressService.currentTest$
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe(test => {
|
||||
this.currentTest = test;
|
||||
this.logger.setCurrentTest(test);
|
||||
});
|
||||
|
||||
this.progressService.progress$
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe(progress => {
|
||||
this.progress = progress;
|
||||
});
|
||||
|
||||
this.logger.status$
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe(status => {
|
||||
this.status = status;
|
||||
});
|
||||
|
||||
this.totalTests = this.progressService.getTotalTests();
|
||||
this.logger.updateStatus('Test suite initialized - Assessment 1 ready');
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
onTestComplete(): void {
|
||||
this.progressService.nextTest();
|
||||
}
|
||||
|
||||
redoTest(testNumber: number): void {
|
||||
this.progressService.goToTest(testNumber);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,16 @@
|
|||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { DataExportService } from './data-export.service';
|
||||
|
||||
describe('DataExportService', () => {
|
||||
let service: DataExportService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
service = TestBed.inject(DataExportService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class DataExportService {
|
||||
|
||||
constructor() { }
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class TestProgressService {
|
||||
private totalTests = 7;
|
||||
private currentTestSubject = new BehaviorSubject<number>(1);
|
||||
private progressSubject = new BehaviorSubject<number>(14.3); // (1/7) * 100
|
||||
|
||||
currentTest$ = this.currentTestSubject.asObservable();
|
||||
progress$ = this.progressSubject.asObservable();
|
||||
|
||||
constructor(private router: Router) {}
|
||||
|
||||
getCurrentTest(): number {
|
||||
return this.currentTestSubject.value;
|
||||
}
|
||||
|
||||
getTotalTests(): number {
|
||||
return this.totalTests;
|
||||
}
|
||||
|
||||
nextTest(): void {
|
||||
const current = this.currentTestSubject.value;
|
||||
if (current < this.totalTests) {
|
||||
const nextTest = current + 1;
|
||||
this.currentTestSubject.next(nextTest);
|
||||
this.updateProgress(nextTest);
|
||||
} else {
|
||||
// All tests completed, navigate to completion screen
|
||||
this.router.navigate(['/completion']);
|
||||
}
|
||||
}
|
||||
|
||||
goToTest(testNumber: number): void {
|
||||
if (testNumber >= 1 && testNumber <= this.totalTests) {
|
||||
this.currentTestSubject.next(testNumber);
|
||||
this.updateProgress(testNumber);
|
||||
}
|
||||
}
|
||||
|
||||
private updateProgress(testNumber: number): void {
|
||||
const progress = (testNumber / this.totalTests) * 100;
|
||||
this.progressSubject.next(progress);
|
||||
}
|
||||
|
||||
resetProgress(): void {
|
||||
this.currentTestSubject.next(1);
|
||||
this.updateProgress(1);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { WebxrService } from './webxr.service';
|
||||
|
||||
describe('WebxrService', () => {
|
||||
let service: WebxrService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
service = TestBed.inject(WebxrService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class WebxrService {
|
||||
|
||||
constructor() { }
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,13 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>CeilingArAssessment</title>
|
||||
<base href="/">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
</head>
|
||||
<body>
|
||||
<app-root></app-root>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
import { bootstrapApplication } from '@angular/platform-browser';
|
||||
import { AppComponent } from './app/app.component';
|
||||
import { provideRouter } from '@angular/router';
|
||||
import { routes } from './app/app.routes';
|
||||
|
||||
bootstrapApplication(AppComponent, {
|
||||
providers: [
|
||||
provideRouter(routes)
|
||||
]
|
||||
}).catch(err => console.error(err));
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: [
|
||||
"./src/**/*.{html,ts}",
|
||||
],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
|
||||
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./out-tsc/app",
|
||||
"types": []
|
||||
},
|
||||
"files": [
|
||||
"src/main.ts"
|
||||
],
|
||||
"include": [
|
||||
"src/**/*.d.ts"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
|
||||
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
|
||||
{
|
||||
"compileOnSave": false,
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist/out-tsc",
|
||||
"strict": true,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"sourceMap": true,
|
||||
"declaration": false,
|
||||
"experimentalDecorators": true,
|
||||
"moduleResolution": "bundler",
|
||||
"importHelpers": true,
|
||||
"target": "ES2022",
|
||||
"module": "ES2022",
|
||||
"useDefineForClassFields": false,
|
||||
"lib": [
|
||||
"ES2022",
|
||||
"dom"
|
||||
]
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"enableI18nLegacyMessageIdFormat": false,
|
||||
"strictInjectionParameters": true,
|
||||
"strictInputAccessModifiers": true,
|
||||
"strictTemplates": true
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
|
||||
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./out-tsc/spec",
|
||||
"types": [
|
||||
"jasmine"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.d.ts"
|
||||
]
|
||||
}
|
||||
Loading…
Reference in New Issue