import { ReactWidget, Message, Widget } from "@theia/core/lib/browser"; import { postConstruct, injectable, inject } from "inversify"; import * as React from 'react'; import Select, { components } from 'react-select'; import { Styles } from "react-select/src/styles"; import { ThemeConfig } from "react-select/src/theme"; import { OptionsType } from "react-select/src/types"; import { MonitorServiceClientImpl } from "./monitor-service-client-impl"; import { MessageService } from "@theia/core"; import { ConnectionConfig, MonitorService } from "../../common/protocol/monitor-service"; import { MonitorConnection } from "./monitor-connection"; import { BoardsServiceClientImpl } from "../boards/boards-service-client-impl"; import { AttachedSerialBoard, BoardsService, Board } from "../../common/protocol/boards-service"; import { BoardsConfig } from "../boards/boards-config"; import { MonitorModel } from "./monitor-model"; export namespace SerialMonitorSendField { export interface Props { onSend: (text: string) => void } export interface State { value: string; } } export class SerialMonitorSendField extends React.Component { constructor(props: SerialMonitorSendField.Props) { super(props); this.state = { value: '' }; this.handleChange = this.handleChange.bind(this); this.handleSubmit = this.handleSubmit.bind(this); } render() { return
} protected handleChange(event: React.ChangeEvent) { this.setState({ value: event.target.value }); } protected handleSubmit(event: React.FormEvent) { this.props.onSend(this.state.value); this.setState({ value: '' }); event.preventDefault(); } } export namespace SerialMonitorOutput { export interface Props { lines: string[]; model: MonitorModel; } } export class SerialMonitorOutput extends React.Component { protected theEnd: HTMLDivElement | null; render() { let result = ''; const style: React.CSSProperties = { whiteSpace: 'pre', fontFamily: 'monospace', }; for (const text of this.props.lines) { result += text; } return
{result}
{ this.theEnd = el; }}>
; } protected scrollToBottom() { if (this.theEnd) { this.theEnd.scrollIntoView(); } } componentDidMount() { if (this.props.model.autoscroll) { this.scrollToBottom(); } } componentDidUpdate() { if (this.props.model.autoscroll) { this.scrollToBottom(); } } } export interface SelectOption { label: string; value: string | number; } @injectable() export class MonitorWidget extends ReactWidget { static readonly ID = 'serial-monitor'; protected lines: string[]; protected tempData: string; protected baudRate: number; protected _lineEnding: string; protected widgetHeight: number; constructor( @inject(MonitorServiceClientImpl) protected readonly serviceClient: MonitorServiceClientImpl, @inject(MonitorConnection) protected readonly connection: MonitorConnection, @inject(MonitorService) protected readonly monitorService: MonitorService, @inject(BoardsServiceClientImpl) protected readonly boardsServiceClient: BoardsServiceClientImpl, @inject(MessageService) protected readonly messageService: MessageService, @inject(BoardsService) protected readonly boardsService: BoardsService, @inject(MonitorModel) protected readonly model: MonitorModel ) { super(); this.id = MonitorWidget.ID; this.title.label = 'Serial Monitor'; this.title.iconClass = 'arduino-serial-monitor-tab-icon'; this.lines = []; this.tempData = ''; this._lineEnding = '\n'; this.scrollOptions = undefined; this.toDisposeOnDetach.push(serviceClient.onRead(({ data, connectionId }) => { this.tempData += data; if (this.tempData.endsWith('\n')) { if (this.model.timestamp) { const nu = new Date(); const h = (100 + nu.getHours()).toString().substr(1) const min = (100 + nu.getMinutes()).toString().substr(1) const sec = (100 + nu.getSeconds()).toString().substr(1) const ms = (1000 + nu.getMilliseconds()).toString().substr(1); this.tempData = `${h}:${min}:${sec}.${ms} -> ` + this.tempData; } this.lines.push(this.tempData); this.tempData = ''; this.update(); } })); // TODO onError } @postConstruct() protected init(): void { this.update(); } clear(): void { this.lines = []; this.update(); } protected onAfterAttach(msg: Message) { super.onAfterAttach(msg); this.clear(); this.connect(); this.toDisposeOnDetach.push(this.boardsServiceClient.onBoardsChanged(async states => { const currentConnectionConfig = this.connection.connectionConfig; const connectedBoard = states.newState.boards .filter(AttachedSerialBoard.is) .find(board => { const potentiallyConnected = currentConnectionConfig && currentConnectionConfig.board; if (AttachedSerialBoard.is(potentiallyConnected)) { return Board.equals(board, potentiallyConnected) && board.port === potentiallyConnected.port; } return false; }); if (!connectedBoard) { this.close(); } })); this.toDisposeOnDetach.push(this.connection.onConnectionChanged(() => { this.clear(); })); } protected onBeforeDetach(msg: Message) { super.onBeforeDetach(msg); this.connection.disconnect(); } protected onResize(msg: Widget.ResizeMessage) { super.onResize(msg); this.widgetHeight = msg.height; this.update(); } protected async connect() { const config = await this.getConnectionConfig(); if (config) { this.connection.connect(config); } } protected async getConnectionConfig(): Promise { const baudRate = this.baudRate; const { boardsConfig } = this.boardsServiceClient; const { selectedBoard, selectedPort } = boardsConfig; if (!selectedBoard) { this.messageService.warn('No boards selected.'); return; } const { name } = selectedBoard; if (!selectedPort) { this.messageService.warn(`No ports selected for board: '${name}'.`); return; } const attachedBoards = await this.boardsService.getAttachedBoards(); const connectedBoard = attachedBoards.boards.filter(AttachedSerialBoard.is).find(board => BoardsConfig.Config.sameAs(boardsConfig, board)); if (!connectedBoard) { this.messageService.warn(`The selected '${name}' board is not connected on ${selectedPort}.`); return; } return { baudRate, board: selectedBoard, port: selectedPort } } protected getLineEndings(): OptionsType { return [ { label: 'No Line Ending', value: '' }, { label: 'Newline', value: '\n' }, { label: 'Carriage Return', value: '\r' }, { label: 'Both NL & CR', value: '\r\n' } ] } protected getBaudRates(): OptionsType { const baudRates = [300, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200]; return baudRates.map(baudRate => ({ label: baudRate + ' baud', value: baudRate })) } protected render(): React.ReactNode { const le = this.getLineEndings(); const br = this.getBaudRates(); return
{this.renderSelectField('arduino-serial-monitor-line-endings', le, le[1], this.onChangeLineEnding)} {this.renderSelectField('arduino-serial-monitor-baud-rates', br, br[4], this.onChangeBaudRate)}
; } protected readonly onSend = (value: string) => this.doSend(value); protected async doSend(value: string) { const { connectionId } = this.connection; if (connectionId) { this.monitorService.send(connectionId, value + this._lineEnding); } } protected readonly onChangeLineEnding = (le: SelectOption) => { this._lineEnding = typeof le.value === 'string' ? le.value : '\n'; } protected readonly onChangeBaudRate = (br: SelectOption) => { this.baudRate = typeof br.value === 'number' ? br.value : 9600; } protected renderSelectField(id: string, options: OptionsType, defaultVal: SelectOption, onChange: (v: SelectOption) => void): React.ReactNode { const height = 25; const selectStyles: Styles = { control: (provided, state) => ({ ...provided, width: 200, border: 'none' }), dropdownIndicator: (p, s) => ({ ...p, padding: 0 }), indicatorSeparator: (p, s) => ({ display: 'none' }), indicatorsContainer: (p, s) => ({ padding: '0 5px' }), menu: (p, s) => ({ ...p, marginTop: 0 }) }; const theme: ThemeConfig = theme => ({ ...theme, borderRadius: 0, spacing: { controlHeight: height, baseUnit: 2, menuGutter: 4 } }); const DropdownIndicator = ( props: React.Props ) => { return ( ); }; return