stash
This commit is contained in:
parent
15dad8e3be
commit
9c9f0f2d9b
4 changed files with 117 additions and 179 deletions
|
|
@ -79,7 +79,7 @@
|
|||
}
|
||||
|
||||
/* Corner badge flagging *why* a layout failed to render - a capacity error (label.js's "bigger
|
||||
code than the tape allows"/"doesn't fit on this tape") or label-layouts.js's templateContent
|
||||
code than the tape allows"/"doesn't fit on this tape") or label.js's templateContent
|
||||
throwing "missing data" because a field it needs isn't filled in yet (see redraw()'s catch).
|
||||
Shows the short label directly (e.g. "too big", "missing data") rather than just an icon, so the
|
||||
reason reads at a glance; the full sentence is still the title tooltip. Layered on top of the
|
||||
|
|
@ -113,8 +113,15 @@
|
|||
</style>
|
||||
|
||||
<script>
|
||||
import {drawLabel, drawFallbackLabel, preloadQrEncoder, preloadPixelFontRenderer} from "@/label.js";
|
||||
import {LABEL_TEMPLATES, LABEL_TAGS, templateIsAvailable, templateContent} from "@/label-layouts.js";
|
||||
import {
|
||||
drawLabel,
|
||||
drawFallbackLabel,
|
||||
preloadQrEncoder,
|
||||
preloadPixelFontRenderer,
|
||||
templateIsAvailable,
|
||||
templateContent
|
||||
} from "@/label.js";
|
||||
import {LABEL_TEMPLATES, LABEL_TAGS} from "@/label-layouts.js";
|
||||
|
||||
export default {
|
||||
name: "LabelLayoutPreview",
|
||||
|
|
|
|||
|
|
@ -4,148 +4,92 @@ import {encodeHandleForUrl} from "@/router";
|
|||
// docs/implementation.md#template-layout-tree.
|
||||
const GAP = {type: "empty", "min-width": "1mm", "min-height": "1mm"};
|
||||
|
||||
// Generated matrix of "just the code" templates covering every QR_LEAF_TYPES combo. See
|
||||
// docs/implementation.md#generated-qr-only-template-matrix.
|
||||
const QR_ONLY_TEMPLATES = [
|
||||
{
|
||||
id: "qr-l", name: "QR code only (low error correction)",
|
||||
description: "Just the code, at QR's lowest error-correction level - fits more data (or a "
|
||||
+ "smaller code) for the same text, but less tolerant of damage.",
|
||||
},
|
||||
{id: "qr", name: "QR code only", description: "Just the code - smallest label, prints fastest."},
|
||||
{
|
||||
id: "qr-q", name: "QR code only (quartile error correction)",
|
||||
description: "Just the code, at QR's second-highest (quartile) error-correction level - a "
|
||||
+ "middle ground between code size and damage tolerance.",
|
||||
},
|
||||
{
|
||||
id: "qr-h", name: "QR code only (high error correction)",
|
||||
description: "Just the code, at QR's highest error-correction level - still scans if scuffed "
|
||||
+ "or partly obscured, at the cost of a bigger code for the same text.",
|
||||
},
|
||||
{
|
||||
id: "mqr-d", name: "Micro QR code only (detection only)",
|
||||
description: "Just the code, in the more compact Micro QR format at its lowest, M1-only level - "
|
||||
+ "the smallest QR-family code there is, but can only tell a scan is corrupted, not "
|
||||
+ "recover from it.",
|
||||
},
|
||||
{
|
||||
id: "mqr-l", name: "Micro QR code only (low error correction)",
|
||||
description: "Just the code, in the more compact Micro QR format at its low error-correction level.",
|
||||
},
|
||||
{
|
||||
id: "mqr", name: "Micro QR code only",
|
||||
description: "Just the code, in the more compact Micro QR format - smallest label, prints fastest.",
|
||||
},
|
||||
{
|
||||
id: "mqr-q", name: "Micro QR code only (quartile error correction)",
|
||||
description: "Just the code, in the more compact Micro QR format at its highest (quartile) "
|
||||
+ "error-correction level.",
|
||||
},
|
||||
{
|
||||
id: "rmqr", name: "rMQR code only",
|
||||
description: "Just the code, in the rectangular rMQR format, sized for the smallest total area "
|
||||
+ "that fits the text - smallest label, prints fastest.",
|
||||
},
|
||||
{
|
||||
id: "rmqr-min", name: "rMQR code only (shortest, widest)",
|
||||
description: "Just the code, in the rectangular rMQR format, preferring the flattest/widest "
|
||||
+ "symbol that fits the text - shortest across the tape, longest along it.",
|
||||
},
|
||||
{
|
||||
id: "rmqr-max", name: "rMQR code only (tallest, narrowest)",
|
||||
description: "Just the code, in the rectangular rMQR format, preferring the tallest/narrowest "
|
||||
+ "symbol that fits the text - tallest across the tape, shortest along it.",
|
||||
},
|
||||
{
|
||||
id: "rmqr-h", name: "rMQR code only (high error correction)",
|
||||
description: "Just the code, in the rectangular rMQR format at its high error-correction level, "
|
||||
+ "sized for the smallest total area that fits the text - still scans if scuffed or partly "
|
||||
+ "obscured, at the cost of a bigger code for the same text.",
|
||||
},
|
||||
{
|
||||
id: "rmqr-h-min", name: "rMQR code only (high error correction, shortest/widest)",
|
||||
description: "Just the code, in the rectangular rMQR format at its high error-correction level, "
|
||||
+ "preferring the flattest/widest symbol that fits the text.",
|
||||
},
|
||||
{
|
||||
id: "rmqr-h-max", name: "rMQR code only (high error correction, tallest/narrowest)",
|
||||
description: "Just the code, in the rectangular rMQR format at its high error-correction level, "
|
||||
+ "preferring the tallest/narrowest symbol that fits the text.",
|
||||
},
|
||||
].map(t => ({...t, required_vars: ["text"], layout: [{type: t.id, content: c => c.text}]}));
|
||||
|
||||
// QR-types
|
||||
// qr-l, qr, qr-q, qr-h, mqr-d, mqr-l, mqr, mqr-q, rmqr, rmqr-min, rmqr-max, rmqr-h, rmqr-h-min, rmqr-h-max
|
||||
|
||||
export const LABEL_TEMPLATES = [
|
||||
{
|
||||
id: "mqr-token", name: "MQR Token", description: "The code with the encoded text printed next to it.",
|
||||
id: "mqr-token", name: "MQR Token",
|
||||
description: "Just the code, encoding the compact internal short id - no other text on the label.",
|
||||
required_vars: ["shortId"], tags: ["internal"],
|
||||
layout: [{type: "mqr", content: c => c.shortId}]
|
||||
},
|
||||
{
|
||||
id: "qr-url", name: "MQR Token", description: "The code with the encoded text printed next to it.",
|
||||
id: "qr-url", name: "QR Token",
|
||||
description: "Just the code, encoding the short link - no other text on the label.",
|
||||
required_vars: ["shortUrl"], tags: ["external"],
|
||||
layout: [{type: "qr-h", content: c => c.shortUrl}]
|
||||
},
|
||||
{
|
||||
id: "rmqr-url", name: "rMQR Token", description: "The code with the encoded text printed next to it.",
|
||||
id: "rmqr-url", name: "rMQR Token + Item ID",
|
||||
description: "The code encoding the short link, with the item id printed alongside for a human to read.",
|
||||
required_vars: ["shortUrl", "itemId"], tags: ["external"],
|
||||
layout: [{type: "rmqr", content: c => c.shortUrl}, GAP, {
|
||||
layout: [{type: "rmqr", content: c => c.shortUrl}, {
|
||||
type: "text",
|
||||
content: c => c.itemId?.toString().padStart(4, "0")
|
||||
}]
|
||||
},
|
||||
{
|
||||
id: "mqr-tokem-id", name: "rMQR Token", description: "The code with the encoded text printed next to it.",
|
||||
id: "rmqr-url-url", name: "rMQR Token + Item ID + URL",
|
||||
description: "The code encoding the short link, with the item id printed alongside for a human to read.",
|
||||
required_vars: ["shortUrl", "itemId"], tags: ["external"],
|
||||
layout: [[[{type: "rmqr", content: c => c.shortUrl}, {
|
||||
type: "text",
|
||||
content: c => "I"+c.itemId?.toString().padStart(4, "0")
|
||||
}],{type: "text", content: c => c.itemUrl}]]
|
||||
},
|
||||
{
|
||||
id: "mqr-token-id", name: "MQR Token + Item ID",
|
||||
description: "The code encoding the internal short id, with the item id printed alongside for a human to read.",
|
||||
required_vars: ["shortId", "itemId"], tags: ["internal"],
|
||||
layout: [{type: "mqr", content: c => c.shortId}, GAP, {
|
||||
layout: [{type: "mqr", content: c => c.shortId}, {
|
||||
type: "text",
|
||||
content: c => c.itemId?.toString().padStart(4, "0")
|
||||
}]
|
||||
},
|
||||
{
|
||||
id: "location-rmqr-url", name: "rMQR Token", description: "The code with the encoded text printed next to it.",
|
||||
id: "location-rmqr-url", name: "rMQR Token + Location ID",
|
||||
description: "The code encoding the short link, with the storage location id printed alongside for a "
|
||||
+ "human to read.",
|
||||
required_vars: ["shortUrl", "locationId"], tags: ["external"],
|
||||
layout: [{type: "rmqr", content: c => c.shortUrl}, GAP, {
|
||||
layout: [{type: "rmqr", content: c => c.shortUrl}, {
|
||||
type: "text",
|
||||
content: c => c.locationId?.toString().padStart(4, "0")
|
||||
}]
|
||||
},
|
||||
{
|
||||
id: "location-mqr-tokem-id",
|
||||
name: "rMQR Token",
|
||||
description: "The code with the encoded text printed next to it.",
|
||||
id: "location-mqr-token-id",
|
||||
name: "MQR Token + Location ID",
|
||||
description: "The code encoding the internal short id, with the storage location id printed alongside "
|
||||
+ "for a human to read.",
|
||||
required_vars: ["shortId", "locationId"],
|
||||
tags: ["internal"],
|
||||
layout: [{type: "mqr", content: c => c.shortId}, GAP, {
|
||||
layout: [{type: "mqr", content: c => c.shortId}, {
|
||||
type: "text",
|
||||
content: c => c.locationId?.toString().padStart(4, "0")
|
||||
}]
|
||||
}, ...QR_ONLY_TEMPLATES,
|
||||
},
|
||||
|
||||
{
|
||||
id: "qr-text", name: "QR code + text", description: "The code with the encoded text printed next to it.",
|
||||
required_vars: ["text"],
|
||||
layout: [{type: "qr", content: c => c.text}, GAP, {type: "text", content: c => c.text?.split("\n")}]
|
||||
layout: [{type: "qr", content: c => c.text}, {type: "text", content: c => c.text?.split("\n")}]
|
||||
},
|
||||
{
|
||||
id: "qr-text-below", name: "QR code + text below",
|
||||
description: "The code with the encoded text printed below it.",
|
||||
required_vars: ["text"],
|
||||
layout: [[{type: "qr", content: c => c.text}, GAP, {type: "text", content: c => c.text?.split("\n")}]]
|
||||
layout: [[{type: "qr", content: c => c.text}, {type: "text", content: c => c.text?.split("\n")}]]
|
||||
},
|
||||
{
|
||||
id: "id-qr-text-vertical", name: "ID + QR code + text below",
|
||||
description: "The code with the encoded text printed below it.",
|
||||
required_vars: ["itemId", "text", "userHandle"],
|
||||
layout: [[{type: "text", content: c => "Item: " + c.itemId}, GAP, {
|
||||
layout: [[{type: "text", content: c => "Item: " + c.itemId}, {
|
||||
type: "qr",
|
||||
content: c => c.text
|
||||
}, GAP, {type: "text", content: c => c.userHandle}]]
|
||||
},
|
||||
{
|
||||
id: "text", name: "Text only", description: "No code, just the text itself, as large as it fits.",
|
||||
required_vars: ["text"],
|
||||
layout: [{type: "text", content: c => c.text?.split("\n")}]
|
||||
}, {type: "text", content: c => c.userHandle}]]
|
||||
},
|
||||
{
|
||||
id: "item-handle", name: "Item handle",
|
||||
|
|
@ -159,12 +103,6 @@ export const LABEL_TEMPLATES = [
|
|||
required_vars: ["locationHandle"], tags: ["internal"],
|
||||
layout: [{type: "text", content: c => c.locationHandle}]
|
||||
},
|
||||
{
|
||||
id: "item-url", name: "Item URL",
|
||||
description: "The full item URL as text, with no code - for copying rather than scanning.",
|
||||
required_vars: ["itemUrl"], tags: ["external"],
|
||||
layout: [{type: "text", content: c => c.itemUrl}]
|
||||
},
|
||||
{
|
||||
id: "owner-handle", name: "Owner handle", description: "Just the owning user's handle, as text only.",
|
||||
required_vars: ["userHandle"], tags: ["internal"],
|
||||
|
|
@ -176,11 +114,17 @@ export const LABEL_TEMPLATES = [
|
|||
layout: [{type: "text", content: c => c.itemId}]
|
||||
},
|
||||
{
|
||||
id: "owner-id-text", name: "Owner + item ID",
|
||||
id: "owner-id-text-multiline", name: "Owner + item ID (same size)",
|
||||
description: "The owner's handle and the item id, as two lines of text - no code.",
|
||||
required_vars: ["userHandle", "itemId"], tags: ["internal"],
|
||||
layout: [{type: "text", content: c => [c.userHandle, c.itemId]}]
|
||||
},
|
||||
{
|
||||
id: "owner-id-text", name: "Owner + item ID",
|
||||
description: "The owner's handle and the item id, as two lines of text - no code.",
|
||||
required_vars: ["userHandle", "itemId"], tags: ["internal"],
|
||||
layout: [[{type: "text", content: c => [c.userHandle]},{type: "text", content: c => [c.itemId]}]]
|
||||
},
|
||||
{
|
||||
id: "location-id", name: "Location ID", description: "Just the bare storage location id, as text only.",
|
||||
required_vars: ["locationId"], tags: ["internal"],
|
||||
|
|
@ -196,25 +140,25 @@ export const LABEL_TEMPLATES = [
|
|||
id: "item-url-qr-handle", name: "Item URL + handle",
|
||||
description: "Scannable item URL, with the item's compact handle printed alongside.",
|
||||
required_vars: ["itemUrl", "itemHandle"], tags: ["external"],
|
||||
layout: [{type: "qr", content: c => c.itemUrl}, GAP, {type: "text", content: c => c.itemHandle}]
|
||||
layout: [{type: "qr", content: c => c.itemUrl}, {type: "text", content: c => c.itemHandle}]
|
||||
},
|
||||
{
|
||||
id: "item-url-qr-owner", name: "Item URL + owner",
|
||||
description: "Scannable item URL, with the owner's handle printed alongside.",
|
||||
required_vars: ["itemUrl", "userHandle"], tags: ["external"],
|
||||
layout: [{type: "qr", content: c => c.itemUrl}, GAP, {type: "text", content: c => c.userHandle}]
|
||||
layout: [{type: "qr", content: c => c.itemUrl}, {type: "text", content: c => c.userHandle}]
|
||||
},
|
||||
{
|
||||
id: "item-url-qr-id", name: "Item URL + item ID",
|
||||
description: "Scannable item URL, with the bare item id printed alongside.",
|
||||
required_vars: ["itemUrl", "itemId"], tags: ["external"],
|
||||
layout: [{type: "qr", content: c => c.itemUrl}, GAP, {type: "text", content: c => c.itemId}]
|
||||
layout: [{type: "qr", content: c => c.itemUrl}, {type: "text", content: c => c.itemId}]
|
||||
},
|
||||
{
|
||||
id: "item-url-qr-owner-id", name: "Item URL + owner + ID",
|
||||
description: "Scannable item URL, with the owner's handle and the item id on two lines alongside.",
|
||||
required_vars: ["itemUrl", "userHandle", "itemId"], tags: ["external"],
|
||||
layout: [{type: "qr", content: c => c.itemUrl}, GAP, {type: "text", content: c => [c.userHandle, c.itemId]}]
|
||||
layout: [{type: "qr", content: c => c.itemUrl}, {type: "text", content: c => [c.userHandle, c.itemId]}]
|
||||
},
|
||||
{
|
||||
id: "short-url-qr", name: "Short link (QR code)",
|
||||
|
|
@ -227,40 +171,26 @@ export const LABEL_TEMPLATES = [
|
|||
id: "item-url-qr-owner-id2", name: "Item URL + owner + ID",
|
||||
description: "Scannable item URL, with the owner's handle and the item id on two lines alongside.",
|
||||
required_vars: ["itemUrl", "userHandle", "itemId"], tags: ["external"],
|
||||
layout: [{type: "qr", content: c => c.itemUrl}, GAP, [{
|
||||
layout: [{type: "qr", content: c => c.itemUrl}, [{
|
||||
type: "text",
|
||||
content: c => c.userHandle
|
||||
}, GAP, {type: "text", content: c => c.itemId}]]
|
||||
}, {type: "text", content: c => c.itemId}]]
|
||||
},
|
||||
];
|
||||
|
||||
// Every distinct tag value used across LABEL_TEMPLATES' tags, in first-seen order - drives
|
||||
// LabelLayoutPreview.vue's filter buttons without hand-listing "internal"/"external" there.
|
||||
export const LABEL_TAGS = [...new Set(LABEL_TEMPLATES.flatMap(t => t.tags ?? []))];
|
||||
|
||||
// Every field name any template's required_vars names, in first-seen order.
|
||||
export const KNOWN_VARS = [...new Set(LABEL_TEMPLATES.flatMap(t => t.required_vars))];
|
||||
|
||||
// DERIVED_VARS shape and declaration-order invariant. See
|
||||
// docs/implementation.md#derived-vars-shape-and-ordering.
|
||||
export const DERIVED_VARS = {
|
||||
// Full user@domain handle, kept as separate user/domain base vars since that's how the
|
||||
// account is actually shaped (see federation.md's Unique Handles); this is just the
|
||||
// display/URL form.
|
||||
userHandle: {
|
||||
inputs: ["user", "domain"],
|
||||
calc: (f) => `${f.user}@${f.domain}`,
|
||||
},
|
||||
// Self-contained Item URL (see docs/design-in-progress/items-labels.md) - what a printed
|
||||
// label encodes, since scanning it must resolve everything with no other context. `webdomain`
|
||||
// defaults to this origin but is editable, since any frontend can resolve any handle.
|
||||
itemUrl: {
|
||||
inputs: ["webdomain", "userHandle", "itemId"],
|
||||
calc: (f) => `${f.webdomain}/i/${encodeHandleForUrl(f.userHandle)}/${f.itemId}`,
|
||||
},
|
||||
// Compact User-Qualified ID (see docs/handles-and-shortids.md#user-qualified-id) - owner
|
||||
// handle + a one-letter kind tag + local id, meaningful only where context already makes
|
||||
// clear it's Toolshed data, unlike itemUrl. `i` = item, `s` = storage location.
|
||||
itemHandle: {
|
||||
inputs: ["userHandle", "itemId"],
|
||||
calc: (f) => `${f.userHandle}:i${f.itemId}`,
|
||||
|
|
@ -271,60 +201,7 @@ export const DERIVED_VARS = {
|
|||
},
|
||||
};
|
||||
|
||||
// Text-input vars = template KNOWN_VARS minus derived ones, plus any var a DERIVED_VARS calc
|
||||
// needs (e.g. "webdomain") even if no template names it directly.
|
||||
export const BASE_VARS = [...new Set([
|
||||
...KNOWN_VARS.filter(v => !(v in DERIVED_VARS)),
|
||||
...Object.values(DERIVED_VARS).flatMap(d => d.inputs).filter(v => !(v in DERIVED_VARS)),
|
||||
])];
|
||||
|
||||
// Runs each DERIVED_VARS calc against `fields`, adding the result wherever its inputs are
|
||||
// present, so callers never need to know DERIVED_VARS' {inputs, calc} shape.
|
||||
export function withDerivedVars(fields) {
|
||||
const result = {...fields};
|
||||
for (const [name, {inputs, calc}] of Object.entries(DERIVED_VARS)) {
|
||||
if (inputs.every(v => result[v])) {
|
||||
result[name] = calc(result);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function walkLeaves(node, fn) {
|
||||
if (Array.isArray(node)) {
|
||||
node.forEach(child => walkLeaves(child, fn));
|
||||
} else {
|
||||
fn(node);
|
||||
}
|
||||
}
|
||||
|
||||
function mapTree(node, fn) {
|
||||
return Array.isArray(node) ? node.map(child => mapTree(child, fn)) : fn(node);
|
||||
}
|
||||
|
||||
// Resolved only if every part is - a single string for a QR-family/"text" leaf, every line for a
|
||||
// multi-line "text" (see "owner-id-text" and "item-url-qr-owner-id" above).
|
||||
function isResolved(value) {
|
||||
return Array.isArray(value) ? value.every(isResolved) : value !== undefined && value !== null;
|
||||
}
|
||||
|
||||
// Shared by LabelLayoutPreview.vue's grid and Print.vue's preview, so neither reimplements
|
||||
// leaf-walking/field-resolving itself.
|
||||
export function templateIsAvailable(t, fields) {
|
||||
let available = true;
|
||||
walkLeaves(t.layout, leaf => {
|
||||
if (leaf.type !== "empty" && !isResolved(leaf.content(fields))) {
|
||||
available = false;
|
||||
}
|
||||
});
|
||||
return available;
|
||||
}
|
||||
|
||||
export function templateContent(t, fields) {
|
||||
if (!templateIsAvailable(t, fields)) {
|
||||
const err = new Error("This layout needs more fields filled in before it can be drawn.");
|
||||
err.short = "missing data";
|
||||
throw err;
|
||||
}
|
||||
return mapTree(t.layout, leaf => leaf.type === "empty" ? leaf : {...leaf, value: leaf.content(fields)});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import {loadAnyDCode} from "../vendor/anyd-qr.js";
|
||||
import {encodeHandleForUrl} from "@/router"
|
||||
import {drawPixelText, preloadPixelFontRenderer} from "@/pixel-font.js";
|
||||
import {DERIVED_VARS} from "@/label-layouts.js";
|
||||
|
||||
// Re-exported so every caller can preload both the QR encoder and the bitmap-font rasterizer
|
||||
// (see fontTierFor's `pixel` tiers below) the same way, alongside this file's own preloadQrEncoder.
|
||||
|
|
@ -349,7 +350,7 @@ function measureTextBlock(ctx, lines, referencePx) {
|
|||
return {width, height};
|
||||
}
|
||||
|
||||
// Converts a resolved content tree (see label-layouts.js's resolveContent) into one ready for layout. See
|
||||
// Converts a resolved content tree (see templateContent below) into one ready for layout. See
|
||||
// docs/implementation.md#render-tree-construction.
|
||||
function buildRenderTree(ctx, node, referencePx) {
|
||||
if (isSplit(node)) {
|
||||
|
|
@ -483,7 +484,7 @@ function layoutContent(ctx, content, fixedSize, maxLength, referencePx, pxPerMm,
|
|||
return {tree, length, warnings, qrInfo, textInfo};
|
||||
}
|
||||
|
||||
// The tape-fed layout: draws a fully resolved content tree (see label-layouts.js's resolveContent) at the tape's
|
||||
// The tape-fed layout: draws a fully resolved content tree (see templateContent below) at the tape's
|
||||
// real pixel dimensions. See docs/implementation.md#tape-fed-label-drawing. Returns
|
||||
// {textSizesPx}: each "text" leaf's effective font size, in the tree's own left-to-right,
|
||||
// top-to-bottom order; {warnings}: {short, message} scan-reliability entries from
|
||||
|
|
@ -627,3 +628,54 @@ export function buildLabelFields(prefill) {
|
|||
const build = LABEL_FIELD_BUILDERS[prefill.kind];
|
||||
return build ? build(prefill.components) : {};
|
||||
}
|
||||
|
||||
// Runs each DERIVED_VARS calc against `fields`, adding the result wherever its inputs are
|
||||
// present, so callers never need to know DERIVED_VARS' {inputs, calc} shape.
|
||||
export function withDerivedVars(fields) {
|
||||
const result = {...fields};
|
||||
for (const [name, {inputs, calc}] of Object.entries(DERIVED_VARS)) {
|
||||
if (inputs.every(v => result[v])) {
|
||||
result[name] = calc(result);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function walkLeaves(node, fn) {
|
||||
if (Array.isArray(node)) {
|
||||
node.forEach(child => walkLeaves(child, fn));
|
||||
} else {
|
||||
fn(node);
|
||||
}
|
||||
}
|
||||
|
||||
function mapTree(node, fn) {
|
||||
return Array.isArray(node) ? node.map(child => mapTree(child, fn)) : fn(node);
|
||||
}
|
||||
|
||||
// Resolved only if every part is - a single string for a QR-family/"text" leaf, every line for a
|
||||
// multi-line "text" (see label-layouts.js's "owner-id-text" and "item-url-qr-owner-id" templates).
|
||||
function isResolved(value) {
|
||||
return Array.isArray(value) ? value.every(isResolved) : value !== undefined && value !== null;
|
||||
}
|
||||
|
||||
// Shared by LabelLayoutPreview.vue's grid and Print.vue's preview, so neither reimplements
|
||||
// leaf-walking/field-resolving itself.
|
||||
export function templateIsAvailable(t, fields) {
|
||||
let available = true;
|
||||
walkLeaves(t.layout, leaf => {
|
||||
if (leaf.type !== "empty" && !isResolved(leaf.content(fields))) {
|
||||
available = false;
|
||||
}
|
||||
});
|
||||
return available;
|
||||
}
|
||||
|
||||
export function templateContent(t, fields) {
|
||||
if (!templateIsAvailable(t, fields)) {
|
||||
const err = new Error("This layout needs more fields filled in before it can be drawn.");
|
||||
err.short = "missing data";
|
||||
throw err;
|
||||
}
|
||||
return mapTree(t.layout, leaf => leaf.type === "empty" ? leaf : {...leaf, value: leaf.content(fields)});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -197,9 +197,11 @@ import {
|
|||
buildLabelContent,
|
||||
buildLabelFields,
|
||||
preloadQrEncoder,
|
||||
preloadPixelFontRenderer
|
||||
preloadPixelFontRenderer,
|
||||
withDerivedVars,
|
||||
templateContent
|
||||
} from "@/label.js";
|
||||
import {LABEL_TEMPLATES, BASE_VARS, DERIVED_VARS, withDerivedVars, templateContent} from "@/label-layouts.js";
|
||||
import {LABEL_TEMPLATES, BASE_VARS, DERIVED_VARS} from "@/label-layouts.js";
|
||||
import {shortenedRoute} from "@/router";
|
||||
|
||||
// Print.vue-local calculated fields on top of label-layouts.js's DERIVED_VARS. See
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue