From c2fbccc9e8280016d55f3a826e69bffbefc5f45c Mon Sep 17 00:00:00 2001 From: jbicker Date: Fri, 12 Jul 2019 16:22:03 +0200 Subject: [PATCH 1/2] App doesn't show "open..." anymore if there are no sketches in default sketch folder. Opens file navigator directly instead. Signed-off-by: jbicker --- .../src/browser/arduino-file-menu.ts | 48 +--------------- .../browser/arduino-frontend-contribution.tsx | 56 ++++++++++++++++--- 2 files changed, 49 insertions(+), 55 deletions(-) diff --git a/arduino-ide-extension/src/browser/arduino-file-menu.ts b/arduino-ide-extension/src/browser/arduino-file-menu.ts index 2a0d1663..74a6c292 100644 --- a/arduino-ide-extension/src/browser/arduino-file-menu.ts +++ b/arduino-ide-extension/src/browser/arduino-file-menu.ts @@ -1,10 +1,7 @@ import { injectable, inject } from "inversify"; -import { MenuContribution, MenuModelRegistry, MenuPath, CommandRegistry, Command } from "@theia/core"; +import { MenuContribution, MenuModelRegistry, MenuPath } from "@theia/core"; 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 } from "../common/protocol/boards-service"; export namespace ArduinoToolbarContextMenu { export const OPEN_SKETCH_PATH: MenuPath = ['arduino-open-sketch-context-menu']; @@ -20,55 +17,14 @@ export namespace ArduinoToolbarContextMenu { @injectable() export class ArduinoToolbarMenuContribution implements MenuContribution { - @inject(CommandRegistry) - protected readonly commands: CommandRegistry; - - @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); - } - }) - } - - 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 - }); - }) - } - - protected async getWorkspaceSketches(): Promise { - const sketches = this.sketches.getSketches(this.workspaceService.workspace); - return sketches; } 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, - label: 'Open...' - }); + }) registry.registerMenuAction(ArduinoToolbarContextMenu.OPEN_BOARDS_DIALOG_GROUP, { commandId: ArduinoCommands.OPEN_BOARDS_DIALOG.id, diff --git a/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx b/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx index 51f29225..e7d4efb6 100644 --- a/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx +++ b/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx @@ -66,9 +66,6 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C @inject(BoardsNotificationService) protected readonly boardsNotificationService: BoardsNotificationService; - @inject(WorkspaceService) - protected readonly workspaceService: WorkspaceService; - @inject(SelectionService) protected readonly selectionService: SelectionService; @@ -108,6 +105,15 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C protected boardsToolbarItem: BoardsToolBarItem | null; protected attachedBoards: Board[]; protected selectedBoard: Board; + protected wsSketchCount: number = 0; + + constructor(@inject(WorkspaceService) protected readonly workspaceService: WorkspaceService) { + this.workspaceService.onWorkspaceChanged(() => { + if (this.workspaceService.workspace) { + this.registerSketchesInMenu(this.menuRegistry); + } + }) + } @postConstruct() protected async init(): Promise { @@ -233,12 +239,16 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C isVisible: widget => this.isArduinoToolbar(widget), isEnabled: widget => this.isArduinoToolbar(widget), execute: async (widget: Widget, target: EventTarget) => { - const el = (target as HTMLElement).parentElement; - if (el) { - this.contextMenuRenderer.render(ArduinoToolbarContextMenu.OPEN_SKETCH_PATH, { - x: el.getBoundingClientRect().left, - y: el.getBoundingClientRect().top + el.offsetHeight - }); + if (this.wsSketchCount) { + const el = (target as HTMLElement).parentElement; + if (el) { + this.contextMenuRenderer.render(ArduinoToolbarContextMenu.OPEN_SKETCH_PATH, { + x: el.getBoundingClientRect().left, + y: el.getBoundingClientRect().top + el.offsetHeight + }); + } + } else { + this.commands.executeCommand(ArduinoCommands.OPEN_FILE_NAVIGATOR.id); } } }); @@ -332,6 +342,10 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C label: 'Upload', order: '2' }); + registry.registerMenuAction(ArduinoToolbarContextMenu.OPEN_GROUP, { + commandId: ArduinoCommands.OPEN_FILE_NAVIGATOR.id, + label: 'Open...' + }); registry.registerSubmenu(ArduinoMenus.TOOLS, 'Tools'); } @@ -342,6 +356,30 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C return menuId; } + protected registerSketchesInMenu(registry: MenuModelRegistry) { + this.getWorkspaceSketches().then(sketches => { + this.wsSketchCount = sketches.length; + 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 + }); + }) + }) + } + + protected async getWorkspaceSketches(): Promise { + const sketches = this.sketches.getSketches(this.workspaceService.workspace); + return sketches; + } + protected async openSketchFilesInNewWindow(uri: string) { const location = new URL(window.location.href); location.searchParams.set('sketch', uri); From 4d2bd87f74ebaabfffb6a05586a281bec5d502a0 Mon Sep 17 00:00:00 2001 From: jbicker Date: Fri, 19 Jul 2019 17:44:27 +0200 Subject: [PATCH 2/2] Implemented custom dropdown for board selection Signed-off-by: jbicker --- .../src/browser/arduino-file-menu.ts | 9 - .../browser/arduino-frontend-contribution.tsx | 44 +--- .../browser/boards/boards-toolbar-item.tsx | 215 +++++++++++++++--- .../src/browser/style/board-select-dialog.css | 40 +++- .../src/browser/style/main.css | 21 +- 5 files changed, 232 insertions(+), 97 deletions(-) diff --git a/arduino-ide-extension/src/browser/arduino-file-menu.ts b/arduino-ide-extension/src/browser/arduino-file-menu.ts index 74a6c292..99fddd0c 100644 --- a/arduino-ide-extension/src/browser/arduino-file-menu.ts +++ b/arduino-ide-extension/src/browser/arduino-file-menu.ts @@ -8,10 +8,6 @@ export namespace ArduinoToolbarContextMenu { 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() @@ -25,10 +21,5 @@ export class ArduinoToolbarMenuContribution implements MenuContribution { registry.registerMenuAction([...CommonMenus.FILE, '0_new_sletch'], { commandId: ArduinoCommands.NEW_SKETCH.id }) - - 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 e7d4efb6..25a1c8f7 100644 --- a/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx +++ b/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx @@ -5,7 +5,7 @@ import { EditorWidget } from '@theia/editor/lib/browser/editor-widget'; import { MessageService } from '@theia/core/lib/common/message-service'; import { CommandContribution, CommandRegistry, Command } from '@theia/core/lib/common/command'; import { TabBarToolbarContribution, TabBarToolbarRegistry } from '@theia/core/lib/browser/shell/tab-bar-toolbar'; -import { BoardsService, Board, AttachedSerialBoard } 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'; @@ -103,8 +103,6 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C protected readonly commands: CommandRegistry; protected boardsToolbarItem: BoardsToolBarItem | null; - protected attachedBoards: Board[]; - protected selectedBoard: Board; protected wsSketchCount: number = 0; constructor(@inject(WorkspaceService) protected readonly workspaceService: WorkspaceService) { @@ -119,41 +117,6 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C 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 { @@ -184,7 +147,9 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C registry.registerItem({ id: ConnectedBoards.TOOLBAR_ID, render: () => this.boardsToolbarItem = ref} + commands={this.commands} contextMenuRenderer={this.contextMenuRenderer} boardsNotificationService={this.boardsNotificationService} boardService={this.boardService} />, @@ -305,11 +270,10 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C } protected async selectBoard(board: Board) { - await this.boardService.selectBoard(board) + await this.boardService.selectBoard(board); if (this.boardsToolbarItem) { this.boardsToolbarItem.setSelectedBoard(board); } - this.selectedBoard = board; } registerMenus(registry: MenuModelRegistry) { 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 765daf5f..f2eeba11 100644 --- a/arduino-ide-extension/src/browser/boards/boards-toolbar-item.tsx +++ b/arduino-ide-extension/src/browser/boards/boards-toolbar-item.tsx @@ -1,48 +1,135 @@ import * as React from 'react'; -import { BoardsService, Board } from '../../common/protocol/boards-service'; +import { BoardsService, Board, AttachedSerialBoard } 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 { Command, CommandRegistry } from '@theia/core'; +import { ArduinoCommands } from '../arduino-commands'; +import ReactDOM = require('react-dom'); + +export interface BoardsDropdownItem { + label: string; + commandExecutor: () => void; + isSelected: () => boolean; +} + +export interface BoardsDropDownListCoord { + top: number; + left: number; + width: number; + paddingTop: number; +} + +export namespace BoardsDropdownItemComponent { + export interface Props { + label: string; + onClick: () => void; + isSelected: boolean; + } +} + +export class BoardsDropdownItemComponent extends React.Component { + render() { + return
+
{this.props.label}
+ {this.props.isSelected ? : ''} +
; + } +} + +export namespace BoardsDropDown { + export interface Props { + readonly coords: BoardsDropDownListCoord; + readonly isOpen: boolean; + readonly dropDownItems: BoardsDropdownItem[]; + readonly openDialog: () => void; + } +} + +export class BoardsDropDown extends React.Component { + protected dropdownId: string = 'boards-dropdown-container'; + protected dropdownElement: HTMLElement; + + constructor(props: BoardsDropDown.Props) { + super(props); + + let list = document.getElementById(this.dropdownId); + if (!list) { + list = document.createElement('div'); + list.id = this.dropdownId; + document.body.appendChild(list); + this.dropdownElement = list; + } + } + + render(): React.ReactNode { + return ReactDOM.createPortal(this.renderNode(), this.dropdownElement); + } + + renderNode(): React.ReactNode { + if (this.props.isOpen) { + return
+ { + this.props.dropDownItems.map(item => { + return + + ; + }) + } + +
+ } else { + return ''; + } + } +} export namespace BoardsToolBarItem { export interface Props { readonly contextMenuRenderer: ContextMenuRenderer; readonly boardsNotificationService: BoardsNotificationService; readonly boardService: BoardsService; + readonly commands: CommandRegistry; } export interface State { selectedBoard?: Board; - selectedIsAttached: boolean + selectedIsAttached: boolean; + boardItems: BoardsDropdownItem[]; + isOpen: boolean; } } export class BoardsToolBarItem extends React.Component { protected attachedBoards: Board[]; + protected dropDownListCoord: BoardsDropDownListCoord; constructor(props: BoardsToolBarItem.Props) { super(props); this.state = { selectedBoard: undefined, - selectedIsAttached: true + selectedIsAttached: true, + boardItems: [], + isOpen: false }; + + document.addEventListener('click', () => { + this.setState({ isOpen: false }); + }); } 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 && this.attachedBoards.length) { this.setState({ selectedIsAttached: !!this.attachedBoards.find(attachedBoard => attachedBoard.name === board.name) }); @@ -50,31 +137,101 @@ export class BoardsToolBarItem extends React.Component) => this.showSelectBoardsMenu(event); - protected showSelectBoardsMenu(event: React.MouseEvent) { - const el = (event.target as HTMLElement).parentElement; - if (el) { - this.props.contextMenuRenderer.render({ - menuPath: ArduinoToolbarContextMenu.SELECT_BOARDS_PATH, - anchor: { - x: el.getBoundingClientRect().left, - y: el.getBoundingClientRect().top + el.offsetHeight + protected async setAttachedBoards() { + const { boards } = await this.props.boardService.getAttachedBoards(); + this.attachedBoards = boards; + if (this.attachedBoards.length) { + await this.createBoardDropdownItems(); + await this.props.boardService.selectBoard(this.attachedBoards[0]); + this.setSelectedBoard(this.attachedBoards[0]); + } + } + + protected createBoardDropdownItems() { + const boardItems: BoardsDropdownItem[] = []; + this.attachedBoards.forEach(board => { + const { commands } = this.props; + const port = this.getPort(board); + const command: Command = { + id: 'selectBoard' + port + } + commands.registerCommand(command, { + execute: () => { + commands.executeCommand(ArduinoCommands.SELECT_BOARD.id, board); + this.setState({ isOpen: false, selectedBoard: board }); } - }) + }); + boardItems.push({ + commandExecutor: () => commands.executeCommand(command.id), + label: board.name + ' at ' + port, + isSelected: () => this.doIsSelectedBoard(board) + }); + }); + this.setState({ boardItems }); + } + + protected doIsSelectedBoard = (board: Board) => this.isSelectedBoard(board); + protected isSelectedBoard(board: Board): boolean { + return AttachedSerialBoard.is(board) && + !!this.state.selectedBoard && + AttachedSerialBoard.is(this.state.selectedBoard) && + board.port === this.state.selectedBoard.port && + board.fqbn === this.state.selectedBoard.fqbn; + } + + protected getPort(board: Board): string { + if (AttachedSerialBoard.is(board)) { + return board.port; + } + return ''; + } + + protected readonly doShowSelectBoardsMenu = (event: React.MouseEvent) => { + this.showSelectBoardsMenu(event); + event.stopPropagation(); + event.nativeEvent.stopImmediatePropagation(); + }; + protected showSelectBoardsMenu(event: React.MouseEvent) { + const el = (event.currentTarget as HTMLElement); + if (el) { + this.dropDownListCoord = { + top: el.getBoundingClientRect().top, + left: el.getBoundingClientRect().left, + paddingTop: el.getBoundingClientRect().height, + width: el.getBoundingClientRect().width + } + this.setState({ isOpen: !this.state.isOpen }); } } render(): React.ReactNode { + const selectedBoard = this.state.selectedBoard; + const port = selectedBoard ? this.getPort(selectedBoard) : undefined; return -
-
-
- -
{this.state.selectedBoard ? this.state.selectedBoard.name : 'no board selected'}
- +
+
+
+ +
+
+ {selectedBoard ? `${selectedBoard.name}${port ? ' at ' + port : ''}` : 'no board selected'} +
+
+
+ + ; } + + protected openDialog = () => { + this.props.commands.executeCommand(ArduinoCommands.OPEN_BOARDS_DIALOG.id); + this.setState({ isOpen: false }); + }; } \ No newline at end of file diff --git a/arduino-ide-extension/src/browser/style/board-select-dialog.css b/arduino-ide-extension/src/browser/style/board-select-dialog.css index ce19946f..d7023208 100644 --- a/arduino-ide-extension/src/browser/style/board-select-dialog.css +++ b/arduino-ide-extension/src/browser/style/board-select-dialog.css @@ -123,30 +123,62 @@ button.theia-button.main { .arduino-boards-toolbar-item-container .arduino-boards-toolbar-item .inner-container { display: flex; align-items: baseline; - margin: 0 5px; + width: 100%; } .arduino-boards-toolbar-item-container .arduino-boards-toolbar-item .inner-container .notAttached { width: 10px; height: 10px; color: red; - margin-right: 5px; + margin: 0 5px; } .arduino-boards-toolbar-item-container { display: flex; align-items: center; + width: 220px; } .arduino-boards-toolbar-item .label { height: 100%; display: flex; align-items: center; + margin: 0 5px; + width: 100%; + font-weight: bold; +} + +.arduino-boards-toolbar-item .caret { + width: 10px; margin-right: 5px; } .arduino-boards-toolbar-item { background: white; - height: 18px; + height: 22px; + display: flex; + width: 100%; + overflow: hidden; } - + +.arduino-boards-dropdown-list { + background: #f7f7f7; + border: 3px solid var(--theia-border-color2); + margin: -3px; +} + +.arduino-boards-dropdown-item { + font-size: var(--theia-ui-font-size1); + display: flex; + padding: 10px; + cursor: pointer; +} + +.arduino-boards-dropdown-item .fa-check { + color: var(--theia-accent-color2); +} + +.arduino-boards-dropdown-item.selected, +.arduino-boards-dropdown-item:hover { + background: var(--theia-ui-button-color-secondary-hover); +} \ 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 1b7cf4bc..64806c1b 100644 --- a/arduino-ide-extension/src/browser/style/main.css +++ b/arduino-ide-extension/src/browser/style/main.css @@ -61,21 +61,6 @@ 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-boards-toolbar-item { - background: white; -} - .arduino-tool-item.item.connected-boards select { line-height: var(--theia-content-line-height); font-size: var(--theia-ui-font-size1); @@ -104,4 +89,10 @@ .monaco-editor .margin { border-right: 2px solid var(--theia-border-color1); box-sizing: border-box; +} + +.noWrapInfo { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } \ No newline at end of file