import { useEffect, useState } from 'react'; import { Printer, HardDrive, Network, Box, ChevronRight, RefreshCw } from 'lucide-react'; import DeviceDetailOverlay from './DeviceDetailOverlay'; import { toast } from 'sonner'; import { useWs } from '../ws'; interface Device { id: string; number: string; type: 'printer' | 'drive' | 'network' | 'other' | 'meatloaf'; name?: string; enabled: boolean | number; base_url?: string; url?: string; mode?: number; } interface DevicesPageProps { config: any; setConfig: (config: any) => void; openDeviceId?: string | null; onClearOpenDevice?: () => void; } export default function DevicesPage({ config, setConfig, openDeviceId, onClearOpenDevice }: DevicesPageProps) { // Host Settings update function const updateSetting = (path: string[], value: any) => { const newConfig = JSON.parse(JSON.stringify(config)); let current = newConfig; for (let i = 0; i < path.length - 1; i++) { current = current[path[i]]; } current[path[path.length - 1]] = value; setConfig(newConfig); }; const [selectedDeviceIndex, setSelectedDeviceIndex] = useState(null); const [isScanning, setIsScanning] = useState(false); const hardware = config.hardware || {}; const modem = config.modem || {}; const cassette = config.cassette || {}; const boip = config.boip || {}; const devices: Device[] = []; // Printer devices if (config.iec?.devices?.printer) { Object.entries(config.iec.devices.printer).forEach(([num, device]: [string, any]) => { devices.push({ id: `printer-${num}`, number: num, type: 'printer', name: device.name, enabled: device.enabled }); }); } // Drive devices if (config.iec?.devices?.drive) { Object.entries(config.iec.devices.drive).forEach(([key, value]: [string, any]) => { if (key !== 'vdrive' && key !== 'rom') { devices.push({ id: `drive-${key}`, number: key, type: 'drive', name: `Drive ${key}`, enabled: value.enabled, base_url: value.base_url, url: value.url, mode: value.mode }); } }); } // Network devices if (config.iec?.devices?.network) { Object.entries(config.iec.devices.network).forEach(([num, device]: [string, any]) => { devices.push({ id: `network-${num}`, number: num, type: 'network', name: `Network ${num}`, enabled: device.enabled, base_url: device.base_url, url: device.url }); }); } // Other devices if (config.iec?.devices?.other) { Object.entries(config.iec.devices.other).forEach(([num, device]: [string, any]) => { devices.push({ id: `other-${num}`, number: num, type: 'other', name: device.name, enabled: device.enabled }); }); } // Meatloaf devices if (config.iec?.devices?.meatloaf) { Object.entries(config.iec.devices.meatloaf).forEach(([num, device]: [string, any]) => { devices.push({ id: `meatloaf-${num}`, number: num, type: 'meatloaf', name: `Meatloaf ${num}`, enabled: device.enabled, base_url: device.base_url, url: device.url, mode: device.mode }); }); } // Auto-open the overlay when the parent passes a device ID (e.g. from a toast action) useEffect(() => { if (!openDeviceId) return; const idx = devices.findIndex(d => d.id === openDeviceId); if (idx >= 0) setSelectedDeviceIndex(idx); onClearOpenDevice?.(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [openDeviceId]); const getDeviceIcon = (type: Device['type']) => { switch (type) { case 'printer': return ; case 'drive': return ; case 'network': return ; default: return ; } }; const handleDeviceClick = (index: number) => { setSelectedDeviceIndex(index); }; const handleCloseOverlay = () => { setSelectedDeviceIndex(null); }; const handleNavigate = (direction: 'prev' | 'next') => { if (selectedDeviceIndex === null) return; if (direction === 'prev' && selectedDeviceIndex > 0) { setSelectedDeviceIndex(selectedDeviceIndex - 1); } else if (direction === 'next' && selectedDeviceIndex < devices.length - 1) { setSelectedDeviceIndex(selectedDeviceIndex + 1); } }; const { send: wsSend } = useWs(); const rescanBus = async () => { setIsScanning(true); wsSend('iec scan'); toast.loading('Scanning IEC bus...'); // Simulate bus scan await new Promise(resolve => setTimeout(resolve, 2000)); setIsScanning(false); toast.dismiss(); toast.success(`Found ${devices.length} devices on the bus`); }; const toggleDeviceEnabled = (device: Device, e: React.MouseEvent) => { e.stopPropagation(); const [type, num] = device.id.split('-'); const path = ['iec', 'devices', type, num, 'enabled']; const newConfig = JSON.parse(JSON.stringify(config)); let current = newConfig; for (let i = 0; i < path.length - 1; i++) { current = current[path[i]]; } current[path[path.length - 1]] = device.enabled ? 0 : 1; setConfig(newConfig); toast.success(`Device #${device.number} ${device.enabled ? 'disabled' : 'enabled'}`); }; return (
{/* Host Settings section moved from GeneralPage */}

Host Settings

IEC Devices

{devices.map((device, index) => (
{getDeviceIcon(device.type)}
))}
{selectedDeviceIndex !== null && ( 0} hasNext={selectedDeviceIndex < devices.length - 1} /> )} {/* ── Cassette ── */}

Cassette

updateSetting(['cassette', 'play_record'], e.target.value)} className="w-full px-3 py-2 border border-neutral-300 rounded-lg" />
updateSetting(['cassette', 'pulldown'], e.target.value)} className="w-full px-3 py-2 border border-neutral-300 rounded-lg" />
updateSetting(['cassette', 'url'], e.target.value)} className="w-full px-3 py-2 border border-neutral-300 rounded-lg" />
{/* ── Hardware ── */}

Hardware

{/* ── Modem ── */}

Modem

{/* ── BOIP ── */}

BOIP

updateSetting(['boip', 'host'], e.target.value)} className="w-full px-3 py-2 border border-neutral-300 rounded-lg" />
updateSetting(['boip', 'port'], e.target.value)} className="w-full px-3 py-2 border border-neutral-300 rounded-lg" />
); }