import { useEffect, useState } from 'react'; import { X, ChevronLeft, ChevronRight, Printer, HardDrive, Network, Box, FolderOpen, MoreVertical, Play, Pause, SkipForward, SkipBack, RotateCcw } from 'lucide-react'; import { motion, AnimatePresence } from 'motion/react'; import FileBrowser from './FileBrowser'; interface Device { id: string; number: string; type: 'printer' | 'drive' | 'network' | 'other' | 'meatloaf'; name?: string; enabled: boolean | number; url?: string; mode?: number; } interface DeviceDetailOverlayProps { device: Device; config: any; setConfig: (config: any) => void; onClose: () => void; onNavigate: (direction: 'prev' | 'next') => void; hasPrev: boolean; hasNext: boolean; } export default function DeviceDetailOverlay({ device, config, setConfig, onClose, onNavigate, hasPrev, hasNext }: DeviceDetailOverlayProps) { const [touchStart, setTouchStart] = useState(0); const [touchEnd, setTouchEnd] = useState(0); const [showFileBrowser, setShowFileBrowser] = useState(false); const [showCommandMenu, setShowCommandMenu] = useState(false); const [selectedMediaIndex, setSelectedMediaIndex] = useState(0); const minSwipeDistance = 50; const onTouchStart = (e: React.TouchEvent) => { setTouchEnd(0); setTouchStart(e.targetTouches[0].clientX); }; const onTouchMove = (e: React.TouchEvent) => { setTouchEnd(e.targetTouches[0].clientX); }; const onTouchEnd = () => { if (!touchStart || !touchEnd) return; const distance = touchStart - touchEnd; const isLeftSwipe = distance > minSwipeDistance; const isRightSwipe = distance < -minSwipeDistance; if (isLeftSwipe && hasNext) { onNavigate('next'); } if (isRightSwipe && hasPrev) { onNavigate('prev'); } }; useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (e.key === 'Escape') { onClose(); } else if (e.key === 'ArrowLeft' && hasPrev) { onNavigate('prev'); } else if (e.key === 'ArrowRight' && hasNext) { onNavigate('next'); } }; window.addEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown); }, [hasPrev, hasNext, onClose, onNavigate]); const updateDeviceSetting = (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 getDevicePath = (): string[] => { const [type, num] = device.id.split('-'); return ['iec', 'devices', type, num]; }; const getDeviceData = () => { const path = getDevicePath(); let current = config; for (const key of path) { current = current?.[key]; } return current || {}; }; const deviceData = getDeviceData(); const getDeviceIcon = (type: Device['type']) => { switch (type) { case 'printer': return ; case 'drive': return ; case 'network': return ; default: return ; } }; // Detect if URL is part of a media set (e.g., disk1.d64, disk2.d64) const detectMediaSet = () => { if (!deviceData.url) return null; const match = deviceData.url.match(/^(.+?)(\d+)(\.[^.]+)$/); if (!match) return null; const [, prefix, num, ext] = match; const currentNum = parseInt(num); // Generate potential media set const mediaSet = []; for (let i = 1; i <= 10; i++) { mediaSet.push(`${prefix}${i}${ext}`); } return { prefix, extension: ext, currentIndex: currentNum - 1, files: mediaSet }; }; const mediaSet = detectMediaSet(); const switchMedia = (index: number) => { if (!mediaSet) return; const path = getDevicePath(); updateDeviceSetting([...path, 'url'], mediaSet.files[index]); setSelectedMediaIndex(index); }; const sendCommand = (command: string) => { console.log(`Sending command to device ${device.number}: ${command}`); // In a real app, this would send the command to the device setShowCommandMenu(false); }; return ( e.stopPropagation()} onTouchStart={onTouchStart} onTouchMove={onTouchMove} onTouchEnd={onTouchEnd} >
{getDeviceIcon(device.type)}
{device.type}
#{device.number}
{showCommandMenu && (
)}
Swipe to navigate
{ const path = getDevicePath(); updateDeviceSetting([...path, 'name'], e.target.value); }} className="w-full px-3 py-2 border border-neutral-300 rounded-lg" />
{device.type.charAt(0).toUpperCase() + device.type.slice(1)}
{deviceData.url !== undefined && (
{ const path = getDevicePath(); updateDeviceSetting([...path, 'url'], e.target.value); }} className="flex-1 px-3 py-2 border border-neutral-300 rounded-lg" />
{mediaSet && (
{mediaSet.files.slice(0, 5).map((file, index) => { // Attempt to extract a title from the filename, fallback to filename // Example: /path/to/Game Disk.d64 or /path/to/disk1.d64 const fileName = file.split('/').pop() || file; // If you have a title mapping, replace this logic const title = fileName.replace(/\.[^.]+$/, ''); return ( ); })}
)}
)} {deviceData.mode !== undefined && (
{ const path = getDevicePath(); updateDeviceSetting([...path, 'mode'], parseInt(e.target.value)); }} className="w-full px-3 py-2 border border-neutral-300 rounded-lg" />
)} {deviceData.type && (
{deviceData.type}
)} {deviceData.baud !== undefined && (
{ const path = getDevicePath(); updateDeviceSetting([...path, 'baud'], parseInt(e.target.value)); }} className="w-full px-3 py-2 border border-neutral-300 rounded-lg" />
)}

Device ID

{device.id}
{showFileBrowser && ( { const devicePath = getDevicePath(); updateDeviceSetting([...devicePath, 'url'], path); }} onClose={() => setShowFileBrowser(false)} /> )}
); }