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 {
|
.pg {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 16px;
|
top: 16px;
|
||||||
right: 16px;
|
right: 20px;
|
||||||
width: 40px;
|
width: 40px;
|
||||||
height: 130px;
|
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';
|
import { useState } from 'react';
|
||||||
|
|
||||||
const useLogsPagination = () => {
|
const useLogsPagination = () => {
|
||||||
const [pageSize, setPageSize] = useState(1000);
|
const [pageSize, setPageSize] = useState(500);
|
||||||
const [page, setPage] = useState(1);
|
const [page, setPage] = useState(1);
|
||||||
const [total, setTotal] = 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 { Spin } from 'antd';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import {
|
import React, {
|
||||||
forwardRef,
|
forwardRef,
|
||||||
memo,
|
memo,
|
||||||
useCallback,
|
useCallback,
|
||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
useState
|
useState
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import { controlSeqRegex, replaceLineRegex } from './config';
|
import { controlSeqRegex, replaceLineRegex } from './config';
|
||||||
import LogsInner from './logs-inner';
|
import LogsList from './logs-list';
|
||||||
import LogsPagination from './logs-pagination';
|
import LogsPagination from './logs-pagination';
|
||||||
import './styles/index.less';
|
import './styles/index.less';
|
||||||
import useLogsPagination from './use-logs-pagination';
|
import useLogsPagination from './use-logs-pagination';
|
||||||
@@ -132,7 +132,6 @@ const LogsViewer: React.FC<LogsViewerProps> = forwardRef((props, ref) => {
|
|||||||
|
|
||||||
const updateContent = useCallback(
|
const updateContent = useCallback(
|
||||||
(inputStr: string) => {
|
(inputStr: string) => {
|
||||||
console.log('data========', inputStr);
|
|
||||||
const data = inputStr.replace(replaceLineRegex, '\n');
|
const data = inputStr.replace(replaceLineRegex, '\n');
|
||||||
if (isClean(data)) {
|
if (isClean(data)) {
|
||||||
cacheDataRef.current = data;
|
cacheDataRef.current = data;
|
||||||
@@ -188,11 +187,11 @@ const LogsViewer: React.FC<LogsViewerProps> = forwardRef((props, ref) => {
|
|||||||
<div className="logs-viewer-wrap-w2">
|
<div className="logs-viewer-wrap-w2">
|
||||||
<div className="wrap">
|
<div className="wrap">
|
||||||
<div className={classNames('content')}>
|
<div className={classNames('content')}>
|
||||||
<LogsInner
|
<LogsList
|
||||||
data={logs}
|
dataList={logs}
|
||||||
diffHeight={diffHeight}
|
diffHeight={diffHeight}
|
||||||
onScroll={handleOnScroll}
|
onScroll={handleOnScroll}
|
||||||
></LogsInner>
|
></LogsList>
|
||||||
</div>
|
</div>
|
||||||
<Spin
|
<Spin
|
||||||
spinning={loading && isAtTop}
|
spinning={loading && isAtTop}
|
||||||
|
|||||||
Reference in New Issue
Block a user