import React, { useState, useEffect, useRef } from 'react'; import { Activity, Settings, Move, Camera, Play, Square, RotateCw, Cpu, AlertTriangle, Siren, Terminal, Layers } from 'lucide-react'; import { Machine3D } from './components/Machine3D'; import { SettingsModal } from './components/SettingsModal'; import { RecipePanel } from './components/RecipePanel'; import { IOPanel } from './components/IOPanel'; import { MotionPanel } from './components/MotionPanel'; import { CameraPanel } from './components/CameraPanel'; import { CyberPanel } from './components/common/CyberPanel'; import { TechButton } from './components/common/TechButton'; import { SystemState, Recipe, IOPoint, LogEntry, RobotTarget, ConfigItem } from './types'; import { comms } from './communication'; // --- MOCK DATA --- const MOCK_RECIPES: Recipe[] = [ { id: '1', name: 'Wafer_Proc_300_Au', lastModified: '2023-10-25' }, { id: '2', name: 'Wafer_Insp_200_Adv', lastModified: '2023-10-26' }, { id: '3', name: 'Glass_Gen5_Bonding', lastModified: '2023-10-27' }, ]; const INITIAL_IO: IOPoint[] = [ ...Array.from({ length: 32 }, (_, i) => { let name = `DOUT_${i.toString().padStart(2, '0')}`; if (i === 0) name = "Tower Lamp Red"; if (i === 1) name = "Tower Lamp Yel"; if (i === 2) name = "Tower Lamp Grn"; return { id: i, name, type: 'output' as const, state: false }; }), ...Array.from({ length: 32 }, (_, i) => { let name = `DIN_${i.toString().padStart(2, '0')}`; let initialState = false; if (i === 0) name = "Front Door Sensor"; if (i === 1) name = "Right Door Sensor"; if (i === 2) name = "Left Door Sensor"; if (i === 3) name = "Back Door Sensor"; if (i === 4) { name = "Main Air Pressure"; initialState = true; } if (i === 5) { name = "Vacuum Generator"; initialState = true; } if (i === 6) { name = "Emergency Stop Loop"; initialState = true; } return { id: i, name, type: 'input' as const, state: initialState }; }) ]; // --- MAIN APP --- export default function App() { const [activeTab, setActiveTab] = useState<'recipe' | 'io' | 'motion' | 'camera' | 'setting' | null>(null); const [systemState, setSystemState] = useState(SystemState.IDLE); const [currentRecipe, setCurrentRecipe] = useState(MOCK_RECIPES[0]); const [robotTarget, setRobotTarget] = useState({ x: 0, y: 0, z: 0 }); const [logs, setLogs] = useState([]); const [ioPoints, setIoPoints] = useState(INITIAL_IO); const [currentTime, setCurrentTime] = useState(new Date()); const [config, setConfig] = useState(null); const [isConfigRefreshing, setIsConfigRefreshing] = useState(false); const [isLoading, setIsLoading] = useState(true); const videoRef = useRef(null); // Check if running in WebView2 context const isWebView = typeof window !== 'undefined' && !!window.chrome?.webview; // -- COMMUNICATION LAYER -- useEffect(() => { // Subscribe to unified communication layer const unsubscribe = comms.subscribe((msg: any) => { if (!msg) return; if (msg.type === 'STATUS_UPDATE') { // Update Motion State if (msg.position) { setRobotTarget({ x: msg.position.x, y: msg.position.y, z: msg.position.z }); } // Update IO State (Merge with existing names/configs) if (msg.ioState) { setIoPoints(prev => { const newIO = [...prev]; msg.ioState.forEach((update: { id: number, type: string, state: boolean }) => { const idx = newIO.findIndex(p => p.id === update.id && p.type === update.type); if (idx >= 0) newIO[idx] = { ...newIO[idx], state: update.state }; }); return newIO; }); } // Update System State if (msg.sysState) { setSystemState(msg.sysState as SystemState); } } }); addLog("COMMUNICATION CHANNEL OPEN", "info"); // Timer for Clock const timer = setInterval(() => setCurrentTime(new Date()), 1000); return () => { clearInterval(timer); unsubscribe(); }; }, []); // -- INITIALIZATION -- useEffect(() => { const initSystem = async () => { // Just start up without fetching config addLog("SYSTEM STARTED", "info"); setIsLoading(false); }; initSystem(); }, []); // -- ON-DEMAND CONFIG FETCH -- useEffect(() => { if (activeTab === 'setting') { const fetchConfig = async () => { setIsConfigRefreshing(true); try { const configStr = await comms.getConfig(); setConfig(JSON.parse(configStr)); addLog("CONFIG REFRESHED", "info"); } catch (e) { addLog("CONFIG REFRESH FAILED", "error"); } setIsConfigRefreshing(false); }; fetchConfig(); } }, [activeTab]); const addLog = (msg: string, type: 'info' | 'warning' | 'error' = 'info') => { setLogs(prev => [{ id: Date.now() + Math.random(), timestamp: new Date().toLocaleTimeString(), message: msg, type }, ...prev].slice(0, 50)); }; // Logic Helpers const doorStates = { front: ioPoints.find(p => p.id === 0 && p.type === 'input')?.state || false, right: ioPoints.find(p => p.id === 1 && p.type === 'input')?.state || false, left: ioPoints.find(p => p.id === 2 && p.type === 'input')?.state || false, back: ioPoints.find(p => p.id === 3 && p.type === 'input')?.state || false, }; const isLowPressure = !(ioPoints.find(p => p.id === 4 && p.type === 'input')?.state ?? true); const isEmergencyStop = !(ioPoints.find(p => p.id === 6 && p.type === 'input')?.state ?? true); // -- COMMAND HANDLERS -- // -- COMMAND HANDLERS -- const handleControl = async (action: 'start' | 'stop' | 'reset') => { if (isEmergencyStop && action === 'start') return addLog('EMERGENCY STOP ACTIVE', 'error'); try { await comms.sendControl(action.toUpperCase()); addLog(`CMD SENT: ${action.toUpperCase()}`, 'info'); } catch (e) { addLog('COMM ERROR', 'error'); } }; const toggleIO = async (id: number, type: 'input' | 'output', forceState?: boolean) => { // Only allow output toggling if (type === 'output') { const current = ioPoints.find(p => p.id === id && p.type === type)?.state; const nextState = forceState !== undefined ? forceState : !current; await comms.setIO(id, nextState); } }; const moveAxis = async (axis: 'X' | 'Y' | 'Z', value: number) => { if (isEmergencyStop) return; await comms.moveAxis(axis, value); addLog(`CMD MOVE ${axis}: ${value}`, 'info'); }; useEffect(() => { if (activeTab === 'camera' && navigator.mediaDevices?.getUserMedia) { navigator.mediaDevices.getUserMedia({ video: true }).then(s => { if (videoRef.current) videoRef.current.srcObject = s }); } }, [activeTab]); const handleSaveConfig = async (newConfig: ConfigItem[]) => { try { await comms.saveConfig(JSON.stringify(newConfig)); setConfig(newConfig); addLog("CONFIGURATION SAVED", "success"); } catch (e) { console.error(e); addLog("FAILED TO SAVE CONFIG", "error"); } }; return (
{/* Animated Nebula Background */}
{/* LOADING OVERLAY */} {isLoading && (

SYSTEM INITIALIZING

ESTABLISHING CONNECTION...

)} {/* HEADER */}

EQUI-HANDLER PRO

SYS.VER 4.2.0 | LINK: {isWebView ? "NATIVE" : "SIMULATION"}
{/* Top Navigation */}
{[ { id: 'recipe', icon: Layers, label: 'RECIPE' }, { id: 'io', icon: Activity, label: 'I/O MONITOR' }, { id: 'motion', icon: Move, label: 'MOTION' }, { id: 'camera', icon: Camera, label: 'VISION' }, { id: 'setting', icon: Settings, label: 'CONFIG' } ].map(item => ( ))}
{currentTime.toLocaleTimeString('en-GB')}
{currentTime.toLocaleDateString().toUpperCase()}
{/* MAIN CONTENT */}
{/* 3D Canvas (Background Layer) */}
{/* Center Alarms */}
{isEmergencyStop && (

EMERGENCY STOP

SYSTEM HALTED - RELEASE TO RESET

)} {isLowPressure && !isEmergencyStop && (
LOW AIR PRESSURE WARNING
)}
{/* Floating Panel (Left) */} {activeTab && activeTab !== 'setting' && (
{activeTab === 'recipe' && { setCurrentRecipe(r); addLog(`LOADED: ${r.name}`) }} />} {activeTab === 'io' && } {activeTab === 'motion' && } {activeTab === 'camera' && }
)} {/* Settings Modal */} setActiveTab(null)} config={config} isRefreshing={isConfigRefreshing} onSave={handleSaveConfig} /> {/* Right Sidebar (Dashboard) */}
System Status
{systemState}
handleControl('start')} > START AUTO handleControl('stop')} > STOP / PAUSE handleControl('reset')} > SYSTEM RESET
Event Log
{logs.map(log => (
[{log.timestamp}] {log.message}
))}
{/* FOOTER STATUS */}
); }