mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-07-08 11:56:36 +00:00
commit
3e0842e93a
@ -23,7 +23,10 @@
|
|||||||
"@theia/search-in-workspace": "next",
|
"@theia/search-in-workspace": "next",
|
||||||
"@types/ps-tree": "^1.1.0",
|
"@types/ps-tree": "^1.1.0",
|
||||||
"@types/which": "^1.3.1",
|
"@types/which": "^1.3.1",
|
||||||
|
"@types/react-select": "^3.0.0",
|
||||||
|
"@types/google-protobuf": "^3.7.1",
|
||||||
"css-element-queries": "^1.2.0",
|
"css-element-queries": "^1.2.0",
|
||||||
|
"react-select": "^3.0.4",
|
||||||
"p-queue": "^5.0.0",
|
"p-queue": "^5.0.0",
|
||||||
"ps-tree": "^1.2.0",
|
"ps-tree": "^1.2.0",
|
||||||
"tree-kill": "^1.2.1",
|
"tree-kill": "^1.2.1",
|
||||||
|
@ -42,15 +42,4 @@ export namespace ArduinoCommands {
|
|||||||
export const TOGGLE_PRO_MODE: Command = {
|
export const TOGGLE_PRO_MODE: Command = {
|
||||||
id: "arduino-toggle-pro-mode"
|
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'
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ import { EditorWidget } from '@theia/editor/lib/browser/editor-widget';
|
|||||||
import { MessageService } from '@theia/core/lib/common/message-service';
|
import { MessageService } from '@theia/core/lib/common/message-service';
|
||||||
import { CommandContribution, CommandRegistry, Command } from '@theia/core/lib/common/command';
|
import { CommandContribution, CommandRegistry, Command } from '@theia/core/lib/common/command';
|
||||||
import { TabBarToolbarContribution, TabBarToolbarRegistry } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
|
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 { ArduinoCommands } from './arduino-commands';
|
||||||
import { CoreService } from '../common/protocol/core-service';
|
import { CoreService } from '../common/protocol/core-service';
|
||||||
import { WorkspaceServiceExt } from './workspace-service-ext';
|
import { WorkspaceServiceExt } from './workspace-service-ext';
|
||||||
@ -26,8 +26,6 @@ import {
|
|||||||
StatusBar,
|
StatusBar,
|
||||||
ShellLayoutRestorer,
|
ShellLayoutRestorer,
|
||||||
StatusBarAlignment,
|
StatusBarAlignment,
|
||||||
QuickOpenItem,
|
|
||||||
QuickOpenMode,
|
|
||||||
QuickOpenService,
|
QuickOpenService,
|
||||||
LabelProvider
|
LabelProvider
|
||||||
} from '@theia/core/lib/browser';
|
} from '@theia/core/lib/browser';
|
||||||
@ -47,6 +45,8 @@ import { BoardsToolBarItem } from './boards/boards-toolbar-item';
|
|||||||
import { BoardsConfig } from './boards/boards-config';
|
import { BoardsConfig } from './boards/boards-config';
|
||||||
import { MonitorService } from '../common/protocol/monitor-service';
|
import { MonitorService } from '../common/protocol/monitor-service';
|
||||||
import { ConfigService } from '../common/protocol/config-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 namespace ArduinoMenus {
|
||||||
export const SKETCH = [...MAIN_MENU_BAR, '3_sketch'];
|
export const SKETCH = [...MAIN_MENU_BAR, '3_sketch'];
|
||||||
@ -72,9 +72,6 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C
|
|||||||
@inject(MonitorService)
|
@inject(MonitorService)
|
||||||
protected readonly monitorService: MonitorService;
|
protected readonly monitorService: MonitorService;
|
||||||
|
|
||||||
// TODO: make this better!
|
|
||||||
protected connectionId: string | undefined;
|
|
||||||
|
|
||||||
@inject(WorkspaceServiceExt)
|
@inject(WorkspaceServiceExt)
|
||||||
protected readonly workspaceServiceExt: WorkspaceServiceExt;
|
protected readonly workspaceServiceExt: WorkspaceServiceExt;
|
||||||
|
|
||||||
@ -143,6 +140,8 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C
|
|||||||
|
|
||||||
@inject(ConfigService)
|
@inject(ConfigService)
|
||||||
protected readonly configService: ConfigService;
|
protected readonly configService: ConfigService;
|
||||||
|
@inject(MonitorConnection)
|
||||||
|
protected readonly monitorConnection: MonitorConnection;
|
||||||
|
|
||||||
protected boardsToolbarItem: BoardsToolBarItem | null;
|
protected boardsToolbarItem: BoardsToolBarItem | null;
|
||||||
protected wsSketchCount: number = 0;
|
protected wsSketchCount: number = 0;
|
||||||
@ -197,13 +196,19 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C
|
|||||||
commands={this.commands}
|
commands={this.commands}
|
||||||
boardsServiceClient={this.boardsServiceClient}
|
boardsServiceClient={this.boardsServiceClient}
|
||||||
boardService={this.boardsService} />,
|
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 {
|
registerCommands(registry: CommandRegistry): void {
|
||||||
registry.registerCommand(ArduinoCommands.VERIFY, {
|
registry.registerCommand(ArduinoCommands.VERIFY, {
|
||||||
isVisible: widget => this.isArduinoToolbar(widget),
|
isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'left',
|
||||||
isEnabled: widget => true,
|
isEnabled: widget => true,
|
||||||
execute: async () => {
|
execute: async () => {
|
||||||
const widget = this.getCurrentWidget();
|
const widget = this.getCurrentWidget();
|
||||||
@ -231,7 +236,7 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
registry.registerCommand(ArduinoCommands.UPLOAD, {
|
registry.registerCommand(ArduinoCommands.UPLOAD, {
|
||||||
isVisible: widget => this.isArduinoToolbar(widget),
|
isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'left',
|
||||||
isEnabled: widget => true,
|
isEnabled: widget => true,
|
||||||
execute: async () => {
|
execute: async () => {
|
||||||
const widget = this.getCurrentWidget();
|
const widget = this.getCurrentWidget();
|
||||||
@ -244,6 +249,9 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const connectionConfig = this.monitorConnection.connectionConfig;
|
||||||
|
await this.monitorConnection.disconnect();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { boardsConfig } = this.boardsServiceClient;
|
const { boardsConfig } = this.boardsServiceClient;
|
||||||
if (!boardsConfig || !boardsConfig.selectedBoard) {
|
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 });
|
await this.coreService.upload({ uri: uri.toString(), board: boardsConfig.selectedBoard, port: selectedPort });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await this.messageService.error(e.toString());
|
await this.messageService.error(e.toString());
|
||||||
|
} finally {
|
||||||
|
if (connectionConfig) {
|
||||||
|
await this.monitorConnection.connect(connectionConfig);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
registry.registerCommand(ArduinoCommands.SHOW_OPEN_CONTEXT_MENU, {
|
registry.registerCommand(ArduinoCommands.SHOW_OPEN_CONTEXT_MENU, {
|
||||||
isVisible: widget => this.isArduinoToolbar(widget),
|
isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'left',
|
||||||
isEnabled: widget => this.isArduinoToolbar(widget),
|
isEnabled: widget => ArduinoToolbar.is(widget) && widget.side === 'left',
|
||||||
execute: async (widget: Widget, target: EventTarget) => {
|
execute: async (widget: Widget, target: EventTarget) => {
|
||||||
if (this.wsSketchCount) {
|
if (this.wsSketchCount) {
|
||||||
const el = (target as HTMLElement).parentElement;
|
const el = (target as HTMLElement).parentElement;
|
||||||
@ -287,8 +299,8 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
registry.registerCommand(ArduinoCommands.SAVE_SKETCH, {
|
registry.registerCommand(ArduinoCommands.SAVE_SKETCH, {
|
||||||
isEnabled: widget => this.isArduinoToolbar(widget),
|
isEnabled: widget => ArduinoToolbar.is(widget) && widget.side === 'left',
|
||||||
isVisible: widget => this.isArduinoToolbar(widget),
|
isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'left',
|
||||||
execute: async (sketch: Sketch) => {
|
execute: async (sketch: Sketch) => {
|
||||||
registry.executeCommand(CommonCommands.SAVE_ALL.id);
|
registry.executeCommand(CommonCommands.SAVE_ALL.id);
|
||||||
}
|
}
|
||||||
@ -324,65 +336,6 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C
|
|||||||
},
|
},
|
||||||
isToggled: () => ARDUINO_PRO_MODE
|
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) {
|
registerMenus(registry: MenuModelRegistry) {
|
||||||
@ -555,13 +508,6 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
private isArduinoToolbar(maybeToolbarWidget: any): boolean {
|
|
||||||
if (maybeToolbarWidget instanceof ArduinoToolbar) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private toUri(arg: any): URI | undefined {
|
private toUri(arg: any): URI | undefined {
|
||||||
if (arg instanceof URI) {
|
if (arg instanceof URI) {
|
||||||
return arg;
|
return arg;
|
||||||
|
@ -57,12 +57,12 @@ import { BoardItemRenderer } from './boards/boards-item-renderer';
|
|||||||
import { MonitorServiceClientImpl } from './monitor/monitor-service-client-impl';
|
import { MonitorServiceClientImpl } from './monitor/monitor-service-client-impl';
|
||||||
import { MonitorServicePath, MonitorService, MonitorServiceClient } from '../common/protocol/monitor-service';
|
import { MonitorServicePath, MonitorService, MonitorServiceClient } from '../common/protocol/monitor-service';
|
||||||
import { ConfigService, ConfigServicePath } from '../common/protocol/config-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');
|
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) => {
|
export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Unbind, isBound: interfaces.IsBound, rebind: interfaces.Rebind) => {
|
||||||
ElementQueries.listen();
|
ElementQueries.listen();
|
||||||
ElementQueries.init();
|
ElementQueries.init();
|
||||||
@ -155,12 +155,23 @@ export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Un
|
|||||||
return workspaceServiceExt;
|
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.
|
// Frontend binding for the monitor service.
|
||||||
bind(MonitorService).toDynamicValue(context => {
|
bind(MonitorService).toDynamicValue(context => {
|
||||||
const connection = context.container.get(WebSocketConnectionProvider);
|
const connection = context.container.get(WebSocketConnectionProvider);
|
||||||
const client = context.container.get(MonitorServiceClientImpl);
|
const client = context.container.get(MonitorServiceClientImpl);
|
||||||
return connection.createProxy(MonitorServicePath, client);
|
return connection.createProxy(MonitorServicePath, client);
|
||||||
}).inSingletonScope();
|
}).inSingletonScope();
|
||||||
|
// MonitorConnection
|
||||||
|
bind(MonitorConnection).toSelf().inSingletonScope();
|
||||||
// Monitor service client to receive and delegate notifications from the backend.
|
// Monitor service client to receive and delegate notifications from the backend.
|
||||||
bind(MonitorServiceClientImpl).toSelf().inSingletonScope();
|
bind(MonitorServiceClientImpl).toSelf().inSingletonScope();
|
||||||
bind(MonitorServiceClient).toDynamicValue(context => {
|
bind(MonitorServiceClient).toDynamicValue(context => {
|
||||||
|
@ -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<string | undefined>();
|
||||||
|
readonly onConnectionChanged: Event<string | undefined> = this.onConnectionChangedEmitter.event;
|
||||||
|
|
||||||
|
get connectionConfig(): ConnectionConfig | undefined {
|
||||||
|
return this._connectionConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
async connect(config: ConnectionConfig): Promise<string | undefined> {
|
||||||
|
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<boolean> {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
58
arduino-ide-extension/src/browser/monitor/monitor-model.ts
Normal file
58
arduino-ide-extension/src/browser/monitor/monitor-model.ts
Normal file
@ -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<void>();
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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<MonitorWidget> 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 <React.Fragment key='autoscroll-toolbar-item'>
|
||||||
|
<div
|
||||||
|
title='Toggle Autoscroll'
|
||||||
|
className={`item enabled fa fa-angle-double-down arduino-monitor ${this.model.autoscroll ? 'toggled' : ''}`}
|
||||||
|
onClick={this.toggleAutoScroll}
|
||||||
|
></div>
|
||||||
|
</React.Fragment>;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected readonly toggleAutoScroll = () => this.doToggleAutoScroll();
|
||||||
|
protected async doToggleAutoScroll() {
|
||||||
|
this.model.toggleAutoscroll();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected renderTimestampButton(): React.ReactNode {
|
||||||
|
return <React.Fragment key='line-ending-toolbar-item'>
|
||||||
|
<div
|
||||||
|
title='Toggle Timestamp'
|
||||||
|
className={`item enabled fa fa-clock-o arduino-monitor ${this.model.timestamp ? 'toggled' : ''}`}
|
||||||
|
onClick={this.toggleTimestamp}
|
||||||
|
></div>
|
||||||
|
</React.Fragment>;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected readonly toggleTimestamp = () => this.doToggleTimestamp();
|
||||||
|
protected async doToggleTimestamp() {
|
||||||
|
this.model.toggleTimestamp();
|
||||||
|
}
|
||||||
|
}
|
398
arduino-ide-extension/src/browser/monitor/monitor-widget.tsx
Normal file
398
arduino-ide-extension/src/browser/monitor/monitor-widget.tsx
Normal file
@ -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<SerialMonitorSendField.Props, SerialMonitorSendField.State> {
|
||||||
|
|
||||||
|
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 <React.Fragment>
|
||||||
|
<form onSubmit={this.handleSubmit}>
|
||||||
|
<input
|
||||||
|
tabIndex={-1}
|
||||||
|
ref={ref => this.inputField = ref}
|
||||||
|
type='text' id='serial-monitor-send'
|
||||||
|
autoComplete='off'
|
||||||
|
value={this.state.value}
|
||||||
|
onChange={this.handleChange} />
|
||||||
|
<input className="btn" type="submit" value="Submit" />
|
||||||
|
</form>
|
||||||
|
</React.Fragment>
|
||||||
|
}
|
||||||
|
|
||||||
|
protected handleChange(event: React.ChangeEvent<HTMLInputElement>) {
|
||||||
|
this.setState({ value: event.target.value });
|
||||||
|
}
|
||||||
|
|
||||||
|
protected handleSubmit(event: React.FormEvent<HTMLFormElement>) {
|
||||||
|
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<SerialMonitorOutput.Props> {
|
||||||
|
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 <React.Fragment>
|
||||||
|
<div style={style}>{result}</div>
|
||||||
|
<div style={{ float: "left", clear: "both" }}
|
||||||
|
ref={(el) => { this.theEnd = el; }}>
|
||||||
|
</div>
|
||||||
|
</React.Fragment>;
|
||||||
|
}
|
||||||
|
|
||||||
|
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<ConnectionConfig | undefined> {
|
||||||
|
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<SelectOption> {
|
||||||
|
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<SelectOption> {
|
||||||
|
const baudRates = [300, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200];
|
||||||
|
return baudRates.map<SelectOption>(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 <React.Fragment>
|
||||||
|
<div className='serial-monitor-container'>
|
||||||
|
<div className='head'>
|
||||||
|
<div className='send'>
|
||||||
|
<SerialMonitorSendField onSend={this.onSend} />
|
||||||
|
</div>
|
||||||
|
<div className='config'>
|
||||||
|
{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)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id='serial-monitor-output-container'>
|
||||||
|
<SerialMonitorOutput model={this.model} lines={this.lines} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</React.Fragment>;
|
||||||
|
}
|
||||||
|
|
||||||
|
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<SelectOption>, 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<typeof components.DropdownIndicator>
|
||||||
|
) => {
|
||||||
|
return (
|
||||||
|
<span className='fa fa-caret-down caret'></span>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
return <Select
|
||||||
|
options={options}
|
||||||
|
defaultValue={defaultVal}
|
||||||
|
onChange={onChange}
|
||||||
|
components={{ DropdownIndicator }}
|
||||||
|
theme={theme}
|
||||||
|
styles={selectStyles}
|
||||||
|
maxMenuHeight={this.widgetHeight - 40}
|
||||||
|
classNamePrefix='sms'
|
||||||
|
className='serial-monitor-select'
|
||||||
|
id={id}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
}
|
@ -196,6 +196,7 @@ button.theia-button.main {
|
|||||||
background: #f7f7f7;
|
background: #f7f7f7;
|
||||||
border: 3px solid var(--theia-border-color2);
|
border: 3px solid var(--theia-border-color2);
|
||||||
margin: -3px;
|
margin: -3px;
|
||||||
|
z-index: 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
.arduino-boards-dropdown-item {
|
.arduino-boards-dropdown-item {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
@import './list-widget.css';
|
@import './list-widget.css';
|
||||||
@import './board-select-dialog.css';
|
@import './board-select-dialog.css';
|
||||||
@import './main.css';
|
@import './main.css';
|
||||||
@import './editor.css';
|
@import './editor.css';
|
||||||
|
@import './serial-monitor.css';
|
@ -11,6 +11,7 @@
|
|||||||
cursor: text;
|
cursor: text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#toggle-serial-monitor.arduino-tool-icon:hover,
|
||||||
#arduino-verify.arduino-tool-icon:hover,
|
#arduino-verify.arduino-tool-icon:hover,
|
||||||
#arduino-save-file.arduino-tool-icon:hover,
|
#arduino-save-file.arduino-tool-icon:hover,
|
||||||
#arduino-show-open-context-menu.arduino-tool-icon:hover,
|
#arduino-show-open-context-menu.arduino-tool-icon:hover,
|
||||||
@ -18,6 +19,7 @@
|
|||||||
background-position-y: 60px;
|
background-position-y: 60px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#toggle-serial-monitor.arduino-tool-icon,
|
||||||
#arduino-verify.arduino-tool-icon,
|
#arduino-verify.arduino-tool-icon,
|
||||||
#arduino-save-file.arduino-tool-icon,
|
#arduino-save-file.arduino-tool-icon,
|
||||||
#arduino-show-open-context-menu.arduino-tool-icon,
|
#arduino-show-open-context-menu.arduino-tool-icon,
|
||||||
@ -54,15 +56,41 @@
|
|||||||
background-position-x: 92px;
|
background-position-x: 92px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#toggle-serial-monitor {
|
||||||
|
background: url(../icons/buttons.svg);
|
||||||
|
background-size: 800%;
|
||||||
|
background-position-y: 28px;
|
||||||
|
background-position-x: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
.p-TabBar-toolbar .item.arduino-tool-item {
|
.p-TabBar-toolbar .item.arduino-tool-item {
|
||||||
margin-left: 3px;
|
margin-left: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#arduino-toolbar-container {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-TabBar-toolbar.theia-arduino-toolbar {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#theia-top-panel .p-TabBar-toolbar.theia-arduino-toolbar.right {
|
||||||
|
justify-content: flex-start;
|
||||||
|
min-width: 190px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#theia-top-panel .p-TabBar-toolbar.theia-arduino-toolbar.left {
|
||||||
|
min-width: 398px;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
.arduino-tool-item.item.connected-boards {
|
.arduino-tool-item.item.connected-boards {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.arduino-tool-item.item.connected-boards select {
|
.arduino-tool-item.item.connected-boards select {
|
||||||
line-height: var(--theia-content-line-height);
|
line-height: var(--theia-content-line-height);
|
||||||
font-size: var(--theia-ui-font-size1);
|
font-size: var(--theia-ui-font-size1);
|
||||||
color: var(--theia-ui-font-color1);
|
color: var(--theia-ui-font-color1);
|
||||||
|
116
arduino-ide-extension/src/browser/style/serial-monitor.css
Normal file
116
arduino-ide-extension/src/browser/style/serial-monitor.css
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
.p-TabBar.theia-app-centers .p-TabBar-tabIcon.arduino-serial-monitor-tab-icon {
|
||||||
|
background: url(../icons/buttons.svg);
|
||||||
|
background-size: 800%;
|
||||||
|
background-position-y: 41px;
|
||||||
|
background-position-x: 19px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.serial-monitor-container {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.serial-monitor-container .head {
|
||||||
|
display: flex;
|
||||||
|
padding: 5px;
|
||||||
|
background: var(--theia-brand-color2);
|
||||||
|
height: 27px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.serial-monitor-container .head .send {
|
||||||
|
display: flex;
|
||||||
|
flex:1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.serial-monitor-container .head .send .btn {
|
||||||
|
display: flex;
|
||||||
|
padding: 0 5px;
|
||||||
|
align-items: center;
|
||||||
|
background: var(--theia-brand-color3);
|
||||||
|
color: var(--theia-ui-dialog-font-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.serial-monitor-container .head .send form {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.serial-monitor-container .head .send input#serial-monitor-send {
|
||||||
|
background: var(--theia-layout-color0);
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.serial-monitor-container .head .send input:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.serial-monitor-container .head .config {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.serial-monitor-container .head .config .serial-monitor-select {
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#serial-monitor-output-container {
|
||||||
|
overflow: auto;
|
||||||
|
flex: 1;
|
||||||
|
padding: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-TabBar-toolbar .item.arduino-monitor {
|
||||||
|
width: 24px;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: medium;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-TabBar-toolbar .item.arduino-monitor.toggled {
|
||||||
|
background: var(--theia-brand-color2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-TabBar-toolbar .item .clear-all {
|
||||||
|
background: var(--theia-icon-clear) no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* React Select Styles */
|
||||||
|
.serial-monitor-select .sms__control {
|
||||||
|
border: var(--theia-border-color1) var(--theia-border-width) solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.serial-monitor-select .sms__control--is-focused {
|
||||||
|
border-color: var(--theia-border-color2) !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sms__control--is-focused:hover {
|
||||||
|
border-color: var(--theia-border-color2) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.serial-monitor-select .sms__option--is-selected {
|
||||||
|
background-color: var(--theia-ui-button-color-secondary-hover);
|
||||||
|
color: var(--theia-content-font-color0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.serial-monitor-select .sms__option--is-focused {
|
||||||
|
background-color: var(--theia-ui-button-color-secondary-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.serial-monitor-select .sms__menu {
|
||||||
|
background-color: var(--theia-layout-color1);
|
||||||
|
border: 1px solid var(--theia-border-color2);
|
||||||
|
border-top: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.serial-monitor-select .sms__control.sms__control--menu-is-open {
|
||||||
|
border: 1px solid;
|
||||||
|
border-color: var(--theia-border-color2) !important;
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sms__menu-list {
|
||||||
|
padding-top: 0 !important;
|
||||||
|
padding-bottom: 0 !important;
|
||||||
|
}
|
@ -1,3 +0,0 @@
|
|||||||
.p-Widget.p-TabBar.theia-app-centers.theia-app-bottom .p-TabBar-content-container.ps {
|
|
||||||
display: none;
|
|
||||||
}
|
|
@ -1,26 +1,45 @@
|
|||||||
import { FrontendApplicationContribution, FrontendApplication } from "@theia/core/lib/browser";
|
import { FrontendApplicationContribution, FrontendApplication, Widget, Message } from "@theia/core/lib/browser";
|
||||||
import { injectable, inject } from "inversify";
|
import { injectable, inject } from "inversify";
|
||||||
import { ArduinoToolbar } from "./arduino-toolbar";
|
import { ArduinoToolbar } from "./arduino-toolbar";
|
||||||
import { TabBarToolbarRegistry } from "@theia/core/lib/browser/shell/tab-bar-toolbar";
|
import { TabBarToolbarRegistry } from "@theia/core/lib/browser/shell/tab-bar-toolbar";
|
||||||
import { CommandRegistry } from "@theia/core";
|
import { CommandRegistry } from "@theia/core";
|
||||||
import { LabelParser } from "@theia/core/lib/browser/label-parser";
|
import { LabelParser } from "@theia/core/lib/browser/label-parser";
|
||||||
|
|
||||||
|
export class ArduinoToolbarContainer extends Widget {
|
||||||
|
|
||||||
|
protected toolbars: ArduinoToolbar[];
|
||||||
|
|
||||||
|
constructor(...toolbars: ArduinoToolbar[]) {
|
||||||
|
super();
|
||||||
|
this.id = 'arduino-toolbar-container';
|
||||||
|
this.toolbars = toolbars;
|
||||||
|
}
|
||||||
|
|
||||||
|
onAfterAttach(msg: Message) {
|
||||||
|
for (const toolbar of this.toolbars) {
|
||||||
|
Widget.attach(toolbar, this.node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class ArduinoToolbarContribution implements FrontendApplicationContribution {
|
export class ArduinoToolbarContribution implements FrontendApplicationContribution {
|
||||||
|
|
||||||
protected toolbarWidget: ArduinoToolbar;
|
protected arduinoToolbarContainer: ArduinoToolbarContainer;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@inject(TabBarToolbarRegistry) protected tabBarToolBarRegistry: TabBarToolbarRegistry,
|
@inject(TabBarToolbarRegistry) protected tabBarToolBarRegistry: TabBarToolbarRegistry,
|
||||||
@inject(CommandRegistry) protected commandRegistry: CommandRegistry,
|
@inject(CommandRegistry) protected commandRegistry: CommandRegistry,
|
||||||
@inject(LabelParser) protected labelParser: LabelParser) {
|
@inject(LabelParser) protected labelParser: LabelParser) {
|
||||||
this.toolbarWidget = new ArduinoToolbar(tabBarToolBarRegistry, commandRegistry, labelParser);
|
const leftToolbarWidget = new ArduinoToolbar(tabBarToolBarRegistry, commandRegistry, labelParser, 'left');
|
||||||
|
const rightToolbarWidget = new ArduinoToolbar(tabBarToolBarRegistry, commandRegistry, labelParser, 'right');
|
||||||
|
this.arduinoToolbarContainer = new ArduinoToolbarContainer(leftToolbarWidget, rightToolbarWidget);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
onStart(app: FrontendApplication) {
|
onStart(app: FrontendApplication) {
|
||||||
app.shell.addWidget(this.toolbarWidget, {
|
app.shell.addWidget(this.arduinoToolbarContainer, {
|
||||||
area: 'top'
|
area: 'top'
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -8,6 +8,7 @@ export const ARDUINO_TOOLBAR_ITEM_CLASS = 'arduino-tool-item';
|
|||||||
|
|
||||||
export namespace ArduinoToolbarComponent {
|
export namespace ArduinoToolbarComponent {
|
||||||
export interface Props {
|
export interface Props {
|
||||||
|
side: 'left' | 'right',
|
||||||
items: (TabBarToolbarItem | ReactTabBarToolbarItem)[],
|
items: (TabBarToolbarItem | ReactTabBarToolbarItem)[],
|
||||||
commands: CommandRegistry,
|
commands: CommandRegistry,
|
||||||
commandIsEnabled: (id: string) => boolean,
|
commandIsEnabled: (id: string) => boolean,
|
||||||
@ -29,25 +30,33 @@ export class ArduinoToolbarComponent extends React.Component<ArduinoToolbarCompo
|
|||||||
const command = this.props.commands.getCommand(item.command);
|
const command = this.props.commands.getCommand(item.command);
|
||||||
const cls = `${ARDUINO_TOOLBAR_ITEM_CLASS} ${TabBarToolbar.Styles.TAB_BAR_TOOLBAR_ITEM} ${command && this.props.commandIsEnabled(command.id) ? ' enabled' : ''}`
|
const cls = `${ARDUINO_TOOLBAR_ITEM_CLASS} ${TabBarToolbar.Styles.TAB_BAR_TOOLBAR_ITEM} ${command && this.props.commandIsEnabled(command.id) ? ' enabled' : ''}`
|
||||||
return <div key={item.id}
|
return <div key={item.id}
|
||||||
className={cls} >
|
className={cls} >
|
||||||
<div
|
<div
|
||||||
key={item.id + '-icon'}
|
key={item.id + '-icon'}
|
||||||
id={item.id}
|
id={item.id}
|
||||||
className={`${item.id} arduino-tool-icon`}
|
className={`${item.id} arduino-tool-icon`}
|
||||||
onClick={this.props.executeCommand}
|
onClick={this.props.executeCommand}
|
||||||
onMouseOver={() => this.setState({ tooltip: item.tooltip || '' })}
|
onMouseOver={() => this.setState({ tooltip: item.tooltip || '' })}
|
||||||
onMouseOut={() => this.setState({ tooltip: '' })}
|
onMouseOut={() => this.setState({ tooltip: '' })}
|
||||||
title={item.tooltip}>
|
title={item.tooltip}>
|
||||||
{innerText}
|
{innerText}
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
render(): React.ReactNode {
|
render(): React.ReactNode {
|
||||||
return <React.Fragment>
|
const tooltip = <div key='arduino-toolbar-tooltip' className={'arduino-toolbar-tooltip'}>{this.state.tooltip}</div>;
|
||||||
<div key='arduino-toolbar-tooltip' className={'arduino-toolbar-tooltip'}>{this.state.tooltip}</div>
|
const items = [
|
||||||
{[...this.props.items].map(item => TabBarToolbarItem.is(item) ? this.renderItem(item) : item.render())}
|
<React.Fragment key={this.props.side + '-arduino-toolbar-tooltip'}>
|
||||||
</React.Fragment>;
|
{[...this.props.items].map(item => TabBarToolbarItem.is(item) ? this.renderItem(item) : item.render())}
|
||||||
|
</React.Fragment>
|
||||||
|
]
|
||||||
|
if (this.props.side === 'left') {
|
||||||
|
items.unshift(tooltip);
|
||||||
|
} else {
|
||||||
|
items.push(tooltip)
|
||||||
|
}
|
||||||
|
return items;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,10 +67,11 @@ export class ArduinoToolbar extends ReactWidget {
|
|||||||
constructor(
|
constructor(
|
||||||
protected readonly tabBarToolbarRegistry: TabBarToolbarRegistry,
|
protected readonly tabBarToolbarRegistry: TabBarToolbarRegistry,
|
||||||
protected readonly commands: CommandRegistry,
|
protected readonly commands: CommandRegistry,
|
||||||
protected readonly labelParser: LabelParser
|
protected readonly labelParser: LabelParser,
|
||||||
|
public readonly side: 'left' | 'right'
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.id = 'arduino-toolbar';
|
this.id = side + '-arduino-toolbar';
|
||||||
this.addClass(TabBarToolbar.Styles.TAB_BAR_TOOLBAR);
|
this.addClass(TabBarToolbar.Styles.TAB_BAR_TOOLBAR);
|
||||||
this.init();
|
this.init();
|
||||||
this.tabBarToolbarRegistry.onDidChange(() => this.updateToolbar());
|
this.tabBarToolbarRegistry.onDidChange(() => this.updateToolbar());
|
||||||
@ -82,7 +92,7 @@ export class ArduinoToolbar extends ReactWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected init(): void {
|
protected init(): void {
|
||||||
this.node.classList.add('theia-arduino-toolbar');
|
this.node.classList.add('theia-arduino-toolbar', this.side);
|
||||||
this.update();
|
this.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,6 +103,8 @@ export class ArduinoToolbar extends ReactWidget {
|
|||||||
|
|
||||||
protected render(): React.ReactNode {
|
protected render(): React.ReactNode {
|
||||||
return <ArduinoToolbarComponent
|
return <ArduinoToolbarComponent
|
||||||
|
key='arduino-toolbar-component'
|
||||||
|
side={this.side}
|
||||||
items={[...this.items.values()]}
|
items={[...this.items.values()]}
|
||||||
commands={this.commands}
|
commands={this.commands}
|
||||||
commandIsEnabled={this.doCommandIsEnabled}
|
commandIsEnabled={this.doCommandIsEnabled}
|
||||||
@ -107,3 +119,9 @@ export class ArduinoToolbar extends ReactWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export namespace ArduinoToolbar {
|
||||||
|
export function is(maybeToolbarWidget: any): maybeToolbarWidget is ArduinoToolbar {
|
||||||
|
return maybeToolbarWidget instanceof ArduinoToolbar;
|
||||||
|
}
|
||||||
|
}
|
@ -23,6 +23,7 @@ export interface MonitorService extends JsonRpcServer<MonitorServiceClient> {
|
|||||||
connect(config: ConnectionConfig): Promise<{ connectionId: string }>;
|
connect(config: ConnectionConfig): Promise<{ connectionId: string }>;
|
||||||
disconnect(connectionId: string): Promise<boolean>;
|
disconnect(connectionId: string): Promise<boolean>;
|
||||||
send(connectionId: string, data: string | Uint8Array): Promise<void>;
|
send(connectionId: string, data: string | Uint8Array): Promise<void>;
|
||||||
|
getConnectionIds(): Promise<string[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ConnectionConfig {
|
export interface ConnectionConfig {
|
||||||
|
@ -6,6 +6,7 @@ import { ILogger, Disposable, DisposableCollection } from '@theia/core';
|
|||||||
import { MonitorService, MonitorServiceClient, ConnectionConfig, ConnectionType } from '../../common/protocol/monitor-service';
|
import { MonitorService, MonitorServiceClient, ConnectionConfig, ConnectionType } from '../../common/protocol/monitor-service';
|
||||||
import { StreamingOpenReq, StreamingOpenResp, MonitorConfig } from '../cli-protocol/monitor/monitor_pb';
|
import { StreamingOpenReq, StreamingOpenResp, MonitorConfig } from '../cli-protocol/monitor/monitor_pb';
|
||||||
import { MonitorClientProvider } from './monitor-client-provider';
|
import { MonitorClientProvider } from './monitor-client-provider';
|
||||||
|
import * as google_protobuf_struct_pb from "google-protobuf/google/protobuf/struct_pb";
|
||||||
|
|
||||||
export interface MonitorDuplex {
|
export interface MonitorDuplex {
|
||||||
readonly toDispose: Disposable;
|
readonly toDispose: Disposable;
|
||||||
@ -59,6 +60,10 @@ export class MonitorServiceImpl implements MonitorService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getConnectionIds(): Promise<string[]> {
|
||||||
|
return Array.from(this.connections.keys());
|
||||||
|
}
|
||||||
|
|
||||||
async connect(config: ConnectionConfig): Promise<{ connectionId: string }> {
|
async connect(config: ConnectionConfig): Promise<{ connectionId: string }> {
|
||||||
const client = await this.monitorClientProvider.client;
|
const client = await this.monitorClientProvider.client;
|
||||||
const duplex = client.streamingOpen();
|
const duplex = client.streamingOpen();
|
||||||
@ -94,7 +99,8 @@ export class MonitorServiceImpl implements MonitorService {
|
|||||||
monitorConfig.setType(this.mapType(type));
|
monitorConfig.setType(this.mapType(type));
|
||||||
monitorConfig.setTarget(port);
|
monitorConfig.setTarget(port);
|
||||||
if (config.baudRate !== undefined) {
|
if (config.baudRate !== undefined) {
|
||||||
monitorConfig.setAdditionalconfig({ 'BaudRate': config.baudRate });
|
const obj = google_protobuf_struct_pb.Struct.fromJavaScript({ 'BaudRate': config.baudRate });
|
||||||
|
monitorConfig.setAdditionalconfig(obj);
|
||||||
}
|
}
|
||||||
req.setMonitorconfig(monitorConfig);
|
req.setMonitorconfig(monitorConfig);
|
||||||
|
|
||||||
@ -122,11 +128,12 @@ export class MonitorServiceImpl implements MonitorService {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async doDisconnect(connectionId: string, duplex: MonitorDuplex): Promise<boolean> {
|
protected async doDisconnect(connectionId: string, monitorDuplex: MonitorDuplex): Promise<boolean> {
|
||||||
const { toDispose } = duplex;
|
const { duplex } = monitorDuplex;
|
||||||
this.logger.info(`>>> Disposing monitor connection: ${connectionId}...`);
|
this.logger.info(`>>> Disposing monitor connection: ${connectionId}...`);
|
||||||
try {
|
try {
|
||||||
toDispose.dispose();
|
duplex.cancel();
|
||||||
|
this.connections.delete(connectionId);
|
||||||
this.logger.info(`<<< Connection disposed: ${connectionId}.`);
|
this.logger.info(`<<< Connection disposed: ${connectionId}.`);
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user