diff --git a/src/components/logs-viewer/logs-list.tsx b/src/components/logs-viewer/logs-list.tsx new file mode 100644 index 00000000..557d9802 --- /dev/null +++ b/src/components/logs-viewer/logs-list.tsx @@ -0,0 +1,111 @@ +import useOverlayScroller from '@/hooks/use-overlay-scroller'; +import classNames from 'classnames'; +import _, { throttle } from 'lodash'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import './styles/logs-list.less'; + +interface LogsListProps { + dataList: any[]; + height?: number; + onScroll?: (isTop: boolean) => void; + diffHeight?: number; +} +const LogsList: React.FC = (props) => { + const { dataList, height, onScroll, diffHeight = 96 } = props; + const { + initialize, + updateScrollerPosition, + generateInstance, + scrollEventElement, + instance, + initialized + } = useOverlayScroller({ + theme: 'os-theme-light' + }); + const viewportHeight = window.innerHeight; + const viewHeight = viewportHeight - diffHeight; + const [innerHieght, setInnerHeight] = useState(viewHeight); + const scroller = useRef({}); + const stopScroll = useRef(false); + + const debounceResetStopScroll = _.debounce(() => { + stopScroll.current = false; + }, 30000); + + const handleOnWheel = useCallback( + (e: any) => { + const scrollTop = scrollEventElement?.scrollTop; + const scrollHeight = scrollEventElement?.scrollHeight; + const clientHeight = scrollEventElement?.clientHeight; + // is scroll to bottom + stopScroll.current = scrollTop + clientHeight <= scrollHeight; + // is scroll to top + if (scrollTop === 0) { + onScroll?.(true); + } else { + onScroll?.(false); + } + debounceResetStopScroll(); + }, + [debounceResetStopScroll, scrollEventElement] + ); + + const debounceUpdateScrollerPosition = _.debounce(() => { + generateInstance(); + updateScrollerPosition(0); + }, 200); + + useEffect(() => { + const handleResize = throttle(() => { + const viewportHeight = window.innerHeight; + const viewHeight = viewportHeight - diffHeight; + setInnerHeight(viewHeight); + }, 100); + window.addEventListener('resize', handleResize); + return () => { + window.removeEventListener('resize', handleResize); + }; + }, [diffHeight]); + + useEffect(() => { + if (scroller.current) { + initialize(scroller.current); + } + }, [scroller.current, initialize]); + + useEffect(() => { + if (dataList.length && !stopScroll.current && instance) { + updateScrollerPosition(0); + } else if (dataList.length && !stopScroll.current && scroller.current) { + if (!initialized) { + initialize(scroller.current); + } + if (!instance) { + debounceUpdateScrollerPosition(); + } else { + updateScrollerPosition(0); + } + } + }, [dataList, stopScroll.current, instance, scroller.current]); + + return ( +
+
+ {_.map(dataList, (item: any, index: number) => { + return ( +
+ {item.content} +
+ ); + })} +
+
+ ); +}; + +export default React.memo(LogsList); diff --git a/src/components/logs-viewer/new-parse-ansi.ts b/src/components/logs-viewer/new-parse-ansi.ts deleted file mode 100644 index a443d240..00000000 --- a/src/components/logs-viewer/new-parse-ansi.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { controlSeqRegex, replaceLineRegex } from './config'; - -const useParseAnsi = () => { - const removeBrackets = (str) => str?.replace?.(/^\(…\)/, ''); - - const getRowContent = (screen, row) => { - let rowContent = []; - let col = 0; - while (screen.has(`${row},${col}`)) { - rowContent.push(screen.get(`${row},${col}`)); - col++; - } - return rowContent.join(''); - }; - - const handleText = function* (text, screen, cursor) { - for (let char of text) { - if (char === '\r') { - cursor.col = 0; // Move to the beginning of the line - } else if (char === '\n') { - // Move to the next line - yield getRowContent(screen, cursor.row); - cursor.row++; - cursor.col = 0; - } else { - const position = `${cursor.row},${cursor.col}`; - screen.set(position, char); - cursor.col++; - } - } - }; - - const parseAnsi = function* (inputStr, setId) { - let cursor = { row: 0, col: 0 }; - const screen = new Map(); - let lastIndex = 0; - - // Replace \r\n with \n - const input = inputStr.replace(replaceLineRegex, '\n'); - let match; - - while ((match = controlSeqRegex.exec(input)) !== null) { - const textBeforeControl = input.slice(lastIndex, match.index); - yield* handleText(textBeforeControl, screen, cursor); - lastIndex = controlSeqRegex.lastIndex; - - const [_, n = '1', m = '1', command] = match; - const parsedN = parseInt(n, 10); - const parsedM = parseInt(m, 10); - - switch (command) { - case 'A': - cursor.row = Math.max(0, cursor.row - parsedN); - break; - case 'B': - cursor.row += parsedN; - break; - case 'C': - cursor.col += parsedN; - break; - case 'D': - cursor.col = Math.max(0, cursor.col - parsedN); - break; - case 'H': - cursor.row = Math.max(0, parsedN - 1); - cursor.col = Math.max(0, parsedM - 1); - break; - case 'J': - if (parsedN === 2) { - screen.clear(); - cursor.row = 0; - cursor.col = 0; - } - break; - case 'm': - // Skipping color handling in this basic example; can be expanded as needed - break; - } - } - - yield* handleText(input.slice(lastIndex), screen, cursor); - - // Yield remaining rows - for (let row = 0; row <= cursor.row; row++) { - yield { - content: removeBrackets(getRowContent(screen, row)), - uid: setId() - }; - } - }; - - return { parseAnsi }; -}; - -export default useParseAnsi; diff --git a/src/components/logs-viewer/styles/index.less b/src/components/logs-viewer/styles/index.less index 8747e6fc..63ac7e1d 100644 --- a/src/components/logs-viewer/styles/index.less +++ b/src/components/logs-viewer/styles/index.less @@ -4,7 +4,7 @@ .pg { position: absolute; top: 16px; - right: 16px; + right: 20px; width: 40px; height: 130px; diff --git a/src/components/logs-viewer/styles/logs-list.less b/src/components/logs-viewer/styles/logs-list.less new file mode 100644 index 00000000..f20bc1e5 --- /dev/null +++ b/src/components/logs-viewer/styles/logs-list.less @@ -0,0 +1,26 @@ +.logs-wrap { + background-color: var(--color-logs-bg); + border-radius: var(--border-radius-mini); + font-family: monospace, Menlo, Courier, 'Courier New', Consolas, Monaco, + 'Liberation Mono' !important; + + .content { + word-wrap: break-word; + height: 100%; + padding-right: 2px; + + &.line-break { + word-wrap: break-word; + } + + .text { + min-height: 22px; + } + + color: var(--color-logs-text); + font-size: var(--font-size-small); + line-height: 22px; + white-space: pre-wrap; + background-color: var(--color-logs-bg); + } +} diff --git a/src/components/logs-viewer/use-logs-pagination.ts b/src/components/logs-viewer/use-logs-pagination.ts index 979573f2..0e1b045c 100644 --- a/src/components/logs-viewer/use-logs-pagination.ts +++ b/src/components/logs-viewer/use-logs-pagination.ts @@ -1,7 +1,7 @@ import { useState } from 'react'; const useLogsPagination = () => { - const [pageSize, setPageSize] = useState(1000); + const [pageSize, setPageSize] = useState(500); const [page, setPage] = useState(1); const [total, setTotal] = useState(1); diff --git a/src/components/logs-viewer/use-parse-byline.ts b/src/components/logs-viewer/use-parse-byline.ts deleted file mode 100644 index 28c7fada..00000000 --- a/src/components/logs-viewer/use-parse-byline.ts +++ /dev/null @@ -1,139 +0,0 @@ -import { useRef, useState } from 'react'; - -const usePaseByLine = () => { - const [result, setResult] = useState([]); - const uidRef = useRef(0); - - const cursorRow = useRef(0); // current row - const cursorCol = useRef(0); // current column - // screen.current content array - const screen = useRef([['']]); - // replace carriage return and newline characters in the text - // let input = inputStr.replace(/\r\n/g, '\n') - - const removeBrackets = (str: string) => { - return str?.replace?.(/^\(\.*?\)/, ''); - }; - - const setId = () => { - uidRef.current += 1; - return uidRef.current; - }; - - // handle the \r and \n characters in the text - const handleText = (text: string) => { - for (let char of text) { - if (char === '\r') { - cursorCol.current = 0; // move to the beginning of the line - } else if (char === '\n') { - cursorRow.current += 1; // move to the next line - cursorCol.current = 0; // move to the beginning of the line - screen.current[cursorRow.current] = screen.current[ - cursorRow.current - ] || ['']; // create a new line if it does not exist - } else { - // add the character to the screen.current content array - screen.current[cursorRow.current][cursorCol.current] = char; - cursorCol.current += 1; - } - } - }; - - // ANSI - const controlSeqRegex = /\x1b\[(\d*);?(\d*)?([A-DJKHfm])/g; - - // ANSI color map - const colorMap: Record = { - '30': 'black', - '31': 'red', - '32': 'green', - '33': 'yellow', - '34': 'blue', - '35': 'magenta', - '36': 'cyan', - '37': 'white' - }; - - const updateResult = () => { - let res = []; - for (let row = 0; row < screen.current.length; row++) { - console.log('screen.current[row]', screen.current[row]); - let rowContent = screen.current[row].join(''); - res.push({ - content: removeBrackets(rowContent), - uid: setId() - }); - } - console.log('res', res); - setResult(res); - }; - - const handleLine = (line: string) => { - let match; - let lastIndex = 0; - let currentStyle = ''; // current text style - // match ANSI control characters - while ((match = controlSeqRegex.exec(line)) !== null) { - // handle text before the control character - let textBeforeControl = line.slice(lastIndex, match.index); - - handleText(textBeforeControl); // add the processed text to the output - lastIndex = controlSeqRegex.lastIndex; // update the last index - - const n = parseInt(match[1], 10) || 1; - const m = parseInt(match[2], 10) || 1; - const command = match[3]; - - // handle ANSI control characters - switch (command) { - case 'A': // up - cursorRow.current = Math.max(0, cursorRow.current - n); - break; - case 'B': // down - cursorRow.current += n; - break; - case 'C': // right - cursorCol.current += n; - break; - case 'D': // left - cursorCol.current = Math.max(0, cursorCol.current - n); - break; - case 'H': // move the cursor to the specified position (n, m) - cursorRow.current = Math.max(0, n - 1); - cursorCol.current = Math.max(0, m - 1); - break; - case 'J': // clear the screen.current - if (n === 2) { - screen.current = [['']]; - cursorRow.current = 0; - cursorCol.current = 0; - } - break; - case 'm': // color - if (match[1] === '0') { - currentStyle = ''; - } else if (colorMap[match[1]]) { - currentStyle = `color: ${colorMap[match[1]]};`; - } - break; - } - - // check if the row and column are within the screen.current content array - while (screen.current.length <= cursorRow.current) { - screen.current.push(['']); - } - while (screen.current[cursorRow.current].length <= cursorCol.current) { - screen.current[cursorRow.current].push(''); - } - } - handleText(line.slice(lastIndex)); - updateResult(); - }; - - return { - handleLine, - result - }; -}; - -export default usePaseByLine; diff --git a/src/components/logs-viewer/logs-inner.tsx b/src/components/logs-viewer/virtual-inner.tsx similarity index 100% rename from src/components/logs-viewer/logs-inner.tsx rename to src/components/logs-viewer/virtual-inner.tsx diff --git a/src/components/logs-viewer/virtual-log-list.tsx b/src/components/logs-viewer/virtual-log-list.tsx index 85545418..a02fe604 100644 --- a/src/components/logs-viewer/virtual-log-list.tsx +++ b/src/components/logs-viewer/virtual-log-list.tsx @@ -2,7 +2,7 @@ import useSetChunkFetch from '@/hooks/use-chunk-fetch'; import { Spin } from 'antd'; import classNames from 'classnames'; import _ from 'lodash'; -import { +import React, { forwardRef, memo, useCallback, @@ -12,7 +12,7 @@ import { useState } from 'react'; import { controlSeqRegex, replaceLineRegex } from './config'; -import LogsInner from './logs-inner'; +import LogsList from './logs-list'; import LogsPagination from './logs-pagination'; import './styles/index.less'; import useLogsPagination from './use-logs-pagination'; @@ -132,7 +132,6 @@ const LogsViewer: React.FC = forwardRef((props, ref) => { const updateContent = useCallback( (inputStr: string) => { - console.log('data========', inputStr); const data = inputStr.replace(replaceLineRegex, '\n'); if (isClean(data)) { cacheDataRef.current = data; @@ -188,11 +187,11 @@ const LogsViewer: React.FC = forwardRef((props, ref) => {
- + >