feat(parser): ~DY+^XG upload/recall round-trip for graphics#84
Merged
Conversation
Enterprise-Labels nutzen häufig ein Logo-Sharing-Pattern: einmal via
~DY auf die Drucker-Storage hochladen, dann pro Label-Instanz via ^XG
referenzieren. Im aktuellen Stand landeten beide Commands hart im
browserLimit; importierte Labels verloren ihre Bilder, Re-Export
funktionierte nicht.
Neue Pipeline:
- ImageProps.storedAs: { device, name } markiert ein Image als 'lebt in
der Drucker-Storage'. Vom Parser bei ^XG gesetzt, vom Emitter als
Trigger für DY+XG-Output gelesen. Kein UI-Toggle in diesem PR.
- ~DY-Handler erweitert um Graphics-Pfade (extCode G mit Format A/B/C).
Decodiert via gfPayloadToBytes — derselbe Dispatcher wie ^GF, also
inklusive :B64:/:Z64: und CRC-Validation. Registriert im
downloadedGraphics-Map keyed auf den vollen 'device:name.GRF'-Pfad.
- ^XG-Handler schaut den Pfad nach und instanziiert ein Image-Objekt
mit storedAs. Pfad nicht gefunden → browserLimit.
- generator emittiert ein ~DY pro eindeutigem storedAs-Pfad als
Preamble vor ^XA, image.toZPL emittiert dann ^XG statt ^GFA.
Deduplicated über alle Pages.
Shared infrastructure die im Prozess entstanden ist:
- decodeGraphicToImage: gemeinsamer Decoder + Canvas-Painter + Cache-
Write für ^GF und ~DY (vorher ~50 Zeilen Duplikat).
- src/lib/storagePath.ts: parseStoragePath / formatStoragePath
zentralisiert die 'device:name[.GRF]'-Form-Konvertierung.
Inkl. eigener Unit-Tests.
- _gfaCache: format-letter-preserving (^GF{format} statt hardcoded
^GFA), damit ein :Z64:-Payload nicht beim Re-Export auf invalides
^GFA,…,:Z64:… mutiert.
7 neue Tests (3 Parser, 3 Generator, 6 Storage-Path).
There was a problem hiding this comment.
Code Review
This pull request implements support for Zebra storage paths, enabling images to be uploaded once using the ~DY command and subsequently recalled using ^XG. Key changes include the addition of a storagePath utility for parsing and formatting device paths, updates to the ZPL parser to maintain a registry of uploaded graphics, and modifications to the generator to emit ~DY preambles and ^XG recall commands. Review feedback identifies a potential lookup failure in the parser due to unnormalized paths and suggests relaxing a regular expression in the generator to accommodate optional numeric parameters in cached graphic data.
…headers - ^XG ohne .GRF-Suffix ist gültiges ZPL (Labelary akzeptiert ^XGR:LOGO,1,1 für ein Upload R:LOGO.GRF). Der Map-Lookup nutzte den raw-string und scheiterte. Geht jetzt durch parseStoragePath + formatStoragePath, sodass beide Formen auf den kanonischen Key normalisiert werden. Path-Parse und Lookup als getrennte Guards mit je eigenem early-return — keine redundante ||-Logik. - formatGraphicUpload-Regex war (\d+) für die optionalen byte-count Header — Spec erlaubt aber leere Werte. Zu (\d*) gelockert.
u8array
added a commit
that referenced
this pull request
May 21, 2026
Macht das ~DY+^XG-Feature aus PR #84 für neue Bilder im Designer bedienbar — bisher griff es nur beim Import existierender Labels mit upload+recall. Properties-Panel-Section am Ende der Image-Properties, mit konsistentem 'Drucker-Speicher'-Header (Info-Icon, border-t-Trennung) der in beiden States sichtbar ist: - Off-State: 'Aktivieren'-Button. Toggle-on setzt storedAs auf { device: 'R', name: 'IMG_xxxx' } mit auto-generiertem 4-char-UUID. - On-State: Device-Dropdown (R/E/B/A mit Klartext), Name-Input (max 8 Zeichen, auto-uppercase, [A-Z0-9_]-Filter, empty-Guard gegen broken ZPL), Path-Preview, 'Bytes mitsenden'-Checkbox + Hint (entkoppelt Upload von Recall: off = nur ^XG, on = ~DY+^XG), 'Inline einbetten'-Button als Toggle-off. Parser ergänzt: ^XG ohne preceding ~DY ist jetzt valider Import (recall-only). Erzeugt Image-Objekt mit storedAs.embedInZpl=false und ohne gecachte Bytes; partial-Finding flagged die degradierte Preview. Image-Cache löschen: Trash-Button neben Bildquelle-Dropdown öffnet einen ConfirmDialog (destructive, autoFocus auf Cancel) der erklärt dass das Bild für ALLE referenzierenden Etiketten verschwindet. Vorher konnte man nur das Image-Objekt löschen (Del-Taste), nicht die Bytes selbst. Canvas-Resize via Griffe: image-Registry hatte kein commitTransform — Konva applied sx/sy als visual scale, aber nichts übersetzte das in widthDots. Jetzt: cached PNG = aspect-locked via dominantem Axis- Drag (Math.abs-Heuristik damit alle 8 Handles inward+outward funktionieren); kein Cache (Placeholder) = free-form via heightDots sodass der User die Box als Layout-Platzhalter shapen kann. Empty-Source-Selection: handleImageSelect hatte early-return bei leerem imageId. Die 'Bild auswählen…'-Placeholder-Option war funktional tot. Jetzt clearet sie imageId + _gfaCache. Canvas-Placeholder: 🖼-Emoji ersetzt durch Konva.Path mit dem Heroicons 'photo'-outline. Emoji-Rendering war OS-abhängig. Shared infrastructure die im Prozess entstanden ist: - src/lib/storagePath.ts erweitert um STORAGE_DEVICES, StorageDevice, MAX_STORAGE_NAME_LEN, STORAGE_NAME_FILTER_RE, defaultStorageName — alle Storage-Path-Begriffe in einer kanonischen Heimat. - src/components/Properties/styles.ts: neuer buttonCls für Secondary-Action-Buttons (Upload, Toggle, Inline einbetten), Tailwind-Duplikation eliminiert (vorher 4× derselbe String in image.tsx und text.tsx). ZPL-Codes (^XG, ~DY) ausschließlich in Tooltips, nicht in sichtbaren Labels. Neue Locale-Keys (storage, storeOnPrinter, storeOnPrinterHint, storeInline, embedInZpl, embedInZplHint, removeFromCache, removeFromCacheConfirm) über alle 32 Sprachen gepflegt.
u8array
added a commit
that referenced
this pull request
May 21, 2026
Makes the ~DY+^XG feature from PR #84 usable for new images in the designer — until now it only kicked in when importing existing labels that already used the upload+recall pattern. Properties-panel section at the end of the image properties, with a consistent 'Printer storage' header (info icon, border-t separator) visible in both states: - Off-state: 'Activate' button. Toggling on sets storedAs to { device: 'R', name: 'IMG_xxxx' } with an auto-generated 4-char UUID. - On-state: device dropdown (R/E/B/A as plain letters), name input (max 8 chars, auto-uppercase, [A-Z0-9_] filter, empty-guard against broken ZPL), path preview, 'Ship bytes' checkbox + hint (decouples upload from recall: off = ^XG only, on = ~DY+^XG), 'Embed inline' button as the toggle-off. Parser extension: ^XG without a preceding ~DY is now a valid import (recall-only). Creates an image object with storedAs.embedInZpl=false and no cached bytes; surfaced as a partial finding so the import report flags the degraded preview. Cache deletion: trash button next to the image-source dropdown opens a ConfirmDialog (destructive, autoFocus on Cancel) explaining that the bytes vanish for ALL labels referencing this image. Previously only the image *object* could be removed (Del key), not the cached bytes — so old uploads accumulated in localStorage with no UI to clean them up. Canvas resize via handles: the image registry had no commitTransform, so Konva applied sx/sy as a visual scale but nothing translated that back into widthDots — the only way to resize was the properties panel. Now: cached PNG = aspect-locked via the dominant-axis drag (Math.abs heuristic so all 8 handles work for both grow and shrink); no cache (placeholder) = free-form via heightDots so the user can shape the box as a layout placeholder. Empty source selection: handleImageSelect bailed early on an empty imageId, leaving the 'Select image…' placeholder option functionally dead. Now it clears imageId + _gfaCache, which is the right answer for recall-only setups where the user wants a storedAs path without local preview bytes. Canvas placeholder: 🖼 emoji replaced by a Konva.Path rendering the Heroicons 'photo' outline. The emoji rendered inconsistently across OSes (color on macOS, monochrome on Linux, missing on some Windows configurations); the vector path is deterministic. Shared infrastructure that fell out of the work: - src/lib/storagePath.ts extended with STORAGE_DEVICES, StorageDevice, MAX_STORAGE_NAME_LEN, STORAGE_NAME_FILTER_RE, defaultStorageName — all storage-path concepts living in one canonical place so the parser, emitter, and image registry stay in lockstep. - src/components/Properties/styles.ts: new buttonCls for secondary- action buttons (Upload, toggle, Embed inline), eliminating the four-times-repeated Tailwind string across image.tsx and text.tsx. ZPL codes (^XG, ~DY) are kept to tooltips only, never in user-visible labels. New locale keys (storage, storeOnPrinter, storeOnPrinterHint, storeInline, embedInZpl, embedInZplHint, removeFromCache, removeFromCacheConfirm) cover all 32 locales.
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.