style: replace virtual inner with logs list
This commit is contained in:
@@ -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<LogsListProps> = (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<any>({});
|
||||
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 (
|
||||
<div
|
||||
className="logs-wrap"
|
||||
style={{ height: innerHieght }}
|
||||
ref={scroller}
|
||||
onWheel={handleOnWheel}
|
||||
>
|
||||
<div className={classNames('content')}>
|
||||
{_.map(dataList, (item: any, index: number) => {
|
||||
return (
|
||||
<div key={item.uid} className="text">
|
||||
{item.content}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(LogsList);
|
||||
@@ -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;
|
||||
@@ -4,7 +4,7 @@
|
||||
.pg {
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
right: 16px;
|
||||
right: 20px;
|
||||
width: 40px;
|
||||
height: 130px;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -1,139 +0,0 @@
|
||||
import { useRef, useState } from 'react';
|
||||
|
||||
const usePaseByLine = () => {
|
||||
const [result, setResult] = useState<any[]>([]);
|
||||
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<string, string> = {
|
||||
'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;
|
||||
@@ -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<LogsViewerProps> = 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<LogsViewerProps> = forwardRef((props, ref) => {
|
||||
<div className="logs-viewer-wrap-w2">
|
||||
<div className="wrap">
|
||||
<div className={classNames('content')}>
|
||||
<LogsInner
|
||||
data={logs}
|
||||
<LogsList
|
||||
dataList={logs}
|
||||
diffHeight={diffHeight}
|
||||
onScroll={handleOnScroll}
|
||||
></LogsInner>
|
||||
></LogsList>
|
||||
</div>
|
||||
<Spin
|
||||
spinning={loading && isAtTop}
|
||||
|
||||
Reference in New Issue
Block a user