From b489e21561d045ed760c85b983fe8fa4e339bca7 Mon Sep 17 00:00:00 2001
From: Bill Erickson <berickxx@gmail.com>
Date: Mon, 17 Sep 2018 11:26:11 -0400
Subject: [PATCH] LP#1793005 Angular6 Hatch support / printer settings

Adds support for Hatch print communication to the Angular(6) app.
Migrates the print preferences settings (eg.print.config.*) from
in-Hatch settings to server-stored workstation settings.

Signed-off-by: Bill Erickson <berickxx@gmail.com>
Signed-off-by: blake <blake@mobiusconsortium.org>
---
 Open-ILS/src/eg2/src/app/common.module.ts          |   2 +
 .../src/eg2/src/app/share/print/hatch.service.ts   | 109 +++++++++++++++++++++
 .../src/eg2/src/app/share/print/print.component.ts |  33 +++++--
 .../src/app/staff/sandbox/sandbox.component.html   |   2 +
 .../eg2/src/app/staff/sandbox/sandbox.component.ts |   8 ++
 Open-ILS/src/sql/Pg/950.data.seed-values.sql       |  39 ++++++++
 .../sql/Pg/upgrade/XXXX.data.hatch-settings.sql    |  43 ++++++++
 Open-ILS/web/js/ui/default/staff/services/hatch.js |   4 +-
 8 files changed, 231 insertions(+), 9 deletions(-)
 create mode 100644 Open-ILS/src/eg2/src/app/share/print/hatch.service.ts
 create mode 100644 Open-ILS/src/sql/Pg/upgrade/XXXX.data.hatch-settings.sql

diff --git a/Open-ILS/src/eg2/src/app/common.module.ts b/Open-ILS/src/eg2/src/app/common.module.ts
index ec06a91b80..f1d4467830 100644
--- a/Open-ILS/src/eg2/src/app/common.module.ts
+++ b/Open-ILS/src/eg2/src/app/common.module.ts
@@ -14,6 +14,7 @@ They do not have to be added to the providers list.
 
 // consider moving these to core...
 import {FormatService, FormatValuePipe} from '@eg/core/format.service';
+import {HatchService} from '@eg/share/print/hatch.service';
 import {PrintService} from '@eg/share/print/print.service';
 
 // Globally available components
@@ -67,6 +68,7 @@ export class EgCommonModule {
             providers: [
                 DatePipe,
                 CurrencyPipe,
+                HatchService,
                 PrintService,
                 FormatService
             ]
diff --git a/Open-ILS/src/eg2/src/app/share/print/hatch.service.ts b/Open-ILS/src/eg2/src/app/share/print/hatch.service.ts
new file mode 100644
index 0000000000..015088765a
--- /dev/null
+++ b/Open-ILS/src/eg2/src/app/share/print/hatch.service.ts
@@ -0,0 +1,109 @@
+import {Injectable, EventEmitter} from '@angular/core';
+
+export class HatchMessage {
+    msgid: number;
+    resolver: (HatchMessage) => void; // promise resolver
+    rejector: (HatchMessage) => void; // promise rejector
+    status: number;
+    message: string; // error message
+    from: string;
+    action: string;
+    settings: any;
+    content: string;
+    // Response from Hatch.
+    response: any;
+    contentType: string;
+    showDialog: boolean;
+
+    constructor(hash: any) {
+        if (hash) {
+            Object.keys(hash).forEach(key => this[key] = hash[key]);
+        }
+    }
+}
+
+@Injectable()
+export class HatchService {
+
+    isAvailable: boolean;
+    msgId: number;
+    messages: {[msgid:number]: HatchMessage};
+
+    constructor() {
+        this.isAvailable = null;
+        this.messages = {};
+        this.msgId = 1;
+    }
+
+    connect(): boolean {
+
+        if (this.isAvailable !== null) {
+            return this.isAvailable;
+        }
+
+        // When the Hatch extension loads, it tacks an attribute onto
+        // the top-level documentElement to indicate it's available.
+        if (!window.document.documentElement.getAttribute('hatch-is-open')) {
+            console.warn('Could not connect to Hatch');
+            return this.isAvailable = false;
+        }
+
+        window.addEventListener('message', event => {
+
+            // We only accept messages from our own content script.
+            if (event.source !== window) { return; }
+
+            // We only care about messages from the Hatch extension.
+            if (event.data && event.data.from === 'extension') {
+
+                // Avoid logging full Hatch responses. they can get large.
+                console.debug(
+                    `Hatch responded to message ID ${event.data.msgid}`);
+
+                this.handleResponse(event.data);
+            }
+        }); 
+
+        return this.isAvailable = true;
+    }
+
+    // Send a request from the browser to Hatch.
+    sendRequest(msg: HatchMessage): Promise<HatchMessage> {
+        if (this.isAvailable === false) {
+            return Promise.reject('Hatch is not connected');
+        }
+
+        msg.msgid = this.msgId++;
+        msg.from = 'page';
+        this.messages[msg.msgid] = msg;
+        window.postMessage(msg, window.location.origin);
+
+        return new Promise((resolve, reject) => {
+            msg.resolver = resolve;
+            msg.rejector = reject;
+        });
+    }
+
+    // Handle the data sent back to the browser from Hatch.
+    handleResponse(data: any) {
+
+        const msg = this.messages[data.msgid]; 
+        if (!msg) {
+            console.warn(`No Hatch request found with ID ${data.msgid}`);
+            return;
+        }
+
+        delete this.messages[data.msgid];
+        msg.response = data.content;
+        msg.message = data.message;
+        msg.status = Number(data.status);
+
+        if (msg.status === 200) {
+            msg.resolver(msg);
+        } else {
+            console.error(`Hatch request returned status ${msg.status}`, msg);
+            msg.rejector(msg);
+        }
+    }
+}
+
diff --git a/Open-ILS/src/eg2/src/app/share/print/print.component.ts b/Open-ILS/src/eg2/src/app/share/print/print.component.ts
index 4f6994982b..e7754abbd3 100644
--- a/Open-ILS/src/eg2/src/app/share/print/print.component.ts
+++ b/Open-ILS/src/eg2/src/app/share/print/print.component.ts
@@ -1,6 +1,8 @@
 import {Component, OnInit, TemplateRef, ElementRef, Renderer2} from '@angular/core';
 import {PrintService, PrintRequest} from './print.service';
 import {StoreService} from '@eg/core/store.service';
+import {ServerStoreService} from '@eg/core/server-store.service';
+import {HatchService, HatchMessage} from './hatch.service';
 
 @Component({
     selector: 'eg-print',
@@ -26,6 +28,8 @@ export class PrintComponent implements OnInit {
         private renderer: Renderer2,
         private elm: ElementRef,
         private store: StoreService,
+        private serverStore: ServerStoreService,
+        private hatch: HatchService,
         private printer: PrintService) {
         this.isPrinting = false;
         this.printQueue = [];
@@ -67,7 +71,7 @@ export class PrintComponent implements OnInit {
             return;
         }
 
-        if (printReq.text && true /* !this.hatch.isActive */) {
+        if (printReq.text && !this.useHatch()) {
             // Insert HTML into the browser DOM for in-browser printing only.
 
             if (printReq.contentType === 'text/plain') {
@@ -109,7 +113,7 @@ export class PrintComponent implements OnInit {
             show_dialog: printReq.showDialog
         });
 
-        if (0 /* this.hatch.isActive */) {
+        if (this.useHatch()) {
             this.printViaHatch(printReq);
         } else {
             // Here the needed HTML is already in the page.
@@ -117,17 +121,32 @@ export class PrintComponent implements OnInit {
         }
     }
 
+    useHatch(): boolean {
+        return this.store.getLocalItem('eg.hatch.enable.printing')
+            && this.hatch.connect();
+    }
+
     printViaHatch(printReq: PrintRequest) {
 
         // Send a full HTML document to Hatch
         const html = `<html><body>${printReq.text}</body></html>`;
 
-        /*
-        this.hatch.print({
-            printContext: printReq.printContext,
-            content: html
+        this.serverStore.getItem(`eg.print.config.${printReq.printContext}`)
+        .then(config => {
+
+            const msg = new HatchMessage({
+                action: 'print',
+                content: html,
+                settings: config || {},
+                contentType: 'text/html',
+                showDialog: printReq.showDialog
+            });
+
+            this.hatch.sendRequest(msg).then(
+                ok  => console.debug('Print request succeeded'),
+                err => console.warn('Print request failed', err)
+            );
         });
-        */
     }
 }
 
diff --git a/Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.html b/Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.html
index 84e127e3c0..54cd87d11e 100644
--- a/Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.html
+++ b/Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.html
@@ -114,6 +114,8 @@
 <button class="btn btn-secondary" (click)="doPrint()">Test Print</button>
 <ng-template #printTemplate let-context>Hello, {{context.world}}!</ng-template>
 
+<button class="btn btn-secondary" (click)="printWithDialog()">Print with dialog</button>
+
 <br/><br/>
 <div class="row">
   <div class="col-lg-3">
diff --git a/Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.ts b/Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.ts
index 9b058cd5b1..543e3cb72b 100644
--- a/Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.ts
+++ b/Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.ts
@@ -187,7 +187,15 @@ export class SandboxComponent implements OnInit {
             text: '<b>hello</b>',
             printContext: 'default'
         });
+    }
 
+    printWithDialog() {
+        this.printer.print({
+            template: this.printTemplate,
+            contextData: {world : this.world},
+            printContext: 'default',
+            showDialog: true
+        });
     }
 
     changeDate(date) {
diff --git a/Open-ILS/src/sql/Pg/950.data.seed-values.sql b/Open-ILS/src/sql/Pg/950.data.seed-values.sql
index 3c8a257018..928cdbe766 100644
--- a/Open-ILS/src/sql/Pg/950.data.seed-values.sql
+++ b/Open-ILS/src/sql/Pg/950.data.seed-values.sql
@@ -19874,6 +19874,45 @@ INSERT INTO config.org_unit_setting_type
         'bool'
     );
 
+
+INSERT INTO config.workstation_setting_type (name, grp, datatype, label)
+VALUES (
+    'eg.print.config.default', 'gui', 'object',
+    oils_i18n_gettext (
+        'eg.print.config.default',
+        'Print config for default context',
+        'cwst', 'label'
+    )
+), (
+    'eg.print.config.receipt', 'gui', 'object',
+    oils_i18n_gettext (
+        'eg.print.config.receipt',
+        'Print config for receipt context',
+        'cwst', 'label'
+    )
+), (
+    'eg.print.config.label', 'gui', 'object',
+    oils_i18n_gettext (
+        'eg.print.config.label',
+        'Print config for label context',
+        'cwst', 'label'
+    )
+), (
+    'eg.print.config.mail', 'gui', 'object',
+    oils_i18n_gettext (
+        'eg.print.config.mail',
+        'Print config for mail context',
+        'cwst', 'label'
+    )
+), (
+    'eg.print.config.offline', 'gui', 'object',
+    oils_i18n_gettext (
+        'eg.print.config.offline',
+        'Print config for offline context',
+        'cwst', 'label'
+    )
+);
+
 INSERT INTO config.usr_activity_type 
     (id, ewhat, ehow, egroup, enabled, transient, label)
 VALUES (
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.data.hatch-settings.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.data.hatch-settings.sql
new file mode 100644
index 0000000000..15d9513af7
--- /dev/null
+++ b/Open-ILS/src/sql/Pg/upgrade/XXXX.data.hatch-settings.sql
@@ -0,0 +1,43 @@
+BEGIN;
+
+--SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version);
+
+INSERT INTO config.workstation_setting_type (name, grp, datatype, label)
+VALUES (
+    'eg.print.config.default', 'gui', 'object',
+    oils_i18n_gettext (
+        'eg.print.config.default',
+        'Print config for default context',
+        'cwst', 'label'
+    )
+), (
+    'eg.print.config.receipt', 'gui', 'object',
+    oils_i18n_gettext (
+        'eg.print.config.receipt',
+        'Print config for receipt context',
+        'cwst', 'label'
+    )
+), (
+    'eg.print.config.label', 'gui', 'object',
+    oils_i18n_gettext (
+        'eg.print.config.label',
+        'Print config for label context',
+        'cwst', 'label'
+    )
+), (
+    'eg.print.config.mail', 'gui', 'object',
+    oils_i18n_gettext (
+        'eg.print.config.mail',
+        'Print config for mail context',
+        'cwst', 'label'
+    )
+), (
+    'eg.print.config.offline', 'gui', 'object',
+    oils_i18n_gettext (
+        'eg.print.config.offline',
+        'Print config for offline context',
+        'cwst', 'label'
+    )
+);
+
+COMMIT;
diff --git a/Open-ILS/web/js/ui/default/staff/services/hatch.js b/Open-ILS/web/js/ui/default/staff/services/hatch.js
index 467091aaae..5b6f9235d2 100644
--- a/Open-ILS/web/js/ui/default/staff/services/hatch.js
+++ b/Open-ILS/web/js/ui/default/staff/services/hatch.js
@@ -200,11 +200,11 @@ angular.module('egCoreMod')
     }
 
     service.getPrintConfig = function(context) {
-        return service.getRemoteItem('eg.print.config.' + context);
+        return service.getItem('eg.print.config.' + context);
     }
 
     service.setPrintConfig = function(context, config) {
-        return service.setRemoteItem('eg.print.config.' + context, config);
+        return service.setItem('eg.print.config.' + context, config);
     }
 
     service.getPrinterOptions = function(name) {
-- 
2.11.0