@ai-matrx/print 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +56 -0
- package/LICENSE +21 -0
- package/README.md +141 -0
- package/dist/core.cjs +184 -0
- package/dist/core.cjs.map +1 -0
- package/dist/core.d.cts +139 -0
- package/dist/core.d.ts +139 -0
- package/dist/core.js +163 -0
- package/dist/core.js.map +1 -0
- package/dist/flashcards.cjs +1709 -0
- package/dist/flashcards.cjs.map +1 -0
- package/dist/flashcards.d.cts +35 -0
- package/dist/flashcards.d.ts +35 -0
- package/dist/flashcards.js +1686 -0
- package/dist/flashcards.js.map +1 -0
- package/dist/index.cjs +19 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +5 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -0
- package/dist/labels.cjs +1035 -0
- package/dist/labels.cjs.map +1 -0
- package/dist/labels.d.cts +270 -0
- package/dist/labels.d.ts +270 -0
- package/dist/labels.js +1002 -0
- package/dist/labels.js.map +1 -0
- package/dist/pdf.cjs +325 -0
- package/dist/pdf.cjs.map +1 -0
- package/dist/pdf.d.cts +151 -0
- package/dist/pdf.d.ts +151 -0
- package/dist/pdf.js +294 -0
- package/dist/pdf.js.map +1 -0
- package/dist/qr.cjs +328 -0
- package/dist/qr.cjs.map +1 -0
- package/dist/qr.d.cts +91 -0
- package/dist/qr.d.ts +91 -0
- package/dist/qr.js +297 -0
- package/dist/qr.js.map +1 -0
- package/dist/react.cjs +1569 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +301 -0
- package/dist/react.d.ts +301 -0
- package/dist/react.js +1539 -0
- package/dist/react.js.map +1 -0
- package/package.json +163 -0
package/dist/react.js
ADDED
|
@@ -0,0 +1,1539 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/react.tsx
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
import { useEffect, useMemo, useState } from "react";
|
|
6
|
+
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
|
7
|
+
import { Drawer as DrawerPrimitive } from "vaul";
|
|
8
|
+
import { twMerge } from "tailwind-merge";
|
|
9
|
+
|
|
10
|
+
// src/core.ts
|
|
11
|
+
function escapeHtml(str) {
|
|
12
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
13
|
+
}
|
|
14
|
+
function openPrintWindow(htmlDocument, filename = "print") {
|
|
15
|
+
const win = window.open("", "_blank", "width=920,height=720,scrollbars=yes");
|
|
16
|
+
if (!win) {
|
|
17
|
+
const blob = new Blob([htmlDocument], { type: "text/html;charset=utf-8" });
|
|
18
|
+
const url = URL.createObjectURL(blob);
|
|
19
|
+
const a = document.createElement("a");
|
|
20
|
+
a.href = url;
|
|
21
|
+
a.download = `${filename.replace(/\s+/g, "-").toLowerCase()}.html`;
|
|
22
|
+
document.body.appendChild(a);
|
|
23
|
+
a.click();
|
|
24
|
+
document.body.removeChild(a);
|
|
25
|
+
URL.revokeObjectURL(url);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
win.document.open();
|
|
29
|
+
win.document.write(htmlDocument);
|
|
30
|
+
win.document.close();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// src/qr.ts
|
|
34
|
+
var QR_MIN_QUIET_ZONE_MODULES = 4;
|
|
35
|
+
var DEFAULT_QR_EC_LEVEL = "M";
|
|
36
|
+
var QR_MIN_VERSION = 1;
|
|
37
|
+
var QR_MAX_VERSION = 40;
|
|
38
|
+
var BYTE_CAPACITY = [
|
|
39
|
+
/* v1 */
|
|
40
|
+
[17, 14, 11, 7],
|
|
41
|
+
/* v2 */
|
|
42
|
+
[32, 26, 20, 14],
|
|
43
|
+
/* v3 */
|
|
44
|
+
[53, 42, 32, 24],
|
|
45
|
+
/* v4 */
|
|
46
|
+
[78, 62, 46, 34],
|
|
47
|
+
/* v5 */
|
|
48
|
+
[106, 84, 60, 44],
|
|
49
|
+
/* v6 */
|
|
50
|
+
[134, 106, 74, 58],
|
|
51
|
+
/* v7 */
|
|
52
|
+
[154, 122, 86, 64],
|
|
53
|
+
/* v8 */
|
|
54
|
+
[192, 152, 108, 84],
|
|
55
|
+
/* v9 */
|
|
56
|
+
[230, 180, 130, 98],
|
|
57
|
+
/* v10 */
|
|
58
|
+
[271, 213, 151, 119],
|
|
59
|
+
/* v11 */
|
|
60
|
+
[321, 251, 177, 137],
|
|
61
|
+
/* v12 */
|
|
62
|
+
[367, 287, 203, 155],
|
|
63
|
+
/* v13 */
|
|
64
|
+
[425, 331, 241, 177],
|
|
65
|
+
/* v14 */
|
|
66
|
+
[458, 362, 258, 194],
|
|
67
|
+
/* v15 */
|
|
68
|
+
[520, 412, 292, 220],
|
|
69
|
+
/* v16 */
|
|
70
|
+
[586, 450, 322, 250],
|
|
71
|
+
/* v17 */
|
|
72
|
+
[644, 504, 364, 280],
|
|
73
|
+
/* v18 */
|
|
74
|
+
[718, 560, 394, 310],
|
|
75
|
+
/* v19 */
|
|
76
|
+
[792, 624, 442, 338],
|
|
77
|
+
/* v20 */
|
|
78
|
+
[858, 666, 482, 382],
|
|
79
|
+
/* v21 */
|
|
80
|
+
[929, 711, 509, 403],
|
|
81
|
+
/* v22 */
|
|
82
|
+
[1003, 779, 565, 439],
|
|
83
|
+
/* v23 */
|
|
84
|
+
[1091, 857, 611, 461],
|
|
85
|
+
/* v24 */
|
|
86
|
+
[1171, 911, 661, 511],
|
|
87
|
+
/* v25 */
|
|
88
|
+
[1273, 997, 715, 535],
|
|
89
|
+
/* v26 */
|
|
90
|
+
[1367, 1059, 751, 593],
|
|
91
|
+
/* v27 */
|
|
92
|
+
[1465, 1125, 805, 625],
|
|
93
|
+
/* v28 */
|
|
94
|
+
[1528, 1190, 868, 658],
|
|
95
|
+
/* v29 */
|
|
96
|
+
[1628, 1264, 908, 698],
|
|
97
|
+
/* v30 */
|
|
98
|
+
[1732, 1370, 982, 742],
|
|
99
|
+
/* v31 */
|
|
100
|
+
[1840, 1452, 1030, 790],
|
|
101
|
+
/* v32 */
|
|
102
|
+
[1952, 1538, 1112, 842],
|
|
103
|
+
/* v33 */
|
|
104
|
+
[2068, 1628, 1168, 898],
|
|
105
|
+
/* v34 */
|
|
106
|
+
[2188, 1722, 1228, 958],
|
|
107
|
+
/* v35 */
|
|
108
|
+
[2303, 1809, 1283, 983],
|
|
109
|
+
/* v36 */
|
|
110
|
+
[2431, 1911, 1351, 1051],
|
|
111
|
+
/* v37 */
|
|
112
|
+
[2563, 1989, 1423, 1093],
|
|
113
|
+
/* v38 */
|
|
114
|
+
[2699, 2099, 1499, 1139],
|
|
115
|
+
/* v39 */
|
|
116
|
+
[2809, 2213, 1579, 1219],
|
|
117
|
+
/* v40 */
|
|
118
|
+
[2953, 2331, 1663, 1273]
|
|
119
|
+
];
|
|
120
|
+
var EC_COLUMN = { L: 0, M: 1, Q: 2, H: 3 };
|
|
121
|
+
function assertEcLevel(ecLevel) {
|
|
122
|
+
const column = EC_COLUMN[ecLevel];
|
|
123
|
+
if (column === void 0) {
|
|
124
|
+
throw new Error(`Unknown QR error correction level "${String(ecLevel)}". Expected one of "L", "M", "Q", "H".`);
|
|
125
|
+
}
|
|
126
|
+
return column;
|
|
127
|
+
}
|
|
128
|
+
function assertVersion(version) {
|
|
129
|
+
if (typeof version !== "number" || !Number.isInteger(version) || version < QR_MIN_VERSION || version > QR_MAX_VERSION) {
|
|
130
|
+
throw new Error(
|
|
131
|
+
`QR version must be an integer between ${QR_MIN_VERSION} and ${QR_MAX_VERSION}, got ${String(version)}.`
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
return version;
|
|
135
|
+
}
|
|
136
|
+
function qrByteCapacity(version, ecLevel) {
|
|
137
|
+
const column = assertEcLevel(ecLevel);
|
|
138
|
+
const row = BYTE_CAPACITY[assertVersion(version) - 1];
|
|
139
|
+
if (row === void 0) {
|
|
140
|
+
throw new Error(`No QR capacity row for version ${version}.`);
|
|
141
|
+
}
|
|
142
|
+
return row[column];
|
|
143
|
+
}
|
|
144
|
+
function qrModuleCount(version) {
|
|
145
|
+
return 17 + 4 * assertVersion(version);
|
|
146
|
+
}
|
|
147
|
+
function minQrVersion(payloadByteLen, ecLevel) {
|
|
148
|
+
const column = assertEcLevel(ecLevel);
|
|
149
|
+
if (typeof payloadByteLen !== "number" || !Number.isInteger(payloadByteLen) || payloadByteLen < 0) {
|
|
150
|
+
throw new Error(`QR payload byte length must be a non-negative integer, got ${String(payloadByteLen)}.`);
|
|
151
|
+
}
|
|
152
|
+
for (let version = QR_MIN_VERSION; version <= QR_MAX_VERSION; version += 1) {
|
|
153
|
+
const row = BYTE_CAPACITY[version - 1];
|
|
154
|
+
if (row !== void 0 && row[column] >= payloadByteLen) {
|
|
155
|
+
return version;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
const max = qrByteCapacity(QR_MAX_VERSION, ecLevel);
|
|
159
|
+
throw new Error(
|
|
160
|
+
`QR payload of ${payloadByteLen} bytes does not fit any QR version at error correction level "${ecLevel}" (version ${QR_MAX_VERSION} holds ${max} bytes). Shorten the payload \u2014 a short resolver URL is the intended fix \u2014 or, only for a large clean code, drop to a lower error correction level.`
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
function resolveEcLevel(ecLevel) {
|
|
164
|
+
if (ecLevel === void 0) return DEFAULT_QR_EC_LEVEL;
|
|
165
|
+
assertEcLevel(ecLevel);
|
|
166
|
+
return ecLevel;
|
|
167
|
+
}
|
|
168
|
+
function resolveQuietZone(quietZoneModules) {
|
|
169
|
+
if (typeof quietZoneModules !== "number" || !Number.isFinite(quietZoneModules)) {
|
|
170
|
+
return QR_MIN_QUIET_ZONE_MODULES;
|
|
171
|
+
}
|
|
172
|
+
return Math.max(QR_MIN_QUIET_ZONE_MODULES, Math.floor(quietZoneModules));
|
|
173
|
+
}
|
|
174
|
+
function resolveWidthPx(widthPx) {
|
|
175
|
+
if (widthPx === void 0) return void 0;
|
|
176
|
+
if (typeof widthPx !== "number" || !Number.isFinite(widthPx) || widthPx <= 0) {
|
|
177
|
+
throw new Error(`QR widthPx must be a positive number, got ${String(widthPx)}.`);
|
|
178
|
+
}
|
|
179
|
+
return Math.round(widthPx);
|
|
180
|
+
}
|
|
181
|
+
function assertValue(value) {
|
|
182
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
183
|
+
throw new Error("QR value must be a non-empty string.");
|
|
184
|
+
}
|
|
185
|
+
return value;
|
|
186
|
+
}
|
|
187
|
+
async function generateQrDataUrl(value, opts) {
|
|
188
|
+
assertValue(value);
|
|
189
|
+
const width = resolveWidthPx(opts?.widthPx);
|
|
190
|
+
const options = {
|
|
191
|
+
errorCorrectionLevel: resolveEcLevel(opts?.ecLevel),
|
|
192
|
+
margin: resolveQuietZone(opts?.quietZoneModules)
|
|
193
|
+
};
|
|
194
|
+
if (width !== void 0) options.width = width;
|
|
195
|
+
const QRCode = await import("qrcode");
|
|
196
|
+
return QRCode.toDataURL(value, options);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// src/labels.ts
|
|
200
|
+
var AVERY_5160 = {
|
|
201
|
+
id: "avery-5160",
|
|
202
|
+
name: 'Address 1" \xD7 2\u215D" \u2014 30 per sheet',
|
|
203
|
+
stockCode: "Avery 5160 / 8160",
|
|
204
|
+
sheetWIn: 8.5,
|
|
205
|
+
sheetHIn: 11,
|
|
206
|
+
marginTopIn: 0.5,
|
|
207
|
+
marginLeftIn: 0.1875,
|
|
208
|
+
labelWIn: 2.625,
|
|
209
|
+
labelHIn: 1,
|
|
210
|
+
cols: 3,
|
|
211
|
+
rows: 10,
|
|
212
|
+
gutterXIn: 0.125,
|
|
213
|
+
gutterYIn: 0,
|
|
214
|
+
cornerRadiusIn: 0.1
|
|
215
|
+
};
|
|
216
|
+
var LABEL_TEMPLATES = [
|
|
217
|
+
AVERY_5160,
|
|
218
|
+
{
|
|
219
|
+
id: "avery-5163",
|
|
220
|
+
name: 'Shipping 2" \xD7 4" \u2014 10 per sheet',
|
|
221
|
+
stockCode: "Avery 5163 / 8163",
|
|
222
|
+
sheetWIn: 8.5,
|
|
223
|
+
sheetHIn: 11,
|
|
224
|
+
marginTopIn: 0.5,
|
|
225
|
+
marginLeftIn: 0.15625,
|
|
226
|
+
labelWIn: 4,
|
|
227
|
+
labelHIn: 2,
|
|
228
|
+
cols: 2,
|
|
229
|
+
rows: 5,
|
|
230
|
+
gutterXIn: 0.1875,
|
|
231
|
+
gutterYIn: 0,
|
|
232
|
+
cornerRadiusIn: 0.125
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
id: "avery-5164",
|
|
236
|
+
name: 'Shipping 3\u2153" \xD7 4" \u2014 6 per sheet',
|
|
237
|
+
stockCode: "Avery 5164 / 8164",
|
|
238
|
+
sheetWIn: 8.5,
|
|
239
|
+
sheetHIn: 11,
|
|
240
|
+
marginTopIn: 0.5,
|
|
241
|
+
marginLeftIn: 0.15625,
|
|
242
|
+
labelWIn: 4,
|
|
243
|
+
labelHIn: 3.3333333,
|
|
244
|
+
cols: 2,
|
|
245
|
+
rows: 3,
|
|
246
|
+
gutterXIn: 0.1875,
|
|
247
|
+
gutterYIn: 0,
|
|
248
|
+
cornerRadiusIn: 0.125
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
id: "avery-22806",
|
|
252
|
+
name: 'Square 2" \xD7 2" \u2014 12 per sheet',
|
|
253
|
+
stockCode: "Avery 22806",
|
|
254
|
+
sheetWIn: 8.5,
|
|
255
|
+
sheetHIn: 11,
|
|
256
|
+
marginTopIn: 0.75,
|
|
257
|
+
marginLeftIn: 0.5,
|
|
258
|
+
labelWIn: 2,
|
|
259
|
+
labelHIn: 2,
|
|
260
|
+
cols: 3,
|
|
261
|
+
rows: 4,
|
|
262
|
+
gutterXIn: 0.75,
|
|
263
|
+
gutterYIn: 0.5,
|
|
264
|
+
cornerRadiusIn: 0.09375
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
id: "avery-22807",
|
|
268
|
+
name: 'Round 2" \u2014 12 per sheet',
|
|
269
|
+
stockCode: "Avery 22807",
|
|
270
|
+
sheetWIn: 8.5,
|
|
271
|
+
sheetHIn: 11,
|
|
272
|
+
marginTopIn: 0.75,
|
|
273
|
+
marginLeftIn: 0.5,
|
|
274
|
+
labelWIn: 2,
|
|
275
|
+
labelHIn: 2,
|
|
276
|
+
cols: 3,
|
|
277
|
+
rows: 4,
|
|
278
|
+
gutterXIn: 0.75,
|
|
279
|
+
gutterYIn: 0.5,
|
|
280
|
+
cornerRadiusIn: 1,
|
|
281
|
+
round: true
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
id: "brother-dk-1202",
|
|
285
|
+
name: 'Shipping roll 2.4" \xD7 3.9" \u2014 die-cut',
|
|
286
|
+
stockCode: "Brother DK-1202",
|
|
287
|
+
sheetWIn: 2.4,
|
|
288
|
+
sheetHIn: 3.9,
|
|
289
|
+
marginTopIn: 0,
|
|
290
|
+
marginLeftIn: 0,
|
|
291
|
+
labelWIn: 2.4,
|
|
292
|
+
labelHIn: 3.9,
|
|
293
|
+
cols: 1,
|
|
294
|
+
rows: 1,
|
|
295
|
+
gutterXIn: 0,
|
|
296
|
+
gutterYIn: 0,
|
|
297
|
+
kind: "roll"
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
id: "brother-dk-11201",
|
|
301
|
+
name: 'Address roll 1.14" \xD7 3.5" \u2014 die-cut',
|
|
302
|
+
stockCode: "Brother DK-11201",
|
|
303
|
+
sheetWIn: 1.14,
|
|
304
|
+
sheetHIn: 3.5,
|
|
305
|
+
marginTopIn: 0,
|
|
306
|
+
marginLeftIn: 0,
|
|
307
|
+
labelWIn: 1.14,
|
|
308
|
+
labelHIn: 3.5,
|
|
309
|
+
cols: 1,
|
|
310
|
+
rows: 1,
|
|
311
|
+
gutterXIn: 0,
|
|
312
|
+
gutterYIn: 0,
|
|
313
|
+
kind: "roll"
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
id: "dymo-30252",
|
|
317
|
+
name: 'Address roll 1\u215B" \xD7 3\xBD"',
|
|
318
|
+
stockCode: "DYMO 30252",
|
|
319
|
+
sheetWIn: 1.125,
|
|
320
|
+
sheetHIn: 3.5,
|
|
321
|
+
marginTopIn: 0,
|
|
322
|
+
marginLeftIn: 0,
|
|
323
|
+
labelWIn: 1.125,
|
|
324
|
+
labelHIn: 3.5,
|
|
325
|
+
cols: 1,
|
|
326
|
+
rows: 1,
|
|
327
|
+
gutterXIn: 0,
|
|
328
|
+
gutterYIn: 0,
|
|
329
|
+
kind: "roll"
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
id: "roll-4x6",
|
|
333
|
+
name: 'Shipping roll 4" \xD7 6"',
|
|
334
|
+
stockCode: "Thermal 4x6",
|
|
335
|
+
sheetWIn: 4,
|
|
336
|
+
sheetHIn: 6,
|
|
337
|
+
marginTopIn: 0,
|
|
338
|
+
marginLeftIn: 0,
|
|
339
|
+
labelWIn: 4,
|
|
340
|
+
labelHIn: 6,
|
|
341
|
+
cols: 1,
|
|
342
|
+
rows: 1,
|
|
343
|
+
gutterXIn: 0,
|
|
344
|
+
gutterYIn: 0,
|
|
345
|
+
kind: "roll"
|
|
346
|
+
}
|
|
347
|
+
];
|
|
348
|
+
function getLabelTemplate(id) {
|
|
349
|
+
return LABEL_TEMPLATES.find((t) => t.id === id);
|
|
350
|
+
}
|
|
351
|
+
function assertTemplateGeometry(t) {
|
|
352
|
+
const w = t.marginLeftIn * 2 + t.cols * t.labelWIn + (t.cols - 1) * t.gutterXIn;
|
|
353
|
+
const h = t.marginTopIn * 2 + t.rows * t.labelHIn + (t.rows - 1) * t.gutterYIn;
|
|
354
|
+
const tol = 1 / 64;
|
|
355
|
+
if (Math.abs(w - t.sheetWIn) > tol || Math.abs(h - t.sheetHIn) > tol) {
|
|
356
|
+
console.error(
|
|
357
|
+
`[label-print] Template ${t.id} does not tile its sheet: grid ${w.toFixed(4)}\xD7${h.toFixed(4)}in vs sheet ${t.sheetWIn}\xD7${t.sheetHIn}in`
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
if (typeof process === "undefined" || true) {
|
|
362
|
+
LABEL_TEMPLATES.forEach(assertTemplateGeometry);
|
|
363
|
+
}
|
|
364
|
+
var DEFAULTS = {
|
|
365
|
+
startAtLabel: 1,
|
|
366
|
+
copiesPerCode: 1,
|
|
367
|
+
showCaption: true,
|
|
368
|
+
showLines: true,
|
|
369
|
+
rangeFrom: 0,
|
|
370
|
+
rangeTo: 0,
|
|
371
|
+
ecLevel: "M",
|
|
372
|
+
calibrationPage: false
|
|
373
|
+
};
|
|
374
|
+
function resolveSettings(s) {
|
|
375
|
+
const num = (v, d, min = 0) => typeof v === "number" && !Number.isNaN(v) ? Math.max(min, v) : d;
|
|
376
|
+
const ec = s?.ecLevel;
|
|
377
|
+
return {
|
|
378
|
+
startAtLabel: num(s?.startAtLabel, DEFAULTS.startAtLabel, 1),
|
|
379
|
+
copiesPerCode: num(s?.copiesPerCode, DEFAULTS.copiesPerCode, 1),
|
|
380
|
+
showCaption: s?.showCaption ?? DEFAULTS.showCaption,
|
|
381
|
+
showLines: s?.showLines ?? DEFAULTS.showLines,
|
|
382
|
+
rangeFrom: num(s?.rangeFrom, 0),
|
|
383
|
+
rangeTo: num(s?.rangeTo, 0),
|
|
384
|
+
ecLevel: ec === "L" || ec === "Q" ? ec : "M",
|
|
385
|
+
calibrationPage: s?.calibrationPage ?? false
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
var PRINT_DPI = 300;
|
|
389
|
+
var QUIET_ZONE_MODULES = 4;
|
|
390
|
+
async function generateQrDataUri(value, ecLevel, sizeInches) {
|
|
391
|
+
return generateQrDataUrl(value, {
|
|
392
|
+
ecLevel,
|
|
393
|
+
quietZoneModules: QUIET_ZONE_MODULES,
|
|
394
|
+
widthPx: Math.round(sizeInches * PRINT_DPI)
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
function computeCellLayout(t, showCaption, hasBadge = false) {
|
|
398
|
+
const padIn = Math.min(0.08, t.labelHIn * 0.06);
|
|
399
|
+
const wide = t.labelWIn / t.labelHIn >= 1.8;
|
|
400
|
+
const badgeZoneIn = hasBadge ? Math.min(0.4, t.labelHIn * 0.22) : 0;
|
|
401
|
+
if (wide) {
|
|
402
|
+
return { wide, qrIn: t.labelHIn - padIn * 2, padIn, badgeZoneIn };
|
|
403
|
+
}
|
|
404
|
+
const inset = t.round ? t.labelWIn * 0.15 : 0;
|
|
405
|
+
const captionZone = showCaption ? Math.min(0.28, t.labelHIn * 0.16) : 0;
|
|
406
|
+
const qrIn = Math.min(
|
|
407
|
+
t.labelWIn - padIn * 2 - inset * 2,
|
|
408
|
+
t.labelHIn - padIn * 2 - inset * 2 - captionZone - badgeZoneIn
|
|
409
|
+
);
|
|
410
|
+
return { wide, qrIn, padIn, badgeZoneIn };
|
|
411
|
+
}
|
|
412
|
+
function sheetStyles(t) {
|
|
413
|
+
const radius = t.round ? `${t.labelWIn / 2}in` : t.cornerRadiusIn ? `${t.cornerRadiusIn}in` : "0";
|
|
414
|
+
return `
|
|
415
|
+
@page { size: ${t.sheetWIn}in ${t.sheetHIn}in; margin: 0; }
|
|
416
|
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
417
|
+
html, body { width: ${t.sheetWIn}in; background: #fff; color: #000;
|
|
418
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; }
|
|
419
|
+
|
|
420
|
+
.lp-page {
|
|
421
|
+
width: ${t.sheetWIn}in; height: ${t.sheetHIn}in;
|
|
422
|
+
position: relative; overflow: hidden;
|
|
423
|
+
page-break-after: always; break-after: page;
|
|
424
|
+
border-bottom: 1px solid #e2e8f0; /* screen-only separator */
|
|
425
|
+
}
|
|
426
|
+
.lp-page:last-child { page-break-after: auto; break-after: auto; border-bottom: none; }
|
|
427
|
+
|
|
428
|
+
.lp-cell {
|
|
429
|
+
position: absolute;
|
|
430
|
+
width: ${t.labelWIn}in; height: ${t.labelHIn}in;
|
|
431
|
+
border-radius: ${radius};
|
|
432
|
+
overflow: hidden;
|
|
433
|
+
-webkit-print-color-adjust: exact; print-color-adjust: exact;
|
|
434
|
+
}
|
|
435
|
+
.lp-qr {
|
|
436
|
+
display: block;
|
|
437
|
+
image-rendering: pixelated; /* no resampling blur on crisp modules */
|
|
438
|
+
}
|
|
439
|
+
.lp-cell-wide { display: flex; align-items: center; }
|
|
440
|
+
.lp-cell-stacked { display: flex; flex-direction: column; align-items: center; justify-content: center; }
|
|
441
|
+
|
|
442
|
+
.lp-text { flex: 1 1 auto; min-width: 0; overflow: hidden; align-self: stretch;
|
|
443
|
+
display: flex; flex-direction: column; justify-content: center; }
|
|
444
|
+
.lp-fit-wrap { overflow: hidden; }
|
|
445
|
+
.lp-caption { font-weight: 700; line-height: 1.15; word-break: break-word; }
|
|
446
|
+
.lp-line { line-height: 1.25; word-break: break-word; }
|
|
447
|
+
.lp-caption-stacked { text-align: center; width: 100%; }
|
|
448
|
+
|
|
449
|
+
/* Badge \u2014 the LARGE token above the caption (garment size, bin letter). */
|
|
450
|
+
.lp-badge { font-weight: 800; line-height: 1.05; word-break: break-word;
|
|
451
|
+
-webkit-print-color-adjust: exact; print-color-adjust: exact; }
|
|
452
|
+
.lp-badge-stacked { text-align: center; width: 100%; }
|
|
453
|
+
|
|
454
|
+
/* Durable http(s) artwork in the lines zone. */
|
|
455
|
+
.lp-img { display: block; width: 100%; object-fit: contain; }
|
|
456
|
+
|
|
457
|
+
/* Calibration */
|
|
458
|
+
.lp-cal-cell { border: 0.5pt dashed #94a3b8; background: transparent; }
|
|
459
|
+
.lp-cal-label { position: absolute; inset: 0; display: flex; align-items: center;
|
|
460
|
+
justify-content: center; font-size: 7pt; color: #94a3b8; text-align: center; padding: 4px; }
|
|
461
|
+
.lp-crop { position: absolute; background: #94a3b8;
|
|
462
|
+
-webkit-print-color-adjust: exact; print-color-adjust: exact; }
|
|
463
|
+
|
|
464
|
+
@media print {
|
|
465
|
+
html, body { height: auto; overflow: visible; }
|
|
466
|
+
.lp-page { border-bottom: none; }
|
|
467
|
+
.screen-only { display: none !important; }
|
|
468
|
+
}
|
|
469
|
+
`;
|
|
470
|
+
}
|
|
471
|
+
function cellPosition(t, index) {
|
|
472
|
+
const col = index % t.cols;
|
|
473
|
+
const row = Math.floor(index / t.cols);
|
|
474
|
+
return {
|
|
475
|
+
x: t.marginLeftIn + col * (t.labelWIn + t.gutterXIn),
|
|
476
|
+
y: t.marginTopIn + row * (t.labelHIn + t.gutterYIn)
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
var LP_FIT_SCRIPT = `
|
|
480
|
+
<script>
|
|
481
|
+
(function () {
|
|
482
|
+
function fit(el, box) {
|
|
483
|
+
var size = parseFloat(window.getComputedStyle(el).fontSize);
|
|
484
|
+
var min = 4;
|
|
485
|
+
while (size > min && (el.scrollWidth > box.clientWidth + 1 || el.scrollHeight > box.clientHeight + 1)) {
|
|
486
|
+
size -= 0.5;
|
|
487
|
+
el.style.fontSize = size + "px";
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
function run() {
|
|
491
|
+
var els = document.querySelectorAll(".lp-fit-wrap .lp-fit");
|
|
492
|
+
for (var i = 0; i < els.length; i++) fit(els[i], els[i].parentElement);
|
|
493
|
+
}
|
|
494
|
+
if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", run);
|
|
495
|
+
else setTimeout(run, 0);
|
|
496
|
+
})();
|
|
497
|
+
<\/script>`;
|
|
498
|
+
function isHttpUrl(value) {
|
|
499
|
+
return typeof value === "string" && /^https?:\/\//i.test(value.trim());
|
|
500
|
+
}
|
|
501
|
+
function buildLabelCellHtml(label, qrDataUri, t, layout, s) {
|
|
502
|
+
const qr = qrDataUri ? `<img class="lp-qr" src="${qrDataUri}" alt="" style="width:${layout.qrIn}in;height:${layout.qrIn}in;" />` : "";
|
|
503
|
+
const captionText = s.showCaption ? label.caption ?? label.qrValue : "";
|
|
504
|
+
const badgeText = label.badge?.trim() ? label.badge.trim() : "";
|
|
505
|
+
if (layout.wide) {
|
|
506
|
+
const lines = s.showLines && label.lines?.length ? label.lines.filter((l) => l.trim()).map(
|
|
507
|
+
(l) => `<div class="lp-line" style="font-size:8pt;">${escapeHtml(l)}</div>`
|
|
508
|
+
).join("") : "";
|
|
509
|
+
const caption2 = captionText ? `<div class="lp-fit-wrap" style="max-height:${(t.labelHIn * 0.3).toFixed(3)}in;"><div class="lp-fit lp-caption" style="font-size:12pt;">${escapeHtml(captionText)}</div></div>` : "";
|
|
510
|
+
const badge2 = badgeText ? `<div class="lp-fit-wrap" style="max-height:${layout.badgeZoneIn.toFixed(3)}in;"><div class="lp-fit lp-badge" style="font-size:24pt;">${escapeHtml(badgeText)}</div></div>` : "";
|
|
511
|
+
const image = isHttpUrl(label.imageUrl) ? `<img class="lp-img" src="${escapeHtml(label.imageUrl)}" alt="" style="height:${(t.labelHIn * 0.35).toFixed(3)}in;" />` : "";
|
|
512
|
+
return `<div class="lp-cell-wide" style="height:100%;padding:${layout.padIn}in;gap:${layout.padIn}in;">
|
|
513
|
+
${qr}
|
|
514
|
+
<div class="lp-text">${badge2}${caption2}${lines}${image}</div>
|
|
515
|
+
</div>`;
|
|
516
|
+
}
|
|
517
|
+
const caption = captionText ? `<div class="lp-fit-wrap lp-caption-stacked" style="height:${Math.min(0.28, t.labelHIn * 0.16).toFixed(3)}in;"><div class="lp-fit lp-caption" style="font-size:9pt;">${escapeHtml(captionText)}</div></div>` : "";
|
|
518
|
+
const badge = badgeText ? `<div class="lp-fit-wrap lp-badge-stacked" style="height:${layout.badgeZoneIn.toFixed(3)}in;"><div class="lp-fit lp-badge" style="font-size:18pt;">${escapeHtml(badgeText)}</div></div>` : "";
|
|
519
|
+
return `<div class="lp-cell-stacked" style="height:100%;padding:${layout.padIn}in;">
|
|
520
|
+
${qr}
|
|
521
|
+
${badge}
|
|
522
|
+
${caption}
|
|
523
|
+
</div>`;
|
|
524
|
+
}
|
|
525
|
+
function renderPages(t, cells) {
|
|
526
|
+
const perPage = t.cols * t.rows;
|
|
527
|
+
const pages = [];
|
|
528
|
+
for (let p = 0; p < Math.max(1, Math.ceil(cells.length / perPage)); p++) {
|
|
529
|
+
const batch = cells.slice(p * perPage, (p + 1) * perPage);
|
|
530
|
+
const cellHtml = batch.map((c, i) => {
|
|
531
|
+
const { x, y } = cellPosition(t, i);
|
|
532
|
+
return `<div class="lp-cell" style="left:${x}in;top:${y}in;">${c.html}</div>`;
|
|
533
|
+
}).join("\n");
|
|
534
|
+
pages.push(`<div class="lp-page">${cellHtml}</div>`);
|
|
535
|
+
}
|
|
536
|
+
return pages.join("\n");
|
|
537
|
+
}
|
|
538
|
+
function openSheetWindow(t, bodyHtml, title, withFitScript, warning) {
|
|
539
|
+
const warningHtml = warning ? `<div class="screen-only lp-warning" style="font-family:sans-serif;font-size:12px;font-weight:600;padding:9px 16px;background:#fef2f2;color:#991b1b;border-bottom:1px solid #fecaca;">${escapeHtml(warning)}</div>` : "";
|
|
540
|
+
const html = `<!DOCTYPE html>
|
|
541
|
+
<html lang="en">
|
|
542
|
+
<head>
|
|
543
|
+
<meta charset="UTF-8">
|
|
544
|
+
<title>${escapeHtml(title)} \u2014 ${escapeHtml(t.stockCode)}</title>
|
|
545
|
+
<style>${sheetStyles(t)}</style>
|
|
546
|
+
</head>
|
|
547
|
+
<body>
|
|
548
|
+
${warningHtml}
|
|
549
|
+
<div class="screen-only" style="font-family:sans-serif;font-size:12px;padding:10px 16px;background:#f8fafc;border-bottom:1px solid #e2e8f0;display:flex;gap:10px;align-items:center;flex-wrap:wrap;">
|
|
550
|
+
<button onclick="window.print()" style="padding:7px 18px;background:#111;color:#fff;border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;">Print / Save PDF</button>
|
|
551
|
+
<button onclick="window.close()" style="padding:7px 14px;background:#f1f5f9;border:1px solid #cbd5e1;border-radius:6px;font-size:13px;cursor:pointer;">Close</button>
|
|
552
|
+
<span style="color:#64748b;font-size:11px;">${escapeHtml(t.stockCode)} \xB7 ${escapeHtml(t.name)} \xB7 set printer to <strong>100% scale (no "fit to page"), no margins, Portrait</strong></span>
|
|
553
|
+
</div>
|
|
554
|
+
${bodyHtml}
|
|
555
|
+
${withFitScript ? LP_FIT_SCRIPT : ""}
|
|
556
|
+
</body>
|
|
557
|
+
</html>`;
|
|
558
|
+
openPrintWindow(html, title);
|
|
559
|
+
}
|
|
560
|
+
function expandLabels(labels, s) {
|
|
561
|
+
const from = s.rangeFrom > 0 ? s.rangeFrom : 1;
|
|
562
|
+
const to = s.rangeTo > 0 ? Math.min(s.rangeTo, labels.length) : labels.length;
|
|
563
|
+
const subset = labels.slice(from - 1, to);
|
|
564
|
+
if (s.copiesPerCode <= 1) return subset;
|
|
565
|
+
const out = [];
|
|
566
|
+
for (const l of subset)
|
|
567
|
+
for (let i = 0; i < s.copiesPerCode; i++) out.push(l);
|
|
568
|
+
return out;
|
|
569
|
+
}
|
|
570
|
+
function buildCalibrationBody(t) {
|
|
571
|
+
const perPage = t.cols * t.rows;
|
|
572
|
+
const cells = [];
|
|
573
|
+
for (let i = 0; i < perPage; i++) {
|
|
574
|
+
const { x, y } = cellPosition(t, i);
|
|
575
|
+
cells.push(
|
|
576
|
+
`<div class="lp-cell lp-cal-cell" style="left:${x}in;top:${y}in;"><div class="lp-cal-label">${i + 1}</div></div>`
|
|
577
|
+
);
|
|
578
|
+
}
|
|
579
|
+
const gridR = t.sheetWIn - t.marginLeftIn;
|
|
580
|
+
const gridB = t.sheetHIn - t.marginTopIn;
|
|
581
|
+
const mark = (x, y, h) => `<div class="lp-crop" style="left:${x}in;top:${y}in;width:${h ? 0.25 : 0.0104}in;height:${h ? 0.0104 : 0.25}in;"></div>`;
|
|
582
|
+
const marks = [
|
|
583
|
+
mark(t.marginLeftIn - 0.25, t.marginTopIn, true),
|
|
584
|
+
mark(t.marginLeftIn, t.marginTopIn - 0.25, false),
|
|
585
|
+
mark(gridR, t.marginTopIn, true),
|
|
586
|
+
mark(gridR - 0.0104, t.marginTopIn - 0.25, false),
|
|
587
|
+
mark(t.marginLeftIn - 0.25, gridB - 0.0104, true),
|
|
588
|
+
mark(t.marginLeftIn, gridB, false),
|
|
589
|
+
mark(gridR, gridB - 0.0104, true),
|
|
590
|
+
mark(gridR - 0.0104, gridB, false)
|
|
591
|
+
].join("");
|
|
592
|
+
const banner = `<div style="position:absolute;left:0;right:0;top:${Math.max(0, t.marginTopIn - 0.3).toFixed(2)}in;text-align:center;font-size:7pt;color:#94a3b8;">Calibration \u2014 ${escapeHtml(t.stockCode)} \xB7 hold against your label sheet: every outline must sit on a label</div>`;
|
|
593
|
+
return `<div class="lp-page">${banner}${marks}${cells.join("\n")}</div>`;
|
|
594
|
+
}
|
|
595
|
+
function printCalibrationSheet(template) {
|
|
596
|
+
openSheetWindow(
|
|
597
|
+
template,
|
|
598
|
+
buildCalibrationBody(template),
|
|
599
|
+
"Label calibration",
|
|
600
|
+
false
|
|
601
|
+
);
|
|
602
|
+
}
|
|
603
|
+
var MM_PER_IN = 25.4;
|
|
604
|
+
var MIN_SCANNABLE_MODULE_MM = 0.4;
|
|
605
|
+
function assertScannable(template, payloadByteLen, ecLevel = DEFAULTS.ecLevel) {
|
|
606
|
+
const layout = computeCellLayout(template, true);
|
|
607
|
+
const printedSideMm = layout.qrIn * MM_PER_IN;
|
|
608
|
+
let version;
|
|
609
|
+
try {
|
|
610
|
+
version = minQrVersion(payloadByteLen, ecLevel);
|
|
611
|
+
} catch (err) {
|
|
612
|
+
return {
|
|
613
|
+
ok: false,
|
|
614
|
+
version: 0,
|
|
615
|
+
moduleSizeMm: 0,
|
|
616
|
+
minModuleSizeMm: MIN_SCANNABLE_MODULE_MM,
|
|
617
|
+
message: err instanceof Error ? err.message : String(err)
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
const modulesPerSide = qrModuleCount(version) + QUIET_ZONE_MODULES * 2;
|
|
621
|
+
const moduleSizeMm = printedSideMm / modulesPerSide;
|
|
622
|
+
if (moduleSizeMm >= MIN_SCANNABLE_MODULE_MM) {
|
|
623
|
+
return {
|
|
624
|
+
ok: true,
|
|
625
|
+
version,
|
|
626
|
+
moduleSizeMm,
|
|
627
|
+
minModuleSizeMm: MIN_SCANNABLE_MODULE_MM
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
return {
|
|
631
|
+
ok: false,
|
|
632
|
+
version,
|
|
633
|
+
moduleSizeMm,
|
|
634
|
+
minModuleSizeMm: MIN_SCANNABLE_MODULE_MM,
|
|
635
|
+
message: `A ${payloadByteLen}-byte payload needs a version ${version} QR, which prints at ${moduleSizeMm.toFixed(3)}mm per module on ${template.stockCode} \u2014 below the ${MIN_SCANNABLE_MODULE_MM}mm floor a scanner can resolve. Shorten the payload (a short resolver URL is the intended fix) or move to larger label stock.`
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
function batchScannabilityWarning(t, labels, ecLevel) {
|
|
639
|
+
const encoder = new TextEncoder();
|
|
640
|
+
let worst = -1;
|
|
641
|
+
for (const l of labels) {
|
|
642
|
+
if (!l.qrValue) continue;
|
|
643
|
+
const len = encoder.encode(l.qrValue).length;
|
|
644
|
+
if (len > worst) worst = len;
|
|
645
|
+
}
|
|
646
|
+
if (worst < 0) return void 0;
|
|
647
|
+
const report = assertScannable(t, worst, ecLevel);
|
|
648
|
+
return report.ok ? void 0 : report.message;
|
|
649
|
+
}
|
|
650
|
+
function resolveTemplate(data, variantId) {
|
|
651
|
+
if (variantId && variantId !== "custom") {
|
|
652
|
+
const t = getLabelTemplate(variantId);
|
|
653
|
+
if (t) return t;
|
|
654
|
+
}
|
|
655
|
+
if (data.templateId === "custom" && data.customTemplate)
|
|
656
|
+
return data.customTemplate;
|
|
657
|
+
return getLabelTemplate(data.templateId) ?? data.customTemplate ?? AVERY_5160;
|
|
658
|
+
}
|
|
659
|
+
async function printQrLabelSheet(data, variantId, rawSettings) {
|
|
660
|
+
const t = resolveTemplate(data, variantId);
|
|
661
|
+
const s = resolveSettings(rawSettings);
|
|
662
|
+
if (s.calibrationPage) {
|
|
663
|
+
printCalibrationSheet(t);
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
const labels = expandLabels(data.labels ?? [], s);
|
|
667
|
+
if (!labels.length) {
|
|
668
|
+
openSheetWindow(
|
|
669
|
+
t,
|
|
670
|
+
`<div class="lp-page"><div style="position:absolute;inset:0;display:flex;align-items:center;justify-content:center;color:#94a3b8;font-size:11pt;">No labels to print.</div></div>`,
|
|
671
|
+
"QR labels",
|
|
672
|
+
false
|
|
673
|
+
);
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
const hasBadge = labels.some((l) => (l.badge ?? "").trim() !== "");
|
|
677
|
+
const layout = computeCellLayout(t, s.showCaption, hasBadge);
|
|
678
|
+
const uriByValue = /* @__PURE__ */ new Map();
|
|
679
|
+
for (const l of labels) {
|
|
680
|
+
if (l.qrValue && !uriByValue.has(l.qrValue)) {
|
|
681
|
+
uriByValue.set(
|
|
682
|
+
l.qrValue,
|
|
683
|
+
await generateQrDataUri(l.qrValue, s.ecLevel, layout.qrIn)
|
|
684
|
+
);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
const leadingBlanks = Math.max(0, s.startAtLabel - 1) % (t.cols * t.rows);
|
|
688
|
+
const cells = [
|
|
689
|
+
...Array.from({ length: leadingBlanks }, () => ({ html: "" })),
|
|
690
|
+
...labels.map((l) => ({
|
|
691
|
+
html: buildLabelCellHtml(
|
|
692
|
+
l,
|
|
693
|
+
uriByValue.get(l.qrValue) ?? "",
|
|
694
|
+
t,
|
|
695
|
+
layout,
|
|
696
|
+
s
|
|
697
|
+
)
|
|
698
|
+
}))
|
|
699
|
+
];
|
|
700
|
+
const perPage = t.cols * t.rows;
|
|
701
|
+
const tail = cells.length % perPage;
|
|
702
|
+
if (tail > 0)
|
|
703
|
+
for (let i = 0; i < perPage - tail; i++) cells.push({ html: "" });
|
|
704
|
+
openSheetWindow(
|
|
705
|
+
t,
|
|
706
|
+
renderPages(t, cells),
|
|
707
|
+
"QR labels",
|
|
708
|
+
true,
|
|
709
|
+
batchScannabilityWarning(t, labels, s.ecLevel)
|
|
710
|
+
);
|
|
711
|
+
}
|
|
712
|
+
var qrLabelsPrinter = {
|
|
713
|
+
label: "Print QR label sheet",
|
|
714
|
+
variants: LABEL_TEMPLATES.map((t) => ({
|
|
715
|
+
id: t.id,
|
|
716
|
+
label: t.name,
|
|
717
|
+
description: t.stockCode
|
|
718
|
+
})),
|
|
719
|
+
settings: [
|
|
720
|
+
{
|
|
721
|
+
type: "number",
|
|
722
|
+
id: "startAtLabel",
|
|
723
|
+
label: "Start at label #",
|
|
724
|
+
description: "Skip already-used positions on a partial sheet",
|
|
725
|
+
defaultValue: 1,
|
|
726
|
+
min: 1
|
|
727
|
+
},
|
|
728
|
+
{
|
|
729
|
+
type: "number",
|
|
730
|
+
id: "copiesPerCode",
|
|
731
|
+
label: "Copies per code",
|
|
732
|
+
defaultValue: 1,
|
|
733
|
+
min: 1,
|
|
734
|
+
max: 999
|
|
735
|
+
},
|
|
736
|
+
{
|
|
737
|
+
type: "boolean",
|
|
738
|
+
id: "showCaption",
|
|
739
|
+
label: "Show caption",
|
|
740
|
+
description: "Print the caption (or the code value) beside each QR",
|
|
741
|
+
defaultValue: true
|
|
742
|
+
},
|
|
743
|
+
{
|
|
744
|
+
type: "boolean",
|
|
745
|
+
id: "showLines",
|
|
746
|
+
label: "Show detail lines",
|
|
747
|
+
description: "Extra text lines on wide labels",
|
|
748
|
+
defaultValue: true
|
|
749
|
+
},
|
|
750
|
+
{
|
|
751
|
+
type: "range",
|
|
752
|
+
id: "labelRange",
|
|
753
|
+
label: "Label range",
|
|
754
|
+
description: "Print only labels N to M (blank = all)",
|
|
755
|
+
fromId: "rangeFrom",
|
|
756
|
+
toId: "rangeTo",
|
|
757
|
+
defaultFrom: 0,
|
|
758
|
+
defaultTo: 0
|
|
759
|
+
},
|
|
760
|
+
{
|
|
761
|
+
type: "select",
|
|
762
|
+
id: "ecLevel",
|
|
763
|
+
label: "Error correction",
|
|
764
|
+
description: "Higher survives more damage; M is the warehouse default",
|
|
765
|
+
options: [
|
|
766
|
+
{ value: "L", label: "L" },
|
|
767
|
+
{ value: "M", label: "M" },
|
|
768
|
+
{ value: "Q", label: "Q" }
|
|
769
|
+
],
|
|
770
|
+
defaultValue: "M"
|
|
771
|
+
},
|
|
772
|
+
{
|
|
773
|
+
type: "boolean",
|
|
774
|
+
id: "calibrationPage",
|
|
775
|
+
label: "Calibration test page",
|
|
776
|
+
description: "Print outlines + crop marks only to verify alignment",
|
|
777
|
+
defaultValue: false
|
|
778
|
+
}
|
|
779
|
+
],
|
|
780
|
+
print: (data, variantId, settings) => printQrLabelSheet(data, variantId, settings)
|
|
781
|
+
};
|
|
782
|
+
|
|
783
|
+
// src/react.tsx
|
|
784
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
785
|
+
function cn(...values) {
|
|
786
|
+
return twMerge(values.filter(Boolean).join(" "));
|
|
787
|
+
}
|
|
788
|
+
function LucideIcon({
|
|
789
|
+
lucideClass,
|
|
790
|
+
className,
|
|
791
|
+
children,
|
|
792
|
+
...props
|
|
793
|
+
}) {
|
|
794
|
+
return /* @__PURE__ */ jsx(
|
|
795
|
+
"svg",
|
|
796
|
+
{
|
|
797
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
798
|
+
width: 24,
|
|
799
|
+
height: 24,
|
|
800
|
+
viewBox: "0 0 24 24",
|
|
801
|
+
fill: "none",
|
|
802
|
+
stroke: "currentColor",
|
|
803
|
+
strokeWidth: 2,
|
|
804
|
+
strokeLinecap: "round",
|
|
805
|
+
strokeLinejoin: "round",
|
|
806
|
+
className: cn(lucideClass, className),
|
|
807
|
+
"aria-hidden": "true",
|
|
808
|
+
...props,
|
|
809
|
+
children
|
|
810
|
+
}
|
|
811
|
+
);
|
|
812
|
+
}
|
|
813
|
+
function Printer(props) {
|
|
814
|
+
return /* @__PURE__ */ jsxs(LucideIcon, { lucideClass: "lucide lucide-printer", ...props, children: [
|
|
815
|
+
/* @__PURE__ */ jsx("path", { d: "M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2" }),
|
|
816
|
+
/* @__PURE__ */ jsx("path", { d: "M6 9V3a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v6" }),
|
|
817
|
+
/* @__PURE__ */ jsx("rect", { x: "6", y: "14", width: "12", height: "8", rx: "1" })
|
|
818
|
+
] });
|
|
819
|
+
}
|
|
820
|
+
function X(props) {
|
|
821
|
+
return /* @__PURE__ */ jsxs(LucideIcon, { lucideClass: "lucide lucide-x", ...props, children: [
|
|
822
|
+
/* @__PURE__ */ jsx("path", { d: "M18 6 6 18" }),
|
|
823
|
+
/* @__PURE__ */ jsx("path", { d: "m6 6 12 12" })
|
|
824
|
+
] });
|
|
825
|
+
}
|
|
826
|
+
function Settings2(props) {
|
|
827
|
+
return /* @__PURE__ */ jsxs(
|
|
828
|
+
LucideIcon,
|
|
829
|
+
{
|
|
830
|
+
lucideClass: "lucide lucide-settings2 lucide-settings-2",
|
|
831
|
+
...props,
|
|
832
|
+
children: [
|
|
833
|
+
/* @__PURE__ */ jsx("path", { d: "M14 17H5" }),
|
|
834
|
+
/* @__PURE__ */ jsx("path", { d: "M19 7h-9" }),
|
|
835
|
+
/* @__PURE__ */ jsx("circle", { cx: "17", cy: "17", r: "3" }),
|
|
836
|
+
/* @__PURE__ */ jsx("circle", { cx: "7", cy: "7", r: "3" })
|
|
837
|
+
]
|
|
838
|
+
}
|
|
839
|
+
);
|
|
840
|
+
}
|
|
841
|
+
var MOBILE_BREAKPOINT = 768;
|
|
842
|
+
function useIsMobile() {
|
|
843
|
+
const [isMobile, setIsMobile] = useState(false);
|
|
844
|
+
const [hasMounted, setHasMounted] = useState(false);
|
|
845
|
+
useEffect(() => {
|
|
846
|
+
setHasMounted(true);
|
|
847
|
+
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
|
|
848
|
+
const onChange = () => {
|
|
849
|
+
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
|
|
850
|
+
};
|
|
851
|
+
onChange();
|
|
852
|
+
mql.addEventListener("change", onChange);
|
|
853
|
+
return () => mql.removeEventListener("change", onChange);
|
|
854
|
+
}, []);
|
|
855
|
+
if (!hasMounted) {
|
|
856
|
+
return false;
|
|
857
|
+
}
|
|
858
|
+
return isMobile;
|
|
859
|
+
}
|
|
860
|
+
var DialogModalContext = React.createContext(true);
|
|
861
|
+
function Dialog({
|
|
862
|
+
children,
|
|
863
|
+
...props
|
|
864
|
+
}) {
|
|
865
|
+
const modal = props.modal ?? true;
|
|
866
|
+
return /* @__PURE__ */ jsx(DialogModalContext.Provider, { value: modal, children: /* @__PURE__ */ jsx(DialogPrimitive.Root, { ...props, modal, children }) });
|
|
867
|
+
}
|
|
868
|
+
function DialogOverlay({
|
|
869
|
+
className,
|
|
870
|
+
...props
|
|
871
|
+
}) {
|
|
872
|
+
return /* @__PURE__ */ jsx(
|
|
873
|
+
DialogPrimitive.Overlay,
|
|
874
|
+
{
|
|
875
|
+
"data-slot": "dialog-overlay",
|
|
876
|
+
className: cn(
|
|
877
|
+
// Preserve readable page context behind the modal; separation comes from a light scrim, not blur.
|
|
878
|
+
"fixed inset-0 z-[10000] bg-black/20 dark:bg-black/30 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
|
879
|
+
className
|
|
880
|
+
),
|
|
881
|
+
...props
|
|
882
|
+
}
|
|
883
|
+
);
|
|
884
|
+
}
|
|
885
|
+
var DIALOG_DESKTOP_CLASSES = "fixed left-[50%] top-[50%] z-[10000] grid min-w-0 w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 overflow-x-clip border bg-background p-6 shadow-lg [overflow-wrap:anywhere] [&>*]:min-w-0 [&>*]:max-w-full duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg";
|
|
886
|
+
var DIALOG_MOBILE_SHEET_CLASSES = "fixed inset-x-0 bottom-0 left-0 right-0 top-auto z-[10000] flex min-w-0 flex-col w-full max-w-full max-h-[90dvh] translate-x-0 translate-y-0 gap-4 overflow-x-clip border-t bg-background p-4 pb-safe shadow-lg [overflow-wrap:anywhere] [&>*]:min-w-0 [&>*]:max-w-full duration-200 rounded-t-2xl rounded-b-none overflow-y-auto overscroll-contain data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom";
|
|
887
|
+
var DIALOG_MOBILE_SHEET_OVERRIDE = "inset-x-0 bottom-0 left-0 right-0 top-auto translate-x-0 translate-y-0 w-full max-w-full max-h-[90dvh] rounded-b-none rounded-t-2xl overflow-y-auto";
|
|
888
|
+
function DialogContent({
|
|
889
|
+
className,
|
|
890
|
+
children,
|
|
891
|
+
mobileSheet = true,
|
|
892
|
+
...props
|
|
893
|
+
}) {
|
|
894
|
+
const isMobile = useIsMobile();
|
|
895
|
+
const isModal = React.useContext(DialogModalContext);
|
|
896
|
+
const asSheet = mobileSheet && isMobile;
|
|
897
|
+
return /* @__PURE__ */ jsxs(DialogPrimitive.Portal, { children: [
|
|
898
|
+
isModal ? /* @__PURE__ */ jsx(DialogOverlay, {}) : null,
|
|
899
|
+
/* @__PURE__ */ jsxs(
|
|
900
|
+
DialogPrimitive.Content,
|
|
901
|
+
{
|
|
902
|
+
"aria-modal": isModal || void 0,
|
|
903
|
+
className: cn(
|
|
904
|
+
asSheet ? DIALOG_MOBILE_SHEET_CLASSES : DIALOG_DESKTOP_CLASSES,
|
|
905
|
+
className,
|
|
906
|
+
asSheet && DIALOG_MOBILE_SHEET_OVERRIDE,
|
|
907
|
+
// A non-modal Dialog stays below the WindowPanel manager's z=1000 floor.
|
|
908
|
+
!isModal && "z-[900]"
|
|
909
|
+
),
|
|
910
|
+
...props,
|
|
911
|
+
children: [
|
|
912
|
+
children,
|
|
913
|
+
/* @__PURE__ */ jsxs(
|
|
914
|
+
DialogPrimitive.Close,
|
|
915
|
+
{
|
|
916
|
+
"data-slot": "dialog-close",
|
|
917
|
+
className: "absolute right-2 top-4 flex h-10 w-10 items-center justify-center rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground sm:right-4",
|
|
918
|
+
children: [
|
|
919
|
+
/* @__PURE__ */ jsx(X, { className: "h-4 w-4" }),
|
|
920
|
+
/* @__PURE__ */ jsx("span", { className: "sr-only", children: "Close" })
|
|
921
|
+
]
|
|
922
|
+
}
|
|
923
|
+
)
|
|
924
|
+
]
|
|
925
|
+
}
|
|
926
|
+
)
|
|
927
|
+
] });
|
|
928
|
+
}
|
|
929
|
+
function DialogHeader({
|
|
930
|
+
className,
|
|
931
|
+
...props
|
|
932
|
+
}) {
|
|
933
|
+
return /* @__PURE__ */ jsx(
|
|
934
|
+
"div",
|
|
935
|
+
{
|
|
936
|
+
"data-slot": "dialog-header",
|
|
937
|
+
className: cn(
|
|
938
|
+
// DialogContent owns an absolute close control. Reserve its hit area in
|
|
939
|
+
// every header so trailing actions never render underneath it.
|
|
940
|
+
"flex flex-col space-y-1.5 pr-12 text-center sm:text-left",
|
|
941
|
+
className
|
|
942
|
+
),
|
|
943
|
+
...props
|
|
944
|
+
}
|
|
945
|
+
);
|
|
946
|
+
}
|
|
947
|
+
function DialogTitle({
|
|
948
|
+
className,
|
|
949
|
+
...props
|
|
950
|
+
}) {
|
|
951
|
+
return /* @__PURE__ */ jsx(
|
|
952
|
+
DialogPrimitive.Title,
|
|
953
|
+
{
|
|
954
|
+
className: cn(
|
|
955
|
+
"text-lg font-semibold leading-none tracking-tight",
|
|
956
|
+
className
|
|
957
|
+
),
|
|
958
|
+
...props
|
|
959
|
+
}
|
|
960
|
+
);
|
|
961
|
+
}
|
|
962
|
+
function DialogDescription({
|
|
963
|
+
className,
|
|
964
|
+
...props
|
|
965
|
+
}) {
|
|
966
|
+
return /* @__PURE__ */ jsx(
|
|
967
|
+
DialogPrimitive.Description,
|
|
968
|
+
{
|
|
969
|
+
className: cn("text-sm text-muted-foreground", className),
|
|
970
|
+
...props
|
|
971
|
+
}
|
|
972
|
+
);
|
|
973
|
+
}
|
|
974
|
+
function Drawer({
|
|
975
|
+
children,
|
|
976
|
+
shouldScaleBackground = true,
|
|
977
|
+
...props
|
|
978
|
+
}) {
|
|
979
|
+
return /* @__PURE__ */ jsx(
|
|
980
|
+
DrawerPrimitive.Root,
|
|
981
|
+
{
|
|
982
|
+
modal: true,
|
|
983
|
+
shouldScaleBackground,
|
|
984
|
+
...props,
|
|
985
|
+
children
|
|
986
|
+
}
|
|
987
|
+
);
|
|
988
|
+
}
|
|
989
|
+
function DrawerOverlay({
|
|
990
|
+
className,
|
|
991
|
+
...props
|
|
992
|
+
}) {
|
|
993
|
+
return /* @__PURE__ */ jsx(
|
|
994
|
+
DrawerPrimitive.Overlay,
|
|
995
|
+
{
|
|
996
|
+
className: cn("fixed inset-0 z-50 bg-black/80", className),
|
|
997
|
+
...props
|
|
998
|
+
}
|
|
999
|
+
);
|
|
1000
|
+
}
|
|
1001
|
+
function DrawerContent({
|
|
1002
|
+
className,
|
|
1003
|
+
children,
|
|
1004
|
+
...props
|
|
1005
|
+
}) {
|
|
1006
|
+
return /* @__PURE__ */ jsxs(DrawerPrimitive.Portal, { children: [
|
|
1007
|
+
/* @__PURE__ */ jsx(DrawerOverlay, {}),
|
|
1008
|
+
/* @__PURE__ */ jsxs(
|
|
1009
|
+
DrawerPrimitive.Content,
|
|
1010
|
+
{
|
|
1011
|
+
"aria-modal": "true",
|
|
1012
|
+
className: cn(
|
|
1013
|
+
"fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-background pb-safe",
|
|
1014
|
+
className
|
|
1015
|
+
),
|
|
1016
|
+
"aria-describedby": void 0,
|
|
1017
|
+
...props,
|
|
1018
|
+
children: [
|
|
1019
|
+
/* @__PURE__ */ jsx("div", { className: "mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted" }),
|
|
1020
|
+
children
|
|
1021
|
+
]
|
|
1022
|
+
}
|
|
1023
|
+
)
|
|
1024
|
+
] });
|
|
1025
|
+
}
|
|
1026
|
+
function DrawerHeader({
|
|
1027
|
+
className,
|
|
1028
|
+
...props
|
|
1029
|
+
}) {
|
|
1030
|
+
return /* @__PURE__ */ jsx(
|
|
1031
|
+
"div",
|
|
1032
|
+
{
|
|
1033
|
+
className: cn("grid gap-1.5 p-4 text-center sm:text-left", className),
|
|
1034
|
+
...props
|
|
1035
|
+
}
|
|
1036
|
+
);
|
|
1037
|
+
}
|
|
1038
|
+
function DrawerTitle({
|
|
1039
|
+
className,
|
|
1040
|
+
...props
|
|
1041
|
+
}) {
|
|
1042
|
+
return /* @__PURE__ */ jsx(
|
|
1043
|
+
DrawerPrimitive.Title,
|
|
1044
|
+
{
|
|
1045
|
+
className: cn(
|
|
1046
|
+
"text-lg font-semibold leading-none tracking-tight",
|
|
1047
|
+
className
|
|
1048
|
+
),
|
|
1049
|
+
...props
|
|
1050
|
+
}
|
|
1051
|
+
);
|
|
1052
|
+
}
|
|
1053
|
+
var BUTTON_BASE = "inline-flex cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-0 disabled:pointer-events-none disabled:opacity-50 active:scale-[0.98] [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0";
|
|
1054
|
+
var BUTTON_VARIANTS = {
|
|
1055
|
+
default: "bg-primary text-primary-foreground shadow-sm hover:bg-primary/90",
|
|
1056
|
+
ghost: "hover:bg-accent hover:text-accent-foreground"
|
|
1057
|
+
};
|
|
1058
|
+
var BUTTON_SIZES = {
|
|
1059
|
+
default: "h-9 px-4 py-2",
|
|
1060
|
+
sm: "h-8 rounded-md px-3 text-xs",
|
|
1061
|
+
icon: "h-9 w-9"
|
|
1062
|
+
};
|
|
1063
|
+
function PrintDialogButton({
|
|
1064
|
+
className,
|
|
1065
|
+
variant = "default",
|
|
1066
|
+
size = "default",
|
|
1067
|
+
...props
|
|
1068
|
+
}) {
|
|
1069
|
+
return /* @__PURE__ */ jsx(
|
|
1070
|
+
"button",
|
|
1071
|
+
{
|
|
1072
|
+
className: cn(
|
|
1073
|
+
BUTTON_BASE,
|
|
1074
|
+
BUTTON_VARIANTS[variant],
|
|
1075
|
+
BUTTON_SIZES[size],
|
|
1076
|
+
className
|
|
1077
|
+
),
|
|
1078
|
+
...props
|
|
1079
|
+
}
|
|
1080
|
+
);
|
|
1081
|
+
}
|
|
1082
|
+
function buildDefaultSettings(settings) {
|
|
1083
|
+
const out = {};
|
|
1084
|
+
for (const s of settings) {
|
|
1085
|
+
if (s.type === "boolean") {
|
|
1086
|
+
out[s.id] = s.defaultValue;
|
|
1087
|
+
} else if (s.type === "range") {
|
|
1088
|
+
out[s.fromId] = s.defaultFrom;
|
|
1089
|
+
out[s.toId] = s.defaultTo;
|
|
1090
|
+
} else if (s.type === "number" || s.type === "select") {
|
|
1091
|
+
out[s.id] = s.defaultValue;
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
return out;
|
|
1095
|
+
}
|
|
1096
|
+
function visibleSettings(settings, variantId) {
|
|
1097
|
+
return settings.filter(
|
|
1098
|
+
(s) => !s.appliesTo || s.appliesTo.includes(variantId)
|
|
1099
|
+
);
|
|
1100
|
+
}
|
|
1101
|
+
function PrintOptionsContent({
|
|
1102
|
+
printer,
|
|
1103
|
+
data,
|
|
1104
|
+
onClose
|
|
1105
|
+
}) {
|
|
1106
|
+
const [selectedVariant, setSelectedVariant] = useState(
|
|
1107
|
+
printer.variants[0]?.id ?? ""
|
|
1108
|
+
);
|
|
1109
|
+
const [isPrinting, setIsPrinting] = useState(false);
|
|
1110
|
+
const allSettings = printer.settings ?? [];
|
|
1111
|
+
const [settingValues, setSettingValues] = useState(
|
|
1112
|
+
() => buildDefaultSettings(allSettings)
|
|
1113
|
+
);
|
|
1114
|
+
const currentSettings = useMemo(
|
|
1115
|
+
() => visibleSettings(allSettings, selectedVariant),
|
|
1116
|
+
[allSettings, selectedVariant]
|
|
1117
|
+
);
|
|
1118
|
+
const toggleSetting = (id) => {
|
|
1119
|
+
setSettingValues((prev) => ({ ...prev, [id]: !prev[id] }));
|
|
1120
|
+
};
|
|
1121
|
+
const setNumberSetting = (id, value) => {
|
|
1122
|
+
setSettingValues((prev) => ({ ...prev, [id]: value }));
|
|
1123
|
+
};
|
|
1124
|
+
const handlePrint = async (variantId, useDefaults = false) => {
|
|
1125
|
+
setIsPrinting(true);
|
|
1126
|
+
try {
|
|
1127
|
+
const settings = useDefaults ? buildDefaultSettings(allSettings) : settingValues;
|
|
1128
|
+
await printer.print(data, variantId, settings);
|
|
1129
|
+
} finally {
|
|
1130
|
+
setIsPrinting(false);
|
|
1131
|
+
onClose();
|
|
1132
|
+
}
|
|
1133
|
+
};
|
|
1134
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col min-h-0 flex-1", children: [
|
|
1135
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-y-auto min-h-0 py-2 space-y-4", children: [
|
|
1136
|
+
printer.variants.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
|
|
1137
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-foreground mb-2 px-0.5", children: "Print format" }),
|
|
1138
|
+
/* @__PURE__ */ jsx("div", { className: "space-y-1.5", children: printer.variants.map((variant) => /* @__PURE__ */ jsxs(
|
|
1139
|
+
"button",
|
|
1140
|
+
{
|
|
1141
|
+
onClick: () => setSelectedVariant(variant.id),
|
|
1142
|
+
className: `w-full flex flex-col items-start px-3 py-2.5 rounded-lg border text-left transition-colors ${selectedVariant === variant.id ? "border-primary bg-primary/5 text-primary" : "border-border bg-background hover:bg-accent"}`,
|
|
1143
|
+
children: [
|
|
1144
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: variant.label }),
|
|
1145
|
+
variant.description && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground mt-0.5", children: variant.description })
|
|
1146
|
+
]
|
|
1147
|
+
},
|
|
1148
|
+
variant.id
|
|
1149
|
+
)) })
|
|
1150
|
+
] }),
|
|
1151
|
+
currentSettings.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
|
|
1152
|
+
/* @__PURE__ */ jsxs("p", { className: "text-sm font-medium text-foreground mb-2 px-0.5 flex items-center gap-1.5", children: [
|
|
1153
|
+
/* @__PURE__ */ jsx(Settings2, { className: "w-3.5 h-3.5" }),
|
|
1154
|
+
"Options"
|
|
1155
|
+
] }),
|
|
1156
|
+
/* @__PURE__ */ jsx("div", { className: "space-y-1", children: currentSettings.map((setting) => {
|
|
1157
|
+
if (setting.type === "boolean") {
|
|
1158
|
+
const checked = settingValues[setting.id] === true;
|
|
1159
|
+
return /* @__PURE__ */ jsxs(
|
|
1160
|
+
"button",
|
|
1161
|
+
{
|
|
1162
|
+
onClick: () => toggleSetting(setting.id),
|
|
1163
|
+
className: "w-full flex items-start gap-3 px-3 py-2.5 rounded-lg border border-border bg-background hover:bg-accent text-left transition-colors",
|
|
1164
|
+
children: [
|
|
1165
|
+
/* @__PURE__ */ jsx(
|
|
1166
|
+
"span",
|
|
1167
|
+
{
|
|
1168
|
+
className: `mt-0.5 shrink-0 w-4 h-4 rounded border-2 flex items-center justify-center transition-colors ${checked ? "bg-primary border-primary" : "border-muted-foreground/40"}`,
|
|
1169
|
+
children: checked && /* @__PURE__ */ jsx(
|
|
1170
|
+
"svg",
|
|
1171
|
+
{
|
|
1172
|
+
className: "w-2.5 h-2.5 text-primary-foreground",
|
|
1173
|
+
viewBox: "0 0 10 10",
|
|
1174
|
+
fill: "none",
|
|
1175
|
+
children: /* @__PURE__ */ jsx(
|
|
1176
|
+
"path",
|
|
1177
|
+
{
|
|
1178
|
+
d: "M1.5 5l2.5 2.5 4.5-4.5",
|
|
1179
|
+
stroke: "currentColor",
|
|
1180
|
+
strokeWidth: "1.8",
|
|
1181
|
+
strokeLinecap: "round",
|
|
1182
|
+
strokeLinejoin: "round"
|
|
1183
|
+
}
|
|
1184
|
+
)
|
|
1185
|
+
}
|
|
1186
|
+
)
|
|
1187
|
+
}
|
|
1188
|
+
),
|
|
1189
|
+
/* @__PURE__ */ jsxs("span", { className: "flex flex-col min-w-0", children: [
|
|
1190
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-foreground", children: setting.label }),
|
|
1191
|
+
setting.description && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground mt-0.5", children: setting.description })
|
|
1192
|
+
] })
|
|
1193
|
+
]
|
|
1194
|
+
},
|
|
1195
|
+
setting.id
|
|
1196
|
+
);
|
|
1197
|
+
}
|
|
1198
|
+
if (setting.type === "range") {
|
|
1199
|
+
const min = setting.min ?? 0;
|
|
1200
|
+
const fromVal = settingValues[setting.fromId] ?? 0;
|
|
1201
|
+
const toVal = settingValues[setting.toId] ?? 0;
|
|
1202
|
+
const parse = (raw) => {
|
|
1203
|
+
if (raw === "") return 0;
|
|
1204
|
+
const n = parseInt(raw, 10);
|
|
1205
|
+
return Number.isNaN(n) ? 0 : Math.max(min, n);
|
|
1206
|
+
};
|
|
1207
|
+
return /* @__PURE__ */ jsxs(
|
|
1208
|
+
"div",
|
|
1209
|
+
{
|
|
1210
|
+
className: "w-full flex items-start gap-3 px-3 py-2.5 rounded-lg border border-border bg-background",
|
|
1211
|
+
children: [
|
|
1212
|
+
/* @__PURE__ */ jsxs("span", { className: "flex flex-col min-w-0 flex-1", children: [
|
|
1213
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-foreground", children: setting.label }),
|
|
1214
|
+
setting.description && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground mt-0.5", children: setting.description })
|
|
1215
|
+
] }),
|
|
1216
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 shrink-0 pt-0.5", children: [
|
|
1217
|
+
/* @__PURE__ */ jsx(
|
|
1218
|
+
"input",
|
|
1219
|
+
{
|
|
1220
|
+
type: "number",
|
|
1221
|
+
inputMode: "numeric",
|
|
1222
|
+
min,
|
|
1223
|
+
value: fromVal === 0 ? "" : fromVal,
|
|
1224
|
+
onChange: (e) => setNumberSetting(
|
|
1225
|
+
setting.fromId,
|
|
1226
|
+
parse(e.target.value)
|
|
1227
|
+
),
|
|
1228
|
+
placeholder: setting.fromPlaceholder ?? "start",
|
|
1229
|
+
"aria-label": `${setting.label} \u2014 from`,
|
|
1230
|
+
className: "w-14 h-7 text-xs text-center rounded border border-input bg-background text-foreground focus:outline-none focus:ring-1 focus:ring-ring"
|
|
1231
|
+
}
|
|
1232
|
+
),
|
|
1233
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "to" }),
|
|
1234
|
+
/* @__PURE__ */ jsx(
|
|
1235
|
+
"input",
|
|
1236
|
+
{
|
|
1237
|
+
type: "number",
|
|
1238
|
+
inputMode: "numeric",
|
|
1239
|
+
min,
|
|
1240
|
+
value: toVal === 0 ? "" : toVal,
|
|
1241
|
+
onChange: (e) => setNumberSetting(
|
|
1242
|
+
setting.toId,
|
|
1243
|
+
parse(e.target.value)
|
|
1244
|
+
),
|
|
1245
|
+
placeholder: setting.toPlaceholder ?? "end",
|
|
1246
|
+
"aria-label": `${setting.label} \u2014 to`,
|
|
1247
|
+
className: "w-14 h-7 text-xs text-center rounded border border-input bg-background text-foreground focus:outline-none focus:ring-1 focus:ring-ring"
|
|
1248
|
+
}
|
|
1249
|
+
)
|
|
1250
|
+
] })
|
|
1251
|
+
]
|
|
1252
|
+
},
|
|
1253
|
+
setting.id
|
|
1254
|
+
);
|
|
1255
|
+
}
|
|
1256
|
+
if (setting.type === "number") {
|
|
1257
|
+
const min = setting.min ?? 0;
|
|
1258
|
+
const val = settingValues[setting.id] ?? setting.defaultValue;
|
|
1259
|
+
const parse = (raw) => {
|
|
1260
|
+
if (raw === "") return setting.defaultValue;
|
|
1261
|
+
const n = parseInt(raw, 10);
|
|
1262
|
+
if (Number.isNaN(n)) return setting.defaultValue;
|
|
1263
|
+
const bounded = Math.max(min, n);
|
|
1264
|
+
return setting.max !== void 0 ? Math.min(setting.max, bounded) : bounded;
|
|
1265
|
+
};
|
|
1266
|
+
return /* @__PURE__ */ jsxs(
|
|
1267
|
+
"div",
|
|
1268
|
+
{
|
|
1269
|
+
className: "w-full flex items-start gap-3 px-3 py-2.5 rounded-lg border border-border bg-background",
|
|
1270
|
+
children: [
|
|
1271
|
+
/* @__PURE__ */ jsxs("span", { className: "flex flex-col min-w-0 flex-1", children: [
|
|
1272
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-foreground", children: setting.label }),
|
|
1273
|
+
setting.description && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground mt-0.5", children: setting.description })
|
|
1274
|
+
] }),
|
|
1275
|
+
/* @__PURE__ */ jsx(
|
|
1276
|
+
"input",
|
|
1277
|
+
{
|
|
1278
|
+
type: "number",
|
|
1279
|
+
inputMode: "numeric",
|
|
1280
|
+
min,
|
|
1281
|
+
max: setting.max,
|
|
1282
|
+
value: val,
|
|
1283
|
+
onChange: (e) => setNumberSetting(setting.id, parse(e.target.value)),
|
|
1284
|
+
"aria-label": setting.label,
|
|
1285
|
+
className: "w-16 h-7 text-xs text-center rounded border border-input bg-background text-foreground focus:outline-none focus:ring-1 focus:ring-ring shrink-0 mt-0.5"
|
|
1286
|
+
}
|
|
1287
|
+
)
|
|
1288
|
+
]
|
|
1289
|
+
},
|
|
1290
|
+
setting.id
|
|
1291
|
+
);
|
|
1292
|
+
}
|
|
1293
|
+
if (setting.type === "select") {
|
|
1294
|
+
const val = settingValues[setting.id] ?? setting.defaultValue;
|
|
1295
|
+
return /* @__PURE__ */ jsxs(
|
|
1296
|
+
"div",
|
|
1297
|
+
{
|
|
1298
|
+
className: "w-full flex items-start gap-3 px-3 py-2.5 rounded-lg border border-border bg-background",
|
|
1299
|
+
children: [
|
|
1300
|
+
/* @__PURE__ */ jsxs("span", { className: "flex flex-col min-w-0 flex-1", children: [
|
|
1301
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-foreground", children: setting.label }),
|
|
1302
|
+
setting.description && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground mt-0.5", children: setting.description })
|
|
1303
|
+
] }),
|
|
1304
|
+
/* @__PURE__ */ jsx("div", { className: "flex items-center gap-0.5 shrink-0 rounded-md border border-input p-0.5 mt-0.5", children: setting.options.map((opt) => /* @__PURE__ */ jsx(
|
|
1305
|
+
"button",
|
|
1306
|
+
{
|
|
1307
|
+
onClick: () => setSettingValues((prev) => ({
|
|
1308
|
+
...prev,
|
|
1309
|
+
[setting.id]: opt.value
|
|
1310
|
+
})),
|
|
1311
|
+
className: `px-2 h-6 text-xs rounded transition-colors ${val === opt.value ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:bg-accent"}`,
|
|
1312
|
+
children: opt.label
|
|
1313
|
+
},
|
|
1314
|
+
opt.value
|
|
1315
|
+
)) })
|
|
1316
|
+
]
|
|
1317
|
+
},
|
|
1318
|
+
setting.id
|
|
1319
|
+
);
|
|
1320
|
+
}
|
|
1321
|
+
return null;
|
|
1322
|
+
}) })
|
|
1323
|
+
] })
|
|
1324
|
+
] }),
|
|
1325
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 pt-3 pb-1 border-t border-border mt-2 shrink-0", children: [
|
|
1326
|
+
/* @__PURE__ */ jsxs(
|
|
1327
|
+
PrintDialogButton,
|
|
1328
|
+
{
|
|
1329
|
+
onClick: () => handlePrint(selectedVariant || void 0),
|
|
1330
|
+
disabled: isPrinting,
|
|
1331
|
+
className: "w-full",
|
|
1332
|
+
children: [
|
|
1333
|
+
/* @__PURE__ */ jsx(Printer, { className: "w-4 h-4 mr-2" }),
|
|
1334
|
+
isPrinting ? "Opening print view..." : "Print / Save PDF"
|
|
1335
|
+
]
|
|
1336
|
+
}
|
|
1337
|
+
),
|
|
1338
|
+
printer.variants.length > 0 && /* @__PURE__ */ jsx(
|
|
1339
|
+
PrintDialogButton,
|
|
1340
|
+
{
|
|
1341
|
+
variant: "ghost",
|
|
1342
|
+
size: "sm",
|
|
1343
|
+
onClick: () => handlePrint(void 0, true),
|
|
1344
|
+
disabled: isPrinting,
|
|
1345
|
+
className: "w-full text-muted-foreground text-xs",
|
|
1346
|
+
children: "Print with defaults"
|
|
1347
|
+
}
|
|
1348
|
+
)
|
|
1349
|
+
] })
|
|
1350
|
+
] });
|
|
1351
|
+
}
|
|
1352
|
+
function PrintOptionsDialog({
|
|
1353
|
+
printer,
|
|
1354
|
+
data,
|
|
1355
|
+
open,
|
|
1356
|
+
onOpenChange
|
|
1357
|
+
}) {
|
|
1358
|
+
const isMobile = useIsMobile();
|
|
1359
|
+
const handleClose = () => onOpenChange(false);
|
|
1360
|
+
if (isMobile) {
|
|
1361
|
+
return /* @__PURE__ */ jsx(Drawer, { open, onOpenChange, children: /* @__PURE__ */ jsxs(DrawerContent, { className: "flex flex-col max-h-[85dvh]", children: [
|
|
1362
|
+
/* @__PURE__ */ jsxs(DrawerHeader, { className: "flex items-center justify-between pb-2 shrink-0", children: [
|
|
1363
|
+
/* @__PURE__ */ jsxs(DrawerTitle, { className: "flex items-center gap-2", children: [
|
|
1364
|
+
/* @__PURE__ */ jsx(Printer, { className: "w-4 h-4" }),
|
|
1365
|
+
printer.label
|
|
1366
|
+
] }),
|
|
1367
|
+
/* @__PURE__ */ jsx(
|
|
1368
|
+
PrintDialogButton,
|
|
1369
|
+
{
|
|
1370
|
+
variant: "ghost",
|
|
1371
|
+
size: "icon",
|
|
1372
|
+
onClick: handleClose,
|
|
1373
|
+
className: "h-7 w-7",
|
|
1374
|
+
children: /* @__PURE__ */ jsx(X, { className: "w-4 h-4" })
|
|
1375
|
+
}
|
|
1376
|
+
)
|
|
1377
|
+
] }),
|
|
1378
|
+
/* @__PURE__ */ jsx("div", { className: "flex flex-col min-h-0 flex-1 overflow-hidden px-4 pb-4", children: /* @__PURE__ */ jsx(
|
|
1379
|
+
PrintOptionsContent,
|
|
1380
|
+
{
|
|
1381
|
+
printer,
|
|
1382
|
+
data,
|
|
1383
|
+
onClose: handleClose
|
|
1384
|
+
}
|
|
1385
|
+
) })
|
|
1386
|
+
] }) });
|
|
1387
|
+
}
|
|
1388
|
+
return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs(DialogContent, { className: "sm:max-w-sm flex flex-col max-h-[85dvh]", children: [
|
|
1389
|
+
/* @__PURE__ */ jsxs(DialogHeader, { className: "shrink-0", children: [
|
|
1390
|
+
/* @__PURE__ */ jsxs(DialogTitle, { className: "flex items-center gap-2", children: [
|
|
1391
|
+
/* @__PURE__ */ jsx(Printer, { className: "w-4 h-4" }),
|
|
1392
|
+
printer.label
|
|
1393
|
+
] }),
|
|
1394
|
+
/* @__PURE__ */ jsx(DialogDescription, { className: "sr-only", children: "Choose a print format and options for this content." })
|
|
1395
|
+
] }),
|
|
1396
|
+
/* @__PURE__ */ jsx("div", { className: "flex flex-col min-h-0 flex-1 overflow-hidden", children: /* @__PURE__ */ jsx(
|
|
1397
|
+
PrintOptionsContent,
|
|
1398
|
+
{
|
|
1399
|
+
printer,
|
|
1400
|
+
data,
|
|
1401
|
+
onClose: handleClose
|
|
1402
|
+
}
|
|
1403
|
+
) })
|
|
1404
|
+
] }) });
|
|
1405
|
+
}
|
|
1406
|
+
function usePrintOptions(printer, data) {
|
|
1407
|
+
const [open, setOpen] = useState(false);
|
|
1408
|
+
const triggerPrint = async () => {
|
|
1409
|
+
if (!printer) return;
|
|
1410
|
+
const hasOptions = printer.variants.length > 0 || (printer.settings?.length ?? 0) > 0;
|
|
1411
|
+
if (!hasOptions) {
|
|
1412
|
+
await printer.print(data);
|
|
1413
|
+
} else {
|
|
1414
|
+
setOpen(true);
|
|
1415
|
+
}
|
|
1416
|
+
};
|
|
1417
|
+
return { open, setOpen, triggerPrint };
|
|
1418
|
+
}
|
|
1419
|
+
function LabelSheetPreview({
|
|
1420
|
+
template: t,
|
|
1421
|
+
labels,
|
|
1422
|
+
startAtLabel = 1,
|
|
1423
|
+
showCaption = true,
|
|
1424
|
+
ecLevel = "M",
|
|
1425
|
+
calibration = false,
|
|
1426
|
+
pageIndex = 0,
|
|
1427
|
+
className
|
|
1428
|
+
}) {
|
|
1429
|
+
const [uris, setUris] = useState(/* @__PURE__ */ new Map());
|
|
1430
|
+
const perPage = t.cols * t.rows;
|
|
1431
|
+
const leadingBlanks = Math.max(0, startAtLabel - 1) % perPage;
|
|
1432
|
+
const pageStart = pageIndex * perPage - leadingBlanks;
|
|
1433
|
+
const layout = computeCellLayout(t, showCaption);
|
|
1434
|
+
const cells = Array.from({ length: perPage }, (_, i) => {
|
|
1435
|
+
const li = pageStart + i;
|
|
1436
|
+
return li >= 0 && li < labels.length ? labels[li] ?? null : null;
|
|
1437
|
+
});
|
|
1438
|
+
const valuesKey = Array.from(
|
|
1439
|
+
new Set(
|
|
1440
|
+
cells.filter((c) => c !== null).map((c) => c.qrValue)
|
|
1441
|
+
)
|
|
1442
|
+
).join("\0");
|
|
1443
|
+
const qrIn = layout.qrIn;
|
|
1444
|
+
useEffect(() => {
|
|
1445
|
+
if (calibration) return;
|
|
1446
|
+
let cancelled = false;
|
|
1447
|
+
const values = valuesKey ? valuesKey.split("\0") : [];
|
|
1448
|
+
void (async () => {
|
|
1449
|
+
const next = /* @__PURE__ */ new Map();
|
|
1450
|
+
for (const v of values) {
|
|
1451
|
+
try {
|
|
1452
|
+
next.set(v, await generateQrDataUri(v, ecLevel, qrIn));
|
|
1453
|
+
} catch (err) {
|
|
1454
|
+
console.error("[LabelSheetPreview] QR generation failed:", err);
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
if (!cancelled) setUris(next);
|
|
1458
|
+
})();
|
|
1459
|
+
return () => {
|
|
1460
|
+
cancelled = true;
|
|
1461
|
+
};
|
|
1462
|
+
}, [calibration, ecLevel, qrIn, valuesKey]);
|
|
1463
|
+
const pct = (v, base) => `${v / base * 100}%`;
|
|
1464
|
+
const radiusPct = t.round ? "50%" : t.cornerRadiusIn ? `${t.cornerRadiusIn / t.labelWIn * 100}% / ${t.cornerRadiusIn / t.labelHIn * 100}%` : "0";
|
|
1465
|
+
return /* @__PURE__ */ jsx(
|
|
1466
|
+
"div",
|
|
1467
|
+
{
|
|
1468
|
+
className: `relative w-full bg-white border border-border rounded-md shadow-sm overflow-hidden ${className ?? ""}`,
|
|
1469
|
+
style: { aspectRatio: `${t.sheetWIn} / ${t.sheetHIn}` },
|
|
1470
|
+
"aria-label": `Label sheet preview \u2014 ${t.stockCode}`,
|
|
1471
|
+
children: cells.map((label, i) => {
|
|
1472
|
+
const col = i % t.cols;
|
|
1473
|
+
const row = Math.floor(i / t.cols);
|
|
1474
|
+
const x = t.marginLeftIn + col * (t.labelWIn + t.gutterXIn);
|
|
1475
|
+
const y = t.marginTopIn + row * (t.labelHIn + t.gutterYIn);
|
|
1476
|
+
const uri = label ? uris.get(label.qrValue) : void 0;
|
|
1477
|
+
const wide = layout.wide;
|
|
1478
|
+
return /* @__PURE__ */ jsx(
|
|
1479
|
+
"div",
|
|
1480
|
+
{
|
|
1481
|
+
className: calibration || !label ? "absolute border border-dashed border-slate-300" : "absolute border border-slate-200",
|
|
1482
|
+
style: {
|
|
1483
|
+
left: pct(x, t.sheetWIn),
|
|
1484
|
+
top: pct(y, t.sheetHIn),
|
|
1485
|
+
width: pct(t.labelWIn, t.sheetWIn),
|
|
1486
|
+
height: pct(t.labelHIn, t.sheetHIn),
|
|
1487
|
+
borderRadius: radiusPct,
|
|
1488
|
+
overflow: "hidden"
|
|
1489
|
+
},
|
|
1490
|
+
children: calibration ? /* @__PURE__ */ jsx("div", { className: "w-full h-full flex items-center justify-center text-[8px] text-slate-400", children: i + 1 }) : label ? /* @__PURE__ */ jsxs(
|
|
1491
|
+
"div",
|
|
1492
|
+
{
|
|
1493
|
+
className: `w-full h-full flex ${wide ? "flex-row items-center" : "flex-col items-center justify-center"} gap-[2%] p-[3%]`,
|
|
1494
|
+
children: [
|
|
1495
|
+
uri ? /* @__PURE__ */ jsx(
|
|
1496
|
+
"img",
|
|
1497
|
+
{
|
|
1498
|
+
src: uri,
|
|
1499
|
+
alt: "",
|
|
1500
|
+
className: "block",
|
|
1501
|
+
style: wide ? { height: "88%", aspectRatio: "1" } : {
|
|
1502
|
+
width: `${layout.qrIn / t.labelWIn * 100}%`,
|
|
1503
|
+
aspectRatio: "1"
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
) : /* @__PURE__ */ jsx(
|
|
1507
|
+
"div",
|
|
1508
|
+
{
|
|
1509
|
+
className: "bg-slate-100 animate-pulse rounded-sm",
|
|
1510
|
+
style: wide ? { height: "88%", aspectRatio: "1" } : { width: "70%", aspectRatio: "1" }
|
|
1511
|
+
}
|
|
1512
|
+
),
|
|
1513
|
+
/* @__PURE__ */ jsxs(
|
|
1514
|
+
"div",
|
|
1515
|
+
{
|
|
1516
|
+
className: `min-w-0 overflow-hidden text-black leading-tight ${wide ? "flex-1 text-left" : "text-center w-full"}`,
|
|
1517
|
+
style: { fontSize: "min(1.4vw, 10px)" },
|
|
1518
|
+
children: [
|
|
1519
|
+
showCaption && /* @__PURE__ */ jsx("div", { className: "font-bold truncate", children: label.caption ?? label.qrValue }),
|
|
1520
|
+
wide && label.lines?.filter((l) => l.trim()).map((l, li) => /* @__PURE__ */ jsx("div", { className: "truncate text-slate-600", children: l }, li))
|
|
1521
|
+
]
|
|
1522
|
+
}
|
|
1523
|
+
)
|
|
1524
|
+
]
|
|
1525
|
+
}
|
|
1526
|
+
) : null
|
|
1527
|
+
},
|
|
1528
|
+
i
|
|
1529
|
+
);
|
|
1530
|
+
})
|
|
1531
|
+
}
|
|
1532
|
+
);
|
|
1533
|
+
}
|
|
1534
|
+
export {
|
|
1535
|
+
LabelSheetPreview,
|
|
1536
|
+
PrintOptionsDialog,
|
|
1537
|
+
usePrintOptions
|
|
1538
|
+
};
|
|
1539
|
+
//# sourceMappingURL=react.js.map
|