|
|
@@ -209,6 +209,25 @@ import { getCurrencySymbol } from '../utils/currency';
|
|
|
import { useUiSettings } from '../hooks/useUiSettings';
|
|
|
import { isSlicedLibraryFile, mayBeSlicedFileName } from '../utils/libraryFiles';
|
|
|
|
|
|
+/** The spool Bambuddy has assigned to a slot the printer reports loaded but
|
|
|
+ * blank: typically one swapped in mid-print after a runout, which the printer
|
|
|
+ * takes no configuration for until the print is over (#3434). The card shows
|
|
|
+ * it, in the spool's colour, instead of a bare "?". */
|
|
|
+function pendingSlotSpool(
|
|
|
+ emptyKind: 'physical' | 'reset' | null,
|
|
|
+ spool: { brand?: string | null; material?: string | null; subtype?: string | null; color_name?: string | null; rgba?: string | null } | null | undefined,
|
|
|
+ onUnassign: (() => void) | undefined,
|
|
|
+): { label: string; colorHex: string | null; material: string; onUnassign?: () => void } | null {
|
|
|
+ if (emptyKind !== 'reset' || !spool) return null;
|
|
|
+ const name = [spool.brand, spool.material, spool.subtype].filter(Boolean).join(' ');
|
|
|
+ return {
|
|
|
+ label: spool.color_name ? `${name} (${spool.color_name})` : name,
|
|
|
+ colorHex: spool.rgba ?? null,
|
|
|
+ material: spool.material ?? '',
|
|
|
+ onUnassign,
|
|
|
+ };
|
|
|
+}
|
|
|
+
|
|
|
// The status filter's options, and the only values it may hold. One list so a
|
|
|
// saved filter cannot be validated against a set the dropdown has since moved
|
|
|
// on from (#2833). Labels stay literal so they remain greppable.
|
|
|
@@ -5634,10 +5653,19 @@ function PrinterCard({
|
|
|
const activePrintSlotLabel = activePrintSlotIdx >= 0
|
|
|
? `P${activePrintSlotIdx + 1}`
|
|
|
: null;
|
|
|
+ const pending = isEmpty
|
|
|
+ ? pendingSlotSpool(
|
|
|
+ emptyKind,
|
|
|
+ spoolmanEnabled ? slotSpoolForFill : inventoryAssignment?.spool,
|
|
|
+ spoolmanEnabled
|
|
|
+ ? (slotSpoolForFill ? () => onUnassignSpoolmanSpool?.(slotSpoolForFill.id) : undefined)
|
|
|
+ : (inventoryAssignment ? () => onUnassignSpool?.(printer.id, ams.id, slotIdx) : undefined),
|
|
|
+ )
|
|
|
+ : null;
|
|
|
// Slot visual content (goes inside hover card)
|
|
|
const slotVisual = (
|
|
|
<div
|
|
|
- className={`relative w-full bg-bambu-dark-secondary rounded-lg p-1 text-center ${isEmpty ? 'opacity-50' : ''} ${
|
|
|
+ className={`relative w-full bg-bambu-dark-secondary rounded-lg p-1 text-center ${isEmpty && !pending ? 'opacity-50' : ''} ${
|
|
|
isExpectedSlot
|
|
|
? 'ring-2 ring-amber-400 ring-offset-1 ring-offset-bambu-dark animate-pulse'
|
|
|
: isRanOutSlot
|
|
|
@@ -5671,10 +5699,11 @@ function PrinterCard({
|
|
|
trayType={tray?.tray_type}
|
|
|
isEmpty={isEmpty}
|
|
|
emptyKind={emptyKind}
|
|
|
+ pendingColor={pending?.colorHex}
|
|
|
slotNumber={slotIdx + 1}
|
|
|
/>
|
|
|
<div className="text-[length:var(--pc-t9,9px)] text-white font-bold truncate">
|
|
|
- {tray?.tray_type || t(emptyKind === 'reset' ? 'ams.slotUnconfigured' : 'ams.slotEmpty')}
|
|
|
+ {tray?.tray_type || pending?.material || t(emptyKind === 'reset' ? 'ams.slotUnconfigured' : 'ams.slotEmpty')}
|
|
|
</div>
|
|
|
<KValueLine k={filamentData ? tray?.k : null} reserve={anySlotHasKValue} />
|
|
|
{/* Fill bar */}
|
|
|
@@ -5812,6 +5841,7 @@ function PrinterCard({
|
|
|
) : (
|
|
|
<EmptySlotHoverCard
|
|
|
kind={emptyKind ?? undefined}
|
|
|
+ assigned={pending ?? undefined}
|
|
|
actions={renderAmsSlotActions({
|
|
|
amsId: ams.id,
|
|
|
slotId: slotIdx,
|
|
|
@@ -5928,10 +5958,19 @@ function PrinterCard({
|
|
|
const htActivePrintSlotLabel = htActivePrintSlotIdx >= 0
|
|
|
? `P${htActivePrintSlotIdx + 1}`
|
|
|
: null;
|
|
|
+ const pending = isEmpty
|
|
|
+ ? pendingSlotSpool(
|
|
|
+ emptyKind,
|
|
|
+ spoolmanEnabled ? htSlotSpoolForFill : htInventoryAssignment?.spool,
|
|
|
+ spoolmanEnabled
|
|
|
+ ? (htSlotSpoolForFill ? () => onUnassignSpoolmanSpool?.(htSlotSpoolForFill.id) : undefined)
|
|
|
+ : (htInventoryAssignment ? () => onUnassignSpool?.(printer.id, ams.id, htSlotId) : undefined),
|
|
|
+ )
|
|
|
+ : null;
|
|
|
// Slot visual content (goes inside hover card)
|
|
|
const slotVisual = (
|
|
|
<div
|
|
|
- className={`relative w-full bg-bambu-dark-secondary rounded-lg p-1 text-center ${isEmpty ? 'opacity-50' : ''} ${
|
|
|
+ className={`relative w-full bg-bambu-dark-secondary rounded-lg p-1 text-center ${isEmpty && !pending ? 'opacity-50' : ''} ${
|
|
|
isExpectedSlot
|
|
|
? 'ring-2 ring-amber-400 ring-offset-1 ring-offset-bambu-dark animate-pulse'
|
|
|
: isRanOutSlot
|
|
|
@@ -5965,10 +6004,11 @@ function PrinterCard({
|
|
|
trayType={tray?.tray_type}
|
|
|
isEmpty={isEmpty}
|
|
|
emptyKind={emptyKind}
|
|
|
+ pendingColor={pending?.colorHex}
|
|
|
slotNumber={1}
|
|
|
/>
|
|
|
<div className="text-[length:var(--pc-t9,9px)] text-white font-bold truncate">
|
|
|
- {tray?.tray_type || t(emptyKind === 'reset' ? 'ams.slotUnconfigured' : 'ams.slotEmpty')}
|
|
|
+ {tray?.tray_type || pending?.material || t(emptyKind === 'reset' ? 'ams.slotUnconfigured' : 'ams.slotEmpty')}
|
|
|
</div>
|
|
|
<KValueLine k={filamentData ? tray?.k : null} reserve={anySlotHasKValue} />
|
|
|
{/* Fill bar */}
|
|
|
@@ -6218,6 +6258,7 @@ function PrinterCard({
|
|
|
) : (
|
|
|
<EmptySlotHoverCard
|
|
|
kind={emptyKind ?? undefined}
|
|
|
+ assigned={pending ?? undefined}
|
|
|
actions={renderAmsSlotActions({
|
|
|
amsId: ams.id,
|
|
|
slotId: htSlotId,
|
|
|
@@ -6360,8 +6401,17 @@ function PrinterCard({
|
|
|
|
|
|
const isEmpty = !extTray.tray_type;
|
|
|
const emptyKind = getEmptySlotKind(extTray);
|
|
|
+ const pending = isEmpty
|
|
|
+ ? pendingSlotSpool(
|
|
|
+ emptyKind,
|
|
|
+ spoolmanEnabled ? extSlotSpoolForFill : extInventoryAssignment?.spool,
|
|
|
+ spoolmanEnabled
|
|
|
+ ? (extSlotSpoolForFill ? () => onUnassignSpoolmanSpool?.(extSlotSpoolForFill.id) : undefined)
|
|
|
+ : (extInventoryAssignment ? () => onUnassignSpool?.(printer.id, 255, slotTrayId) : undefined),
|
|
|
+ )
|
|
|
+ : null;
|
|
|
const extSlotContent = (
|
|
|
- <div className={`w-full bg-bambu-dark-secondary rounded-lg p-1 text-center ${isEmpty ? 'opacity-50' : ''} ${isExtActive ? 'ring-2 ring-bambu-green ring-offset-1 ring-offset-bambu-dark' : ''}`}>
|
|
|
+ <div className={`w-full bg-bambu-dark-secondary rounded-lg p-1 text-center ${isEmpty && !pending ? 'opacity-50' : ''} ${isExtActive ? 'ring-2 ring-bambu-green ring-offset-1 ring-offset-bambu-dark' : ''}`}>
|
|
|
{/* Color circle: L/R inside on dual-nozzle external (replaces
|
|
|
the separate Ext-L/Ext-R caption that made the row taller than
|
|
|
regular AMS slots), 1-based slot number on single-nozzle. */}
|
|
|
@@ -6370,10 +6420,11 @@ function PrinterCard({
|
|
|
trayType={extTray.tray_type}
|
|
|
isEmpty={isEmpty}
|
|
|
emptyKind={emptyKind}
|
|
|
+ pendingColor={pending?.colorHex}
|
|
|
slotNumber={isDualNozzle ? (extTrayId === 254 ? 'L' : 'R') : slotTrayId + 1}
|
|
|
/>
|
|
|
- <div className={`text-[length:var(--pc-t9,9px)] font-bold truncate ${isEmpty ? 'text-white/40' : 'text-white'}`}>
|
|
|
- {extTray.tray_type || t('ams.slotEmpty')}
|
|
|
+ <div className={`text-[length:var(--pc-t9,9px)] font-bold truncate ${isEmpty && !pending ? 'text-white/40' : 'text-white'}`}>
|
|
|
+ {extTray.tray_type || pending?.material || t('ams.slotEmpty')}
|
|
|
</div>
|
|
|
<KValueLine k={isEmpty ? null : extTray.k} reserve={anySlotHasKValue} />
|
|
|
<div className="mt-1 h-1.5 bg-black/30 rounded-full overflow-hidden">
|
|
|
@@ -6500,6 +6551,7 @@ function PrinterCard({
|
|
|
) : (
|
|
|
<EmptySlotHoverCard
|
|
|
kind={emptyKind ?? undefined}
|
|
|
+ assigned={pending ?? undefined}
|
|
|
actions={renderAmsSlotActions({
|
|
|
amsId: 255,
|
|
|
slotId: slotTrayId,
|