Ver Fonte

Add clear-plate dot indicator on sidebar Printers icon

When a printer has pending queue items and is in FINISH/FAILED state
awaiting plate clearing, a yellow dot now appears on the Printers
sidebar icon. Uses useQueries to check statuses of printers with
pending queue items from the existing WebSocket-warmed React Query
cache — no new backend endpoints or additional polling needed.
maziggy há 3 meses atrás
pai
commit
77677cb07f

+ 1 - 0
CHANGELOG.md

@@ -42,6 +42,7 @@ All notable changes to Bambuddy will be documented in this file.
 - **Developer LAN Mode Detection & Warning Banner** — Automatically detects whether connected printers have Developer LAN Mode enabled by parsing the MQTT `fun` field (bit `0x20000000`). When any connected printer lacks developer mode, a persistent orange warning banner appears at the top of the UI with the affected printer name(s) and a link to Bambu Lab's documentation on how to enable it. Without developer mode, MQTT write operations (start/stop/pause prints, AMS control, light/speed/gcode commands) are silently rejected by newer firmware. The `developer_mode` state is included in the support bundle for diagnostics. New `/printers/developer-mode-warnings` endpoint provides a lightweight polling summary. Translations added for all 6 locales (en, de, fr, it, ja, pt-BR).
 
 ### Improved
+- **Clear Plate Dot Indicator on Sidebar** — When the print queue is active and a printer finishes or fails with a pending next job, a small yellow dot now appears on the Printers sidebar icon to signal that user action (clearing the build plate) is needed. The indicator reuses the existing WebSocket-driven printer status cache, so no additional API polling is required. The dot disappears once the plate is cleared or the queue empties.
 - **Filament Override Test Coverage** — Added 11 backend unit tests: 6 for `_count_override_color_matches` (no status, exact match, no match, partial match, color normalization, external spool) and 5 for override application in filament matching (color override, tray_info_idx clearing, type change, partial override, nozzle filtering with override). Added 12 frontend tests for the `FilamentOverride` component: 5 rendering tests (null guards, slot display, dropdown count), 2 type filtering tests (same-type only, all colors), 3 nozzle filtering tests (extruder_id matching, single-nozzle passthrough, null extruder_id inclusion), and 2 interaction tests (select override, reset to original).
 - **P2S Dual-AMS tray_now Test Coverage** — Added 14 integration tests for multi-AMS tray_now disambiguation on single-nozzle printers (resolving AMS-B slots via mapping field, AMS-A passthrough, multi-color mapping, ambiguous/missing mapping fallbacks, last_loaded_tray tracking). Added 9 unit tests for `_resolve_local_slot_from_mapping` (snow decoding, unmapped entry filtering, ambiguity detection, AMS-HT slot matching). All 66 tray_now-related tests pass.
 - **Bulk Spool, Stock & Grouping Test Coverage** — Added 13 backend unit tests covering `SpoolBulkCreate` schema validation (quantity bounds, field preservation, stock vs configured distinction) and bulk endpoint logic (correct spool count, single quantity, identical fields). Added 29 frontend tests: 13 for `SpoolFormModal` covering `validateForm` with `quickAdd` flag (6 tests), quick-add toggle visibility, PA Profile tab hiding, quantity field gating (hidden by default, visible only in quick-add, hidden in edit mode), and brand/subtype optional asterisk removal in quick-add; 16 for inventory grouping logic covering `spoolGroupKey` identity/differentiation (7 tests) and `computeDisplayItems` grouping rules (9 tests for identical/different/used/assigned/single/order/mixed/empty scenarios).

+ 28 - 1
frontend/src/components/Layout.tsx

@@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next';
 import { useTheme } from '../contexts/ThemeContext';
 import { KeyboardShortcutsModal } from './KeyboardShortcutsModal';
 import { SwitchbarPopover } from './SwitchbarPopover';
-import { useQuery } from '@tanstack/react-query';
+import { useQuery, useQueries } from '@tanstack/react-query';
 import { api, supportApi, pendingUploadsApi } from '../api/client';
 import { getIconByName } from './IconPicker';
 import { useIsSidebarCompact } from '../hooks/useIsSidebarCompact';
@@ -178,6 +178,29 @@ export function Layout() {
   });
   const pendingUploadsCount = pendingUploadsData?.count ?? 0;
 
+  // Check if any printer with pending queue items needs plate clearing
+  const queuePrinterIds = useMemo(() => {
+    const ids = new Set<number>();
+    queueItems?.forEach(item => {
+      if (item.printer_id) ids.add(item.printer_id);
+    });
+    return Array.from(ids);
+  }, [queueItems]);
+
+  const printerStatusQueries = useQueries({
+    queries: queuePrinterIds.map(id => ({
+      queryKey: ['printerStatus', id],
+      queryFn: () => api.getPrinterStatus(id),
+      staleTime: 30 * 1000, // WebSocket keeps this warm
+    })),
+  });
+
+  const needsClearPlate = printerStatusQueries.some(result => {
+    const status = result.data;
+    if (!status) return false;
+    return (status.state === 'FINISH' || status.state === 'FAILED') && !status.plate_cleared;
+  });
+
   // Calculate debug duration client-side for real-time updates
   const [debugDuration, setDebugDuration] = useState<number | null>(null);
   useEffect(() => {
@@ -532,6 +555,7 @@ export function Layout() {
                 const showArchiveBadge = id === 'archives' && pendingUploadsCount > 0;
                 const badgeCount = showQueueBadge ? pendingQueueCount : showArchiveBadge ? pendingUploadsCount : 0;
                 const showBadge = showQueueBadge || showArchiveBadge;
+                const showClearPlateDot = id === 'printers' && needsClearPlate;
 
                 return (
                   <li
@@ -566,6 +590,9 @@ export function Layout() {
                       )}
                       <div className="relative">
                         <Icon className="w-5 h-5 flex-shrink-0" />
+                        {showClearPlateDot && (
+                          <span className="absolute -top-0.5 -right-0.5 w-2.5 h-2.5 bg-yellow-500 rounded-full border-2 border-bambu-dark-secondary" />
+                        )}
                         {showBadge && (
                           <span className={`absolute -top-1.5 -right-1.5 min-w-[18px] h-[18px] px-1 flex items-center justify-center text-[10px] font-bold rounded-full ${
                             showArchiveBadge ? 'bg-blue-500 text-white' : 'bg-yellow-500 text-black'

Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
static/assets/index-COFMpymr.css


Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
static/assets/index-Cxub00Jo.js


Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
static/assets/index-D40rj90s.css


+ 2 - 2
static/index.html

@@ -23,8 +23,8 @@
 
     <!-- Splash screens for iOS -->
     <link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
-    <script type="module" crossorigin src="/assets/index--GSGtn_x.js"></script>
-    <link rel="stylesheet" crossorigin href="/assets/index-D40rj90s.css">
+    <script type="module" crossorigin src="/assets/index-Cxub00Jo.js"></script>
+    <link rel="stylesheet" crossorigin href="/assets/index-COFMpymr.css">
   </head>
   <body>
     <div id="root"></div>

Alguns ficheiros não foram mostrados porque muitos ficheiros mudaram neste diff