This commit is contained in:
j3d1 2026-08-23 00:56:03 +02:00
parent 5c3b7fc252
commit bbe52e4a78
9 changed files with 817 additions and 287 deletions

View file

@ -1,33 +1,118 @@
// Each template's `layout` is a tree as described in label.js, with "qrcode"/"text" leaves'
// `content` a function from the resolved field values (see label.js's buildLabelFields) to what
// they render - `null`/`undefined` from that function means the field isn't available yet (see
// templateIsAvailable below). A template is only selectable once every leaf's `content` resolves
// to a value.
// Each template's `layout` is a tree as described in label.js, with leaves whose `type` is one of
// label.js's QR_LEAF_TYPES keys or "text", and whose `content` is a function from the resolved
// field values (see label.js's buildLabelFields) to what they render - `null`/`undefined` from
// that function means the field isn't available yet (see templateIsAvailable below). A template
// is only selectable once every leaf's `content` resolves to a value.
const GAP = {type: "empty", "min-width": "1mm", "min-height": "1mm"};
// The full "just the code" matrix: every {symbology, error-correction level, [rMQR] size
// strategy} label.js's QR_LEAF_TYPES supports, one template each - a plain `id` (no suffix) is
// always anyd's own defaults, ecc "M" and (rMQR only) size "balanced". Coverage isn't uniform
// (see QR_LEAF_TYPES): full QR gets all four ecc grades L/M/Q/H; Micro QR swaps "L" (QR's actual
// lowest) for the even-lower, M1-only, detection-only "Detection" and has no "H" at all; rMQR
// only ever supports ecc "M" or "H", each of those crossed with all three size strategies
// (balanced/min/max). Generated (rather than hand-writing every near-duplicate entry) so a
// symbology/level/size this matrix is missing is one new row here, not a new block to keep in
// sync with its neighbors. `id` doubles as the layout's leaf `type`, since that's exactly what
// QR_LEAF_TYPES is keyed by.
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}]}));
export const LABEL_TEMPLATES = [
{
id: "qr", name: "QR code only", description: "Just the code - smallest label, prints fastest.",
required_vars: ["text"],
layout: [{type: "qrcode", content: c => c.text}]
id: "mqr-token", name: "MQR Token", description: "The code with the encoded text printed next to it.",
required_vars: ["shortId"],
layout: [{type: "mqr", content: c => c.shortId}]
},
{
id: "qr-url", name: "MQR Token", description: "The code with the encoded text printed next to it.",
required_vars: ["shortUrl"],
layout: [{type: "qr-h", content: c => c.shortUrl}]
},...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: "qrcode", content: c => c.text}, GAP, {type: "text", content: c => c.text?.split("\n")}]
layout: [{type: "qr", content: c => c.text}, GAP, {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: "qrcode", content: c => c.text}, GAP, {type: "text", content: c => c.text?.split("\n")}]]
layout: [[{type: "qr", content: c => c.text}, GAP, {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, {
type: "qrcode",
type: "qr",
content: c => c.text
}, GAP, {type: "text", content: c => c.userHandle}]]
},
@ -68,31 +153,38 @@ 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"],
layout: [{type: "qrcode", content: c => c.itemUrl}, GAP, {type: "text", content: c => c.itemHandle}]
layout: [{type: "qr", content: c => c.itemUrl}, GAP, {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"],
layout: [{type: "qrcode", content: c => c.itemUrl}, GAP, {type: "text", content: c => c.userHandle}]
layout: [{type: "qr", content: c => c.itemUrl}, GAP, {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"],
layout: [{type: "qrcode", content: c => c.itemUrl}, GAP, {type: "text", content: c => c.itemId}]
layout: [{type: "qr", content: c => c.itemUrl}, GAP, {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"],
layout: [{type: "qrcode", content: c => c.itemUrl}, GAP, {type: "text", content: c => [c.userHandle, c.itemId]}]
layout: [{type: "qr", content: c => c.itemUrl}, GAP, {type: "text", content: c => [c.userHandle, c.itemId]}]
},
{
id: "short-url-qr", name: "Short link (QR code)",
description: "Scannable short link for this item or storage location - more compact than "
+ "the full URL. Available for any element with a resolvable short link, not just items.",
required_vars: ["shortUrl"],
layout: [{type: "qr", content: c => c.shortUrl}]
},
{
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"],
layout: [{type: "qrcode", content: c => c.itemUrl}, GAP, [{
layout: [{type: "qr", content: c => c.itemUrl}, GAP, [{
type: "text",
content: c => c.userHandle
}, GAP, {type: "text", content: c => c.itemId}]]
@ -171,8 +263,8 @@ function mapTree(node, fn) {
}
// A content leaf's resolved value counts as present only if every part of it is - a single
// string for "qrcode"/plain "text", every line for a multi-line "text" (see LABEL_TEMPLATES'
// "owner-id-text" and "item-url-qr-owner-id").
// string for a QR-family leaf (any label.js QR_LEAF_TYPES entry) or plain "text", every line for a
// multi-line "text" (see LABEL_TEMPLATES' "owner-id-text" and "item-url-qr-owner-id").
function isResolved(value) {
return Array.isArray(value) ? value.every(isResolved) : value !== undefined && value !== null;
}