import { useEffect, useState } from 'react'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import { api } from '../../api/client'; import type { Printer, PrinterStatus } from '../../api/client'; import { X, Loader2, AlertTriangle, ChevronDown } from 'lucide-react'; import { Card, CardContent } from '../Card'; interface PrintOptionsModalProps { printer: Printer; status: PrinterStatus | null | undefined; onClose: () => void; } // Checkbox component matching Bambu Studio style function Checkbox({ checked, onChange, disabled, loading, }: { checked: boolean; onChange: (checked: boolean) => void; disabled?: boolean; loading?: boolean; }) { return ( ); } // Sensitivity dropdown component function SensitivityDropdown({ value, onChange, disabled, loading, }: { value: string; onChange: (value: string) => void; disabled?: boolean; loading?: boolean; }) { const [isOpen, setIsOpen] = useState(false); const options = ['Low', 'Medium', 'High']; const displayValue = value.charAt(0).toUpperCase() + value.slice(1).toLowerCase(); const isDisabled = disabled || loading; return (
{isOpen && !isDisabled && ( <>
setIsOpen(false)} />
{options.map((option) => ( ))}
)}
); } export function PrintOptionsModal({ printer, status, onClose }: PrintOptionsModalProps) { const queryClient = useQueryClient(); const printOptions = status?.print_options; const isConnected = status?.connected ?? false; // Close on Escape key useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); }; window.addEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown); }, [onClose]); // Set print option mutation - track both UI and API module names const setOptionMutation = useMutation({ mutationFn: ({ apiModuleName, enabled, printHalt, sensitivity, }: { uiModuleName: string; // The module name from the UI (for loading state) - accessed via variables apiModuleName: string; // The module name sent to API (may differ due to firmware quirk) enabled: boolean; printHalt?: boolean; sensitivity?: string; }) => api.setPrintOption(printer.id, apiModuleName, enabled, printHalt, sensitivity), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['printerStatuses'] }); }, }); const handleToggle = (moduleName: string, enabled: boolean, sensitivity?: string) => { const printHalt = enabled && sensitivity !== undefined && sensitivity !== 'never_halt'; setOptionMutation.mutate({ uiModuleName: moduleName, apiModuleName: moduleName, enabled, printHalt, sensitivity: sensitivity ?? 'medium' }); }; const handleSensitivityChange = (moduleName: string, sensitivity: string) => { // Spaghetti and Pileup sensitivities are linked in firmware // For pileup, we send spaghetti_detector to set the shared sensitivity // All other detectors use their own module name let apiModuleName = moduleName; if (moduleName === 'pileup_detector') { // Pileup sensitivity is linked to spaghetti - use spaghetti_detector apiModuleName = 'spaghetti_detector'; } setOptionMutation.mutate({ uiModuleName: moduleName, apiModuleName, enabled: true, printHalt: true, sensitivity }); }; // Check loading state using the UI module name (not API module name) const isToggling = (moduleName: string) => setOptionMutation.isPending && setOptionMutation.variables?.uiModuleName === moduleName; // Check if sensitivity is being changed for a specific detector // This also returns true for linked detectors (e.g., spaghetti loading shows for pileup too) const isSensitivityLoading = (moduleName: string) => { if (!setOptionMutation.isPending || !setOptionMutation.variables?.sensitivity) return false; const uiModule = setOptionMutation.variables.uiModuleName; // Direct match if (uiModule === moduleName) return true; // Linked: spaghetti and pileup share sensitivity if ((uiModule === 'spaghetti_detector' && moduleName === 'pileup_detector') || (uiModule === 'pileup_detector' && moduleName === 'spaghetti_detector')) return true; return false; }; return (
e.stopPropagation()}> {/* Header */}
Print Options
{/* Content - Scrollable */}
{!isConnected && (
Printer not connected. Options cannot be changed.
)} {!printOptions ? (
Print options not available. Try refreshing.
) : ( <> {/* AI Detections Section */}

AI Detections

Printer will send assistant message or pause printing if any of the following problem is detected.

{/* Spaghetti Detection */}
handleToggle('spaghetti_detector', checked, printOptions.halt_print_sensitivity)} disabled={!isConnected} loading={isToggling('spaghetti_detector')} /> Spaghetti Detection

Detects spaghetti failure (scattered loose filament).

Pausing Sensitivity: handleSensitivityChange('spaghetti_detector', value)} disabled={!isConnected || !printOptions.spaghetti_detector} loading={isSensitivityLoading('spaghetti_detector')} />
{/* Purge Chute Pile-Up Detection */}
handleToggle('pileup_detector', checked, printOptions.pileup_sensitivity)} disabled={!isConnected} loading={isToggling('pileup_detector')} /> Purge Chute Pile-Up Detection

Monitors if the waste is piled up in the purge chute.

Pausing Sensitivity: handleSensitivityChange('pileup_detector', value)} disabled={!isConnected || !printOptions.pileup_detector} loading={isSensitivityLoading('pileup_detector')} />
{/* Nozzle Clumping Detection */}
handleToggle('clump_detector', checked, printOptions.nozzle_clumping_sensitivity)} disabled={!isConnected} loading={isToggling('clump_detector')} /> Nozzle Clumping Detection

Checks if the nozzle is clumping by filaments or other foreign objects.

Pausing Sensitivity: handleSensitivityChange('clump_detector', value)} disabled={!isConnected || !printOptions.nozzle_clumping_detector} loading={isSensitivityLoading('clump_detector')} />
{/* Air Printing Detection */}
handleToggle('airprint_detector', checked, printOptions.airprint_sensitivity)} disabled={!isConnected} loading={isToggling('airprint_detector')} /> Air Printing Detection

Detects air printing caused by nozzle clogging or filament grinding.

Pausing Sensitivity: handleSensitivityChange('airprint_detector', value)} disabled={!isConnected || !printOptions.airprint_detector} loading={isSensitivityLoading('airprint_detector')} />

{/* Build Plate Detection Section */}

Build Plate Detection

Ensures the build plate type and placement are correct.

handleToggle('buildplate_marker_detector', checked)} disabled={!isConnected} loading={isToggling('buildplate_marker_detector')} /> Type Detection

Pauses printing when the detected build plate type does not match the selected one.


{/* Other Options */}
{/* Auto-recover from step loss */}
handleToggle('auto_recovery_step_loss', checked)} disabled={!isConnected} loading={isToggling('auto_recovery_step_loss')} /> Auto-recover from step loss
{/* Store Sent Files on External Storage - Read-only (no MQTT command available) */}
{}} disabled={true} /> Store Sent Files on External Storage (read-only)

Save the printing files initiated from Bambu Studio, Bambu Handy and MakerWorld on External Storage. This option can only be changed on the printer's touchscreen.

)}
); }