This also tries to make the autoscroll functionality a bit more consistent, where all items are always shown in the list, but items which have not yet occurred will be partially transparent until they happen. Due to that change, autoscroll behavior which previously always went all the way to the bottom of a list didn't make sense anymore, so now it scrolls to the current item.
127 lines
4.1 KiB
JavaScript
127 lines
4.1 KiB
JavaScript
import React from 'react';
|
|
import cn from 'classnames';
|
|
import { getRE } from 'App/utils';
|
|
import { Icon, NoContent, Tabs, Input } from 'UI';
|
|
import { jump } from 'Player';
|
|
import { LEVEL } from 'Types/session/log';
|
|
import Autoscroll from '../Autoscroll';
|
|
import BottomBlock from '../BottomBlock';
|
|
import stl from './console.module.css';
|
|
import { Duration } from 'luxon';
|
|
|
|
const ALL = 'ALL';
|
|
const INFO = 'INFO';
|
|
const WARNINGS = 'WARNINGS';
|
|
const ERRORS = 'ERRORS';
|
|
|
|
const LEVEL_TAB = {
|
|
[LEVEL.INFO]: INFO,
|
|
[LEVEL.LOG]: INFO,
|
|
[LEVEL.WARNING]: WARNINGS,
|
|
[LEVEL.ERROR]: ERRORS,
|
|
[LEVEL.EXCEPTION]: ERRORS,
|
|
};
|
|
|
|
const TABS = [ALL, ERRORS, WARNINGS, INFO].map((tab) => ({ text: tab, key: tab }));
|
|
|
|
// eslint-disable-next-line complexity
|
|
const getIconProps = (level) => {
|
|
switch (level) {
|
|
case LEVEL.INFO:
|
|
case LEVEL.LOG:
|
|
return {
|
|
name: 'console/info',
|
|
color: 'blue2',
|
|
};
|
|
case LEVEL.WARN:
|
|
case LEVEL.WARNING:
|
|
return {
|
|
name: 'console/warning',
|
|
color: 'red2',
|
|
};
|
|
case LEVEL.ERROR:
|
|
return {
|
|
name: 'console/error',
|
|
color: 'red',
|
|
};
|
|
}
|
|
return null;
|
|
};
|
|
|
|
function renderWithNL(s = '') {
|
|
if (typeof s !== 'string') return '';
|
|
return s.split('\n').map((line, i) => <div className={cn({ 'ml-20': i !== 0 })}>{line}</div>);
|
|
}
|
|
|
|
export default class ConsoleContent extends React.PureComponent {
|
|
state = {
|
|
filter: '',
|
|
activeTab: ALL,
|
|
};
|
|
onTabClick = (activeTab) => this.setState({ activeTab });
|
|
onFilterChange = ({ target: { value } }) => this.setState({ filter: value });
|
|
|
|
render() {
|
|
const { logs, isResult, additionalHeight, logsNow } = this.props;
|
|
const time = logsNow.length > 0 ? logsNow[logsNow.length - 1].time : undefined;
|
|
const { filter, activeTab, currentError } = this.state;
|
|
const filterRE = getRE(filter, 'i');
|
|
const filtered = logs.filter(({ level, value }) =>
|
|
activeTab === ALL
|
|
? filterRE.test(value)
|
|
: filterRE.test(value) && LEVEL_TAB[level] === activeTab
|
|
);
|
|
|
|
const lastIndex = filtered.filter((item) => item.time <= time).length - 1;
|
|
|
|
return (
|
|
<>
|
|
<BottomBlock style={{ height: 300 + additionalHeight + 'px' }}>
|
|
<BottomBlock.Header showClose={!isResult}>
|
|
<div className="flex items-center">
|
|
<span className="font-semibold color-gray-medium mr-4">Console</span>
|
|
<Tabs tabs={TABS} active={activeTab} onClick={this.onTabClick} border={false} />
|
|
</div>
|
|
<Input
|
|
className="input-small"
|
|
placeholder="Filter by keyword"
|
|
icon="search"
|
|
iconPosition="left"
|
|
name="filter"
|
|
onChange={this.onFilterChange}
|
|
/>
|
|
</BottomBlock.Header>
|
|
<BottomBlock.Content>
|
|
<NoContent size="small" show={filtered.length === 0}>
|
|
<Autoscroll autoScrollTo={Math.max(lastIndex, 0)}>
|
|
{filtered.map((l, index) => (
|
|
<div
|
|
className={cn('flex', {
|
|
info: !l.isYellow() && !l.isRed(),
|
|
warn: l.isYellow(),
|
|
error: l.isRed(),
|
|
[stl.activeRow]: lastIndex === index,
|
|
[stl.inactiveRow]: index > lastIndex,
|
|
'cursor-pointer': !isResult,
|
|
})}
|
|
onClick={() => !isResult && jump(l.time)}
|
|
>
|
|
<div className={cn(stl.timestamp)}>
|
|
<Icon size="14" className={stl.icon} {...getIconProps(l.level)} />
|
|
</div>
|
|
<div className={cn(stl.timestamp, {})}>
|
|
{Duration.fromMillis(l.time).toFormat('mm:ss.SSS')}
|
|
</div>
|
|
<div key={l.key} className={cn(stl.line)} data-scroll-item={l.isRed()}>
|
|
<div className={stl.message}>{renderWithNL(l.value)}</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</Autoscroll>
|
|
</NoContent>
|
|
</BottomBlock.Content>
|
|
</BottomBlock>
|
|
</>
|
|
);
|
|
}
|
|
}
|