workaround for non-unique names.

Fine tuned the port unnselection when attached boards change.

This should make sure we do not have to `await` for the attached boards
from the backend.

Signed-off-by: Akos Kitta <kittaakos@typefox.io>
This commit is contained in:
Akos Kitta 2019-07-31 11:49:26 +02:00 committed by jbicker
parent 0dc45daf01
commit 66f429c478
3 changed files with 78 additions and 26 deletions

View File

@ -1,6 +1,6 @@
import * as React from 'react'; import * as React from 'react';
import { DisposableCollection } from '@theia/core'; import { DisposableCollection } from '@theia/core';
import { BoardsService, Board, AttachedSerialBoard } from '../../common/protocol/boards-service'; import { BoardsService, Board, AttachedSerialBoard, AttachedBoardsChangeEvent } from '../../common/protocol/boards-service';
import { BoardsServiceClientImpl } from './boards-service-client-impl'; import { BoardsServiceClientImpl } from './boards-service-client-impl';
export namespace BoardsConfig { export namespace BoardsConfig {
@ -18,7 +18,7 @@ export namespace BoardsConfig {
} }
export interface State extends Config { export interface State extends Config {
searchResults: Board[]; searchResults: Array<Board & { packageName: string }>;
knownPorts: string[]; knownPorts: string[];
} }
@ -26,13 +26,15 @@ export namespace BoardsConfig {
export abstract class Item<T> extends React.Component<{ export abstract class Item<T> extends React.Component<{
item: T, item: T,
name: string, label: string,
selected: boolean, selected: boolean,
onClick: (item: T) => void, onClick: (item: T) => void,
missing?: boolean }> { missing?: boolean,
detail?: string
}> {
render(): React.ReactNode { render(): React.ReactNode {
const { selected, name, missing } = this.props; const { selected, label, missing, detail } = this.props;
const classNames = ['item']; const classNames = ['item'];
if (selected) { if (selected) {
classNames.push('selected'); classNames.push('selected');
@ -40,9 +42,12 @@ export abstract class Item<T> extends React.Component<{
if (missing === true) { if (missing === true) {
classNames.push('missing') classNames.push('missing')
} }
return <div onClick={this.onClick} className={classNames.join(' ')}> return <div onClick={this.onClick} className={classNames.join(' ')} title={`${label}${!detail ? '' : detail}`}>
{name} <div className='label'>
{selected ? <i className='fa fa-check'></i> : ''} {label}
</div>
{!detail ? '' : <div className='detail'>{detail}</div>}
{!selected ? '' : <div className='selected-icon'><i className='fa fa-check'/></div>}
</div>; </div>;
} }
@ -72,7 +77,7 @@ export class BoardsConfig extends React.Component<BoardsConfig.Props, BoardsConf
this.props.boardsService.getAttachedBoards().then(({ boards }) => this.updatePorts(boards)); this.props.boardsService.getAttachedBoards().then(({ boards }) => this.updatePorts(boards));
const { boardsServiceClient: client } = this.props; const { boardsServiceClient: client } = this.props;
this.toDispose.pushAll([ this.toDispose.pushAll([
client.onBoardsChanged(event => this.updatePorts(event.newState.boards)), client.onBoardsChanged(event => this.updatePorts(event.newState.boards, AttachedBoardsChangeEvent.diff(event).detached)),
client.onBoardsConfigChanged(({ selectedBoard, selectedPort }) => { client.onBoardsConfigChanged(({ selectedBoard, selectedPort }) => {
this.setState({ selectedBoard, selectedPort }, () => this.fireConfigChanged()); this.setState({ selectedBoard, selectedPort }, () => this.fireConfigChanged());
}) })
@ -96,23 +101,24 @@ export class BoardsConfig extends React.Component<BoardsConfig.Props, BoardsConf
this.queryBoards({ query }).then(({ searchResults }) => this.setState({ searchResults })); this.queryBoards({ query }).then(({ searchResults }) => this.setState({ searchResults }));
} }
protected updatePorts = (boards: Board[] = []) => { protected updatePorts = (boards: Board[] = [], detachedBoards: Board[] = []) => {
this.queryPorts(Promise.resolve({ boards })).then(({ knownPorts }) => { this.queryPorts(Promise.resolve({ boards })).then(({ knownPorts }) => {
let { selectedPort } = this.state; let { selectedPort } = this.state;
if (!!selectedPort && knownPorts.indexOf(selectedPort) === -1) { const removedPorts = detachedBoards.filter(AttachedSerialBoard.is).map(({ port }) => port);
if (!!selectedPort && removedPorts.indexOf(selectedPort) === -1) {
selectedPort = undefined; selectedPort = undefined;
} }
this.setState({ knownPorts, selectedPort }, () => this.fireConfigChanged()); this.setState({ knownPorts, selectedPort }, () => this.fireConfigChanged());
}); });
} }
protected queryBoards = (options: { query?: string } = {}): Promise<{ searchResults: Board[] }> => { protected queryBoards = (options: { query?: string } = {}): Promise<{ searchResults: Array<Board & { packageName: string }> }> => {
const { boardsService } = this.props; const { boardsService } = this.props;
const query = (options.query || '').toLocaleLowerCase(); const query = (options.query || '').toLocaleLowerCase();
return new Promise<{ searchResults: Board[] }>(resolve => { return new Promise<{ searchResults: Array<Board & { packageName: string }> }>(resolve => {
boardsService.search(options) boardsService.search(options)
.then(({ items }) => items .then(({ items }) => items
.map(item => item.boards) .map(item => item.boards.map(board => ({ ...board, packageName: item.name })))
.reduce((acc, curr) => acc.concat(curr), []) .reduce((acc, curr) => acc.concat(curr), [])
.filter(board => board.name.toLocaleLowerCase().indexOf(query) !== -1) .filter(board => board.name.toLocaleLowerCase().indexOf(query) !== -1)
.sort(Board.compare)) .sort(Board.compare))
@ -139,7 +145,7 @@ export class BoardsConfig extends React.Component<BoardsConfig.Props, BoardsConf
this.setState({ selectedPort }, () => this.fireConfigChanged()); this.setState({ selectedPort }, () => this.fireConfigChanged());
} }
protected selectBoard = (selectedBoard: Board | undefined) => { protected selectBoard = (selectedBoard: Board & { packageName: string } | undefined) => {
this.setState({ selectedBoard }, () => this.fireConfigChanged()); this.setState({ selectedBoard }, () => this.fireConfigChanged());
} }
@ -166,18 +172,40 @@ export class BoardsConfig extends React.Component<BoardsConfig.Props, BoardsConf
} }
protected renderBoards(): React.ReactNode { protected renderBoards(): React.ReactNode {
const { selectedBoard } = this.state; const { selectedBoard, searchResults } = this.state;
// Board names are not unique. We show the corresponding core name as a detail.
// https://github.com/arduino/arduino-cli/pull/294#issuecomment-513764948
const distinctBoardNames = new Map<string, number>();
for (const { name } of searchResults) {
const counter = distinctBoardNames.get(name) || 0;
distinctBoardNames.set(name, counter + 1);
}
// Due to the non-unique board names, we have to check the package name as well.
const selected = (board: Board & { packageName: string }) => {
if (!!selectedBoard) {
if (Board.equals(board, selectedBoard)) {
if ('packageName' in selectedBoard) {
return board.packageName === (selectedBoard as any).packageName;
}
return true;
}
}
return false;
}
return <React.Fragment> return <React.Fragment>
<div className='search'> <div className='search'>
<input type='search' placeholder='SEARCH BOARD' onChange={this.updateBoards} ref={this.focusNodeSet} /> <input type='search' placeholder='SEARCH BOARD' onChange={this.updateBoards} ref={this.focusNodeSet} />
<i className='fa fa-search'></i> <i className='fa fa-search'></i>
</div> </div>
<div className='boards list'> <div className='boards list'>
{this.state.searchResults.map((board, index) => <Item<Board> {this.state.searchResults.map(board => <Item<Board & { packageName: string }>
key={`${board.name}-${index}`} key={`${board.name}-${board.packageName}`}
item={board} item={board}
name={board.name} label={board.name}
selected={!!selectedBoard && Board.equals(board, selectedBoard)} detail={(distinctBoardNames.get(board.name) || 0) > 1 ? ` - ${board.packageName}` : undefined}
selected={selected(board)}
onClick={this.selectBoard} onClick={this.selectBoard}
missing={!Board.installed(board)} missing={!Board.installed(board)}
/>)} />)}
@ -197,7 +225,7 @@ export class BoardsConfig extends React.Component<BoardsConfig.Props, BoardsConf
{this.state.knownPorts.map(port => <Item<string> {this.state.knownPorts.map(port => <Item<string>
key={port} key={port}
item={port} item={port}
name={port} label={port}
selected={this.state.selectedPort === port} selected={this.state.selectedPort === port}
onClick={this.selectPort} onClick={this.selectPort}
/>)} />)}

View File

@ -83,7 +83,20 @@ div#select-board-dialog .selectBoardContainer .body .list .item.selected i{
#select-board-dialog .selectBoardContainer .body .list .item { #select-board-dialog .selectBoardContainer .body .list .item {
padding: 10px 5px 10px 10px; padding: 10px 5px 10px 10px;
display: flex; display: flex;
justify-content: space-between; justify-content: end;
}
#select-board-dialog .selectBoardContainer .body .list .item .selected-icon {
margin-left: auto;
}
#select-board-dialog .selectBoardContainer .body .list .item .detail {
font-size: var(--theia-ui-font-size1);
color: var(--theia-disabled-color0);
width: 155px; /* used heuristics for the calculation */
white-space: pre;
overflow: hidden;
text-overflow: ellipsis;
} }
#select-board-dialog .selectBoardContainer .body .list .item.missing { #select-board-dialog .selectBoardContainer .body .list .item.missing {

View File

@ -7,6 +7,21 @@ export interface AttachedBoardsChangeEvent {
readonly oldState: Readonly<{ boards: Board[] }>; readonly oldState: Readonly<{ boards: Board[] }>;
readonly newState: Readonly<{ boards: Board[] }>; readonly newState: Readonly<{ boards: Board[] }>;
} }
export namespace AttachedBoardsChangeEvent {
export function diff(event: AttachedBoardsChangeEvent): Readonly<{ attached: Board[], detached: Board[] }> {
const diff = <T>(left: T[], right: T[]) => {
return left.filter(item => right.indexOf(item) === -1);
}
const { boards: newBoards } = event.newState;
const { boards: oldBoards } = event.oldState;
return {
detached: diff(oldBoards, newBoards),
attached: diff(newBoards, oldBoards)
};
}
}
export interface BoardInstalledEvent { export interface BoardInstalledEvent {
readonly pkg: Readonly<BoardPackage>; readonly pkg: Readonly<BoardPackage>;
@ -34,10 +49,6 @@ export interface Board {
fqbn?: string fqbn?: string
} }
export interface Port {
port?: string;
}
export namespace Board { export namespace Board {
export function is(board: any): board is Board { export function is(board: any): board is Board {