diff --git a/src/renderer/components/dock/logs/log-list.tsx b/src/renderer/components/dock/logs/log-list.tsx new file mode 100644 index 0000000000..56202e0676 --- /dev/null +++ b/src/renderer/components/dock/logs/log-list.tsx @@ -0,0 +1,108 @@ +import { useVirtualizer } from '@tanstack/react-virtual'; +import AnsiUp from 'ansi_up'; +import DOMPurify from 'dompurify'; +import React, { useRef } from 'react'; +import { SearchStore } from '../../../search-store/search-store'; +import { cssNames } from '../../../utils'; +import type { LogTabViewModel } from './logs-view-model'; + +export interface LogListProps { + model: LogTabViewModel; +} + +export const LogList = ({ model }: LogListProps) => { + const { logs } = model; + const parentRef = useRef(null) + const rowVirtualizer = useVirtualizer({ + count: logs.get().length, + getScrollElement: () => parentRef.current, + estimateSize: () => 38, + overscan: 5 + }) + + return ( +
+
+ {rowVirtualizer.getVirtualItems().map((virtualRow) => ( +
+
+ +
+
+ ))} +
+
+ ) +} + +const colorConverter = new AnsiUp(); + +function LogRow({ rowIndex, model }: { rowIndex: number; model: LogTabViewModel }) { + const { searchQuery, isActiveOverlay } = model.searchStore; + const log = model.logs.get()[rowIndex]; + const contents: React.ReactElement[] = []; + const ansiToHtml = (ansi: string) => DOMPurify.sanitize(colorConverter.ansi_to_html(ansi)); + + if (searchQuery) { // If search is enabled, replace keyword with backgrounded + // Case-insensitive search (lowercasing query and keywords in line) + const regex = new RegExp(SearchStore.escapeRegex(searchQuery), "gi"); + const matches = log.matchAll(regex); + const modified = log.replace(regex, match => match.toLowerCase()); + // Splitting text line by keyword + const pieces = modified.split(searchQuery.toLowerCase()); + + pieces.forEach((piece, index) => { + const active = isActiveOverlay(rowIndex, index); + const lastItem = index === pieces.length - 1; + const overlayValue = matches.next().value; + const overlay = !lastItem + ? ( + + ) + : null; + + contents.push( + + + {overlay} + , + ); + }); + } + + return ( +
+ {contents.length > 1 ? contents : ( + + )} + {/* For preserving copy-paste experience and keeping line breaks */} +
+
+ ); +} \ No newline at end of file