Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
231 changes: 165 additions & 66 deletions demo/index.html

Large diffs are not rendered by default.

143 changes: 119 additions & 24 deletions demo/test_index.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,32 @@
import * as WebReceipt 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 WebReceipt from 'web-receiptline-printer';
// // First import the lib!
// // This is our lib in this repo here
// import * as WebReceipt from 'web-receiptline-printer';

// 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.
// It's a handy-dandy feature included from the web-device-mux library!
// We need to tell it what type of device it's managing, and how to filter
// USB devices that are receipt printers.
const printerMgr = new WebDevices.UsbDeviceManager<WebReceipt.ReceiptPrinter>(
// // 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.WebReceipt = WebReceipt;
// window.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.

// It's a handy-dandy feature included from the web-device-mux library!
// We need to tell it what type of device it's managing, and how to filter
// USB devices that are receipt printers.
// We'll set a type alias so it's easier to read our code
type PrinterManager = WebDevices.UsbDeviceManager<WebReceipt.ReceiptPrinterUsb>;
// Then we'll construct one to use
const printerMgr: PrinterManager = new WebDevices.UsbDeviceManager(
window.navigator.usb,
WebReceipt.ReceiptPrinter.fromUSBDevice,
{
Expand Down Expand Up @@ -41,7 +55,7 @@ addPrinterBtn.addEventListener('click', async () => printerMgr.promptForNewDevic
const refreshPrinterBtn = document.getElementById('refreshPrinters')!;
refreshPrinterBtn.addEventListener('click', async () => printerMgr.forceReconnect());

// Next we wire up some events on the device manager itself.
// Next we wire up some events on the UsbDeviceManager itself.
printerMgr.addEventListener('connectedDevice', ({ detail }) => {
const printer = detail.device;
const config = printer.printerOptions;
Expand All @@ -52,8 +66,7 @@ printerMgr.addEventListener('connectedDevice', ({ detail }) => {
// There's also an event that will tell you when a printer disconnects.
printerMgr.addEventListener('disconnectedDevice', ({ detail }) => {
const printer = detail.device;
// TODO: Hide appropriate interface.
console.log(`Lost printer ${printer.printerModel} (${printer.printerOptions.serialNumber}).`);
console.log('Lost printer', printer.printerModel, 'serial', printer.printerOptions.serialNumber);
});

// The browser will remember printers that were previously connected, and
Expand All @@ -66,33 +79,95 @@ printerMgr.addEventListener('disconnectedDevice', ({ detail }) => {
// And that's all there is to setup! The page can now talk to printers.
// The rest of this demo is an example of a basic receipt printer app.

// Get some bookkeeping out of the way..

// 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 = `
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
<h4>${titleHtml}</h4>
${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 BasicDocumentPrinterApp {
constructor(
private manager: WebDevices.UsbDeviceManager<WebReceipt.ReceiptPrinter>,
private manager: PrinterManager,
private btnContainer: HTMLElement,
private labelForm: HTMLElement,
private labelFormInstructions: HTMLElement,
) {
// 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(WebReceipt.ErrorState.NoError)) { return; }

showAlert(
// Show a warning for this printer
'warning',
alertId,
`Printer <strong>${printer.printerSerial}</strong> has an error`,
// There can be multiple errors, just show their raw values. A better
// application would use these for good messages!
`<p><ul>${Array.from(msg.errors).map(e => `<li>${e}`)}</ul></p>
<hr>
<p>Fix the issue, then dismiss this alert to check the status again.</p>`,
// And when the alert is dismissed, check the status again!
() => printer.sendDocument({
commands: [new WebReceipt.GetStatus()]
})
);
});
});
this.manager.addEventListener('disconnectedDevice', () => {
this.activePrinterIndex = -1;
this.redrawPrinterButtons();
});
}

get printers(): readonly WebReceipt.ReceiptPrinter[] {
get printers(): readonly WebReceipt.ReceiptPrinterUsb[] {
return this.manager.devices;
}

// Track which printer is currently selected for operations
private _activePrinter = 0;
get activePrinter(): WebReceipt.ReceiptPrinter | undefined {
get activePrinter(): WebReceipt.ReceiptPrinterUsb | undefined {
return this._activePrinter < 0 || this._activePrinter > this.printers.length
? undefined
: this.printers[this._activePrinter];
Expand All @@ -117,17 +192,17 @@ class BasicDocumentPrinterApp {
/** Highlight only the currently selected printer. */
private redrawPrinterButtonHighlights() {
this.printers.forEach((printer, idx) => {
const highlight = this._activePrinter == idx ? "var(--bs-blue)" : "transparent";
const highlight = this._activePrinter === idx ? "var(--bs-blue)" : "transparent";
const element = document.getElementById(`printer_${idx}`)!;
element.style.background = `linear-gradient(to right, ${highlight}, ${highlight}, grey, grey)`;
});
}

/** Add a printer's button UI to the list of printers. */
private drawPrinterButton(printer: WebReceipt.ReceiptPrinter, idx: number) {
const highlight = this._activePrinter == idx ? "var(--bs-blue)" : "transparent";
private drawPrinterButton(printer: WebReceipt.ReceiptPrinterUsb, idx: number) {
const highlight = this._activePrinter === idx ? "var(--bs-blue)" : "transparent";

// Generate a new label printer button for the given printer.
// Generate a new receipt printer button for the given printer.
const element = document.createElement("div");
element.innerHTML = `
<li id="printer_${idx}" data-printer-idx="${idx}"
Expand All @@ -148,13 +223,13 @@ class BasicDocumentPrinterApp {
<span class="visually-hidden">Settings</span>
</button>
<ul class="dropdown-menu">
<li><a id="printtest_${idx}" data-printer-idx="${idx}" class="dropdown-item" href="#">
<li><a id="printtest_${idx}" data-printer-idx="${idx}" class="dropdown-item" href="#">
Print test page
</a></li>
<li><a id="printconfig_${idx}" data-printer-idx="${idx}" class="dropdown-item" href="#">
Print config
</a></li>
<li><a id="drawerkick_${idx}" data-printer-idx="${idx}" class="dropdown-item" href="#">
<li><a id="drawerkick_${idx}" data-printer-idx="${idx}" class="dropdown-item" href="#">
Kick drawer out
</a></li>
</ul>
Expand All @@ -180,7 +255,7 @@ class BasicDocumentPrinterApp {
.addEventListener('click', async (e) => {
e.preventDefault();
const printerIdx = (e.currentTarget as HTMLAnchorElement).dataset.printerIdx as unknown as number;
if (this._activePrinter == printerIdx) {
if (this._activePrinter === printerIdx) {
// Don't refresh anything if we already have this printer selected..
return;
}
Expand Down Expand Up @@ -223,7 +298,7 @@ class BasicDocumentPrinterApp {
/** Redraw the text canvas size according to the printer. */
private redrawTextCanvas() {
const printer = this.activePrinter;
if (printer == null) {
if (printer === undefined) {
this.labelForm.classList.add('d-none');
this.labelFormInstructions.classList.remove('d-none');
return;
Expand Down Expand Up @@ -256,6 +331,26 @@ declare global {
window.printer_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) {
// 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`,
`<p>This usually happens for one of these reasons:
<ul>
<li>Another browser tab is already connected.
<li>Another application loaded a driver to talk to the device.
<li>You're on Windows and need to replace the driver.
</ul>
Fix the issue and re-connect to the device.</p>`
);
}
}

// We're done here. Bring in the dancing lobsters.
Loading
Loading