import useSetChunkRequest from '@/hooks/use-chunk-request'; import { FitAddon } from '@xterm/addon-fit'; import { Terminal } from '@xterm/xterm'; import '@xterm/xterm/css/xterm.css'; import classNames from 'classnames'; import _ from 'lodash'; import { forwardRef, memo, useEffect, useImperativeHandle, useRef, useState } from 'react'; import { replaceLineRegex } from './config'; import './styles/xterm-viewer.less'; import useSize from './use-size'; interface LogsViewerProps { height: number; content?: string; url: string; ref?: any; params?: object; } const LogsViewer: React.FC = forwardRef((props, ref) => { const { height, content, url } = props; const { setChunkRequest } = useSetChunkRequest(); const chunkRequedtRef = useRef(null); const scroller = useRef({}); const termRef = useRef({}); const termwrapRef = useRef({}); const fitAddonRef = useRef({}); const cacheDataRef = useRef(null); const [logs, setLogs] = useState(''); const [loading, setLoading] = useState(false); const size = useSize(scroller); const logParseWorker = useRef(null); const lineCountRef = useRef(0); useImperativeHandle(ref, () => ({ abort() { chunkRequedtRef.current?.current?.abort?.(); logParseWorker.current?.terminate?.(); } })); useEffect(() => { logParseWorker.current?.terminate?.(); logParseWorker.current = new Worker( // @ts-ignore new URL('./parse-worker.ts', import.meta.url), { type: 'module' } ); logParseWorker.current.onmessage = (event: any) => { const { result, lines } = event.data; lineCountRef.current = lines; // setLogs(result.join('\n')); console.log('res+++++++++++', result); const data = result.map((item: any) => item.content); termRef.current?.write?.(data.join('\n')); }; return () => { if (logParseWorker.current) { logParseWorker.current.terminate(); } }; }, []); const throttleScroll = _.throttle(() => { termRef.current?.scrollToBottom?.(); }, 100); const debounceLoading = _.debounce(() => { setLoading(false); }, 200); const updateContent = (inputStr: string) => { const data = inputStr.replace(replaceLineRegex, '\n'); cacheDataRef.current = data; setLoading(true); logParseWorker.current.postMessage({ inputStr: data }); debounceLoading(); }; const fitTerm = () => { fitAddonRef.current?.fit?.(); }; const createChunkConnection = async () => { chunkRequedtRef.current?.current?.cancel?.(); chunkRequedtRef.current = setChunkRequest({ url, params: { ...props.params, watch: true }, contentType: 'text', handler: updateContent }); }; const initTerm = () => { termRef.current?.dispose?.(); termRef.current = new Terminal({ lineHeight: 1.2, fontSize: 13, fontFamily: "monospace,Menlo,Courier,'Courier New',Consolas,Monaco, 'Liberation Mono'", disableStdin: true, convertEol: true, theme: { background: '#1e1e1e', foreground: 'rgba(255,255,255,0.8)' }, cursorInactiveStyle: 'none', smoothScrollDuration: 0 }); fitAddonRef.current = new FitAddon(); termRef.current.loadAddon(fitAddonRef.current); termRef.current.open(termwrapRef.current); // add event // termRef.current.onLineFeed((e: any) => { // if (cacheDataRef.current) { // throttleScroll(); // } // }); }; const handleResize = _.throttle(() => { fitTerm(); }, 100); useEffect(() => { createChunkConnection(); return () => { chunkRequedtRef.current?.current?.cancel?.(); }; }, [url, props.params]); useEffect(() => { if (termwrapRef.current) { initTerm(); } return () => { termRef.current?.dispose?.(); }; }, [termwrapRef.current]); useEffect(() => { if (size) { handleResize(); } }, [size]); useEffect(() => { // throttleScroll(); }, [logs]); return (
); }); export default memo(LogsViewer);