import { useState, useEffect, useRef, useMemo, memo } from 'react'; import { LogService, LogEntry } from '@esengine/editor-core'; import { LogLevel } from '@esengine/ecs-framework'; import { Trash2, AlertCircle, Info, AlertTriangle, XCircle, Bug, Search, Wifi } from 'lucide-react'; import { JsonViewer } from './JsonViewer'; import '../styles/ConsolePanel.css'; interface ConsolePanelProps { logService: LogService; } const MAX_LOGS = 1000; // 提取JSON检测和格式化逻辑 function tryParseJSON(message: string): { isJSON: boolean; parsed?: unknown } { try { const parsed: unknown = JSON.parse(message); return { isJSON: true, parsed }; } catch { return { isJSON: false }; } } // 格式化时间 function formatTime(date: Date): string { const hours = date.getHours().toString().padStart(2, '0'); const minutes = date.getMinutes().toString().padStart(2, '0'); const seconds = date.getSeconds().toString().padStart(2, '0'); const ms = date.getMilliseconds().toString().padStart(3, '0'); return `${hours}:${minutes}:${seconds}.${ms}`; } // 日志等级图标 function getLevelIcon(level: LogLevel) { switch (level) { case LogLevel.Debug: return ; case LogLevel.Info: return ; case LogLevel.Warn: return ; case LogLevel.Error: case LogLevel.Fatal: return ; default: return ; } } // 日志等级样式类 function getLevelClass(level: LogLevel): string { switch (level) { case LogLevel.Debug: return 'log-entry-debug'; case LogLevel.Info: return 'log-entry-info'; case LogLevel.Warn: return 'log-entry-warn'; case LogLevel.Error: case LogLevel.Fatal: return 'log-entry-error'; default: return ''; } } // 单个日志条目组件 const LogEntryItem = memo(({ log, onOpenJsonViewer }: { log: LogEntry; // eslint-disable-next-line @typescript-eslint/no-explicit-any onOpenJsonViewer: (data: any) => void; }) => { const { isJSON, parsed } = useMemo(() => tryParseJSON(log.message), [log.message]); const shouldTruncate = log.message.length > 200; const [isExpanded, setIsExpanded] = useState(false); return (
{getLevelIcon(log.level)}
{formatTime(log.timestamp)}
[{log.source === 'remote' ? '🌐 Remote' : log.source}]
{log.clientId && (
{log.clientId}
)}
{shouldTruncate && !isExpanded ? ( <> {log.message.substring(0, 200)}... ) : ( <> {log.message} {shouldTruncate && ( )} )}
{isJSON && parsed !== undefined && ( )}
); }); LogEntryItem.displayName = 'LogEntryItem'; export function ConsolePanel({ logService }: ConsolePanelProps) { // 状态管理 const [logs, setLogs] = useState(() => logService.getLogs().slice(-MAX_LOGS)); const [filter, setFilter] = useState(''); const [levelFilter, setLevelFilter] = useState>(new Set([ LogLevel.Debug, LogLevel.Info, LogLevel.Warn, LogLevel.Error, LogLevel.Fatal ])); const [showRemoteOnly, setShowRemoteOnly] = useState(false); const [autoScroll, setAutoScroll] = useState(true); // eslint-disable-next-line @typescript-eslint/no-explicit-any const [jsonViewerData, setJsonViewerData] = useState(null); const logContainerRef = useRef(null); // 订阅日志更新 useEffect(() => { const unsubscribe = logService.subscribe((entry) => { setLogs((prev) => { const newLogs = [...prev, entry]; return newLogs.length > MAX_LOGS ? newLogs.slice(-MAX_LOGS) : newLogs; }); }); return unsubscribe; }, [logService]); // 自动滚动 useEffect(() => { if (autoScroll && logContainerRef.current) { logContainerRef.current.scrollTop = logContainerRef.current.scrollHeight; } }, [logs, autoScroll]); // 处理滚动 const handleScroll = () => { if (logContainerRef.current) { const { scrollTop, scrollHeight, clientHeight } = logContainerRef.current; const isAtBottom = Math.abs(scrollHeight - scrollTop - clientHeight) < 10; setAutoScroll(isAtBottom); } }; // 清空日志 const handleClear = () => { logService.clear(); setLogs([]); }; // 切换等级过滤 const toggleLevelFilter = (level: LogLevel) => { const newFilter = new Set(levelFilter); if (newFilter.has(level)) { newFilter.delete(level); } else { newFilter.add(level); } setLevelFilter(newFilter); }; // 过滤日志 const filteredLogs = useMemo(() => { return logs.filter((log) => { if (!levelFilter.has(log.level)) return false; if (showRemoteOnly && log.source !== 'remote') return false; if (filter && !log.message.toLowerCase().includes(filter.toLowerCase())) { return false; } return true; }); }, [logs, levelFilter, showRemoteOnly, filter]); // 统计各等级日志数量 const levelCounts = useMemo(() => ({ [LogLevel.Debug]: logs.filter((l) => l.level === LogLevel.Debug).length, [LogLevel.Info]: logs.filter((l) => l.level === LogLevel.Info).length, [LogLevel.Warn]: logs.filter((l) => l.level === LogLevel.Warn).length, [LogLevel.Error]: logs.filter((l) => l.level === LogLevel.Error || l.level === LogLevel.Fatal).length }), [logs]); const remoteLogCount = useMemo(() => logs.filter((l) => l.source === 'remote').length , [logs]); return (
setFilter(e.target.value)} />
{filteredLogs.length === 0 ? (

No logs to display

) : ( filteredLogs.map((log) => ( )) )}
{jsonViewerData && ( setJsonViewerData(null)} /> )} {!autoScroll && ( )}
); }