1 import {Component, OnInit, ViewChild, TemplateRef} from '@angular/core';
2 import {Observable} from 'rxjs';
3 import {map} from 'rxjs/operators';
4 import {ActivatedRoute} from '@angular/router';
5 import {IdlService, IdlObject} from '@eg/core/idl.service';
6 import {PcrudService} from '@eg/core/pcrud.service';
7 import {AuthService} from '@eg/core/auth.service';
8 import {OrgService} from '@eg/core/org.service';
9 import {ComboboxComponent, ComboboxEntry
10 } from '@eg/share/combobox/combobox.component';
11 import {PrintService} from '@eg/share/print/print.service';
12 import {LocaleService} from '@eg/core/locale.service';
13 import {NgbTabset, NgbTabChangeEvent} from '@ng-bootstrap/ng-bootstrap';
14 import {FmRecordEditorComponent} from '@eg/share/fm-editor/fm-editor.component';
15 import {SampleDataService} from '@eg/share/util/sample-data.service';
16 import {OrgFamily} from '@eg/share/org-family-select/org-family-select.component';
17 import {ConfirmDialogComponent} from '@eg/share/dialog/confirm.component';
20 * Print Template Admin Page
24 templateUrl: 'print-template.component.html'
27 export class PrintTemplateComponent implements OnInit {
29 entries: ComboboxEntry[];
34 localeEntries: ComboboxEntry[];
35 compiledContent: string;
36 templateCache: {[id: number]: IdlObject} = {};
38 selectedOrgs: number[];
40 @ViewChild('templateSelector', { static: true }) templateSelector: ComboboxComponent;
41 @ViewChild('tabs', { static: false }) tabs: NgbTabset;
42 @ViewChild('editDialog', { static: true }) editDialog: FmRecordEditorComponent;
43 @ViewChild('confirmDelete', { static: true }) confirmDelete: ConfirmDialogComponent;
45 // Define some sample data that can be used for various templates
46 // Data will be filled out via the sample data service.
47 // Keys map to print template names
54 private route: ActivatedRoute,
55 private idl: IdlService,
56 private org: OrgService,
57 private pcrud: PcrudService,
58 private auth: AuthService,
59 private locale: LocaleService,
60 private printer: PrintService,
61 private samples: SampleDataService
64 this.localeEntries = [];
68 this.initialOrg = this.auth.user().ws_ou();
69 this.selectedOrgs = [this.initialOrg];
70 this.localeCode = this.locale.currentLocaleCode();
71 this.locale.supportedLocales().subscribe(
72 l => this.localeEntries.push({id: l.code(), label: l.name()}));
73 this.setTemplateInfo().subscribe();
74 this.fleshSampleData();
79 // NOTE: server templates work fine with IDL objects, but
80 // vanilla hashes are easier to work with in the admin UI.
82 // Classes for which sample data exists
83 const classes = ['au', 'ac', 'aua', 'ahr', 'acp', 'mwde'];
84 const samples: any = {};
85 classes.forEach(class_ => samples[class_] =
86 this.idl.toHash(this.samples.listOfThings(class_, 10)));
88 // Wide holds are hashes instead of IDL objects.
89 // Add fields as needed.
91 request_time: this.samples.randomDate().toISOString(),
92 ucard_barcode: samples.ac[0].barcode,
93 usr_family_name: samples.au[0].family_name,
94 usr_alias: samples.au[0].alias,
95 cp_barcode: samples.acp[0].barcode
97 request_time: this.samples.randomDate().toISOString(),
98 ucard_barcode: samples.ac[1].barcode,
99 usr_family_name: samples.au[1].family_name,
100 usr_alias: samples.au[1].alias,
101 cp_barcode: samples.acp[1].barcode
104 this.sampleData.patron_address = {
105 patron: samples.au[0],
106 address: samples.aua[0]
109 this.sampleData.holds_for_bib = wide_holds;
112 onTabChange(evt: NgbTabChangeEvent) {
113 if (evt.nextId === 'template') {
114 this.refreshPreview();
119 // Only present when its tab is visible
120 return document.getElementById('template-preview-pane');
123 // TODO should the ngModelChange handler fire for org-family-select
124 // even when the values don't change?
125 orgOnChange(family: OrgFamily) {
126 // Avoid reundant server calls.
127 if (!this.sameIds(this.selectedOrgs, family.orgIds)) {
128 this.selectedOrgs = family.orgIds;
129 this.setTemplateInfo().subscribe();
133 // True if the 2 arrays contain the same contents,
134 // regardless of the order.
135 sameIds(arr1: any[], arr2: any[]): boolean {
136 if (arr1.length !== arr2.length) {
139 for (let i = 0; i < arr1.length; i++) {
140 if (!arr2.includes(arr1[i])) {
147 localeOnChange(code: string) {
149 this.localeCode = code;
150 this.setTemplateInfo().subscribe();
154 // Fetch name/id for all templates in range.
155 // Avoid fetching the template content until needed.
156 setTemplateInfo(): Observable<IdlObject> {
158 this.template = null;
159 this.templateSelector.applyEntryId(null);
160 this.compiledContent = '';
162 return this.pcrud.search('cpt',
164 owner: this.selectedOrgs,
165 locale: this.localeCode
167 select: {cpt: ['id', 'label', 'owner']},
168 order_by: {cpt: 'label'}
171 this.templateCache[tmpl.id()] = tmpl;
172 this.entries.push({id: tmpl.id(), label: tmpl.label()});
177 getOwnerName(id: number): string {
178 if (this.templateCache[id]) {
179 return this.org.get(this.templateCache[id].owner()).shortname();
184 // If the selected template changes through means other than the
185 // template selecdtor, setting updateSelector=true will force the
186 // template to appear in the selector and get selected, regardless
187 // of whether it would have been fetched with current filters.
188 selectTemplate(id: number, updateSelector?: boolean) {
191 this.template = null;
192 this.compiledContent = '';
196 this.pcrud.retrieve('cpt', id).subscribe(t => {
197 this.template = this.templateCache[id] = t;
199 if (updateSelector) {
200 if (!this.templateSelector.hasEntry(id)) {
201 this.templateSelector.addEntry({id: id, label: t.label()});
203 this.templateSelector.applyEntryId(id);
206 const data = this.sampleData[t.name()];
208 this.sampleJson = JSON.stringify(data, null, 2);
209 this.refreshPreview();
214 // Allow the template editor textarea to expand vertically as
215 // content is added, with a sane minimum row count
216 templateRowCount(): number {
218 if (this.template && this.template.template()) {
220 this.template.template().split(/\n/).length + 2);
226 if (!this.sampleJson) { return; }
227 this.compiledContent = '';
231 data = JSON.parse(this.sampleJson);
232 this.invalidJson = false;
234 this.invalidJson = true;
237 this.printer.compileRemoteTemplate({
238 templateId: this.template.id(),
240 printContext: 'default' // required, has no impact here
242 }).then(response => {
244 this.compiledContent = response.content;
245 if (this.container()) { // null if on alternate tab
246 if (response.contentType === 'text/html') {
247 this.container().innerHTML = response.content;
249 // Assumes text/plain or similar
250 this.container().innerHTML = '<pre>' + response.content + '</pre>';
257 this.container().innerHTML = '';
258 this.pcrud.update(this.template).toPromise()
259 .then(() => this.refreshPreview());
263 this.editDialog.setRecord(this.template);
264 this.editDialog.mode = 'update';
265 this.editDialog.open({size: 'lg'}).toPromise().then(id => {
266 if (id !== undefined) {
267 const selectedId = this.template.id();
268 this.setTemplateInfo().toPromise().then(
269 _ => this.selectTemplate(selectedId)
276 const tmpl = this.idl.clone(this.template);
278 tmpl.active(false); // Cloning requires manual activation
280 this.editDialog.setRecord(tmpl);
281 this.editDialog.mode = 'create';
282 this.editDialog.open({size: 'lg'}).toPromise().then(newTmpl => {
283 if (newTmpl !== undefined) {
284 this.setTemplateInfo().toPromise()
285 .then(_ => this.selectTemplate(newTmpl.id(), true));
291 this.confirmDelete.open().subscribe(confirmed => {
292 if (!confirmed) { return; }
293 this.pcrud.remove(this.template).toPromise().then(_ => {
294 this.setTemplateInfo().toPromise()
295 .then(x => this.selectTemplate(null));