From ff336dccc57cdd11be6620001feeac545b58ada4 Mon Sep 17 00:00:00 2001 From: jbicker Date: Tue, 25 Jun 2019 17:45:44 +0200 Subject: [PATCH 1/7] Enhanced boards toolbar item. Signed-off-by: jbicker --- .../src/browser/arduino-frontend-contribution.tsx | 15 ++++++++------- .../src/browser/components/connected-boards.tsx | 8 ++++---- arduino-ide-extension/src/browser/style/main.css | 1 + 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx b/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx index be0446f1..a03e8954 100644 --- a/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx +++ b/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx @@ -27,6 +27,7 @@ import { ArduinoOpenSketchContextMenu } from './arduino-file-menu'; import { Sketch, SketchesService } from '../common/protocol/sketches-service'; import { WindowService } from '@theia/core/lib/browser/window/window-service'; import { CommonCommands } from '@theia/core/lib/browser/common-frontend-contribution' +import { BoardsToolBarItem } from './components/boards-toolbar-item'; @injectable() export class ArduinoFrontendContribution implements TabBarToolbarContribution, CommandContribution { @@ -118,15 +119,15 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C }); registry.registerItem({ id: ConnectedBoards.TOOLBAR_ID, - // render: () => , - render: () => , + // render: () => , isVisible: widget => this.isArduinoToolbar(widget) }) } diff --git a/arduino-ide-extension/src/browser/components/connected-boards.tsx b/arduino-ide-extension/src/browser/components/connected-boards.tsx index e90163e4..700c78b0 100644 --- a/arduino-ide-extension/src/browser/components/connected-boards.tsx +++ b/arduino-ide-extension/src/browser/components/connected-boards.tsx @@ -29,14 +29,14 @@ export class ConnectedBoards extends React.Component{label} ]; } - return
- - + { content } - + { !!this.state.otherBoard && } diff --git a/arduino-ide-extension/src/browser/style/main.css b/arduino-ide-extension/src/browser/style/main.css index 734cb13c..29d43d35 100644 --- a/arduino-ide-extension/src/browser/style/main.css +++ b/arduino-ide-extension/src/browser/style/main.css @@ -65,6 +65,7 @@ } .arduino-open-boards-button { + background: white; } .arduino-boards-toolbar-item { From 4c66dec36e84cf40454f1ff75b3f1b79f43fa13e Mon Sep 17 00:00:00 2001 From: jbicker Date: Wed, 26 Jun 2019 14:08:45 +0200 Subject: [PATCH 2/7] Added board select toolbar item fill context menu with connected boards Signed-off-by: jbicker --- .../src/browser/arduino-commands.ts | 8 ++ .../src/browser/arduino-file-menu.ts | 85 ++++++++++++++----- .../browser/arduino-frontend-contribution.tsx | 19 ++++- .../src/browser/arduino-frontend-module.ts | 4 +- .../components/boards-toolbar-item.tsx | 25 +++--- 5 files changed, 105 insertions(+), 36 deletions(-) diff --git a/arduino-ide-extension/src/browser/arduino-commands.ts b/arduino-ide-extension/src/browser/arduino-commands.ts index 6c139c3d..d07298d7 100644 --- a/arduino-ide-extension/src/browser/arduino-commands.ts +++ b/arduino-ide-extension/src/browser/arduino-commands.ts @@ -40,4 +40,12 @@ export namespace ArduinoCommands { label: "Refresh attached boards" } + export const SELECT_BOARD: Command = { + id: "arduino-select-board" + } + + export const OPEN_BOARDS_DIALOG: Command = { + id: "arduino-open-boards-dialog" + } + } diff --git a/arduino-ide-extension/src/browser/arduino-file-menu.ts b/arduino-ide-extension/src/browser/arduino-file-menu.ts index e27b28a0..389eada0 100644 --- a/arduino-ide-extension/src/browser/arduino-file-menu.ts +++ b/arduino-ide-extension/src/browser/arduino-file-menu.ts @@ -4,16 +4,21 @@ import { CommonMenus } from "@theia/core/lib/browser"; import { ArduinoCommands } from "./arduino-commands"; import { SketchesService, Sketch } from "../common/protocol/sketches-service"; import { AWorkspaceService } from "./arduino-workspace-service"; +import { BoardsService, Board, AttachedSerialBoard, AttachedNetworkBoard } from "../common/protocol/boards-service"; -export namespace ArduinoOpenSketchContextMenu { - export const PATH: MenuPath = ['arduino-open-sketch-context-menu']; - export const OPEN_GROUP: MenuPath = [...PATH, '1_open']; - export const WS_SKETCHES_GROUP: MenuPath = [...PATH, '2_sketches']; - export const EXAMPLE_SKETCHES_GROUP: MenuPath = [...PATH, '3_examples']; +export namespace ArduinoToolbarContextMenu { + export const OPEN_SKETCH_PATH: MenuPath = ['arduino-open-sketch-context-menu']; + export const OPEN_GROUP: MenuPath = [...OPEN_SKETCH_PATH, '1_open']; + export const WS_SKETCHES_GROUP: MenuPath = [...OPEN_SKETCH_PATH, '2_sketches']; + export const EXAMPLE_SKETCHES_GROUP: MenuPath = [...OPEN_SKETCH_PATH, '3_examples']; + + export const SELECT_BOARDS_PATH: MenuPath = ['arduino-select-boards-context-menu']; + export const CONNECTED_GROUP: MenuPath = [...SELECT_BOARDS_PATH, '1_connected']; + export const OPEN_BOARDS_DIALOG_GROUP: MenuPath = [...SELECT_BOARDS_PATH, '2_open_boards_dialog']; } @injectable() -export class ArduinoFileMenuContribution implements MenuContribution { +export class ArduinoToolbarMenuContribution implements MenuContribution { @inject(CommandRegistry) protected readonly commands: CommandRegistry; @@ -21,31 +26,64 @@ export class ArduinoFileMenuContribution implements MenuContribution { @inject(SketchesService) protected readonly sketches: SketchesService; + @inject(BoardsService) + protected readonly boardsService: BoardsService; + constructor( @inject(AWorkspaceService) protected readonly workspaceService: AWorkspaceService, @inject(MenuModelRegistry) protected readonly menuRegistry: MenuModelRegistry) { workspaceService.onWorkspaceChanged(() => { if (this.workspaceService.workspace) { this.registerSketchesInMenu(menuRegistry); + this.registerConnectedBoardsInMenu(menuRegistry); } }) } - protected registerSketchesInMenu(registry: MenuModelRegistry) { - this.getWorkspaceSketches().then(sketches => { - sketches.forEach(sketch => { - const command: Command = { - id: 'openSketch' + sketch.name - } - this.commands.registerCommand(command, { - execute: () => this.commands.executeCommand(ArduinoCommands.OPEN_SKETCH.id, sketch) - }); + protected async registerConnectedBoardsInMenu(registry: MenuModelRegistry) { + const { boards } = await this.boardsService.getAttachedBoards(); + const selectedBoard = await this.boardsService.getSelectBoard(); + const selectedPort = selectedBoard ? this.getPort(selectedBoard) : ''; + boards.forEach(board => { + const port = this.getPort(board); + const command: Command = { + id: 'selectBoard' + port + } + this.commands.registerCommand(command, { + execute: () => this.commands.executeCommand(ArduinoCommands.SELECT_BOARD.id, board) + }); + registry.registerMenuAction(ArduinoToolbarContextMenu.CONNECTED_GROUP, { + commandId: command.id, + label: board.name + (selectedPort === port ? '*' : '') + }); + }); + } - registry.registerMenuAction(ArduinoOpenSketchContextMenu.WS_SKETCHES_GROUP, { - commandId: command.id, - label: sketch.name - }); - }) + + + protected getPort(board: Board): string { + if(AttachedSerialBoard.is(board)){ + return board.port; + } + if(AttachedNetworkBoard.is(board)) { + return 'netport' + board.port; + } + return ''; + } + + protected async registerSketchesInMenu(registry: MenuModelRegistry) { + const sketches = await this.getWorkspaceSketches(); + sketches.forEach(sketch => { + const command: Command = { + id: 'openSketch' + sketch.name + } + this.commands.registerCommand(command, { + execute: () => this.commands.executeCommand(ArduinoCommands.OPEN_SKETCH.id, sketch) + }); + registry.registerMenuAction(ArduinoToolbarContextMenu.WS_SKETCHES_GROUP, { + commandId: command.id, + label: sketch.name + }); }) } @@ -59,9 +97,14 @@ export class ArduinoFileMenuContribution implements MenuContribution { commandId: ArduinoCommands.NEW_SKETCH.id }) - registry.registerMenuAction(ArduinoOpenSketchContextMenu.OPEN_GROUP, { + registry.registerMenuAction(ArduinoToolbarContextMenu.OPEN_GROUP, { commandId: ArduinoCommands.OPEN_FILE_NAVIGATOR.id, label: 'Open...' }); + + registry.registerMenuAction(ArduinoToolbarContextMenu.OPEN_BOARDS_DIALOG_GROUP, { + commandId: ArduinoCommands.OPEN_BOARDS_DIALOG.id, + label: 'Select Other Board & Port' + }); } } \ No newline at end of file diff --git a/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx b/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx index a03e8954..e526b91f 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 } from '@theia/core/lib/common/command'; import { TabBarToolbarContribution, TabBarToolbarRegistry } from '@theia/core/lib/browser/shell/tab-bar-toolbar'; -import { BoardsService } from '../common/protocol/boards-service'; +import { BoardsService, Board } from '../common/protocol/boards-service'; import { ArduinoCommands } from './arduino-commands'; import { ConnectedBoards } from './components/connected-boards'; import { CoreService } from '../common/protocol/core-service'; @@ -23,7 +23,7 @@ import { EditorManager } from '@theia/editor/lib/browser'; import { ContextMenuRenderer, OpenerService, Widget } from '@theia/core/lib/browser'; import { OpenFileDialogProps, FileDialogService } from '@theia/filesystem/lib/browser/file-dialog'; import { FileSystem } from '@theia/filesystem/lib/common'; -import { ArduinoOpenSketchContextMenu } from './arduino-file-menu'; +import { ArduinoToolbarContextMenu } from './arduino-file-menu'; import { Sketch, SketchesService } from '../common/protocol/sketches-service'; import { WindowService } from '@theia/core/lib/browser/window/window-service'; import { CommonCommands } from '@theia/core/lib/browser/common-frontend-contribution' @@ -120,6 +120,7 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C registry.registerItem({ id: ConnectedBoards.TOOLBAR_ID, render: () => , // render: () => ) => { const el = (event.target as HTMLElement).parentElement; if (el) { - this.contextMenuRenderer.render(ArduinoOpenSketchContextMenu.PATH, { + this.contextMenuRenderer.render(ArduinoToolbarContextMenu.OPEN_SKETCH_PATH, { x: el.getBoundingClientRect().left, y: el.getBoundingClientRect().top + el.offsetHeight }); @@ -222,6 +223,18 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C registry.registerCommand(ArduinoCommands.REFRESH_BOARDS, { isEnabled: () => true, execute: () => this.boardsNotificationService.notifyBoardsInstalled() + }); + registry.registerCommand(ArduinoCommands.SELECT_BOARD, { + isEnabled: () => true, + execute: (board: Board) => { + console.log("SELECT BOARD HERE", board); + } + }) + registry.registerCommand(ArduinoCommands.OPEN_BOARDS_DIALOG, { + isEnabled: () => true, + execute: () => { + console.log("OPEN THE DIALOG HERE"); + } }) } diff --git a/arduino-ide-extension/src/browser/arduino-frontend-module.ts b/arduino-ide-extension/src/browser/arduino-frontend-module.ts index 9dfcdf0b..0df09de3 100644 --- a/arduino-ide-extension/src/browser/arduino-frontend-module.ts +++ b/arduino-ide-extension/src/browser/arduino-frontend-module.ts @@ -27,7 +27,7 @@ import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service import { AWorkspaceService } from './arduino-workspace-service'; import { ThemeService } from '@theia/core/lib/browser/theming'; import { ArduinoTheme } from './arduino-theme'; -import { ArduinoFileMenuContribution } from './arduino-file-menu'; +import { ArduinoToolbarMenuContribution } from './arduino-file-menu'; import { MenuContribution } from '@theia/core'; import { SketchFactory } from './sketch-factory'; import { OutlineViewContribution } from '@theia/outline-view/lib/browser/outline-view-contribution'; @@ -55,7 +55,7 @@ export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Un bind(CommandContribution).toService(ArduinoFrontendContribution); bind(TabBarToolbarContribution).toService(ArduinoFrontendContribution); bind(FrontendApplicationContribution).toService(ArduinoFrontendContribution); - bind(MenuContribution).to(ArduinoFileMenuContribution).inSingletonScope(); + bind(MenuContribution).to(ArduinoToolbarMenuContribution).inSingletonScope(); bind(ArduinoToolbarContribution).toSelf().inSingletonScope(); bind(FrontendApplicationContribution).toService(ArduinoToolbarContribution); diff --git a/arduino-ide-extension/src/browser/components/boards-toolbar-item.tsx b/arduino-ide-extension/src/browser/components/boards-toolbar-item.tsx index f4ea197c..39ec85cb 100644 --- a/arduino-ide-extension/src/browser/components/boards-toolbar-item.tsx +++ b/arduino-ide-extension/src/browser/components/boards-toolbar-item.tsx @@ -1,35 +1,40 @@ import * as React from 'react'; import { Board } from '../../common/protocol/boards-service'; +import { ContextMenuRenderer } from '@theia/core/lib/browser'; +import { ArduinoToolbarContextMenu } from '../arduino-file-menu'; export namespace BoardsToolBarItem { export interface Props { readonly onNoBoardsInstalled: () => void; readonly onUnknownBoard: (board: Board) => void; - } - - export interface State { - showOpenButton: boolean; + readonly contextMenuRenderer: ContextMenuRenderer; } } -export class BoardsToolBarItem extends React.Component { +export class BoardsToolBarItem extends React.Component { constructor(props: BoardsToolBarItem.Props) { super(props); + } - this.state = { - showOpenButton: false + protected readonly doShowSelectBoardsMenu = (event: React.MouseEvent) => this.showSelectBoardsMenu(event); + protected showSelectBoardsMenu(event: React.MouseEvent) { + const el = (event.target as HTMLElement).parentElement; + if (el) { + this.props.contextMenuRenderer.render(ArduinoToolbarContextMenu.SELECT_BOARDS_PATH, { + x: el.getBoundingClientRect().left, + y: el.getBoundingClientRect().top + el.offsetHeight + }); } } render(): React.ReactNode { return -
this.setState({ showOpenButton: !this.state.showOpenButton })}> +
-
Hallo
- {this.state.showOpenButton ?
OPEN BOARDS DIALOG
: ''} +
Show selected Board here
From 769689ff6dc9de77b820891606896d02d3c4b0fa Mon Sep 17 00:00:00 2001 From: jbicker Date: Mon, 8 Jul 2019 11:58:59 +0200 Subject: [PATCH 3/7] Added dialog implementation Signed-off-by: jbicker --- .../src/browser/arduino-file-menu.ts | 8 +- .../browser/arduino-frontend-contribution.tsx | 27 +- .../src/browser/arduino-frontend-module.ts | 9 + .../boards-toolbar-item.tsx | 0 .../boards/select-board-dialog-widget.tsx | 230 ++++++++++++++++++ .../src/browser/boards/select-board-dialog.ts | 78 ++++++ .../src/browser/style/main.css | 12 + .../src/common/protocol/boards-service.ts | 28 +-- .../src/node/boards-service-impl.ts | 2 + 9 files changed, 370 insertions(+), 24 deletions(-) rename arduino-ide-extension/src/browser/{components => boards}/boards-toolbar-item.tsx (100%) create mode 100644 arduino-ide-extension/src/browser/boards/select-board-dialog-widget.tsx create mode 100644 arduino-ide-extension/src/browser/boards/select-board-dialog.ts diff --git a/arduino-ide-extension/src/browser/arduino-file-menu.ts b/arduino-ide-extension/src/browser/arduino-file-menu.ts index 389eada0..8dbca73f 100644 --- a/arduino-ide-extension/src/browser/arduino-file-menu.ts +++ b/arduino-ide-extension/src/browser/arduino-file-menu.ts @@ -62,11 +62,11 @@ export class ArduinoToolbarMenuContribution implements MenuContribution { protected getPort(board: Board): string { - if(AttachedSerialBoard.is(board)){ + if (AttachedSerialBoard.is(board)) { return board.port; } - if(AttachedNetworkBoard.is(board)) { - return 'netport' + board.port; + if (AttachedNetworkBoard.is(board)) { + return 'netport: ' + board.port; } return ''; } @@ -95,7 +95,7 @@ export class ArduinoToolbarMenuContribution implements MenuContribution { registerMenus(registry: MenuModelRegistry) { registry.registerMenuAction([...CommonMenus.FILE, '0_new_sletch'], { commandId: ArduinoCommands.NEW_SKETCH.id - }) + }); registry.registerMenuAction(ArduinoToolbarContextMenu.OPEN_GROUP, { commandId: ArduinoCommands.OPEN_FILE_NAVIGATOR.id, diff --git a/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx b/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx index e526b91f..1f582ef3 100644 --- a/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx +++ b/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx @@ -27,7 +27,8 @@ import { ArduinoToolbarContextMenu } from './arduino-file-menu'; import { Sketch, SketchesService } from '../common/protocol/sketches-service'; import { WindowService } from '@theia/core/lib/browser/window/window-service'; import { CommonCommands } from '@theia/core/lib/browser/common-frontend-contribution' -import { BoardsToolBarItem } from './components/boards-toolbar-item'; +import { BoardsToolBarItem } from './boards/boards-toolbar-item'; +import { SelectBoardsDialog } from './boards/select-board-dialog'; @injectable() export class ArduinoFrontendContribution implements TabBarToolbarContribution, CommandContribution { @@ -86,6 +87,9 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C @inject(SketchesService) protected readonly sketches: SketchesService; + @inject(SelectBoardsDialog) + protected readonly selectBoardsDialog: SelectBoardsDialog; + @postConstruct() protected async init(): Promise { // This is a hack. Otherwise, the backend services won't bind. @@ -227,13 +231,26 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C registry.registerCommand(ArduinoCommands.SELECT_BOARD, { isEnabled: () => true, execute: (board: Board) => { - console.log("SELECT BOARD HERE", board); - } + this.boardService.selectBoard(board).then(() => { + return this.boardService.getSelectBoard(); + }).then(board => { + console.log("and the selected board is", board); + }) + } }) registry.registerCommand(ArduinoCommands.OPEN_BOARDS_DIALOG, { isEnabled: () => true, - execute: () => { - console.log("OPEN THE DIALOG HERE"); + execute: async () => { + const boardAndPort = await this.selectBoardsDialog.open(); + if(boardAndPort && boardAndPort.board){ + const selectedBoard = { + fqbn: boardAndPort.board.fqbn, + name: boardAndPort.board.name, + port: boardAndPort.port + } + this.boardService.selectBoard(selectedBoard); + + } } }) } diff --git a/arduino-ide-extension/src/browser/arduino-frontend-module.ts b/arduino-ide-extension/src/browser/arduino-frontend-module.ts index 0df09de3..7cec9282 100644 --- a/arduino-ide-extension/src/browser/arduino-frontend-module.ts +++ b/arduino-ide-extension/src/browser/arduino-frontend-module.ts @@ -48,6 +48,8 @@ import { CustomApplicationShell } from './customization/custom-application-shell import { CustomFrontendApplication } from './customization/custom-frontend-application'; import { EditorWidgetFactory } from '@theia/editor/lib/browser/editor-widget-factory'; import { CustomEditorWidgetFactory } from './customization/custom-editor-widget-factory'; +import { SelectBoardsDialog, SelectBoardsDialogProps } from './boards/select-board-dialog'; +import { SelectBoardDialogWidget } from './boards/select-board-dialog-widget'; export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Unbind, isBound: interfaces.IsBound, rebind: interfaces.Rebind) => { // Commands and toolbar items @@ -94,6 +96,13 @@ export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Un })); bind(FrontendApplicationContribution).toService(BoardsListWidgetFrontendContribution); + // Board select dialog + bind(SelectBoardDialogWidget).toSelf().inSingletonScope(); + bind(SelectBoardsDialog).toSelf().inSingletonScope(); + bind(SelectBoardsDialogProps).toConstantValue({ + title: 'Select Board' + }) + // Core service bind(CoreService) .toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, CoreServicePath)) diff --git a/arduino-ide-extension/src/browser/components/boards-toolbar-item.tsx b/arduino-ide-extension/src/browser/boards/boards-toolbar-item.tsx similarity index 100% rename from arduino-ide-extension/src/browser/components/boards-toolbar-item.tsx rename to arduino-ide-extension/src/browser/boards/boards-toolbar-item.tsx diff --git a/arduino-ide-extension/src/browser/boards/select-board-dialog-widget.tsx b/arduino-ide-extension/src/browser/boards/select-board-dialog-widget.tsx new file mode 100644 index 00000000..9dee9256 --- /dev/null +++ b/arduino-ide-extension/src/browser/boards/select-board-dialog-widget.tsx @@ -0,0 +1,230 @@ +import * as React from 'react'; +import { ReactWidget } from '@theia/core/lib/browser'; +import { injectable, inject } from 'inversify'; +import { BoardsService, Board, BoardPackage, AttachedSerialBoard } from '../../common/protocol/boards-service'; +import { BoardsNotificationService } from '../boards-notification-service'; +import { Emitter, Event } from '@theia/core'; + +export interface BoardAndPortSelection { + board?: Board; + port?: string; +} + +export namespace SelectableBoardsItem { + export interface Props { + board: Board, + selected: boolean, + onClick: (selection: BoardAndPortSelection) => void + } +} + +export class SelectableBoardsItem extends React.Component { + + render(): React.ReactNode { + return
{this.props.board.name}
+ } + + protected readonly select = (() => { + this.props.onClick({ board: this.props.board }) + }).bind(this); +} + +export namespace SelectablePortsItem { + export interface Props { + port: string, + selected: boolean, + onClick: (selection: BoardAndPortSelection) => void + } +} + +export class SelectablePortsItem extends React.Component { + + render(): React.ReactNode { + return
this.props.onClick({ port: this.props.port })} className={`item ${this.props.selected ? 'selected': ''}`}>{this.props.port}
+ } + + protected readonly select = (() => { + this.props.onClick({ port: this.props.port }) + }).bind(this); +} + +export namespace BoardAndPortSelectionComponent { + export interface Props { + boardsService: BoardsService; + onSelect: (selection: BoardAndPortSelection) => void; + } + + export interface State { + boards: Board[]; + ports: string[]; + selection: BoardAndPortSelection; + } +} + +export class BoardAndPortSelectionComponent extends React.Component { + + protected allBoards: Board[] = []; + + constructor(props: BoardAndPortSelectionComponent.Props) { + super(props); + + this.state = { + boards: [], + ports: [], + selection: {} + } + } + + componentDidMount() { + this.searchAvailableBoards(); + this.setPorts(); + } + + render(): React.ReactNode { + return +
+
+
+ BOARDS +
+
+ +
+
+ {this.state.boards.map(board => )} +
+
+
+
+ PORTS +
+
+ {this.state.ports.map(port => )} +
+
+
+
+ } + + protected readonly isSelectedBoard = ((board: Board) => { + return (this.state.selection.board && this.state.selection.board === board) || false; + }); + + protected readonly isSelectedPort = ((port: string) => { + return (this.state.selection.port && this.state.selection.port === port) || false; + }); + + protected readonly doSelect = (boardAndPortSelection: BoardAndPortSelection) => { + const selection = this.state.selection; + if (boardAndPortSelection.board) { + selection.board = boardAndPortSelection.board; + } + if (boardAndPortSelection.port) { + selection.port = boardAndPortSelection.port; + } + this.setState({ selection }); + this.props.onSelect(this.state.selection); + } + + protected sort(items: Board[]): Board[] { + return items.sort((a, b) => { + if (a.name < b.name) { + return -1; + } else if (a.name === b.name) { + return 0; + } else { + return 1; + } + }); + } + + protected readonly doFilter = (event: React.ChangeEvent) => { + const boards = this.allBoards.filter(board => board.name.toLowerCase().indexOf(event.target.value.toLowerCase()) >= 0); + this.setState({ boards }) + } + + protected async searchAvailableBoards() { + const boardPkg = await this.props.boardsService.search({}); + const boards = [].concat.apply([], boardPkg.items.map(item => item.boards)) as Board[]; + this.allBoards = this.sort(boards); + this.setState({ boards: this.allBoards }); + } + + protected async setPorts() { + const ports: string[] = []; + const attached = await this.props.boardsService.getAttachedBoards(); + attached.boards.forEach(board => { + if (AttachedSerialBoard.is(board)) { + ports.push(board.port); + } + }); + this.setState({ ports }); + } +} + +@injectable() +export class SelectBoardDialogWidget extends ReactWidget { + + @inject(BoardsService) + protected readonly boardsService: BoardsService; + @inject(BoardsNotificationService) + protected readonly boardsNotificationService: BoardsNotificationService; + + protected readonly onChangedEmitter = new Emitter(); + + boardAndPort: BoardAndPortSelection = {}; + + constructor() { + super(); + this.id = 'select-board-dialog'; + + this.toDispose.push(this.onChangedEmitter); + } + + get onChanged(): Event { + return this.onChangedEmitter.event; + } + + protected fireChanged(boardAndPort: BoardAndPortSelection): void { + this.onChangedEmitter.fire(boardAndPort); + } + + protected render(): React.ReactNode { + let content: React.ReactNode; + + const boardsServiceDelegate = this.boardsService; + const boardsService: BoardsService = { + getAttachedBoards: () => boardsServiceDelegate.getAttachedBoards(), + selectBoard: (board: Board) => boardsServiceDelegate.selectBoard(board), + getSelectBoard: () => boardsServiceDelegate.getSelectBoard(), + search: (options: { query?: string }) => boardsServiceDelegate.search(options), + install: async (item: BoardPackage) => { + await boardsServiceDelegate.install(item); + this.boardsNotificationService.notifyBoardsInstalled(); + } + } + + content = +
+
+
+ Select Other Board & Port +
+
+ Select both a BOARD and a PORT if you want to upload a sketch.
+ If you only select a BOARD you will be able just to compile, but not to upload your sketch. +
+
+ +
+
+ + return content; + } + + protected readonly onSelect = (selection: BoardAndPortSelection) => { this.doOnSelect(selection) }; + protected doOnSelect(selection: BoardAndPortSelection) { + this.boardAndPort = selection; + this.fireChanged(this.boardAndPort); + } +} \ No newline at end of file diff --git a/arduino-ide-extension/src/browser/boards/select-board-dialog.ts b/arduino-ide-extension/src/browser/boards/select-board-dialog.ts new file mode 100644 index 00000000..55278bfb --- /dev/null +++ b/arduino-ide-extension/src/browser/boards/select-board-dialog.ts @@ -0,0 +1,78 @@ +import { AbstractDialog, DialogProps, Widget, Panel, DialogError } from '@theia/core/lib/browser'; +import { injectable, inject } from 'inversify'; +import { SelectBoardDialogWidget, BoardAndPortSelection } from './select-board-dialog-widget'; +import { Message } from '@phosphor/messaging'; +import { Disposable } from '@theia/core'; + +@injectable() +export class SelectBoardsDialogProps extends DialogProps { + +} + +@injectable() +export class SelectBoardsDialog extends AbstractDialog { + + protected readonly dialogPanel: Panel; + + constructor( + @inject(SelectBoardsDialogProps) protected readonly props: SelectBoardsDialogProps, + @inject(SelectBoardDialogWidget) protected readonly widget: SelectBoardDialogWidget + ) { + super({ title: props.title }); + + this.dialogPanel = new Panel(); + this.dialogPanel.addWidget(this.widget); + + this.toDispose.push(this.widget.onChanged(() => this.update())); + + this.toDispose.push(this.dialogPanel); + + this.appendCloseButton('CANCEL'); + this.appendAcceptButton('OK'); + } + + protected onAfterAttach(msg: Message): void { + Widget.attach(this.dialogPanel, this.contentNode); + + this.toDisposeOnDetach.push(Disposable.create(() => { + Widget.detach(this.dialogPanel); + })) + + super.onAfterAttach(msg); + this.update(); + } + + protected onUpdateRequest(msg: Message) { + super.onUpdateRequest(msg); + + this.widget.update(); + } + + protected onActivateRequest(msg: Message): void { + this.widget.activate(); + } + + protected handleEnter(event: KeyboardEvent): boolean | void { + if (event.target instanceof HTMLTextAreaElement) { + return false; + } + } + + protected isValid(value: BoardAndPortSelection): DialogError { + if(!value.board) { + if(value.port) { + return 'Please pick the Board connected to the Port you have selected'; + } + return false; + } + return ''; + } + + get value(): BoardAndPortSelection { + return this.widget.boardAndPort; + } + + protected async accept(): Promise { + super.accept(); + } +} \ No newline at end of file diff --git a/arduino-ide-extension/src/browser/style/main.css b/arduino-ide-extension/src/browser/style/main.css index 29d43d35..681eb841 100644 --- a/arduino-ide-extension/src/browser/style/main.css +++ b/arduino-ide-extension/src/browser/style/main.css @@ -96,4 +96,16 @@ display: flex; align-items: center; color: var(--theia-ui-font-color3); +} + +div#select-board-dialog .selectBoardContainer .body { + display: flex; +} + +div#select-board-dialog .selectBoardContainer .head { + margin-bottom: 10px; +} + +div#select-board-dialog .selectBoardContainer .body .list .item.selected { + background: #aaaaaa; } \ No newline at end of file diff --git a/arduino-ide-extension/src/common/protocol/boards-service.ts b/arduino-ide-extension/src/common/protocol/boards-service.ts index 16654df1..b35861c2 100644 --- a/arduino-ide-extension/src/common/protocol/boards-service.ts +++ b/arduino-ide-extension/src/common/protocol/boards-service.ts @@ -4,8 +4,8 @@ export const BoardsServicePath = '/services/boards-service'; export const BoardsService = Symbol('BoardsService'); export interface BoardsService { getAttachedBoards(): Promise<{ boards: Board[] }>; - selectBoard(board: Board): Promise; - getSelectBoard(): Promise; + selectBoard(board: Board | AttachedSerialBoard | AttachedNetworkBoard): Promise; + getSelectBoard(): Promise; search(options: { query?: string }): Promise<{ items: BoardPackage[] }>; install(item: BoardPackage): Promise; @@ -23,31 +23,29 @@ export interface Board { export interface AttachedSerialBoard extends Board { port: string; - serialNumber: string; - productID: string; - vendorID: string; + type: 'serial'; + serialNumber?: string; + productID?: string; + vendorID?: string; } export namespace AttachedSerialBoard { export function is(b: Board): b is AttachedSerialBoard { - return 'port' in b - && 'serialNumber' in b - && 'productID' in b - && 'vendorID' in b; + return 'type' in b && (b as Board & { type: any }).type === 'serial' && + 'port' in b && !!(b as Board & { port: any }).port && typeof (b as Board & { port: any }).port === 'string'; } } export interface AttachedNetworkBoard extends Board { - info: string; - address: string; + info?: string; + address?: string; port: number; + type: 'network'; } export namespace AttachedNetworkBoard { export function is(b: Board): b is AttachedNetworkBoard { - return 'name' in b - && 'info' in b - && 'address' in b - && 'port' in b; + return 'type' in b && (b as Board & { type: any }).type === 'network' && + 'port' in b && !!(b as Board & { port: any }).port && typeof (b as Board & { port: any }).port === 'number'; } } diff --git a/arduino-ide-extension/src/node/boards-service-impl.ts b/arduino-ide-extension/src/node/boards-service-impl.ts index 18b6c1b0..50df0265 100644 --- a/arduino-ide-extension/src/node/boards-service-impl.ts +++ b/arduino-ide-extension/src/node/boards-service-impl.ts @@ -31,6 +31,7 @@ export class BoardsServiceImpl implements BoardsService { name: b.getName() || "unknown", fqbn: b.getFqbn(), port: b.getPort(), + type: 'serial', serialNumber: b.getSerialnumber(), productID: b.getProductid(), vendorID: b.getVendorid() @@ -41,6 +42,7 @@ export class BoardsServiceImpl implements BoardsService { address: b.getAddress(), info: b.getInfo(), port: b.getPort(), + type: 'network' }); return { boards: serialBoards.concat(networkBoards) }; From 4429094139fb38aeb4bb06aefbb0a2710051c922 Mon Sep 17 00:00:00 2001 From: jbicker Date: Mon, 8 Jul 2019 13:55:31 +0200 Subject: [PATCH 4/7] Styling of select board and port dialog Signed-off-by: jbicker --- .../boards/select-board-dialog-widget.tsx | 38 ++++++++++--------- .../src/browser/style/arduino.useable.css | 2 +- .../src/browser/style/main.css | 32 ++++++++++++++++ 3 files changed, 54 insertions(+), 18 deletions(-) diff --git a/arduino-ide-extension/src/browser/boards/select-board-dialog-widget.tsx b/arduino-ide-extension/src/browser/boards/select-board-dialog-widget.tsx index 9dee9256..11751a70 100644 --- a/arduino-ide-extension/src/browser/boards/select-board-dialog-widget.tsx +++ b/arduino-ide-extension/src/browser/boards/select-board-dialog-widget.tsx @@ -21,7 +21,7 @@ export namespace SelectableBoardsItem { export class SelectableBoardsItem extends React.Component { render(): React.ReactNode { - return
{this.props.board.name}
+ return
{this.props.board.name}
} protected readonly select = (() => { @@ -40,7 +40,7 @@ export namespace SelectablePortsItem { export class SelectablePortsItem extends React.Component { render(): React.ReactNode { - return
this.props.onClick({ port: this.props.port })} className={`item ${this.props.selected ? 'selected': ''}`}>{this.props.port}
+ return
this.props.onClick({ port: this.props.port })} className={`item ${this.props.selected ? 'selected' : ''}`}>{this.props.port}
} protected readonly select = (() => { @@ -83,23 +83,27 @@ export class BoardAndPortSelectionComponent extends React.Component
-
-
- BOARDS +
+
+
+ BOARDS
-
- -
-
- {this.state.boards.map(board => )} +
+ +
+
+ {this.state.boards.map(board => )} +
-
-
- PORTS +
+
+
+ PORTS
-
- {this.state.ports.map(port => )} +
+ {this.state.ports.map(port => )} +
@@ -107,11 +111,11 @@ export class BoardAndPortSelectionComponent extends React.Component { - return (this.state.selection.board && this.state.selection.board === board) || false; + return (this.state.selection.board && this.state.selection.board === board) || false; }); protected readonly isSelectedPort = ((port: string) => { - return (this.state.selection.port && this.state.selection.port === port) || false; + return (this.state.selection.port && this.state.selection.port === port) || false; }); protected readonly doSelect = (boardAndPortSelection: BoardAndPortSelection) => { diff --git a/arduino-ide-extension/src/browser/style/arduino.useable.css b/arduino-ide-extension/src/browser/style/arduino.useable.css index 3ad86611..74f9056e 100644 --- a/arduino-ide-extension/src/browser/style/arduino.useable.css +++ b/arduino-ide-extension/src/browser/style/arduino.useable.css @@ -169,7 +169,7 @@ is not optimized for dense, information rich UIs. /* Dialogs */ --theia-ui-dialog-header-color: var(--theia-arduino-light); --theia-ui-dialog-header-font-color: var(--theia-inverse-ui-font-color0); - --theia-ui-dialog-color: var(--theia-layout-color0); + --theia-ui-dialog-color: rgb(236, 241, 241); --theia-ui-dialog-font-color: var(--theia-ui-font-color1); /* Variables */ --theia-variable-name-color: #9B46B0; diff --git a/arduino-ide-extension/src/browser/style/main.css b/arduino-ide-extension/src/browser/style/main.css index 681eb841..cb38cd29 100644 --- a/arduino-ide-extension/src/browser/style/main.css +++ b/arduino-ide-extension/src/browser/style/main.css @@ -108,4 +108,36 @@ div#select-board-dialog .selectBoardContainer .head { div#select-board-dialog .selectBoardContainer .body .list .item.selected { background: #aaaaaa; +} + +#select-board-dialog .selectBoardContainer .body .search input, +#select-board-dialog .selectBoardContainer .body .boards.list, +#select-board-dialog .selectBoardContainer .body .search, +#select-board-dialog .selectBoardContainer .body .ports.list { + background: white; +} + +#select-board-dialog .selectBoardContainer .body .search input { + border: none; + width: 100%; +} + +#select-board-dialog .selectBoardContainer .body .container { + flex: 1; +} + +#select-board-dialog .selectBoardContainer .body .container .content { + margin: 0 5px; +} + +#select-board-dialog .selectBoardContainer .body .list .item { + padding: 10px 5px 10px 20px; +} + +#select-board-dialog .selectBoardContainer .body .list { + max-height: 265px; +} + +#select-board-dialog .selectBoardContainer .body .search { + margin-bottom: 10px; } \ No newline at end of file From a039597d40ff2c7bf1a7a2cf6fda82eca2a0bc06 Mon Sep 17 00:00:00 2001 From: jbicker Date: Tue, 9 Jul 2019 18:00:24 +0200 Subject: [PATCH 5/7] Select Board Dialog Style and Layout Signed-off-by: jbicker --- .../src/browser/arduino-file-menu.ts | 2 +- .../browser/arduino-frontend-contribution.tsx | 67 ++++---- .../browser/boards/boards-toolbar-item.tsx | 46 +++++- .../boards/select-board-dialog-widget.tsx | 150 +++++++++++------ .../src/browser/boards/select-board-dialog.ts | 15 +- .../src/browser/style/arduino.useable.css | 8 +- .../src/browser/style/board-select-dialog.css | 152 ++++++++++++++++++ .../src/browser/style/index.css | 4 +- .../src/browser/style/main.css | 59 ------- .../src/browser/style/select-board-dialog.css | 13 -- 10 files changed, 349 insertions(+), 167 deletions(-) create mode 100644 arduino-ide-extension/src/browser/style/board-select-dialog.css delete mode 100644 arduino-ide-extension/src/browser/style/select-board-dialog.css diff --git a/arduino-ide-extension/src/browser/arduino-file-menu.ts b/arduino-ide-extension/src/browser/arduino-file-menu.ts index 8dbca73f..a33dd454 100644 --- a/arduino-ide-extension/src/browser/arduino-file-menu.ts +++ b/arduino-ide-extension/src/browser/arduino-file-menu.ts @@ -54,7 +54,7 @@ export class ArduinoToolbarMenuContribution implements MenuContribution { }); registry.registerMenuAction(ArduinoToolbarContextMenu.CONNECTED_GROUP, { commandId: command.id, - label: board.name + (selectedPort === port ? '*' : '') + label: board.name + ' at ' + port + (selectedPort === port ? '*' : '') }); }); } diff --git a/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx b/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx index 1f582ef3..0d228020 100644 --- a/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx +++ b/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx @@ -90,6 +90,8 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C @inject(SelectBoardsDialog) protected readonly selectBoardsDialog: SelectBoardsDialog; + protected boardsToolbarItem: BoardsToolBarItem | null; + @postConstruct() protected async init(): Promise { // This is a hack. Otherwise, the backend services won't bind. @@ -124,15 +126,10 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C registry.registerItem({ id: ConnectedBoards.TOOLBAR_ID, render: () => this.boardsToolbarItem = ref} contextMenuRenderer={this.contextMenuRenderer} - onNoBoardsInstalled={this.onNoBoardsInstalled.bind(this)} - onUnknownBoard={this.onUnknownBoard.bind(this)} />, - // render: () => , + boardsNotificationService={this.boardsNotificationService} + boardService={this.boardService} />, isVisible: widget => this.isArduinoToolbar(widget) }) } @@ -230,31 +227,37 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C }); registry.registerCommand(ArduinoCommands.SELECT_BOARD, { isEnabled: () => true, - execute: (board: Board) => { - this.boardService.selectBoard(board).then(() => { - return this.boardService.getSelectBoard(); - }).then(board => { - console.log("and the selected board is", board); - }) + execute: async (board: Board) => { + this.selectBoard(board); } }) registry.registerCommand(ArduinoCommands.OPEN_BOARDS_DIALOG, { isEnabled: () => true, execute: async () => { const boardAndPort = await this.selectBoardsDialog.open(); - if(boardAndPort && boardAndPort.board){ + if (boardAndPort && boardAndPort.board) { const selectedBoard = { fqbn: boardAndPort.board.fqbn, name: boardAndPort.board.name, port: boardAndPort.port } - this.boardService.selectBoard(selectedBoard); - + this.selectBoard(selectedBoard); } } }) } + protected async selectBoard(board: Board) { + const attached = await this.boardService.getAttachedBoards(); + if(attached.boards.length) { + board = attached.boards.find(b => b.name === board.name && b.fqbn === board.fqbn) || board; + } + await this.boardService.selectBoard(board) + if (this.boardsToolbarItem) { + this.boardsToolbarItem.setSelectedBoard(board); + } + } + protected async openSketchFilesInNewWindow(uri: string) { const location = new URL(window.location.href); location.searchParams.set('sketch', uri); @@ -309,24 +312,24 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C return widget; } - private async onNoBoardsInstalled() { - const action = await this.messageService.info("You have no boards installed. Use the boards mangager to install one.", "Open Boards Manager"); - if (!action) { - return; - } + // private async onNoBoardsInstalled() { + // const action = await this.messageService.info("You have no boards installed. Use the boards mangager to install one.", "Open Boards Manager"); + // if (!action) { + // return; + // } - this.boardsListWidgetFrontendContribution.openView({ reveal: true }); - } + // this.boardsListWidgetFrontendContribution.openView({ reveal: true }); + // } - private async onUnknownBoard() { - const action = await this.messageService.warn("There's a board connected for which you need to install software." + - " If this were not a PoC we would offer you the right package now.", "Open Boards Manager"); - if (!action) { - return; - } + // private async onUnknownBoard() { + // const action = await this.messageService.warn("There's a board connected for which you need to install software." + + // " If this were not a PoC we would offer you the right package now.", "Open Boards Manager"); + // if (!action) { + // return; + // } - this.boardsListWidgetFrontendContribution.openView({ reveal: true }); - } + // this.boardsListWidgetFrontendContribution.openView({ reveal: true }); + // } private isArduinoToolbar(maybeToolbarWidget: any): boolean { if (maybeToolbarWidget instanceof ArduinoToolbar) { diff --git a/arduino-ide-extension/src/browser/boards/boards-toolbar-item.tsx b/arduino-ide-extension/src/browser/boards/boards-toolbar-item.tsx index 39ec85cb..859410d0 100644 --- a/arduino-ide-extension/src/browser/boards/boards-toolbar-item.tsx +++ b/arduino-ide-extension/src/browser/boards/boards-toolbar-item.tsx @@ -1,20 +1,53 @@ import * as React from 'react'; -import { Board } from '../../common/protocol/boards-service'; +import { BoardsService, Board } from '../../common/protocol/boards-service'; import { ContextMenuRenderer } from '@theia/core/lib/browser'; import { ArduinoToolbarContextMenu } from '../arduino-file-menu'; +import { BoardsNotificationService } from '../boards-notification-service'; export namespace BoardsToolBarItem { export interface Props { - readonly onNoBoardsInstalled: () => void; - readonly onUnknownBoard: (board: Board) => void; readonly contextMenuRenderer: ContextMenuRenderer; + readonly boardsNotificationService: BoardsNotificationService; + readonly boardService: BoardsService; + } + + export interface State { + selectedBoard?: Board; + selectedIsAttached: boolean } } -export class BoardsToolBarItem extends React.Component { +export class BoardsToolBarItem extends React.Component { + + protected attachedBoards: Board[]; constructor(props: BoardsToolBarItem.Props) { super(props); + + this.state = { + selectedBoard: undefined, + selectedIsAttached: true + }; + } + + componentDidMount() { + this.setAttachedBoards(); + } + + protected async setAttachedBoards() { + const { boards } = await this.props.boardService.getAttachedBoards(); + this.attachedBoards = boards; + if(this.attachedBoards.length){ + await this.props.boardService.selectBoard(this.attachedBoards[0]); + this.setSelectedBoard(this.attachedBoards[0]); + } + } + + setSelectedBoard(board: Board) { + if (this.attachedBoards.length) { + this.setState({ selectedIsAttached: !!this.attachedBoards.find(attachedBoard => attachedBoard.name === board.name) }); + } + this.setState({ selectedBoard: board }); } protected readonly doShowSelectBoardsMenu = (event: React.MouseEvent) => this.showSelectBoardsMenu(event); @@ -29,12 +62,13 @@ export class BoardsToolBarItem extends React.Component
-
Show selected Board here
+ +
{this.state.selectedBoard ? this.state.selectedBoard.name : 'no board selected'}
+
diff --git a/arduino-ide-extension/src/browser/boards/select-board-dialog-widget.tsx b/arduino-ide-extension/src/browser/boards/select-board-dialog-widget.tsx index 11751a70..ab400243 100644 --- a/arduino-ide-extension/src/browser/boards/select-board-dialog-widget.tsx +++ b/arduino-ide-extension/src/browser/boards/select-board-dialog-widget.tsx @@ -10,42 +10,84 @@ export interface BoardAndPortSelection { port?: string; } -export namespace SelectableBoardsItem { +export namespace BoardAndPortSelectableItem { export interface Props { - board: Board, + item: BoardAndPortSelection, selected: boolean, - onClick: (selection: BoardAndPortSelection) => void + onSelect: (selection: BoardAndPortSelection) => void } } -export class SelectableBoardsItem extends React.Component { +export class BoardAndPortSelectableItem extends React.Component { render(): React.ReactNode { - return
{this.props.board.name}
+ if (this.props.item.board || this.props.item.port) { + return
+ {this.props.item.board ? this.props.item.board.name : this.props.item.port} + {this.props.selected ? : ''} +
; + } } protected readonly select = (() => { - this.props.onClick({ board: this.props.board }) + this.props.onSelect({ board: this.props.item.board, port: this.props.item.port }) }).bind(this); } -export namespace SelectablePortsItem { +export namespace BoardAndPortSelectionList { export interface Props { - port: string, - selected: boolean, - onClick: (selection: BoardAndPortSelection) => void + type: 'boards' | 'ports'; + list: BoardAndPortSelection[]; + onSelect: (selection: BoardAndPortSelection) => void; + } + + export interface State { + selection: BoardAndPortSelection } } -export class SelectablePortsItem extends React.Component { +export class BoardAndPortSelectionList extends React.Component { + + constructor(props: BoardAndPortSelectionList.Props) { + super(props); + + this.state = { + selection: {} + } + } + + reset(): void { + this.setState({ selection: {} }); + } render(): React.ReactNode { - return
this.props.onClick({ port: this.props.port })} className={`item ${this.props.selected ? 'selected' : ''}`}>{this.props.port}
+ return
+ {this.props.list.map(item => )} +
} - protected readonly select = (() => { - this.props.onClick({ port: this.props.port }) - }).bind(this); + protected readonly doSelect = (boardAndPortSelection: BoardAndPortSelection) => { + this.setState({ selection: boardAndPortSelection }); + this.props.onSelect(boardAndPortSelection); + } + + protected readonly isSelectedItem = ((item: BoardAndPortSelection) => { + if (this.state.selection.board) { + return (this.state.selection.board === item.board); + } else if (this.state.selection.port) { + return (this.state.selection.port === item.port); + } + return false; + }); + + protected readonly isSelectedPort = ((port: string) => { + return (this.state.selection.port && this.state.selection.port === port) || false; + }); } export namespace BoardAndPortSelectionComponent { @@ -64,6 +106,8 @@ export namespace BoardAndPortSelectionComponent { export class BoardAndPortSelectionComponent extends React.Component { protected allBoards: Board[] = []; + protected boardListComponent: BoardAndPortSelectionList | null; + protected portListComponent: BoardAndPortSelectionList | null; constructor(props: BoardAndPortSelectionComponent.Props) { super(props); @@ -80,6 +124,16 @@ export class BoardAndPortSelectionComponent extends React.Component
@@ -87,13 +141,16 @@ export class BoardAndPortSelectionComponent extends React.Component
BOARDS -
+
+
-
- {this.state.boards.map(board => )} -
+ { this.boardListComponent = ref }} + type='boards' + onSelect={this.doSelect} + list={this.state.boards.map(board => ({ board }))} />
@@ -101,35 +158,17 @@ export class BoardAndPortSelectionComponent extends React.Component PORTS
-
- {this.state.ports.map(port => )} -
+ { this.portListComponent = ref }} + type='ports' + onSelect={this.doSelect} + list={this.state.ports.map(port => ({ port }))} />
} - protected readonly isSelectedBoard = ((board: Board) => { - return (this.state.selection.board && this.state.selection.board === board) || false; - }); - - protected readonly isSelectedPort = ((port: string) => { - return (this.state.selection.port && this.state.selection.port === port) || false; - }); - - protected readonly doSelect = (boardAndPortSelection: BoardAndPortSelection) => { - const selection = this.state.selection; - if (boardAndPortSelection.board) { - selection.board = boardAndPortSelection.board; - } - if (boardAndPortSelection.port) { - selection.port = boardAndPortSelection.port; - } - this.setState({ selection }); - this.props.onSelect(this.state.selection); - } - protected sort(items: Board[]): Board[] { return items.sort((a, b) => { if (a.name < b.name) { @@ -142,6 +181,18 @@ export class BoardAndPortSelectionComponent extends React.Component { + const selection = this.state.selection; + if (boardAndPortSelection.board) { + selection.board = boardAndPortSelection.board; + } + if (boardAndPortSelection.port) { + selection.port = boardAndPortSelection.port; + } + this.setState({ selection }); + this.props.onSelect(this.state.selection); + } + protected readonly doFilter = (event: React.ChangeEvent) => { const boards = this.allBoards.filter(board => board.name.toLowerCase().indexOf(event.target.value.toLowerCase()) >= 0); this.setState({ boards }) @@ -175,6 +226,7 @@ export class SelectBoardDialogWidget extends ReactWidget { protected readonly boardsNotificationService: BoardsNotificationService; protected readonly onChangedEmitter = new Emitter(); + protected boardAndPortSelectionComponent: BoardAndPortSelectionComponent | null; boardAndPort: BoardAndPortSelection = {}; @@ -189,6 +241,13 @@ export class SelectBoardDialogWidget extends ReactWidget { return this.onChangedEmitter.event; } + reset(): void { + if (this.boardAndPortSelectionComponent) { + this.boardAndPortSelectionComponent.reset(); + } + this.boardAndPort = {}; + } + protected fireChanged(boardAndPort: BoardAndPortSelection): void { this.onChangedEmitter.fire(boardAndPort); } @@ -215,11 +274,12 @@ export class SelectBoardDialogWidget extends ReactWidget { Select Other Board & Port
- Select both a BOARD and a PORT if you want to upload a sketch.
- If you only select a BOARD you will be able just to compile, but not to upload your sketch. +

Select both a BOARD and a PORT if you want to upload a sketch.

+

If you only select a BOARD you will be able just to compile,

+

but not to upload your sketch.

- + this.boardAndPortSelectionComponent = ref} boardsService={boardsService} onSelect={this.onSelect} />
diff --git a/arduino-ide-extension/src/browser/boards/select-board-dialog.ts b/arduino-ide-extension/src/browser/boards/select-board-dialog.ts index 55278bfb..2541b4ad 100644 --- a/arduino-ide-extension/src/browser/boards/select-board-dialog.ts +++ b/arduino-ide-extension/src/browser/boards/select-board-dialog.ts @@ -44,7 +44,6 @@ export class SelectBoardsDialog extends AbstractDialog { protected onUpdateRequest(msg: Message) { super.onUpdateRequest(msg); - this.widget.update(); } @@ -59,8 +58,8 @@ export class SelectBoardsDialog extends AbstractDialog { } protected isValid(value: BoardAndPortSelection): DialogError { - if(!value.board) { - if(value.port) { + if (!value.board) { + if (value.port) { return 'Please pick the Board connected to the Port you have selected'; } return false; @@ -72,7 +71,13 @@ export class SelectBoardsDialog extends AbstractDialog { return this.widget.boardAndPort; } - protected async accept(): Promise { - super.accept(); + close(): void { + this.widget.reset(); + super.close(); + } + + onAfterDetach(msg: Message) { + this.widget.reset(); + super.onAfterDetach(msg); } } \ No newline at end of file diff --git a/arduino-ide-extension/src/browser/style/arduino.useable.css b/arduino-ide-extension/src/browser/style/arduino.useable.css index 74f9056e..8862821c 100644 --- a/arduino-ide-extension/src/browser/style/arduino.useable.css +++ b/arduino-ide-extension/src/browser/style/arduino.useable.css @@ -86,7 +86,7 @@ is not optimized for dense, information rich UIs. --theia-brand-color3: var(--md-blue-100); /* Secondary Brand colors */ --theia-secondary-brand-color0: var(--md-grey-700); - --theia-secondary-brand-color1: var(--md-grey-500); + --theia-secondary-brand-color1: #b5c8c9; --theia-secondary-brand-color2: var(--md-grey-300); --theia-secondary-brand-color3: var(--md-grey-100); /* Accent colors (dark to bright): Use these to create contrast to layout colors. */ @@ -147,7 +147,7 @@ is not optimized for dense, information rich UIs. /* Menu */ --theia-menu-color0: var(--theia-layout-color3); --theia-menu-color1: var(--theia-layout-color0); - --theia-menu-color2: var(--theia-layout-color3); + --theia-menu-color2: #dae3e3; /* Statusbar */ --theia-statusbar-color: var(--theia-arduino-light); --theia-statusBar-font-color: var(--theia-inverse-ui-font-color0); @@ -157,7 +157,7 @@ is not optimized for dense, information rich UIs. --theia-ui-button-color-hover: var(--theia-arduino-light1); --theia-ui-button-font-color: var(--theia-inverse-ui-font-color0); --theia-ui-button-color-secondary: var(--theia-secondary-brand-color1); - --theia-ui-button-color-secondary-hover: var(--theia-secondary-brand-color0); + --theia-ui-button-color-secondary-hover: var(--theia-menu-color2); --theia-ui-button-font-color-secondary: var(--theia-inverse-ui-font-color0); --theia-ui-button-color-disabled: var(--theia-accent-color3); --theia-ui-button-font-color-disabled: var(--theia-ui-font-color2); @@ -170,7 +170,7 @@ is not optimized for dense, information rich UIs. --theia-ui-dialog-header-color: var(--theia-arduino-light); --theia-ui-dialog-header-font-color: var(--theia-inverse-ui-font-color0); --theia-ui-dialog-color: rgb(236, 241, 241); - --theia-ui-dialog-font-color: var(--theia-ui-font-color1); + --theia-ui-dialog-font-color: black; /* Variables */ --theia-variable-name-color: #9B46B0; --theia-variable-value-color: rgba(108, 108, 108, 0.8); diff --git a/arduino-ide-extension/src/browser/style/board-select-dialog.css b/arduino-ide-extension/src/browser/style/board-select-dialog.css new file mode 100644 index 00000000..ce19946f --- /dev/null +++ b/arduino-ide-extension/src/browser/style/board-select-dialog.css @@ -0,0 +1,152 @@ +div#select-board-dialog { + margin: 5px 20px 50px 20px; +} + +div#select-board-dialog .selectBoardContainer .body { + display: flex; + overflow: hidden; +} + +div#select-board-dialog .selectBoardContainer .head { + margin-bottom: 10px; +} + +div#select-board-dialog .selectBoardContainer .head .title { + font-weight: 400; + letter-spacing: .02em; + font-size: 1.2em; + color: #00979d; + margin: 17px 0; +} + +div#select-board-dialog .selectBoardContainer .head .text { + margin-bottom: 21px; +} + +div#select-board-dialog .selectBoardContainer .body .list .item.selected { + background: var(--theia-ui-button-color-secondary-hover); +} + +div#select-board-dialog .selectBoardContainer .body .list .item.selected i{ + color: var(--theia-arduino-light); +} + +#select-board-dialog .selectBoardContainer .body .search input, +#select-board-dialog .selectBoardContainer .body .boards.list, +#select-board-dialog .selectBoardContainer .body .search, +#select-board-dialog .selectBoardContainer .body .ports.list { + background: white; +} + +#select-board-dialog .selectBoardContainer .body .search input { + border: none; + width: 100%; + height: auto; + max-height: 37px; + padding: 10px 8px; + margin: 0; + vertical-align: top; + display: flex; + color: var(--theia-content-font-color0); +} + +#select-board-dialog .selectBoardContainer .body .search input:focus { + box-shadow: none; +} + +#select-board-dialog .selectBoardContainer .body .container { + flex: 1; +} + +#select-board-dialog .selectBoardContainer .body .left.container .content { + margin: 0 5px 0 0; +} + +#select-board-dialog .selectBoardContainer .body .right.container .content { + margin: 0 0 0 5px; +} + +#select-board-dialog .selectBoardContainer .body .container .content .title{ + color: #7f8c8d; + margin-bottom: 10px; +} + +#select-board-dialog .selectBoardContainer .body .list .item { + padding: 10px 5px 10px 20px; + display: flex; + justify-content: space-between; +} +#select-board-dialog .selectBoardContainer .body .list .item:hover { + background: var(--theia-ui-button-color-secondary-hover); +} + +#select-board-dialog .selectBoardContainer .body .list { + max-height: 265px; + overflow-y: auto; +} + +#select-board-dialog .selectBoardContainer .body .search { + margin-bottom: 10px; + display: flex; + align-items: center; + padding-right: 5px; +} + +.p-Widget.dialogOverlay .dialogBlock { + width: 740px; +} + +button.theia-button { + height: 31px; +} + +button.theia-button.secondary { + background-color: #b5c8c9; + color: #000; + box-shadow: 0 4px #95a5a6; +} + +button.theia-button.main { + color: #fff; + background-color: #00979c; + box-shadow: 0 4px #005c5f; +} + +.dialogControl { + margin: 0 20px 30px 0; +} + +.arduino-boards-toolbar-item-container { + margin-left: 3px; +} + +.arduino-boards-toolbar-item-container .arduino-boards-toolbar-item .inner-container { + display: flex; + align-items: baseline; + margin: 0 5px; +} + +.arduino-boards-toolbar-item-container .arduino-boards-toolbar-item .inner-container .notAttached { + width: 10px; + height: 10px; + color: red; + margin-right: 5px; +} + +.arduino-boards-toolbar-item-container { + display: flex; + align-items: center; +} + +.arduino-boards-toolbar-item .label { + height: 100%; + display: flex; + align-items: center; + margin-right: 5px; +} + +.arduino-boards-toolbar-item { + background: white; + height: 18px; +} + diff --git a/arduino-ide-extension/src/browser/style/index.css b/arduino-ide-extension/src/browser/style/index.css index 4a57acb5..e9e53b25 100644 --- a/arduino-ide-extension/src/browser/style/index.css +++ b/arduino-ide-extension/src/browser/style/index.css @@ -1,3 +1,3 @@ @import './list-widget.css'; -@import './select-board-dialog.css'; -@import './main.css'; \ No newline at end of file +@import './board-select-dialog.css'; +@import './main.css'; diff --git a/arduino-ide-extension/src/browser/style/main.css b/arduino-ide-extension/src/browser/style/main.css index cb38cd29..2d0e9f04 100644 --- a/arduino-ide-extension/src/browser/style/main.css +++ b/arduino-ide-extension/src/browser/style/main.css @@ -53,25 +53,10 @@ opacity: 1; } -.arduino-boards-toolbar-item-container { - display: flex; - align-items: center; -} - -.arduino-boards-toolbar-item .label { - height: 100%; - display: flex; - align-items: center; -} - .arduino-open-boards-button { background: white; } -.arduino-boards-toolbar-item { - background: white; - height: 18px; -} .arduino-tool-item.item.connected-boards select { line-height: var(--theia-content-line-height); @@ -96,48 +81,4 @@ display: flex; align-items: center; color: var(--theia-ui-font-color3); -} - -div#select-board-dialog .selectBoardContainer .body { - display: flex; -} - -div#select-board-dialog .selectBoardContainer .head { - margin-bottom: 10px; -} - -div#select-board-dialog .selectBoardContainer .body .list .item.selected { - background: #aaaaaa; -} - -#select-board-dialog .selectBoardContainer .body .search input, -#select-board-dialog .selectBoardContainer .body .boards.list, -#select-board-dialog .selectBoardContainer .body .search, -#select-board-dialog .selectBoardContainer .body .ports.list { - background: white; -} - -#select-board-dialog .selectBoardContainer .body .search input { - border: none; - width: 100%; -} - -#select-board-dialog .selectBoardContainer .body .container { - flex: 1; -} - -#select-board-dialog .selectBoardContainer .body .container .content { - margin: 0 5px; -} - -#select-board-dialog .selectBoardContainer .body .list .item { - padding: 10px 5px 10px 20px; -} - -#select-board-dialog .selectBoardContainer .body .list { - max-height: 265px; -} - -#select-board-dialog .selectBoardContainer .body .search { - margin-bottom: 10px; } \ No newline at end of file diff --git a/arduino-ide-extension/src/browser/style/select-board-dialog.css b/arduino-ide-extension/src/browser/style/select-board-dialog.css deleted file mode 100644 index 1e3004e8..00000000 --- a/arduino-ide-extension/src/browser/style/select-board-dialog.css +++ /dev/null @@ -1,13 +0,0 @@ - -.select-board-dialog { - width: 600px; -} - -.select-board-dialog input { - width: calc(100% - 8px); - margin-bottom: 5px; -} - -.select-board-dialog select { - width: 100%; -} \ No newline at end of file From 89fb2fddbd7bdff6e7efb4c74e612fab6e2ce393 Mon Sep 17 00:00:00 2001 From: jbicker Date: Wed, 10 Jul 2019 11:50:44 +0200 Subject: [PATCH 6/7] Cache attached boards Signed-off-by: jbicker --- .../src/browser/arduino-file-menu.ts | 6 ++++- .../browser/arduino-frontend-contribution.tsx | 11 +++++++--- .../src/browser/arduino-frontend-module.ts | 2 ++ .../browser/boards/board-frontend-service.ts | 22 +++++++++++++++++++ .../browser/boards/boards-toolbar-item.tsx | 6 +++-- .../boards/select-board-dialog-widget.tsx | 16 ++++++++++---- 6 files changed, 53 insertions(+), 10 deletions(-) create mode 100644 arduino-ide-extension/src/browser/boards/board-frontend-service.ts diff --git a/arduino-ide-extension/src/browser/arduino-file-menu.ts b/arduino-ide-extension/src/browser/arduino-file-menu.ts index a33dd454..30598267 100644 --- a/arduino-ide-extension/src/browser/arduino-file-menu.ts +++ b/arduino-ide-extension/src/browser/arduino-file-menu.ts @@ -5,6 +5,7 @@ import { ArduinoCommands } from "./arduino-commands"; import { SketchesService, Sketch } from "../common/protocol/sketches-service"; import { AWorkspaceService } from "./arduino-workspace-service"; import { BoardsService, Board, AttachedSerialBoard, AttachedNetworkBoard } from "../common/protocol/boards-service"; +import { BoardFrontendService } from "./boards/board-frontend-service"; export namespace ArduinoToolbarContextMenu { export const OPEN_SKETCH_PATH: MenuPath = ['arduino-open-sketch-context-menu']; @@ -29,6 +30,9 @@ export class ArduinoToolbarMenuContribution implements MenuContribution { @inject(BoardsService) protected readonly boardsService: BoardsService; + @inject(BoardFrontendService) + protected readonly boardFrontendService: BoardFrontendService; + constructor( @inject(AWorkspaceService) protected readonly workspaceService: AWorkspaceService, @inject(MenuModelRegistry) protected readonly menuRegistry: MenuModelRegistry) { @@ -41,7 +45,7 @@ export class ArduinoToolbarMenuContribution implements MenuContribution { } protected async registerConnectedBoardsInMenu(registry: MenuModelRegistry) { - const { boards } = await this.boardsService.getAttachedBoards(); + const boards = await this.boardFrontendService.getAttachedBoards(); const selectedBoard = await this.boardsService.getSelectBoard(); const selectedPort = selectedBoard ? this.getPort(selectedBoard) : ''; boards.forEach(board => { diff --git a/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx b/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx index 0d228020..0dd0f38e 100644 --- a/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx +++ b/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx @@ -29,6 +29,7 @@ import { WindowService } from '@theia/core/lib/browser/window/window-service'; import { CommonCommands } from '@theia/core/lib/browser/common-frontend-contribution' import { BoardsToolBarItem } from './boards/boards-toolbar-item'; import { SelectBoardsDialog } from './boards/select-board-dialog'; +import { BoardFrontendService } from './boards/board-frontend-service'; @injectable() export class ArduinoFrontendContribution implements TabBarToolbarContribution, CommandContribution { @@ -39,6 +40,9 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C @inject(BoardsService) protected readonly boardService: BoardsService; + @inject(BoardFrontendService) + protected readonly boardFrontendService: BoardFrontendService; + @inject(CoreService) protected readonly coreService: CoreService; @@ -129,6 +133,7 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C ref={ref => this.boardsToolbarItem = ref} contextMenuRenderer={this.contextMenuRenderer} boardsNotificationService={this.boardsNotificationService} + boardFrontendService={this.boardFrontendService} boardService={this.boardService} />, isVisible: widget => this.isArduinoToolbar(widget) }) @@ -248,9 +253,9 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C } protected async selectBoard(board: Board) { - const attached = await this.boardService.getAttachedBoards(); - if(attached.boards.length) { - board = attached.boards.find(b => b.name === board.name && b.fqbn === board.fqbn) || board; + const boards = await this.boardFrontendService.getAttachedBoards(); + if (boards.length) { + board = boards.find(b => b.name === board.name && b.fqbn === board.fqbn) || board; } await this.boardService.selectBoard(board) if (this.boardsToolbarItem) { diff --git a/arduino-ide-extension/src/browser/arduino-frontend-module.ts b/arduino-ide-extension/src/browser/arduino-frontend-module.ts index 7cec9282..dc7317d8 100644 --- a/arduino-ide-extension/src/browser/arduino-frontend-module.ts +++ b/arduino-ide-extension/src/browser/arduino-frontend-module.ts @@ -50,6 +50,7 @@ import { EditorWidgetFactory } from '@theia/editor/lib/browser/editor-widget-fac import { CustomEditorWidgetFactory } from './customization/custom-editor-widget-factory'; import { SelectBoardsDialog, SelectBoardsDialogProps } from './boards/select-board-dialog'; import { SelectBoardDialogWidget } from './boards/select-board-dialog-widget'; +import { BoardFrontendService } from './boards/board-frontend-service'; export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Unbind, isBound: interfaces.IsBound, rebind: interfaces.Rebind) => { // Commands and toolbar items @@ -86,6 +87,7 @@ export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Un // Boards service bind(BoardsService).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, BoardsServicePath)).inSingletonScope(); + bind(BoardFrontendService).toSelf().inSingletonScope(); // Boards list widget bind(BoardsListWidget).toSelf(); diff --git a/arduino-ide-extension/src/browser/boards/board-frontend-service.ts b/arduino-ide-extension/src/browser/boards/board-frontend-service.ts new file mode 100644 index 00000000..edacf39c --- /dev/null +++ b/arduino-ide-extension/src/browser/boards/board-frontend-service.ts @@ -0,0 +1,22 @@ +import { injectable, inject } from "inversify"; +import { BoardsService, Board } from "../../common/protocol/boards-service"; + +@injectable() +export class BoardFrontendService { + @inject(BoardsService) protected readonly boardService: BoardsService; + + protected attachedBoards: Board[]; + + async getAttachedBoards(): Promise { + if (this.attachedBoards) { + return this.attachedBoards; + } + await this.refreshAttachedBoards(); + return this.attachedBoards; + } + + async refreshAttachedBoards(): Promise { + const { boards } = await this.boardService.getAttachedBoards(); + this.attachedBoards = boards; + } +} \ No newline at end of file diff --git a/arduino-ide-extension/src/browser/boards/boards-toolbar-item.tsx b/arduino-ide-extension/src/browser/boards/boards-toolbar-item.tsx index 859410d0..01c8e9eb 100644 --- a/arduino-ide-extension/src/browser/boards/boards-toolbar-item.tsx +++ b/arduino-ide-extension/src/browser/boards/boards-toolbar-item.tsx @@ -3,12 +3,14 @@ import { BoardsService, Board } from '../../common/protocol/boards-service'; import { ContextMenuRenderer } from '@theia/core/lib/browser'; import { ArduinoToolbarContextMenu } from '../arduino-file-menu'; import { BoardsNotificationService } from '../boards-notification-service'; +import { BoardFrontendService } from './board-frontend-service'; export namespace BoardsToolBarItem { export interface Props { readonly contextMenuRenderer: ContextMenuRenderer; readonly boardsNotificationService: BoardsNotificationService; readonly boardService: BoardsService; + readonly boardFrontendService: BoardFrontendService; } export interface State { @@ -35,9 +37,9 @@ export class BoardsToolBarItem extends React.Component void; } @@ -131,7 +133,7 @@ export class BoardAndPortSelectionComponent extends React.Component { + const boards = await this.props.boardFrontendService.getAttachedBoards(); + boards.forEach(board => { if (AttachedSerialBoard.is(board)) { ports.push(board.port); } @@ -222,6 +224,8 @@ export class SelectBoardDialogWidget extends ReactWidget { @inject(BoardsService) protected readonly boardsService: BoardsService; + @inject(BoardFrontendService) + protected readonly boardFrontendService: BoardFrontendService; @inject(BoardsNotificationService) protected readonly boardsNotificationService: BoardsNotificationService; @@ -279,7 +283,11 @@ export class SelectBoardDialogWidget extends ReactWidget {

but not to upload your sketch.

- this.boardAndPortSelectionComponent = ref} boardsService={boardsService} onSelect={this.onSelect} /> + this.boardAndPortSelectionComponent = ref} + boardFrontendService={this.boardFrontendService} + boardsService={boardsService} + onSelect={this.onSelect} /> From 23446284b7e3b3f6dc4f243b1eb1d90fd9512249 Mon Sep 17 00:00:00 2001 From: jbicker Date: Wed, 10 Jul 2019 17:48:20 +0200 Subject: [PATCH 7/7] Toggle the selected board in dropdown Signed-off-by: jbicker --- .../src/browser/arduino-file-menu.ts | 38 +--------- .../browser/arduino-frontend-contribution.tsx | 72 +++++++++++++------ .../src/browser/arduino-frontend-module.ts | 8 +-- .../browser/boards/board-frontend-service.ts | 22 ------ .../browser/boards/boards-toolbar-item.tsx | 19 ++--- .../boards/select-board-dialog-widget.tsx | 29 ++++---- .../src/browser/boards/select-board-dialog.ts | 40 +++++++++-- 7 files changed, 115 insertions(+), 113 deletions(-) delete mode 100644 arduino-ide-extension/src/browser/boards/board-frontend-service.ts diff --git a/arduino-ide-extension/src/browser/arduino-file-menu.ts b/arduino-ide-extension/src/browser/arduino-file-menu.ts index 30598267..2a0d1663 100644 --- a/arduino-ide-extension/src/browser/arduino-file-menu.ts +++ b/arduino-ide-extension/src/browser/arduino-file-menu.ts @@ -4,8 +4,7 @@ import { CommonMenus } from "@theia/core/lib/browser"; import { ArduinoCommands } from "./arduino-commands"; import { SketchesService, Sketch } from "../common/protocol/sketches-service"; import { AWorkspaceService } from "./arduino-workspace-service"; -import { BoardsService, Board, AttachedSerialBoard, AttachedNetworkBoard } from "../common/protocol/boards-service"; -import { BoardFrontendService } from "./boards/board-frontend-service"; +import { BoardsService } from "../common/protocol/boards-service"; export namespace ArduinoToolbarContextMenu { export const OPEN_SKETCH_PATH: MenuPath = ['arduino-open-sketch-context-menu']; @@ -30,51 +29,16 @@ export class ArduinoToolbarMenuContribution implements MenuContribution { @inject(BoardsService) protected readonly boardsService: BoardsService; - @inject(BoardFrontendService) - protected readonly boardFrontendService: BoardFrontendService; - constructor( @inject(AWorkspaceService) protected readonly workspaceService: AWorkspaceService, @inject(MenuModelRegistry) protected readonly menuRegistry: MenuModelRegistry) { workspaceService.onWorkspaceChanged(() => { if (this.workspaceService.workspace) { this.registerSketchesInMenu(menuRegistry); - this.registerConnectedBoardsInMenu(menuRegistry); } }) } - protected async registerConnectedBoardsInMenu(registry: MenuModelRegistry) { - const boards = await this.boardFrontendService.getAttachedBoards(); - const selectedBoard = await this.boardsService.getSelectBoard(); - const selectedPort = selectedBoard ? this.getPort(selectedBoard) : ''; - boards.forEach(board => { - const port = this.getPort(board); - const command: Command = { - id: 'selectBoard' + port - } - this.commands.registerCommand(command, { - execute: () => this.commands.executeCommand(ArduinoCommands.SELECT_BOARD.id, board) - }); - registry.registerMenuAction(ArduinoToolbarContextMenu.CONNECTED_GROUP, { - commandId: command.id, - label: board.name + ' at ' + port + (selectedPort === port ? '*' : '') - }); - }); - } - - - - protected getPort(board: Board): string { - if (AttachedSerialBoard.is(board)) { - return board.port; - } - if (AttachedNetworkBoard.is(board)) { - return 'netport: ' + board.port; - } - return ''; - } - protected async registerSketchesInMenu(registry: MenuModelRegistry) { const sketches = await this.getWorkspaceSketches(); sketches.forEach(sketch => { diff --git a/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx b/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx index 0dd0f38e..97849d20 100644 --- a/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx +++ b/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx @@ -3,9 +3,9 @@ import { injectable, inject, postConstruct } from 'inversify'; import URI from '@theia/core/lib/common/uri'; import { EditorWidget } from '@theia/editor/lib/browser/editor-widget'; import { MessageService } from '@theia/core/lib/common/message-service'; -import { CommandContribution, CommandRegistry } 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 { BoardsService, Board } from '../common/protocol/boards-service'; +import { BoardsService, Board, AttachedSerialBoard } from '../common/protocol/boards-service'; import { ArduinoCommands } from './arduino-commands'; import { ConnectedBoards } from './components/connected-boards'; import { CoreService } from '../common/protocol/core-service'; @@ -15,7 +15,7 @@ import { QuickPickService } from '@theia/core/lib/common/quick-pick-service'; import { BoardsListWidgetFrontendContribution } from './boards/boards-widget-frontend-contribution'; import { BoardsNotificationService } from './boards-notification-service'; import { WorkspaceRootUriAwareCommandHandler, WorkspaceCommands } from '@theia/workspace/lib/browser/workspace-commands'; -import { SelectionService } from '@theia/core'; +import { SelectionService, MenuModelRegistry } from '@theia/core'; import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service'; import { SketchFactory } from './sketch-factory'; import { ArduinoToolbar } from './toolbar/arduino-toolbar'; @@ -28,8 +28,7 @@ import { Sketch, SketchesService } from '../common/protocol/sketches-service'; import { WindowService } from '@theia/core/lib/browser/window/window-service'; import { CommonCommands } from '@theia/core/lib/browser/common-frontend-contribution' import { BoardsToolBarItem } from './boards/boards-toolbar-item'; -import { SelectBoardsDialog } from './boards/select-board-dialog'; -import { BoardFrontendService } from './boards/board-frontend-service'; +import { SelectBoardDialog } from './boards/select-board-dialog'; @injectable() export class ArduinoFrontendContribution implements TabBarToolbarContribution, CommandContribution { @@ -40,9 +39,6 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C @inject(BoardsService) protected readonly boardService: BoardsService; - @inject(BoardFrontendService) - protected readonly boardFrontendService: BoardFrontendService; - @inject(CoreService) protected readonly coreService: CoreService; @@ -91,15 +87,58 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C @inject(SketchesService) protected readonly sketches: SketchesService; - @inject(SelectBoardsDialog) - protected readonly selectBoardsDialog: SelectBoardsDialog; + @inject(SelectBoardDialog) + protected readonly selectBoardsDialog: SelectBoardDialog; + + @inject(MenuModelRegistry) + protected readonly menuRegistry: MenuModelRegistry; + + @inject(CommandRegistry) + protected readonly commands: CommandRegistry; protected boardsToolbarItem: BoardsToolBarItem | null; + protected attachedBoards: Board[]; + protected selectedBoard: Board; @postConstruct() protected async init(): Promise { // This is a hack. Otherwise, the backend services won't bind. await this.workspaceServiceExt.roots(); + const { boards } = await this.boardService.getAttachedBoards(); + this.attachedBoards = boards; + this.registerConnectedBoardsInMenu(this.menuRegistry); + } + + protected async registerConnectedBoardsInMenu(registry: MenuModelRegistry) { + this.attachedBoards.forEach(board => { + const port = this.getPort(board); + const command: Command = { + id: 'selectBoard' + port + } + this.commands.registerCommand(command, { + execute: () => this.commands.executeCommand(ArduinoCommands.SELECT_BOARD.id, board), + isToggled: () => this.isSelectedBoard(board) + }); + registry.registerMenuAction(ArduinoToolbarContextMenu.CONNECTED_GROUP, { + commandId: command.id, + label: board.name + ' at ' + port + }); + }); + } + + protected isSelectedBoard(board: Board): boolean { + return AttachedSerialBoard.is(board) && + this.selectedBoard && + AttachedSerialBoard.is(this.selectedBoard) && + board.port === this.selectedBoard.port && + board.fqbn === this.selectedBoard.fqbn; + } + + protected getPort(board: Board): string { + if (AttachedSerialBoard.is(board)) { + return board.port; + } + return ''; } registerToolbarItems(registry: TabBarToolbarRegistry): void { @@ -133,7 +172,6 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C ref={ref => this.boardsToolbarItem = ref} contextMenuRenderer={this.contextMenuRenderer} boardsNotificationService={this.boardsNotificationService} - boardFrontendService={this.boardFrontendService} boardService={this.boardService} />, isVisible: widget => this.isArduinoToolbar(widget) }) @@ -241,26 +279,18 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C execute: async () => { const boardAndPort = await this.selectBoardsDialog.open(); if (boardAndPort && boardAndPort.board) { - const selectedBoard = { - fqbn: boardAndPort.board.fqbn, - name: boardAndPort.board.name, - port: boardAndPort.port - } - this.selectBoard(selectedBoard); + this.selectBoard(boardAndPort.board); } } }) } protected async selectBoard(board: Board) { - const boards = await this.boardFrontendService.getAttachedBoards(); - if (boards.length) { - board = boards.find(b => b.name === board.name && b.fqbn === board.fqbn) || board; - } await this.boardService.selectBoard(board) if (this.boardsToolbarItem) { this.boardsToolbarItem.setSelectedBoard(board); } + this.selectedBoard = board; } protected async openSketchFilesInNewWindow(uri: string) { diff --git a/arduino-ide-extension/src/browser/arduino-frontend-module.ts b/arduino-ide-extension/src/browser/arduino-frontend-module.ts index dc7317d8..89483e52 100644 --- a/arduino-ide-extension/src/browser/arduino-frontend-module.ts +++ b/arduino-ide-extension/src/browser/arduino-frontend-module.ts @@ -48,9 +48,8 @@ import { CustomApplicationShell } from './customization/custom-application-shell import { CustomFrontendApplication } from './customization/custom-frontend-application'; import { EditorWidgetFactory } from '@theia/editor/lib/browser/editor-widget-factory'; import { CustomEditorWidgetFactory } from './customization/custom-editor-widget-factory'; -import { SelectBoardsDialog, SelectBoardsDialogProps } from './boards/select-board-dialog'; +import { SelectBoardDialog, SelectBoardDialogProps } from './boards/select-board-dialog'; import { SelectBoardDialogWidget } from './boards/select-board-dialog-widget'; -import { BoardFrontendService } from './boards/board-frontend-service'; export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Unbind, isBound: interfaces.IsBound, rebind: interfaces.Rebind) => { // Commands and toolbar items @@ -87,7 +86,6 @@ export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Un // Boards service bind(BoardsService).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, BoardsServicePath)).inSingletonScope(); - bind(BoardFrontendService).toSelf().inSingletonScope(); // Boards list widget bind(BoardsListWidget).toSelf(); @@ -100,8 +98,8 @@ export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Un // Board select dialog bind(SelectBoardDialogWidget).toSelf().inSingletonScope(); - bind(SelectBoardsDialog).toSelf().inSingletonScope(); - bind(SelectBoardsDialogProps).toConstantValue({ + bind(SelectBoardDialog).toSelf().inSingletonScope(); + bind(SelectBoardDialogProps).toConstantValue({ title: 'Select Board' }) diff --git a/arduino-ide-extension/src/browser/boards/board-frontend-service.ts b/arduino-ide-extension/src/browser/boards/board-frontend-service.ts deleted file mode 100644 index edacf39c..00000000 --- a/arduino-ide-extension/src/browser/boards/board-frontend-service.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { injectable, inject } from "inversify"; -import { BoardsService, Board } from "../../common/protocol/boards-service"; - -@injectable() -export class BoardFrontendService { - @inject(BoardsService) protected readonly boardService: BoardsService; - - protected attachedBoards: Board[]; - - async getAttachedBoards(): Promise { - if (this.attachedBoards) { - return this.attachedBoards; - } - await this.refreshAttachedBoards(); - return this.attachedBoards; - } - - async refreshAttachedBoards(): Promise { - const { boards } = await this.boardService.getAttachedBoards(); - this.attachedBoards = boards; - } -} \ No newline at end of file diff --git a/arduino-ide-extension/src/browser/boards/boards-toolbar-item.tsx b/arduino-ide-extension/src/browser/boards/boards-toolbar-item.tsx index 01c8e9eb..765daf5f 100644 --- a/arduino-ide-extension/src/browser/boards/boards-toolbar-item.tsx +++ b/arduino-ide-extension/src/browser/boards/boards-toolbar-item.tsx @@ -3,14 +3,12 @@ import { BoardsService, Board } from '../../common/protocol/boards-service'; import { ContextMenuRenderer } from '@theia/core/lib/browser'; import { ArduinoToolbarContextMenu } from '../arduino-file-menu'; import { BoardsNotificationService } from '../boards-notification-service'; -import { BoardFrontendService } from './board-frontend-service'; export namespace BoardsToolBarItem { export interface Props { readonly contextMenuRenderer: ContextMenuRenderer; readonly boardsNotificationService: BoardsNotificationService; readonly boardService: BoardsService; - readonly boardFrontendService: BoardFrontendService; } export interface State { @@ -37,16 +35,16 @@ export class BoardsToolBarItem extends React.Component attachedBoard.name === board.name) }); } this.setState({ selectedBoard: board }); @@ -56,10 +54,13 @@ export class BoardsToolBarItem extends React.Component) { const el = (event.target as HTMLElement).parentElement; if (el) { - this.props.contextMenuRenderer.render(ArduinoToolbarContextMenu.SELECT_BOARDS_PATH, { - x: el.getBoundingClientRect().left, - y: el.getBoundingClientRect().top + el.offsetHeight - }); + this.props.contextMenuRenderer.render({ + menuPath: ArduinoToolbarContextMenu.SELECT_BOARDS_PATH, + anchor: { + x: el.getBoundingClientRect().left, + y: el.getBoundingClientRect().top + el.offsetHeight + } + }) } } diff --git a/arduino-ide-extension/src/browser/boards/select-board-dialog-widget.tsx b/arduino-ide-extension/src/browser/boards/select-board-dialog-widget.tsx index eead64e6..d7657303 100644 --- a/arduino-ide-extension/src/browser/boards/select-board-dialog-widget.tsx +++ b/arduino-ide-extension/src/browser/boards/select-board-dialog-widget.tsx @@ -4,7 +4,6 @@ import { injectable, inject } from 'inversify'; import { BoardsService, Board, BoardPackage, AttachedSerialBoard } from '../../common/protocol/boards-service'; import { BoardsNotificationService } from '../boards-notification-service'; import { Emitter, Event } from '@theia/core'; -import { BoardFrontendService } from './board-frontend-service'; export interface BoardAndPortSelection { board?: Board; @@ -94,7 +93,6 @@ export class BoardAndPortSelectionList extends React.Component void; } @@ -160,11 +158,14 @@ export class BoardAndPortSelectionComponent extends React.Component PORTS - { this.portListComponent = ref }} - type='ports' - onSelect={this.doSelect} - list={this.state.ports.map(port => ({ port }))} /> + { + this.state.ports.length ? + { this.portListComponent = ref }} + type='ports' + onSelect={this.doSelect} + list={this.state.ports.map(port => ({ port }))} /> : 'loading ports...' + } @@ -209,7 +210,7 @@ export class BoardAndPortSelectionComponent extends React.Component { if (AttachedSerialBoard.is(board)) { ports.push(board.port); @@ -221,16 +222,14 @@ export class BoardAndPortSelectionComponent extends React.Component(); protected boardAndPortSelectionComponent: BoardAndPortSelectionComponent | null; + protected attachedBoards: Promise<{ boards: Board[] }>; boardAndPort: BoardAndPortSelection = {}; @@ -252,6 +251,10 @@ export class SelectBoardDialogWidget extends ReactWidget { this.boardAndPort = {}; } + setAttachedBoards(attachedBoards: Promise<{ boards: Board[] }>): void { + this.attachedBoards = attachedBoards; + } + protected fireChanged(boardAndPort: BoardAndPortSelection): void { this.onChangedEmitter.fire(boardAndPort); } @@ -260,8 +263,9 @@ export class SelectBoardDialogWidget extends ReactWidget { let content: React.ReactNode; const boardsServiceDelegate = this.boardsService; + const attachedBoards = this.attachedBoards; const boardsService: BoardsService = { - getAttachedBoards: () => boardsServiceDelegate.getAttachedBoards(), + getAttachedBoards: () => attachedBoards, selectBoard: (board: Board) => boardsServiceDelegate.selectBoard(board), getSelectBoard: () => boardsServiceDelegate.getSelectBoard(), search: (options: { query?: string }) => boardsServiceDelegate.search(options), @@ -285,7 +289,6 @@ export class SelectBoardDialogWidget extends ReactWidget { this.boardAndPortSelectionComponent = ref} - boardFrontendService={this.boardFrontendService} boardsService={boardsService} onSelect={this.onSelect} /> diff --git a/arduino-ide-extension/src/browser/boards/select-board-dialog.ts b/arduino-ide-extension/src/browser/boards/select-board-dialog.ts index 2541b4ad..d7c5c81d 100644 --- a/arduino-ide-extension/src/browser/boards/select-board-dialog.ts +++ b/arduino-ide-extension/src/browser/boards/select-board-dialog.ts @@ -3,20 +3,23 @@ import { injectable, inject } from 'inversify'; import { SelectBoardDialogWidget, BoardAndPortSelection } from './select-board-dialog-widget'; import { Message } from '@phosphor/messaging'; import { Disposable } from '@theia/core'; +import { Board, BoardsService, AttachedSerialBoard } from '../../common/protocol/boards-service'; @injectable() -export class SelectBoardsDialogProps extends DialogProps { +export class SelectBoardDialogProps extends DialogProps { } @injectable() -export class SelectBoardsDialog extends AbstractDialog { +export class SelectBoardDialog extends AbstractDialog { protected readonly dialogPanel: Panel; + protected attachedBoards: Board[]; constructor( - @inject(SelectBoardsDialogProps) protected readonly props: SelectBoardsDialogProps, - @inject(SelectBoardDialogWidget) protected readonly widget: SelectBoardDialogWidget + @inject(SelectBoardDialogProps) protected readonly props: SelectBoardDialogProps, + @inject(SelectBoardDialogWidget) protected readonly widget: SelectBoardDialogWidget, + @inject(BoardsService) protected readonly boardService: BoardsService ) { super({ title: props.title }); @@ -24,13 +27,21 @@ export class SelectBoardsDialog extends AbstractDialog { this.dialogPanel.addWidget(this.widget); this.toDispose.push(this.widget.onChanged(() => this.update())); - this.toDispose.push(this.dialogPanel); + this.attachedBoards = []; + this.init(); + this.appendCloseButton('CANCEL'); this.appendAcceptButton('OK'); } + protected init() { + const boards = this.boardService.getAttachedBoards(); + boards.then(b => this.attachedBoards = b.boards); + this.widget.setAttachedBoards(boards); + } + protected onAfterAttach(msg: Message): void { Widget.attach(this.dialogPanel, this.contentNode); @@ -68,7 +79,24 @@ export class SelectBoardsDialog extends AbstractDialog { } get value(): BoardAndPortSelection { - return this.widget.boardAndPort; + const boardAndPortSelection = this.widget.boardAndPort; + if (this.attachedBoards.length) { + boardAndPortSelection.board = this.attachedBoards.find(b => { + const isAttachedBoard = !!boardAndPortSelection.board && + b.name === boardAndPortSelection.board.name && + b.fqbn === boardAndPortSelection.board.fqbn; + if (boardAndPortSelection.port) { + return isAttachedBoard && + AttachedSerialBoard.is(b) && + b.port === boardAndPortSelection.port; + } else { + return isAttachedBoard; + } + + }) + || boardAndPortSelection.board; + } + return boardAndPortSelection; } close(): void {