Skip to content

feat(parser): ~DY+^XG upload/recall round-trip for graphics#84

Merged
u8array merged 2 commits into
mainfrom
feat/dy-download-graphic
May 21, 2026
Merged

feat(parser): ~DY+^XG upload/recall round-trip for graphics#84
u8array merged 2 commits into
mainfrom
feat/dy-download-graphic

Conversation

@u8array
Copy link
Copy Markdown
Owner

@u8array u8array commented May 21, 2026

No description provided.

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).
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/lib/zplParser.ts Outdated
Comment thread src/lib/zplGenerator.ts Outdated
…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 u8array merged commit 3e9a321 into main May 21, 2026
2 checks passed
@u8array u8array deleted the feat/dy-download-graphic branch May 21, 2026 20:44
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant