The app was built dark-first, so hundreds of hardcoded Tailwind semantic
text/icon utilities at light shades (text-amber-400, text-blue-300, ...) had
no dark: variant. With darkMode:'class' they applied in light theme too,
producing washed-out text on pale tints and white cards — including the three
reported spots (AMS Drying banner, Archives no-3MF warning, debug-logging
banner). Give each a theme-aware pair: a darker readable shade in light theme
with the original pinned to dark:, so dark theme is unchanged. ~100 files.
The bambu-* CSS-variable palette (self-correcting) and the dark-only SpoolBuddy
kiosk are left untouched. Plain text-white is already theme-aware via the
existing index.css .text-white override, so it needed no changes.
Adds page-wide drag-and-drop file upload to the File Manager
tab — drop any file anywhere on the page and the upload modal
opens pre-populated with the dropped files. The Upload Files
button still works for click-to-browse.
Also fixes the Archives drag-cancel bug @maikolscripts reported
in the same issue: cancelling a drag (drag back outside the
browser, Escape mid-drag, or release outside the page) used to
leave the overlay stuck until page refresh.
Both pages share a new hook usePageFileDrop. The fix:
- relatedTarget containment check (catches drag-out-of-window)
- document-level drop / dragend / keydown(Escape) listeners
that only register while isDraggingOver === true, so the
three cancel paths all reset uniformly
FileUploadModal gains an optional initialFiles prop so the
File Manager page can pre-seed the modal from a page-wide
drop. seededInitialRef guards against re-adding on re-renders.
Permission gate: File Manager drop zone disabled when the
user lacks library:upload, so a viewer-tier user doesn't get
a misleading overlay.
Reporter @iitazz uploaded slicer output to Bambuddy, clicked Print,
and the printer rejected every job with "Printing stopped because
the printer was unable to parse the 3mf file". Support bundle showed
the stored library file ended in .gcode (not .gcode.3mf), and
background_dispatch.py appends ".3mf" to filenames that don't
already end in .gcode.3mf/.3mf — so raw gcode shipped to the printer
named .gcode.3mf and the firmware's 3MF parser choked. Same shape
also surfaced as "File is not a zip file" on Bambuddy's own plate
parser.
New validate_print_file_upload() helper in library.py runs at upload
time:
- Reject filenames ending in .gcode (but not .gcode.3mf) with a
clear message — Bambu printers need .gcode.3mf zip containers,
not raw gcode.
- For .3mf / .gcode.3mf uploads, verify body starts with PK\x03\x04
(ZIP magic); reject otherwise pointing at the slicer's "Export
Plate Sliced File" action.
Applied to every relevant upload route: POST /library/files (covers
File Manager + printer-card drag-drop), POST /archives/upload,
POST /archives/upload-bulk (rejects per-row so one bad file doesn't
abort the batch), POST /archives/{id}/source, POST /archives/upload-source.
Runs after _resolve_upload_destination so folder-permission errors
(403 readonly, 400 missing-path, 409 collision) still take precedence.
STL / image / other non-print uploads bypass the validator.
FileUploadModal frontend fix: the modal auto-closed after every
batch regardless of per-file results, so a 400 rejection was captured
but invisible. Now:
- Errors render inline as red text under the file row instead of
as a hover-only title tooltip.
- Modal stays open if any file ended with status='error', so the
user can read the backend's remediation message before closing.
- Successful-only batches still auto-close as before.
UploadModal (bulk archive) was already showing inline errors and
not auto-closing — no change needed there.
* Add printer card print flow with drag-drop and reusable LibraryUploadModal
Extract inline upload modal from FileManagerPage into a reusable
LibraryUploadModal component. Add a Print button and drag-drop zone
to printer cards that upload files to the library and open PrintModal
with the printer pre-selected. Only .gcode/.gcode.3mf files are
accepted for printing. PrintModal hides printer selector when a
printer is provided via initialSelectedPrinterIds prop.
* Add printer compatibility check for drag-drop and upload-to-print flows
Validates sliced_for_model from file metadata against the target printer model
before opening the print modal, preventing users from printing with incompatible files.
* Improve FileUploadModal: auto-close on success, inline errors, file validation, and i18n
- Close modal automatically after successful upload instead of showing summary
- Show printer compatibility errors inline in the modal instead of closing
- Add validateFile and accept props to restrict file types for print flow
- Add onFileUploaded error return to prevent modal close on incompatible files
- Internationalize all hardcoded error/warning strings across 6 locales
* Clean up incompatible files from library and fix print button i18n
- Delete uploaded file from library when printer compatibility check fails
in both drag-drop and modal upload flows
- Fix print button tooltip to use existing common.print i18n key
- Fix accept attribute to use .gcode,.3mf for proper browser support
* Add printer information modal accessible from card 3-dot menu
Replace inline IP/serial display at bottom of printer card with a
dedicated "Printer Information" modal opened via the card menu. The
modal shows model, status, state, IP address (copyable), serial number
(copyable), WiFi signal, firmware, developer mode, nozzle count, SD
card, auto-archive, total print hours, location, and added date, along
with the printer image. Includes full i18n support across all 6 locales
and accessibility attributes (role="dialog", aria-modal).
* Extract getPrinterImage and getWifiStrength into shared printer utils
---------
Co-authored-by: MartinNYHC <mz@v8w.de>