import React, { useState, useRef, useEffect } from 'react'; import { serialService } from '../services/serialService'; import { Terminal as TerminalIcon, Send, Trash2, ArrowUp, ArrowDown } from 'lucide-react'; interface LogEntry { id: number; type: 'tx' | 'rx' | 'info' | 'error'; timestamp: string; data: string; } const Terminal: React.FC = () => { const [logs, setLogs] = useState([]); const [input, setInput] = useState(''); const [autoScroll, setAutoScroll] = useState(true); const logsContainerRef = useRef(null); const MAX_LOGS = 200; useEffect(() => { // Subscribe to serial service logs serialService.setLogCallback((type, data) => { setLogs(prev => { const newLog: LogEntry = { id: Date.now() + Math.random(), // Ensure unique key for fast updates type, timestamp: new Date().toLocaleTimeString([], { hour12: false, hour: "2-digit", minute: "2-digit", second: "2-digit" }), data }; // NEWEST FIRST: Prepend new log to the beginning of the array const newLogs = [newLog, ...prev]; if (newLogs.length > MAX_LOGS) { return newLogs.slice(0, MAX_LOGS); } return newLogs; }); }); return () => { serialService.setLogCallback(null); }; }, []); useEffect(() => { // If auto-scroll is on, force scroll to top when logs change if (autoScroll && logsContainerRef.current) { logsContainerRef.current.scrollTop = 0; } }, [logs, autoScroll]); const handleSend = async () => { if (!input.trim()) return; // Remove spaces and validate hex const hexStr = input.replace(/\s+/g, ''); if (!/^[0-9A-Fa-f]+$/.test(hexStr) || hexStr.length % 2 !== 0) { // Local error log (prepended) setLogs(prev => [{ id: Date.now(), type: 'error', timestamp: new Date().toLocaleTimeString(), data: '유효하지 않은 HEX 포맷입니다.' }, ...prev]); return; } const bytes = new Uint8Array(hexStr.match(/.{1,2}/g)!.map(byte => parseInt(byte, 16))); try { await serialService.sendRaw(bytes); // readRaw also triggers callback inside service await serialService.readRaw(); } catch (e: any) { // Errors from service are logged via callback usually } setInput(''); }; return (
Terminal / Logs
{/* Input Area (Top) or Bottom? Keeping input at bottom is standard, logs flow down from top */}
{logs.length === 0 &&
대기 중... (Logs)
} {logs.map(log => (
[{log.timestamp}]
{log.type === 'tx' && TX} {log.type === 'rx' && RX} {log.type === 'error' && ERR} {log.type === 'info' && INF} {log.data}
))}
setInput(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && handleSend()} placeholder="HEX (e.g. DD A5 03...)" className="flex-1 bg-white border border-gray-300 rounded px-2 py-1.5 text-gray-900 focus:outline-none focus:border-blue-500 font-mono text-xs" />
); }; export default Terminal;