/** * Serial Monitor — shows Arduino Serial output and allows sending data back. * Connects to the AVRSimulator USART via the Zustand store. */ import React, { useRef, useEffect, useState, useCallback } from 'react'; import { useSimulatorStore } from '../../store/useSimulatorStore'; export const SerialMonitor: React.FC = () => { const serialOutput = useSimulatorStore((s) => s.serialOutput); const serialBaudRate = useSimulatorStore((s) => s.serialBaudRate); const running = useSimulatorStore((s) => s.running); const serialWrite = useSimulatorStore((s) => s.serialWrite); const clearSerialOutput = useSimulatorStore((s) => s.clearSerialOutput); const [inputValue, setInputValue] = useState(''); const [lineEnding, setLineEnding] = useState<'none' | 'nl' | 'cr' | 'both'>('nl'); const [autoscroll, setAutoscroll] = useState(true); const outputRef = useRef(null); // Auto-scroll to bottom when new output arrives useEffect(() => { if (autoscroll && outputRef.current) { outputRef.current.scrollTop = outputRef.current.scrollHeight; } }, [serialOutput, autoscroll]); const handleSend = useCallback(() => { if (!inputValue && lineEnding === 'none') return; let text = inputValue; switch (lineEnding) { case 'nl': text += '\n'; break; case 'cr': text += '\r'; break; case 'both': text += '\r\n'; break; } serialWrite(text); setInputValue(''); }, [inputValue, lineEnding, serialWrite]); const handleKeyDown = useCallback((e: React.KeyboardEvent) => { if (e.key === 'Enter') { e.preventDefault(); handleSend(); } }, [handleSend]); return (
{/* Header */}
Serial Monitor
{serialBaudRate > 0 && ( {serialBaudRate.toLocaleString()} baud )}
{/* Output area */}
        {serialOutput || (running ? 'Waiting for serial data...\n' : 'Start simulation to see serial output.\n')}
      
{/* Input row */}
setInputValue(e.target.value)} onKeyDown={handleKeyDown} placeholder="Type message to send..." style={styles.input} disabled={!running} />
); }; const styles: Record = { container: { display: 'flex', flexDirection: 'column', height: '100%', background: '#1e1e1e', borderTop: '1px solid #333', fontFamily: 'monospace', fontSize: 13, }, header: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '4px 8px', background: '#252526', borderBottom: '1px solid #333', minHeight: 28, }, title: { color: '#cccccc', fontWeight: 600, fontSize: 12, }, headerControls: { display: 'flex', alignItems: 'center', gap: 8, }, baudRate: { color: '#569cd6', fontSize: 11, fontFamily: 'monospace', background: '#1e1e1e', border: '1px solid #3a3a3a', borderRadius: 3, padding: '1px 6px', }, autoscrollLabel: { color: '#999', fontSize: 11, display: 'flex', alignItems: 'center', gap: 4, cursor: 'pointer', }, checkbox: { margin: 0, cursor: 'pointer', }, clearBtn: { background: 'transparent', border: '1px solid #555', color: '#ccc', padding: '2px 8px', borderRadius: 3, cursor: 'pointer', fontSize: 11, }, output: { flex: 1, margin: 0, padding: 8, color: '#00ff41', background: '#0a0a0a', overflowY: 'auto', whiteSpace: 'pre-wrap', wordBreak: 'break-all', minHeight: 0, fontSize: 13, lineHeight: '1.4', }, inputRow: { display: 'flex', gap: 4, padding: 4, background: '#252526', borderTop: '1px solid #333', }, input: { flex: 1, background: '#1e1e1e', border: '1px solid #444', color: '#ccc', padding: '4px 8px', borderRadius: 3, fontFamily: 'monospace', fontSize: 12, outline: 'none', }, select: { background: '#1e1e1e', border: '1px solid #444', color: '#ccc', padding: '4px', borderRadius: 3, fontSize: 11, outline: 'none', }, sendBtn: { background: '#0e639c', border: 'none', color: '#fff', padding: '4px 12px', borderRadius: 3, cursor: 'pointer', fontSize: 12, fontWeight: 600, }, };