Compare commits

...
Sign in to create a new pull request.
Author SHA1 Message Date
Gundersen
6382544457 bux fixes 2022-03-01 14:30:09 -06:00
Gundersen
a46a5b4012 bux fixes with template 2022-03-01 13:58:54 -06:00
Gundersen
8d9a49c337 boilerplate report page 2022-03-01 11:31:11 -06:00
Gundersen
5f0f80a7cd testing initialized 2022-03-01 10:57:15 -06:00
Gundersen
766a911df5 thank you page 2022-03-01 10:50:45 -06:00
Gundersen
15d7928439 camera and sign components, submit code 2022-03-01 10:16:58 -06:00
18 changed files with 414 additions and 55 deletions

16
package-lock.json generated
View file

@ -4610,6 +4610,22 @@
} }
} }
}, },
"jasmine": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/jasmine/-/jasmine-4.0.2.tgz",
"integrity": "sha512-YsrgxJQEggxzByYe4j68eQLOiQeSrPDYGv4sHhGBp3c6HHdq+uPXeAQ73kOAQpdLZ3/0zN7x/TZTloqeE1/qIA==",
"dev": true,
"requires": {
"glob": "^7.1.6",
"jasmine-core": "^4.0.0"
}
},
"jasmine-core": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-4.0.1.tgz",
"integrity": "sha512-w+JDABxQCkxbGGxg+a2hUVZyqUS2JKngvIyLGu/xiw2ZwgsoSB0iiecLQsQORSeaKQ6iGrCyWG86RfNDuoA7Lg==",
"dev": true
},
"jest-worker": { "jest-worker": {
"version": "27.5.1", "version": "27.5.1",
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz",

View file

@ -3,6 +3,7 @@
"version": "0.0.0", "version": "0.0.0",
"scripts": { "scripts": {
"ng": "ng", "ng": "ng",
"test": "jasmine",
"start": "ng serve", "start": "ng serve",
"build": "ng build", "build": "ng build",
"watch": "ng build --watch --configuration development" "watch": "ng build --watch --configuration development"
@ -26,6 +27,7 @@
"@angular/cli": "~13.2.2", "@angular/cli": "~13.2.2",
"@angular/compiler-cli": "~13.2.0", "@angular/compiler-cli": "~13.2.0",
"@types/node": "^12.11.1", "@types/node": "^12.11.1",
"jasmine": "^4.0.2",
"typescript": "~4.5.2" "typescript": "~4.5.2"
} }
} }

13
spec/support/jasmine.json Normal file
View file

@ -0,0 +1,13 @@
{
"spec_dir": "spec",
"spec_files": [
"**/*[sS]pec.?(m)js"
],
"helpers": [
"helpers/**/*.?(m)js"
],
"env": {
"stopSpecOnExpectationFailure": false,
"random": true
}
}

View file

@ -0,0 +1,17 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HomeComponent } from './home.component';
import { ReportComponent } from './report.component';
import { ThanksComponent } from './thanks.component';
const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'thanks', component: ThanksComponent },
{ path: 'report', component: ReportComponent }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }

View file

@ -1,39 +1,13 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { checkin, checkout } from "src/assets/forms";
@Component({ @Component({
selector: 'app-root', selector: 'app-root',
template: ` template: `
<div class="menu"> <router-outlet></router-outlet>
<button (click)="switch('checkout')">Check Out</button>
<button (click)="switch('checkin')">Check In</button>
</div>
<app-form *ngIf="action=='checkin'" [template]="checkin"></app-form>
<app-form *ngIf="action=='checkout'" [template]="checkout"></app-form>
<footer>Accenture | 2022</footer>
` `
}) })
export class AppComponent { export class AppComponent {
checkin: Form = checkin;
checkout: Form = checkout;
action: string = "checkout";
ngOnInit() { } ngOnInit() { }
switch(action: string) {
alert("Are you sure?");
this.action = action;
}
} }

View file

@ -7,6 +7,12 @@ import { FormComponent } from './form.component';
import { TextComponent } from './text.component'; import { TextComponent } from './text.component';
import { RadioComponent } from './radio.component'; import { RadioComponent } from './radio.component';
import { StepComponent } from './step.component'; import { StepComponent } from './step.component';
import { CameraComponent } from './camera.component';
import { SignComponent } from './sign.component';
import { ThanksComponent } from './thanks.component';
import { AppRoutingModule } from './app-routing.module';
import { HomeComponent } from './home.component';
import { ReportComponent } from './report.component';
@NgModule({ @NgModule({
declarations: [ declarations: [
@ -15,10 +21,16 @@ import { StepComponent } from './step.component';
TextComponent, TextComponent,
RadioComponent, RadioComponent,
StepComponent, StepComponent,
CameraComponent,
SignComponent,
ThanksComponent,
HomeComponent,
ReportComponent,
], ],
imports: [ imports: [
BrowserModule, BrowserModule,
FormsModule, FormsModule,
AppRoutingModule,
], ],
providers: [], providers: [],
bootstrap: [AppComponent] bootstrap: [AppComponent]

View file

@ -0,0 +1,58 @@
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-camera',
template: `
<div class="camera">
<video id="video">Unable to access camera.</video>
<img [src]="this.field.value" />
<button (click)="capture()">Take photo</button>
</div>
`
})
export class CameraComponent implements OnInit {
@Input() field: Field;
@Output() update: EventEmitter<null> = new EventEmitter();
video: HTMLVideoElement;
ngOnInit() { console.log("Camera", this.field.id); }
ngAfterViewInit() {
this.video = document.getElementById("video") as HTMLVideoElement;
navigator.mediaDevices.getUserMedia({video: true, audio: false})
.then((stream) => {
this.video.srcObject = stream;
this.video.play();
});
}
capture() {
const canvas = document.createElement("canvas") as HTMLCanvasElement;
canvas.width = this.video.videoWidth;
canvas.height = this.video.videoHeight;
canvas
.getContext("2d")
.drawImage(this.video, 0, 0, canvas.width, canvas.height);
this.field.ok = true;
this.field.value = canvas.toDataURL();
this.update.emit();
}
}

View file

@ -1,4 +1,5 @@
import { Component, OnInit, Input } from '@angular/core'; import { Component, OnInit, Input } from '@angular/core';
import { Router } from '@angular/router';
@Component({ @Component({
selector: 'app-form', selector: 'app-form',
@ -15,6 +16,7 @@ import { Component, OnInit, Input } from '@angular/core';
></app-step> ></app-step>
<button <button
class="bar"
[class.active]="form.step > 0" [class.active]="form.step > 0"
(click)="back()" (click)="back()"
> >
@ -22,6 +24,7 @@ import { Component, OnInit, Input } from '@angular/core';
</button> </button>
<button <button
class="bar"
[class.active]="form.steps[form.step].ok" [class.active]="form.steps[form.step].ok"
(click)="next()" (click)="next()"
> >
@ -33,30 +36,60 @@ import { Component, OnInit, Input } from '@angular/core';
}) })
export class FormComponent implements OnInit { export class FormComponent implements OnInit {
constructor(private router: Router) { }
@Input() template: Form; @Input() template: Form;
form: Form; form: Form;
ngOnInit() { this.set() } ngOnInit() { this.set(); }
set() { this.form = Object.assign({}, this.template); } set() {
/* Make a deep copy of the template */
this.form = JSON.parse(JSON.stringify(this.template));
}
next() { next() {
this.form.step == this.form.steps.length - 1 ? this.form.step == this.form.steps.length - 1 ?
this.submit() : this.submit() :
this.form.step++ this.form.step++;
} }
back() { this.form.step--; } back() { this.form.step--; }
submit() { async submit() {
console.log("Submitting", this.form); const res = await fetch(this.form.submit, parseForm(this.form.steps));
this.set(); res.ok ?
this.router.navigate(["/thanks"]) :
alert("Failed to connect to the backend.");
} }
} }
/* Util to extract key, values from Form */
const parseForm = (steps: Step[]) => {
const results = {};
steps.forEach((s: Step) => {
s.fields.forEach((f: Field) => {
results[f.id] = f.value;
});
});
return results;
};

41
src/app/home.component.ts Normal file
View file

@ -0,0 +1,41 @@
import { Component, OnInit } from '@angular/core';
import { checkin, checkout } from "src/assets/forms";
@Component({
selector: 'app-home',
template: `
<div class="menu">
<button (click)="switch('checkout')">Check Out</button>
<button (click)="switch('checkin')">Check In</button>
</div>
<app-form *ngIf="action=='checkin'" [template]="checkin"></app-form>
<app-form *ngIf="action=='checkout'" [template]="checkout"></app-form>
<a routerLink="/report">Go To Report Page</a>
<footer>Accenture | 2022</footer>
`
})
export class HomeComponent implements OnInit {
checkin: Form = checkin;
checkout: Form = checkout;
action: string = "checkout";
ngOnInit() { }
switch(action: string) {
alert("Are you sure?");
this.action = action;
}
}

View file

@ -25,7 +25,7 @@ export class RadioComponent implements OnInit {
@Input() field: Field; @Input() field: Field;
@Output() update: EventEmitter<null> = new EventEmitter(); @Output() update: EventEmitter<null> = new EventEmitter();
ngOnInit() { console.log("Text", this.field.id); } ngOnInit() { console.log("Radio", this.field.id); }
check() { check() {

View file

@ -0,0 +1,53 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-report',
template: `
<div class="report">
<span *ngFor="let entry of data">
<img [src]="entry.photo"/>
<div class="info">
{{ entry.name }}
</div>
</span>
</div>
`,
styles: [
]
})
export class ReportComponent implements OnInit {
data: Entry[];
ngOnInit() {
this.data = data;
// try {
// const res = await fetch("url");
// } catch(e) { alert("Something went wrong."); }
// this.data = res.json();
}
}
/* Placeholder data */
const data: Entry[] = [{
name: "John Doe",
eid: "john.doe",
asset: "monitor",
complete: false,
photo: "./assets/monitor.jfif"
}]

72
src/app/sign.component.ts Normal file
View file

@ -0,0 +1,72 @@
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-sign',
template: `
<div class="sign">
<canvas id="pad"></canvas>
<button (click)="clear()">Clear</button>
</div>
`
})
export class SignComponent implements OnInit {
@Input() field: Field;
@Output() update: EventEmitter<null> = new EventEmitter();
pad: HTMLCanvasElement;
ngOnInit() { console.log("Sign", this.field.id); }
ngAfterViewInit() {
this.pad = document.getElementById("pad") as HTMLCanvasElement;
const ctx = this.pad.getContext('2d');
this.pad.width = 300;
this.pad.height = 100;
ctx.lineWidth = 3;
ctx.lineJoin = 'round';
ctx.lineCap = 'round';
let active, cursor;
this.pad.onmousedown = (e) => {
active = true;
cursor = { x: e.offsetX, y: e.offsetY };
}
this.pad.onmousemove = (e) => {
if (active) {
ctx.beginPath();
ctx.moveTo(cursor.x, cursor.y);
ctx.lineTo(e.offsetX, e.offsetY);
ctx.stroke();
cursor = { x: e.offsetX, y: e.offsetY };
}
}
this.pad.onmouseup = (_e) => {
active = false;
this.field.value = this.pad.toDataURL();
this.field.ok = true;
this.update.emit();
};
}
clear() {
this.pad.getContext('2d').clearRect(0, 0, this.pad.width, this.pad.height);
this.field.value = null;
this.field.ok = false;
this.update.emit();
};
}

View file

@ -23,6 +23,20 @@ import { Component, OnInit, Input } from '@angular/core';
></app-radio> ></app-radio>
<app-camera *ngSwitchCase="'camera'"
[field]="field"
(update)="update()"
></app-camera>
<app-sign *ngSwitchCase="'sign'"
[field]="field"
(update)="update()"
></app-sign>
</div> </div>
</div> </div>
@ -32,13 +46,10 @@ export class StepComponent implements OnInit {
@Input() step: Step; @Input() step: Step;
ngOnInit() { console.log("Step", this.step); } ngOnInit() { console.log("Step", this.step.prompt); }
update() { update() {
console.log(this.step.fields);
/* Check for completeness */
for (let field of this.step.fields) { for (let field of this.step.fields) {
if (!field.ok) { if (!field.ok) {
this.step.ok = false; this.step.ok = false;

View file

@ -0,0 +1,22 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-thanks',
template: `
<p>
Thank-You!
<a routerLink="/">Go Home</a>
</p>
`,
styles: [
]
})
export class ThanksComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}

View file

@ -35,6 +35,28 @@ export const checkout: Form = {
options: { choices: ["mouse", "keyboard", "monitor", "chair"] } options: { choices: ["mouse", "keyboard", "monitor", "chair"] }
}] }]
}, },
{
ok: false,
prompt: "Take a photo.",
fields: [{
ok: false,
type: "camera",
id: "photo",
value: null,
options: null
}]
},
{
ok: false,
prompt: "Sign your name.",
fields: [{
ok: false,
type: "sign",
id: "signiture",
value: null,
options: null
}]
},
{ {
ok: false, ok: false,
prompt: "Would you like a confirmation email?", prompt: "Would you like a confirmation email?",
@ -81,13 +103,13 @@ export const checkin: Form = {
}, },
{ {
ok: false, ok: false,
prompt: "Would you like a confirmation email?", prompt: "Sign your name.",
fields: [{ fields: [{
ok: false, ok: false,
type: "radio", type: "sign",
id: "confirmation", id: "signiture",
value: null, value: null,
options: { choices: ["yes", "no"] } options: null
}] }]
}] }]

BIN
src/assets/monitor.jfif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

View file

@ -1,5 +1,18 @@
.bar {
pointer-events: none;
}
.active { .active {
pointer-events: auto; pointer-events: auto;
color: blue;
border-color: blue; border-color: blue;
color: blue;
}
video, img {
width: 100px;
}
#pad {
background-color: white;
border: 2px solid black;
} }

16
src/types.d.ts vendored
View file

@ -1,11 +1,3 @@
declare interface TextOptions {
modifier: string;
}
declare interface RadioOptions {
choices: string[];
}
declare interface Field { declare interface Field {
type: string; type: string;
id: string; id: string;
@ -27,3 +19,11 @@ declare interface Form {
step: number; step: number;
steps: Step[]; steps: Step[];
} }
declare interface Entry {
name: string;
eid: string;
asset: string;
complete: boolean;
photo: string;
}