]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/eg2/src/app/share/print/print.component.ts
LP1955838 Hatch Native Browser Printing Option
[Evergreen.git] / Open-ILS / src / eg2 / src / app / share / print / print.component.ts
1 import {Component, OnInit, TemplateRef, ElementRef, Renderer2} from '@angular/core';
2 import {PrintService, PrintRequest} from './print.service';
3 import {StoreService} from '@eg/core/store.service';
4 import {ServerStoreService} from '@eg/core/server-store.service';
5 import {HatchService, HatchMessage} from '@eg/core/hatch.service';
6 import {ToastService} from '@eg/share/toast/toast.service';
7 import {StringService} from '@eg/share/string/string.service';
8 import {HtmlToTxtService} from '@eg/share/util/htmltotxt.service';
9
10 const HATCH_FILE_WRITER_PRINTER = 'hatch_file_writer';
11 const HATCH_BROWSER_PRINTING_PRINTER = 'hatch_browser_printing';
12
13 @Component({
14     selector: 'eg-print',
15     templateUrl: './print.component.html'
16 })
17
18 export class PrintComponent implements OnInit {
19
20     // Template that requires local processing
21     template: TemplateRef<any>;
22
23     // Context data used for processing the template.
24     context: any;
25
26     // Insertion point for externally-compiled templates
27     htmlContainer: Element;
28
29     isPrinting: boolean;
30
31     printQueue: PrintRequest[];
32
33     // True if Hatch printing is enabled and we're able to talk to Hatch.
34     useHatchPrinting: boolean = null;
35
36     constructor(
37         private renderer: Renderer2,
38         private elm: ElementRef,
39         private store: StoreService,
40         private serverStore: ServerStoreService,
41         private h2txt: HtmlToTxtService,
42         private hatch: HatchService,
43         private toast: ToastService,
44         private strings: StringService,
45         private printer: PrintService) {
46         this.isPrinting = false;
47         this.printQueue = [];
48     }
49
50     ngOnInit() {
51         this.printer.onPrintRequest$.subscribe(
52             printReq => this.handlePrintRequest(printReq));
53
54         this.htmlContainer =
55             this.renderer.selectRootElement('#eg-print-html-container');
56     }
57
58
59     // Returns promise of true if Hatch should be used for printing.
60     // To avoid race conditions, always check this inline before
61     // relaying print requests.
62     checkHatchEnabled(): Promise<boolean> {
63         if (this.useHatchPrinting !== null) {
64             return Promise.resolve(this.useHatchPrinting);
65         }
66
67         return this.serverStore.getItem('eg.hatch.enable.printing')
68             .then(use => this.useHatchPrinting = (use && this.hatch.connect()));
69     }
70
71     // Resolves to true if a) Hatch is usable and b) the requested print
72     // context is not using the 'native brower printing' printer.
73     checkHatchEnabledForRequest(printReq: PrintRequest): Promise<boolean> {
74         return this.checkHatchEnabled().then(enabled => {
75             if (!enabled) { return false; }
76
77             return this.serverStore.getItem(`eg.print.config.${printReq.printContext}`)
78             .then(config => {
79                 return (
80                     !config ||
81                     config.printer !== HATCH_BROWSER_PRINTING_PRINTER
82                 );
83             });
84         });
85     }
86
87     handlePrintRequest(printReq: PrintRequest) {
88
89         if (this.isPrinting) {
90             // Avoid print collisions by queuing requests as needed.
91             this.printQueue.push(printReq);
92             return;
93         }
94
95         // Load the configured print context.
96         let promise = Promise.resolve();
97         if (printReq.templateName) {
98             promise = this.serverStore.getItem(
99                 'eg.print.template_context.' + printReq.templateName)
100             .then(setting => {
101                 if (setting) {
102                     printReq.printContext = setting;
103                 }
104             });
105         }
106
107         this.isPrinting = true;
108         promise.then(_ => {
109
110             if (printReq.printContext === 'no-print') {
111                 console.debug('Skipping print request with No-Print context');
112                 this.reset();
113                 return;
114             }
115
116             this.applyTemplate(printReq).then(() => {
117                 // Give templates a chance to render before printing
118                 setTimeout(() => {
119                     this.dispatchPrint(printReq).then(__ => this.reset());
120                 });
121             });
122         });
123     }
124
125     applyTemplate(printReq: PrintRequest): Promise<any> {
126
127         if (printReq.template) {
128             // Local Angular template.
129             this.template = printReq.template;
130             this.context = {$implicit: printReq.contextData};
131             return Promise.resolve();
132         }
133
134         let promise;
135
136         // Precompiled text
137         if (printReq.text) {
138             promise = Promise.resolve();
139
140         } else if (printReq.templateName || printReq.templateId) {
141             // Server-compiled template
142
143             promise = this.printer.compileRemoteTemplate(printReq).then(
144                 response => {
145                     printReq.text = response.content;
146                     printReq.contentType = response.contentType;
147                 },
148                 err => {
149
150                     if (err && err.notFound) {
151
152                         this.strings.interpolate(
153                             'eg.print.template.not_found',
154                             {name: printReq.templateName}
155                         ).then(msg => this.toast.danger(msg));
156
157                     } else {
158
159                         console.error('Print generation failed', printReq);
160
161                         this.strings.interpolate(
162                             'eg.print.template.error',
163                             {name: printReq.templateName, id: printReq.templateId}
164                         ).then(msg => this.toast.danger(msg));
165                     }
166
167                     return Promise.reject(new Error(
168                         'Error compiling server-hosted print template'));
169                 }
170             );
171
172         } else {
173             console.error('Cannot find template', printReq);
174             return Promise.reject(new Error('Cannot find print template'));
175         }
176
177         return promise.then(() => {
178
179             return this.checkHatchEnabledForRequest(printReq).then(enabled => {
180
181                 // Insert HTML into the browser DOM for in-browser printing.
182                 if (printReq.text && !enabled) {
183
184                     if (printReq.contentType === 'text/plain') {
185                     // Wrap text/plain content in pre's to prevent
186                     // unintended html formatting.
187                         printReq.text = `<pre>${printReq.text}</pre>`;
188                     }
189
190                     this.htmlContainer.innerHTML = printReq.text;
191                 }
192             });
193         });
194     }
195
196     // Clear the print data
197     reset() {
198         this.isPrinting = false;
199         this.template = null;
200         this.context = null;
201         this.htmlContainer.innerHTML = '';
202
203         if (this.printQueue.length) {
204             this.handlePrintRequest(this.printQueue.pop());
205         }
206     }
207
208     dispatchPrint(printReq: PrintRequest): Promise<any> {
209
210         if (!printReq.text) {
211
212             // Extract the print container div from our component markup.
213             const container =
214                 this.elm.nativeElement.querySelector('#eg-print-container');
215
216             // Sometimes the results come from an externally-parsed HTML
217             // template, other times they come from an in-page template.
218             printReq.text = container.innerHTML;
219         }
220
221         // Retain a copy of each printed document in localStorage
222         // so it may be reprinted.
223         this.store.setLocalItem('eg.print.last_printed', {
224             content: printReq.text,
225             context: printReq.printContext,
226             content_type: printReq.contentType,
227             show_dialog: printReq.showDialog
228         });
229
230         return this.checkHatchEnabledForRequest(printReq).then(enabled => {
231             if (enabled) {
232                 this.printViaHatch(printReq);
233             } else {
234                 // Here the needed HTML is already in the page.
235                 window.print();
236             }
237         });
238     }
239
240     printViaHatch(printReq: PrintRequest) {
241         if (!printReq.contentType) {
242             printReq.contentType = 'text/html';
243         }
244
245         // Send a full HTML document to Hatch
246         let html = printReq.text;
247         if (printReq.contentType === 'text/html') {
248             html = `<html><body>${printReq.text}</body></html>`;
249         }
250
251         this.serverStore.getItem(`eg.print.config.${printReq.printContext}`)
252         .then(config => {
253
254             let msg: HatchMessage;
255
256             if (config && config.printer === HATCH_FILE_WRITER_PRINTER) {
257
258                 const text = printReq.contentType === 'text/plain' ?
259                     html : this.h2txt.htmlToTxt(html);
260
261                 msg = new HatchMessage({
262                     action: 'set',
263                     key: `receipt.${printReq.printContext}.txt`,
264                     content: text,
265                     bare: true
266                 });
267
268             } else {
269
270                 msg = new HatchMessage({
271                     action: 'print',
272                     content: html,
273                     settings: config || {},
274                     contentType: 'text/html',
275                     showDialog: printReq.showDialog
276                 });
277             }
278
279             this.hatch.sendRequest(msg).then(
280                 ok  => console.debug('Print request succeeded'),
281                 err => console.warn('Print request failed', err)
282             );
283         });
284     }
285 }
286