import {Injectable} from '@angular/core';
-import {Params} from '@angular/router';
+import {ParamMap} from '@angular/router';
+import {EgOrgService} from '@eg/core/org';
import {CatalogSearchContext} from './search-context';
+import {CATALOG_CCVM_FILTERS} from './catalog.service';
@Injectable()
export class EgCatalogUrlService {
// consider supporting a param name prefix/namespace
- constructor() { }
+ constructor(private org: EgOrgService) { }
/**
* Returns a URL query structure suitable for using with
* router.navigate(..., {queryParams:...}).
* No navigation is performed within.
*/
- toUrlParams(context: CatalogSearchContext): any {
- let params = {};
+ toUrlParams(context: CatalogSearchContext):
+ {[key: string]: string | string[]} {
+
+ let params = {
+ query: [],
+ fieldClass: [],
+ joinOp: [],
+ matchOp: [],
+ facets: [],
+ org: null
+ };
- params.query = [];
- params.searchClass = [];
- params.joiner = [];
- params.match = [];
+ // These fields can be copied directly into place
+ ['limit','offset','format','sort','available','global']
+ .forEach(field => {
+ if (context[field]) {
+ // Only propagate applied values to the URL.
+ params[field] = context[field];
+ }
+ });
context.query.forEach((q, idx) => {
- ['query', 'searchClass','joiner','match'].forEach(field => {
+ ['query', 'fieldClass','joinOp','matchOp'].forEach(field => {
+ // Propagate all array-based fields regardless of
+ // whether a value is applied to ensure correct
+ // correlation between values.
params[field][idx] = context[field][idx];
});
});
+ // CCVM filters are encoded as comma-separated lists
+ Object.keys(context.ccvmFilters).forEach(code => {
+ if (context.ccvmFilters[code] &&
+ context.ccvmFilters[code][0] != '') {
+ params[code] = context.ccvmFilters[code].join(',');
+ }
+ });
+
+ // Each facet is a JSON encoded blob of class, name, and value
+ context.facetFilters.forEach(facet => {
+ params.facets.push(JSON.stringify(facet));
+ });
+
+ params.org = context.searchOrg.id();
+
return params;
}
/**
* Creates a new search context from the active route params.
*/
- fromUrlParams(params: Params): CatalogSearchContext {
+ fromUrlParams(params: ParamMap): CatalogSearchContext {
let context = new CatalogSearchContext();
this.applyUrlParams(context, params);
return context;
}
- applyUrlParams(context: CatalogSearchContext, params: Params): void {
+ applyUrlParams(context: CatalogSearchContext, params: ParamMap): void {
// These fields can be copied directly into place
['limit','offset','format','sort','available','global']
.forEach(field => {
- if (params[field] != undefined) {
- context[field] = params[field];
- }
+ let val = params.get(field);
+ if (val !== null) context[field] = val;
});
- if (typeof params.query == 'string') {
- // Only one query set is encoded in the URL
- context.query = [params.query];
- if (params.searchClass) context.searchClass = [params.searchClass];
- if (params.joiner) context.joiner = [params.joiner];
- if (params.match) context.match = [params.match];
-
- } else if (Array.isArray(params.query)) {
- // Multiple query sets encoded in the URL.
- params.query.forEach((q, idx) => {
- context.query[idx] = params.query[idx];
- context.searchClass[idx] = params.searchClass[idx];
- context.joiner[idx] = params.joiner[idx];
- context.match[idx] = params.match[idx];
- });
- }
-
- /*
-
- // Decode and propagate array-based values
- angular.forEach(scs.ccvm_list_keys, function(field) {
- if (url_search[field]) {
- ctx.search_args[field] = url_search[field].split(/,/);
- }
- });
-
- // if there's only a single value it will be a string.
- if (typeof url_search.facet == 'string')
- url_search.facet = [url_search.facet];
- angular.forEach(url_search.facet, function(facet) {
- console.log('parsing: ' + facet);
- ctx.search_args.facets.push(JSON.parse(facet));
- });
-
- // Handle special-case values
- if (url_search.org)
- ctx.search_args.context_org = egCore.org.get(url_search.org);
-
- */
- }
+ ['query','fieldClass','joinOp','matchOp'].forEach(field => {
+ let arr = params.getAll(field);
+ if (arr && arr.length) context[field] = arr;
+ });
+ CATALOG_CCVM_FILTERS.forEach(code => {
+ let val = params.get(code);
+ if (val) context.ccvmFilters[code] = val.split(/,/);
+ });
+
+ params.getAll('facets').forEach(blob => {
+ context.facetFilters.push(JSON.parse(blob));
+ });
+ context.searchOrg = this.org.get(+params.get('org'));
+ }
}
import {EgPcrudService} from '@eg/core/pcrud';
import {CatalogSearchContext} from './search-context';
-const CCVM_FILTER_TYPES = [
+export const CATALOG_CCVM_FILTERS = [
'item_type',
'item_form',
'item_lang',
@Injectable()
export class EgCatalogService {
- // Search context is set by the application.
searchContext: CatalogSearchContext;
-
ccvmMap: {[ccvm:string] : EgIdlObject[]} = {};
cmfMap: {[cmf:string] : EgIdlObject[]} = {};
private pcrud: EgPcrudService
) {}
+ // Though multiple search contexts could exist (e.g. for caching),
+ // there will only be one active search context at a time.
+ // Wrap in a getter for future-proofing.
+ activeSearchContext(): CatalogSearchContext {
+ return this.searchContext;
+ }
+
search(): Promise<void> {
- let ctx = this.searchContext;
+ let ctx = this.activeSearchContext();
var fullQuery = ctx.compileSearch();
return new Promise((resolve, reject) => {
this.pcrud.search('ccvm',
- {ctype : CCVM_FILTER_TYPES}, {}, {atomic: true}
+ {ctype : CATALOG_CCVM_FILTERS}, {}, {atomic: true}
).subscribe(list => {
this.compileCcvms(list);
resolve();
const UNAPI_PATHS = {
title : 'titleInfo[0].title[0]',
author: 'name[0].namePart[0]',
+ edition: 'originInfo[0].edition[0]',
+ pubdate: 'originInfo[0].dateIssued[0]',
genre: 'genre[0]._'
}
- let response = {};
+ let response = {
+ copyCounts : [],
+ ccvms : {}
+ };
+
for (let key in UNAPI_PATHS) {
try {
response[key] = eval(`summary.mods.${UNAPI_PATHS[key]}`);
} catch(E) {
- response[key] ='';
+ response[key] = '';
}
}
+ summary.mods.attributes[0].field.forEach(attrField => {
+ response.ccvms[attrField['$'].name] = {
+ code: attrField['_'],
+ label: attrField['$']['coded-value']
+ };
+ });
+
+ summary.mods.holdings[0].counts[0].count.forEach(count => {
+ response.copyCounts.push(count['$']);
+ });
+
+ //console.log(summary);
+ console.log(response);
+
return response;
}
}
available: boolean;
global: boolean;
sort: string;
- searchClass: string[];
+ fieldClass: string[];
query: string[];
- joiner: string[];
- match: string[];
+ joinOp: string[];
+ matchOp: string[];
format: string;
searchOrg: EgIdlObject;
- ccvmFilters: {[ccvmCode:string] : string};
+ ccvmFilters: {[ccvmCode:string] : string[]};
facetFilters: FacetFilter[];
// Result from most recent search.
this.format = '',
this.sort = '',
this.query = [''];
- this.searchClass = ['keyword'];
- this.match = ['contains'];
- this.joiner = [''];
+ this.fieldClass = ['keyword'];
+ this.matchOp = ['contains'];
+ this.joinOp = [''];
this.available = false;
this.global = false;
this.ccvmFilters = {};
}
compileSearch(): string {
-
let str: string = '';
if (this.available) str += ' #available';
compileBoolQuerySet(idx: number): string {
let query = this.query[idx];
- let joiner = this.joiner[idx];
- let match = this.match[idx];
- let searchClass = this.searchClass[idx];
+ let joinOp = this.joinOp[idx];
+ let matchOp = this.matchOp[idx];
+ let fieldClass = this.fieldClass[idx];
let str = '';
if (!query) return str;
- if (idx > 0) str += ' ' + joiner + ' ';
+ if (idx > 0) str += ' ' + joinOp + ' ';
str += '(';
- if (searchClass) str += searchClass + ':';
+ if (fieldClass) str += fieldClass + ':';
- switch(match) {
+ switch(matchOp) {
case 'phrase':
query = this.addQuotes(this.stripQuotes(query));
break;
let url = `${UNAPI_PATH}${params.target}/${params.id}${params.extras}/` +
`${org.shortname()}/${depth}&format=${params.format}`;
+ //console.debug(`UNAPI: ${url}`);
+
return new Promise((resolve, reject) => {
this.http.get(url, {responseType: 'text'})
.subscribe(xmlStr => resolve(xmlStr));
ngOnInit() {
// Initialize the search context from the load-time URL params.
+ // Do this here so the search form and other context data are
+ // applied on every page, not just the search results page. The
+ // search results pages will handle running the actual search.
this.cat.searchContext = this.searchContext =
- this.catUrl.fromUrlParams(this.route.snapshot.queryParams);
+ this.catUrl.fromUrlParams(this.route.snapshot.queryParamMap);
this.searchContext.org = this.org; // offer a copy of the service.
-
- console.debug(this.searchContext);
}
}
) {}
ngOnInit() {
- this.searchContext = this.cat.searchContext;
+ this.searchContext = this.cat.activeSearchContext();
}
}
) {}
ngOnInit() {
- this.searchContext = this.cat.searchContext;
+ this.searchContext = this.cat.activeSearchContext();
}
}
+.cat-record-row {
+ margin-bottom:10px;
+ padding:9px;
+ /*
+ background-color: #f5f5f5;
+ border: 1px solid #e3e3e3;
+ */
+}
+
+.weak-text-1 {
+ font-size: 85%;
+}
+
-<div class="row">
- <div class="col-1">{{index + 1}}</div>
- <div class="col-3">{{bibSummary.title}}</div>
- <div class="col-3">{{bibSummary.author}}</div>
- <div class="col-3">{{bibSummary.genre}}</div>
-</div>
+<div class="cat-record-row col-12 card card-body bg-light">
+ <div class="row">
+ <div class="col-1">
+ <!-- TODO router links -->
+ <a href="./cat/catalog/record/{{bibSummary.id}}">
+ <img style="height:80px"
+ src="/opac/extras/ac/jacket/small/r/{{bibSummary.id}}"/>
+ </a>
+ </div>
+ <div class="col-5">
+ <div class="row">
+ <div class="col-12 font-weight-bold">
+ <!-- nbsp allows the column to take shape when no value exists -->
+ <a href="./cat/staffcat/record/{{bibSummary.id}}">
+ {{bibSummary.title || ' '}}
+ </a>
+ </div>
+ </div>
+ <div class="row pt-2">
+ <div class="col-12">
+ <!-- nbsp allows the column to take shape when no value exists -->
+ <a href (click)="searchAuthor(bibSummary)" style="font-style:italic">
+ {{bibSummary.author || ' '}}
+ </a>
+ </div>
+ </div>
+ <div class="row pt-2">
+ <div class="col-12">
+ <span>#{{index + 1 + searchContext.pager.offset}}</span>
+ <span>
+ <img class="pad-right-min" ng-cloak
+ src="/images/format_icons/icon_format/{{bibSummary.ccvms.icon_format.code}}.png"/>
+ <span>{{bibSummary.ccvms.icon_format.label}}</span>
+ </span>
+ <span style='pl-2'>{{bibSummary.edition}}</span>
+ <span style='pl-2'>{{bibSummary.pubdate}}</span>
+ </div>
+ </div>
+ </div>
+ <div class="col-2">
+ <div class="row" [ngClass]="{'pt-2':copyIndex > 0}"
+ *ngFor="let copyCount of bibSummary.copyCounts; let copyIdx = index">
+ <div class="col-12">
+ {{copyCount.available}} / {{copyCount.count}}
+ items @ {{orgName(copyCount.org_unit)}}
+ </div>
+ </div>
+ </div>
+ <div class="col-4">
+ <!--
+ <div class="row">
+ <div class="col-4">
+ <div class="pull-right weak-text-1">
+ [% l('Holds: [_1]', '{{record.hold_count}}') %]
+ </div>
+ </div>
+ <div class="col-8">
+ <div class="pull-right weak-text-1">
+ [% l('Created [_1] by [_2]',
+ '{{record.bre.create_date() | date:$root.egDateFormat}}',
+ '<a target="_self"
+ href="./circ/patron/{{record.bre.creator().id()}}/checkout">{{record.bre.creator().usrname()}}</a>'
+ ) %]
+ </div>
+ </div>
+ </div>
+ <div class="row pt-2">
+ <div class="col-4">
+ <div class="pull-right weak-text-1">
+ [% l('TCN: [_1]', '{{record.bre.tcn_value()}}') %]
+ </div>
+ </div>
+ <div class="col-8">
+ <div class="pull-right weak-text-1">
+ [% l('Edited [_1] by [_2]',
+ '{{record.bre.edit_date() | date:$root.egDateFormat}}',
+ '<a target="_self"
+ href="./circ/patron/{{record.bre.editor().id()}}/checkout">{{record.bre.editor().usrname()}}</a>'
+ ) %]
+ </div>
+ </div>
+ </div>
+ <div class="row pt-2">
+ <div class="col-4">
+ <div class="pull-right weak-text-1">
+ [% l('ID: [_1]', '{{bibSummary.id}}') %]
+ </div>
+ </div>
+ <div class="col-8">
+ <div class="pull-right">
+ <span>
+ <button (click)="searchContext.place_hold(record)"
+ class="btn btn-sm btn-success">
+ <span class="glyphicon glyphicon-ok"></span>
+ [% l('Place Hold') %]
+ </button>
+ </span>
+ <span>
+ <button (click)="searchContext.add_to_list(record)"
+ class="btn btn-sm btn-info">
+ <span class="glyphicon glyphicon-list-alt"></span>
+ [% l('Add to List') %]
+ </button>
+ </span>
+ </div>
+ </div>
+ </div>
+ </div>--><!-- col -->
+ </div><!-- row -->
+</div><!-- col -->
import {Component, OnInit, Input} from '@angular/core';
+import {EgOrgService} from '@eg/core/org';
import {EgCatalogService} from '@eg/share/catalog/catalog.service';
import {CatalogSearchContext} from '@eg/share/catalog/search-context';
searchContext: CatalogSearchContext;
constructor(
+ private org: EgOrgService,
private cat: EgCatalogService
) {}
ngOnInit() {
- this.searchContext = this.cat.searchContext;
+ this.searchContext = this.cat.activeSearchContext();
+ }
+
+ orgName(orgId: number): string {
+ return this.org.get(orgId).shortname();
}
}
import {Component, OnInit, Input} from '@angular/core';
-import {ActivatedRoute} from '@angular/router';
+import {Observable} from 'rxjs/Rx';
+import {map, switchMap, distinctUntilChanged} from 'rxjs/operators';
+import {ActivatedRoute, ParamMap} from '@angular/router';
import {EgCatalogService} from '@eg/share/catalog/catalog.service';
+import {EgCatalogUrlService} from '@eg/share/catalog/catalog-url.service';
import {CatalogSearchContext} from '@eg/share/catalog/search-context';
-
import {EgOrgService} from '@eg/core/org';
@Component({
searchContext: CatalogSearchContext;
constructor(
+ private route: ActivatedRoute,
private org: EgOrgService,
- private cat: EgCatalogService
+ private cat: EgCatalogService,
+ private catUrl: EgCatalogUrlService
) {}
ngOnInit() {
- this.searchContext = this.cat.searchContext;
- this.searchByUrl();
- }
+ this.searchContext = this.cat.activeSearchContext();
- searchByUrl(): void {
- // skip searching when there's no query, etc.
+ // Our search context is initialized on page load. Once
+ // ResultsComponent is active, it will not be reinitialized,
+ // even if the route parameters changes (unless we change the
+ // route reuse policy). Watch for changes here to pick up new
+ // searches. This will also fire on page load.
+ this.route.queryParamMap.subscribe((params: ParamMap) => {
+ // TODO: Angular docs suggest using switchMap(), but
+ // it's not firing for some reason. Also, could avoid
+ // firing unnecessary searches when a param unrelated to
+ // searching is changed by .map()'ing out only the desired
+ // params and running through .distinctUntilChanged(), but
+ // .map() is not firing either. I'm missing something.
+ this.searchByUrl(params);
+ })
+ }
+
+ searchByUrl(params: ParamMap): void {
+ this.catUrl.applyUrlParams(this.searchContext, params);
this.searchContext.searchOrg = this.org.get(4); // TODO: testing
+ // A query string is required at minimum.
if (!this.searchContext.query[0]) return;
this.cat.search().then(ok => {
+
+ // Do some post-search tidying before component rendering.
+ this.searchContext.result.records.forEach(b => {
+ b.copyCounts = b.copyCounts.filter(c => {
+ return c.type == 'staff'
+ })
+ });
+
console.debug('search complete');
+
});
+ }
+ orgName(orgId: number): string {
+ return this.org.get(orgId).shortname();
}
+ searchAuthor(bibSummary: any) {
+ }
}
padding-left: 0px;
}
+.search-plus-minus {
+ /* Transparent background */
+ background-color: rgba(0, 0, 0, 0.0);
+ padding-left: .25rem;
+ padding-right: .25rem; /* default .5rem */
+}
<div id='staffcat-search-form'>
<div class="row"
*ngFor="let q of searchContext.query; let idx = index; trackBy:trackByIdx">
- <div class="col-9 flex-row">
+ <div class="col-md-8 flex-row">
<div class="flex-cell">
<div *ngIf="idx == 0">
<select class="form-control" [(ngModel)]="searchContext.format">
</div>
<div *ngIf="idx > 0">
<select class="form-control"
- [(ngModel)]="searchContext.joiner[idx]">
+ [(ngModel)]="searchContext.joinOp[idx]">
<option value='&&'>And</option>
<option value='||'>Or</option>
</select>
</div>
<div class="flex-cell">
<select class="form-control"
- [(ngModel)]="searchContext.searchClass[idx]">
+ [(ngModel)]="searchContext.fieldClass[idx]">
<option value='keyword'>Keyword</option>
<option value='title'>Title</option>
<option value='jtitle'>Journal Title</option>
</div>
<div class="flex-cell">
<select class="form-control"
- [(ngModel)]="searchContext.match[idx]">
+ [(ngModel)]="searchContext.matchOp[idx]">
<option value='contains'>Contains</option>
<option value='nocontains'>Does not contain</option>
<option value='phrase'>Contains phrase</option>
</div>
</div>
<div class="flex-cell">
- <button class="btn btn-sm btn-default"
+ <button class="btn btn-sm search-plus-minus"
(click)="addSearchRow(idx + 1)">
<span class="material-icons">add_circle_outline</span>
</button>
- <button class="btn btn-sm btn-default"
+ <button class="btn btn-sm search-plus-minus"
[disabled]="searchContext.query.length < 2"
(click)="delSearchRow(idx)">
<span class="material-icons">remove_circle_outline</span>
</button>
</div>
</div><!-- col -->
- <div class="col-3">
- <div *ngIf="idx == 0" class="pull-right">
+ <div class="col-md-4">
+ <div *ngIf="idx == 0" class="float-right">
<button class="btn btn-success" type="button"
(click)="searchContext.pager.offset=0;searchByForm()">
Search
<!--
<div class="row">
- <div class="col-9 flex-row">
+ <div class="col-md-9 flex-row">
<div class="flex-cell">
<eg-org-select nodefault
selected="searchContext.searchContext_org">
</div>
<div class="row pad-vert-min" ng-show="showAdvancedSearch">
- <div class="col-2">
+ <div class="col-md-2">
<select class="form-control" [(ngModel)]="searchContext.item_type" multiple="true">
<option value=''>[% l('All Item Types') %]</option>
<option *ngFor="let item_type of searchContext.ccvm_lists('item_type')"
value="{{item_type.code()}}">{{item_type.value()}}</option>
</select>
</div>
- <div class="col-2">
+ <div class="col-md-2">
<select class="form-control" [(ngModel)]="searchContext.item_form" multiple="true">
<option value=''>[% l('All Item Forms') %]</option>
<option *ngFor="let item_form of searchContext.ccvm_lists('item_form')"
value="{{item_form.code()}}">{{item_form.value()}}</option>
</select>
</div>
- <div class="col-2">
+ <div class="col-md-2">
<select class="form-control" [(ngModel)]="searchContext.item_lang" multiple="true">
<option value=''>[% l('All Languages') %]</option>
<option *ngFor="let item_lang of searchContext.ccvm_lists('item_lang')"
value="{{item_lang.code()}}">{{item_lang.value()}}</option>
</select>
</div>
- <div class="col-2">
+ <div class="col-md-2">
<select class="form-control" [(ngModel)]="searchContext.audience" multiple="true">
<option value=''>[% l('All Audiences') %]</option>
<option *ngFor="let audience of searchContext.ccvm_lists('audience')"
</div>
</div>
<div class="row pad-vert-min" ng-show="showAdvancedSearch">
- <div class="col-2">
+ <div class="col-md-2">
<select class="form-control" [(ngModel)]="searchContext.vr_format" multiple="true">
<option value=''>[% l('All Video Formats') %]</option>
<option *ngFor="let vr_format of searchContext.ccvm_lists('vr_format')"
value="{{vr_format.code()}}">{{vr_format.value()}}</option>
</select>
</div>
- <div class="col-2">
+ <div class="col-md-2">
<select class="form-control" [(ngModel)]="searchContext.bib_level" multiple="true">
<option value=''>[% l('All Bib Levels') %]</option>
<option *ngFor="let bib_level of searchContext.ccvm_lists('bib_level')"
value="{{bib_level.code()}}">{{bib_level.value()}}</option>
</select>
</div>
- <div class="col-2">
+ <div class="col-md-2">
<select class="form-control" [(ngModel)]="searchContext.lit_form" multiple="true">
<option value=''>[% l('All Literary Forms') %]</option>
<option *ngFor="let lit_form of searchContext.ccvm_lists('lit_form')"
value="{{lit_form.code()}}">{{lit_form.value()}}</option>
</select>
</div>
- <div class="col-2">
+ <div class="col-md-2">
<i>Copy location filter goes here...</i>
</div>
</div>
ccvmMap: {[ccvm:string] : EgIdlObject[]} = {};
cmfMap: {[cmf:string] : EgIdlObject[]} = {};
showAdvancedSearch: boolean = false;
+ routeIndex: number = 0;
constructor(
private router: Router,
ngOnInit() {
this.ccvmMap = this.cat.ccvmMap;
this.cmfMap = this.cat.cmfMap;
- this.searchContext = this.cat.searchContext;
+ this.searchContext = this.cat.activeSearchContext();
}
addSearchRow(index: number): void {
this.searchContext.query.splice(index, 0, '');
- this.searchContext.searchClass.splice(index, 0, 'keyword');
- this.searchContext.joiner.splice(index, 0, '&&');
- this.searchContext.match.splice(index, 0, 'contains');
+ this.searchContext.fieldClass.splice(index, 0, 'keyword');
+ this.searchContext.joinOp.splice(index, 0, '&&');
+ this.searchContext.matchOp.splice(index, 0, 'contains');
}
delSearchRow(index: number): void {
this.searchContext.query.splice(index, 1);
- this.searchContext.searchClass.splice(index, 1);
- this.searchContext.joiner.splice(index, 1);
- this.searchContext.match.splice(index, 1);
+ this.searchContext.fieldClass.splice(index, 1);
+ this.searchContext.joinOp.splice(index, 1);
+ this.searchContext.matchOp.splice(index, 1);
}
checkEnter($event: any): void {
searchByForm(): void {
this.searchContext.searchOrg = this.org.get(4); // TODO: TEST BR1
let params = this.catUrl.toUrlParams(this.searchContext);
+
+ // Force a new search every time a search is fired, even if it's
+ // the same search. Since router navigation exits early when
+ // the route + params is identical, add a random token to the
+ // route params to force a full navigation. This also resolves
+ // a problem where only removing secondary+ versions of a query
+ // param fail to cause a route navigation. (E.g. going from two
+ // query= params to one). Investigation pending.
+ params.ridx=''+this.routeIndex++;
+
this.router.navigate(
['/staff/catalog/search'], {queryParams: params});
}
--- /dev/null
+#staff-content-container {
+ width: 95%;
+ margin-top:56px;
+ padding-right: 10px;
+ padding-left: 10px;
+ margin-right: auto;
+ margin-left: auto;
+}
<!-- top navigation bar -->
<eg-staff-nav-bar></eg-staff-nav-bar>
-<!-- margin is there to accommodate navbar -->
-<div style="margin-top:56px">
-
+<div id='staff-content-container'>
<!-- page content -->
<router-outlet></router-outlet>
</div>
import { EgNetService } from '@eg/core/net';
@Component({
- templateUrl: 'staff.component.html'
+ templateUrl: 'staff.component.html',
+ styleUrls: ['staff.component.css']
})
export class EgStaffComponent implements OnInit {
@import "~@angular/material/prebuilt-themes/indigo-pink.css";
*/
+
+/* match the font-size of the angularjs client
+ -- the default BS 4 fonts are quite large */
+body, .form-control, .btn {
+ font-size: 14px;
+}
+