This commit is contained in:
j3d1 2026-08-24 19:08:23 +02:00
parent ed04d98bf1
commit aa94c92000
10 changed files with 873 additions and 296 deletions

View file

@ -6,6 +6,10 @@
<div v-if="error" class="alert alert-danger" role="alert">{{ error }}</div>
<div v-if="warnings.length" class="alert alert-warning" role="alert">
<div v-for="(w, i) in warnings" :key="i">{{ w.message }}</div>
</div>
<div v-if="!usbSupported" class="alert alert-warning">
This browser can't talk to USB label printers directly. Open this page in Chrome, Edge or
Opera over https:// (or http://localhost) to print straight from here, or use one of the
@ -244,7 +248,7 @@
<div class="row">
<div class="col-12">
<label-layout-preview :fields="fields" :value="selectedTemplate"
<label-layout-preview :fields="fields" :value="selectedTemplate" :tape="tape"
:recent-template-ids="recentTemplateIds"
@input="selectedTemplate = $event"></label-layout-preview>
</div>
@ -260,6 +264,7 @@ import {markRaw, nextTick} from "vue";
import {mapActions, mapGetters} from "vuex";
import BaseLayout from "@/components/BaseLayout.vue";
import LabelLayoutPreview from "@/components/LabelLayoutPreview.vue";
import printerManager from "@/printerManager.js";
import {MultiPrinterBlob, canvasToBitmap, bitmapToCanvas} from "../../vendor/weblabel.js";
import {tapeFromStatus, drawLabel, drawFallbackLabel, buildLabelContent, buildLabelFields, preloadQrEncoder} from "@/label.js";
@ -319,6 +324,8 @@ export default {
printedWidthPx: 0,
// Each "text" leaf's effective font size (see label.js's drawLabel), shown beside the tape width so a too-small-to-render field reads as a suspiciously tiny number rather than silently absent.
textSizesPx: [],
// Scan-reliability messages from label.js's drawLabel/drawFallbackLabel (too few px per QR module, or too small in mm) - the label still rendered/prints fine, just flagged as a risk.
warnings: [],
// One input per BASE_VARS entry; derived vars (userHandle/itemUrl/itemHandle) are calculated-only (see the `fields` computed), never stored here. Prefilled from query params but left editable.
varValues: {
@ -554,7 +561,7 @@ export default {
},
async refreshDevices() {
this.devices = (await navigator.usb.getDevices()).map((d) => markRaw(d));
this.devices = (await printerManager.enumerateDevices()).map((d) => markRaw(d));
/* A printer unplugged while open drops from the list: its handle is gone, so close the card rather than keep it open on a dead connection. */
if (this.connected !== null && !this.devices.includes(this.connected)) {
this.connected = null;
@ -584,18 +591,32 @@ export default {
}
},
// Only one open connection at a time: connecting a different printer disconnects the current one first.
async connectToDevice(device) {
await this.closeConnection();
this.blob.setDevices([device]);
await this.blob.open(device.vendorId, device.productId);
this.connected = device;
printerManager.savePreferredPrinter(device);
const status = await this.blob.status();
// Setting `tape` alone is enough to redraw - see the tape watcher above.
this.tape = this.blob.can("print") ? tapeFromStatus(status) : null;
},
connect(index) {
this.guard(async () => {
// Only one open connection at a time: connecting a different printer disconnects the current one first.
await this.closeConnection();
const device = this.devices[index];
this.blob.setDevices([device]);
await this.blob.open(device.vendorId, device.productId);
this.connected = device;
const status = await this.blob.status();
// Setting `tape` alone is enough to redraw - see the tape watcher above.
this.tape = this.blob.can("print") ? tapeFromStatus(status) : null;
});
this.guard(() => this.connectToDevice(this.devices[index]));
},
// Auto-connects to the most-recently-used printer once it's paired and visible again (page
// load, or plugged back in - see onUsbChange), mirroring cameraManager's preferred-camera
// auto-select. Gated on "nothing connected yet" since a manual connect() to a different
// paired printer shouldn't be silently overridden by a later device-list refresh.
autoConnectPreferred() {
if (this.connected) {
return Promise.resolve();
}
const preferred = printerManager.findPreferredDevice(this.devices);
return preferred ? this.connectToDevice(preferred) : Promise.resolve();
},
disconnect() {
@ -613,15 +634,16 @@ export default {
return;
}
this.resizeObserver.observe(canvas.parentElement);
let textSizesPx;
let textSizesPx, warnings;
try {
({textSizesPx} = drawLabel(canvas, this.tape, content, this.orientation));
({textSizesPx, warnings} = drawLabel(canvas, this.tape, content, this.orientation));
} catch (e) {
this.error = e.message;
return;
}
this.error = null;
this.textSizesPx = textSizesPx;
this.warnings = warnings;
const bitmap = canvasToBitmap(canvas);
bitmapToCanvas(canvas, bitmap);
this.labelBitmap = bitmap;
@ -640,13 +662,15 @@ export default {
return;
}
this.resizeObserver.observe(canvas.parentElement);
let warnings;
try {
drawFallbackLabel(canvas, content, this.orientation);
({warnings} = drawFallbackLabel(canvas, content, this.orientation));
} catch (e) {
this.error = e.message;
return;
}
this.error = null;
this.warnings = warnings;
this.fallbackReady = true;
this.fitZoom(canvas);
},
@ -700,7 +724,10 @@ export default {
},
onUsbChange() {
this.guard(() => this.refreshDevices());
this.guard(async () => {
await this.refreshDevices();
await this.autoConnectPreferred();
});
},
// Works around Vue's refInFor array-collecting behavior for :ref in v-for, same as LabelLayoutPreview.vue's setTemplateCanvasRef.
@ -779,7 +806,10 @@ export default {
navigator.usb.addEventListener("connect", this.onUsbChange);
navigator.usb.addEventListener("disconnect", this.onUsbChange);
await qrReady;
await this.guard(() => this.refreshDevices());
await this.guard(async () => {
await this.refreshDevices();
await this.autoConnectPreferred();
});
},
beforeUnmount() {
if ("usb" in navigator) {