chore: chat hooks
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
.alert-info-block {
|
||||
padding: 6px 10px;
|
||||
position: relative;
|
||||
padding-top: 35px;
|
||||
text-align: center;
|
||||
border-radius: var(--border-radius-base);
|
||||
margin: 0;
|
||||
border: 1px solid transparent;
|
||||
|
||||
.ant-typography {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
&.danger {
|
||||
border-color: var(--ant-color-error-border);
|
||||
background-color: var(--ant-color-error-bg);
|
||||
}
|
||||
|
||||
&.warning {
|
||||
border-color: var(--ant-color-warning-border);
|
||||
background-color: var(--ant-color-warning-bg);
|
||||
}
|
||||
|
||||
.title {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
display: flex;
|
||||
height: 30px;
|
||||
width: 100%;
|
||||
padding: 5px 10px;
|
||||
border-radius: var(--border-radius-base) var(--border-radius-base) 0 0;
|
||||
|
||||
&.danger {
|
||||
background-color: var(--ant-color-error-bg-hover);
|
||||
}
|
||||
|
||||
&.warning {
|
||||
background-color: var(--ant-color-warning-bg-hover);
|
||||
}
|
||||
|
||||
.info-icon {
|
||||
margin-right: 8px;
|
||||
|
||||
&.danger {
|
||||
color: var(--ant-color-error);
|
||||
}
|
||||
|
||||
&.warning {
|
||||
color: var(--ant-color-warning);
|
||||
}
|
||||
}
|
||||
|
||||
.text {
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { WarningFilled } from '@ant-design/icons';
|
||||
import { Typography } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import React from 'react';
|
||||
import './block.less';
|
||||
interface AlertInfoProps {
|
||||
type: 'danger' | 'warning';
|
||||
message: string;
|
||||
rows?: number;
|
||||
icon?: React.ReactNode;
|
||||
ellipsis?: boolean;
|
||||
style?: React.CSSProperties;
|
||||
title: React.ReactNode;
|
||||
}
|
||||
|
||||
const AlertInfo: React.FC<AlertInfoProps> = (props) => {
|
||||
const { message, type, rows = 1, ellipsis, style, title } = props;
|
||||
|
||||
return (
|
||||
<>
|
||||
{message ? (
|
||||
<div
|
||||
className={classNames('alert-info-block', type)}
|
||||
style={{ ...style }}
|
||||
>
|
||||
<Typography.Paragraph
|
||||
ellipsis={
|
||||
ellipsis !== undefined
|
||||
? ellipsis
|
||||
: {
|
||||
rows: rows,
|
||||
tooltip: message
|
||||
}
|
||||
}
|
||||
>
|
||||
<div className={classNames('title', type)}>
|
||||
<WarningFilled className={classNames('info-icon', type)} />
|
||||
<span className="text">{title}</span>
|
||||
</div>
|
||||
{message}
|
||||
</Typography.Paragraph>
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(AlertInfo);
|
||||
@@ -1,147 +0,0 @@
|
||||
import useSetChunkFetch from '@/hooks/use-chunk-fetch';
|
||||
import useOverlayScroller from '@/hooks/use-overlay-scroller';
|
||||
import '@xterm/xterm/css/xterm.css';
|
||||
import classNames from 'classnames';
|
||||
import _ from 'lodash';
|
||||
import { memo, useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { replaceLineRegex } from './config';
|
||||
import useParseAnsi from './parse-ansi';
|
||||
import './styles/index.less';
|
||||
|
||||
interface LogsViewerProps {
|
||||
height: number;
|
||||
content?: string;
|
||||
url: string;
|
||||
params?: object;
|
||||
}
|
||||
const LogsViewer: React.FC<LogsViewerProps> = (props) => {
|
||||
const { height, url } = props;
|
||||
const {
|
||||
initialize,
|
||||
updateScrollerPosition,
|
||||
generateInstance,
|
||||
scrollEventElement,
|
||||
instance,
|
||||
initialized
|
||||
} = useOverlayScroller({
|
||||
options: {
|
||||
theme: 'os-theme-light'
|
||||
}
|
||||
});
|
||||
const { isClean, parseAnsi } = useParseAnsi();
|
||||
const { setChunkFetch } = useSetChunkFetch();
|
||||
const chunkRequedtRef = useRef<any>(null);
|
||||
const scroller = useRef<any>({});
|
||||
const cacheDataRef = useRef<any>('');
|
||||
const uidRef = useRef<any>(0);
|
||||
const [logs, setLogs] = useState<any[]>([]);
|
||||
const stopScroll = useRef(false);
|
||||
|
||||
const setId = () => {
|
||||
uidRef.current += 1;
|
||||
return uidRef.current;
|
||||
};
|
||||
|
||||
const updateContent = useCallback(
|
||||
(inputStr: string) => {
|
||||
const data = inputStr.replace(replaceLineRegex, '\n');
|
||||
if (isClean(data)) {
|
||||
cacheDataRef.current = data;
|
||||
} else {
|
||||
cacheDataRef.current += data;
|
||||
}
|
||||
const res = parseAnsi(cacheDataRef.current, setId);
|
||||
console.log('res===', res);
|
||||
setLogs(res);
|
||||
},
|
||||
[setLogs, setId]
|
||||
);
|
||||
|
||||
const createChunkConnection = async () => {
|
||||
chunkRequedtRef.current?.current?.abort?.();
|
||||
chunkRequedtRef.current = setChunkFetch({
|
||||
url,
|
||||
params: {
|
||||
...props.params,
|
||||
watch: true
|
||||
},
|
||||
contentType: 'text',
|
||||
handler: updateContent
|
||||
});
|
||||
};
|
||||
|
||||
const debounceResetStopScroll = _.debounce(() => {
|
||||
stopScroll.current = false;
|
||||
}, 30000);
|
||||
|
||||
const handleOnWheel = useCallback(
|
||||
(e: any) => {
|
||||
const scrollTop = scrollEventElement?.scrollTop;
|
||||
const scrollHeight = scrollEventElement?.scrollHeight;
|
||||
const clientHeight = scrollEventElement?.clientHeight;
|
||||
if (scrollTop + clientHeight >= scrollHeight) {
|
||||
stopScroll.current = false;
|
||||
} else {
|
||||
stopScroll.current = true;
|
||||
}
|
||||
debounceResetStopScroll();
|
||||
},
|
||||
[debounceResetStopScroll, scrollEventElement]
|
||||
);
|
||||
|
||||
const debounceUpdateScrollerPosition = _.debounce(() => {
|
||||
generateInstance();
|
||||
updateScrollerPosition(0);
|
||||
}, 200);
|
||||
|
||||
useEffect(() => {
|
||||
createChunkConnection();
|
||||
return () => {
|
||||
chunkRequedtRef.current?.current?.abort?.();
|
||||
};
|
||||
}, [url, props.params]);
|
||||
|
||||
useEffect(() => {
|
||||
if (scroller.current) {
|
||||
initialize(scroller.current);
|
||||
}
|
||||
}, [scroller.current, initialize]);
|
||||
|
||||
useEffect(() => {
|
||||
if (logs.length && !stopScroll.current && instance) {
|
||||
updateScrollerPosition(0);
|
||||
} else if (logs.length && !stopScroll.current && scroller.current) {
|
||||
if (!initialized) {
|
||||
initialize(scroller.current);
|
||||
}
|
||||
if (!instance) {
|
||||
debounceUpdateScrollerPosition();
|
||||
} else {
|
||||
updateScrollerPosition(0);
|
||||
}
|
||||
}
|
||||
}, [logs, stopScroll.current, instance, scroller.current]);
|
||||
|
||||
return (
|
||||
<div className="logs-viewer-wrap-w2">
|
||||
<div
|
||||
className="wrap"
|
||||
style={{ height: height }}
|
||||
ref={scroller}
|
||||
onWheel={handleOnWheel}
|
||||
>
|
||||
<div className={classNames('content')}>
|
||||
{_.map(logs, (item: any, index: number) => {
|
||||
return (
|
||||
<div key={item.uid} className="text">
|
||||
{item.content}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(LogsViewer);
|
||||
@@ -1,149 +0,0 @@
|
||||
import _, { throttle } from 'lodash';
|
||||
import List from 'rc-virtual-list';
|
||||
import React, {
|
||||
forwardRef,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useRef,
|
||||
useState
|
||||
} from 'react';
|
||||
import './styles/logs-list.less';
|
||||
|
||||
interface LogsInnerProps {
|
||||
ref?: any;
|
||||
data: { content: string; uid: number }[];
|
||||
onScroll?: (data: { isTop: boolean; isBottom: boolean }) => void;
|
||||
diffHeight?: number;
|
||||
}
|
||||
|
||||
const LogsInner: React.FC<LogsInnerProps> = forwardRef((props, ref) => {
|
||||
const { data, diffHeight = 96, onScroll } = props;
|
||||
const viewportHeight = window.innerHeight;
|
||||
const viewHeight = viewportHeight - diffHeight;
|
||||
const [innerHieght, setInnerHeight] = useState(viewHeight);
|
||||
const scroller = useRef<any>(null);
|
||||
const stopScroll = useRef(false);
|
||||
const logsWrapper = useRef<any>(null);
|
||||
|
||||
const RC_VIRTUAL_LIST_HOLDER_CLASS = '.rc-virtual-list-holder';
|
||||
|
||||
const updataPositionToBottom = useCallback(
|
||||
throttle(() => {
|
||||
if (!stopScroll.current && data.length > 0) {
|
||||
scroller.current?.scrollTo?.({
|
||||
index: data.length - 1,
|
||||
align: 'bottom'
|
||||
});
|
||||
}
|
||||
}, 200),
|
||||
[data, scroller.current, stopScroll.current]
|
||||
);
|
||||
|
||||
const updataPositionToTop = useCallback(
|
||||
throttle(() => {
|
||||
if (!stopScroll.current && data.length > 0) {
|
||||
scroller.current?.scrollTo?.({
|
||||
index: 0,
|
||||
align: 'bottom'
|
||||
});
|
||||
}
|
||||
}, 200),
|
||||
[data, scroller.current, stopScroll.current]
|
||||
);
|
||||
|
||||
const updatePositionToTop = useCallback(
|
||||
_.throttle((data: { isTop: boolean; isBottom: boolean }) => {
|
||||
props.onScroll?.(data);
|
||||
}, 200),
|
||||
[props.onScroll]
|
||||
);
|
||||
|
||||
const isScrollBottom = useCallback((root: HTMLElement) => {
|
||||
const virtualList = root.querySelector(RC_VIRTUAL_LIST_HOLDER_CLASS);
|
||||
if (!virtualList) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
virtualList.scrollTop >=
|
||||
virtualList.scrollHeight - virtualList.clientHeight
|
||||
);
|
||||
}, []);
|
||||
|
||||
const isScrollTop = useCallback((root: HTMLElement) => {
|
||||
const virtualList = root.querySelector(RC_VIRTUAL_LIST_HOLDER_CLASS);
|
||||
if (!virtualList) {
|
||||
return false;
|
||||
}
|
||||
return virtualList.scrollTop <= 0;
|
||||
}, []);
|
||||
|
||||
const handleOnScroll = useCallback((e: any) => {
|
||||
const isBottom = isScrollBottom(logsWrapper.current);
|
||||
const isTop = isScrollTop(logsWrapper.current);
|
||||
if (isBottom) {
|
||||
stopScroll.current = false;
|
||||
} else {
|
||||
stopScroll.current = true;
|
||||
}
|
||||
updatePositionToTop({
|
||||
isTop,
|
||||
isBottom
|
||||
});
|
||||
}, []);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
scrollToBottom() {
|
||||
updataPositionToBottom();
|
||||
},
|
||||
scrollToTop() {
|
||||
updataPositionToTop();
|
||||
}
|
||||
}));
|
||||
|
||||
useEffect(() => {
|
||||
updataPositionToBottom();
|
||||
}, [updataPositionToBottom]);
|
||||
|
||||
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]);
|
||||
return (
|
||||
<div ref={logsWrapper} className="logs-wrap" style={{ height: '100%' }}>
|
||||
<List
|
||||
ref={scroller}
|
||||
onScroll={handleOnScroll}
|
||||
data={data}
|
||||
itemHeight={22}
|
||||
height={innerHieght}
|
||||
itemKey="uid"
|
||||
className="content"
|
||||
styles={{
|
||||
verticalScrollBar: {
|
||||
width: 'var(--scrollbar-size)'
|
||||
},
|
||||
verticalScrollBarThumb: {
|
||||
borderRadius: '4px',
|
||||
backgroundColor: 'var(--scrollbar-handle-light-bg)'
|
||||
}
|
||||
}}
|
||||
>
|
||||
{(item: any, index: number) => (
|
||||
<div key={item.uid} className="text">
|
||||
{item.content}
|
||||
</div>
|
||||
)}
|
||||
</List>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export default React.memo(LogsInner);
|
||||
@@ -28,7 +28,6 @@ export interface TableHeaderProps {
|
||||
sortOrder?: 'ascend' | 'descend' | null;
|
||||
dataIndex: string;
|
||||
onSort?: (dataIndex: string, order: 'ascend' | 'descend') => void;
|
||||
children?: React.ReactElement<SealColumnProps>;
|
||||
title: React.ReactNode;
|
||||
style?: React.CSSProperties;
|
||||
firstCell?: boolean;
|
||||
|
||||
Reference in New Issue
Block a user