151 lines
5.2 KiB
Vue
151 lines
5.2 KiB
Vue
<template>
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="card-title mb-0">Label layout</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="template-grid d-flex flex-wrap align-items-start">
|
|
<div v-for="t in labelTemplates" :key="t.id" class="template-option d-flex flex-column text-center"
|
|
:class="{'template-option-disabled': !isAvailable(t)}"
|
|
:title="isAvailable(t) ? '' : 'Not available - fill in the fields this layout needs above.'"
|
|
role="button" @click="isAvailable(t) && $emit('input', t.id)">
|
|
<canvas :ref="el => setTemplateCanvasRef(t.id, el)"
|
|
class="img-thumbnail template-thumb-canvas"
|
|
:class="{'border-primary': value === t.id}"></canvas>
|
|
<div class="small"
|
|
:class="{'fw-bold text-primary': value === t.id}">
|
|
{{ t.name }}
|
|
</div>
|
|
<div class="small text-muted">{{ t.description }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
/* This build pins Bootstrap 4 (no gap-* utilities, those are Bootstrap 5.1+), so the spacing
|
|
here is plain CSS gap rather than a Bootstrap gap-N class. */
|
|
.template-grid {
|
|
gap: 1rem;
|
|
}
|
|
|
|
.template-option {
|
|
width: 20rem;
|
|
}
|
|
|
|
.template-option-disabled {
|
|
opacity: .45;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.template-thumb-canvas {
|
|
display: block;
|
|
width: 100%;
|
|
height: 10rem;
|
|
/* The canvas itself is drawn at whatever size fits its content (see redraw/drawFallbackLabel)
|
|
- object-fit scales that down to the thumbnail box the same way it would for an <img>,
|
|
no manual zoom math needed. */
|
|
object-fit: contain;
|
|
background: #fff;
|
|
}
|
|
</style>
|
|
|
|
<script>
|
|
import {drawFallbackLabel, preloadQrEncoder} from "@/label.js";
|
|
import {LABEL_TEMPLATES, templateIsAvailable, templateContent} from "@/label-layouts.js";
|
|
|
|
export default {
|
|
name: "LabelLayoutPreview",
|
|
props: {
|
|
// Named content fields the templates draw from (see label.js's buildLabelFields)
|
|
// - kept in sync by the parent, not owned here. A field missing from this object (rather
|
|
// than present-but-empty) means a template that needs it is unavailable right now.
|
|
fields: {
|
|
type: Object,
|
|
required: true
|
|
},
|
|
// The selected template id.
|
|
value: {
|
|
type: String,
|
|
required: true
|
|
},
|
|
// Print.vue's global qr/micro-qr/rmqr choice (see label.js's QR_CODE_TYPES) - passed
|
|
// straight through to drawFallbackLabel so these thumbnails match whatever symbology the
|
|
// main preview is actually using.
|
|
codeType: {
|
|
type: String,
|
|
default: "qr"
|
|
}
|
|
},
|
|
model: {
|
|
prop: "value",
|
|
event: "input"
|
|
},
|
|
emits: ["input"],
|
|
computed: {
|
|
labelTemplates() {
|
|
return LABEL_TEMPLATES;
|
|
}
|
|
},
|
|
watch: {
|
|
fields() {
|
|
this.redraw();
|
|
},
|
|
codeType() {
|
|
this.redraw();
|
|
}
|
|
},
|
|
methods: {
|
|
setTemplateCanvasRef(id, el) {
|
|
if (el) {
|
|
this.templateCanvases[id] = el;
|
|
} else {
|
|
delete this.templateCanvases[id];
|
|
}
|
|
},
|
|
|
|
isAvailable(t) {
|
|
return templateIsAvailable(t, this.fields);
|
|
},
|
|
|
|
/* Live per-template thumbnails. Always uses the content-fit fallback renderer (rather
|
|
than the tape-fed one), regardless of whether a real printer is connected - these are
|
|
illustrative previews sized by CSS object-fit, not the accurate to-be-printed canvas
|
|
the main preview is. */
|
|
redraw() {
|
|
for (const t of LABEL_TEMPLATES) {
|
|
const canvas = this.templateCanvases[t.id];
|
|
if (!canvas) {
|
|
continue;
|
|
}
|
|
const content = templateContent(t, this.fields);
|
|
if (!this.isAvailable(t) || !content) {
|
|
canvas.width = 1;
|
|
canvas.height = 1;
|
|
continue;
|
|
}
|
|
try {
|
|
drawFallbackLabel(canvas, content, "along", this.codeType);
|
|
} catch (e) {
|
|
// A thumbnail that can't render at this content length just stays blank
|
|
// rather than surfacing an error for every keystroke.
|
|
}
|
|
}
|
|
}
|
|
},
|
|
created() {
|
|
// Keyed by template id, populated by setTemplateCanvasRef() - a plain :ref="t.id" string
|
|
// inside v-for would still get Vue's refInFor array-collecting behavior even though
|
|
// each iteration uses a different name, turning this.$refs[t.id] into a one-element
|
|
// array rather than the canvas itself. A function ref sidesteps that entirely.
|
|
this.templateCanvases = {};
|
|
},
|
|
async mounted() {
|
|
// See Print.vue's mounted() - every thumbnail here has a "qrcode" leaf, so there's
|
|
// nothing worth drawing before this resolves.
|
|
await preloadQrEncoder();
|
|
this.redraw();
|
|
}
|
|
}
|
|
</script>
|