Merge pull request #2 from sartography/bug/layout-problems
Bug/layout problems
This commit is contained in:
commit
95fe8e413d
|
@ -29,6 +29,11 @@
|
|||
],
|
||||
"styles": [
|
||||
"src/styles.scss"
|
||||
],
|
||||
"allowedCommonJsDependencies": [
|
||||
"bwip-js",
|
||||
"qrcode",
|
||||
"serialize-javascript"
|
||||
]
|
||||
},
|
||||
"configurations": {
|
||||
|
|
|
@ -10,9 +10,10 @@ describe('COVID19 Testing Kiosk App', () => {
|
|||
http = new HttpClient('http://localhost:5001');
|
||||
});
|
||||
|
||||
it('should automatically sign-in and redirect to home screen', () => {
|
||||
it('should automatically sign-in and redirect to sample input screen', () => {
|
||||
page.navigateTo();
|
||||
expect(page.getRoute()).toEqual('/');
|
||||
|
||||
});
|
||||
|
||||
it('should navigate to settings screen', () => {
|
||||
|
@ -23,15 +24,7 @@ describe('COVID19 Testing Kiosk App', () => {
|
|||
page.clickAndExpectRoute('#nav_home', '/');
|
||||
});
|
||||
|
||||
it('should navigate to occupancy count input screen', () => {
|
||||
page.clickAndExpectRoute('#nav_count', '/count');
|
||||
});
|
||||
|
||||
it('should navigate back to home screen', () => {
|
||||
it('should navigate back to sample input screen', () => {
|
||||
page.clickAndExpectRoute('#nav_home', '/');
|
||||
});
|
||||
|
||||
it('should navigate to sample input screen', () => {
|
||||
page.clickAndExpectRoute('#nav_sample', '/sample');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -2959,6 +2959,11 @@
|
|||
"integrity": "sha1-y5T662HIaWRR2zZTThQi+U8K7og=",
|
||||
"dev": true
|
||||
},
|
||||
"bwip-js": {
|
||||
"version": "2.0.11",
|
||||
"resolved": "https://registry.npmjs.org/bwip-js/-/bwip-js-2.0.11.tgz",
|
||||
"integrity": "sha512-8hAwUM+5FrGguKRaFi2QxKF9SxQM4fAfTy5TVAkeBODH6Eq3Q8MMEoBN+ymIBxiGKL+wNHB1Z1pv5f9oWGLUKQ=="
|
||||
},
|
||||
"bytes": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
"@angular/router": "~11.0.0-next.2",
|
||||
"@ngx-formly/core": "^5.8.0",
|
||||
"@ngx-formly/material": "^5.8.0",
|
||||
"bwip-js": "^2.0.11",
|
||||
"lodash.isequal": "^4.5.0",
|
||||
"ngx-qrcode-svg": "^2.0.0",
|
||||
"rfdc": "^1.1.4",
|
||||
|
|
|
@ -3,7 +3,7 @@ import {formatDate} from '@angular/common';
|
|||
export const createQrCodeValue = (barCode: string, initials: string, dateCreated: Date, locationId: string): string => {
|
||||
const valArray = [
|
||||
barCode,
|
||||
initials,
|
||||
initials.toUpperCase(),
|
||||
formatDate(dateCreated, 'yyyyMMddHHmm', 'en-us'),
|
||||
locationId,
|
||||
];
|
||||
|
|
|
@ -11,7 +11,7 @@ export const routes: Routes = [
|
|||
{
|
||||
path: '',
|
||||
pathMatch: 'full',
|
||||
component: HomeComponent
|
||||
component: SampleComponent
|
||||
},
|
||||
{
|
||||
path: 'sample',
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<div class="mat-typography">
|
||||
<app-navbar [testingLocation]="testingLocation"></app-navbar>
|
||||
<app-navbar></app-navbar>
|
||||
<router-outlet *ngIf="!loading; else loadingMessage"></router-outlet>
|
||||
<app-footer></app-footer>
|
||||
</div>
|
||||
|
|
|
@ -1,22 +1,15 @@
|
|||
import {Component, Inject} from '@angular/core';
|
||||
import {MatIconRegistry} from '@angular/material/icon';
|
||||
import {DomSanitizer, Title} from '@angular/platform-browser';
|
||||
import {Router} from '@angular/router';
|
||||
import {Title} from '@angular/platform-browser';
|
||||
import {AppEnvironment} from './models/appEnvironment.interface';
|
||||
import {TestingLocation} from './models/testingLocation.interface';
|
||||
import {GoogleAnalyticsService} from './services/google-analytics.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.scss']
|
||||
})
|
||||
|
||||
export class AppComponent {
|
||||
loading: boolean;
|
||||
testingLocation: TestingLocation = {
|
||||
id: '0000',
|
||||
name: 'Click here to set location',
|
||||
};
|
||||
|
||||
constructor(
|
||||
@Inject('APP_ENVIRONMENT') private environment: AppEnvironment,
|
||||
|
@ -25,6 +18,7 @@ export class AppComponent {
|
|||
this.titleService.setTitle(this.environment.title);
|
||||
}
|
||||
|
||||
|
||||
reload() {
|
||||
this.loading = true;
|
||||
setTimeout(() => this.loading = false, 300);
|
||||
|
|
|
@ -18,9 +18,14 @@ import {QRCodeSVGModule} from 'ngx-qrcode-svg';
|
|||
import {ThisEnvironment} from '../environments/environment.injectable';
|
||||
import {AppRoutingModule} from './app-routing.module';
|
||||
import {AppComponent} from './app.component';
|
||||
import {BarcodeSvgDirective} from './barcode-svg/barcode-svg.directive';
|
||||
import {CountComponent} from './count/count.component';
|
||||
import {FooterComponent} from './footer/footer.component';
|
||||
import {HomeComponent} from './home/home.component';
|
||||
import {CircleQRcodeDoubleComponent} from './label-layout/formats/circle-qrcode-double/circle-qrcode-double.component';
|
||||
import {CircleQRcodeSingleComponent} from './label-layout/formats/circle-qrcode-single/circle-qrcode-single.component';
|
||||
import {RectangleCode128Component} from './label-layout/formats/rectangle-code128/rectangle-code128.component';
|
||||
import {RectangleDatamatrixComponent} from './label-layout/formats/rectangle-datamatrix/rectangle-datamatrix.component';
|
||||
import {LabelLayoutComponent} from './label-layout/label-layout.component';
|
||||
import {LoadingComponent} from './loading/loading.component';
|
||||
import {NavbarComponent} from './navbar/navbar.component';
|
||||
|
@ -50,6 +55,9 @@ export function getBaseHref(platformLocation: PlatformLocation): string {
|
|||
@NgModule({
|
||||
declarations: [
|
||||
AppComponent,
|
||||
BarcodeSvgDirective,
|
||||
CircleQRcodeDoubleComponent,
|
||||
CircleQRcodeSingleComponent,
|
||||
CountComponent,
|
||||
FooterComponent,
|
||||
HomeComponent,
|
||||
|
@ -58,6 +66,8 @@ export function getBaseHref(platformLocation: PlatformLocation): string {
|
|||
NavbarComponent,
|
||||
PrintComponent,
|
||||
PrintLayoutComponent,
|
||||
RectangleCode128Component,
|
||||
RectangleDatamatrixComponent,
|
||||
SampleComponent,
|
||||
SettingsComponent,
|
||||
],
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
import { BarcodeSvgDirective } from './barcode-svg.directive';
|
||||
|
||||
describe('BarcodeSvgDirective', () => {
|
||||
it('should create an instance', () => {
|
||||
const directive = new BarcodeSvgDirective(null);
|
||||
expect(directive).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,40 @@
|
|||
import bwipjs from 'bwip-js';
|
||||
import DrawingSVG from './draw-svg.js';
|
||||
import {Directive, ElementRef, Input, OnInit} from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
selector: '[appBarcodeSvg]'
|
||||
})
|
||||
export class BarcodeSvgDirective implements OnInit {
|
||||
@Input() format: string;
|
||||
@Input() value: string;
|
||||
@Input() x: number;
|
||||
@Input() y: number;
|
||||
@Input() width: number;
|
||||
@Input() height: number;
|
||||
|
||||
constructor(private barcodeContainer: ElementRef) {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
if (!!(bwipjs && bwipjs.render && this.format)) {
|
||||
const opts: { [key: string]: any } = {
|
||||
bcid: this.format,
|
||||
text: this.value,
|
||||
scale: 1,
|
||||
width: this.width,
|
||||
height: this.height,
|
||||
// includetext: false,
|
||||
// textalign: 'center',
|
||||
// version: '12x64',
|
||||
// padding: this.settings.labelLayout.marginSize,
|
||||
};
|
||||
|
||||
if (this.format === 'qrcode') {
|
||||
opts.eclevel = 'H';
|
||||
}
|
||||
this.barcodeContainer.nativeElement.innerHTML = bwipjs.render(opts, DrawingSVG(opts, bwipjs.FontLib));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,270 @@
|
|||
// bwip-js/examples/drawing-svg.js
|
||||
//
|
||||
// This is an advanced demonstation of using the drawing interface.
|
||||
//
|
||||
// It converts the drawing primitives into the equivalent SVG. Linear barcodes
|
||||
// are rendered as a series of stroked paths. 2D barcodes are rendered as a
|
||||
// series of filled paths.
|
||||
//
|
||||
// Rotation is handled during drawing. The resulting SVG will contain the
|
||||
// already-rotated barcode without an SVG transform.
|
||||
//
|
||||
// If the requested barcode image contains text, the glyph paths are
|
||||
// extracted from the font file (via the builtin FontLib and stb_truetype.js)
|
||||
// and added as filled SVG paths.
|
||||
//
|
||||
// This code can run in the browser and in nodejs.
|
||||
(function (root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
define([], factory);
|
||||
} else if (typeof module === 'object' && module.exports) {
|
||||
module.exports = factory();
|
||||
} else {
|
||||
root.DrawingSVG = factory();
|
||||
}
|
||||
}(typeof self !== 'undefined' ? self : this, function () {
|
||||
"use strict";
|
||||
|
||||
function DrawingSVG(opts, FontLib) {
|
||||
// Unrolled x,y rotate/translate matrix
|
||||
var tx0 = 0, tx1 = 0, tx2 = 0, tx3 = 0;
|
||||
var ty0 = 0, ty1 = 0, ty2 = 0, ty3 = 0;
|
||||
|
||||
var svg = '';
|
||||
var path;
|
||||
var lines = {};
|
||||
|
||||
// Magic number to approximate an ellipse/circle using 4 cubic beziers.
|
||||
var ELLIPSE_MAGIC = 0.55228475 - 0.00045;
|
||||
|
||||
// Global graphics state
|
||||
var gs_width, gs_height; // image size, in pixels
|
||||
var gs_dx, gs_dy; // x,y translate (padding)
|
||||
|
||||
return {
|
||||
// Make no adjustments
|
||||
scale(sx, sy) {
|
||||
},
|
||||
// Measure text. This and scale() are the only drawing primitives that
|
||||
// are called before init().
|
||||
//
|
||||
// `font` is the font name typically OCR-A or OCR-B.
|
||||
// `fwidth` and `fheight` are the requested font cell size. They will
|
||||
// usually be the same, except when the scaling is not symetric.
|
||||
measure(str, font, fwidth, fheight) {
|
||||
fwidth = fwidth|0;
|
||||
fheight = fheight|0;
|
||||
|
||||
var fontid = FontLib.lookup(font);
|
||||
var width = 0;
|
||||
var ascent = 0;
|
||||
var descent = 0;
|
||||
for (var i = 0; i < str.length; i++) {
|
||||
var ch = str.charCodeAt(i);
|
||||
var glyph = FontLib.getpaths(fontid, ch, fwidth, fheight);
|
||||
if (!glyph) {
|
||||
continue;
|
||||
}
|
||||
ascent = Math.max(ascent, glyph.ascent);
|
||||
descent = Math.max(descent, -glyph.descent);
|
||||
width += glyph.advance;
|
||||
}
|
||||
return { width, ascent, descent };
|
||||
},
|
||||
|
||||
// width and height represent the maximum bounding box the graphics will occupy.
|
||||
// The dimensions are for an unrotated rendering. Adjust as necessary.
|
||||
init(width, height) {
|
||||
// Add in the effects of padding. These are always set before the
|
||||
// drawing constructor is called.
|
||||
var padl = opts.paddingleft || 0;
|
||||
var padr = opts.paddingright || 0;
|
||||
var padt = opts.paddingtop || 0;
|
||||
var padb = opts.paddingbottom || 0;
|
||||
var rot = opts.rotate || 'N';
|
||||
|
||||
width += padl + padr;
|
||||
height += padt + padb;
|
||||
|
||||
// Transform indexes are: x, y, w, h
|
||||
switch (rot) {
|
||||
// tx = w-y, ty = x
|
||||
case 'R': tx1 = -1; tx2 = 1; ty0 = 1; break;
|
||||
// tx = w-x, ty = h-y
|
||||
case 'I': tx0 = -1; tx2 = 1; ty1 = -1; ty3 = 1; break;
|
||||
// tx = y, ty = h-x
|
||||
case 'L': tx1 = 1; ty0 = -1; ty3 = 1; break;
|
||||
// tx = x, ty = y
|
||||
default: tx0 = ty1 = 1; break;
|
||||
}
|
||||
|
||||
// Setup the graphics state
|
||||
var swap = rot == 'L' || rot == 'R';
|
||||
gs_width = swap ? height : width;
|
||||
gs_height = swap ? width : height;
|
||||
gs_dx = padl;
|
||||
gs_dy = padt;
|
||||
|
||||
svg = '';
|
||||
},
|
||||
// Unconnected stroked lines are used to draw the bars in linear barcodes.
|
||||
// No line cap should be applied. These lines are always orthogonal.
|
||||
line(x0, y0, x1, y1, lw, rgb) {
|
||||
// Try to get non-blurry lines...
|
||||
x0 = x0|0;
|
||||
y0 = y0|0;
|
||||
x1 = x1|0;
|
||||
y1 = y1|0;
|
||||
lw = Math.round(lw);
|
||||
|
||||
// Try to keep the lines "crisp" by using with the SVG line drawing spec to
|
||||
// our advantage.
|
||||
if (lw & 1) {
|
||||
if (x0 === x1) {
|
||||
x0 += 0.5;
|
||||
x1 += 0.5;
|
||||
}
|
||||
if (y0 === y1) {
|
||||
y0 += 0.5;
|
||||
y1 += 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
// Group together all lines of the same width and emit as single paths.
|
||||
// Dramatically reduces resulting text size.
|
||||
var key = '' + lw + '#' + rgb;
|
||||
if (!lines[key]) {
|
||||
lines[key] = '<path stroke="#' + rgb + '" stroke-width="' + lw + '" d="';
|
||||
}
|
||||
lines[key] += 'M' + transform(x0, y0) + 'L' + transform(x1, y1);
|
||||
},
|
||||
// Polygons are used to draw the connected regions in a 2d barcode.
|
||||
// These will always be unstroked, filled, non-intersecting,
|
||||
// orthogonal shapes.
|
||||
// You will see a series of polygon() calls, followed by a fill().
|
||||
polygon(pts) {
|
||||
if (!path) {
|
||||
path = '<path d="';
|
||||
}
|
||||
path += 'M' + transform(pts[0][0], pts[0][1]);
|
||||
for (var i = 1, n = pts.length; i < n; i++) {
|
||||
var p = pts[i];
|
||||
path += 'L' + transform(p[0], p[1]);
|
||||
}
|
||||
path += 'Z';
|
||||
},
|
||||
// An unstroked, filled hexagon used by maxicode. You can choose to fill
|
||||
// each individually, or wait for the final fill().
|
||||
//
|
||||
// The hexagon is drawn from the top, counter-clockwise.
|
||||
hexagon(pts, rgb) {
|
||||
this.polygon(pts); // A hexagon is just a polygon...
|
||||
},
|
||||
// An unstroked, filled ellipse. Used by dotcode and maxicode at present.
|
||||
// maxicode issues pairs of ellipse calls (one cw, one ccw) followed by a fill()
|
||||
// to create the bullseye rings. dotcode issues all of its ellipses then a
|
||||
// fill().
|
||||
ellipse(x, y, rx, ry, ccw) {
|
||||
if (!path) {
|
||||
path = '<path d="';
|
||||
}
|
||||
var dx = rx * ELLIPSE_MAGIC;
|
||||
var dy = ry * ELLIPSE_MAGIC;
|
||||
|
||||
// Since we fill with even-odd, don't worry about cw/ccw
|
||||
path += 'M' + transform(x - rx, y) +
|
||||
'C' + transform(x - rx, y - dy) + ' ' +
|
||||
transform(x - dx, y - ry) + ' ' +
|
||||
transform(x, y - ry) +
|
||||
'C' + transform(x + dx, y - ry) + ' ' +
|
||||
transform(x + rx, y - dy) + ' ' +
|
||||
transform(x + rx, y) +
|
||||
'C' + transform(x + rx, y + dy) + ' ' +
|
||||
transform(x + dx, y + ry) + ' ' +
|
||||
transform(x, y + ry) +
|
||||
'C' + transform(x - dx, y + ry) + ' ' +
|
||||
transform(x - rx, y + dy) + ' ' +
|
||||
transform(x - rx, y) +
|
||||
'Z';
|
||||
},
|
||||
// PostScript's default fill rule is even-odd.
|
||||
fill(rgb) {
|
||||
if (path) {
|
||||
svg += path + '" fill="#' + rgb + '" fill-rule="evenodd" />\n';
|
||||
path = null;
|
||||
}
|
||||
},
|
||||
// Draw text with optional inter-character spacing. `y` is the baseline.
|
||||
// font is an object with properties { name, width, height, dx }
|
||||
// width and height are the font cell size.
|
||||
// dx is extra space requested between characters (usually zero).
|
||||
text(x, y, str, rgb, font) {
|
||||
var fontid = FontLib.lookup(font.name);
|
||||
var fwidth = font.width|0;
|
||||
var fheight = font.height|0;
|
||||
var dx = font.dx|0;
|
||||
var path = '';
|
||||
for (var k = 0; k < str.length; k++) {
|
||||
var ch = str.charCodeAt(k);
|
||||
var glyph = FontLib.getpaths(fontid, ch, fwidth, fheight);
|
||||
if (!glyph) {
|
||||
continue;
|
||||
}
|
||||
if (glyph.length) {
|
||||
// A glyph is composed of sequence of curve and line segments.
|
||||
// M is move-to
|
||||
// L is line-to
|
||||
// Q is quadratic bezier curve-to
|
||||
// C is cubic bezier curve-to
|
||||
for (var i = 0, l = glyph.length; i < l; i++) {
|
||||
let seg = glyph[i];
|
||||
if (seg.type == 'M' || seg.type == 'L') {
|
||||
path += seg.type + transform(seg.x + x, y - seg.y);
|
||||
} else if (seg.type == 'Q') {
|
||||
path += seg.type + transform(seg.cx + x, y - seg.cy) + ' ' +
|
||||
transform(seg.x + x, y - seg.y);
|
||||
} else if (seg.type == 'C') {
|
||||
path += seg.type + transform(seg.cx1 + x, y - seg.cy1) + ' ' +
|
||||
transform(seg.cx2 + x, y - seg.cy2) + ' ' +
|
||||
transform(seg.x + x, y - seg.y);
|
||||
}
|
||||
}
|
||||
// Close the shape
|
||||
path += 'Z';
|
||||
}
|
||||
x += glyph.advance + dx;
|
||||
}
|
||||
if (path) {
|
||||
svg += '<path d="' + path + '" fill="#' + rgb + '" />\n';
|
||||
}
|
||||
},
|
||||
// Called after all drawing is complete. The return value from this method
|
||||
// is the return value from `bwipjs.render()`.
|
||||
end() {
|
||||
var linesvg = '';
|
||||
for (var key in lines) {
|
||||
linesvg += lines[key] + '" />\n';
|
||||
}
|
||||
var bg = opts.backgroundcolor;
|
||||
return '<svg version="1.1" width="' + gs_width + '" height="' + gs_height +
|
||||
'" xmlns="http://www.w3.org/2000/svg">\n' +
|
||||
(/^[0-9A-Fa-f]{6}$/.test(''+bg)
|
||||
? '<rect width="100%" height="100%" fill="#' + bg + '" />\n'
|
||||
: '') +
|
||||
linesvg + svg + '</svg>\n';
|
||||
},
|
||||
};
|
||||
|
||||
// translate/rotate and return as an SVG coordinate pair
|
||||
function transform(x, y) {
|
||||
x += gs_dx;
|
||||
y += gs_dy;
|
||||
var tx = tx0 * x + tx1 * y + tx2 * (gs_width-1) + tx3 * (gs_height-1);
|
||||
var ty = ty0 * x + ty1 * y + ty2 * (gs_width-1) + ty3 * (gs_height-1);
|
||||
return '' + ((tx|0) === tx ? tx : tx.toFixed(2)) + ' ' +
|
||||
((ty|0) === ty ? ty : ty.toFixed(2));
|
||||
}
|
||||
}
|
||||
|
||||
return DrawingSVG;
|
||||
}));
|
|
@ -2,34 +2,52 @@ import {AppDefaultsOptions} from '../models/appDefaults.interface';
|
|||
import {LabelLayout} from '../models/labelLayout.interface';
|
||||
|
||||
export const labelLayouts = {
|
||||
round_32mm_1up: new LabelLayout({
|
||||
name: '32mm Round Label - 1up',
|
||||
type: 'round_32mm_1up',
|
||||
numCols: 1,
|
||||
columnGap: 0,
|
||||
circle_qrcode_single: new LabelLayout({
|
||||
name: '32mm Round Label - QR Code (1up)',
|
||||
barcodeType: 'qrcode',
|
||||
id: 'circle_qrcode_single',
|
||||
pageWidth: 32,
|
||||
pageHeight: 32,
|
||||
}),
|
||||
round_32mm_2up: new LabelLayout({
|
||||
name: '32mm Round Label - 2up',
|
||||
type: 'round_32mm_2up',
|
||||
numCols: 2,
|
||||
columnGap: 3.4,
|
||||
circle_qrcode_double: new LabelLayout({
|
||||
name: '32mm Round Label - QR Code (2up)',
|
||||
barcodeType: 'qrcode',
|
||||
id: 'circle_qrcode_double',
|
||||
pageWidth: 64,
|
||||
pageHeight: 32,
|
||||
}),
|
||||
rectangle_code128: new LabelLayout({
|
||||
name: '2in x 1.25in Rectangular Label - CODE128',
|
||||
barcodeType: 'code128',
|
||||
id: 'rectangle_code128',
|
||||
pageWidth: 54,
|
||||
pageHeight: 34,
|
||||
}),
|
||||
rectangle_datamatrix: new LabelLayout({
|
||||
name: '2in x 1.25in Rectangular Label - DataMatrix',
|
||||
barcodeType: 'datamatrix',
|
||||
id: 'rectangle_datamatrix',
|
||||
pageWidth: 54,
|
||||
pageHeight: 34,
|
||||
}),
|
||||
};
|
||||
|
||||
export const defaultOptions: AppDefaultsOptions = {
|
||||
barCodeNumLength: 9, // Number of digits in Bar Code.
|
||||
barCodeRegExp: /^[\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.
|
||||
countsCollection: 'counts', // Name of collection for Line Counts in Firebase.
|
||||
dateDisplayFormat: 'MM/dd/yyyy, hh:mm aa', // Format for dates when displayed to user.
|
||||
dateEncodedFormat: 'yyyyMMddHHmm', // Format for dates when encoded in IDs for database records.
|
||||
barCodeNumLength: 9, // Number of digits in Bar Code.
|
||||
barCodeRegExp: /^[\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.
|
||||
countsCollection: 'counts', // Name of collection for Line Counts in Firebase.
|
||||
dateDisplayFormat: 'MM/dd/yyyy, hh:mm aa', // Format for dates when displayed to user.
|
||||
dateEncodedFormat: 'yyyyMMddHHmm', // Format for dates when encoded in IDs for database records.
|
||||
initialsLength: 5,
|
||||
initialsRegExp: /^[a-zA-Z]{2,5}$/,
|
||||
labelLayout: labelLayouts.round_32mm_1up, // Which label layout to use for printing. Can be overridden by user setting.
|
||||
lineCountRegExp: /^[\d]{4}-[\d]{12}$/, // ID format for Line Count records.
|
||||
locationId: '0000', // Default location ID. Can be overridden by user setting.
|
||||
locationIdRegExp: /^[\d]{4}$/, // ID format for Line Count records.
|
||||
numCopies: 1, // Default number of copies of labels to print. Can be overridden by user setting.
|
||||
qrCodeRegExp: /^[\d]{9}-[a-zA-Z]+-[\d]{12}-[\d]{4}$/, // ID format for QR Code records.
|
||||
samplesCollection: 'samples', // Name of collection for Line Counts in Firebase.
|
||||
labelLayout: labelLayouts.circle_qrcode_single, // Which label layout to use for printing. Can be overridden by user setting.
|
||||
lineCountRegExp: /^[\d]{4}-[\d]{12}$/, // ID format for Line Count records.
|
||||
locationId: '0000', // Default location ID. Can be overridden by user setting.
|
||||
locationIdRegExp: /^[\d]{4}$/, // ID format for Line Count records.
|
||||
numCopies: 1, // Default number of copies of labels to print.
|
||||
// Can be overridden by user setting.
|
||||
qrCodeRegExp: /^[\d]{9}-[a-zA-Z]+-[\d]{12}-[\d]{4}$/, // ID format for QR Code records.
|
||||
samplesCollection: 'samples', // Name of collection for Line Counts in Firebase.
|
||||
};
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import {mockSample} from '../../../testing/sample.mock';
|
||||
|
||||
import { CircleQRcodeDoubleComponent } from './circle-qrcode-double.component';
|
||||
|
||||
describe('CircleQRcodeDoubleComponent', () => {
|
||||
let component: CircleQRcodeDoubleComponent;
|
||||
let fixture: ComponentFixture<CircleQRcodeDoubleComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ CircleQRcodeDoubleComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(CircleQRcodeDoubleComponent);
|
||||
component = fixture.componentInstance;
|
||||
component.sample = mockSample;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,52 @@
|
|||
<svg
|
||||
class="label-layout-format"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="64mm"
|
||||
height="32mm"
|
||||
viewBox="0 0 64 32"
|
||||
>
|
||||
<defs>
|
||||
<g id="circle_qrcode_single">
|
||||
<circle
|
||||
class="label-layout-border"
|
||||
cx="16"
|
||||
cy="16"
|
||||
fill="#FFFFFF"
|
||||
stroke="#CCCCCC"
|
||||
stroke-width="0.2"
|
||||
r="14.3"/>
|
||||
<g transform="translate(7, 6.8)">
|
||||
<g
|
||||
appBarcodeSvg
|
||||
[format]="'qrcode'"
|
||||
[value]="sample.barcode"
|
||||
[width]="18"
|
||||
[height]="18"
|
||||
transform="scale(0.35)"
|
||||
/>
|
||||
</g>
|
||||
<g transform="translate(4.2, 14)">
|
||||
<text x="0" y="0">T</text>
|
||||
<text x="0" y="2">{{sample.date | date: 'HH'}}</text>
|
||||
<text x="0" y="4">{{sample.date | date: 'mm'}}</text>
|
||||
</g>
|
||||
<g transform="translate(27.4, 14)">
|
||||
<text x="0" y="0">L</text>
|
||||
<text x="0" y="2">{{sample.location.slice(0,2)}}</text>
|
||||
<text x="0" y="4">{{sample.location.slice(2,4)}}</text>
|
||||
</g>
|
||||
</g>
|
||||
</defs>
|
||||
<use xlink:href="#circle_qrcode_single" x="0" y="0"></use>
|
||||
<use xlink:href="#circle_qrcode_single" x="32" y="0"></use>
|
||||
<g>
|
||||
<text x="25%" y="6">#{{sample.student_id}}</text>
|
||||
<text x="25%" y="27.6">{{sample.date | date:'yyMMdd'}}{{sample.initials}}</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="75%" y="6">#{{sample.student_id}}</text>
|
||||
<text x="75%" y="27.6">{{sample.date | date:'yyMMdd'}}{{sample.initials}}</text>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
|
@ -0,0 +1,23 @@
|
|||
import {Component, Input, OnInit} from '@angular/core';
|
||||
import {AppDefaults} from '../../../models/appDefaults.interface';
|
||||
import {Sample} from '../../../models/sample.interface';
|
||||
|
||||
@Component({
|
||||
selector: 'app-circle-qrcode-double',
|
||||
templateUrl: './circle-qrcode-double.component.svg',
|
||||
styleUrls: ['./circle-qrcode-double.component.scss']
|
||||
})
|
||||
export class CircleQRcodeDoubleComponent implements OnInit {
|
||||
@Input() sample: Sample;
|
||||
@Input() settings: AppDefaults;
|
||||
@Input() x: number;
|
||||
@Input() y: number;
|
||||
@Input() width: number;
|
||||
@Input() height: number;
|
||||
|
||||
constructor() { }
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import {mockSample} from '../../../testing/sample.mock';
|
||||
|
||||
import { CircleQRcodeSingleComponent } from './circle-qrcode-single.component';
|
||||
|
||||
describe('CircleQRcodeSingleComponent', () => {
|
||||
let component: CircleQRcodeSingleComponent;
|
||||
let fixture: ComponentFixture<CircleQRcodeSingleComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ CircleQRcodeSingleComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(CircleQRcodeSingleComponent);
|
||||
component = fixture.componentInstance;
|
||||
component.sample = mockSample;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,40 @@
|
|||
<svg
|
||||
class="label-layout-format"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="32mm"
|
||||
height="32mm"
|
||||
viewBox="0 0 32 32"
|
||||
>
|
||||
<circle
|
||||
class="label-layout-border"
|
||||
cx="16"
|
||||
cy="16"
|
||||
fill="#FFFFFF"
|
||||
stroke="#CCCCCC"
|
||||
stroke-width="0.2"
|
||||
r="14.3"/>
|
||||
<g transform="translate(7, 6.8)">
|
||||
<g
|
||||
appBarcodeSvg
|
||||
[format]="'qrcode'"
|
||||
[value]="sample.barcode"
|
||||
[width]="18"
|
||||
[height]="18"
|
||||
transform="scale(0.35)"
|
||||
/>
|
||||
</g>
|
||||
<text x="50%" y="6">#{{sample.student_id}}</text>
|
||||
<text x="50%" y="27.6">{{sample.date | date:'yyMMdd'}}{{sample.initials}}</text>
|
||||
<g transform="translate(4.2, 14)">
|
||||
<text x="0" y="0">T</text>
|
||||
<text x="0" y="2">{{sample.date | date: 'HH'}}</text>
|
||||
<text x="0" y="4">{{sample.date | date: 'mm'}}</text>
|
||||
</g>
|
||||
<g transform="translate(27.4, 14)">
|
||||
<text x="0" y="0">L</text>
|
||||
<text x="0" y="2">{{sample.location.slice(0,2)}}</text>
|
||||
<text x="0" y="4">{{sample.location.slice(2,4)}}</text>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.0 KiB |
|
@ -0,0 +1,27 @@
|
|||
import {ChangeDetectorRef, Component, Input, OnInit} from '@angular/core';
|
||||
import {AppDefaults} from '../../../models/appDefaults.interface';
|
||||
import {Sample} from '../../../models/sample.interface';
|
||||
|
||||
@Component({
|
||||
selector: 'app-circle-qrcode-single',
|
||||
templateUrl: './circle-qrcode-single.component.svg',
|
||||
styleUrls: ['./circle-qrcode-single.component.scss']
|
||||
})
|
||||
export class CircleQRcodeSingleComponent implements OnInit {
|
||||
@Input() sample: Sample;
|
||||
@Input() settings: AppDefaults;
|
||||
@Input() x: number;
|
||||
@Input() y: number;
|
||||
@Input() width: number;
|
||||
@Input() height: number;
|
||||
|
||||
constructor(private changeDetector: ChangeDetectorRef) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
if (this.sample) {
|
||||
this.changeDetector.detectChanges();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import {mockSample} from '../../../testing/sample.mock';
|
||||
|
||||
import { RectangleCode128Component } from './rectangle-code128.component';
|
||||
|
||||
describe('RectangleCode128Component', () => {
|
||||
let component: RectangleCode128Component;
|
||||
let fixture: ComponentFixture<RectangleCode128Component>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ RectangleCode128Component ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(RectangleCode128Component);
|
||||
component = fixture.componentInstance;
|
||||
component.sample = mockSample;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,46 @@
|
|||
<svg
|
||||
class="label-layout-format"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="54mm"
|
||||
height="34mm"
|
||||
viewBox="0 0 54 34"
|
||||
>
|
||||
<defs>
|
||||
<g id="up_arrow">
|
||||
<g transform="translate(2.5, 2)">
|
||||
<g transform="translate(-2.5, -2) scale(0.1)">
|
||||
<path d="M 0,0 H 24 V 24 H 0 Z" style="fill:none"/>
|
||||
<path d="M 20,11 H 7.83 L 13.42,5.41 12,4 4,12 12,20 13.41,18.59 7.83,13 H 20 Z" />
|
||||
</g>
|
||||
<text x="1" y="0">UP</text>
|
||||
</g>
|
||||
</g>
|
||||
</defs>
|
||||
<g transform="translate(1.5, 1)">
|
||||
<rect
|
||||
class="label-layout-border"
|
||||
fill="#FFFFFF"
|
||||
stroke="#CCCCCC"
|
||||
stroke-width="0.2"
|
||||
width="51"
|
||||
height="32"
|
||||
x="0"
|
||||
y="0"
|
||||
rx="2"
|
||||
/>
|
||||
<use xlink:href="#up_arrow" x="2" y="2" />
|
||||
<g transform="translate(2, 11)">
|
||||
<g
|
||||
appBarcodeSvg
|
||||
[format]="'code128'"
|
||||
[height]="10"
|
||||
[value]="sample.barcode"
|
||||
[width]="47"
|
||||
transform="scale(0.345)"/>
|
||||
</g>
|
||||
<use xlink:href="#up_arrow" x="2" y="27.5" />
|
||||
</g>
|
||||
<text x="50%" y="75%">{{sample.barcode}}</text>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
|
@ -0,0 +1,25 @@
|
|||
import {Component, Input, OnInit} from '@angular/core';
|
||||
import {AppDefaults} from '../../../models/appDefaults.interface';
|
||||
import {Sample} from '../../../models/sample.interface';
|
||||
|
||||
@Component({
|
||||
selector: 'app-rectangle-code128',
|
||||
templateUrl: './rectangle-code128.component.svg',
|
||||
styleUrls: ['./rectangle-code128.component.scss']
|
||||
})
|
||||
export class RectangleCode128Component implements OnInit {
|
||||
@Input() sample: Sample;
|
||||
@Input() settings: AppDefaults;
|
||||
@Input() x: number;
|
||||
@Input() y: number;
|
||||
@Input() width: number;
|
||||
@Input() height: number;
|
||||
|
||||
constructor() { }
|
||||
|
||||
ngOnInit(): void {
|
||||
// Replace "#barcode" element with svg of barcode
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import {mockSample} from '../../../testing/sample.mock';
|
||||
|
||||
import { RectangleDatamatrixComponent } from './rectangle-datamatrix.component';
|
||||
|
||||
describe('RectangleDatamatrixComponent', () => {
|
||||
let component: RectangleDatamatrixComponent;
|
||||
let fixture: ComponentFixture<RectangleDatamatrixComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ RectangleDatamatrixComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(RectangleDatamatrixComponent);
|
||||
component = fixture.componentInstance;
|
||||
component.sample = mockSample;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,56 @@
|
|||
<svg
|
||||
class="label-layout-format"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="54mm"
|
||||
height="34mm"
|
||||
viewBox="0 0 54 34"
|
||||
>
|
||||
<defs>
|
||||
<g id="up_arrow">
|
||||
<g transform="translate(2.5, 2)">
|
||||
<g transform="translate(-2.5, -2) scale(0.1)">
|
||||
<path d="M 0,0 H 24 V 24 H 0 Z" style="fill:none"/>
|
||||
<path d="M 20,11 H 7.83 L 13.42,5.41 12,4 4,12 12,20 13.41,18.59 7.83,13 H 20 Z" />
|
||||
</g>
|
||||
<text x="1" y="0">UP</text>
|
||||
</g>
|
||||
</g>
|
||||
</defs>
|
||||
|
||||
<g transform="translate(1.5, 1)">
|
||||
<rect
|
||||
class="label-layout-border"
|
||||
fill="#FFFFFF"
|
||||
stroke="#CCCCCC"
|
||||
stroke-width="0.2"
|
||||
width="51"
|
||||
height="32"
|
||||
x="0"
|
||||
y="0"
|
||||
rx="2"
|
||||
/>
|
||||
<use xlink:href="#up_arrow" x="2" y="2" />
|
||||
<g
|
||||
appBarcodeSvg
|
||||
format="datamatrix"
|
||||
[value]="sample.barcode"
|
||||
[width]="3.5"
|
||||
[height]="3.5"
|
||||
transform="translate(3.5, 10)"
|
||||
/>
|
||||
<g transform="translate(18, 14)">
|
||||
<text x="0" y="0" style="text-anchor: start; text-align: left;">
|
||||
#{{sample.student_id}}
|
||||
</text>
|
||||
<text x="0" y="3" style="text-anchor: start; text-align: left;">
|
||||
{{sample.date | date:'yyyy-MM-dd HH:mm'}}
|
||||
</text>
|
||||
<text x="0" y="6" style="text-anchor: start; text-align: left;">
|
||||
{{sample.initials}} {{sample.location}}
|
||||
</text>
|
||||
</g>
|
||||
<use xlink:href="#up_arrow" x="2" y="27.5" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
|
@ -0,0 +1,23 @@
|
|||
import {Component, Input, OnInit} from '@angular/core';
|
||||
import {AppDefaults} from '../../../models/appDefaults.interface';
|
||||
import {Sample} from '../../../models/sample.interface';
|
||||
|
||||
@Component({
|
||||
selector: 'app-rectangle-datamatrix',
|
||||
templateUrl: './rectangle-datamatrix.component.svg',
|
||||
styleUrls: ['./rectangle-datamatrix.component.scss']
|
||||
})
|
||||
export class RectangleDatamatrixComponent implements OnInit {
|
||||
@Input() sample: Sample;
|
||||
@Input() settings: AppDefaults;
|
||||
@Input() x: number;
|
||||
@Input() y: number;
|
||||
@Input() width: number;
|
||||
@Input() height: number;
|
||||
|
||||
constructor() { }
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
}
|
|
@ -1,18 +1,4 @@
|
|||
<div class="label-layout">
|
||||
<div class="date">{{dateCreated | date:'yyMMdd'}}{{initials}}</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 />
|
||||
{{settings.locationId.slice(0, 2)}}<br />
|
||||
{{settings.locationId.slice(2, 4)}}
|
||||
</div>
|
||||
<div class="barcode">#{{barCode}}</div>
|
||||
</div>
|
||||
<app-circle-qrcode-single *ngIf="isLayout('circle_qrcode_single')" [sample]="sample"></app-circle-qrcode-single>
|
||||
<app-circle-qrcode-double *ngIf="isLayout('circle_qrcode_double')" [sample]="sample"></app-circle-qrcode-double>
|
||||
<app-rectangle-code128 *ngIf="isLayout('rectangle_code128')" [sample]="sample"></app-rectangle-code128>
|
||||
<app-rectangle-datamatrix *ngIf="isLayout('rectangle_datamatrix')" [sample]="sample"></app-rectangle-datamatrix>
|
||||
|
|
|
@ -1,51 +1,13 @@
|
|||
.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;
|
||||
}
|
||||
::ng-deep app-circle-qrcode-single,
|
||||
::ng-deep app-circle-qrcode-double,
|
||||
::ng-deep app-rectangle-code128,
|
||||
::ng-deep app-rectangle-datamatrix {
|
||||
background-color: transparent;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
overflow: hidden !important;
|
||||
line-height: 0;
|
||||
width: inherit;
|
||||
height: inherit;
|
||||
display: block;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import {Component, Input, OnInit} from '@angular/core';
|
||||
import {createQrCodeValue} from '../_util/qrCode';
|
||||
import {AppDefaults} from '../models/appDefaults.interface';
|
||||
import {CssStyle} from '../models/cssStyle.interface';
|
||||
import {Sample} from '../models/sample.interface';
|
||||
import {SettingsService} from '../services/settings.service';
|
||||
|
||||
@Component({
|
||||
|
@ -8,26 +10,36 @@ import {SettingsService} from '../services/settings.service';
|
|||
templateUrl: './label-layout.component.html',
|
||||
styleUrls: ['./label-layout.component.scss']
|
||||
})
|
||||
export class LabelLayoutComponent implements OnInit {
|
||||
export class LabelLayoutComponent {
|
||||
@Input() dateCreated: Date;
|
||||
@Input() barCode: string;
|
||||
@Input() initials: string;
|
||||
settings: AppDefaults;
|
||||
sample: Sample;
|
||||
barcodeValue: string;
|
||||
|
||||
constructor(private settingsService: SettingsService) {
|
||||
this.settings = this.settingsService.getSettings();
|
||||
}
|
||||
|
||||
get qrCodeValue(): string {
|
||||
return createQrCodeValue(
|
||||
this.barCode,
|
||||
this.initials,
|
||||
this.dateCreated,
|
||||
this.settings.locationId
|
||||
this.sample = {
|
||||
barcode: '',
|
||||
student_id: '123456789',
|
||||
initials: 'ABCDE',
|
||||
date: new Date(),
|
||||
location: this.settings.locationId,
|
||||
};
|
||||
|
||||
this.barcodeValue = createQrCodeValue(
|
||||
this.sample.student_id,
|
||||
this.sample.initials,
|
||||
this.sample.date,
|
||||
this.sample.location
|
||||
);
|
||||
|
||||
this.sample.barcode = this.barcodeValue;
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
isLayout(layoutId: string) {
|
||||
return this.settings.labelLayout.id === layoutId;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
export interface CssStyle { [klass: string]: any; }
|
|
@ -1,78 +1,42 @@
|
|||
export interface LayoutOptions {
|
||||
type?: string;
|
||||
barcodeType?: string;
|
||||
id?: string;
|
||||
name?: string;
|
||||
units?: string;
|
||||
pointsPerUnit?: number;
|
||||
labelSize?: number;
|
||||
marginSize?: number;
|
||||
pageHeight?: number;
|
||||
pageWidth?: number;
|
||||
numCols?: number;
|
||||
columnGap?: number;
|
||||
sideTextWidth?: number;
|
||||
sideTextTop?: number;
|
||||
sideTextMargin?: number;
|
||||
topTextMargin?: number;
|
||||
bottomTextMargin?: number;
|
||||
fontSizePt?: number;
|
||||
numCopies?: number;
|
||||
}
|
||||
|
||||
export class LabelLayout {
|
||||
type = 'round_32mm_1up';
|
||||
name = '32mm Round Label - 1up';
|
||||
name = '32mm Round Label - QR Code (1up)';
|
||||
barcodeType = 'qrcode';
|
||||
id = 'circle_qrcode_double';
|
||||
units = 'mm';
|
||||
pointsPerUnit = 0.3528;
|
||||
labelSize = 28.6;
|
||||
marginSize = 1.7;
|
||||
pageHeight = 32;
|
||||
pageWidth = 32;
|
||||
numCols = 1;
|
||||
columnGap = 4;
|
||||
sideTextWidth = 4;
|
||||
sideTextTop = 11;
|
||||
sideTextMargin = 1.5;
|
||||
topTextMargin = 3;
|
||||
bottomTextMargin = 2.5;
|
||||
fontSizePt = 6;
|
||||
numCopies = 1;
|
||||
|
||||
constructor(private options: LayoutOptions) {
|
||||
const keys = Object.keys(options);
|
||||
keys.forEach(k => {
|
||||
this[k] = options[k];
|
||||
});
|
||||
if (options) {
|
||||
const keys = Object.keys(options);
|
||||
keys.forEach(k => {
|
||||
this[k] = options[k];
|
||||
});
|
||||
} else {
|
||||
console.error('options is empty:', options);
|
||||
}
|
||||
}
|
||||
|
||||
get dimensions() {
|
||||
return {
|
||||
bottomTextMargin: this._toUnits(this.bottomTextMargin),
|
||||
columnGap: this._toUnits(this.columnGap),
|
||||
fontSize: this._toUnits(this.fontSize),
|
||||
labelSize: this._toUnits(this.labelSize),
|
||||
labelSizeWithMargins: this._toUnits(this.labelSizeWithMargins),
|
||||
marginWidth: this._toUnits(this.marginSize),
|
||||
pageHeight: this._toUnits(this.pageHeight),
|
||||
pageWidth: this._toUnits(this.pageWidth),
|
||||
sideTextMargin: this._toUnits(this.sideTextMargin),
|
||||
sideTextTop: this._toUnits(this.sideTextTop),
|
||||
sideTextWidth: this._toUnits(this.sideTextWidth),
|
||||
topTextMargin: this._toUnits(this.topTextMargin),
|
||||
pageHeight: this._toUnits(this.pageHeight),
|
||||
};
|
||||
}
|
||||
|
||||
get labelSizeWithMargins(): number {
|
||||
return (this.labelSize + (this.marginSize * 2));
|
||||
}
|
||||
|
||||
get pageWidth(): number {
|
||||
return (this.labelSizeWithMargins * this.numCols);
|
||||
}
|
||||
|
||||
get pageHeight(): number {
|
||||
return (this.labelSizeWithMargins * this.numCopies);
|
||||
}
|
||||
|
||||
get fontSize(): number {
|
||||
return this.fontSizePt * this.pointsPerUnit;
|
||||
}
|
||||
|
||||
private _toUnits(num: number) {
|
||||
return `${num}${this.units}`;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
export interface Sample {
|
||||
barcode: string;
|
||||
student_id: string;
|
||||
initials?: string;
|
||||
date: Date;
|
||||
location: string;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {Component, Input, OnInit} from '@angular/core';
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {AppDefaults} from '../models/appDefaults.interface';
|
||||
import {SettingsService} from '../services/settings.service';
|
||||
import {TestingLocation} from '../models/testingLocation.interface';
|
||||
|
||||
@Component({
|
||||
selector: 'app-navbar',
|
||||
|
@ -8,16 +8,20 @@ import {TestingLocation} from '../models/testingLocation.interface';
|
|||
styleUrls: ['./navbar.component.scss']
|
||||
})
|
||||
export class NavbarComponent implements OnInit {
|
||||
@Input() testingLocation: TestingLocation;
|
||||
settings: AppDefaults;
|
||||
locationId: string;
|
||||
|
||||
constructor(private settingsService: SettingsService) {
|
||||
this.settingsService.settings.subscribe(settings => {
|
||||
this.settings = settings;
|
||||
this.locationId = !this.hasDefaultId ? this.settings.locationId : '0000';
|
||||
});
|
||||
}
|
||||
|
||||
get hasDefaultId(): boolean {
|
||||
return (!this.settings || this.settings.locationId === '0000');
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
get locationId(): string {
|
||||
const settings = this.settingsService.getSettings();
|
||||
return settings.locationId;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
class="print-layout"
|
||||
[style]="{width: pageWidth, height: pageHeight}"
|
||||
[gdRows]="pagesToGridFractions"
|
||||
[gdGap]="marginWidth"
|
||||
gdAlignColumns="center"
|
||||
gdAlignRows="center"
|
||||
>
|
||||
|
@ -11,12 +10,11 @@
|
|||
[gdColumns]="colsToGridFractions"
|
||||
gdAlignColumns="center"
|
||||
gdAlignRows="center"
|
||||
[gdGap]="columnGap"
|
||||
>
|
||||
<app-label-layout
|
||||
*ngFor="let col of columns"
|
||||
[barCode]="barCode"
|
||||
[initials]="initials"
|
||||
[initials]="initials.toUpperCase()"
|
||||
[dateCreated]="dateCreated"
|
||||
></app-label-layout>
|
||||
</div>
|
||||
|
|
|
@ -16,6 +16,7 @@ describe('PrintLayoutComponent', () => {
|
|||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(PrintLayoutComponent);
|
||||
component = fixture.componentInstance;
|
||||
component.initials = 'abcde';
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
|
|
|
@ -37,14 +37,6 @@ export class PrintLayoutComponent implements AfterViewInit {
|
|||
return this.layout.dimensions.pageWidth;
|
||||
}
|
||||
|
||||
get marginWidth(): string {
|
||||
return this.layout.dimensions.marginWidth;
|
||||
}
|
||||
|
||||
get columnGap(): string {
|
||||
return this.layout.dimensions.columnGap;
|
||||
}
|
||||
|
||||
get pages() {
|
||||
return Array(this.layout.numCopies).fill('');
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
>
|
||||
<app-print-layout
|
||||
[barCode]="barCode"
|
||||
[initials]="initials"
|
||||
[initials]="initials.toUpperCase()"
|
||||
[dateCreated]="dateCreated"
|
||||
></app-print-layout>
|
||||
</mat-card-content>
|
||||
|
@ -26,7 +26,7 @@
|
|||
#saveAndPrintButton="matButton"
|
||||
mat-flat-button
|
||||
class="btn-lg"
|
||||
[color]="isSaved ? '' : 'accent'"
|
||||
color="accent"
|
||||
(click)="saveAndPrint()"
|
||||
autofocus
|
||||
fxFlex="33%"
|
||||
|
@ -37,8 +37,7 @@
|
|||
class="btn-lg"
|
||||
routerLink="/"
|
||||
fxFlex="33%"
|
||||
[color]="isSaved ? 'accent' : ''"
|
||||
><mat-icon>done</mat-icon> Done</button>
|
||||
><mat-icon>cancel</mat-icon> Cancel</button>
|
||||
</mat-card-actions>
|
||||
</mat-card>
|
||||
</div>
|
||||
|
@ -46,7 +45,7 @@
|
|||
<div id="media-print">
|
||||
<app-print-layout
|
||||
[barCode]="barCode"
|
||||
[initials]="initials"
|
||||
[initials]="initials.toUpperCase()"
|
||||
[dateCreated]="dateCreated"
|
||||
></app-print-layout>
|
||||
</div>
|
||||
|
|
|
@ -39,7 +39,11 @@
|
|||
display: none;
|
||||
}
|
||||
|
||||
#media-print { display: block !important; }
|
||||
#media-print {
|
||||
display: block !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
#media-screen { display: none !important; }
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {AfterViewInit, ChangeDetectorRef, Component, OnInit, ViewChild} from '@angular/core';
|
||||
import {MatButton} from '@angular/material/button';
|
||||
import {ActivatedRoute} from '@angular/router';
|
||||
import {ActivatedRoute, Router} from '@angular/router';
|
||||
import {createQrCodeValue} from '../_util/qrCode';
|
||||
import {AppDefaults} from '../models/appDefaults.interface';
|
||||
import {LabelLayout} from '../models/labelLayout.interface';
|
||||
|
@ -26,6 +26,7 @@ export class PrintComponent implements AfterViewInit {
|
|||
constructor(
|
||||
private api: ApiService,
|
||||
private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
private changeDetector: ChangeDetectorRef,
|
||||
private settingsService: SettingsService,
|
||||
private cacheService: CacheService,
|
||||
|
@ -33,13 +34,14 @@ export class PrintComponent implements AfterViewInit {
|
|||
this.dateCreated = new Date();
|
||||
this.route.queryParamMap.subscribe(queryParamMap => {
|
||||
this.barCode = queryParamMap.get('barCode');
|
||||
this.initials = queryParamMap.get('initials');
|
||||
this.initials = queryParamMap.get('initials').toUpperCase();
|
||||
});
|
||||
this.settings = this.settingsService.getSettings();
|
||||
this.isSaved = false;
|
||||
|
||||
this.save(s => {
|
||||
this.isSaved = true;
|
||||
this.changeDetector.detectChanges();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -102,8 +104,7 @@ export class PrintComponent implements AfterViewInit {
|
|||
saveAndPrint() {
|
||||
this.save(s => {
|
||||
window.print();
|
||||
this.doneButton.focus();
|
||||
this.changeDetector.detectChanges();
|
||||
this.router.navigate(['/']);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,20 +45,14 @@
|
|||
color="accent"
|
||||
[disabled]="hasErrors"
|
||||
(click)="goPrint()"
|
||||
fxFlex="33%"
|
||||
fxFlex="50%"
|
||||
>Next <mat-icon>navigate_next</mat-icon></button>
|
||||
<button
|
||||
mat-flat-button
|
||||
class="btn-lg"
|
||||
(click)="resetForm()"
|
||||
fxFlex="33%"
|
||||
fxFlex="50%"
|
||||
><mat-icon>restore</mat-icon> Reset</button>
|
||||
<button
|
||||
mat-flat-button
|
||||
class="btn-lg"
|
||||
routerLink="/"
|
||||
fxFlex="33%"
|
||||
><mat-icon>cancel</mat-icon> Cancel</button>
|
||||
</mat-card-actions>
|
||||
</mat-card>
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@ export class SampleComponent implements AfterViewInit {
|
|||
get queryParams(): Params {
|
||||
return {
|
||||
barCode: this.barCodeValue.slice(0, 9),
|
||||
initials: this.initialsValue,
|
||||
initials: this.initialsValue.toUpperCase(),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -50,7 +50,7 @@ export class SampleComponent implements AfterViewInit {
|
|||
}
|
||||
|
||||
get initialsValue(): string {
|
||||
return this.initialsFormControl.value;
|
||||
return this.initialsFormControl.value.toUpperCase();
|
||||
}
|
||||
|
||||
get hasBarCode(): boolean {
|
||||
|
@ -58,7 +58,7 @@ export class SampleComponent implements AfterViewInit {
|
|||
}
|
||||
|
||||
get hasInitials(): boolean {
|
||||
return this.settings.initialsRegExp.test(this.initialsValue);
|
||||
return this.settings.initialsRegExp.test(this.initialsValue.toUpperCase());
|
||||
}
|
||||
|
||||
get hasErrors(): boolean {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import {Injectable} from '@angular/core';
|
||||
import createClone from 'rfdc';
|
||||
import {BehaviorSubject} from 'rxjs';
|
||||
import serializeJs from 'serialize-javascript';
|
||||
import {defaultOptions} from '../config/defaults';
|
||||
import {AppDefaults, AppDefaultsOptions} from '../models/appDefaults.interface';
|
||||
|
@ -11,10 +12,15 @@ export class SettingsService {
|
|||
// Default form field and data values
|
||||
defaults: AppDefaults = new AppDefaults(defaultOptions);
|
||||
|
||||
// Observable to subscribe to settings updates
|
||||
settings = new BehaviorSubject<AppDefaults>(this.defaults);
|
||||
|
||||
// Deserializes settings from local storage and returns AppDefaults instance
|
||||
getStoredSettings(): AppDefaults {
|
||||
// tslint:disable-next-line:no-eval
|
||||
return new AppDefaults(eval(`(${localStorage.getItem('settings')})`));
|
||||
const storedSettings = new AppDefaults(eval(`(${localStorage.getItem('settings')})`));
|
||||
this.settings.next(storedSettings);
|
||||
return storedSettings;
|
||||
}
|
||||
|
||||
// Returns true if settings are found in local storage
|
||||
|
@ -40,6 +46,7 @@ export class SettingsService {
|
|||
});
|
||||
|
||||
localStorage.setItem('settings', serializeJs(settings));
|
||||
this.settings.next(settings);
|
||||
return settings;
|
||||
}
|
||||
|
||||
|
|
|
@ -10,13 +10,14 @@
|
|||
</mat-card-header>
|
||||
|
||||
<mat-card-content fxLayout="row wrap" fxLayoutAlign="center center">
|
||||
<mat-form-field class="location-input" fxFlex="95%">
|
||||
<mat-form-field class="location-input" fxFlex="95%" >
|
||||
<mat-label>Location ID #</mat-label>
|
||||
<input
|
||||
#locationIdInput="matInput"
|
||||
matInput
|
||||
type="text"
|
||||
[formControl]="locationIdFormControl"
|
||||
(keyup.enter)="save()"
|
||||
>
|
||||
<mat-error *ngIf="locationIdFormControl.hasError('required')">This field is required.</mat-error>
|
||||
<mat-error *ngIf="locationIdFormControl.hasError('pattern')">Please enter exactly 4 digits.</mat-error>
|
||||
|
@ -27,7 +28,19 @@
|
|||
#labelLayoutSelect="matSelect"
|
||||
[formControl]="labelLayoutFormControl"
|
||||
>
|
||||
<mat-option *ngFor="let layout of labelLayouts" [value]="layout.type">{{layout.name}}</mat-option>
|
||||
<mat-option *ngFor="let layout of labelLayouts" [value]="layout.id" [ngStyle]="{height: 'inherit', width: 'inherit'}">
|
||||
<div fxLayout="row" fxLayoutAlign="center center" fxLayoutGap="40px" (click)="selectLabelLayout(layout)">
|
||||
<div fxFlex="50%" [ngStyle]="{whiteSpace: 'normal', lineHeight: '1.2'}" (click)="selectLabelLayout(layout)">
|
||||
{{layout.name}}
|
||||
</div>
|
||||
<div fxFlex="50%" (click)="selectLabelLayout(layout)">
|
||||
<app-circle-qrcode-single *ngIf="layout.id === 'circle_qrcode_single'" [sample]="fakeSample"></app-circle-qrcode-single>
|
||||
<app-circle-qrcode-double *ngIf="layout.id === 'circle_qrcode_double'" [sample]="fakeSample"></app-circle-qrcode-double>
|
||||
<app-rectangle-code128 *ngIf="layout.id === 'rectangle_code128'" [sample]="fakeSample"></app-rectangle-code128>
|
||||
<app-rectangle-datamatrix *ngIf="layout.id === 'rectangle_datamatrix'" [sample]="fakeSample"></app-rectangle-datamatrix>
|
||||
</div>
|
||||
</div>
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
<mat-error *ngIf="labelLayoutFormControl.hasError('required')">This field is required.</mat-error>
|
||||
</mat-form-field>
|
||||
|
@ -38,6 +51,7 @@
|
|||
matInput
|
||||
type="number"
|
||||
[formControl]="numCopiesFormControl"
|
||||
(keyup.enter)="save()"
|
||||
>
|
||||
<mat-error *ngIf="numCopiesFormControl.hasError('required')">This field is required.</mat-error>
|
||||
<mat-error *ngIf="numCopiesFormControl.hasError('pattern')">Please enter only 2-5 letters.</mat-error>
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
import {Component, OnInit} from '@angular/core';
|
||||
import {AfterViewInit, ChangeDetectorRef, Component, OnInit, ViewChild} from '@angular/core';
|
||||
import {FormControl, Validators} from '@angular/forms';
|
||||
import {MatInput} from '@angular/material/input';
|
||||
import {Router} from '@angular/router';
|
||||
import {createQrCodeValue} from '../_util/qrCode';
|
||||
import {labelLayouts} from '../config/defaults';
|
||||
import {AppDefaults} from '../models/appDefaults.interface';
|
||||
import {LabelLayout} from '../models/labelLayout.interface';
|
||||
import {Sample} from '../models/sample.interface';
|
||||
import {SettingsService} from '../services/settings.service';
|
||||
|
||||
@Component({
|
||||
|
@ -11,23 +14,28 @@ import {SettingsService} from '../services/settings.service';
|
|||
templateUrl: './settings.component.html',
|
||||
styleUrls: ['./settings.component.scss']
|
||||
})
|
||||
export class SettingsComponent implements OnInit {
|
||||
export class SettingsComponent implements AfterViewInit {
|
||||
settings: AppDefaults;
|
||||
numCopiesFormControl: FormControl;
|
||||
labelLayoutFormControl: FormControl;
|
||||
locationIdFormControl: FormControl;
|
||||
labelLayouts: LabelLayout[];
|
||||
fakeSample: Sample;
|
||||
fakeBarcodeValue: string;
|
||||
|
||||
@ViewChild('locationIdInput') locationIdInput: MatInput;
|
||||
|
||||
constructor(
|
||||
private router: Router,
|
||||
private settingsService: SettingsService
|
||||
private settingsService: SettingsService,
|
||||
private changeDetector: ChangeDetectorRef,
|
||||
) {
|
||||
this.settings = this.settingsService.getSettings();
|
||||
this.numCopiesFormControl = new FormControl(this.settings.numCopies, [
|
||||
Validators.required,
|
||||
]);
|
||||
|
||||
this.labelLayoutFormControl = new FormControl(this.settings.labelLayout.type, [
|
||||
this.labelLayoutFormControl = new FormControl(this.settings.labelLayout.id, [
|
||||
Validators.required,
|
||||
]);
|
||||
|
||||
|
@ -37,21 +45,51 @@ export class SettingsComponent implements OnInit {
|
|||
]);
|
||||
|
||||
this.labelLayouts = Object.values(labelLayouts);
|
||||
|
||||
this._loadFakeData();
|
||||
}
|
||||
|
||||
get hasInfo(): boolean {
|
||||
return this.numCopiesFormControl.valid && this.locationIdFormControl.valid;
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
ngAfterViewInit(): void {
|
||||
this.locationIdInput.focus();
|
||||
this.changeDetector.detectChanges();
|
||||
}
|
||||
|
||||
save() {
|
||||
this.settingsService.saveSettings({
|
||||
labelLayout: labelLayouts[this.labelLayoutFormControl.value],
|
||||
numCopies: this.numCopiesFormControl.value,
|
||||
locationId: this.locationIdFormControl.value,
|
||||
});
|
||||
this.router.navigate(['/']);
|
||||
if (this.hasInfo) {
|
||||
this.settingsService.saveSettings({
|
||||
labelLayout: labelLayouts[this.labelLayoutFormControl.value],
|
||||
numCopies: this.numCopiesFormControl.value,
|
||||
locationId: this.locationIdFormControl.value,
|
||||
});
|
||||
this.router.navigate(['/']);
|
||||
}
|
||||
}
|
||||
|
||||
// Make some fake data for sample barcodes
|
||||
private _loadFakeData() {
|
||||
this.fakeSample = {
|
||||
barcode: '',
|
||||
student_id: '123456789',
|
||||
initials: 'ABCDE',
|
||||
date: new Date(),
|
||||
location: this.settings.locationId,
|
||||
};
|
||||
|
||||
this.fakeBarcodeValue = createQrCodeValue(
|
||||
this.fakeSample.student_id,
|
||||
this.fakeSample.initials,
|
||||
this.fakeSample.date,
|
||||
this.fakeSample.location
|
||||
);
|
||||
|
||||
this.fakeSample.barcode = this.fakeBarcodeValue;
|
||||
}
|
||||
|
||||
selectLabelLayout(layout: LabelLayout) {
|
||||
this.labelLayoutFormControl.patchValue(layout);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
import {Sample} from '../models/sample.interface';
|
||||
|
||||
export const mockSample: Sample = {
|
||||
barcode: '123456789-ABCDE-202101231234-0101',
|
||||
student_id: '123456789',
|
||||
date: new Date(),
|
||||
initials: 'ABCDE',
|
||||
location: '0101',
|
||||
};
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -442,4 +442,31 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
svg.label-layout-format {
|
||||
text, tspan {
|
||||
font-style: normal;
|
||||
font-variant: normal;
|
||||
font-weight: bold;
|
||||
font-stretch: normal;
|
||||
font-size: 2.11666656px; // 6pt
|
||||
line-height: 1.25;
|
||||
font-family: monospace;
|
||||
text-align: center;
|
||||
font-variant-ligatures: normal;
|
||||
font-variant-caps: normal;
|
||||
font-variant-numeric: normal;
|
||||
font-feature-settings: normal;
|
||||
font-variant-numeric: normal;
|
||||
font-feature-settings: normal;
|
||||
text-anchor: middle;
|
||||
}
|
||||
|
||||
@media print {
|
||||
.label-layout-border {
|
||||
stroke: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue