@printwithsynergy/artwork-pdf-editor 0.1.6 → 0.3.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/LICENSE +62 -0
- package/README.md +63 -2
- package/dist/components/AccessibilityHintsPanel.d.ts +142 -0
- package/dist/components/AccessibilityHintsPanel.d.ts.map +1 -0
- package/dist/components/AccessibilityHintsPanel.js +158 -0
- package/dist/components/AccessibilityHintsPanel.js.map +1 -0
- package/dist/components/AnnotationOverlay.d.ts +142 -0
- package/dist/components/AnnotationOverlay.d.ts.map +1 -0
- package/dist/components/AnnotationOverlay.js +141 -0
- package/dist/components/AnnotationOverlay.js.map +1 -0
- package/dist/components/AnnotationsSidebar.d.ts +98 -0
- package/dist/components/AnnotationsSidebar.d.ts.map +1 -0
- package/dist/components/AnnotationsSidebar.js +100 -0
- package/dist/components/AnnotationsSidebar.js.map +1 -0
- package/dist/components/BarcodeGeneratorPanel.d.ts +58 -0
- package/dist/components/BarcodeGeneratorPanel.d.ts.map +1 -0
- package/dist/components/BarcodeGeneratorPanel.js +91 -0
- package/dist/components/BarcodeGeneratorPanel.js.map +1 -0
- package/dist/components/BraillePanel.d.ts +99 -0
- package/dist/components/BraillePanel.d.ts.map +1 -0
- package/dist/components/BraillePanel.js +221 -0
- package/dist/components/BraillePanel.js.map +1 -0
- package/dist/components/BrandAssetsPanel.d.ts +130 -0
- package/dist/components/BrandAssetsPanel.d.ts.map +1 -0
- package/dist/components/BrandAssetsPanel.js +125 -0
- package/dist/components/BrandAssetsPanel.js.map +1 -0
- package/dist/components/BrandConsistencyPanel.d.ts +140 -0
- package/dist/components/BrandConsistencyPanel.d.ts.map +1 -0
- package/dist/components/BrandConsistencyPanel.js +158 -0
- package/dist/components/BrandConsistencyPanel.js.map +1 -0
- package/dist/components/ComplianceFindingsPanel.d.ts +62 -0
- package/dist/components/ComplianceFindingsPanel.d.ts.map +1 -0
- package/dist/components/ComplianceFindingsPanel.js +118 -0
- package/dist/components/ComplianceFindingsPanel.js.map +1 -0
- package/dist/components/DesignSuggestionsPanel.d.ts +148 -0
- package/dist/components/DesignSuggestionsPanel.d.ts.map +1 -0
- package/dist/components/DesignSuggestionsPanel.js +154 -0
- package/dist/components/DesignSuggestionsPanel.js.map +1 -0
- package/dist/components/DielineParametersPanel.d.ts +62 -0
- package/dist/components/DielineParametersPanel.d.ts.map +1 -0
- package/dist/components/DielineParametersPanel.js +170 -0
- package/dist/components/DielineParametersPanel.js.map +1 -0
- package/dist/components/DielinePreview.d.ts +150 -0
- package/dist/components/DielinePreview.d.ts.map +1 -0
- package/dist/components/DielinePreview.js +146 -0
- package/dist/components/DielinePreview.js.map +1 -0
- package/dist/components/EditorApp.d.ts +39 -5
- package/dist/components/EditorApp.d.ts.map +1 -1
- package/dist/components/EditorApp.js +110 -5
- package/dist/components/EditorApp.js.map +1 -1
- package/dist/components/EditorCanvas.d.ts +51 -2
- package/dist/components/EditorCanvas.d.ts.map +1 -1
- package/dist/components/EditorCanvas.js +117 -48
- package/dist/components/EditorCanvas.js.map +1 -1
- package/dist/components/EmailNotifyPanel.d.ts +165 -0
- package/dist/components/EmailNotifyPanel.d.ts.map +1 -0
- package/dist/components/EmailNotifyPanel.js +211 -0
- package/dist/components/EmailNotifyPanel.js.map +1 -0
- package/dist/components/FileDropZone.d.ts +9 -1
- package/dist/components/FileDropZone.d.ts.map +1 -1
- package/dist/components/FileDropZone.js +53 -5
- package/dist/components/FileDropZone.js.map +1 -1
- package/dist/components/FoldEditorPanel.d.ts +68 -0
- package/dist/components/FoldEditorPanel.d.ts.map +1 -0
- package/dist/components/FoldEditorPanel.js +65 -0
- package/dist/components/FoldEditorPanel.js.map +1 -0
- package/dist/components/FoldPreviewOverlay.d.ts +48 -0
- package/dist/components/FoldPreviewOverlay.d.ts.map +1 -0
- package/dist/components/FoldPreviewOverlay.js +182 -0
- package/dist/components/FoldPreviewOverlay.js.map +1 -0
- package/dist/components/Gs1DigitalLinkPanel.d.ts +103 -0
- package/dist/components/Gs1DigitalLinkPanel.d.ts.map +1 -0
- package/dist/components/Gs1DigitalLinkPanel.js +199 -0
- package/dist/components/Gs1DigitalLinkPanel.js.map +1 -0
- package/dist/components/HistoryPanel.d.ts +39 -0
- package/dist/components/HistoryPanel.d.ts.map +1 -0
- package/dist/components/HistoryPanel.js +72 -0
- package/dist/components/HistoryPanel.js.map +1 -0
- package/dist/components/IccSoftProofOverlay.d.ts +67 -0
- package/dist/components/IccSoftProofOverlay.d.ts.map +1 -0
- package/dist/components/IccSoftProofOverlay.js +119 -0
- package/dist/components/IccSoftProofOverlay.js.map +1 -0
- package/dist/components/ImposePanel.d.ts +71 -0
- package/dist/components/ImposePanel.d.ts.map +1 -0
- package/dist/components/ImposePanel.js +127 -0
- package/dist/components/ImposePanel.js.map +1 -0
- package/dist/components/InksPanel.d.ts +61 -0
- package/dist/components/InksPanel.d.ts.map +1 -0
- package/dist/components/InksPanel.js +84 -0
- package/dist/components/InksPanel.js.map +1 -0
- package/dist/components/JobSetupPanel.d.ts +118 -0
- package/dist/components/JobSetupPanel.d.ts.map +1 -0
- package/dist/components/JobSetupPanel.js +169 -0
- package/dist/components/JobSetupPanel.js.map +1 -0
- package/dist/components/LayersPanel.d.ts.map +1 -1
- package/dist/components/LayersPanel.js +1 -0
- package/dist/components/LayersPanel.js.map +1 -1
- package/dist/components/MarkLibraryPanel.d.ts +131 -0
- package/dist/components/MarkLibraryPanel.d.ts.map +1 -0
- package/dist/components/MarkLibraryPanel.js +184 -0
- package/dist/components/MarkLibraryPanel.js.map +1 -0
- package/dist/components/MisEstimateButton.d.ts +73 -0
- package/dist/components/MisEstimateButton.d.ts.map +1 -0
- package/dist/components/MisEstimateButton.js +57 -0
- package/dist/components/MisEstimateButton.js.map +1 -0
- package/dist/components/NutritionPanel.d.ts +118 -0
- package/dist/components/NutritionPanel.d.ts.map +1 -0
- package/dist/components/NutritionPanel.js +169 -0
- package/dist/components/NutritionPanel.js.map +1 -0
- package/dist/components/PageNavigator.d.ts +26 -0
- package/dist/components/PageNavigator.d.ts.map +1 -0
- package/dist/components/PageNavigator.js +96 -0
- package/dist/components/PageNavigator.js.map +1 -0
- package/dist/components/PaletteManager.d.ts +32 -0
- package/dist/components/PaletteManager.d.ts.map +1 -0
- package/dist/components/PaletteManager.js +89 -0
- package/dist/components/PaletteManager.js.map +1 -0
- package/dist/components/PaletteToSpotPanel.d.ts +122 -0
- package/dist/components/PaletteToSpotPanel.d.ts.map +1 -0
- package/dist/components/PaletteToSpotPanel.js +160 -0
- package/dist/components/PaletteToSpotPanel.js.map +1 -0
- package/dist/components/PreflightAutoFixPanel.d.ts +110 -0
- package/dist/components/PreflightAutoFixPanel.d.ts.map +1 -0
- package/dist/components/PreflightAutoFixPanel.js +119 -0
- package/dist/components/PreflightAutoFixPanel.js.map +1 -0
- package/dist/components/PreflightDiffPanel.d.ts +127 -0
- package/dist/components/PreflightDiffPanel.d.ts.map +1 -0
- package/dist/components/PreflightDiffPanel.js +0 -0
- package/dist/components/PreflightDiffPanel.js.map +1 -0
- package/dist/components/ProcessRulesPanel.d.ts +81 -0
- package/dist/components/ProcessRulesPanel.d.ts.map +1 -0
- package/dist/components/ProcessRulesPanel.js +143 -0
- package/dist/components/ProcessRulesPanel.js.map +1 -0
- package/dist/components/SlackNotifyPanel.d.ts +139 -0
- package/dist/components/SlackNotifyPanel.d.ts.map +1 -0
- package/dist/components/SlackNotifyPanel.js +133 -0
- package/dist/components/SlackNotifyPanel.js.map +1 -0
- package/dist/components/SmartSpotMatchPanel.d.ts +143 -0
- package/dist/components/SmartSpotMatchPanel.d.ts.map +1 -0
- package/dist/components/SmartSpotMatchPanel.js +159 -0
- package/dist/components/SmartSpotMatchPanel.js.map +1 -0
- package/dist/components/SwatchesPicker.d.ts +83 -0
- package/dist/components/SwatchesPicker.d.ts.map +1 -0
- package/dist/components/SwatchesPicker.js +151 -0
- package/dist/components/SwatchesPicker.js.map +1 -0
- package/dist/components/TacOverlay.d.ts +47 -0
- package/dist/components/TacOverlay.d.ts.map +1 -0
- package/dist/components/TacOverlay.js +116 -0
- package/dist/components/TacOverlay.js.map +1 -0
- package/dist/components/TrapEditorPanel.d.ts +52 -0
- package/dist/components/TrapEditorPanel.d.ts.map +1 -0
- package/dist/components/TrapEditorPanel.js +64 -0
- package/dist/components/TrapEditorPanel.js.map +1 -0
- package/dist/components/TrapPreviewOverlay.d.ts +64 -0
- package/dist/components/TrapPreviewOverlay.d.ts.map +1 -0
- package/dist/components/TrapPreviewOverlay.js +120 -0
- package/dist/components/TrapPreviewOverlay.js.map +1 -0
- package/dist/components/VariantMatrixPanel.d.ts +61 -0
- package/dist/components/VariantMatrixPanel.d.ts.map +1 -0
- package/dist/components/VariantMatrixPanel.js +97 -0
- package/dist/components/VariantMatrixPanel.js.map +1 -0
- package/dist/components/VariantMatrixVersionPanel.d.ts +122 -0
- package/dist/components/VariantMatrixVersionPanel.d.ts.map +1 -0
- package/dist/components/VariantMatrixVersionPanel.js +162 -0
- package/dist/components/VariantMatrixVersionPanel.js.map +1 -0
- package/dist/components/WebhookNotifyPanel.d.ts +160 -0
- package/dist/components/WebhookNotifyPanel.d.ts.map +1 -0
- package/dist/components/WebhookNotifyPanel.js +100 -0
- package/dist/components/WebhookNotifyPanel.js.map +1 -0
- package/dist/components/WhiteUnderbasePanel.d.ts +107 -0
- package/dist/components/WhiteUnderbasePanel.d.ts.map +1 -0
- package/dist/components/WhiteUnderbasePanel.js +104 -0
- package/dist/components/WhiteUnderbasePanel.js.map +1 -0
- package/dist/data/dielines.json +35 -0
- package/dist/hooks/useEditorMode.d.ts +25 -5
- package/dist/hooks/useEditorMode.d.ts.map +1 -1
- package/dist/hooks/useEditorMode.js +18 -5
- package/dist/hooks/useEditorMode.js.map +1 -1
- package/dist/index.d.ts +49 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +49 -2
- package/dist/index.js.map +1 -1
- package/dist/lens/dieline-overlay.d.ts +25 -0
- package/dist/lens/dieline-overlay.d.ts.map +1 -0
- package/dist/lens/dieline-overlay.js +50 -0
- package/dist/lens/dieline-overlay.js.map +1 -0
- package/dist/lens/index.d.ts +27 -0
- package/dist/lens/index.d.ts.map +1 -0
- package/dist/lens/index.js +28 -0
- package/dist/lens/index.js.map +1 -0
- package/dist/lens/preflight-findings.d.ts +21 -0
- package/dist/lens/preflight-findings.d.ts.map +1 -0
- package/dist/lens/preflight-findings.js +82 -0
- package/dist/lens/preflight-findings.js.map +1 -0
- package/dist/lib/barcode-scan.d.ts +154 -0
- package/dist/lib/barcode-scan.d.ts.map +1 -0
- package/dist/lib/barcode-scan.js +152 -0
- package/dist/lib/barcode-scan.js.map +1 -0
- package/dist/lib/color-math.d.ts +76 -0
- package/dist/lib/color-math.d.ts.map +1 -0
- package/dist/lib/color-math.js +96 -0
- package/dist/lib/color-math.js.map +1 -0
- package/dist/lib/dieline-template.d.ts +169 -0
- package/dist/lib/dieline-template.d.ts.map +1 -1
- package/dist/lib/dieline-template.js +229 -1
- package/dist/lib/dieline-template.js.map +1 -1
- package/dist/lib/editor-config.d.ts +384 -1
- package/dist/lib/editor-config.d.ts.map +1 -1
- package/dist/lib/editor-config.js +89 -2
- package/dist/lib/editor-config.js.map +1 -1
- package/dist/lib/fold-geometry.d.ts +144 -0
- package/dist/lib/fold-geometry.d.ts.map +1 -0
- package/dist/lib/fold-geometry.js +138 -0
- package/dist/lib/fold-geometry.js.map +1 -0
- package/dist/lib/merge-tokens.d.ts +81 -0
- package/dist/lib/merge-tokens.d.ts.map +1 -0
- package/dist/lib/merge-tokens.js +88 -0
- package/dist/lib/merge-tokens.js.map +1 -0
- package/dist/lib/palette-registry.d.ts +40 -0
- package/dist/lib/palette-registry.d.ts.map +1 -0
- package/dist/lib/palette-registry.js +49 -0
- package/dist/lib/palette-registry.js.map +1 -0
- package/dist/lib/panel-anchor.d.ts +101 -0
- package/dist/lib/panel-anchor.d.ts.map +1 -0
- package/dist/lib/panel-anchor.js +68 -0
- package/dist/lib/panel-anchor.js.map +1 -0
- package/dist/lib/preflight/checks.d.ts.map +1 -1
- package/dist/lib/preflight/checks.js +71 -0
- package/dist/lib/preflight/checks.js.map +1 -1
- package/dist/lib/preflight/types.d.ts.map +1 -1
- package/dist/lib/preflight/types.js +11 -0
- package/dist/lib/preflight/types.js.map +1 -1
- package/dist/lib/rasterize.d.ts +93 -0
- package/dist/lib/rasterize.d.ts.map +1 -0
- package/dist/lib/rasterize.js +117 -0
- package/dist/lib/rasterize.js.map +1 -0
- package/dist/lib/separations-registry.d.ts +99 -0
- package/dist/lib/separations-registry.d.ts.map +1 -0
- package/dist/lib/separations-registry.js +59 -0
- package/dist/lib/separations-registry.js.map +1 -0
- package/dist/lib/unwired.d.ts +29 -0
- package/dist/lib/unwired.d.ts.map +1 -0
- package/dist/lib/unwired.js +58 -0
- package/dist/lib/unwired.js.map +1 -0
- package/package.json +29 -11
- package/dist/components/SeparationsPanel.d.ts +0 -9
- package/dist/components/SeparationsPanel.d.ts.map +0 -1
- package/dist/components/SeparationsPanel.js +0 -168
- package/dist/components/SeparationsPanel.js.map +0 -1
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
"use client";
|
|
3
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
4
|
+
const DEFAULT_MIN = -180;
|
|
5
|
+
const DEFAULT_MAX = 180;
|
|
6
|
+
/**
|
|
7
|
+
* @public
|
|
8
|
+
*/
|
|
9
|
+
export function FoldEditorPanel({ value, onChange, showFolded, onShowFoldedChange, minAngleDeg, maxAngleDeg, }) {
|
|
10
|
+
const min = minAngleDeg ?? DEFAULT_MIN;
|
|
11
|
+
const max = maxAngleDeg ?? DEFAULT_MAX;
|
|
12
|
+
if (!value || value.edges.length === 0) {
|
|
13
|
+
return (_jsx("div", { "data-testid": "fold-editor-panel", style: { padding: "0.5rem", opacity: 0.6 }, children: "Load a dieline with fold metadata to edit panel angles." }));
|
|
14
|
+
}
|
|
15
|
+
const setEdgeAngle = (id, raw) => {
|
|
16
|
+
// In-progress edits ("", "-", "1e") parse to `NaN` / partial
|
|
17
|
+
// values — silently keep the last good angle so the user can
|
|
18
|
+
// type through to a valid one. The browser owns its `<input>`
|
|
19
|
+
// DOM value mid-edit, so the field still shows whatever they
|
|
20
|
+
// typed even though we haven't propagated it.
|
|
21
|
+
if (raw === "" || raw === "-" || raw === "+")
|
|
22
|
+
return;
|
|
23
|
+
const n = Number(raw);
|
|
24
|
+
if (!Number.isFinite(n))
|
|
25
|
+
return;
|
|
26
|
+
const clamped = Math.min(Math.max(n, min), max);
|
|
27
|
+
onChange({
|
|
28
|
+
...value,
|
|
29
|
+
edges: value.edges.map((e) => (e.id === id ? { ...e, angleDeg: clamped } : e)),
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
const setEdgeDirection = (id, direction) => {
|
|
33
|
+
onChange({
|
|
34
|
+
...value,
|
|
35
|
+
edges: value.edges.map((e) => {
|
|
36
|
+
if (e.id !== id)
|
|
37
|
+
return e;
|
|
38
|
+
// Clearing the select drops the field entirely — restores the
|
|
39
|
+
// "no direction declared" state so the renderer can fall back
|
|
40
|
+
// to its own heuristic.
|
|
41
|
+
if (direction === undefined) {
|
|
42
|
+
const { direction: _omit, ...rest } = e;
|
|
43
|
+
return rest;
|
|
44
|
+
}
|
|
45
|
+
return { ...e, direction };
|
|
46
|
+
}),
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
return (_jsxs("div", { "data-testid": "fold-editor-panel", style: { padding: "0.5rem" }, children: [_jsxs("header", { style: { display: "flex", alignItems: "baseline", gap: "0.5rem" }, children: [_jsx("h3", { style: { margin: 0 }, children: "Fold angles" }), onShowFoldedChange && (_jsxs("label", { style: { marginLeft: "auto", fontSize: "0.85rem" }, children: [_jsx("input", { type: "checkbox", checked: !!showFolded, onChange: (e) => onShowFoldedChange(e.target.checked), "aria-label": "Show folded preview" }), " ", "Show folded"] }))] }), _jsx("ul", { style: { listStyle: "none", padding: 0, margin: "0.5rem 0 0 0" }, children: value.edges.map((edge) => (_jsxs("li", { style: {
|
|
50
|
+
display: "grid",
|
|
51
|
+
gridTemplateColumns: "auto 1fr auto auto",
|
|
52
|
+
gap: "0.5rem",
|
|
53
|
+
alignItems: "center",
|
|
54
|
+
padding: "0.25rem 0",
|
|
55
|
+
}, children: [_jsxs("span", { style: { fontFamily: "monospace", fontSize: "0.85rem" }, children: [edge.panelA, " \u2194 ", edge.panelB] }), _jsx("input", { type: "range", min: min, max: max, step: 1, value: edge.angleDeg, onChange: (e) => setEdgeAngle(edge.id, e.target.value), "aria-label": `Angle for ${edge.id}` }), _jsx("input", { type: "number", min: min, max: max, step: 1, value: edge.angleDeg, onChange: (e) => setEdgeAngle(edge.id, e.target.value), "aria-label": `Numeric angle for ${edge.id}`, style: { width: "4rem" } }), _jsxs("select", { value: edge.direction ?? "", onChange: (e) => {
|
|
56
|
+
const v = e.target.value;
|
|
57
|
+
if (v === "mountain" || v === "valley") {
|
|
58
|
+
setEdgeDirection(edge.id, v);
|
|
59
|
+
}
|
|
60
|
+
else if (v === "") {
|
|
61
|
+
setEdgeDirection(edge.id, undefined);
|
|
62
|
+
}
|
|
63
|
+
}, "aria-label": `Fold direction for ${edge.id}`, children: [_jsx("option", { value: "", children: "\u2014" }), _jsx("option", { value: "mountain", children: "mountain" }), _jsx("option", { value: "valley", children: "valley" })] })] }, edge.id))) })] }));
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=FoldEditorPanel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FoldEditorPanel.js","sourceRoot":"","sources":["../../src/components/FoldEditorPanel.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { type FoldGeometryConfig, type FoldGeometryPanelMetadata } from "../lib/fold-geometry";
|
|
2
|
+
/**
|
|
3
|
+
* S4 — 3D fold preview overlay (Wave 2 PR-3 scaffold).
|
|
4
|
+
*
|
|
5
|
+
* Renders a Three.js scene of the dieline's panels in 3D space.
|
|
6
|
+
* The PR-3 scaffold ships a *flat* preview (every panel at Z=0)
|
|
7
|
+
* with the hinge axes drawn in colored lines so the operator can
|
|
8
|
+
* see where folds will happen. Interactive fold-angle scrubbing
|
|
9
|
+
* lands in PR-4.
|
|
10
|
+
*
|
|
11
|
+
* Bundle note: Three.js is a hard dependency (~150 KB) so hosts
|
|
12
|
+
* paying for the editor get fold preview out of the box. Hosts that
|
|
13
|
+
* never need it can disable via {@link EditorConfig.enable_3d_fold_preview}
|
|
14
|
+
* — when the flag is `false`, this component is never rendered and
|
|
15
|
+
* the Three.js code path stays cold, but the symbols still ship
|
|
16
|
+
* with the bundle. A future refactor may switch to a dynamic
|
|
17
|
+
* `import("three")` for tighter chunking; that's deferred until the
|
|
18
|
+
* editor's bundle profile shows it's worth the complexity.
|
|
19
|
+
*
|
|
20
|
+
* The Three.js scene is owned by this component — it creates the
|
|
21
|
+
* renderer / scene / camera on mount, recomputes geometry on prop
|
|
22
|
+
* change, and disposes on unmount. Hosts get a controlled component:
|
|
23
|
+
* pass `panelMetadata` + `foldConfig`, get a rendered scene.
|
|
24
|
+
*
|
|
25
|
+
* @public
|
|
26
|
+
*/
|
|
27
|
+
export type FoldPreviewOverlayProps = {
|
|
28
|
+
/** Panel registry derived from the active page's dieline.
|
|
29
|
+
* Structurally compatible with `document-model`'s `PanelMetadata`.
|
|
30
|
+
* When `undefined`, the overlay no-ops (renders an empty container). */
|
|
31
|
+
panelMetadata: FoldGeometryPanelMetadata | undefined;
|
|
32
|
+
/** Fold-edge config for the active page. Structurally compatible
|
|
33
|
+
* with `document-model`'s `FoldConfig`. When `undefined`, panels
|
|
34
|
+
* are still rendered (flat layout), but no hinge lines appear. */
|
|
35
|
+
foldConfig?: FoldGeometryConfig | undefined;
|
|
36
|
+
/** Container dimensions in CSS pixels. */
|
|
37
|
+
width: number;
|
|
38
|
+
height: number;
|
|
39
|
+
/** Optional background color for the 3D viewport (CSS color
|
|
40
|
+
* string). Defaults to a transparent canvas so the overlay can
|
|
41
|
+
* layer over the editor's existing chrome. */
|
|
42
|
+
backgroundColor?: string;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* @public
|
|
46
|
+
*/
|
|
47
|
+
export declare function FoldPreviewOverlay({ panelMetadata, foldConfig, width, height, backgroundColor, }: FoldPreviewOverlayProps): import("react/jsx-runtime").JSX.Element;
|
|
48
|
+
//# sourceMappingURL=FoldPreviewOverlay.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FoldPreviewOverlay.d.ts","sourceRoot":"","sources":["../../src/components/FoldPreviewOverlay.tsx"],"names":[],"mappings":"AAIA,OAAO,EACL,KAAK,kBAAkB,EACvB,KAAK,yBAAyB,EAG/B,MAAM,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,MAAM,uBAAuB,GAAG;IACpC;;6EAEyE;IACzE,aAAa,EAAE,yBAAyB,GAAG,SAAS,CAAC;IACrD;;uEAEmE;IACnE,UAAU,CAAC,EAAE,kBAAkB,GAAG,SAAS,CAAC;IAC5C,0CAA0C;IAC1C,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf;;mDAE+C;IAC/C,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,aAAa,EACb,UAAU,EACV,KAAK,EACL,MAAM,EACN,eAAe,GAChB,EAAE,uBAAuB,2CA6FzB"}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
"use client";
|
|
3
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
4
|
+
import { useEffect, useRef } from "react";
|
|
5
|
+
import { buildFoldScene, } from "../lib/fold-geometry";
|
|
6
|
+
/**
|
|
7
|
+
* @public
|
|
8
|
+
*/
|
|
9
|
+
export function FoldPreviewOverlay({ panelMetadata, foldConfig, width, height, backgroundColor, }) {
|
|
10
|
+
const containerRef = useRef(null);
|
|
11
|
+
const sceneRef = useRef(null);
|
|
12
|
+
// Hold the latest props in a ref so the mount effect's initial
|
|
13
|
+
// scene picks them up without depending on them — depending on
|
|
14
|
+
// `panelMetadata` / `foldConfig` here would tear down + recreate
|
|
15
|
+
// the renderer on every edit, defeating the per-update apply path
|
|
16
|
+
// in the second effect.
|
|
17
|
+
const propsRef = useRef({ panelMetadata, foldConfig });
|
|
18
|
+
propsRef.current = { panelMetadata, foldConfig };
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
const container = containerRef.current;
|
|
21
|
+
if (!container)
|
|
22
|
+
return;
|
|
23
|
+
// Guard zero / negative dimensions — `setSize(0, 0)` produces a
|
|
24
|
+
// degenerate canvas and `width / height` on the camera becomes
|
|
25
|
+
// NaN/Infinity. Layout transitions can briefly hit this during
|
|
26
|
+
// mount; defer scene creation until both dims are positive.
|
|
27
|
+
if (width <= 0 || height <= 0)
|
|
28
|
+
return;
|
|
29
|
+
let runtime = null;
|
|
30
|
+
let disposed = false;
|
|
31
|
+
(async () => {
|
|
32
|
+
const three = await import("three");
|
|
33
|
+
if (disposed)
|
|
34
|
+
return;
|
|
35
|
+
const created = await createScene(three, container, width, height, backgroundColor);
|
|
36
|
+
// Re-check after the second await — cleanup may have fired
|
|
37
|
+
// during `await createScene(...)`. At that point `runtime` was
|
|
38
|
+
// still null in the cleanup closure, so it couldn't dispose;
|
|
39
|
+
// do it here ourselves before the renderer / canvas escape.
|
|
40
|
+
if (disposed) {
|
|
41
|
+
disposeScene(created);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
runtime = created;
|
|
45
|
+
sceneRef.current = runtime;
|
|
46
|
+
const { panelMetadata: pm, foldConfig: fc } = propsRef.current;
|
|
47
|
+
const spec = pm ? buildFoldScene(pm, fc) : EMPTY_SCENE;
|
|
48
|
+
applyScene(three, runtime, spec);
|
|
49
|
+
// Render-on-change: PR-3's scaffold is static, so one draw per
|
|
50
|
+
// mutation is enough. PR-4 swaps this for a RAF loop driven by
|
|
51
|
+
// user interaction (drag-to-fold), so the loop machinery isn't
|
|
52
|
+
// worth keeping cold here.
|
|
53
|
+
runtime.renderer.render(runtime.scene, runtime.camera);
|
|
54
|
+
})().catch((err) => {
|
|
55
|
+
// Swallow rejections after unmount; surface real errors during
|
|
56
|
+
// mount (chunk-load failures from `import("three")`, WebGL
|
|
57
|
+
// context creation failure, etc.) via console so devtools can
|
|
58
|
+
// catch them.
|
|
59
|
+
if (!disposed)
|
|
60
|
+
console.error("[FoldPreviewOverlay] scene init failed", err);
|
|
61
|
+
});
|
|
62
|
+
return () => {
|
|
63
|
+
disposed = true;
|
|
64
|
+
sceneRef.current = null;
|
|
65
|
+
if (runtime)
|
|
66
|
+
disposeScene(runtime);
|
|
67
|
+
};
|
|
68
|
+
}, [width, height, backgroundColor]);
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
const runtime = sceneRef.current;
|
|
71
|
+
if (!runtime)
|
|
72
|
+
return;
|
|
73
|
+
let disposed = false;
|
|
74
|
+
(async () => {
|
|
75
|
+
const three = await import("three");
|
|
76
|
+
if (disposed)
|
|
77
|
+
return;
|
|
78
|
+
// When panelMetadata becomes undefined, apply an empty scene
|
|
79
|
+
// so previously rendered meshes / hinges clear off the canvas —
|
|
80
|
+
// matches the documented no-op behaviour for absent metadata.
|
|
81
|
+
const spec = panelMetadata ? buildFoldScene(panelMetadata, foldConfig) : EMPTY_SCENE;
|
|
82
|
+
applyScene(three, runtime, spec);
|
|
83
|
+
runtime.renderer.render(runtime.scene, runtime.camera);
|
|
84
|
+
})().catch((err) => {
|
|
85
|
+
if (!disposed)
|
|
86
|
+
console.error("[FoldPreviewOverlay] scene update failed", err);
|
|
87
|
+
});
|
|
88
|
+
return () => {
|
|
89
|
+
disposed = true;
|
|
90
|
+
};
|
|
91
|
+
}, [panelMetadata, foldConfig]);
|
|
92
|
+
return (_jsx("div", { ref: containerRef, "data-testid": "fold-preview-overlay", style: {
|
|
93
|
+
position: "relative",
|
|
94
|
+
width,
|
|
95
|
+
height,
|
|
96
|
+
pointerEvents: "auto",
|
|
97
|
+
} }));
|
|
98
|
+
}
|
|
99
|
+
/** Empty scene spec used to clear the canvas when `panelMetadata`
|
|
100
|
+
* is absent. Keeps the meshes / hinges from a previous render off
|
|
101
|
+
* the screen. */
|
|
102
|
+
const EMPTY_SCENE = {
|
|
103
|
+
panels: [],
|
|
104
|
+
hinges: [],
|
|
105
|
+
bounds: { min: [0, 0, 0], max: [0, 0, 0] },
|
|
106
|
+
};
|
|
107
|
+
async function createScene(three, container, width, height, backgroundColor) {
|
|
108
|
+
const renderer = new three.WebGLRenderer({ antialias: true, alpha: !backgroundColor });
|
|
109
|
+
renderer.setSize(width, height);
|
|
110
|
+
renderer.setPixelRatio(typeof window !== "undefined" ? window.devicePixelRatio : 1);
|
|
111
|
+
container.replaceChildren(renderer.domElement);
|
|
112
|
+
const scene = new three.Scene();
|
|
113
|
+
if (backgroundColor) {
|
|
114
|
+
scene.background = new three.Color(backgroundColor);
|
|
115
|
+
}
|
|
116
|
+
const camera = new three.PerspectiveCamera(45, width / height, 0.1, 10000);
|
|
117
|
+
camera.position.set(0, 0, 500);
|
|
118
|
+
const light = new three.HemisphereLight(0xffffff, 0x202020, 1);
|
|
119
|
+
scene.add(light);
|
|
120
|
+
const panelGroup = new three.Group();
|
|
121
|
+
const hingeGroup = new three.Group();
|
|
122
|
+
scene.add(panelGroup, hingeGroup);
|
|
123
|
+
const runtime = {
|
|
124
|
+
renderer,
|
|
125
|
+
scene,
|
|
126
|
+
camera,
|
|
127
|
+
panelGroup,
|
|
128
|
+
hingeGroup,
|
|
129
|
+
resources: [],
|
|
130
|
+
};
|
|
131
|
+
return runtime;
|
|
132
|
+
}
|
|
133
|
+
function applyScene(three, runtime, spec) {
|
|
134
|
+
// Tear down previous geometry without disposing the renderer.
|
|
135
|
+
runtime.panelGroup.clear();
|
|
136
|
+
runtime.hingeGroup.clear();
|
|
137
|
+
for (const r of runtime.resources)
|
|
138
|
+
r.dispose?.();
|
|
139
|
+
runtime.resources = [];
|
|
140
|
+
for (const panel of spec.panels) {
|
|
141
|
+
const geo = new three.BufferGeometry();
|
|
142
|
+
const positions = new Float32Array(panel.corners.flatMap((c) => [c[0], c[1], c[2]]));
|
|
143
|
+
geo.setAttribute("position", new three.BufferAttribute(positions, 3));
|
|
144
|
+
geo.setIndex([0, 1, 2, 0, 2, 3]);
|
|
145
|
+
geo.computeVertexNormals();
|
|
146
|
+
const mat = new three.MeshStandardMaterial({
|
|
147
|
+
color: 0xe6e6e6,
|
|
148
|
+
side: three.DoubleSide,
|
|
149
|
+
roughness: 0.6,
|
|
150
|
+
metalness: 0.0,
|
|
151
|
+
});
|
|
152
|
+
runtime.panelGroup.add(new three.Mesh(geo, mat));
|
|
153
|
+
runtime.resources.push(geo, mat);
|
|
154
|
+
}
|
|
155
|
+
for (const hinge of spec.hinges) {
|
|
156
|
+
const geo = new three.BufferGeometry().setFromPoints([
|
|
157
|
+
new three.Vector3(hinge.from[0], hinge.from[1], hinge.from[2]),
|
|
158
|
+
new three.Vector3(hinge.to[0], hinge.to[1], hinge.to[2]),
|
|
159
|
+
]);
|
|
160
|
+
const mat = new three.LineBasicMaterial({
|
|
161
|
+
color: hinge.direction === "mountain" ? 0x2563eb : 0xdc2626,
|
|
162
|
+
linewidth: 2,
|
|
163
|
+
});
|
|
164
|
+
runtime.hingeGroup.add(new three.Line(geo, mat));
|
|
165
|
+
runtime.resources.push(geo, mat);
|
|
166
|
+
}
|
|
167
|
+
// Frame the camera around the scene bounds.
|
|
168
|
+
const { min, max } = spec.bounds;
|
|
169
|
+
const cx = (min[0] + max[0]) / 2;
|
|
170
|
+
const cy = (min[1] + max[1]) / 2;
|
|
171
|
+
const span = Math.max(max[0] - min[0], max[1] - min[1], 1);
|
|
172
|
+
runtime.camera.position.set(cx, cy, span * 1.2);
|
|
173
|
+
runtime.camera.lookAt(cx, cy, 0);
|
|
174
|
+
}
|
|
175
|
+
function disposeScene(runtime) {
|
|
176
|
+
for (const r of runtime.resources)
|
|
177
|
+
r.dispose?.();
|
|
178
|
+
runtime.resources = [];
|
|
179
|
+
runtime.renderer.dispose?.();
|
|
180
|
+
runtime.renderer.domElement?.parentElement?.removeChild(runtime.renderer.domElement);
|
|
181
|
+
}
|
|
182
|
+
//# sourceMappingURL=FoldPreviewOverlay.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FoldPreviewOverlay.js","sourceRoot":"","sources":["../../src/components/FoldPreviewOverlay.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import type { BarcodeRenderFn, BarcodeRenderResult } from "./BarcodeGeneratorPanel";
|
|
2
|
+
/**
|
|
3
|
+
* One additional GS1 Application Identifier carried in either the URL
|
|
4
|
+
* path or query string. `ai` is the numeric AI (e.g. `"10"` for lot,
|
|
5
|
+
* `"17"` for expiration date `YYMMDD`); `value` is the raw payload
|
|
6
|
+
* before percent-encoding.
|
|
7
|
+
*
|
|
8
|
+
* The composer percent-encodes `value` automatically; callers should
|
|
9
|
+
* pass the human-readable form.
|
|
10
|
+
*
|
|
11
|
+
* @public
|
|
12
|
+
*/
|
|
13
|
+
export type Gs1AiEntry = {
|
|
14
|
+
ai: string;
|
|
15
|
+
value: string;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Result returned by {@link composeGs1DigitalLink}. `url` is the
|
|
19
|
+
* canonical GS1 Digital Link form ready to be QR-encoded;
|
|
20
|
+
* `pathSegment` and `querySegment` are the split halves the host can
|
|
21
|
+
* surface separately (e.g. a "preview" UI that highlights mandatory
|
|
22
|
+
* vs. optional segments).
|
|
23
|
+
*
|
|
24
|
+
* @public
|
|
25
|
+
*/
|
|
26
|
+
export type Gs1DigitalLinkResult = {
|
|
27
|
+
url: string;
|
|
28
|
+
pathSegment: string;
|
|
29
|
+
querySegment: string;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* @public
|
|
33
|
+
*/
|
|
34
|
+
export type Gs1DigitalLinkPanelProps = {
|
|
35
|
+
/** Optional renderer (typically the same adapter wired for
|
|
36
|
+
* {@link import("./BarcodeGeneratorPanel").BarcodeGeneratorPanel}).
|
|
37
|
+
* When supplied, "Generate QR" calls it with `format: "QR"` and the
|
|
38
|
+
* composed Digital Link URL; the bitmap flows to `onRendered`. When
|
|
39
|
+
* absent, the panel only emits the URL via `onLink`. */
|
|
40
|
+
renderer?: BarcodeRenderFn;
|
|
41
|
+
/** Fired when the user clicks **Preview URL** or **Generate QR**
|
|
42
|
+
* with the composed URL. The panel does not fire on every
|
|
43
|
+
* keystroke — hosts that want live preview should poll the
|
|
44
|
+
* current form values via their own state or wrap
|
|
45
|
+
* {@link composeGs1DigitalLink} themselves. */
|
|
46
|
+
onLink?: (result: Gs1DigitalLinkResult) => void;
|
|
47
|
+
/** Fired with the rendered bitmap when the host has wired a
|
|
48
|
+
* `renderer` and the user clicks "Generate QR". */
|
|
49
|
+
onRendered?: (result: BarcodeRenderResult) => void;
|
|
50
|
+
/** Default resolver domain — typically `"id.gs1.org"` or a
|
|
51
|
+
* brand-owned alternate. Defaults to `"id.gs1.org"` (the GS1
|
|
52
|
+
* community default) when omitted. */
|
|
53
|
+
defaultDomain?: string;
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* The GS1 community-default resolver domain. Re-exported for hosts
|
|
57
|
+
* that want to display it as a hint or seed a settings UI.
|
|
58
|
+
*
|
|
59
|
+
* @public
|
|
60
|
+
*/
|
|
61
|
+
export declare const DEFAULT_GS1_DOMAIN = "id.gs1.org";
|
|
62
|
+
/**
|
|
63
|
+
* Validate a GS1 AI 17 (expiration date) value: must be exactly six
|
|
64
|
+
* digits in `YYMMDD` order with month 1-12 and a real calendar day.
|
|
65
|
+
* Year is two digits per the GS1 spec so the validator checks the
|
|
66
|
+
* day against an arbitrary 21st-century year — the year value itself
|
|
67
|
+
* does not constrain the calendar.
|
|
68
|
+
*
|
|
69
|
+
* Exported alongside {@link composeGs1DigitalLink} so server-side
|
|
70
|
+
* callers can reject invalid expiries before composing.
|
|
71
|
+
*
|
|
72
|
+
* @public
|
|
73
|
+
*/
|
|
74
|
+
export declare function isValidGs1Ai17(value: string): boolean;
|
|
75
|
+
/**
|
|
76
|
+
* Compose a canonical GS1 Digital Link URL from a GTIN + optional
|
|
77
|
+
* path-AIs + optional query-AIs. Pure function — exported so server
|
|
78
|
+
* components (Next.js RSC, Astro frontmatter) can pre-compute URLs
|
|
79
|
+
* without bundling the panel.
|
|
80
|
+
*
|
|
81
|
+
* Path-AI order is `22 → 10 → 21` per the GS1 Digital Link Standard;
|
|
82
|
+
* any other AIs flow into the query string in the order supplied.
|
|
83
|
+
*
|
|
84
|
+
* @public
|
|
85
|
+
*/
|
|
86
|
+
export declare function composeGs1DigitalLink(input: {
|
|
87
|
+
domain: string;
|
|
88
|
+
gtin: string;
|
|
89
|
+
pathAis?: readonly Gs1AiEntry[];
|
|
90
|
+
queryAis?: readonly Gs1AiEntry[];
|
|
91
|
+
}): Gs1DigitalLinkResult;
|
|
92
|
+
/**
|
|
93
|
+
* GS1 Digital Link composer panel. The user picks a domain, types a
|
|
94
|
+
* GTIN, optionally adds CPV / lot / serial path-AIs and free-form
|
|
95
|
+
* query-AIs; the panel emits the composed URL via `onLink`. When the
|
|
96
|
+
* host wires a `renderer`, a "Generate QR" button hands the URL to
|
|
97
|
+
* the adapter with `format: "QR"` and forwards the bitmap to
|
|
98
|
+
* `onRendered`.
|
|
99
|
+
*
|
|
100
|
+
* @public
|
|
101
|
+
*/
|
|
102
|
+
export declare function Gs1DigitalLinkPanel({ renderer, onLink, onRendered, defaultDomain, }: Gs1DigitalLinkPanelProps): import("react/jsx-runtime").JSX.Element;
|
|
103
|
+
//# sourceMappingURL=Gs1DigitalLinkPanel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Gs1DigitalLinkPanel.d.ts","sourceRoot":"","sources":["../../src/components/Gs1DigitalLinkPanel.tsx"],"names":[],"mappings":"AA4BA,OAAO,KAAK,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAEpF;;;;;;;;;;GAUG;AACH,MAAM,MAAM,UAAU,GAAG;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,MAAM,oBAAoB,GAAG;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,wBAAwB,GAAG;IACrC;;;;6DAIyD;IACzD,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B;;;;oDAIgD;IAChD,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,oBAAoB,KAAK,IAAI,CAAC;IAChD;wDACoD;IACpD,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,mBAAmB,KAAK,IAAI,CAAC;IACnD;;2CAEuC;IACvC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,eAAe,CAAC;AAI/C;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAUrD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE;IAC3C,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,SAAS,UAAU,EAAE,CAAC;IAChC,QAAQ,CAAC,EAAE,SAAS,UAAU,EAAE,CAAC;CAClC,GAAG,oBAAoB,CA6BvB;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,QAAQ,EACR,MAAM,EACN,UAAU,EACV,aAAkC,GACnC,EAAE,wBAAwB,2CA6K1B"}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
"use client";
|
|
3
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
4
|
+
/**
|
|
5
|
+
* Wave 3 G3 — GS1 Digital Link composer panel.
|
|
6
|
+
*
|
|
7
|
+
* GS1 Digital Link (ISO/IEC 15459 + GS1 Digital Link Standard v1.3) is
|
|
8
|
+
* a structured URL form for product identifiers that can be encoded
|
|
9
|
+
* into a single QR code and resolved on-device. Shape:
|
|
10
|
+
*
|
|
11
|
+
* `https://<domain>/01/<gtin>[/22/<cpv>][/10/<lot>][/21/<serial>][?...]`
|
|
12
|
+
*
|
|
13
|
+
* Path AIs (Application Identifiers) carry the "primary key" segments
|
|
14
|
+
* — `01` (GTIN) is mandatory, then `22`/`10`/`21` in canonical order.
|
|
15
|
+
* Query-string AIs carry the long tail (expiration date, batch, etc.)
|
|
16
|
+
* via `<ai>=<value>` pairs.
|
|
17
|
+
*
|
|
18
|
+
* The panel handles URL composition only — emitting the URL via
|
|
19
|
+
* `onLink`. Hosts that also want a rendered QR bitmap wire the Wave 3
|
|
20
|
+
* G2g {@link import("./BarcodeGeneratorPanel").BarcodeRenderFn} shape
|
|
21
|
+
* via the optional `renderer` prop and receive the bitmap via
|
|
22
|
+
* `onRendered`. This keeps the composer free of any rendering library
|
|
23
|
+
* dep and lets G2g + G3 share one adapter.
|
|
24
|
+
*
|
|
25
|
+
* @public
|
|
26
|
+
*/
|
|
27
|
+
import { useState } from "react";
|
|
28
|
+
/**
|
|
29
|
+
* The GS1 community-default resolver domain. Re-exported for hosts
|
|
30
|
+
* that want to display it as a hint or seed a settings UI.
|
|
31
|
+
*
|
|
32
|
+
* @public
|
|
33
|
+
*/
|
|
34
|
+
export const DEFAULT_GS1_DOMAIN = "id.gs1.org";
|
|
35
|
+
const PATH_AIS_IN_ORDER = ["22", "10", "21"];
|
|
36
|
+
/**
|
|
37
|
+
* Validate a GS1 AI 17 (expiration date) value: must be exactly six
|
|
38
|
+
* digits in `YYMMDD` order with month 1-12 and a real calendar day.
|
|
39
|
+
* Year is two digits per the GS1 spec so the validator checks the
|
|
40
|
+
* day against an arbitrary 21st-century year — the year value itself
|
|
41
|
+
* does not constrain the calendar.
|
|
42
|
+
*
|
|
43
|
+
* Exported alongside {@link composeGs1DigitalLink} so server-side
|
|
44
|
+
* callers can reject invalid expiries before composing.
|
|
45
|
+
*
|
|
46
|
+
* @public
|
|
47
|
+
*/
|
|
48
|
+
export function isValidGs1Ai17(value) {
|
|
49
|
+
if (!/^\d{6}$/.test(value))
|
|
50
|
+
return false;
|
|
51
|
+
const yy = Number(value.slice(0, 2));
|
|
52
|
+
const mm = Number(value.slice(2, 4));
|
|
53
|
+
const dd = Number(value.slice(4, 6));
|
|
54
|
+
if (mm < 1 || mm > 12)
|
|
55
|
+
return false;
|
|
56
|
+
if (dd < 1 || dd > 31)
|
|
57
|
+
return false;
|
|
58
|
+
const fullYear = 2000 + yy;
|
|
59
|
+
const date = new Date(fullYear, mm - 1, dd);
|
|
60
|
+
return date.getFullYear() === fullYear && date.getMonth() === mm - 1 && date.getDate() === dd;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Compose a canonical GS1 Digital Link URL from a GTIN + optional
|
|
64
|
+
* path-AIs + optional query-AIs. Pure function — exported so server
|
|
65
|
+
* components (Next.js RSC, Astro frontmatter) can pre-compute URLs
|
|
66
|
+
* without bundling the panel.
|
|
67
|
+
*
|
|
68
|
+
* Path-AI order is `22 → 10 → 21` per the GS1 Digital Link Standard;
|
|
69
|
+
* any other AIs flow into the query string in the order supplied.
|
|
70
|
+
*
|
|
71
|
+
* @public
|
|
72
|
+
*/
|
|
73
|
+
export function composeGs1DigitalLink(input) {
|
|
74
|
+
// Normalize the host: trim whitespace, strip any explicit
|
|
75
|
+
// `http(s)://` scheme the user pasted (the composer always emits
|
|
76
|
+
// `https://`), then drop trailing slashes. Empty / whitespace-only
|
|
77
|
+
// input falls back to the GS1 community default rather than
|
|
78
|
+
// producing the malformed `https:///01/...`.
|
|
79
|
+
const trimmed = input.domain.trim().replace(/^https?:\/\//i, "");
|
|
80
|
+
const stripped = trimmed.replace(/\/+$/, "");
|
|
81
|
+
const domain = stripped === "" ? DEFAULT_GS1_DOMAIN : stripped;
|
|
82
|
+
const pathAisByCode = new Map();
|
|
83
|
+
for (const entry of input.pathAis ?? []) {
|
|
84
|
+
pathAisByCode.set(entry.ai, entry.value);
|
|
85
|
+
}
|
|
86
|
+
let pathSegment = `/01/${encodeURIComponent(input.gtin)}`;
|
|
87
|
+
for (const code of PATH_AIS_IN_ORDER) {
|
|
88
|
+
const value = pathAisByCode.get(code);
|
|
89
|
+
if (value !== undefined && value !== "") {
|
|
90
|
+
pathSegment += `/${code}/${encodeURIComponent(value)}`;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const querySegments = (input.queryAis ?? [])
|
|
94
|
+
.filter((entry) => entry.value !== "")
|
|
95
|
+
.map((entry) => `${encodeURIComponent(entry.ai)}=${encodeURIComponent(entry.value)}`);
|
|
96
|
+
const querySegment = querySegments.length > 0 ? `?${querySegments.join("&")}` : "";
|
|
97
|
+
return {
|
|
98
|
+
url: `https://${domain}${pathSegment}${querySegment}`,
|
|
99
|
+
pathSegment,
|
|
100
|
+
querySegment,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* GS1 Digital Link composer panel. The user picks a domain, types a
|
|
105
|
+
* GTIN, optionally adds CPV / lot / serial path-AIs and free-form
|
|
106
|
+
* query-AIs; the panel emits the composed URL via `onLink`. When the
|
|
107
|
+
* host wires a `renderer`, a "Generate QR" button hands the URL to
|
|
108
|
+
* the adapter with `format: "QR"` and forwards the bitmap to
|
|
109
|
+
* `onRendered`.
|
|
110
|
+
*
|
|
111
|
+
* @public
|
|
112
|
+
*/
|
|
113
|
+
export function Gs1DigitalLinkPanel({ renderer, onLink, onRendered, defaultDomain = DEFAULT_GS1_DOMAIN, }) {
|
|
114
|
+
const [domain, setDomain] = useState(defaultDomain);
|
|
115
|
+
const [gtin, setGtin] = useState("");
|
|
116
|
+
const [cpv, setCpv] = useState("");
|
|
117
|
+
const [lot, setLot] = useState("");
|
|
118
|
+
const [serial, setSerial] = useState("");
|
|
119
|
+
const [expiration, setExpiration] = useState("");
|
|
120
|
+
const [busy, setBusy] = useState(false);
|
|
121
|
+
const [error, setError] = useState(null);
|
|
122
|
+
/**
|
|
123
|
+
* Validate the form. Returns the composed result on success or an
|
|
124
|
+
* error message; null `result` means the caller must not proceed.
|
|
125
|
+
* Centralised here so `handlePreview` and `handleGenerate` share
|
|
126
|
+
* the same checks.
|
|
127
|
+
*/
|
|
128
|
+
function validateAndCompose() {
|
|
129
|
+
const trimmedGtin = gtin.trim();
|
|
130
|
+
if (!trimmedGtin) {
|
|
131
|
+
return { result: null, error: "GTIN is required." };
|
|
132
|
+
}
|
|
133
|
+
// GS1 GTINs are 8 / 12 / 13 / 14 digits (GTIN-8, UPC-A, EAN-13,
|
|
134
|
+
// GTIN-14). The check digit isn't validated here — leave that to
|
|
135
|
+
// the host renderer / server, which can also surface check-digit
|
|
136
|
+
// errors with a specific message.
|
|
137
|
+
if (!/^\d{8}$|^\d{12}$|^\d{13}$|^\d{14}$/.test(trimmedGtin)) {
|
|
138
|
+
return {
|
|
139
|
+
result: null,
|
|
140
|
+
error: "GTIN must be 8, 12, 13, or 14 digits.",
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
const trimmedExpiration = expiration.trim();
|
|
144
|
+
if (trimmedExpiration !== "" && !isValidGs1Ai17(trimmedExpiration)) {
|
|
145
|
+
return {
|
|
146
|
+
result: null,
|
|
147
|
+
error: "Expiration date (AI 17) must be a valid YYMMDD value.",
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
error: null,
|
|
152
|
+
result: composeGs1DigitalLink({
|
|
153
|
+
domain,
|
|
154
|
+
gtin: trimmedGtin,
|
|
155
|
+
pathAis: [
|
|
156
|
+
...(cpv.trim() ? [{ ai: "22", value: cpv.trim() }] : []),
|
|
157
|
+
...(lot.trim() ? [{ ai: "10", value: lot.trim() }] : []),
|
|
158
|
+
...(serial.trim() ? [{ ai: "21", value: serial.trim() }] : []),
|
|
159
|
+
],
|
|
160
|
+
queryAis: trimmedExpiration ? [{ ai: "17", value: trimmedExpiration }] : [],
|
|
161
|
+
}),
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
function handlePreview() {
|
|
165
|
+
const { result, error: validationError } = validateAndCompose();
|
|
166
|
+
if (!result) {
|
|
167
|
+
setError(validationError);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
setError(null);
|
|
171
|
+
onLink?.(result);
|
|
172
|
+
}
|
|
173
|
+
async function handleGenerate() {
|
|
174
|
+
const { result, error: validationError } = validateAndCompose();
|
|
175
|
+
if (!result) {
|
|
176
|
+
setError(validationError);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (!renderer) {
|
|
180
|
+
setError("No QR renderer wired.");
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
setBusy(true);
|
|
184
|
+
setError(null);
|
|
185
|
+
try {
|
|
186
|
+
onLink?.(result);
|
|
187
|
+
const bitmap = await renderer({ format: "QR", payload: result.url });
|
|
188
|
+
onRendered?.(bitmap);
|
|
189
|
+
}
|
|
190
|
+
catch (err) {
|
|
191
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
192
|
+
}
|
|
193
|
+
finally {
|
|
194
|
+
setBusy(false);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return (_jsxs("div", { "data-testid": "gs1-digital-link-panel", style: { padding: "0.5rem" }, children: [_jsx("h3", { style: { margin: "0 0 0.5rem 0" }, children: "GS1 Digital Link" }), _jsxs("label", { style: { display: "block", marginBottom: "0.5rem" }, children: ["Resolver domain", _jsx("input", { type: "text", value: domain, onChange: (e) => setDomain(e.target.value), "aria-label": "GS1 resolver domain", style: { marginLeft: "0.5rem", width: "16em" } })] }), _jsxs("label", { style: { display: "block", marginBottom: "0.5rem" }, children: ["GTIN (AI 01)", _jsx("input", { type: "text", value: gtin, onChange: (e) => setGtin(e.target.value), "aria-label": "GTIN", style: { marginLeft: "0.5rem", width: "16em" } })] }), _jsxs("label", { style: { display: "block", marginBottom: "0.5rem" }, children: ["CPV (AI 22, optional)", _jsx("input", { type: "text", value: cpv, onChange: (e) => setCpv(e.target.value), "aria-label": "Consumer product variant", style: { marginLeft: "0.5rem", width: "12em" } })] }), _jsxs("label", { style: { display: "block", marginBottom: "0.5rem" }, children: ["Lot (AI 10, optional)", _jsx("input", { type: "text", value: lot, onChange: (e) => setLot(e.target.value), "aria-label": "Lot or batch", style: { marginLeft: "0.5rem", width: "12em" } })] }), _jsxs("label", { style: { display: "block", marginBottom: "0.5rem" }, children: ["Serial (AI 21, optional)", _jsx("input", { type: "text", value: serial, onChange: (e) => setSerial(e.target.value), "aria-label": "Serial number", style: { marginLeft: "0.5rem", width: "12em" } })] }), _jsxs("label", { style: { display: "block", marginBottom: "0.5rem" }, children: ["Expiration date (AI 17, YYMMDD, optional)", _jsx("input", { type: "text", value: expiration, onChange: (e) => setExpiration(e.target.value), "aria-label": "Expiration date", style: { marginLeft: "0.5rem", width: "8em" } })] }), _jsx("button", { type: "button", onClick: handlePreview, style: { padding: "0.4rem 0.8rem", marginRight: "0.5rem" }, children: "Preview URL" }), renderer && (_jsx("button", { type: "button", onClick: handleGenerate, disabled: busy, style: { padding: "0.4rem 0.8rem" }, children: busy ? "Generating…" : "Generate QR" })), error && (_jsx("div", { role: "alert", style: { marginTop: "0.5rem", color: "#a00" }, children: error }))] }));
|
|
198
|
+
}
|
|
199
|
+
//# sourceMappingURL=Gs1DigitalLinkPanel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Gs1DigitalLinkPanel.js","sourceRoot":"","sources":["../../src/components/Gs1DigitalLinkPanel.tsx"],"names":[],"mappings":"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"}
|