stash
This commit is contained in:
parent
5c3b7fc252
commit
bbe52e4a78
9 changed files with 817 additions and 287 deletions
|
|
@ -102,21 +102,6 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Code type</label>
|
||||
<div class="btn-group d-block" role="group">
|
||||
<template v-for="opt in codeTypeOptions" :key="opt.id">
|
||||
<input type="radio" class="btn-check"
|
||||
:id="'code-type-' + opt.id + '-fallback'"
|
||||
autocomplete="off" :value="opt.id" v-model="codeType">
|
||||
<label class="btn btn-outline-secondary"
|
||||
:for="'code-type-' + opt.id + '-fallback'">
|
||||
{{ opt.label }}
|
||||
</label>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-primary" :disabled="!fallbackReady" @click="downloadPng">
|
||||
<b-icon-download class="me-1"></b-icon-download>
|
||||
Download label as PNG
|
||||
|
|
@ -207,19 +192,6 @@
|
|||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<label class="form-label">Code type</label>
|
||||
<div class="btn-group" role="group">
|
||||
<template v-for="opt in codeTypeOptions" :key="opt.id">
|
||||
<input type="radio" class="btn-check"
|
||||
:id="'code-type-' + opt.id" autocomplete="off"
|
||||
:value="opt.id" v-model="codeType">
|
||||
<label class="btn btn-outline-secondary" :for="'code-type-' + opt.id">
|
||||
{{ opt.label }}
|
||||
</label>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<label class="form-label">Copies</label>
|
||||
<input type="number" class="form-control copies-input"
|
||||
|
|
@ -275,7 +247,7 @@
|
|||
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<label-layout-preview :fields="fields" :value="selectedTemplate" :code-type="codeType"
|
||||
<label-layout-preview :fields="fields" :value="selectedTemplate"
|
||||
@input="selectedTemplate = $event"></label-layout-preview>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -287,12 +259,27 @@
|
|||
<script>
|
||||
import * as BIcons from "bootstrap-icons-vue";
|
||||
import {markRaw, nextTick} from "vue";
|
||||
import {mapActions, mapGetters} from "vuex";
|
||||
import BaseLayout from "@/components/BaseLayout.vue";
|
||||
import LabelLayoutPreview from "@/components/LabelLayoutPreview.vue";
|
||||
|
||||
import {MultiPrinterBlob, canvasToBitmap, bitmapToCanvas} from "../../vendor/weblabel.js";
|
||||
import {tapeFromStatus, drawLabel, drawFallbackLabel, buildLabelContent, buildLabelFields, preloadQrEncoder, QR_CODE_TYPES} from "@/label.js";
|
||||
import {tapeFromStatus, drawLabel, drawFallbackLabel, buildLabelContent, buildLabelFields, preloadQrEncoder} from "@/label.js";
|
||||
import {LABEL_TEMPLATES, BASE_VARS, DERIVED_VARS, withDerivedVars, templateContent} from "@/label-layouts.js";
|
||||
import {shortenedRoute} from "@/router";
|
||||
|
||||
// The extra "Calculated" fields (and label-layouts.js "Short link (QR code)" template input) this
|
||||
// view adds on top of label-layouts.js's own DERIVED_VARS - resolving either needs the current
|
||||
// identityIdByHandle map (see store.js's fetchIdMap) to turn a handle into the numeric
|
||||
// owner_identity_id short-id.js's 'item'/'storage_location' kinds encode, so neither can be a pure
|
||||
// fields->value calc like the others and both live here instead of in label-layouts.js. Excluded
|
||||
// from baseVars below since, unlike every other entry KNOWN_VARS picks up from a template's
|
||||
// required_vars, neither is ever typed directly.
|
||||
const SHORT_URL_VAR = "shortUrl";
|
||||
// The bare short-id.js token itself (e.g. "~AbCd12"), with no domain or leading "/" - what
|
||||
// shortUrl's own path is built from (see fields()/the shortId method below), for a label that
|
||||
// wants just the compact code rather than a full scannable URL.
|
||||
const SHORT_ID_VAR = "shortId";
|
||||
|
||||
// Served verbatim from public/vendor/ rather than bundled: libweblabel.js's
|
||||
// own emscripten glue resolves its .wasm sibling relative to *its own*
|
||||
|
|
@ -314,15 +301,6 @@ const RULER_TIERS = [
|
|||
{aboveMm: 500, tickMm: 5, majorEveryMm: 25},
|
||||
];
|
||||
|
||||
// Display labels for label.js's QR_CODE_TYPES - the code-type button group below reads this
|
||||
// (via codeTypeOptions) rather than hard-coding its own list, so a new symbology added to
|
||||
// anyd-qr.js/QR_CODE_TYPES only needs a label here, not a whole new radio group.
|
||||
const CODE_TYPE_LABELS = {
|
||||
"qr": "QR",
|
||||
"micro-qr": "Micro QR",
|
||||
"rmqr": "rMQR",
|
||||
};
|
||||
|
||||
export default {
|
||||
name: "Print",
|
||||
components: {
|
||||
|
|
@ -386,10 +364,6 @@ export default {
|
|||
// instead, so it reads across the tape rather than along it. See label.js's
|
||||
// drawLabel/drawFallbackLabel for how that turn is actually drawn.
|
||||
orientation: "along",
|
||||
// Which of label.js's QR_CODE_TYPES every "qrcode" leaf in the current template
|
||||
// renders as - one global choice for the whole label, same as `orientation` above,
|
||||
// since a template only ever has one scannable code in practice.
|
||||
codeType: "qr",
|
||||
|
||||
fallbackReady: false,
|
||||
// TODO: replace with the real commands for our printers.
|
||||
|
|
@ -417,19 +391,20 @@ export default {
|
|||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(["identityIdByHandle"]),
|
||||
// The base variables the "Label content" form renders an input for, and the derived ones
|
||||
// it instead calculates and lists read-only beside that form - plain passthroughs, but
|
||||
// keep the template from importing label-layouts.js just for these.
|
||||
// keep the template from importing label-layouts.js just for these. SHORT_URL_VAR is
|
||||
// excluded here even though the "Short link (QR code)" template's required_vars puts it in
|
||||
// BASE_VARS (it isn't a label-layouts.js DERIVED_VARS entry) - see fields() below for why
|
||||
// it's calculated, not typed. SHORT_ID_VAR isn't referenced by any template's
|
||||
// required_vars (so isn't actually in BASE_VARS today), filtered out too in case one ever
|
||||
// is.
|
||||
baseVars() {
|
||||
return BASE_VARS;
|
||||
},
|
||||
// {id, label} pairs for the code-type button group (see CODE_TYPE_LABELS above), in
|
||||
// label.js's QR_CODE_TYPES order.
|
||||
codeTypeOptions() {
|
||||
return QR_CODE_TYPES.map(id => ({id, label: CODE_TYPE_LABELS[id]}));
|
||||
return BASE_VARS.filter(v => v !== SHORT_URL_VAR && v !== SHORT_ID_VAR);
|
||||
},
|
||||
derivedVars() {
|
||||
return Object.keys(DERIVED_VARS);
|
||||
return [...Object.keys(DERIVED_VARS), SHORT_ID_VAR, SHORT_URL_VAR];
|
||||
},
|
||||
// Named content fields the templates draw from: the form's own base vars, plus every
|
||||
// DERIVED_VARS format string calculated live from those - so typing a userHandle and
|
||||
|
|
@ -445,7 +420,15 @@ export default {
|
|||
base[v] = this.varValues[v];
|
||||
}
|
||||
}
|
||||
return withDerivedVars(base);
|
||||
const derived = withDerivedVars(base);
|
||||
const shortId = this.shortId(derived);
|
||||
if (shortId) {
|
||||
derived[SHORT_ID_VAR] = shortId;
|
||||
if (derived.webdomain) {
|
||||
derived[SHORT_URL_VAR] = derived.webdomain + "/" + shortId;
|
||||
}
|
||||
}
|
||||
return derived;
|
||||
},
|
||||
currentTemplate() {
|
||||
return LABEL_TEMPLATES.find(t => t.id === this.selectedTemplate) || LABEL_TEMPLATES[0];
|
||||
|
|
@ -546,13 +529,6 @@ export default {
|
|||
this.redrawFallback();
|
||||
}
|
||||
},
|
||||
codeType() {
|
||||
if (this.usbSupported) {
|
||||
this.redraw();
|
||||
} else {
|
||||
this.redrawFallback();
|
||||
}
|
||||
},
|
||||
// Covers connecting/disconnecting/switching printers - anything that changes the
|
||||
// tape dimensions redraw() sizes the canvas from. flush: 'post' because the canvas
|
||||
// itself only exists once `tape` is truthy (see the v-if/v-else in the template), so
|
||||
|
|
@ -565,6 +541,8 @@ export default {
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions(["fetchIdMap"]),
|
||||
|
||||
// Turns a camelCase variable name (see label-layouts.js's KNOWN_VARS) into a form label,
|
||||
// e.g. "itemHandle" -> "Item Handle" - so adding a new template variable doesn't also
|
||||
// require hand-writing a label for it here.
|
||||
|
|
@ -572,6 +550,36 @@ export default {
|
|||
return v.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/^./, c => c.toUpperCase());
|
||||
},
|
||||
|
||||
// The same shortened link Inventory.vue/StorageLocation.vue's own shortIdLink builds for
|
||||
// one of their rows, but as the bare token (see short-id.js's encodeShortId) rather than a
|
||||
// router target or full URL - shortenedRoute's own leading "/" is stripped since this is a
|
||||
// plain display field/potential label content, not something this view itself navigates
|
||||
// to; fields() below reattaches a domain and slash to build shortUrl from this same value,
|
||||
// so the two can never disagree. Which short-id.js kind applies depends on which id field
|
||||
// the current prefill's LABEL_FIELD_BUILDERS populated (see label.js) - itemId for an
|
||||
// item, locationId for a storage location - rather than trusting the prefill's own `kind`
|
||||
// directly, so hand-typing userHandle+itemId with no prefill at all still resolves this
|
||||
// the same way. Falls back to no value - same as an unresolved DERIVED_VARS entry - until
|
||||
// identityIdByHandle has loaded (see mounted's fetchIdMap) or if the handle isn't in it.
|
||||
shortId(f) {
|
||||
if (!f.userHandle) {
|
||||
return null;
|
||||
}
|
||||
const owner_identity_id = this.identityIdByHandle[f.userHandle];
|
||||
if (owner_identity_id === undefined) {
|
||||
return null;
|
||||
}
|
||||
if (f.itemId) {
|
||||
return shortenedRoute({kind: "item", owner_identity_id, item_local_id: f.itemId}).slice(1);
|
||||
}
|
||||
if (f.locationId) {
|
||||
return shortenedRoute({
|
||||
kind: "storage_location", owner_identity_id, storage_location_id: f.locationId
|
||||
}).slice(1);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
// Ticks from 0 up to totalMm, each positioned in on-screen pixels via tapePxPerMm - shared
|
||||
// by the horizontal/vertical ruler computeds above. Both the plain tick spacing and the
|
||||
// labeled/major one come from the shared rulerTier (see above), not from this totalMm, so
|
||||
|
|
@ -663,7 +671,7 @@ export default {
|
|||
this.resizeObserver.observe(canvas.parentElement);
|
||||
let textSizesPx;
|
||||
try {
|
||||
({textSizesPx} = drawLabel(canvas, this.tape, content, this.orientation, this.codeType));
|
||||
({textSizesPx} = drawLabel(canvas, this.tape, content, this.orientation));
|
||||
} catch (e) {
|
||||
this.error = e.message;
|
||||
return;
|
||||
|
|
@ -689,7 +697,7 @@ export default {
|
|||
}
|
||||
this.resizeObserver.observe(canvas.parentElement);
|
||||
try {
|
||||
drawFallbackLabel(canvas, content, this.orientation, this.codeType);
|
||||
drawFallbackLabel(canvas, content, this.orientation);
|
||||
} catch (e) {
|
||||
this.error = e.message;
|
||||
return;
|
||||
|
|
@ -823,10 +831,16 @@ export default {
|
|||
},
|
||||
async mounted() {
|
||||
this.drawCandidateFontTests();
|
||||
// Not awaited: itemShortUrl just reads whatever identityIdByHandle currently holds (see
|
||||
// the fields computed), so this resolving after first render only means it shows "—"
|
||||
// briefly rather than blocking the rest of mounted's (unrelated) printer/wasm setup.
|
||||
this.fetchIdMap().catch(e => {
|
||||
this.error = e.message;
|
||||
});
|
||||
this.resizeObserver = new ResizeObserver(this.handleContainerResize);
|
||||
// Kicked off here rather than awaited immediately, so it loads concurrently with
|
||||
// MultiPrinterBlob below instead of serializing two independent wasm fetches - every
|
||||
// redraw()/redrawFallback() call below still waits on it first, since a "qrcode" leaf
|
||||
// redraw()/redrawFallback() call below still waits on it first, since a qr/mqr/rmqr leaf
|
||||
// throws (see label.js's encodeQr) until it resolves.
|
||||
const qrReady = preloadQrEncoder();
|
||||
if (!("usb" in navigator)) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue