diff --git a/arduino-ide-extension/package.json b/arduino-ide-extension/package.json index 861026a0..3850424b 100644 --- a/arduino-ide-extension/package.json +++ b/arduino-ide-extension/package.json @@ -23,7 +23,10 @@ "@theia/search-in-workspace": "next", "@types/ps-tree": "^1.1.0", "@types/which": "^1.3.1", + "@types/react-select": "^3.0.0", + "@types/google-protobuf": "^3.7.1", "css-element-queries": "^1.2.0", + "react-select": "^3.0.4", "p-queue": "^5.0.0", "ps-tree": "^1.2.0", "tree-kill": "^1.2.1", diff --git a/arduino-ide-extension/src/browser/arduino-commands.ts b/arduino-ide-extension/src/browser/arduino-commands.ts index 0629e024..9c8e1dd7 100644 --- a/arduino-ide-extension/src/browser/arduino-commands.ts +++ b/arduino-ide-extension/src/browser/arduino-commands.ts @@ -42,15 +42,4 @@ export namespace ArduinoCommands { export const TOGGLE_PRO_MODE: Command = { id: "arduino-toggle-pro-mode" } - - export const CONNECT_TODO: Command = { - id: 'connect-to-attached-board', - label: 'Connect to Attached Board' - } - - export const SEND: Command = { - id: 'send', - label: 'Send a Message to the Connected Board' - } - } diff --git a/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx b/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx index 9a778948..5dabaac0 100644 --- a/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx +++ b/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx @@ -5,7 +5,7 @@ import { EditorWidget } from '@theia/editor/lib/browser/editor-widget'; import { MessageService } from '@theia/core/lib/common/message-service'; import { CommandContribution, CommandRegistry, Command } from '@theia/core/lib/common/command'; import { TabBarToolbarContribution, TabBarToolbarRegistry } from '@theia/core/lib/browser/shell/tab-bar-toolbar'; -import { BoardsService, AttachedSerialBoard } from '../common/protocol/boards-service'; +import { BoardsService } from '../common/protocol/boards-service'; import { ArduinoCommands } from './arduino-commands'; import { CoreService } from '../common/protocol/core-service'; import { WorkspaceServiceExt } from './workspace-service-ext'; @@ -26,8 +26,6 @@ import { StatusBar, ShellLayoutRestorer, StatusBarAlignment, - QuickOpenItem, - QuickOpenMode, QuickOpenService, LabelProvider } from '@theia/core/lib/browser'; @@ -47,6 +45,8 @@ import { BoardsToolBarItem } from './boards/boards-toolbar-item'; import { BoardsConfig } from './boards/boards-config'; import { MonitorService } from '../common/protocol/monitor-service'; import { ConfigService } from '../common/protocol/config-service'; +import { MonitorConnection } from './monitor/monitor-connection'; +import { MonitorViewContribution } from './monitor/monitor-view-contribution'; export namespace ArduinoMenus { export const SKETCH = [...MAIN_MENU_BAR, '3_sketch']; @@ -72,9 +72,6 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C @inject(MonitorService) protected readonly monitorService: MonitorService; - // TODO: make this better! - protected connectionId: string | undefined; - @inject(WorkspaceServiceExt) protected readonly workspaceServiceExt: WorkspaceServiceExt; @@ -143,6 +140,8 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C @inject(ConfigService) protected readonly configService: ConfigService; + @inject(MonitorConnection) + protected readonly monitorConnection: MonitorConnection; protected boardsToolbarItem: BoardsToolBarItem | null; protected wsSketchCount: number = 0; @@ -197,13 +196,19 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C commands={this.commands} boardsServiceClient={this.boardsServiceClient} boardService={this.boardsService} />, - isVisible: widget => this.isArduinoToolbar(widget) + isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'left' + }); + registry.registerItem({ + id: 'toggle-serial-monitor', + command: MonitorViewContribution.OPEN_SERIAL_MONITOR, + tooltip: 'Toggle Serial Monitor', + isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'right' }) } registerCommands(registry: CommandRegistry): void { registry.registerCommand(ArduinoCommands.VERIFY, { - isVisible: widget => this.isArduinoToolbar(widget), + isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'left', isEnabled: widget => true, execute: async () => { const widget = this.getCurrentWidget(); @@ -231,7 +236,7 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C } }); registry.registerCommand(ArduinoCommands.UPLOAD, { - isVisible: widget => this.isArduinoToolbar(widget), + isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'left', isEnabled: widget => true, execute: async () => { const widget = this.getCurrentWidget(); @@ -244,6 +249,9 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C return; } + const connectionConfig = this.monitorConnection.connectionConfig; + await this.monitorConnection.disconnect(); + try { const { boardsConfig } = this.boardsServiceClient; if (!boardsConfig || !boardsConfig.selectedBoard) { @@ -256,12 +264,16 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C await this.coreService.upload({ uri: uri.toString(), board: boardsConfig.selectedBoard, port: selectedPort }); } catch (e) { await this.messageService.error(e.toString()); + } finally { + if (connectionConfig) { + await this.monitorConnection.connect(connectionConfig); + } } } }); registry.registerCommand(ArduinoCommands.SHOW_OPEN_CONTEXT_MENU, { - isVisible: widget => this.isArduinoToolbar(widget), - isEnabled: widget => this.isArduinoToolbar(widget), + isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'left', + isEnabled: widget => ArduinoToolbar.is(widget) && widget.side === 'left', execute: async (widget: Widget, target: EventTarget) => { if (this.wsSketchCount) { const el = (target as HTMLElement).parentElement; @@ -287,8 +299,8 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C } }) registry.registerCommand(ArduinoCommands.SAVE_SKETCH, { - isEnabled: widget => this.isArduinoToolbar(widget), - isVisible: widget => this.isArduinoToolbar(widget), + isEnabled: widget => ArduinoToolbar.is(widget) && widget.side === 'left', + isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'left', execute: async (sketch: Sketch) => { registry.executeCommand(CommonCommands.SAVE_ALL.id); } @@ -324,65 +336,6 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C }, isToggled: () => ARDUINO_PRO_MODE }); - registry.registerCommand(ArduinoCommands.CONNECT_TODO, { - execute: async () => { - 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; - } - if (this.connectionId) { - console.log('>>> Disposing existing monitor connection before establishing a new one...'); - const result = await this.monitorService.disconnect(this.connectionId); - if (!result) { - // TODO: better!!! - console.error(`Could not close connection: ${this.connectionId}. Check the backend logs.`); - } else { - console.log(`<<< Disposed ${this.connectionId} connection.`) - } - } - const { connectionId } = await this.monitorService.connect({ board: selectedBoard, port: selectedPort }); - this.connectionId = connectionId; - } - }); - registry.registerCommand(ArduinoCommands.SEND, { - isEnabled: () => !!this.connectionId, - execute: async () => { - const { monitorService, connectionId } = this; - const model = { - onType(lookFor: string, acceptor: (items: QuickOpenItem[]) => void): void { - acceptor([ - new QuickOpenItem({ - label: "Type your message and press 'Enter' to send it to the board. Escape to cancel.", - run: (mode: QuickOpenMode): boolean => { - if (mode !== QuickOpenMode.OPEN) { - return false; - } - monitorService.send(connectionId!, lookFor + '\n'); - return true; - } - }) - ]); - } - }; - const options = { - placeholder: "Your message. The message will be suffixed with a LF ['\\n'].", - }; - this.quickOpenService.open(model, options); - } - }) } registerMenus(registry: MenuModelRegistry) { @@ -555,13 +508,6 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C return undefined; } - private isArduinoToolbar(maybeToolbarWidget: any): boolean { - if (maybeToolbarWidget instanceof ArduinoToolbar) { - return true; - } - return false; - } - private toUri(arg: any): URI | undefined { if (arg instanceof URI) { return arg; diff --git a/arduino-ide-extension/src/browser/arduino-frontend-module.ts b/arduino-ide-extension/src/browser/arduino-frontend-module.ts index 4e74e2c9..645c96d0 100644 --- a/arduino-ide-extension/src/browser/arduino-frontend-module.ts +++ b/arduino-ide-extension/src/browser/arduino-frontend-module.ts @@ -57,12 +57,12 @@ import { BoardItemRenderer } from './boards/boards-item-renderer'; import { MonitorServiceClientImpl } from './monitor/monitor-service-client-impl'; import { MonitorServicePath, MonitorService, MonitorServiceClient } from '../common/protocol/monitor-service'; import { ConfigService, ConfigServicePath } from '../common/protocol/config-service'; +import { MonitorWidget } from './monitor/monitor-widget'; +import { MonitorViewContribution } from './monitor/monitor-view-contribution'; +import { MonitorConnection } from './monitor/monitor-connection'; +import { MonitorModel } from './monitor/monitor-model'; const ElementQueries = require('css-element-queries/src/ElementQueries'); -if (!ARDUINO_PRO_MODE) { - require('../../src/browser/style/silent-bottom-panel-tabs.css'); -} - export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Unbind, isBound: interfaces.IsBound, rebind: interfaces.Rebind) => { ElementQueries.listen(); ElementQueries.init(); @@ -155,12 +155,23 @@ export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Un return workspaceServiceExt; }); + // Serial Monitor + bind(MonitorModel).toSelf().inSingletonScope(); + bind(MonitorWidget).toSelf(); + bindViewContribution(bind, MonitorViewContribution); + bind(TabBarToolbarContribution).toService(MonitorViewContribution); + bind(WidgetFactory).toDynamicValue(context => ({ + id: MonitorWidget.ID, + createWidget: () => context.container.get(MonitorWidget) + })); // Frontend binding for the monitor service. bind(MonitorService).toDynamicValue(context => { const connection = context.container.get(WebSocketConnectionProvider); const client = context.container.get(MonitorServiceClientImpl); return connection.createProxy(MonitorServicePath, client); }).inSingletonScope(); + // MonitorConnection + bind(MonitorConnection).toSelf().inSingletonScope(); // Monitor service client to receive and delegate notifications from the backend. bind(MonitorServiceClientImpl).toSelf().inSingletonScope(); bind(MonitorServiceClient).toDynamicValue(context => { diff --git a/arduino-ide-extension/src/browser/monitor/monitor-connection.ts b/arduino-ide-extension/src/browser/monitor/monitor-connection.ts new file mode 100644 index 00000000..ce08ee33 --- /dev/null +++ b/arduino-ide-extension/src/browser/monitor/monitor-connection.ts @@ -0,0 +1,53 @@ +import { injectable, inject } from "inversify"; +import { MonitorService, ConnectionConfig } from "../../common/protocol/monitor-service"; +import { Emitter, Event } from "@theia/core"; + +@injectable() +export class MonitorConnection { + + @inject(MonitorService) + protected readonly monitorService: MonitorService; + + connectionId: string | undefined; + + protected _connectionConfig: ConnectionConfig | undefined; + + protected readonly onConnectionChangedEmitter = new Emitter(); + readonly onConnectionChanged: Event = this.onConnectionChangedEmitter.event; + + get connectionConfig(): ConnectionConfig | undefined { + return this._connectionConfig; + } + + async connect(config: ConnectionConfig): Promise { + if (this.connectionId) { + await this.disconnect(); + } + const { connectionId } = await this.monitorService.connect(config); + this.connectionId = connectionId; + this._connectionConfig = config; + + this.onConnectionChangedEmitter.fire(this.connectionId); + + return connectionId; + } + + async disconnect(): Promise { + let result = true; + const connections = await this.monitorService.getConnectionIds(); + if (this.connectionId && connections.findIndex(id => id === this.connectionId) >= 0) { + console.log('>>> Disposing existing monitor connection before establishing a new one...'); + result = await this.monitorService.disconnect(this.connectionId); + if (!result) { + // TODO: better!!! + console.error(`Could not close connection: ${this.connectionId}. Check the backend logs.`); + } else { + console.log(`<<< Disposed ${this.connectionId} connection.`); + this.connectionId = undefined; + this._connectionConfig = undefined; + this.onConnectionChangedEmitter.fire(this.connectionId); + } + } + return result; + } +} \ No newline at end of file diff --git a/arduino-ide-extension/src/browser/monitor/monitor-model.ts b/arduino-ide-extension/src/browser/monitor/monitor-model.ts new file mode 100644 index 00000000..d364b435 --- /dev/null +++ b/arduino-ide-extension/src/browser/monitor/monitor-model.ts @@ -0,0 +1,58 @@ +import { injectable } from "inversify"; +import { Emitter } from "@theia/core"; + +export namespace MonitorModel { + export interface Data { + autoscroll: boolean, + timestamp: boolean, + baudRate: number, + lineEnding: string + } +} + +@injectable() +export class MonitorModel { + + protected readonly onChangeEmitter = new Emitter(); + + readonly onChange = this.onChangeEmitter.event; + + protected _autoscroll: boolean = true; + protected _timestamp: boolean = false; + baudRate: number; + lineEnding: string = '\n'; + + get autoscroll(): boolean { + return this._autoscroll; + } + + get timestamp(): boolean { + return this._timestamp; + } + + toggleAutoscroll(): void { + this._autoscroll = !this._autoscroll; + this.onChangeEmitter.fire(undefined); + } + + toggleTimestamp(): void { + this._timestamp = !this._timestamp; + this.onChangeEmitter.fire(undefined); + } + + restore(model: MonitorModel.Data) { + this._autoscroll = model.autoscroll; + this._timestamp = model.timestamp; + this.baudRate = model.baudRate; + this.lineEnding = model.lineEnding; + } + + store(): MonitorModel.Data { + return { + autoscroll: this._autoscroll, + timestamp: this._timestamp, + baudRate: this.baudRate, + lineEnding: this.lineEnding + } + } +} \ No newline at end of file diff --git a/arduino-ide-extension/src/browser/monitor/monitor-view-contribution.tsx b/arduino-ide-extension/src/browser/monitor/monitor-view-contribution.tsx new file mode 100644 index 00000000..26bc93ad --- /dev/null +++ b/arduino-ide-extension/src/browser/monitor/monitor-view-contribution.tsx @@ -0,0 +1,127 @@ +import * as React from 'react'; +import { injectable, inject } from "inversify"; +import { AbstractViewContribution } from "@theia/core/lib/browser"; +import { MonitorWidget } from "./monitor-widget"; +import { MenuModelRegistry, Command, CommandRegistry } from "@theia/core"; +import { ArduinoMenus } from "../arduino-frontend-contribution"; +import { TabBarToolbarContribution, TabBarToolbarRegistry } from "@theia/core/lib/browser/shell/tab-bar-toolbar"; +import { MonitorModel } from './monitor-model'; +import { ArduinoToolbar } from '../toolbar/arduino-toolbar'; + +export namespace SerialMonitor { + export namespace Commands { + export const AUTOSCROLL: Command = { + id: 'serial-monitor-autoscroll', + label: 'Autoscroll' + } + export const TIMESTAMP: Command = { + id: 'serial-monitor-timestamp', + label: 'Timestamp' + } + export const CLEAR_OUTPUT: Command = { + id: 'serial-monitor-clear-output', + label: 'Clear Output', + iconClass: 'clear-all' + } + } +} + +@injectable() +export class MonitorViewContribution extends AbstractViewContribution implements TabBarToolbarContribution { + + static readonly OPEN_SERIAL_MONITOR = MonitorWidget.ID + ':toggle'; + + @inject(MonitorModel) protected readonly model: MonitorModel; + + constructor() { + super({ + widgetId: MonitorWidget.ID, + widgetName: 'Serial Monitor', + defaultWidgetOptions: { + area: 'bottom' + }, + toggleCommandId: MonitorViewContribution.OPEN_SERIAL_MONITOR, + toggleKeybinding: 'ctrl+shift+m' + }) + } + + registerMenus(menus: MenuModelRegistry): void { + if (this.toggleCommand) { + menus.registerMenuAction(ArduinoMenus.TOOLS, { + commandId: this.toggleCommand.id, + label: 'Serial Monitor' + }); + } + } + + async registerToolbarItems(registry: TabBarToolbarRegistry) { + registry.registerItem({ + id: 'monitor-autoscroll', + render: () => this.renderAutoScrollButton(), + isVisible: widget => widget instanceof MonitorWidget, + onDidChange: this.model.onChange + }); + registry.registerItem({ + id: 'monitor-timestamp', + render: () => this.renderTimestampButton(), + isVisible: widget => widget instanceof MonitorWidget, + onDidChange: this.model.onChange + }); + registry.registerItem({ + id: SerialMonitor.Commands.CLEAR_OUTPUT.id, + command: SerialMonitor.Commands.CLEAR_OUTPUT.id, + tooltip: 'Clear Output' + }); + } + + registerCommands(commands: CommandRegistry): void { + commands.registerCommand(SerialMonitor.Commands.CLEAR_OUTPUT, { + isEnabled: widget => widget instanceof MonitorWidget, + isVisible: widget => widget instanceof MonitorWidget, + execute: widget => { + if (widget instanceof MonitorWidget) { + widget.clear(); + } + } + }); + if (this.toggleCommand) { + commands.registerCommand(this.toggleCommand, { + execute: () => this.openView({ + toggle: true, + activate: true + }), + isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'right' + }); + } + } + + protected renderAutoScrollButton(): React.ReactNode { + return +
+
; + } + + protected readonly toggleAutoScroll = () => this.doToggleAutoScroll(); + protected async doToggleAutoScroll() { + this.model.toggleAutoscroll(); + } + + protected renderTimestampButton(): React.ReactNode { + return +
+
; + } + + protected readonly toggleTimestamp = () => this.doToggleTimestamp(); + protected async doToggleTimestamp() { + this.model.toggleTimestamp(); + } +} \ No newline at end of file diff --git a/arduino-ide-extension/src/browser/monitor/monitor-widget.tsx b/arduino-ide-extension/src/browser/monitor/monitor-widget.tsx new file mode 100644 index 00000000..84425bff --- /dev/null +++ b/arduino-ide-extension/src/browser/monitor/monitor-widget.tsx @@ -0,0 +1,398 @@ +import { ReactWidget, Message, Widget, StatefulWidget } 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 { + + protected inputField: HTMLInputElement | null; + + constructor(props: SerialMonitorSendField.Props) { + super(props); + this.state = { value: '' }; + + this.handleChange = this.handleChange.bind(this); + this.handleSubmit = this.handleSubmit.bind(this); + } + + componentDidMount() { + if (this.inputField) { + this.inputField.focus(); + } + } + + render() { + return +
+ this.inputField = ref} + type='text' id='serial-monitor-send' + autoComplete='off' + value={this.state.value} + onChange={this.handleChange} /> + +
+
+ } + + 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 implements StatefulWidget { + + static readonly ID = 'serial-monitor'; + + protected lines: string[]; + protected tempData: string; + + protected widgetHeight: number; + + protected continuePreviousConnection: boolean; + + 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.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(); + } + + storeState(): MonitorModel.Data { + return this.model.store(); + } + + restoreState(oldState: MonitorModel.Data): void { + this.model.restore(oldState); + } + + 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 && currentConnectionConfig) { + this.continuePreviousConnection = true; + this.connection.connect(currentConnectionConfig); + } + }) + ); + this.toDisposeOnDetach.push( + this.boardsServiceClient.onBoardsConfigChanged(async boardConfig => { + this.connect(); + }) + ) + + this.toDisposeOnDetach.push(this.connection.onConnectionChanged(() => { + if (!this.continuePreviousConnection) { + this.clear(); + } else { + this.continuePreviousConnection = false; + } + })); + } + + 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.model.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) { + 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(); + const leVal = this.model.lineEnding && le.find(val => val.value === this.model.lineEnding); + const brVal = this.model.baudRate && br.find(val => val.value === this.model.baudRate); + return +
+
+
+ +
+
+ {this.renderSelectField('arduino-serial-monitor-line-endings', le, leVal || le[1], this.onChangeLineEnding)} + {this.renderSelectField('arduino-serial-monitor-baud-rates', br, brVal || 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.model.lineEnding); + } + } + + protected readonly onChangeLineEnding = (le: SelectOption) => { + this.model.lineEnding = typeof le.value === 'string' ? le.value : '\n'; + } + + protected readonly onChangeBaudRate = async (br: SelectOption) => { + await this.connection.disconnect(); + this.model.baudRate = typeof br.value === 'number' ? br.value : 9600; + this.clear(); + const config = await this.getConnectionConfig(); + if (config) { + await this.connection.connect(config); + } + } + + 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