Просмотр исходного кода

Size the H2C nozzle rack card to its contents and number its slots

    The rack card shared a row with the nozzle, bed and chamber readings but
    was set to flex: 2 1 190px -- a 190px floor plus twice their growth share
    -- to draw six fixed 28px chips. On anything wider than a compact card it
    claimed several hundred pixels and left most of them empty, and the width
    came out of the cards that needed it: the combined dual-nozzle reading was
    wrapping "220 / 220" onto two lines beside a mostly blank rack. It is now
    flex: 0 1 auto, so it takes its content width and gives the remainder
    back. Shrink stays enabled so it still gives way on a narrow card instead
    of overflowing.

    Each slot also carries its physical rack position 1-6 below the chip, so a
    nozzle can be named rather than counted along. The numbering is positional
    -- an empty slot keeps its number -- so "the nozzle in slot 4" means the
    same thing however many of the six are occupied.

    The #943 regression test read every span in the slot row, which the new
    number spans would have interleaved with the diameters; it now matches the
    diameter spans specifically. A second test pins the 1-6 labelling across a
    rack with empty positions.
maziggy 3 недель назад
Родитель
Сommit
1b944739d2

+ 25 - 1
frontend/src/__tests__/pages/PrintersPage.test.tsx

@@ -812,10 +812,34 @@ describe('PrintersPage', () => {
       const rackLabel = screen.getAllByText('Nozzle Rack')[0];
       const rackCard = rackLabel.parentElement!;
       const slotRow = rackCard.querySelectorAll('div.flex')[0];
-      const slotTexts = Array.from(slotRow.querySelectorAll('span')).map(s => s.textContent);
+      // Match the diameter spans specifically — each chip also carries a slot
+      // number span, and a bare `span` sweep would interleave the two.
+      const slotTexts = Array.from(slotRow.querySelectorAll('span[data-rack-diameter]')).map(s => s.textContent);
       expect(slotTexts).toEqual(['—', '0.2', '0.6', '0.8', '1.0', '1.2']);
     });
 
+    it('labels every rack position 1..6 regardless of which nozzles are present', async () => {
+      // The numbering is positional, not a count of what is loaded: the empty
+      // position keeps its number so "the nozzle in slot 4" means the same
+      // thing whether or not slots 1..3 are occupied.
+      server.use(
+        http.get('/api/v1/printers/:id/status', () => {
+          return HttpResponse.json(h2cStatus);
+        })
+      );
+
+      render(<PrintersPage />);
+
+      await waitFor(() => {
+        expect(screen.getAllByText('Nozzle Rack').length).toBeGreaterThan(0);
+      });
+
+      const rackLabel = screen.getAllByText('Nozzle Rack')[0];
+      const slotRow = rackLabel.parentElement!.querySelectorAll('div.flex')[0];
+      const numbers = Array.from(slotRow.querySelectorAll('span:not([data-rack-diameter])')).map(s => s.textContent);
+      expect(numbers).toEqual(['1', '2', '3', '4', '5', '6']);
+    });
+
     it('hides nozzle rack when only L/R nozzles present (H2D)', async () => {
       const h2dStatus = {
         ...mockPrinterStatus,

+ 28 - 12
frontend/src/pages/PrintersPage.tsx

@@ -555,7 +555,12 @@ function NozzleRackCard({ slots, filamentInfo }: { slots: import('../api/client'
   );
 
   return (
-    <div className="text-center px-2.5 py-1.5 bg-bambu-dark rounded-lg flex-[2_1_190px] flex flex-col justify-center">
+    // Sized to its contents rather than growing: the six chips are a fixed
+    // 28px each, so any extra width is dead space taken from the temperature
+    // cards beside it — which are flex-1 with a 0 basis and wrap their values
+    // ("220° / 220°") as soon as they lose it. Shrink stays enabled so the
+    // card still gives way on a narrow printer card instead of overflowing.
+    <div className="text-center px-2.5 py-1.5 bg-bambu-dark rounded-lg flex-[0_1_auto] flex flex-col justify-center">
       <p className="text-[length:var(--pc-t9,9px)] text-bambu-gray mb-1">{t('printers.nozzleRack')}</p>
       <div className="flex gap-[3px] justify-center">
         {rackSlots.map((slot, i) => {
@@ -565,18 +570,29 @@ function NozzleRackCard({ slots, filamentInfo }: { slots: import('../api/client'
 
           return (
             <NozzleSlotHoverCard key={slot.id >= 0 ? slot.id : `empty-${i}`} slot={slot} index={i} filamentName={slot.filament_id ? filamentInfo?.[slot.filament_id]?.name : undefined}>
-              <div
-                className={`w-7 h-7 rounded flex items-center justify-center cursor-default transition-colors border-b-2 ${
-                  isEmpty
-                    ? 'bg-bambu-dark-tertiary/20 border-bambu-dark-tertiary/20'
-                    : 'bg-bambu-dark-tertiary/40 border-bambu-dark-tertiary/40'
-                }`}
-                style={filamentBg ? { backgroundColor: filamentBg } : undefined}
-              >
-                <span className={`text-[length:var(--pc-t10,10px)] font-semibold ${isEmpty ? 'text-bambu-gray/30' : lightBg ? 'text-black/80' : 'text-white'}`}
-                      style={filamentBg && !lightBg ? { textShadow: '0 1px 3px rgba(0,0,0,0.9)' } : undefined}
+              <div className="flex flex-col items-center gap-0.5">
+                <div
+                  className={`w-7 h-7 rounded flex items-center justify-center cursor-default transition-colors border-b-2 ${
+                    isEmpty
+                      ? 'bg-bambu-dark-tertiary/20 border-bambu-dark-tertiary/20'
+                      : 'bg-bambu-dark-tertiary/40 border-bambu-dark-tertiary/40'
+                  }`}
+                  style={filamentBg ? { backgroundColor: filamentBg } : undefined}
                 >
-                  {isEmpty ? '—' : (slot.nozzle_diameter || '?')}
+                  <span
+                    data-rack-diameter
+                    className={`text-[length:var(--pc-t10,10px)] font-semibold ${isEmpty ? 'text-bambu-gray/30' : lightBg ? 'text-black/80' : 'text-white'}`}
+                    style={filamentBg && !lightBg ? { textShadow: '0 1px 3px rgba(0,0,0,0.9)' } : undefined}
+                  >
+                    {isEmpty ? '—' : (slot.nozzle_diameter || '?')}
+                  </span>
+                </div>
+                {/* Physical rack position, so "swap the nozzle in slot 4" can be
+                    acted on without counting chips. Sits below the chip rather
+                    than inside it: the chip is 28px and already carries the
+                    diameter over a filament-coloured background. */}
+                <span className="text-[length:var(--pc-t8,8px)] leading-none tabular-nums text-bambu-gray/70">
+                  {i + 1}
                 </span>
               </div>
             </NozzleSlotHoverCard>

Разница между файлами не показана из-за своего большого размера
+ 1 - 0
static/assets/index-B3n6Lnj1.css


Разница между файлами не показана из-за своего большого размера
+ 0 - 1
static/assets/index-BVhCaZnn.css


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
static/assets/index-CiO0qpbC.js


+ 2 - 2
static/index.html

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

Некоторые файлы не были показаны из-за большого количества измененных файлов