import { lazy, Suspense, useEffect, useState } from 'react';
import { Cpu, Settings, Wifi, Network, HardDrive, Activity, Search, Wrench, User, LogOut, Bell, FileText, AppWindow, Folder, Edit, Eye, Database, Upload, Download, Code2, LayoutList, Image, ChevronLeft, Loader2, Terminal, Link, Printer, Maximize2, Minimize2 } from 'lucide-react';
import { Toaster, toast } from 'sonner';
import StatusPage from './components/StatusPage';
import DevicesPage from './components/DevicesPage';
import GeneralPage from './components/GeneralPage';
import NetworkPage from './components/NetworkPage';
import IECPage from './components/IECPage';
import ToolsPage from './components/ToolsPage';
import SearchOverlay from './components/SearchOverlay';
import RealityOverrideAdminPage from './components/RealityOverrideAdminPage';
import MediaManager from './components/MediaManager';
import logoSvg from '../imports/logo.svg';
import { useSettings } from './settings';
import { WsProvider } from './ws';
// Three.js lives only in RealityOverridePage — keep lazy so it doesn't load on startup.
// CodeMirror/syntax-highlighter/ReactMarkdown live in MediaViewerEditor — lazy-loaded
// inside MediaManager when the user first opens a file to view or edit.
const RealityOverridePage = lazy(() => import('./components/RealityOverridePage'));
type Page = 'status' | 'devices' | 'iec' | 'network' | 'general' | 'tools' | 'apps' | AppId;
type AppId =
| 'file-manager'
| 'print-manager'
| 'serial-console'
| 'directory-editor'
| 'sector-editor'
| 'bam-editor'
| 'disk-visualizer'
| 'ramrom-explorer'
| 'dump-disk-image'
| 'write-disk-image'
| 'prg-to-crt'
| 'magic-desk-cart-builder'
| 'easy-flash-cart-builder'
| 'basic-editor'
| 'assembler'
| 'sprite-editor'
| 'charset-editor'
| 'petscii-editor'
| 'idle-animation'
| 'loading-animation'
| 'reality-override'
| 'reality-override-admin';
function PageLoader() {
return (
);
}
export default function App() {
const [currentPage, setCurrentPage] = useState('status');
const { config, setConfig, saveStatus, pendingCount, flushNow, reload } = useSettings();
const [showSearch, setShowSearch] = useState(false);
const [showProfileMenu, setShowProfileMenu] = useState(false);
const [devicesOpenId, setDevicesOpenId] = useState(null);
const [isFullscreen, setIsFullscreen] = useState(false);
useEffect(() => {
const id = 'save-status';
switch (saveStatus) {
case 'idle': toast.dismiss(id); break;
case 'loading': toast.loading('Loading settings…', { id }); break;
case 'unsaved':
toast(`${pendingCount} unsaved change${pendingCount === 1 ? '' : 's'}`, {
id, duration: Infinity,
action: { label: 'Save', onClick: flushNow },
});
break;
case 'saving': toast.loading('Saving…', { id }); break;
case 'saved': toast.success('Settings saved', { id, duration: 2000 }); break;
case 'error':
toast.error('Save failed', {
id, duration: Infinity,
action: { label: 'Retry', onClick: reload },
});
break;
}
}, [saveStatus, pendingCount, flushNow, reload]);
useEffect(() => {
const onChange = () => setIsFullscreen(!!document.fullscreenElement);
document.addEventListener('fullscreenchange', onChange);
document.addEventListener('webkitfullscreenchange', onChange);
return () => {
document.removeEventListener('fullscreenchange', onChange);
document.removeEventListener('webkitfullscreenchange', onChange);
};
}, []);
const toggleFullscreen = () => {
if (!document.fullscreenElement) {
(document.documentElement.requestFullscreen?.() ?? (document.documentElement as any).webkitRequestFullscreen?.());
} else {
(document.exitFullscreen?.() ?? (document as any).webkitExitFullscreen?.());
}
};
const pages = {
status: ,
devices: setDevicesOpenId(null)} />,
iec: ,
network: ,
general: ,
tools: ,
apps: (
Apps
Management
} label="Media Manager" onClick={() => setCurrentPage('file-manager')} />
} label="Print Manager" onClick={() => setCurrentPage('print-manager')} />
} label="Serial Console" onClick={() => setCurrentPage('serial-console')} />
} label="Short Codes" onClick={() => setCurrentPage('serial-console')} />
Disk
} label="RAM/ROM Explorer" onClick={() => setCurrentPage('ramrom-explorer')} />
} label="BAM Editor" onClick={() => setCurrentPage('bam-editor')} />
} label="Directory Editor" onClick={() => setCurrentPage('directory-editor')} />
} label="Sector Editor" onClick={() => setCurrentPage('sector-editor')} />
} label="Disk Visualizer" onClick={() => setCurrentPage('disk-visualizer')} />
} label="Dump Disk Image" onClick={() => setCurrentPage('dump-disk-image')} />
} label="Write Disk Image" onClick={() => setCurrentPage('write-disk-image')} />
Cartridge
} label="PRG to CRT" onClick={() => setCurrentPage('prg-to-crt')} />
} label="Magic Desk Cart Builder" onClick={() => setCurrentPage('magic-desk-cart-builder')} />
} label="Easy Flash Cart Builder" onClick={() => setCurrentPage('easy-flash-cart-builder')} />
Development
} label="Basic Editor" onClick={() => setCurrentPage('basic-editor')} />
} label="Assembler" onClick={() => setCurrentPage('assembler')} />
} label="Sprite Editor" onClick={() => setCurrentPage('sprite-editor')} />
} label="Character Set Editor" onClick={() => setCurrentPage('charset-editor')} />
} label="Petscii Editor" onClick={() => setCurrentPage('petscii-editor')} />
Display
} label="Idle Animation" onClick={() => setCurrentPage('idle-animation')} />
} label="Loading Animation" onClick={() => setCurrentPage('loading-animation')} />
} label="Reality Override" onClick={() => setCurrentPage('reality-override')} />
} label="Override Admin" onClick={() => setCurrentPage('reality-override-admin')} />
),
'file-manager': setCurrentPage('apps')}
config={config}
setConfig={setConfig}
onNavigateToDevice={(id) => { setCurrentPage('devices'); setDevicesOpenId(id); }}
/>,
'print-manager': setCurrentPage('apps')}
config={config}
setConfig={setConfig}
/>,
'serial-console': setCurrentPage('apps')} />,
'directory-editor': setCurrentPage('apps')} />,
'sector-editor': setCurrentPage('apps')} />,
'bam-editor': setCurrentPage('apps')} />,
'disk-visualizer': setCurrentPage('apps')} />,
'ramrom-explorer': setCurrentPage('apps')} />,
'dump-disk-image': setCurrentPage('apps')} />,
'write-disk-image': setCurrentPage('apps')} />,
'prg-to-crt': setCurrentPage('apps')} />,
'magic-desk-cart-builder': setCurrentPage('apps')} />,
'easy-flash-cart-builder': setCurrentPage('apps')} />,
'basic-editor': setCurrentPage('apps')} />,
'assembler': setCurrentPage('apps')} />,
'sprite-editor': setCurrentPage('apps')} />,
'charset-editor': setCurrentPage('apps')} />,
'petscii-editor': setCurrentPage('apps')} />,
'idle-animation': setCurrentPage('apps')} />,
'loading-animation': setCurrentPage('apps')} />,
'reality-override': setCurrentPage('apps')} />,
'reality-override-admin': setCurrentPage('apps')} />,
};
// AppCard component for app grid
function AppCard({ icon, label, onClick }: { icon: React.ReactNode; label: string; onClick: () => void }) {
return (
);
}
// AppPage component for individual app pages
function AppPage({ title, onBack }: { title: string; onBack: () => void }) {
return (
{title}
{title} coming soon...
);
}
return (
{showProfileMenu && (
)}
}>
{pages[currentPage]}
{showSearch && (
setShowSearch(false)}
/>
)}
);
}