From f9c30ee9bf9fec40eb7509eda2e8bf4826fad412 Mon Sep 17 00:00:00 2001 From: Cellivar <1441553+Cellivar@users.noreply.github.com> Date: Mon, 10 Feb 2025 22:35:34 -0800 Subject: [PATCH] Show a clear error on OS driver errors --- demo/advanced.html | 77 ++++++++++++++-------- demo/editor.html | 67 ++++++++++++------- demo/index.html | 139 +++++++++++++++++++++++++++++++++++---- demo/test_advanced.ts | 77 +++++++++++++++------- demo/test_editor.ts | 71 +++++++++++++------- demo/test_index.ts | 148 ++++++++++++++++++++++++++++++++++++++---- 6 files changed, 456 insertions(+), 123 deletions(-) diff --git a/demo/advanced.html b/demo/advanced.html index 768206f..3f4781f 100644 --- a/demo/advanced.html +++ b/demo/advanced.html @@ -75,8 +75,8 @@ // We'll drop these into the Window object so we can play with them in // the DevTools console if we want to. - window.WebLabel = WebLabel; - window.WebDevices = WebDevices; + (window as any).WebLabel = WebLabel; + (window as any).WebDevices = WebDevices; // For this demo we're going to make use of the USB printer manager // so it can take care of concerns like the USB connect and disconnect events. @@ -104,11 +104,50 @@ // We'll wire up some basic event listeners to the printer manager. // First, a button to prompt a user to add a printer. const addPrinterBtn = document.getElementById('addprinter')!; - addPrinterBtn.addEventListener('click', async () => printerMgr.promptForNewDevice()); + addPrinterBtn.addEventListener('click', async () => { + try { + await printerMgr.promptForNewDevice(); + } catch (e) { + if (e instanceof WebDevices.DriverAccessDeniedError) { + deviceErrorAlert(); + } else { + throw e; + } + } + }); + + // And a function to call if it fails + function deviceErrorAlert() { + // This happens when the operating system didn't let Chrome connect. + // Usually either another tab is open talking to the device, or the driver + // is already loaded by another application. + showAlert( + 'danger', + 'alert-printer-comm-error', + `Operating system denied device access`, + `

Chrome wasn't allowed to connect to a device. This usually happens because: +

+ Fix the issue and re-connect to the device.

` + ); + } // Next a button to manually refresh all printers, just in case. const refreshPrinterBtn = document.getElementById('refreshPrinters')!; - refreshPrinterBtn.addEventListener('click', async () => printerMgr.forceReconnect()); + refreshPrinterBtn.addEventListener('click', async () => { + try { + await printerMgr.forceReconnect(); + } catch (e) { + if (e instanceof WebDevices.DriverAccessDeniedError) { + deviceErrorAlert(); + } else { + throw e; + } + } + }); // Next we wire up some events on the UsbDeviceManager itself. printerMgr.addEventListener('connectedDevice', ({ detail }) => { @@ -174,13 +213,13 @@ // commands or configs specific to their printers. // Sensors modalZplRibbonTHold: HTMLInputElement - modalZplRibbonGain : HTMLInputElement + modalZplRibbonGain : HTMLInputElement modalZplWebTHold : HTMLInputElement modalZplWebMedia : HTMLInputElement - modalZplTransGain : HTMLInputElement + modalZplTransGain : HTMLInputElement modalZplMarkTHold : HTMLInputElement modalZplMarkMedia : HTMLInputElement - modalZplMarkGain : HTMLInputElement + modalZplMarkGain : HTMLInputElement modalZplWithSensorGraph: HTMLInputElement @@ -748,33 +787,17 @@

${titleHtml}

// and let it take over the UI. await app.init(); - // Make the TypeScript type system happy by adding a property to the Window object. - declare global { - interface Window { label_app: BasicLabelDesignerApp } - } // Now we can access our printer in the dev console if we want to mess with it! - window.label_app = app; + (window as any).label_app = app; // Now we'll fire the reconnect since our UI is wired up. try { await printerMgr.forceReconnect(); } catch (e) { if (e instanceof WebDevices.DriverAccessDeniedError) { - // This happens when the operating system didn't let Chrome connect. - // Usually either another tab is open talking to the device, or the driver - // is already loaded by another application. - showAlert( - 'danger', - 'alert-printer-comm-error', - `Operating system refused device access`, - `

This usually happens for one of these reasons: -

- Fix the issue and re-connect to the device.

` - ); + deviceErrorAlert(); + } else { + throw e; } } diff --git a/demo/editor.html b/demo/editor.html index d1ed73e..b80f7da 100644 --- a/demo/editor.html +++ b/demo/editor.html @@ -72,9 +72,9 @@ // Internally it makes use of an HTML canvas, and thus it's easy to plug into // WebZLP for label designing! // Import the canvas editor lib and create a canvas to use for printing. - import * as Editor from 'fabricjs-label-editor' import * as WebLabel from 'webzlp'; import * as WebDevices from 'web-device-mux'; + import * as Editor from 'fabricjs-label-editor' (window as any).FabricEditor = Editor; (window as any).WebLabel = WebLabel; (window as any).WebDevices = WebDevices; @@ -85,9 +85,6 @@ // For this demo we're going to make use of the USB printer manager // so it can take care of concerns like the USB connect and disconnect events. - // For this demo we're going to make use of the USB printer manager - // so it can take care of concerns like the USB connect and disconnect events. - // We'll set a type alias so it's easier to read our code type PrinterManager = WebDevices.UsbDeviceManager; // Then we'll construct one to use @@ -111,11 +108,50 @@ // We'll wire up some basic event listeners to the printer manager. // First, a button to prompt a user to add a printer. const addPrinterBtn = document.getElementById('addprinter')!; - addPrinterBtn.addEventListener('click', async () => printerMgr.promptForNewDevice()); + addPrinterBtn.addEventListener('click', async () => { + try { + await printerMgr.promptForNewDevice(); + } catch (e) { + if (e instanceof WebDevices.DriverAccessDeniedError) { + deviceErrorAlert(); + } else { + throw e; + } + } + }); + + // And a function to call if it fails + function deviceErrorAlert() { + // This happens when the operating system didn't let Chrome connect. + // Usually either another tab is open talking to the device, or the driver + // is already loaded by another application. + showAlert( + 'danger', + 'alert-printer-comm-error', + `Operating system denied device access`, + `

Chrome wasn't allowed to connect to a device. This usually happens because: +

+ Fix the issue and re-connect to the device.

` + ); + } // Next a button to manually refresh all printers, just in case. const refreshPrinterBtn = document.getElementById('refreshPrinters')!; - refreshPrinterBtn.addEventListener('click', async () => printerMgr.forceReconnect()); + refreshPrinterBtn.addEventListener('click', async () => { + try { + await printerMgr.forceReconnect(); + } catch (e) { + if (e instanceof WebDevices.DriverAccessDeniedError) { + deviceErrorAlert(); + } else { + throw e; + } + } + }); // Get some bookkeeping out of the way.. // First we create an interface to describe our settings form. @@ -156,7 +192,6 @@ modalZplHeadCloseAction: HTMLSelectElement } - // A function to find and hide any alerts for a given alert ID. function hideAlerts(alertId: string) { const existingAlerts = document.getElementById('printerAlertSpace')?.querySelectorAll(`.${alertId}`) ?? []; @@ -685,21 +720,9 @@

${titleHtml}

await printerMgr.forceReconnect(); } catch (e) { if (e instanceof WebDevices.DriverAccessDeniedError) { - // This happens when the operating system didn't let Chrome connect. - // Usually either another tab is open talking to the device, or the driver - // is already loaded by another application. - showAlert( - 'danger', - 'alert-printer-comm-error', - `Operating system refused device access`, - `

This usually happens for one of these reasons: -

- Fix the issue and re-connect to the device.

` - ); + deviceErrorAlert(); + } else { + throw e; } } diff --git a/demo/index.html b/demo/index.html index ae9ea4f..b91d4eb 100644 --- a/demo/index.html +++ b/demo/index.html @@ -85,8 +85,8 @@ // We'll drop these into the Window object so we can play with them in // the DevTools console if we want to. - window.WebLabel = WebLabel; - window.WebDevices = WebDevices; + (window as any).WebLabel = WebLabel; + (window as any).WebDevices = WebDevices; // For this demo we're going to make use of the USB printer manager // so it can take care of concerns like the USB connect and disconnect events. @@ -114,14 +114,55 @@ // We'll wire up some basic event listeners to the printer manager. // First, a button to prompt a user to add a printer. const addPrinterBtn = document.getElementById('addprinter')!; - addPrinterBtn.addEventListener('click', async () => printerMgr.promptForNewDevice()); + addPrinterBtn.addEventListener('click', async () => { + try { + await printerMgr.promptForNewDevice(); + } catch (e) { + if (e instanceof WebDevices.DriverAccessDeniedError) { + deviceErrorAlert(); + } else { + throw e; + } + } + }); + + // And a function to call if it fails + function deviceErrorAlert() { + // This happens when the operating system didn't let Chrome connect. + // Usually either another tab is open talking to the device, or the driver + // is already loaded by another application. + showAlert( + 'danger', + 'alert-printer-comm-error', + `Operating system denied device access`, + `

Chrome wasn't allowed to connect to a device. This usually happens because: +

+ Fix the issue and re-connect to the device.

` + ); + } // Next a button to manually refresh all printers, just in case. const refreshPrinterBtn = document.getElementById('refreshPrinters')!; - refreshPrinterBtn.addEventListener('click', async () => printerMgr.forceReconnect()); + refreshPrinterBtn.addEventListener('click', async () => { + try { + await printerMgr.forceReconnect(); + } catch (e) { + if (e instanceof WebDevices.DriverAccessDeniedError) { + deviceErrorAlert(); + } else { + throw e; + } + } + }); // Next we wire up some events on the UsbDeviceManager itself. printerMgr.addEventListener('connectedDevice', ({ detail }) => { + + // Let's print some details about the printer that was connected. const printer = detail.device; console.log('New printer is a', printer.printerModel); const config = printer.printerOptions; @@ -134,6 +175,10 @@ config.mediaLengthInches, 'in long'); console.log('Printer media mode is', WebLabel.MediaMediaGapDetectionMode[config.mediaGapDetectMode]); + + // You can do stuff with the printer that connected. It's a good idea + // to immediately query the printer for its status. + printer.sendDocument(WebLabel.ReadyToPrintDocuments.printerStatusDocument); }); // There's also an event that will tell you when a printer disconnects. @@ -168,6 +213,39 @@ modalWithAutosense : HTMLInputElement } + // A function to find and hide any alerts for a given alert ID. + function hideAlerts(alertId: string) { + const existingAlerts = document.getElementById('printerAlertSpace')?.querySelectorAll(`.${alertId}`) ?? []; + existingAlerts.forEach((a: Element) => { a.remove(); }); + } + + // A function to make it easier to show alerts + function showAlert( + level: 'warning' | 'danger', + alertId: string, + titleHtml: string, + bodyHtml: string, + closedCallback = () => {} + ) { + hideAlerts(alertId); + + // Create the bootstrap alert div with the provided content + const alertWrapper = document.createElement('div'); + alertWrapper.classList.add("alert", `alert-${level}`, "alert-dismissible", "fade", "show", alertId); + alertWrapper.id = alertId; + alertWrapper.role = "alert"; + alertWrapper.innerHTML = ` + +

${titleHtml}

+ ${bodyHtml}`; + + // Add it to the document and activate it + document.getElementById('printerAlertSpace')?.appendChild(alertWrapper); + new bootstrap.Alert(alertWrapper); + + alertWrapper.addEventListener('closed.bs.alert', closedCallback); + } + // The app's logic is wrapped in a class just for ease of reading. class BasicLabelDesignerApp { constructor( @@ -187,9 +265,34 @@ // Add a second set of event listeners for printer connect and disconnect to redraw // the printer list when it changes. - this.manager.addEventListener('connectedDevice', () => { + this.manager.addEventListener('connectedDevice', ({ detail }) => { this.activePrinterIndex = -1; this.redrawPrinterButtons(); + + // Printers themselves also have events, let's show an alert on errors. + const printer = detail.device; + printer.addEventListener('reportedError', ({ detail: msg }) => { + // Use the same ID so there's only one error message per printer. + const alertId = `alert-printererror-${printer.printerSerial}`; + hideAlerts(alertId); + + // Error messages are also status messages, such as indicating no problem. + if (msg.errors.size === 0 || msg.errors.has(WebLabel.ErrorState.NoError)) { return; } + + showAlert( + // Show a warning for this printer + 'warning', + alertId, + `Printer ${printer.printerSerial} has an error`, + // There can be multiple errors, just show their raw values. A better + // application would use these for good messages! + `

+
+

Fix the issue, then dismiss this alert to check the status again.

`, + // And when the alert is dismissed, check the status again! + () => printer.sendDocument(WebLabel.ReadyToPrintDocuments.printerStatusDocument) + ); + }); }); this.manager.addEventListener('disconnectedDevice', () => { this.activePrinterIndex = -1; @@ -247,7 +350,7 @@ /** Display the configuration for a printer. */ public showConfigModal(printer: WebLabel.LabelPrinterUsb, printerIdx: number) { - if (printer == undefined) { + if (printer === undefined) { return; } const config = printer.printerOptions; @@ -507,10 +610,16 @@ // And send the whole shebang to the printer! await printer.sendDocument(doc); + // Then get the updated printer info.. + await printer.sendDocument(WebLabel.ReadyToPrintDocuments.configDocument); + form.modalSubmit.removeAttribute("disabled"); form.modalCancel.removeAttribute("disabled"); this.activePrinterIndex = printerIdx; this.configModalHandle.hide(); + + // Redraw the buttons with the updated config + this.redrawPrinterButtons(); } } @@ -526,16 +635,20 @@ // and let it take over the UI. await app.init(); - // Make the TypeScript type system happy by adding a property to the Window object. - declare global { - interface Window { printer_app: BasicLabelDesignerApp } - } // Now we can access our printer in the dev console if we want to mess with it! - window.printer_app = app; + (window as any).label_app = app; // Now we'll fire the reconnect since our UI is wired up. - await printerMgr.forceReconnect(); - + try { + await printerMgr.forceReconnect(); + } catch (e) { + if (e instanceof WebDevices.DriverAccessDeniedError) { + deviceErrorAlert(); + } else { + throw e; + } + } + // We're done here. Bring in the dancing lobsters. diff --git a/demo/test_advanced.ts b/demo/test_advanced.ts index e866794..8bb669c 100644 --- a/demo/test_advanced.ts +++ b/demo/test_advanced.ts @@ -1,13 +1,19 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import * as WebLabel from '../src/index.js'; -import * as WebDevices from 'web-device-mux'; import bootstrap from 'bootstrap'; // This file exists to test the index.html's typescript. Unfortunately there isn't // a good way to configure Visual Studio Code to, well, treat it as typescript. //////////////////////////////////////////////////////////////////////////////// - // First import the lib! -//import * as WebLabel from 'web-receiptline-printer'; +// This is our lib in this repo here +import * as WebLabel from '../src/index.js'; + +// This is a utility lib we'll be using that makes it easier to use devices. +import * as WebDevices from 'web-device-mux'; + +// We'll drop these into the Window object so we can play with them in +// the DevTools console if we want to. +(window as any).WebLabel = WebLabel; +(window as any).WebDevices = WebDevices; // For this demo we're going to make use of the USB printer manager // so it can take care of concerns like the USB connect and disconnect events. @@ -35,11 +41,50 @@ const printerMgr: PrinterManager = new WebDevices.UsbDeviceManager( // We'll wire up some basic event listeners to the printer manager. // First, a button to prompt a user to add a printer. const addPrinterBtn = document.getElementById('addprinter')!; -addPrinterBtn.addEventListener('click', async () => printerMgr.promptForNewDevice()); +addPrinterBtn.addEventListener('click', async () => { + try { + await printerMgr.promptForNewDevice(); + } catch (e) { + if (e instanceof WebDevices.DriverAccessDeniedError) { + deviceErrorAlert(); + } else { + throw e; + } + } +}); + +// And a function to call if it fails +function deviceErrorAlert() { + // This happens when the operating system didn't let Chrome connect. + // Usually either another tab is open talking to the device, or the driver + // is already loaded by another application. + showAlert( + 'danger', + 'alert-printer-comm-error', + `Operating system denied device access`, + `

Chrome wasn't allowed to connect to a device. This usually happens because: +

+ Fix the issue and re-connect to the device.

` + ); +} // Next a button to manually refresh all printers, just in case. const refreshPrinterBtn = document.getElementById('refreshPrinters')!; -refreshPrinterBtn.addEventListener('click', async () => printerMgr.forceReconnect()); +refreshPrinterBtn.addEventListener('click', async () => { + try { + await printerMgr.forceReconnect(); + } catch (e) { + if (e instanceof WebDevices.DriverAccessDeniedError) { + deviceErrorAlert(); + } else { + throw e; + } + } +}); // Next we wire up some events on the UsbDeviceManager itself. printerMgr.addEventListener('connectedDevice', ({ detail }) => { @@ -120,7 +165,6 @@ interface ConfigModalForm extends HTMLCollection { modalZplHeadCloseAction: HTMLSelectElement } - // A function to find and hide any alerts for a given alert ID. function hideAlerts(alertId: string) { const existingAlerts = document.getElementById('printerAlertSpace')?.querySelectorAll(`.${alertId}`) ?? []; @@ -679,7 +723,6 @@ const app = new BasicLabelDesignerApp(printerMgr, btnContainer, labelForm, label // and let it take over the UI. await app.init(); - // Now we can access our printer in the dev console if we want to mess with it! (window as any).label_app = app; @@ -688,21 +731,9 @@ try { await printerMgr.forceReconnect(); } catch (e) { if (e instanceof WebDevices.DriverAccessDeniedError) { - // This happens when the operating system didn't let Chrome connect. - // Usually either another tab is open talking to the device, or the driver - // is already loaded by another application. - showAlert( - 'danger', - 'alert-printer-comm-error', - `Operating system refused device access`, - `

This usually happens for one of these reasons: -

- Fix the issue and re-connect to the device.

` - ); + deviceErrorAlert(); + } else { + throw e; } } diff --git a/demo/test_editor.ts b/demo/test_editor.ts index e35aa8a..ffef98b 100644 --- a/demo/test_editor.ts +++ b/demo/test_editor.ts @@ -1,7 +1,4 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import * as WebLabel from '../src/index.js'; -import * as WebDevices from 'web-device-mux'; -import * as Editor from '../../fabricjs-label-editor/lib/index.js' import bootstrap from 'bootstrap'; // This file exists to test the index.html's typescript. Unfortunately there isn't // a good way to configure Visual Studio Code to, well, treat it as typescript. @@ -11,9 +8,9 @@ import bootstrap from 'bootstrap'; // Internally it makes use of an HTML canvas, and thus it's easy to plug into // WebZLP for label designing! // Import the canvas editor lib and create a canvas to use for printing. -// import * as Editor from 'fabricjs-label-editor' -// import * as WebLabel from 'webzlp'; -// import * as WebDevices from 'web-device-mux'; +import * as WebLabel from '../src/index.js'; +import * as WebDevices from 'web-device-mux'; +import * as Editor from '../../fabricjs-label-editor/lib/index.js' (window as any).FabricEditor = Editor; (window as any).WebLabel = WebLabel; (window as any).WebDevices = WebDevices; @@ -47,11 +44,50 @@ const printerMgr: PrinterManager = new WebDevices.UsbDeviceManager( // We'll wire up some basic event listeners to the printer manager. // First, a button to prompt a user to add a printer. const addPrinterBtn = document.getElementById('addprinter')!; -addPrinterBtn.addEventListener('click', async () => printerMgr.promptForNewDevice()); +addPrinterBtn.addEventListener('click', async () => { + try { + await printerMgr.promptForNewDevice(); + } catch (e) { + if (e instanceof WebDevices.DriverAccessDeniedError) { + deviceErrorAlert(); + } else { + throw e; + } + } +}); + +// And a function to call if it fails +function deviceErrorAlert() { + // This happens when the operating system didn't let Chrome connect. + // Usually either another tab is open talking to the device, or the driver + // is already loaded by another application. + showAlert( + 'danger', + 'alert-printer-comm-error', + `Operating system denied device access`, + `

Chrome wasn't allowed to connect to a device. This usually happens because: +

+ Fix the issue and re-connect to the device.

` + ); +} // Next a button to manually refresh all printers, just in case. const refreshPrinterBtn = document.getElementById('refreshPrinters')!; -refreshPrinterBtn.addEventListener('click', async () => printerMgr.forceReconnect()); +refreshPrinterBtn.addEventListener('click', async () => { + try { + await printerMgr.forceReconnect(); + } catch (e) { + if (e instanceof WebDevices.DriverAccessDeniedError) { + deviceErrorAlert(); + } else { + throw e; + } + } +}); // Get some bookkeeping out of the way.. // First we create an interface to describe our settings form. @@ -92,7 +128,6 @@ interface ConfigModalForm extends HTMLCollection { modalZplHeadCloseAction: HTMLSelectElement } - // A function to find and hide any alerts for a given alert ID. function hideAlerts(alertId: string) { const existingAlerts = document.getElementById('printerAlertSpace')?.querySelectorAll(`.${alertId}`) ?? []; @@ -621,21 +656,9 @@ try { await printerMgr.forceReconnect(); } catch (e) { if (e instanceof WebDevices.DriverAccessDeniedError) { - // This happens when the operating system didn't let Chrome connect. - // Usually either another tab is open talking to the device, or the driver - // is already loaded by another application. - showAlert( - 'danger', - 'alert-printer-comm-error', - `Operating system refused device access`, - `

This usually happens for one of these reasons: -

- Fix the issue and re-connect to the device.

` - ); + deviceErrorAlert(); + } else { + throw e; } } diff --git a/demo/test_index.ts b/demo/test_index.ts index 6783638..a2ae490 100644 --- a/demo/test_index.ts +++ b/demo/test_index.ts @@ -1,12 +1,19 @@ -import * as WebLabel from '../src/index.js'; -import * as WebDevices from 'web-device-mux'; +/* eslint-disable @typescript-eslint/no-explicit-any */ import bootstrap from 'bootstrap'; // This file exists to test the index.html's typescript. Unfortunately there isn't // a good way to configure Visual Studio Code to, well, treat it as typescript. //////////////////////////////////////////////////////////////////////////////// - // First import the lib! -//import * as WebLabel from 'web-receiptline-printer'; +// This is our lib in this repo here +import * as WebLabel from '../src/index.js'; + +// This is a utility lib we'll be using that makes it easier to use devices. +import * as WebDevices from 'web-device-mux'; + +// We'll drop these into the Window object so we can play with them in +// the DevTools console if we want to. +(window as any).WebLabel = WebLabel; +(window as any).WebDevices = WebDevices; // For this demo we're going to make use of the USB printer manager // so it can take care of concerns like the USB connect and disconnect events. @@ -34,14 +41,55 @@ const printerMgr: PrinterManager = new WebDevices.UsbDeviceManager( // We'll wire up some basic event listeners to the printer manager. // First, a button to prompt a user to add a printer. const addPrinterBtn = document.getElementById('addprinter')!; -addPrinterBtn.addEventListener('click', async () => printerMgr.promptForNewDevice()); +addPrinterBtn.addEventListener('click', async () => { + try { + await printerMgr.promptForNewDevice(); + } catch (e) { + if (e instanceof WebDevices.DriverAccessDeniedError) { + deviceErrorAlert(); + } else { + throw e; + } + } +}); + +// And a function to call if it fails +function deviceErrorAlert() { + // This happens when the operating system didn't let Chrome connect. + // Usually either another tab is open talking to the device, or the driver + // is already loaded by another application. + showAlert( + 'danger', + 'alert-printer-comm-error', + `Operating system denied device access`, + `

Chrome wasn't allowed to connect to a device. This usually happens because: +

+ Fix the issue and re-connect to the device.

` + ); +} // Next a button to manually refresh all printers, just in case. const refreshPrinterBtn = document.getElementById('refreshPrinters')!; -refreshPrinterBtn.addEventListener('click', async () => printerMgr.forceReconnect()); +refreshPrinterBtn.addEventListener('click', async () => { + try { + await printerMgr.forceReconnect(); + } catch (e) { + if (e instanceof WebDevices.DriverAccessDeniedError) { + deviceErrorAlert(); + } else { + throw e; + } + } +}); // Next we wire up some events on the UsbDeviceManager itself. printerMgr.addEventListener('connectedDevice', ({ detail }) => { + + // Let's print some details about the printer that was connected. const printer = detail.device; console.log('New printer is a', printer.printerModel); const config = printer.printerOptions; @@ -54,6 +102,10 @@ printerMgr.addEventListener('connectedDevice', ({ detail }) => { config.mediaLengthInches, 'in long'); console.log('Printer media mode is', WebLabel.MediaMediaGapDetectionMode[config.mediaGapDetectMode]); + + // You can do stuff with the printer that connected. It's a good idea + // to immediately query the printer for its status. + printer.sendDocument(WebLabel.ReadyToPrintDocuments.printerStatusDocument); }); // There's also an event that will tell you when a printer disconnects. @@ -88,6 +140,39 @@ interface ConfigModalForm extends HTMLCollection { modalWithAutosense : HTMLInputElement } +// A function to find and hide any alerts for a given alert ID. +function hideAlerts(alertId: string) { + const existingAlerts = document.getElementById('printerAlertSpace')?.querySelectorAll(`.${alertId}`) ?? []; + existingAlerts.forEach((a: Element) => { a.remove(); }); +} + +// A function to make it easier to show alerts +function showAlert( + level: 'warning' | 'danger', + alertId: string, + titleHtml: string, + bodyHtml: string, + closedCallback = () => {} +) { + hideAlerts(alertId); + + // Create the bootstrap alert div with the provided content + const alertWrapper = document.createElement('div'); + alertWrapper.classList.add("alert", `alert-${level}`, "alert-dismissible", "fade", "show", alertId); + alertWrapper.id = alertId; + alertWrapper.role = "alert"; + alertWrapper.innerHTML = ` + +

${titleHtml}

+ ${bodyHtml}`; + + // Add it to the document and activate it + document.getElementById('printerAlertSpace')?.appendChild(alertWrapper); + new bootstrap.Alert(alertWrapper); + + alertWrapper.addEventListener('closed.bs.alert', closedCallback); +} + // The app's logic is wrapped in a class just for ease of reading. class BasicLabelDesignerApp { constructor( @@ -107,9 +192,34 @@ class BasicLabelDesignerApp { // Add a second set of event listeners for printer connect and disconnect to redraw // the printer list when it changes. - this.manager.addEventListener('connectedDevice', () => { + this.manager.addEventListener('connectedDevice', ({ detail }) => { this.activePrinterIndex = -1; this.redrawPrinterButtons(); + + // Printers themselves also have events, let's show an alert on errors. + const printer = detail.device; + printer.addEventListener('reportedError', ({ detail: msg }) => { + // Use the same ID so there's only one error message per printer. + const alertId = `alert-printererror-${printer.printerSerial}`; + hideAlerts(alertId); + + // Error messages are also status messages, such as indicating no problem. + if (msg.errors.size === 0 || msg.errors.has(WebLabel.ErrorState.NoError)) { return; } + + showAlert( + // Show a warning for this printer + 'warning', + alertId, + `Printer ${printer.printerSerial} has an error`, + // There can be multiple errors, just show their raw values. A better + // application would use these for good messages! + `

+
+

Fix the issue, then dismiss this alert to check the status again.

`, + // And when the alert is dismissed, check the status again! + () => printer.sendDocument(WebLabel.ReadyToPrintDocuments.printerStatusDocument) + ); + }); }); this.manager.addEventListener('disconnectedDevice', () => { this.activePrinterIndex = -1; @@ -167,7 +277,7 @@ class BasicLabelDesignerApp { /** Display the configuration for a printer. */ public showConfigModal(printer: WebLabel.LabelPrinterUsb, printerIdx: number) { - if (printer == undefined) { + if (printer === undefined) { return; } const config = printer.printerOptions; @@ -427,10 +537,16 @@ class BasicLabelDesignerApp { // And send the whole shebang to the printer! await printer.sendDocument(doc); + // Then get the updated printer info.. + await printer.sendDocument(WebLabel.ReadyToPrintDocuments.configDocument); + form.modalSubmit.removeAttribute("disabled"); form.modalCancel.removeAttribute("disabled"); this.activePrinterIndex = printerIdx; this.configModalHandle.hide(); + + // Redraw the buttons with the updated config + this.redrawPrinterButtons(); } } @@ -446,14 +562,18 @@ const app = new BasicLabelDesignerApp(printerMgr, btnContainer, labelForm, label // and let it take over the UI. await app.init(); -// Make the TypeScript type system happy by adding a property to the Window object. -declare global { - interface Window { printer_app: BasicLabelDesignerApp } -} // Now we can access our printer in the dev console if we want to mess with it! -window.printer_app = app; +(window as any).label_app = app; // Now we'll fire the reconnect since our UI is wired up. -await printerMgr.forceReconnect(); +try { + await printerMgr.forceReconnect(); +} catch (e) { + if (e instanceof WebDevices.DriverAccessDeniedError) { + deviceErrorAlert(); + } else { + throw e; + } +} // We're done here. Bring in the dancing lobsters.