Skip to content

feat(export): add separate AML/PNG label export flows and improvements#860

Open
akira69 wants to merge 19 commits into
Donkie:masterfrom
akira69:feat/aml-export-upstream-split
Open

feat(export): add separate AML/PNG label export flows and improvements#860
akira69 wants to merge 19 commits into
Donkie:masterfrom
akira69:feat/aml-export-upstream-split

Conversation

@akira69
Copy link
Copy Markdown

@akira69 akira69 commented Feb 24, 2026

Summary

This PR delivers dedicated label export flows for spool and filament labels, including AML/PNG export options and export-specific UX.

Why This Matters

  • Keeps export behavior explicit and separate from print-only behavior.
  • Adds practical export controls (ZIP packaging, filename templates, DPI/options).
  • Makes exported filenames predictable, configurable, and visible before export.

Code Quality Compliance ✅

Per https://github.com/Donkie/Spoolman/wiki/Contribute#style

  • ESLint (frontend) ✓ Fixed: control character regex
  • Prettier (frontend) ✓ Fixed: 7 files formatting
  • Ruff (backend) ✓
  • Pre-commit hooks validated ✓

Code Quality Improvements 🔧

Performance Optimizations Applied:

  • Added useCallback memoization to filament selector callbacks (filamentSelectModal.tsx):
    • selectUnselectFiltered, handleSelectItem, handlePageChange, handlePageSizeChange
    • Prevents unnecessary re-renders of child components (pagination, checkboxes, buttons)
    • Stable function references persist across renders
  • Added useCallback memoization to spool selector callbacks (spoolSelectModal.tsx):
    • selectUnselectFiltered, handleSelectItem
    • Improves React.memo effectiveness for table and checkbox components
    • Consistent optimization pattern across both selection modals

Alignment with PR 846:

React Immutability Fixes Applied:

  • Replaced prop mutations with spread copies in qrCodeExportDialog.tsx (×5 sites)
  • Pattern: curPreset.field = value; updateCurrentPreset(curPreset)updateCurrentPreset({ ...curPreset, field: value })

Module-Scope Extraction (exportDialog):

  • Moved 9 PNG binary helpers (pngSignature, readUint32BE, writeUint32BE, isPng, getChunkType, getCrc32Table, crc32, createPngChunk, setPngDpiMetadata) from inside the ExportDialog component body to module scope
  • These pure functions have no dependency on component state; recreating them on every render was wasteful
  • getCrc32Table lazy-init IIFE renamed to CRC32_TABLE immediate IIFE — table computed once unconditionally at module load

What Changed

Export flow and routing

  • Adds dedicated export routes/pages for spool and filament label export flows.
  • Hooks export actions into the selection flow without changing selection-table UX behavior.
  • Keeps return URL handling intact for selection → export → back navigation.

Export capabilities

  • AML export path supports ZIP packaging.
  • PNG export supports DPI/layout export options.
  • Configurable filename templates for exported outputs.
  • Duplicate filenames in one export are auto-suffixed (01, 02, 03, ...)
  • Filename template control is now placed with export-format controls.
  • Preview pane now shows per-item output filenames.

UI wording consistency

  • Entity-specific titles:
    • Export Filament Labels
    • Export Spool Labels

Screenshots

The selection window with separate export labels button

  • Specific wording for spools
image image

The new export Label page with AML export added and DPI selection for PNG

image

"Export as .zip" for both PNG and AML exports (one file per label inside the archive).

image

ZIP name to be exported shown :

  • PNG spool labels.zip / AML spool labels.zip
  • PNG filament labels.zip / AML filament labels.zip
image

Configurable individual label filename template

  • Specific for spool and filament exports under the format/options area
  • Default Spool: {filament.vendor.name}-{filament.material}-{filament.name}-{id}
  • Default Filament: {vendor.name}-{material}-{name}
  • Tooltip references template usage + duplicate suffix behavior.
image

Latest Fixes

  • Export and print dialogs now handle older base_url settings without crashing when the value was stored as a plain string.
  • Export state loading now tolerates the legacy string value "undefined" in saved local storage instead of crashing the export page.
  • If a saved preset was deleted, the dialog now falls back to the first valid preset instead of getting stuck in a broken temporary state.
  • Template value replacement now uses the same safer handling already applied in feat(printing): filament label printing foundation #846.
  • The filament label/export selector now uses a single debounced ?search= API call (from feat(printing): filament label printing foundation #846), so full-dataset vendor and material searches work in the export flow too.

Manual Verification Completed

  • ✓ Verified locally on March 27, 2026 via Playwright at http://127.0.0.1:9860.
  • ✓ Opened spool and filament export flows and confirmed the explicit page titles render correctly.
  • ✓ Set a legacy plain-string base_url value and confirmed both export and settings pages still load without crashing.
  • ✓ Set legacy saved-state data to the string "undefined" and confirmed the export page recovers instead of failing during initialization.
  • ✓ Deleted or invalidated the remembered preset, reopened the dialog, and confirmed it falls back to the first real saved preset.
  • ✓ Searched the filament selector for vendor/material values that were not on page 1 and confirmed the single debounced ?search= flow returned full-dataset matches.
  • ✓ Ran spool and filament AML exports, PNG exports, and ZIP exports and confirmed previews and filenames matched the generated files.
  • ✓ Verified duplicate export filenames are auto-suffixed (01, 02, ...) and custom filename templates persist and reload correctly.

Testing Performed

  • Frontend production build: ✓
  • Targeted frontend lint on touched files: ✓
  • Local branch image rebuilt and deployed for validation: ✓
  • Playwright browser validation against http://127.0.0.1:9860: ✓
  • Exported PNG at default and custom DPI and confirmed output dimensions changed as expected: ✓
  • Exported AML files and ZIP packages for both spool and filament flows: ✓
  • Verified filename templating, duplicate suffixing, selector pagination, preset persistence, and legacy setting recovery paths: ✓

Test Checklist

  • Export Spool Labels page title displays correctly
  • Export Filament Labels page title displays correctly
  • PNG export generates valid images at default and custom DPI
  • AML export generates valid AML files usable in Labelife
  • ZIP packaging works for both PNG and AML exports
  • Filename template applies correctly to exports (matches preview)
  • Duplicate filenames get auto-suffixed (01, 02, 03...) in exports
  • Dialog handles older base_url format without errors or crashes
  • Filament selector pagination responds smoothly with callback memoization
  • Spool selector checkbox interactions remain responsive
  • Both modals render without console warnings related to callback changes

Merge Notes (Combined-Branch Integration) 🔀

Commit fba5e5d pre-patches four conflict hotspots discovered during combined-branch integration:

  • useSetPrintSettings — returns the raw useSetSetting<>() mutation object directly; fixes .mutate() call patterns expected by callers in combined branches
  • Dialog interfaces (QRCodePrintingDialog, QRCodeExportDialog) — extraTitleSettings?, extraInfoSettings?, extraExportSettings? props added with render sites; allows filament-specific settings rows to be injected from callers in other PRs without conflict
  • FilamentSelectModalonPrint made optional, onExport? + initialSelectedIds? added, conditional Export Labels button added; aligns with SpoolSelectModal pattern already in this PR
  • otherModels.tsxuseSimpleSortedArrayQuery factory added, 4 verbose hooks refactored to 1-liners; identical to feat(ui/filters): searchable filter dropdown + bulk selection + query responsiveness #862's approach, so the two PRs auto-merge on this file instead of conflicting

Merge order: after #846, before #862.

Database Impact

  • None.
  • No migration, schema, or index changes.

Scope Mapping

PR Chain / Dependencies

  1. Base dependency: #846 (required).
  2. This PR is the export layer in the chain.
  3. #857 (logo core) is stacked on top of this PR.
  4. #872 (logo GitHub sync) is stacked on top of #857.
  • The intended stacking order to get the full feature set of label improvements and logos is :
  1. feat(printing): filament label printing foundation #846
  2. feat(manufacturers): **Logos for vendors** logo-aware UI and label workflows (core, no bundled assets) #857
  3. feat(export): add separate AML/PNG label export flows and improvements #860
  4. feat(vendors/logos): add GitHub logo-pack sync workflow (no bundled assets) #872

@akira69 akira69 force-pushed the feat/aml-export-upstream-split branch 2 times, most recently from 8663e53 to c6e4217 Compare February 26, 2026 16:31
@akira69 akira69 marked this pull request as ready for review March 2, 2026 05:19
@akira69 akira69 closed this Mar 2, 2026
@akira69 akira69 reopened this Mar 2, 2026
@akira69 akira69 changed the title feat(export): AML/PNG label export flows and print/export UX split feat(export): add separate AML/PNG label export flows and improvements Mar 2, 2026
@akira69 akira69 force-pushed the feat/aml-export-upstream-split branch from 0f24dbe to 32d1e50 Compare March 10, 2026 12:37
@akira69 akira69 force-pushed the feat/aml-export-upstream-split branch 2 times, most recently from 4e38030 to 6e733a8 Compare March 13, 2026 22:11
@akira69 akira69 marked this pull request as draft March 14, 2026 07:01
@akira69 akira69 marked this pull request as ready for review March 15, 2026 04:46
@akira69 akira69 marked this pull request as draft March 15, 2026 14:20
@akira69 akira69 marked this pull request as ready for review March 16, 2026 01:33
- Extract _build_search_filters() helper to reduce McCabe complexity
- Reduces find() complexity from 13 → ~8 (under 10 limit)
- Reduces branch count from 13 → acceptable levels
- Resolves Ruff linting errors C901 + PLR0912
- Pure refactoring: behavior unchanged
- Also fixes Prettier formatting on filamentSelectModal and qrCodePrintingDialog
@akira69 akira69 force-pushed the feat/aml-export-upstream-split branch 2 times, most recently from 1e0164d to e699666 Compare March 16, 2026 20:47
- Wrap selectUnselectFiltered, handleSelectItem callbacks in useCallback
- Wrap handlePageChange, handlePageSizeChange, applySearchFilter in useCallback
- Reduces unnecessary re-renders of child components (pagination, checkboxes, buttons)
- Stable function references now persist across renders
akira69 added 8 commits March 16, 2026 21:33
…ack PR773)

- Fetch extra field definitions once at endpoint entry instead of per-request
- Eliminates redundant database calls for extra field validation
- Applies feedback from PR 773: 'get_extra_fields only needs to be run max at once per call'
- Affected endpoints: create/update for filament, spool, and vendor resources
- Reduces API layer complexity and improves performance
(cherry picked from commit dc11b7bdae1777a70a68bedfe503f988e96acd13)
…ase URL handling

Harden QR export base URL parsing to prevent template injection attacks

Per https://github.com/Donkie/Spoolman/wiki/Contribute#style
- ESLint (frontend) ✓ Fixed: control character regex
- Prettier (frontend) ✓ Fixed: 7 files formatting
- Ruff (backend) ✓
- Pre-commit hooks validated ✓
- Wrap selectUnselectFiltered, handleSelectItem callbacks in useCallback
- Reduces unnecessary re-renders of table and checkboxes
- Consistent with filament selector optimization pattern
- Stable function references improve React.memo effectiveness
@akira69 akira69 force-pushed the feat/aml-export-upstream-split branch from e699666 to e81b670 Compare March 17, 2026 02:54
- Use single debounced ?search= call (mirrors spool API / PR846 approach)
- Remove 4-field parallel fetch loop, getAPIURL import, allSearchResults state
- API ?search= param was already present via 'Carry filament search foundation'
@akira69 akira69 force-pushed the feat/aml-export-upstream-split branch from cff78ed to 6df5e78 Compare March 17, 2026 13:16
akira69 added 4 commits March 17, 2026 16:58
- useSetPrintSettings returns mutation object directly (fixes combined-branch .mutate() calls)
- All four dialog callers updated to use setRemotePresets.mutate()
- QRCodePrintingDialog/QRCodeExportDialog: add extraTitleSettings, extraInfoSettings, extraExportSettings props
- FilamentSelectModal: add optional onExport, initialSelectedIds props + Export button (matches SpoolSelectModal pattern)
- otherModels: add useSimpleSortedArrayQuery factory, refactor 4 verbose hooks to 1-liners (matches PR862 approach)
…to module scope

- qrCodeExportDialog: 5 mutation sites replaced with spreads
- exportDialog: move pngSignature, readUint32BE, writeUint32BE, isPng, getChunkType,
  CRC32_TABLE (was getCrc32Table IIFE), crc32, createPngChunk, setPngDpiMetadata
  to module scope — helpers are now allocated once per page load, not per render
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