stash
This commit is contained in:
parent
95ddb484eb
commit
8622e488a4
16 changed files with 607 additions and 369 deletions
138
frontend/src/components/LabelLayoutPreview.vue
Normal file
138
frontend/src/components/LabelLayoutPreview.vue
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
<template>
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0">Label layout</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="d-flex flex-wrap gap-3">
|
||||
<div v-for="t in labelTemplates" :key="t.id" class="template-option text-center"
|
||||
:class="{'template-option-disabled': !isAvailable(t)}"
|
||||
:title="isAvailable(t) ? '' : 'Not available - open this page from an item to fill in the fields this layout needs.'"
|
||||
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 mt-1"
|
||||
: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>
|
||||
.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 {LABEL_TEMPLATES, drawFallbackLabel} from "@/label-drawing.js";
|
||||
|
||||
export default {
|
||||
name: "LabelLayoutPreview",
|
||||
props: {
|
||||
// Named content fields the templates draw from (see label-content.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
|
||||
}
|
||||
},
|
||||
model: {
|
||||
prop: "value",
|
||||
event: "input"
|
||||
},
|
||||
emits: ["input"],
|
||||
computed: {
|
||||
labelTemplates() {
|
||||
return LABEL_TEMPLATES;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
fields() {
|
||||
this.redraw();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setTemplateCanvasRef(id, el) {
|
||||
if (el) {
|
||||
this.templateCanvases[id] = el;
|
||||
} else {
|
||||
delete this.templateCanvases[id];
|
||||
}
|
||||
},
|
||||
|
||||
isAvailable(t) {
|
||||
return [t.qr, t.text].filter(Boolean).every(key => this.fields[key] !== undefined);
|
||||
},
|
||||
|
||||
contentFor(t) {
|
||||
return {
|
||||
qr: t.qr ? this.fields[t.qr] : null,
|
||||
text: t.text ? this.fields[t.text] : null,
|
||||
};
|
||||
},
|
||||
|
||||
/* 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 = this.contentFor(t);
|
||||
if (!this.isAvailable(t) || (!content.qr && !content.text)) {
|
||||
canvas.width = 1;
|
||||
canvas.height = 1;
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
drawFallbackLabel(canvas, content);
|
||||
} 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 = {};
|
||||
},
|
||||
mounted() {
|
||||
this.redraw();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Loading…
Add table
Add a link
Reference in a new issue