import { useState, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { Loader2, Check, AlertTriangle, Printer, Eye, EyeOff, Info, ChevronDown, ExternalLink, ArrowRightLeft } from 'lucide-react'; import { api, virtualPrinterApi } from '../api/client'; import { Card, CardContent, CardHeader } from './Card'; import { Button } from './Button'; import { useToast } from '../contexts/ToastContext'; type LocalMode = 'immediate' | 'review' | 'print_queue' | 'proxy'; export function VirtualPrinterSettings() { const { t } = useTranslation(); const queryClient = useQueryClient(); const { showToast } = useToast(); const [localEnabled, setLocalEnabled] = useState(false); const [localAccessCode, setLocalAccessCode] = useState(''); const [localMode, setLocalMode] = useState('immediate'); const [localModel, setLocalModel] = useState('BL-P001'); const [localTargetPrinterId, setLocalTargetPrinterId] = useState(null); const [localRemoteInterfaceIp, setLocalRemoteInterfaceIp] = useState(''); const [showAccessCode, setShowAccessCode] = useState(false); const [pendingAction, setPendingAction] = useState<'toggle' | 'accessCode' | 'mode' | 'model' | 'targetPrinter' | 'remoteInterface' | null>(null); // Fetch current settings const { data: settings, isLoading } = useQuery({ queryKey: ['virtual-printer-settings'], queryFn: virtualPrinterApi.getSettings, refetchInterval: 10000, // Refresh every 10 seconds for status updates }); // Fetch available models const { data: modelsData } = useQuery({ queryKey: ['virtual-printer-models'], queryFn: virtualPrinterApi.getModels, }); // Fetch printers for proxy mode dropdown const { data: printers } = useQuery({ queryKey: ['printers'], queryFn: api.getPrinters, }); // Fetch network interfaces for IP override (all modes when enabled) const { data: networkInterfaces } = useQuery({ queryKey: ['network-interfaces'], queryFn: () => api.getNetworkInterfaces().then(res => res.interfaces), enabled: localEnabled, }); // Initialize local state from settings useEffect(() => { if (settings) { setLocalEnabled(settings.enabled); // Map legacy 'queue' mode to 'review' let mode: LocalMode = settings.mode === 'queue' ? 'review' : settings.mode as LocalMode; if (mode !== 'immediate' && mode !== 'review' && mode !== 'print_queue' && mode !== 'proxy') { mode = 'immediate'; // fallback } setLocalMode(mode); setLocalModel(settings.model); setLocalTargetPrinterId(settings.target_printer_id); setLocalRemoteInterfaceIp(settings.remote_interface_ip || ''); } }, [settings]); // Update mutation const updateMutation = useMutation({ mutationFn: (data: { enabled?: boolean; access_code?: string; mode?: LocalMode; model?: string; target_printer_id?: number; remote_interface_ip?: string }) => virtualPrinterApi.updateSettings(data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['virtual-printer-settings'] }); showToast(t('virtualPrinter.toast.updated')); setPendingAction(null); }, onError: (error: Error) => { showToast(error.message || t('virtualPrinter.toast.failedToUpdate'), 'error'); // Revert local state on error if (settings) { setLocalEnabled(settings.enabled); // Map legacy 'queue' mode to 'review' const mode = settings.mode === 'queue' ? 'review' : settings.mode; setLocalMode(['immediate', 'review', 'print_queue', 'proxy'].includes(mode) ? mode as LocalMode : 'immediate'); setLocalModel(settings.model); setLocalTargetPrinterId(settings.target_printer_id); } setPendingAction(null); }, }); const handleToggleEnabled = () => { const newEnabled = !localEnabled; // Validation depends on mode if (newEnabled) { if (localMode === 'proxy') { // Proxy mode requires target printer if (!localTargetPrinterId) { showToast(t('virtualPrinter.toast.targetPrinterRequired'), 'error'); return; } } else { // Other modes require access code if (!localAccessCode && !settings?.access_code_set) { showToast(t('virtualPrinter.toast.accessCodeRequired'), 'error'); return; } } } setLocalEnabled(newEnabled); setPendingAction('toggle'); updateMutation.mutate({ enabled: newEnabled, access_code: localMode !== 'proxy' ? (localAccessCode || undefined) : undefined, mode: localMode, target_printer_id: localMode === 'proxy' ? (localTargetPrinterId ?? undefined) : undefined, }); }; const handleAccessCodeChange = () => { if (!localAccessCode) { showToast(t('virtualPrinter.toast.accessCodeEmpty'), 'error'); return; } if (localAccessCode.length !== 8) { showToast(t('virtualPrinter.toast.accessCodeLength'), 'error'); return; } setPendingAction('accessCode'); updateMutation.mutate({ access_code: localAccessCode, }); setLocalAccessCode(''); // Clear after saving }; const handleModeChange = (mode: LocalMode) => { setLocalMode(mode); setPendingAction('mode'); updateMutation.mutate({ mode }); }; const handleTargetPrinterChange = (printerId: number) => { setLocalTargetPrinterId(printerId); setPendingAction('targetPrinter'); updateMutation.mutate({ target_printer_id: printerId, }); }; const handleModelChange = (model: string) => { setLocalModel(model); setPendingAction('model'); updateMutation.mutate({ model }); }; const handleRemoteInterfaceChange = (ip: string) => { setLocalRemoteInterfaceIp(ip); setPendingAction('remoteInterface'); updateMutation.mutate({ remote_interface_ip: ip }); }; if (isLoading) { return ( ); } const status = settings?.status; const isRunning = status?.running || false; return (
{/* Left Column - Settings */}

{t('virtualPrinter.title')}

{status && (
{isRunning ? t('virtualPrinter.running') : t('virtualPrinter.stopped')}
)}

{localMode === 'proxy' ? t('virtualPrinter.description.proxy') : t('virtualPrinter.description.default')}

{/* Enable/Disable Toggle */}
{t('virtualPrinter.enable.title')}
{isRunning ? ( localMode === 'proxy' ? t('virtualPrinter.enable.proxyingTo', { name: printers?.find(p => p.id === localTargetPrinterId)?.name || 'printer' }) : t('virtualPrinter.enable.visibleInSlicer') ) : t('virtualPrinter.enable.notActive')}
{/* Printer Model - only for non-proxy modes */} {localMode !== 'proxy' && (
{t('virtualPrinter.model.title')}
{t('virtualPrinter.model.description')}
{localEnabled && isRunning && (

{t('virtualPrinter.model.restartWarning')}

)}
)} {/* Access Code - only for non-proxy modes */} {localMode !== 'proxy' && (
{t('virtualPrinter.accessCode.title')}
{settings?.access_code_set ? ( {t('virtualPrinter.accessCode.isSet')} ) : ( {t('virtualPrinter.accessCode.notSet')} )}
setLocalAccessCode(e.target.value)} placeholder={settings?.access_code_set ? t('virtualPrinter.accessCode.placeholderChange') : t('virtualPrinter.accessCode.placeholder')} maxLength={8} className="w-full bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-md px-3 py-2 text-white placeholder-bambu-gray pr-10 font-mono" />

{t('virtualPrinter.accessCode.hint')} {localAccessCode && ( {' '}{t('virtualPrinter.accessCode.charCount', { count: localAccessCode.length })} )}

)} {/* Target Printer - only for proxy mode */} {localMode === 'proxy' && (
{t('virtualPrinter.targetPrinter.title')}
{localTargetPrinterId ? ( {t('virtualPrinter.targetPrinter.configured')} ) : ( {t('virtualPrinter.targetPrinter.notConfigured')} )}

{t('virtualPrinter.targetPrinter.hint')}

{!printers?.length && (

{t('virtualPrinter.targetPrinter.noPrinters')}

)}
)} {/* Remote Interface - available for all modes (IP override / SSDP proxy) */} {localEnabled && (
{t('virtualPrinter.remoteInterface.title')}
{localRemoteInterfaceIp ? ( {t('virtualPrinter.remoteInterface.configured')} ) : ( {t('virtualPrinter.remoteInterface.optional')} )}

{t('virtualPrinter.remoteInterface.hint')}

)} {/* Mode */}
{t('virtualPrinter.mode.title')}
{/* Right Column - Info & Status */}
{/* Setup Required Warning */}

{t('virtualPrinter.setupRequired.title')}

{t('virtualPrinter.setupRequired.description')}

{t('virtualPrinter.setupRequired.readGuide')}
{/* How it works */}

{localMode === 'proxy' ? t('virtualPrinter.howItWorks.titleProxy') : t('virtualPrinter.howItWorks.title')}:

{localMode === 'proxy' ? (
  1. {t('virtualPrinter.howItWorks.proxyStep1')}
  2. {t('virtualPrinter.howItWorks.proxyStep2')}
  3. {t('virtualPrinter.howItWorks.proxyStep3')}
  4. {t('virtualPrinter.howItWorks.proxyStep4')}
  5. {t('virtualPrinter.howItWorks.proxyStep5')}
) : (
  1. {t('virtualPrinter.howItWorks.step1')}
  2. {t('virtualPrinter.howItWorks.step2')}
  3. {t('virtualPrinter.howItWorks.step3')}
  4. {t('virtualPrinter.howItWorks.step4')}
  5. {t('virtualPrinter.howItWorks.step5')}
  6. {t('virtualPrinter.howItWorks.step6')}
)}
{/* Status Details (when running) */} {status && isRunning && (

{t('virtualPrinter.status.title')}

{status.mode === 'proxy' && status.proxy ? (
{t('virtualPrinter.status.targetPrinter')}
{printers?.find(p => p.id === localTargetPrinterId)?.name || status.proxy.target_host}
{status.proxy.target_host}
{t('virtualPrinter.status.mode')}
{t('virtualPrinter.mode.proxy')}
{t('virtualPrinter.status.ftpPort')}
{status.proxy.ftp_port}
{t('virtualPrinter.status.mqttPort')}
{status.proxy.mqtt_port}
{t('virtualPrinter.status.ftpConnections')}
{status.proxy.ftp_connections ?? 0}
{t('virtualPrinter.status.mqttConnections')}
{status.proxy.mqtt_connections ?? 0}
) : (
{t('virtualPrinter.status.printerName')}
{status.name}
{t('virtualPrinter.status.model')}
{status.model_name || status.model}
{t('virtualPrinter.status.serialNumber')}
{status.serial}
{t('virtualPrinter.status.mode')}
{status.mode}
{t('virtualPrinter.status.pendingFiles')}
{status.pending_files}
)}
)}
); }