arduino-ide/arduino-ide-extension/src/browser/serial/monitor/serial-monitor-send-output.tsx
2021-12-20 14:56:26 +01:00

148 lines
3.8 KiB
TypeScript

import * as React from 'react';
import { Event } from '@theia/core/lib/common/event';
import { DisposableCollection } from '@theia/core/lib/common/disposable';
import { areEqual, FixedSizeList as List } from 'react-window';
import { SerialModel } from '../serial-model';
import { SerialConnectionManager } from '../serial-connection-manager';
import dateFormat = require('dateformat');
import { messagesToLines, truncateLines } from './monitor-utils';
export type Line = { message: string; timestamp?: Date; lineLen: number };
export class SerialMonitorOutput extends React.Component<
SerialMonitorOutput.Props,
SerialMonitorOutput.State
> {
/**
* Do not touch it. It is used to be able to "follow" the serial monitor log.
*/
protected toDisposeBeforeUnmount = new DisposableCollection();
private listRef: React.RefObject<any>;
constructor(props: Readonly<SerialMonitorOutput.Props>) {
super(props);
this.listRef = React.createRef();
this.state = {
lines: [],
timestamp: this.props.serialModel.timestamp,
charCount: 0,
};
}
render(): React.ReactNode {
return (
<List
className="serial-monitor-messages"
height={this.props.height}
itemData={
{
lines: this.state.lines,
timestamp: this.state.timestamp,
} as any
}
itemCount={this.state.lines.length}
itemSize={18}
width={'100%'}
style={{ whiteSpace: 'nowrap' }}
ref={this.listRef}
>
{Row}
</List>
);
}
shouldComponentUpdate(): boolean {
return true;
}
componentDidMount(): void {
this.scrollToBottom();
this.toDisposeBeforeUnmount.pushAll([
this.props.serialConnection.onRead(({ messages }) => {
const [newLines, totalCharCount] = messagesToLines(
messages,
this.state.lines,
this.state.charCount
);
const [lines, charCount] = truncateLines(newLines, totalCharCount);
this.setState({
lines,
charCount,
});
this.scrollToBottom();
}),
this.props.clearConsoleEvent(() =>
this.setState({ lines: [], charCount: 0 })
),
this.props.serialModel.onChange(({ property }) => {
if (property === 'timestamp') {
const { timestamp } = this.props.serialModel;
this.setState({ timestamp });
}
if (property === 'autoscroll') {
this.scrollToBottom();
}
}),
]);
}
componentWillUnmount(): void {
// TODO: "Your preferred browser's local storage is almost full." Discard `content` before saving layout?
this.toDisposeBeforeUnmount.dispose();
}
scrollToBottom = ((): void => {
if (this.listRef.current && this.props.serialModel.autoscroll) {
this.listRef.current.scrollToItem(this.state.lines.length, 'end');
}
}).bind(this);
}
const _Row = ({
index,
style,
data,
}: {
index: number;
style: any;
data: { lines: Line[]; timestamp: boolean };
}) => {
const timestamp =
(data.timestamp &&
`${dateFormat(data.lines[index].timestamp, 'H:M:ss.l')} -> `) ||
'';
return (
(data.lines[index].lineLen && (
<div style={style}>
{timestamp}
{data.lines[index].message}
</div>
)) ||
null
);
};
const Row = React.memo(_Row, areEqual);
export namespace SerialMonitorOutput {
export interface Props {
readonly serialModel: SerialModel;
readonly serialConnection: SerialConnectionManager;
readonly clearConsoleEvent: Event<void>;
readonly height: number;
}
export interface State {
lines: Line[];
timestamp: boolean;
charCount: number;
}
export interface SelectOption<T> {
readonly label: string;
readonly value: T;
}
export const MAX_CHARACTERS = 1_000_000;
}