change(ui): add unified row height to state tab, add virt to console tab

This commit is contained in:
sylenien 2022-11-18 14:34:00 +01:00 committed by Delirium
parent 605f047e90
commit a111dc95e9
6 changed files with 116 additions and 57 deletions

View file

@ -9,12 +9,14 @@ interface Props {
iconProps: any; iconProps: any;
jump?: any; jump?: any;
renderWithNL?: any; renderWithNL?: any;
style?: any;
} }
function ConsoleRow(props: Props) { function ConsoleRow(props: Props) {
const { log, iconProps, jump, renderWithNL } = props; const { log, iconProps, jump, renderWithNL, style } = props;
const [expanded, setExpanded] = useState(false); const [expanded, setExpanded] = useState(false);
const lines = log.value.split('\n').filter((l: any) => !!l); const lines = log.value.split('\n').filter((l: any) => !!l);
const canExpand = lines.length > 1; const canExpand = lines.length > 1;
return ( return (
<div <div
className={cn(stl.line, 'flex py-2 px-4 overflow-hidden group relative select-none', { className={cn(stl.line, 'flex py-2 px-4 overflow-hidden group relative select-none', {
@ -23,6 +25,7 @@ function ConsoleRow(props: Props) {
error: log.isRed(), error: log.isRed(),
'cursor-pointer': canExpand, 'cursor-pointer': canExpand,
})} })}
style={style}
onClick={() => setExpanded(!expanded)} onClick={() => setExpanded(!expanded)}
> >
<div className={cn(stl.timestamp)}> <div className={cn(stl.timestamp)}>
@ -38,7 +41,7 @@ function ConsoleRow(props: Props) {
)} )}
<span>{renderWithNL(lines.pop())}</span> <span>{renderWithNL(lines.pop())}</span>
</div> </div>
{canExpand && expanded && lines.map((l: any) => <div className="ml-4 mb-1">{l}</div>)} {canExpand && expanded && lines.map((l: any, i: number) => <div key={l.slice(0,3)+i} className="ml-4 mb-1">{l}</div>)}
</div> </div>
<JumpButton onClick={() => jump(log.time)} /> <JumpButton onClick={() => jump(log.time)} />
</div> </div>

View file

@ -63,17 +63,27 @@ function DiffRow({ diff, path }: Props) {
)} )}
> >
{oldValueSafe || 'undefined'} {oldValueSafe || 'undefined'}
{diffLengths[0] > 50
? (
<div onClick={() => setShortenOldVal(!shortenOldVal)} className="cursor-pointer px-1 text-white bg-gray-light rounded text-sm w-fit">
{!shortenOldVal ? 'collapse' : 'expand'}
</div>
) : null}
</span> </span>
{' -> '} {' -> '}
<span <span
onClick={() => setShortenNewVal(!shortenNewVal)}
className={cn( className={cn(
'whitespace-pre', 'whitespace-pre',
newValue ? 'text-red' : 'text-green', newValue ? 'text-red' : 'text-green',
diffLengths[1] > 50 ? 'cursor-pointer' : ''
)} )}
> >
{newValueSafe || 'undefined'} {newValueSafe || 'undefined'}
{diffLengths[1] > 50
? (
<div onClick={() => setShortenNewVal(!shortenNewVal)} className="cursor-pointer px-1 text-white bg-gray-light rounded text-sm w-fit">
{!shortenNewVal ? 'collapse' : 'expand'}
</div>
) : null}
</span> </span>
</div> </div>
); );

View file

@ -12,7 +12,6 @@ import { JSONTree, NoContent, Tooltip } from 'UI';
import { formatMs } from 'App/date'; import { formatMs } from 'App/date';
import { diff } from 'deep-diff'; import { diff } from 'deep-diff';
import { jump } from 'Player'; import { jump } from 'Player';
import Autoscroll from '../Autoscroll';
import BottomBlock from '../BottomBlock/index'; import BottomBlock from '../BottomBlock/index';
import DiffRow from './DiffRow'; import DiffRow from './DiffRow';
import cn from 'classnames'; import cn from 'classnames';
@ -22,6 +21,7 @@ import { List, CellMeasurer, CellMeasurerCache, AutoSizer } from 'react-virtuali
// const STATE = 'STATE'; // const STATE = 'STATE';
// const DIFF = 'DIFF'; // const DIFF = 'DIFF';
// const TABS = [ DIFF, STATE ].map(tab => ({ text: tab, key: tab })); // const TABS = [ DIFF, STATE ].map(tab => ({ text: tab, key: tab }));
const ROW_HEIGHT = 90;
function getActionsName(type) { function getActionsName(type) {
switch (type) { switch (type) {
@ -48,8 +48,8 @@ function getActionsName(type) {
) )
//@withEnumToggle('activeTab', 'setActiveTab', DIFF) //@withEnumToggle('activeTab', 'setActiveTab', DIFF)
export default class Storage extends React.PureComponent { export default class Storage extends React.PureComponent {
constructor(props, ctx) { constructor(props) {
super(props, ctx); super(props);
this.lastBtnRef = React.createRef(); this.lastBtnRef = React.createRef();
this._list = React.createRef(); this._list = React.createRef();
@ -57,8 +57,6 @@ export default class Storage extends React.PureComponent {
fixedWidth: true, fixedWidth: true,
keyMapper: index => this.props.listNow[index] keyMapper: index => this.props.listNow[index]
}); });
this._listNowLen = this.props.listNow.length
this._listLen = this.props.list.length
this._rowRenderer = this._rowRenderer.bind(this) this._rowRenderer = this._rowRenderer.bind(this)
} }
@ -72,27 +70,25 @@ export default class Storage extends React.PureComponent {
this.focusNextButton(); this.focusNextButton();
} }
componentDidUpdate(prevProps) { componentDidUpdate(prevProps, prevState) {
if (prevProps.listNow.length !== this.props.listNow.length) { if (prevProps.listNow.length !== this.props.listNow.length) {
this.focusNextButton(); this.focusNextButton();
const newRows = this.props.listNow.filter(evt => prevProps.listNow.indexOf(evt.id) < 0); /** possible performance gain, but does not work with dynamic list insertion for some reason
console.log(newRows, this.props.listNow) * getting NaN offsets, maybe I detect changed rows wrongly
if (newRows.length > 0) { */
const newRowsIndexes = newRows.map(r => this.props.listNow.indexOf(r)) // const newRows = this.props.listNow.filter(evt => prevProps.listNow.indexOf(evt._index) < 0);
// if (newRows.length > 0) {
newRowsIndexes.forEach(ind => this.cache.clean(ind)) // const newRowsIndexes = newRows.map(r => this.props.listNow.indexOf(r))
this._list.recomputeRowHeights([...newRowsIndexes]) // newRowsIndexes.forEach(ind => this.cache.clear(ind))
} // this._list.recomputeRowHeights(newRowsIndexes)
// }
this._listNowLen = this.props.listNow.length
this._listLen = this.props.list.length
} }
} }
renderDiff(item, prevItem) { renderDiff(item, prevItem) {
if (!prevItem) { if (!prevItem) {
// we don't have state before first action // we don't have state before first action
return <div style={{ flex: 1 }} className="p-1" />; return <div style={{ flex: 3 }} className="p-1" />;
} }
const stateDiff = diff(prevItem.state, item.state); const stateDiff = diff(prevItem.state, item.state);
@ -106,7 +102,7 @@ export default class Storage extends React.PureComponent {
} }
return ( return (
<div style={{ flex: 3 }} className="flex flex-col p-1 font-mono"> <div style={{ flex: 3, maxHeight: ROW_HEIGHT, overflowY: 'scroll' }} className="flex flex-col p-1 font-mono">
{stateDiff.map((d, i) => this.renderDiffs(d, i))} {stateDiff.map((d, i) => this.renderDiffs(d, i))}
</div> </div>
); );
@ -114,6 +110,7 @@ export default class Storage extends React.PureComponent {
renderDiffs(diff, i) { renderDiffs(diff, i) {
const path = this.createPath(diff); const path = this.createPath(diff);
return ( return (
<React.Fragment key={i}> <React.Fragment key={i}>
<DiffRow shades={this.pathShades} path={path} diff={diff} /> <DiffRow shades={this.pathShades} path={path} diff={diff} />
@ -153,7 +150,7 @@ export default class Storage extends React.PureComponent {
return <JSONTree collapsed={2} src={listNow[listNow.length - 1].state} />; return <JSONTree collapsed={2} src={listNow[listNow.length - 1].state} />;
} }
renderItem(item, i, prevItem, style) { renderItem(item, i, prevItem, style, measure) {
const { type } = this.props; const { type } = this.props;
let src; let src;
let name; let name;
@ -179,10 +176,10 @@ export default class Storage extends React.PureComponent {
return ( return (
<div <div
style={style} style={{ ...style, height: ROW_HEIGHT }}
className={cn('flex justify-between items-start', src !== null ? 'border-b' : '')} className={cn('flex justify-between items-start', src !== null ? 'border-b' : '')}
key={`store-${i}`} key={`store-${i}`}
onClick={() => this._list.recomputeRowHeights(i)} // onClick={() => {measure(); this._list.recomputeRowHeights(i)}}
> >
{src === null ? ( {src === null ? (
<div className="font-mono" style={{ flex: 2, marginLeft: '26.5%' }}> <div className="font-mono" style={{ flex: 2, marginLeft: '26.5%' }}>
@ -190,13 +187,14 @@ export default class Storage extends React.PureComponent {
</div> </div>
) : ( ) : (
<> <>
{this.renderDiff(item, prevItem)} {this.renderDiff(item, prevItem, i)}
<div style={{ flex: 2 }} className="flex pl-10 pt-2"> <div style={{ flex: 2, maxHeight: ROW_HEIGHT, overflowY: 'scroll', overflowX: 'scroll' }} className="flex pl-10 pt-2">
<JSONTree <JSONTree
name={this.ensureString(name)} name={this.ensureString(name)}
src={src} src={src}
collapsed collapsed
collapseStringsAfterLength={7} collapseStringsAfterLength={7}
onSelect={() => console.log('test')}
/> />
</div> </div>
</> </>
@ -206,12 +204,12 @@ export default class Storage extends React.PureComponent {
<div className="font-size-12 color-gray-medium">{formatMs(item.duration)}</div> <div className="font-size-12 color-gray-medium">{formatMs(item.duration)}</div>
)} )}
<div className="w-12"> <div className="w-12">
{i + 1 < this._listNowLen && ( {i + 1 < this.props.listNow.length && (
<button className={stl.button} onClick={() => jump(item.time, item._index)}> <button className={stl.button} onClick={() => jump(item.time, item._index)}>
{'JUMP'} {'JUMP'}
</button> </button>
)} )}
{i + 1 === this._listNowLen && i + 1 < this._listLen && ( {i + 1 === this.props.listNow.length && i + 1 < this.props.list.length && (
<button className={stl.button} ref={this.lastBtnRef} onClick={this.goNext}> <button className={stl.button} ref={this.lastBtnRef} onClick={this.goNext}>
{'NEXT'} {'NEXT'}
</button> </button>
@ -227,15 +225,18 @@ export default class Storage extends React.PureComponent {
// this.renderItem(item, i, i > 0 ? listNow[i - 1] : undefined, listNowLen, listLen) // this.renderItem(item, i, i > 0 ? listNow[i - 1] : undefined, listNowLen, listLen)
// ) // )
const { listNow } = this.props; const { listNow } = this.props;
if (!listNow[index]) return console.warn(index, listNow)
return ( return (
<CellMeasurer <CellMeasurer
cache={this.cache} cache={this.cache}
columnIndex={0} columnIndex={0}
key={key} key={key}
rowIndex={index} rowIndex={index}
parent={parent} parent={parent}
> >
{this.renderItem(listNow[index], index, index > 0 ? listNow[index - 1] : undefined, style)} {({ measure }) => this.renderItem(listNow[index], index, index > 0 ? listNow[index - 1] : undefined, style, measure)}
</CellMeasurer> </CellMeasurer>
) )
} }
@ -345,20 +346,20 @@ export default class Storage extends React.PureComponent {
)} )}
<div className="flex" style={{ width: showStore ? '75%' : '100%' }}> <div className="flex" style={{ width: showStore ? '75%' : '100%' }}>
<AutoSizer> <AutoSizer>
{({ height, width }) => ( {({ height, width }) => (
<List <List
ref={element => { ref={element => {
this._list = element; this._list = element;
}} }}
deferredMeasurementCache={this.cache} deferredMeasurementCache={this.cache}
overscanRowCount={2} overscanRowCount={1}
rowCount={this._listNowLen} rowCount={Math.ceil(parseInt(this.props.listNow.length) || 1)}
rowHeight={this.cache.rowHeight} rowHeight={ROW_HEIGHT}
rowRenderer={this._rowRenderer} rowRenderer={this._rowRenderer}
width={width} width={width}
height={height} height={height}
/> />
)} )}
</AutoSizer> </AutoSizer>
</div> </div>
</NoContent> </NoContent>

View file

@ -8,6 +8,12 @@ import { Tabs, Input, Icon, NoContent } from 'UI';
import cn from 'classnames'; import cn from 'classnames';
import ConsoleRow from '../ConsoleRow'; import ConsoleRow from '../ConsoleRow';
import { getRE } from 'App/utils'; import { getRE } from 'App/utils';
import {
List,
CellMeasurer,
CellMeasurerCache,
AutoSizer,
} from 'react-virtualized';
const ALL = 'ALL'; const ALL = 'ALL';
const INFO = 'INFO'; const INFO = 'INFO';
@ -62,6 +68,34 @@ function ConsolePanel(props: Props) {
const [activeTab, setActiveTab] = useState(ALL); const [activeTab, setActiveTab] = useState(ALL);
const [filter, setFilter] = useState(''); const [filter, setFilter] = useState('');
const cache = new CellMeasurerCache({
fixedWidth: true,
keyMapper: (index: number) => filtered[index],
});
const _list = React.useRef();
const _rowRenderer = ({ index, key, parent, style }: any) => {
const item = filtered[index];
return (
<CellMeasurer cache={cache} columnIndex={0} key={key} rowIndex={index} parent={parent}>
{({ measure }: any) => (
<ConsoleRow
style={style}
log={item}
jump={jump}
iconProps={getIconProps(item.level)}
renderWithNL={renderWithNL}
recalcHeight={() => {
measure();
(_list as any).current.recomputeRowHeights(index);
}}
/>
)}
</CellMeasurer>
);
};
let filtered = React.useMemo(() => { let filtered = React.useMemo(() => {
const filterRE = getRE(filter, 'i'); const filterRE = getRE(filter, 'i');
let list = logs; let list = logs;
@ -105,17 +139,20 @@ function ConsolePanel(props: Props) {
size="small" size="small"
show={filtered.length === 0} show={filtered.length === 0}
> >
{/* <Autoscroll> */} <AutoSizer>
{filtered.map((l: any, index: any) => ( {({ height, width }: any) => (
<ConsoleRow <List
key={index} ref={_list}
log={l} deferredMeasurementCache={cache}
jump={jump} overscanRowCount={5}
iconProps={getIconProps(l.level)} rowCount={Math.ceil(filtered.length || 1)}
renderWithNL={renderWithNL} rowHeight={cache.rowHeight}
/> rowRenderer={_rowRenderer}
))} width={width}
{/* </Autoscroll> */} height={height}
/>
)}
</AutoSizer>
</NoContent> </NoContent>
</BottomBlock.Content> </BottomBlock.Content>
</BottomBlock> </BottomBlock>

View file

@ -10,9 +10,11 @@ interface Props {
iconProps: any; iconProps: any;
jump?: any; jump?: any;
renderWithNL?: any; renderWithNL?: any;
style?: any;
recalcHeight?: () => void;
} }
function ConsoleRow(props: Props) { function ConsoleRow(props: Props) {
const { log, iconProps, jump, renderWithNL } = props; const { log, iconProps, jump, renderWithNL, style, recalcHeight } = props;
const { showModal } = useModal(); const { showModal } = useModal();
const [expanded, setExpanded] = useState(false); const [expanded, setExpanded] = useState(false);
const lines = log.value.split('\n').filter((l: any) => !!l); const lines = log.value.split('\n').filter((l: any) => !!l);
@ -23,8 +25,14 @@ function ConsoleRow(props: Props) {
const onErrorClick = () => { const onErrorClick = () => {
showModal(<ErrorDetailsModal errorId={log.errorId} />, { right: true }); showModal(<ErrorDetailsModal errorId={log.errorId} />, { right: true });
}; };
const toggleExpand = () => {
setExpanded(!expanded)
setTimeout(() => recalcHeight(), 0)
}
return ( return (
<div <div
style={style}
className={cn( className={cn(
'border-b flex items-center py-2 px-4 overflow-hidden group relative select-none', 'border-b flex items-center py-2 px-4 overflow-hidden group relative select-none',
{ {
@ -36,7 +44,7 @@ function ConsoleRow(props: Props) {
} }
)} )}
onClick={ onClick={
clickable ? () => (!!log.errorId ? onErrorClick() : setExpanded(!expanded)) : () => {} clickable ? () => (!!log.errorId ? onErrorClick() : toggleExpand()) : () => {}
} }
> >
<div className="mr-2"> <div className="mr-2">
@ -49,7 +57,7 @@ function ConsoleRow(props: Props) {
)} )}
<span>{renderWithNL(lines.pop())}</span> <span>{renderWithNL(lines.pop())}</span>
</div> </div>
{canExpand && expanded && lines.map((l: any) => <div className="ml-4 mb-1">{l}</div>)} {canExpand && expanded && lines.map((l: string, i: number) => <div key={l.slice(0,4)+i} className="ml-4 mb-1">{l}</div>)}
</div> </div>
<JumpButton onClick={() => jump(log.time)} /> <JumpButton onClick={() => jump(log.time)} />
</div> </div>

View file

@ -8,7 +8,7 @@ function updateObjectLink(obj) {
} }
export default ({ src, ...props }) => ( export default ({ src, ...props }) => (
<JSONTree <JSONTree
name={ false } name={ false }
collapsed={ 1 } collapsed={ 1 }
enableClipboard={ false } enableClipboard={ false }
@ -21,4 +21,4 @@ export default ({ src, ...props }) => (
iconStle="triangle" iconStle="triangle"
{ ...props } { ...props }
/> />
); );