ATL-750: Handle board name change after install.

Signed-off-by: Akos Kitta <kittaakos@typefox.io>
This commit is contained in:
Akos Kitta
2020-12-02 09:03:04 +01:00
committed by Akos Kitta
parent 7696e2c4c9
commit c024a8d3d1
26 changed files with 674 additions and 79 deletions

View File

@@ -19,6 +19,7 @@ export class BoardsConfigDialogWidget extends ReactWidget {
@inject(NotificationCenter)
protected readonly notificationCenter: NotificationCenter;
protected readonly onFilterTextDidChangeEmitter = new Emitter<string>();
protected readonly onBoardConfigChangedEmitter = new Emitter<BoardsConfig.Config>();
readonly onBoardConfigChanged = this.onBoardConfigChangedEmitter.event;
@@ -27,6 +28,14 @@ export class BoardsConfigDialogWidget extends ReactWidget {
constructor() {
super();
this.id = 'select-board-dialog';
this.toDispose.pushAll([
this.onBoardConfigChangedEmitter,
this.onFilterTextDidChangeEmitter
]);
}
search(query: string): void {
this.onFilterTextDidChangeEmitter.fire(query);
}
protected fireConfigChanged = (config: BoardsConfig.Config) => {
@@ -43,7 +52,8 @@ export class BoardsConfigDialogWidget extends ReactWidget {
boardsServiceProvider={this.boardsServiceClient}
notificationCenter={this.notificationCenter}
onConfigChange={this.fireConfigChanged}
onFocusNodeSet={this.setFocusNode} />
onFocusNodeSet={this.setFocusNode}
onFilteredTextDidChangeEvent={this.onFilterTextDidChangeEmitter.event} />
</div>;
}

View File

@@ -1,10 +1,10 @@
import { injectable, inject, postConstruct } from 'inversify';
import { Message } from '@phosphor/messaging';
import { AbstractDialog, DialogProps, Widget, DialogError } from '@theia/core/lib/browser';
import { BoardsService } from '../../common/protocol/boards-service';
import { BoardsConfig } from './boards-config';
import { BoardsConfigDialogWidget } from './boards-config-dialog-widget';
import { BoardsService } from '../../common/protocol/boards-service';
import { BoardsServiceProvider } from './boards-service-provider';
import { BoardsConfigDialogWidget } from './boards-config-dialog-widget';
@injectable()
export class BoardsConfigDialogProps extends DialogProps {
@@ -42,6 +42,16 @@ export class BoardsConfigDialog extends AbstractDialog<BoardsConfig.Config> {
}));
}
/**
* Pass in an empty string if you want to reset the search term. Using `undefined` has no effect.
*/
async open(query: string | undefined = undefined): Promise<BoardsConfig.Config | undefined> {
if (typeof query === 'string') {
this.widget.search(query);
}
return super.open();
}
protected createDescription(): HTMLElement {
const head = document.createElement('div');
head.classList.add('head');

View File

@@ -1,10 +1,11 @@
import * as React from 'react';
import { Event } from '@theia/core/lib/common/event';
import { notEmpty } from '@theia/core/lib/common/objects';
import { MaybePromise } from '@theia/core/lib/common/types';
import { DisposableCollection } from '@theia/core/lib/common/disposable';
import { Board, Port, AttachedBoardsChangeEvent } from '../../common/protocol/boards-service';
import { BoardsServiceProvider } from './boards-service-provider';
import { Board, Port, AttachedBoardsChangeEvent, BoardWithPackage } from '../../common/protocol/boards-service';
import { NotificationCenter } from '../notification-center';
import { MaybePromise } from '@theia/core';
import { BoardsServiceProvider } from './boards-service-provider';
export namespace BoardsConfig {
@@ -18,10 +19,11 @@ export namespace BoardsConfig {
readonly notificationCenter: NotificationCenter;
readonly onConfigChange: (config: Config) => void;
readonly onFocusNodeSet: (element: HTMLElement | undefined) => void;
readonly onFilteredTextDidChangeEvent: Event<string>;
}
export interface State extends Config {
searchResults: Array<Board & { packageName: string }>;
searchResults: Array<BoardWithPackage>;
knownPorts: Port[];
showAllPorts: boolean;
query: string;
@@ -91,7 +93,8 @@ export class BoardsConfig extends React.Component<BoardsConfig.Props, BoardsConf
this.props.notificationCenter.onPlatformUninstalled(() => this.updateBoards(this.state.query)),
this.props.notificationCenter.onIndexUpdated(() => this.updateBoards(this.state.query)),
this.props.notificationCenter.onDaemonStarted(() => this.updateBoards(this.state.query)),
this.props.notificationCenter.onDaemonStopped(() => this.setState({ searchResults: [] }))
this.props.notificationCenter.onDaemonStopped(() => this.setState({ searchResults: [] })),
this.props.onFilteredTextDidChangeEvent(query => this.setState({ query }, () => this.updateBoards(this.state.query)))
]);
}
@@ -105,10 +108,9 @@ export class BoardsConfig extends React.Component<BoardsConfig.Props, BoardsConf
}
protected updateBoards = (eventOrQuery: React.ChangeEvent<HTMLInputElement> | string = '') => {
const query = (typeof eventOrQuery === 'string'
const query = typeof eventOrQuery === 'string'
? eventOrQuery
: eventOrQuery.target.value.toLowerCase()
).trim();
: eventOrQuery.target.value.toLowerCase();
this.setState({ query });
this.queryBoards({ query }).then(searchResults => this.setState({ searchResults }));
}
@@ -124,7 +126,7 @@ export class BoardsConfig extends React.Component<BoardsConfig.Props, BoardsConf
});
}
protected queryBoards = (options: { query?: string } = {}): Promise<Array<Board & { packageName: string }>> => {
protected queryBoards = (options: { query?: string } = {}): Promise<Array<BoardWithPackage>> => {
return this.props.boardsServiceProvider.searchBoards(options);
}
@@ -145,7 +147,7 @@ export class BoardsConfig extends React.Component<BoardsConfig.Props, BoardsConf
this.setState({ selectedPort }, () => this.fireConfigChanged());
}
protected selectBoard = (selectedBoard: Board & { packageName: string } | undefined) => {
protected selectBoard = (selectedBoard: BoardWithPackage | undefined) => {
this.setState({ selectedBoard }, () => this.fireConfigChanged());
}
@@ -175,7 +177,7 @@ export class BoardsConfig extends React.Component<BoardsConfig.Props, BoardsConf
}
protected renderBoards(): React.ReactNode {
const { selectedBoard, searchResults } = this.state;
const { selectedBoard, searchResults, query } = this.state;
// Board names are not unique per core https://github.com/arduino/arduino-pro-ide/issues/262#issuecomment-661019560
// It is tricky when the core is not yet installed, no FQBNs are available.
const distinctBoards = new Map<string, Board.Detailed>();
@@ -189,11 +191,18 @@ export class BoardsConfig extends React.Component<BoardsConfig.Props, BoardsConf
return <React.Fragment>
<div className='search'>
<input type='search' className='theia-input' placeholder='SEARCH BOARD' onChange={this.updateBoards} ref={this.focusNodeSet} />
<input
type='search'
value={query}
className='theia-input'
placeholder='SEARCH BOARD'
onChange={this.updateBoards}
ref={this.focusNodeSet}
/>
<i className='fa fa-search'></i>
</div>
<div className='boards list'>
{Array.from(distinctBoards.values()).map(board => <Item<Board & { packageName: string }>
{Array.from(distinctBoards.values()).map(board => <Item<BoardWithPackage>
key={`${board.name}-${board.packageName}`}
item={board}
label={board.name}

View File

@@ -83,7 +83,7 @@ export class BoardsDataStore implements FrontendApplicationContribution {
}
const key = this.getStorageKey(fqbn, version);
let data = await this.storageService.getData<BoardsDataStore.Data | undefined>(key, undefined);
if (data) {
if (BoardsDataStore.Data.is(data)) {
return data;
}
@@ -202,5 +202,10 @@ export namespace BoardsDataStore {
configOptions: [],
programmers: []
};
export function is(arg: any): arg is Data {
return !!arg
&& 'configOptions' in arg && Array.isArray(arg['configOptions'])
&& 'programmers' in arg && Array.isArray(arg['programmers'])
}
}
}

View File

@@ -10,15 +10,18 @@ import {
Board,
BoardsService,
BoardsPackage,
AttachedBoardsChangeEvent
AttachedBoardsChangeEvent,
BoardWithPackage
} from '../../common/protocol';
import { BoardsConfig } from './boards-config';
import { naturalCompare } from '../../common/utils';
import { compareAnything } from '../theia/monaco/comparers';
import { NotificationCenter } from '../notification-center';
import { CommandService } from '@theia/core';
import { ArduinoCommands } from '../arduino-commands';
interface BoardMatch {
readonly board: Board & Readonly<{ packageName: string }>;
readonly board: BoardWithPackage;
readonly matches: monaco.filters.IMatch[] | undefined;
}
@@ -37,6 +40,9 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
@inject(BoardsService)
protected boardsService: BoardsService;
@inject(CommandService)
protected commandService: CommandService;
@inject(NotificationCenter)
protected notificationCenter: NotificationCenter;
@@ -107,6 +113,19 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
};
return;
}
// The board name can change after install.
// This logic handles it "gracefully" by unselecting the board, so that we can avoid no FQBN is set error.
// https://github.com/arduino/arduino-cli/issues/620
// https://github.com/arduino/arduino-pro-ide/issues/374
if (BoardWithPackage.is(selectedBoard) && selectedBoard.packageId === event.item.id && !installedBoard) {
this.messageService.warn(`Could not find previously selected board '${selectedBoard.name}' in installed platform '${event.item.name}'. Please manually reselect the board you want to use. Do you want to reselect it now?`, 'Reselect later', 'Yes').then(async answer => {
if (answer === 'Yes') {
this.commandService.executeCommand(ArduinoCommands.OPEN_BOARDS_DIALOG.id, selectedBoard.name);
}
});
this.boardsConfig = {}
return;
}
// Trigger a board re-set. See: https://github.com/arduino/arduino-cli/issues/954
// E.g: install `adafruit:avr`, then select `adafruit:avr:adafruit32u4` board, and finally install the required `arduino:avr`
this.boardsConfig = this.boardsConfig;
@@ -173,15 +192,15 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
}
}
async searchBoards({ query, cores }: { query?: string, cores?: string[] }): Promise<Array<Board & { packageName: string }>> {
async searchBoards({ query, cores }: { query?: string, cores?: string[] }): Promise<Array<BoardWithPackage>> {
const boards = await this.boardsService.allBoards({});
const coresFilter = !!cores && cores.length
? ((toFilter: { packageName: string }) => cores.some(core => core === toFilter.packageName))
? ((toFilter: BoardWithPackage) => cores.some(core => core === toFilter.packageName || core === toFilter.packageId))
: () => true;
if (!query) {
return boards.filter(coresFilter).sort(Board.compare);
}
const toMatch = ((toFilter: Board & { packageName: string }) => (({ board: toFilter, matches: monaco.filters.matchesFuzzy(query, toFilter.name, true) })));
const toMatch = ((toFilter: BoardWithPackage) => (({ board: toFilter, matches: monaco.filters.matchesFuzzy(query, toFilter.name, true) })));
const compareEntries = (left: BoardMatch, right: BoardMatch, lookFor: string) => {
const leftMatches = left.matches || [];
const rightMatches = right.matches || [];