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 {ServerStoreService} from '@eg/core/server-store.service';
10 import {ComboboxComponent, ComboboxEntry
11 } from '@eg/share/combobox/combobox.component';
12 import {PrintService} from '@eg/share/print/print.service';
13 import {LocaleService} from '@eg/core/locale.service';
14 import {NgbTabset, NgbTabChangeEvent} from '@ng-bootstrap/ng-bootstrap';
15 import {FmRecordEditorComponent} from '@eg/share/fm-editor/fm-editor.component';
16 import {SampleDataService} from '@eg/share/util/sample-data.service';
17 import {OrgFamily} from '@eg/share/org-family-select/org-family-select.component';
18 import {ConfirmDialogComponent} from '@eg/share/dialog/confirm.component';
21 * Print Template Admin Page
25 templateUrl: 'print-template.component.html'
28 export class PrintTemplateComponent implements OnInit {
30 entries: ComboboxEntry[];
35 localeEntries: ComboboxEntry[];
36 compiledContent: string;
37 templateCache: {[id: number]: IdlObject} = {};
39 selectedOrgs: number[];
41 @ViewChild('templateSelector', { static: true }) templateSelector: ComboboxComponent;
42 @ViewChild('tabs', { static: false }) tabs: NgbTabset;
43 @ViewChild('editDialog', { static: true }) editDialog: FmRecordEditorComponent;
44 @ViewChild('confirmDelete', { static: true }) confirmDelete: ConfirmDialogComponent;
45 @ViewChild('printContextCbox', {static: false}) printContextCbox: ComboboxComponent;
47 // Define some sample data that can be used for various templates
48 // Data will be filled out via the sample data service.
49 // Keys map to print template names
56 private route: ActivatedRoute,
57 private idl: IdlService,
58 private org: OrgService,
59 private pcrud: PcrudService,
60 private auth: AuthService,
61 private store: ServerStoreService,
62 private locale: LocaleService,
63 private printer: PrintService,
64 private samples: SampleDataService
67 this.localeEntries = [];
71 this.initialOrg = this.auth.user().ws_ou();
72 this.selectedOrgs = [this.initialOrg];
73 this.localeCode = this.locale.currentLocaleCode();
74 this.locale.supportedLocales().subscribe(
75 l => this.localeEntries.push({id: l.code(), label: l.name()}));
76 this.setTemplateInfo().subscribe();
77 this.fleshSampleData();
82 // NOTE: server templates work fine with IDL objects, but
83 // vanilla hashes are easier to work with in the admin UI.
85 // Classes for which sample data exists
86 const classes = ['au', 'ac', 'aua', 'ahr', 'acp', 'mwde'];
87 const samples: any = {};
88 classes.forEach(class_ => samples[class_] =
89 this.idl.toHash(this.samples.listOfThings(class_, 10)));
91 // Wide holds are hashes instead of IDL objects.
92 // Add fields as needed.
94 request_time: this.samples.randomDate().toISOString(),
95 ucard_barcode: samples.ac[0].barcode,
96 usr_family_name: samples.au[0].family_name,
97 usr_alias: samples.au[0].alias,
98 cp_barcode: samples.acp[0].barcode
100 request_time: this.samples.randomDate().toISOString(),
101 ucard_barcode: samples.ac[1].barcode,
102 usr_family_name: samples.au[1].family_name,
103 usr_alias: samples.au[1].alias,
104 cp_barcode: samples.acp[1].barcode
107 this.sampleData.patron_address = {
108 patron: samples.au[0],
109 address: samples.aua[0]
112 this.sampleData.holds_for_bib = wide_holds;
115 onTabChange(evt: NgbTabChangeEvent) {
116 if (evt.nextId === 'template') {
117 this.refreshPreview();
122 // Only present when its tab is visible
123 return document.getElementById('template-preview-pane');
126 // TODO should the ngModelChange handler fire for org-family-select
127 // even when the values don't change?
128 orgOnChange(family: OrgFamily) {
129 // Avoid reundant server calls.
130 if (!this.sameIds(this.selectedOrgs, family.orgIds)) {
131 this.selectedOrgs = family.orgIds;
132 this.setTemplateInfo().subscribe();
136 // True if the 2 arrays contain the same contents,
137 // regardless of the order.
138 sameIds(arr1: any[], arr2: any[]): boolean {
139 if (arr1.length !== arr2.length) {
142 for (let i = 0; i < arr1.length; i++) {
143 if (!arr2.includes(arr1[i])) {
150 localeOnChange(code: string) {
152 this.localeCode = code;
153 this.setTemplateInfo().subscribe();
157 // Fetch name/id for all templates in range.
158 // Avoid fetching the template content until needed.
159 setTemplateInfo(): Observable<IdlObject> {
161 this.template = null;
162 this.templateSelector.applyEntryId(null);
163 this.compiledContent = '';
165 return this.pcrud.search('cpt',
167 owner: this.selectedOrgs,
168 locale: this.localeCode
170 select: {cpt: ['id', 'label', 'owner']},
171 order_by: {cpt: 'label'}
174 this.templateCache[tmpl.id()] = tmpl;
175 this.entries.push({id: tmpl.id(), label: tmpl.label()});
180 getOwnerName(id: number): string {
181 if (this.templateCache[id]) {
182 return this.org.get(this.templateCache[id].owner()).shortname();
187 // If the selected template changes through means other than the
188 // template selecdtor, setting updateSelector=true will force the
189 // template to appear in the selector and get selected, regardless
190 // of whether it would have been fetched with current filters.
191 selectTemplate(id: number, updateSelector?: boolean) {
194 this.template = null;
195 this.compiledContent = '';
199 this.pcrud.retrieve('cpt', id).subscribe(t => {
200 this.template = this.templateCache[id] = t;
202 if (updateSelector) {
203 if (!this.templateSelector.hasEntry(id)) {
204 this.templateSelector.addEntry({id: id, label: t.label()});
206 this.templateSelector.applyEntryId(id);
209 const data = this.sampleData[t.name()];
211 this.sampleJson = JSON.stringify(data, null, 2);
212 this.refreshPreview();
215 this.store.getItem('eg.print.template_context.' + this.template.name())
217 this.printContextCbox.applyEntryId(setting || 'unset');
222 // Allow the template editor textarea to expand vertically as
223 // content is added, with a sane minimum row count
224 templateRowCount(): number {
226 if (this.template && this.template.template()) {
228 this.template.template().split(/\n/).length + 2);
234 if (!this.sampleJson) { return; }
235 this.compiledContent = '';
239 data = JSON.parse(this.sampleJson);
240 this.invalidJson = false;
242 this.invalidJson = true;
245 this.printer.compileRemoteTemplate({
246 templateId: this.template.id(),
248 printContext: 'default' // required, has no impact here
250 }).then(response => {
252 this.compiledContent = response.content;
253 if (this.container()) { // null if on alternate tab
254 if (response.contentType === 'text/html') {
255 this.container().innerHTML = response.content;
257 // Assumes text/plain or similar
258 this.container().innerHTML = '<pre>' + response.content + '</pre>';
265 this.container().innerHTML = '';
266 this.pcrud.update(this.template).toPromise()
267 .then(() => this.refreshPreview());
271 this.editDialog.setRecord(this.template);
272 this.editDialog.mode = 'update';
273 this.editDialog.open({size: 'lg'}).toPromise().then(id => {
274 if (id !== undefined) {
275 const selectedId = this.template.id();
276 this.setTemplateInfo().toPromise().then(
277 _ => this.selectTemplate(selectedId)
284 const tmpl = this.idl.clone(this.template);
286 tmpl.active(false); // Cloning requires manual activation
288 this.editDialog.setRecord(tmpl);
289 this.editDialog.mode = 'create';
290 this.editDialog.open({size: 'lg'}).toPromise().then(newTmpl => {
291 if (newTmpl !== undefined) {
292 this.setTemplateInfo().toPromise()
293 .then(_ => this.selectTemplate(newTmpl.id(), true));
299 this.confirmDelete.open().subscribe(confirmed => {
300 if (!confirmed) { return; }
301 this.pcrud.remove(this.template).toPromise().then(_ => {
302 this.setTemplateInfo().toPromise()
303 .then(x => this.selectTemplate(null));
308 forceContextChange(entry: ComboboxEntry) {
309 if (entry && entry.id !== 'unset') {
312 'eg.print.template_context.' + this.template.name(), entry.id);
316 this.store.removeItem(
317 'eg.print.template_context.' + this.template.name());