({ logService, locale = 'en', onClose }: OutputLogPanelProps)
| 146 | LogEntryItem.displayName = 'LogEntryItem'; |
| 147 | |
| 148 | export function OutputLogPanel({ logService, locale = 'en', onClose }: OutputLogPanelProps) { |
| 149 | const { t } = useLocale(); |
| 150 | const [logs, setLogs] = useState<LogEntry[]>(() => logService.getLogs().slice(-MAX_LOGS)); |
| 151 | const [searchQuery, setSearchQuery] = useState(''); |
| 152 | const [levelFilter, setLevelFilter] = useState<Set<LogLevel>>(new Set([ |
| 153 | LogLevel.Debug, |
| 154 | LogLevel.Info, |
| 155 | LogLevel.Warn, |
| 156 | LogLevel.Error, |
| 157 | LogLevel.Fatal |
| 158 | ])); |
| 159 | const [showRemoteOnly, setShowRemoteOnly] = useState(false); |
| 160 | const [autoScroll, setAutoScroll] = useState(true); |
| 161 | const [showFilterMenu, setShowFilterMenu] = useState(false); |
| 162 | const [showSettingsMenu, setShowSettingsMenu] = useState(false); |
| 163 | const [expandedLogIds, setExpandedLogIds] = useState<Set<string>>(new Set()); |
| 164 | const logContainerRef = useRef<HTMLDivElement>(null); |
| 165 | const filterMenuRef = useRef<HTMLDivElement>(null); |
| 166 | const settingsMenuRef = useRef<HTMLDivElement>(null); |
| 167 | |
| 168 | useEffect(() => { |
| 169 | const unsubscribe = logService.subscribe((entry) => { |
| 170 | setLogs((prev) => { |
| 171 | const newLogs = [...prev, entry]; |
| 172 | return newLogs.length > MAX_LOGS ? newLogs.slice(-MAX_LOGS) : newLogs; |
| 173 | }); |
| 174 | }); |
| 175 | return unsubscribe; |
| 176 | }, [logService]); |
| 177 | |
| 178 | useEffect(() => { |
| 179 | if (autoScroll && logContainerRef.current) { |
| 180 | logContainerRef.current.scrollTop = logContainerRef.current.scrollHeight; |
| 181 | } |
| 182 | }, [logs, autoScroll]); |
| 183 | |
| 184 | useEffect(() => { |
| 185 | const handleClickOutside = (e: MouseEvent) => { |
| 186 | if (filterMenuRef.current && !filterMenuRef.current.contains(e.target as Node)) { |
| 187 | setShowFilterMenu(false); |
| 188 | } |
| 189 | if (settingsMenuRef.current && !settingsMenuRef.current.contains(e.target as Node)) { |
| 190 | setShowSettingsMenu(false); |
| 191 | } |
| 192 | }; |
| 193 | document.addEventListener('mousedown', handleClickOutside); |
| 194 | return () => document.removeEventListener('mousedown', handleClickOutside); |
| 195 | }, []); |
| 196 | |
| 197 | const handleScroll = useCallback(() => { |
| 198 | if (logContainerRef.current) { |
| 199 | const { scrollTop, scrollHeight, clientHeight } = logContainerRef.current; |
| 200 | const isAtBottom = Math.abs(scrollHeight - scrollTop - clientHeight) < 10; |
| 201 | setAutoScroll(isAtBottom); |
| 202 | } |
| 203 | }, []); |
| 204 | |
| 205 | const handleClear = useCallback(() => { |
nothing calls this directly
no test coverage detected