Browse Source

Fix virtual printer queue mode not assigning printer (#518), settings text fields resetting mid-typing

Virtual printer "print_queue" mode created queue items with no printer
assignment. Now sets target_model from the VP's SSDP model code (e.g.
P1S, X1C) for "Any Printer" scheduling, or uses target_printer_id if
configured.

Settings page auto-save onSuccess overwrote localSettings with the
server response, discarding characters typed during the save request.
Removed the stale state overwrite so in-progress input is preserved.
maziggy 3 months ago
parent
commit
1973a5bead

+ 2 - 0
CHANGELOG.md

@@ -11,6 +11,8 @@ All notable changes to Bambuddy will be documented in this file.
 - **Created Admin Users Can't See Settings Button** ([#503](https://github.com/maziggy/bambuddy/issues/503)) — The sidebar hid the Settings link based on a hardcoded `role === 'user'` check instead of the actual `settings:read` permission, so newly created admin users who had the permission still couldn't see the button. Also, after login the auth state was set directly from the login response instead of re-fetching the full auth status, which could miss permission data. Now uses `hasPermission('settings:read')` for the sidebar check and calls `checkAuthStatus()` after login to load the complete user state including permissions.
 - **"Open in Slicer" Fails for Filenames Containing Special Characters** — Filenames with `/`, `\`, `?`, or `#` (e.g., `Abzweigdose/Verteilerdose 70mm`) caused the slicer protocol handler to fail. The filename is placed in the download URL path and `encodeURIComponent`-encoded, but BambuStudio and OrcaSlicer call `url_decode()` on the entire protocol handler URL before downloading. This decoded `%2F` back to `/`, creating extra path segments that resulted in a 404. The URL filename is purely cosmetic (the backend resolves files by archive ID, not filename), so now sanitizes `/`, `\`, `?`, and `#` to `_` in slicer download URLs.
 - **"Queue to Any Printer" Ignores Filament Color Override** ([#486](https://github.com/maziggy/bambuddy/issues/486)) — When scheduling a print to "any printer" with a filament color override, the scheduler picked a printer with the correct filament type but wrong color. `_find_idle_printer_for_model()` validated only filament type (via `_get_missing_filament_types()`), while color matching (`_count_override_color_matches()`) was used only for ranking candidates, not filtering them. A printer with 0 color matches was still selected if it had the right types. Now requires at least 1 color match when filament overrides specify colors — printers with 0 matches are skipped and added to the "waiting for filament" reason instead of being treated as valid candidates.
+- **Virtual Printer Queue Mode Doesn't Assign Printer** ([#518](https://github.com/maziggy/bambuddy/issues/518)) — Files sent to a virtual printer in "print queue" mode were added to the queue with no printer assigned, requiring manual assignment. The `_add_to_print_queue()` method always created queue items with `printer_id=None` and no `target_model`. Now assigns the virtual printer's `target_printer_id` if configured, or falls back to the VP's model (e.g., P1S, X1C) as `target_model` for "Any Printer" scheduling.
+- **Settings Text Fields Reset While Typing** — Text input fields on the Settings page (MQTT broker hostname, HA URL, tokens, etc.) reset mid-typing because the auto-save `onSuccess` handler overwrote `localSettings` with the server response, discarding characters typed during the save request. Removed the stale state overwrite so in-progress user input is preserved.
 
 ## [0.2.1b3] - 2026-02-23
 

+ 10 - 2
backend/app/services/virtual_printer/manager.py

@@ -99,6 +99,7 @@ class VirtualPrinterInstance:
         serial_suffix: str,
         target_printer_ip: str = "",
         target_printer_serial: str = "",
+        target_printer_id: int | None = None,
         bind_ip: str = "",
         remote_interface_ip: str = "",
         base_dir: Path,
@@ -112,6 +113,7 @@ class VirtualPrinterInstance:
         self.serial_suffix = serial_suffix
         self.target_printer_ip = target_printer_ip
         self.target_printer_serial = target_printer_serial
+        self.target_printer_id = target_printer_id
         self.bind_ip = bind_ip
         self.remote_interface_ip = remote_interface_ip
         self._session_factory = session_factory
@@ -274,7 +276,7 @@ class VirtualPrinterInstance:
             logger.error("Error queueing file: %s", e)
 
     async def _add_to_print_queue(self, file_path: Path, source_ip: str) -> None:
-        """Archive file and add to print queue (unassigned)."""
+        """Archive file and add to print queue, assigned to target printer or model."""
         if not self._session_factory:
             logger.error("Cannot add to print queue: no database session factory configured")
             return
@@ -304,8 +306,13 @@ class VirtualPrinterInstance:
                 )
                 if archive:
                     logger.info("[VP %s] Archived: %s - %s", self.name, archive.id, archive.print_name)
+                    # Assign to specific printer if configured, otherwise use model for "Any X" scheduling
+                    target_model = None
+                    if not self.target_printer_id and self.model:
+                        target_model = VIRTUAL_PRINTER_MODELS.get(self.model)
                     queue_item = PrintQueueItem(
-                        printer_id=None,
+                        printer_id=self.target_printer_id,
+                        target_model=target_model,
                         archive_id=archive.id,
                         position=1,
                         status="pending",
@@ -631,6 +638,7 @@ class VirtualPrinterManager:
                     model=vp.model or DEFAULT_VIRTUAL_PRINTER_MODEL,
                     access_code=vp.access_code or "",
                     serial_suffix=vp.serial_suffix,
+                    target_printer_id=vp.target_printer_id,
                     bind_ip=vp.bind_ip or "",
                     remote_interface_ip=vp.remote_interface_ip or "",
                     base_dir=self._base_dir,

+ 5 - 3
frontend/src/pages/SettingsPage.tsx

@@ -654,9 +654,11 @@ export function SettingsPage() {
     mutationFn: api.updateSettings,
     onSuccess: (data) => {
       queryClient.setQueryData(['settings'], data);
-      // Sync localSettings with the saved data to prevent re-triggering saves
-      setLocalSettings(data);
-      // Invalidate archive stats to reflect energy tracking mode change
+      // Don't call setLocalSettings(data) here — it would overwrite in-progress
+      // user input (e.g. typing a hostname) with the stale saved snapshot,
+      // causing the text field to reset mid-typing. Instead, let the useEffect
+      // re-compare the updated `settings` with current `localSettings` and
+      // debounce-save any remaining differences.
       queryClient.invalidateQueries({ queryKey: ['archiveStats'] });
       showToast(t('settings.toast.settingsSaved'), 'success');
     },

File diff suppressed because it is too large
+ 0 - 0
static/assets/index-DXDq-oI9.js


+ 1 - 1
static/index.html

@@ -23,7 +23,7 @@
 
     <!-- Splash screens for iOS -->
     <link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
-    <script type="module" crossorigin src="/assets/index-CIrY3KrR.js"></script>
+    <script type="module" crossorigin src="/assets/index-DXDq-oI9.js"></script>
     <link rel="stylesheet" crossorigin href="/assets/index-1Ts9jjQl.css">
   </head>
   <body>

Some files were not shown because too many files changed in this diff