Feat/variables phase1#87
Merged
Merged
Conversation
…1 step 1) Introduces the document-level Variables data model that ^FN/^FV round-trip will build on. No UI, no parser/generator changes yet. - new Variable type + zod schema in src/types/Variable.ts - variableId? added to labelObjectBaseSchema - store: variables state, addVariable/updateVariable/removeVariable (removeVariable strips variableId across all pages and groups) - designFile: optional variables array, omitted when empty for back-compat - useDesignFileActions passes variables through save/load
… (Phase 1 step 2)
Text and barcode emitters now consult ctx.variables: when an object
carries variableId pointing at a known variable, the field block becomes
^FN{n}^FD{default}^FS so the printer treats it as a template slot.
Parser walks the same path in reverse and reconstructs Variables from
^FN slots, deriving names from immediately-preceding ^FX comments.
- ZplEmitContext.variables threaded from generator into every emitter
- fdFieldFor helper in zplHelpers wraps fdField with FN handling
- 9 emitters (text + 8 barcode types) switched to fdFieldFor
- Parser: ^FN handler + flushField binding, accumulator returned in ParsedZPL
- variableNameFromComment + shared uniqueVariableName helper
- zplImportService merges variables across multi-page blocks by fnNumber
with id remap so bindings survive the merge
- ZplImportModal threads importedVariables into loadDesign;
append-mode intentionally drops them (no merge dialog yet)
- All callers of generate(Multi)PageZPL and printLabel pass variables
- 12 new tests: parser binding, generator emission, round-trip,
orphan handling, out-of-range ^FN
Standalone CRUD UI for document variables: add, rename, change ^FN slot, edit default value, delete with confirm. Not yet wired into the right sidebar tabs (that lands in step 3c). Hardcoded English copy marked for locale extraction in step 3d. - inline name/slot/default editors with commit-on-blur to avoid store thrashing on each keystroke - per-row error hint when the store rejects an update (name/slot collision, out-of-range slot) - delete dialog surfaces binding count so the user knows what gets unbound - add button auto-picks the lowest free var_N name and ^FN slot via the existing nextFreeFnNumber helper
…se 1 step 3c) Extract the four right-sidebar tabs (Properties, Layers, Variables, Fonts) into a dedicated RightSidebar component. Switch from text-only tabs to icon-only with title + aria-label tooltips, fitting four tabs into the 256px column without truncation. AppShell loses ~40 lines of tab markup. - new RightSidebar component owns rightTab state and panel routing - icons: AdjustmentsHorizontalIcon (properties), RectangleStackIcon (layers), VariableIcon (variables), inline AaIcon (fonts, no fitting heroicon available) - AppShell delegates to RightSidebar via canvasRef prop - Variables tab label is a literal English fallback marked for locale extraction in step 3d
…sible copy Move ZPL command codes (^FN, ^FD, ^FV) into per-field info tooltips so the panel reads cleanly for users who don't need to know about printer internals. Power users still see them on hover. Panel hint at the top drops ZPL jargon entirely. - new FieldLabel helper renders label + InformationCircleIcon (matches existing PropertiesPanel pattern) - name/slot/default each get a short 'what this is for' explanation - panel hint rewritten to plain language Locale extraction stays deferred to the end-of-branch sweep (see docs/variables-plan.local.md).
Text and barcode fields show a 'Variable' dropdown in their Properties panel: pick an existing variable to bind, 'Create new…' to define one inline (seeded with the field's current content), or unbind via X. Binding writes obj.variableId; the generator already swaps in the variable's default at ^FN/^FD emit time. - ObjectTypeDefinition gets a bindable flag; text + 8 barcode types opt in, mirroring which registries actually wire fdFieldFor - new VariableBindingControl component handles dropdown + inline-create flow, error states (slot exhaustion, name collision) - PropertiesPanel renders it above the per-type section when definition.bindable, only for leaves - orphan binding (variableId pointing at deleted variable) treated as unbound in the UI — emitter already falls back to literal content
…fault Previously the canvas kept rendering props.content for bound fields, which left the user editing a variable in the Variables tab without seeing the change anywhere in the designer. Now the dispatcher patches props.content with the bound variable's defaultValue before any per-type renderer runs, so the canvas previews exactly what the printer will print (absent a runtime ^FV override). - new pure helper lib/variableBinding.ts: lookupBoundVariable + applyBindingToObject (generic, identity-preserving) - KonvaObject dispatcher applies it once, all 9 bindable renderers inherit the swap without per-type changes - VariableBindingControl now previews the bound default below the dropdown with a 'edit in Variables tab' hint, so the relationship between the Properties pane and the Variables panel is explicit
Collected fixes after live-testing the Phase 1 feature in the browser:
- B1: per-type CONTENT input mirrors bound variable defaultValue and
redirects edits into updateVariable, so canvas / CONTENT input /
Variables tab tell one consistent story (PropertiesPanel patches
the obj it hands to TypePanel + intercepts onChange).
- B4: VariableBindingControl dropdown options read
`{name} — "{default}"` (truncated at 24 chars; empty default shows
as `(empty)`) so users pick the right binding without remembering
which variable holds what.
- B5: LayerRow shows a small VariableIcon badge between the type icon
and the row label when the leaf is bound, with the variable name
as a hover tooltip.
- Polish A: PropertiesPanel wraps VariableBindingControl in a
CollapsibleSection. defaultOpen={!!obj.variableId} — unbound fields
hide the binding UI behind a chevron (less jargon for beginners),
bound fields stay expanded. Separate localStorage ids per state.
- Polish C: Variables-tab empty state adds a one-line example
('sequential SKUs, batch numbers, customer names') instead of just
'No variables yet.'. Panel hint also tightened — extended help
belongs in the planned hover-help system (see plan-doc).
Locale sweep across all 32 supported languages, completing Phase 1's
deferred i18n work. New keys live in a top-level `variables` block
plus a `variablesTab` key in the `layers` block (where the other
sidebar tab labels sit).
Migrated COPY constants → t.variables.X in:
- VariablesPanel.tsx (panel hint, empty-state, row labels + tooltips,
delete-confirm template, error messages, parameterised badges)
- VariableBindingControl.tsx (section title + tooltip, dropdown
placeholders, inline-create flow, bound preview)
- PropertiesPanel.tsx (Variable section title)
- RightSidebar.tsx (Variables tab tooltip)
Parameterised strings use the existing `Fmt` suffix convention with
`{name}`/`{n}` placeholders, consistent with multipleSelectedFmt
elsewhere in the locale.
Tooling notes (gitignored .local. scripts):
- add_locale_key.local.py extended with block_indent + block-scoped
presence checks so anchors like `empty` or `cancel` that exist in
multiple sibling blocks land in the intended one. Also accepts
array-of-configs for batch addition (chains anchors automatically).
- add_variables_block.local.py is a one-off helper that seeded the
`variables: {}` skeleton in all 32 locales before the key sweep.
There was a problem hiding this comment.
Code Review
This pull request introduces a comprehensive variable binding system, allowing users to define template variables that map to ZPL ^FN slots. Key changes include the addition of a dedicated Variables panel in a restructured right sidebar, logic for synchronizing variable defaults with canvas objects, and enhanced ZPL parsing and generation to support round-tripping of template fields. Feedback focuses on ensuring runtime stability by providing fallbacks for crypto.randomUUID() in non-secure contexts, correcting a Tailwind CSS typo, and improving local state synchronization in the UI components to better handle external store updates like Undo/Redo.
Two style fixes folded together:
- em-dashes in new comments and UI strings replaced with colon/semicolon
per repo writing-style convention; the Variable dropdown options now
read `{name}: "{default}"`.
- row-error cleanup uses rest-spread destructure instead of a manual
rebuild loop (Gemini PR-review suggestion #4). `void _drop`
satisfies the unused-var lint.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.