Renders label layout

This commit is contained in:
Aaron Louie 2020-09-22 22:33:07 -04:00
parent b75126779a
commit 0d4ed8f6d9
40 changed files with 2481 additions and 1249 deletions

View File

@ -3,7 +3,7 @@
"compilerOptions": {
"outDir": "../out-tsc/e2e",
"module": "commonjs",
"target": "es5",
"target": "es2018",
"types": [
"jasmine",
"jasminewd2",

2804
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -22,48 +22,49 @@
},
"private": true,
"dependencies": {
"@angular/animations": "~9.1.11",
"@angular/cdk": "^9.2.4",
"@angular/common": "~9.1.11",
"@angular/compiler": "~9.1.11",
"@angular/core": "~9.1.11",
"@angular/animations": "~11.0.0-next.2",
"@angular/cdk": "^10.2.2",
"@angular/common": "~11.0.0-next.2",
"@angular/compiler": "~11.0.0-next.2",
"@angular/core": "~10.1.2",
"@angular/flex-layout": "^9.0.0-beta.31",
"@angular/forms": "~9.1.11",
"@angular/material": "^9.2.4",
"@angular/platform-browser": "~9.1.11",
"@angular/platform-browser-dynamic": "~9.1.11",
"@angular/router": "~9.1.11",
"@angular/forms": "~11.0.0-next.2",
"@angular/material": "^10.2.2",
"@angular/platform-browser": "~11.0.0-next.2",
"@angular/platform-browser-dynamic": "~11.0.0-next.2",
"@angular/router": "~11.0.0-next.2",
"@ngx-formly/core": "^5.8.0",
"@ngx-formly/material": "^5.8.0",
"lodash.isequal": "^4.5.0",
"ngx-qrcode-svg": "^2.0.0",
"rfdc": "^1.1.4",
"rxjs": "~6.5.4",
"tslib": "^1.13.0",
"rxjs": "~6.6.3",
"tslib": "^2.0.0",
"zone.js": "~0.10.3"
},
"devDependencies": {
"@angular-devkit/build-angular": "^0.901.10",
"@angular/cli": "^9.1.10",
"@angular/compiler-cli": "~9.1.11",
"@angular/language-service": "~9.1.11",
"@angular-devkit/build-angular": "^0.1001.2",
"@angular/cli": "^10.1.2",
"@angular/compiler-cli": "~11.0.0-next.2",
"@angular/language-service": "~11.0.0-next.2",
"@types/jasmine": "^3.5.11",
"@types/jasminewd2": "~2.0.3",
"@types/node": "^12.12.47",
"codelyzer": "^5.1.2",
"jasmine-core": "~3.5.0",
"jasmine-spec-reporter": "~4.2.1",
"karma": "^5.1.0",
"jasmine-spec-reporter": "~5.0.0",
"karma": "~5.0.0",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage-istanbul-reporter": "~2.1.1",
"karma-jasmine": "~3.1.1",
"karma-jasmine-html-reporter": "^1.5.4",
"karma-coverage-istanbul-reporter": "~3.0.2",
"karma-jasmine": "~4.0.0",
"karma-jasmine-html-reporter": "^1.5.0",
"lodash.get": "^4.4.2",
"lodash.set": "^4.3.2",
"protractor": "^7.0.0",
"protractor": "~7.0.0",
"protractor-http-client": "^1.0.4",
"sonar-scanner": "^3.1.0",
"ts-node": "~8.6.2",
"tslint": "~6.0.0",
"typescript": "~3.7.5"
"tslint": "~6.1.0",
"typescript": "~4.0.3"
}
}

View File

@ -1,7 +1,11 @@
import {NgModule} from '@angular/core';
import {RouterModule, Routes} from '@angular/router';
import {ThisEnvironment} from '../environments/environment.injectable';
import {CountComponent} from './count/count.component';
import {HomeComponent} from './home/home.component';
import {PrintComponent} from './print/print.component';
import {SampleComponent} from './sample/sample.component';
import {SettingsComponent} from './settings/settings.component';
export const routes: Routes = [
{
@ -9,6 +13,26 @@ export const routes: Routes = [
pathMatch: 'full',
component: HomeComponent
},
{
path: 'sample',
pathMatch: 'full',
component: SampleComponent
},
{
path: 'print',
pathMatch: 'full',
component: PrintComponent
},
{
path: 'count',
pathMatch: 'full',
component: CountComponent
},
{
path: 'settings',
pathMatch: 'full',
component: SettingsComponent
},
];
@NgModule({

View File

@ -1,5 +1,5 @@
<div class="mat-typography">
<app-navbar></app-navbar>
<app-navbar [testingLocation]="testingLocation"></app-navbar>
<router-outlet *ngIf="!loading; else loadingMessage"></router-outlet>
<app-footer></app-footer>
</div>

View File

@ -3,6 +3,7 @@ import {MatIconRegistry} from '@angular/material/icon';
import {DomSanitizer, Title} from '@angular/platform-browser';
import {Router} from '@angular/router';
import {AppEnvironment} from './interfaces/appEnvironment.interface';
import {TestingLocation} from './interfaces/testingLocation.interface';
import {GoogleAnalyticsService} from './services/google-analytics.service';
@Component({
@ -12,6 +13,10 @@ import {GoogleAnalyticsService} from './services/google-analytics.service';
})
export class AppComponent {
loading: boolean;
testingLocation: TestingLocation = {
id: '0000',
name: 'Click here to set location',
};
constructor(
@Inject('APP_ENVIRONMENT') private environment: AppEnvironment,

View File

@ -4,20 +4,28 @@ import {NgModule} from '@angular/core';
import {FlexLayoutModule} from '@angular/flex-layout';
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
import {MatButtonModule} from '@angular/material/button';
import {MAT_FORM_FIELD_DEFAULT_OPTIONS} from '@angular/material/form-field';
import {MatCardModule} from '@angular/material/card';
import {MAT_FORM_FIELD_DEFAULT_OPTIONS, MatFormFieldModule} from '@angular/material/form-field';
import {MatIconModule} from '@angular/material/icon';
import {MatInputModule} from '@angular/material/input';
import {MatProgressSpinnerModule} from '@angular/material/progress-spinner';
import {MatToolbarModule} from '@angular/material/toolbar';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {FormlyModule} from '@ngx-formly/core';
import {QRCodeSVGModule} from 'ngx-qrcode-svg';
import {ThisEnvironment} from '../environments/environment.injectable';
import {AppRoutingModule} from './app-routing.module';
import {AppComponent} from './app.component';
import {CountComponent} from './count/count.component';
import {FooterComponent} from './footer/footer.component';
import {HomeComponent} from './home/home.component';
import {LoadingComponent} from './loading/loading.component';
import {NavbarComponent} from './navbar/navbar.component';
import {PrintComponent} from './print/print.component';
import {SampleComponent} from './sample/sample.component';
import {ApiService} from './services/api.service';
import {SettingsComponent} from './settings/settings.component';
import { LabelLayoutComponent } from './label-layout/label-layout.component';
/**
* This function is used internal to get a string instance of the `<base href="" />` value from `index.html`.
@ -41,6 +49,11 @@ export function getBaseHref(platformLocation: PlatformLocation): string {
FooterComponent,
NavbarComponent,
HomeComponent,
SampleComponent,
CountComponent,
SettingsComponent,
PrintComponent,
LabelLayoutComponent,
],
imports: [
BrowserAnimationsModule,
@ -50,11 +63,14 @@ export function getBaseHref(platformLocation: PlatformLocation): string {
HttpClientModule,
MatProgressSpinnerModule,
ReactiveFormsModule,
AppRoutingModule,
MatCardModule,
MatInputModule,
MatToolbarModule,
MatButtonModule,
MatIconModule,
// <-- This line MUST be last (https://angular.io/guide/router#module-import-order-matters)
MatFormFieldModule,
QRCodeSVGModule,
AppRoutingModule, // <-- This line MUST be last (https://angular.io/guide/router#module-import-order-matters)
],
providers: [
{provide: MAT_FORM_FIELD_DEFAULT_OPTIONS, useValue: {appearance: 'outline'}},

View File

@ -0,0 +1,31 @@
import {AppDefaults} from '../interfaces/appDefaults.interface';
import {LabelLayout, LabelLayoutType} from '../interfaces/labelLayout';
// Default form field and data values
export const defaults: AppDefaults = {
countsCollection: 'counts', // Name of collection for Line Counts in Firebase.
samplesCollection: 'samples', // Name of collection for Line Counts in Firebase.
dateEncodedFormat: 'yyyyMMddHHmm', // Format for dates when encoded in IDs for database records.
dateDisplayFormat: 'MM/dd/yyyy, hh:mm aa', // Format for dates when displayed to user.
numCopies: 3, // Default number of copies of labels to print. Can be overridden by user setting.
labelLayout: 'round_32mm_1up' as LabelLayoutType, // Which label layout to use for printing. Can be overridden by user setting.
locationId: '0000', // Default location ID. Can be overridden by user setting.
lineCountRegex: /^[\d]{4}-[\d]{12}$/, // ID format for Line Count records.
qrCodeRegex: /^[\d]{9}-[a-zA-Z]+-[\d]{12}-[\d]{4}$/, // ID format for QR Code records.
barCodeNumLength: 9, // Number of digits in Bar Code.
barCodeRegex: /^[\d]{14}$|^[\d]{9}$/, // Pattern for Bar Code data. Scanned barcodes will be either 9 or 14 digits long.
// Manually-entered ID numbers will be exactly 9 digits long.
initialsLength: 5,
initialsRegex: /^[a-zA-Z]{2,5}$/,
};
export const labelLayouts = {
round_32mm_1up: new LabelLayout({
numCols: 1,
columnGap: 0,
}),
round_32mm_2up: new LabelLayout({
numCols: 2,
columnGap: 1.3,
}),
};

View File

@ -0,0 +1 @@
<p>count works!</p>

View File

View File

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CountComponent } from './count.component';
describe('CountComponent', () => {
let component: CountComponent;
let fixture: ComponentFixture<CountComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ CountComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(CountComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-count',
templateUrl: './count.component.html',
styleUrls: ['./count.component.scss']
})
export class CountComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}

View File

@ -1 +0,0 @@
<p>footer works!</p>

View File

@ -1 +1,19 @@
<p>home works!</p>
<div
class="full-height bg-primary"
fxLayout="column"
fxLayoutAlign="center center"
fxLayoutGap="40px"
>
<button
mat-flat-button
color="accent"
class="btn-xl"
routerLink="/sample"
autofocus
>New sample</button>
<button
mat-flat-button
class="btn-xl"
routerLink="/count"
>Line count</button>
</div>

View File

@ -0,0 +1,17 @@
import {LabelLayoutType} from './labelLayout';
export interface AppDefaults {
countsCollection: string;
samplesCollection: string;
dateEncodedFormat: string;
dateDisplayFormat: string;
numCopies: number;
labelLayout: LabelLayoutType;
locationId: string;
lineCountRegex: RegExp;
qrCodeRegex: RegExp;
barCodeRegex: RegExp;
barCodeNumLength: number;
initialsRegex: RegExp;
initialsLength: number;
}

View File

@ -0,0 +1,90 @@
export declare type LabelLayoutType = 'round_32mm_1up' | 'round_32mm_2up';
export interface LayoutOptions {
units?: string;
pointsPerUnit?: number;
labelSize?: number;
marginSize?: number;
numCols?: number;
columnGap?: number;
sideTextWidth?: number;
sideTextTop?: number;
sideTextMargin?: number;
topTextMargin?: number;
bottomTextMargin?: number;
fontSizePt?: number;
numCopies?: number;
}
export class LabelLayout {
units = 'mm';
pointsPerUnit = 0.3528;
labelSize = 28.6;
marginSize = 1.7;
numCols = 2;
columnGap = 4;
sideTextWidth = 4;
sideTextTop = 11;
sideTextMargin = 1.5;
topTextMargin = 3;
bottomTextMargin = 2.5;
fontSizePt = 6;
numCopies = 1;
constructor(private options: LayoutOptions) {
this.units = options.units || this.units;
this.pointsPerUnit = options.pointsPerUnit || this.pointsPerUnit;
this.marginSize = options.marginSize || this.marginSize;
this.labelSize = options.labelSize || this.labelSize;
this.numCols = options.numCols || this.numCols;
this.columnGap = options.columnGap || this.columnGap;
this.sideTextWidth = options.sideTextWidth || this.sideTextWidth;
this.sideTextTop = options.sideTextTop || this.sideTextTop;
this.sideTextMargin = options.sideTextMargin || this.sideTextMargin;
this.topTextMargin = options.topTextMargin || this.topTextMargin;
this.bottomTextMargin = options.bottomTextMargin || this.bottomTextMargin;
this.fontSizePt = options.fontSizePt || this.fontSizePt;
}
get dimensions() {
return {
columnGap: this._toUnits(this.columnGap),
sideTextWidth: this._toUnits(this.sideTextWidth),
sideTextTop: this._toUnits(this.sideTextTop),
sideTextMargin: this._toUnits(this.sideTextMargin),
topTextMargin: this._toUnits(this.topTextMargin),
bottomTextMargin: this._toUnits(this.bottomTextMargin),
columnGapWidth: this._toUnits(this.columnGapWidth),
marginWidth: this._toUnits(this.marginSize),
labelSize: this._toUnits(this.labelSize),
labelSizeWithMargins: this._toUnits(this.labelSizeWithMargins),
pageWidth: this._toUnits(this.pageWidth),
pageHeight: this._toUnits(this.pageHeight),
fontSize: this._toUnits(this.fontSize),
};
}
get columnGapWidth(): number {
return this.columnGap;
}
get labelSizeWithMargins(): number {
return (this.labelSize + (this.marginSize * 2));
}
get pageWidth(): number {
return (this.labelSizeWithMargins * this.numCols) + (this.columnGap * this.numCols - 1);
}
get pageHeight(): number {
return (this.labelSizeWithMargins * this.numCopies);
}
get fontSize(): number {
return this.fontSizePt * this.pointsPerUnit;
}
private _toUnits(num: number) {
return `${num}${this.units}`;
}
}

View File

@ -0,0 +1,4 @@
export interface TestingLocation {
id: string;
name: string;
}

View File

@ -0,0 +1,18 @@
<div class="label-layout">
<div class="date">{{dateCreated | date:'yyyyMMdd'}}</div>
<div class="time">
T<br />
{{dateCreated | date:'HH'}}<br />
{{dateCreated | date:'mm'}}
</div>
<qrcode-svg
[value]="qrCodeValue"
errorCorrectionLevel="H"
></qrcode-svg>
<div class="location">
L<br />
{{locationId.slice(0, 2)}}<br />
{{locationId.slice(2, 4)}}
</div>
<div class="barcode">#{{barCode}}</div>
</div>

View File

@ -0,0 +1,51 @@
.date,
.time,
.location,
.barcode {
position: absolute;
font-family: monospace;
font-weight: bolder;
text-align: center;
font-size: 6pt;
}
.label-layout {
position: relative;
width: 24.6mm;
height: 24.6mm;
border: 2mm solid white;
border-radius: 100%;
background-color: white;
padding: 0;
margin: 0;
.date {
top: 0;
width: 100%;
}
.barcode {
bottom: 0;
width: 100%;
}
.time {
top: calc(50% - 3mm);
left: 0;
}
.location {
top: calc(50% - 3mm);
right: 0;
}
qrcode-svg {
position: absolute;
left: 50%;
top: 50%;
margin-left: -11mm;
margin-top: -11mm;
width: 22mm;
height: 22mm;
}
}

View File

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { LabelLayoutComponent } from './label-layout.component';
describe('LabelLayoutComponent', () => {
let component: LabelLayoutComponent;
let fixture: ComponentFixture<LabelLayoutComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ LabelLayoutComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(LabelLayoutComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,32 @@
import {formatDate} from '@angular/common';
import {Component, Input, OnInit} from '@angular/core';
import {defaults} from '../config/defaults';
@Component({
selector: 'app-label-layout',
templateUrl: './label-layout.component.html',
styleUrls: ['./label-layout.component.scss']
})
export class LabelLayoutComponent implements OnInit {
@Input() dateCreated: Date;
@Input() barCode: string;
@Input() initials: string;
locationId = defaults.locationId;
constructor() {
}
get qrCodeValue(): string {
const valArray = [
this.barCode,
this.initials,
formatDate(this.dateCreated, 'yyyyMMddHHmm', 'en-us'),
defaults.locationId,
];
return valArray.join('-');
}
ngOnInit(): void {
}
}

View File

@ -1,5 +1,8 @@
<mat-toolbar color="primary">
<mat-toolbar-row>
<button mat-button routerLink="/" class="logo">BeSAFE</button>
<button mat-button routerLink="/settings">{{testingLocation.name}}</button>
<span fxFlex></span>
<button mat-icon-button routerLink="/"><mat-icon>home</mat-icon></button>
<button mat-icon-button routerLink="/settings"><mat-icon>settings</mat-icon></button>
</mat-toolbar-row>

View File

@ -0,0 +1,4 @@
.logo {
font-weight: bolder;
font-size: 2rem;
}

View File

@ -1,4 +1,5 @@
import { Component, OnInit } from '@angular/core';
import {Component, Input, OnInit} from '@angular/core';
import {TestingLocation} from '../interfaces/testingLocation.interface';
@Component({
selector: 'app-navbar',
@ -6,6 +7,7 @@ import { Component, OnInit } from '@angular/core';
styleUrls: ['./navbar.component.scss']
})
export class NavbarComponent implements OnInit {
@Input() testingLocation: TestingLocation;
constructor() { }

View File

@ -0,0 +1,46 @@
<div
id="screen"
class="full-height bg-primary"
fxLayout="row"
fxLayoutAlign="center center"
fxLayoutGap="40px"
>
<mat-card fxFlex="50%">
<mat-card-header>
<h1>Print Labels</h1>
</mat-card-header>
<mat-card-content fxLayout="column" fxLayoutAlign="center center">
<app-label-layout
[barCode]="barCode"
[initials]="initials"
[dateCreated]="dateCreated"
></app-label-layout>
</mat-card-content>
</mat-card>
</div>
<div
id="print"
[style]="{width: pageWidth, height: pageHeight}"
[gdRows]="pagesToGridFractions"
gdGap="1.5mm"
gdAlignColumns="center"
gdAlignRows="center"
>
<div
*ngFor="let page of pages"
[gdColumns]="colsToGridFractions"
gdAlignColumns="center"
gdAlignRows="center"
gdGap="3mm"
>
<app-label-layout
*ngFor="let col of columns"
[barCode]="barCode"
[initials]="initials"
[dateCreated]="dateCreated"
></app-label-layout>
</div>
</div>

View File

@ -0,0 +1,53 @@
@media screen {
#print { display: none !important; }
#screen { display: flex !important; }
}
@media print {
@page {
size: 64mm 32mm;
margin: 0 !important;
padding: 0 !important;
}
::ng-deep html,
::ng-deep body {
width: 64mm;
height: 32mm;
margin: 0 !important;
padding: 0 !important;
background-color: transparent;
}
::ng-deep .no-print,
::ng-deep mat-card,
::ng-deep app-navbar,
::ng-deep app-footer {
display: none;
}
#print {
display: grid !important;
background-color: transparent;
width: 64mm;
height: 32mm;
padding-top: 2mm;
padding-bottom: 2mm;
.page {
padding-top: 2mm;
padding-bottom: 2mm;
}
.column {
padding: 1mm;
}
}
#screen { display: none !important; }
}
.mat-card-content {
background-color: #6C799C;
height: 500px;
}

View File

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { PrintComponent } from './print.component';
describe('PrintComponent', () => {
let component: PrintComponent;
let fixture: ComponentFixture<PrintComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ PrintComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(PrintComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,52 @@
import {Component, OnInit} from '@angular/core';
import {ActivatedRoute} from '@angular/router';
import {defaults} from '../config/defaults';
@Component({
selector: 'app-print',
templateUrl: './print.component.html',
styleUrls: ['./print.component.scss']
})
export class PrintComponent implements OnInit {
barCode: string;
initials: string;
dateCreated: Date;
get pagesToGridFractions(): string {
const what = this.pages.map(() => '1fr').join(' ');
console.log('what', what);
return what;
}
get colsToGridFractions(): string {
return this.columns.map(() => '1fr').join(' ');
}
get pageHeight(): string {
return `${this.pages.length * 32}mm`;
}
get pageWidth(): string {
return `${this.columns.length * 32}mm`;
}
constructor(private route: ActivatedRoute) {
this.dateCreated = new Date();
this.route.queryParamMap.subscribe(queryParamMap => {
this.barCode = queryParamMap.get('barCode');
this.initials = queryParamMap.get('initials');
});
}
get pages() {
return Array(defaults.numCopies).fill('');
}
get columns() {
return Array(defaults.labelLayout === 'round_32mm_2up' ? 2 : 1).fill('');
}
ngOnInit(): void {
}
}

View File

@ -0,0 +1,70 @@
<div
class="full-height bg-primary"
fxLayout="row"
fxLayoutAlign="center center"
fxLayoutGap="40px"
>
<mat-card fxFlex="50%">
<mat-card-header>
<h1>Scan Barcode</h1>
</mat-card-header>
<mat-card-content fxLayout="row wrap" fxLayoutAlign="center center">
<mat-form-field class="barcode-input" fxFlex="95%">
<mat-label>Barcode ID #</mat-label>
<input
#barCodeInput="matInput"
matInput
type="text"
[formControl]="barCodeFormControl"
>
<button
mat-button
*ngIf="barCodeValue"
matSuffix
mat-icon-button
aria-label="Clear"
(click)="barCodeFormControl.patchValue('')"
>
<mat-icon>close</mat-icon>
</button>
<mat-error *ngIf="barCodeFormControl.hasError('required')">This field is required.</mat-error>
<mat-error *ngIf="barCodeFormControl.hasError('pattern')">Please enter exactly 9 or 14 digits.</mat-error>
</mat-form-field>
<mat-form-field class="initials-input" fxFlex="95%">
<mat-label>Initials</mat-label>
<input
#initialsInput="matInput"
matInput
type="text"
[formControl]="initialsFormControl"
>
<button
mat-button
*ngIf="initialsValue"
matSuffix
mat-icon-button
aria-label="Clear"
(click)="initialsFormControl.patchValue('')"
>
<mat-icon>close</mat-icon>
</button>
<mat-error *ngIf="initialsFormControl.hasError('required')">This field is required.</mat-error>
<mat-error *ngIf="initialsFormControl.hasError('pattern')">Please enter only letters.</mat-error>
</mat-form-field>
</mat-card-content>
<mat-card-actions>
<button
mat-flat-button
class="btn-xl"
color="accent"
fxFlex="50%"
[disabled]="!hasInfo"
routerLink="/print"
[queryParams]="queryParams"
>Next</button>
<button mat-flat-button class="btn-xl" fxFlex="50%" routerLink="/">Cancel</button>
</mat-card-actions>
</mat-card>
</div>

View File

@ -0,0 +1,2 @@
.barcode-input {
}

View File

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { SampleComponent } from './sample.component';
describe('SampleComponent', () => {
let component: SampleComponent;
let fixture: ComponentFixture<SampleComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ SampleComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(SampleComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,84 @@
import {AfterViewInit, ChangeDetectorRef, Component, OnInit, ViewChild} from '@angular/core';
import {FormControl, Validators} from '@angular/forms';
import {MatInput} from '@angular/material/input';
import {Params} from '@angular/router';
import {defaults} from '../config/defaults';
@Component({
selector: 'app-sample',
templateUrl: './sample.component.html',
styleUrls: ['./sample.component.scss']
})
export class SampleComponent implements AfterViewInit {
barCodeErrorMessage = '';
initialsErrorMessage = '';
barCodeFormControl = new FormControl('', [
Validators.required,
Validators.pattern(defaults.barCodeRegex),
]);
initialsFormControl = new FormControl('', [
Validators.required,
Validators.pattern(defaults.initialsRegex),
]);
@ViewChild('barCodeInput') barCodeInput: MatInput;
@ViewChild('initialsInput') initialsInput: MatInput;
get queryParams(): Params {
return {
barCode: this.barCodeValue,
initials: this.initialsValue,
};
}
constructor(private changeDetector: ChangeDetectorRef) {
this.barCodeFormControl.registerOnChange(() => {
this.checkBarCodeValue();
});
this.initialsFormControl.registerOnChange(() => this.checkInitialsValue());
}
get barCodeValue(): string {
return this.barCodeFormControl.value;
}
get initialsValue(): string {
return this.initialsFormControl.value;
}
get hasBarCode(): boolean {
return defaults.barCodeRegex.test(this.barCodeValue);
}
get hasInitials(): boolean {
return defaults.initialsRegex.test(this.initialsValue);
}
get hasInfo(): boolean {
return this.hasBarCode && this.hasInitials;
}
ngAfterViewInit() {
this.barCodeInput.focus();
this.changeDetector.detectChanges();
}
checkBarCodeValue() {
console.log('--- checkBarCodeValue ---');
if (this.hasBarCode) {
this.barCodeErrorMessage = '';
this.initialsInput.focus();
this.changeDetector.detectChanges();
} else {
this.barCodeErrorMessage = 'Wrong barcode.';
}
}
checkInitialsValue() {
if (this.hasInitials) {
this.initialsErrorMessage = '';
} else {
this.initialsErrorMessage = 'Wrong barcode.';
}
}
}

View File

@ -0,0 +1 @@
<p>settings works!</p>

View File

View File

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { SettingsComponent } from './settings.component';
describe('SettingsComponent', () => {
let component: SettingsComponent;
let fixture: ComponentFixture<SettingsComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ SettingsComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(SettingsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-settings',
templateUrl: './settings.component.html',
styleUrls: ['./settings.component.scss']
})
export class SettingsComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}

View File

@ -16,6 +16,14 @@
padding-top: $header-height;
}
app-navbar {
@include mat-elevation(4);
position: fixed;
top: 0;
left: 0;
right: 0;
}
.mat-display-4 {
color: $brand-primary;
border-bottom: 1px solid $brand-gray-light;
@ -120,6 +128,10 @@
}
button {
&:focus {
outline: 4px solid white;
}
&.btn-xl {
font-size: 24px;
padding-left: 24px;
@ -195,6 +207,9 @@
}
}
.bg-primary { background-color: $brand-primary; }
.bg-accent { background-color: $brand-accent; }
.pad-0 {
padding: 0px;
}

View File

@ -7,7 +7,7 @@
"declaration": false,
"downlevelIteration": true,
"experimentalDecorators": true,
"module": "esnext",
"module": "es2020",
"moduleResolution": "node",
"importHelpers": true,
"target": "es2015",

View File

@ -1,13 +1,21 @@
{
"extends": "tslint:recommended",
"rules": {
"align": {
"options": [
"parameters",
"statements"
]
},
"array-type": false,
"arrow-parens": false,
"arrow-return-shorthand": true,
"deprecation": {
"severity": "warning"
},
"component-class-suffix": true,
"contextual-lifecycle": true,
"curly": true,
"directive-class-suffix": true,
"directive-selector": [
true,
@ -21,10 +29,17 @@
"app",
"kebab-case"
],
"eofline": true,
"import-blacklist": [
true,
"rxjs/Rx"
],
"import-spacing": true,
"indent": {
"options": [
"spaces"
]
},
"interface-name": false,
"max-classes-per-file": false,
"max-line-length": [
@ -79,11 +94,60 @@
"no-output-native": true,
"no-output-on-prefix": true,
"no-output-rename": true,
"semicolon": {
"options": [
"always"
]
},
"space-before-function-paren": {
"options": {
"anonymous": "never",
"asyncArrow": "always",
"constructor": "never",
"method": "never",
"named": "never"
}
},
"no-outputs-metadata-property": true,
"template-banana-in-box": true,
"template-no-negated-async": true,
"typedef-whitespace": {
"options": [
{
"call-signature": "nospace",
"index-signature": "nospace",
"parameter": "nospace",
"property-declaration": "nospace",
"variable-declaration": "nospace"
},
{
"call-signature": "onespace",
"index-signature": "onespace",
"parameter": "onespace",
"property-declaration": "onespace",
"variable-declaration": "onespace"
}
]
},
"use-lifecycle-interface": true,
"use-pipe-transform-interface": true
"use-pipe-transform-interface": true,
"variable-name": {
"options": [
"ban-keywords",
"check-format",
"allow-pascal-case"
]
},
"whitespace": {
"options": [
"check-branch",
"check-decl",
"check-operator",
"check-separator",
"check-type",
"check-typecast"
]
}
},
"rulesDirectory": [
"codelyzer"