Use eslint&prettier for code linting&formatting

This commit is contained in:
Francesco Stasi
2021-06-16 15:08:48 +02:00
committed by Francesco Stasi
parent 2a3873a923
commit 0592199858
173 changed files with 8963 additions and 3841 deletions

View File

@@ -1,7 +1,11 @@
import { injectable, inject } from 'inversify';
import { MessageService } from '@theia/core/lib/common/message-service';
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
import { BoardsService, BoardsPackage, Board } from '../../common/protocol/boards-service';
import {
BoardsService,
BoardsPackage,
Board,
} from '../../common/protocol/boards-service';
import { BoardsServiceProvider } from './boards-service-provider';
import { BoardsListWidgetFrontendContribution } from './boards-widget-frontend-contribution';
import { BoardsConfig } from './boards-config';
@@ -14,7 +18,6 @@ import { ResponseServiceImpl } from '../response-service-impl';
*/
@injectable()
export class BoardsAutoInstaller implements FrontendApplicationContribution {
@inject(MessageService)
protected readonly messageService: MessageService;
@@ -34,57 +37,92 @@ export class BoardsAutoInstaller implements FrontendApplicationContribution {
protected notifications: Board[] = [];
onStart(): void {
this.boardsServiceClient.onBoardsConfigChanged(this.ensureCoreExists.bind(this));
this.boardsServiceClient.onBoardsConfigChanged(
this.ensureCoreExists.bind(this)
);
this.ensureCoreExists(this.boardsServiceClient.boardsConfig);
}
protected ensureCoreExists(config: BoardsConfig.Config): void {
const { selectedBoard } = config;
if (selectedBoard && !this.notifications.find(board => Board.sameAs(board, selectedBoard))) {
if (
selectedBoard &&
!this.notifications.find((board) =>
Board.sameAs(board, selectedBoard)
)
) {
this.notifications.push(selectedBoard);
this.boardsService.search({}).then(packages => {
this.boardsService.search({}).then((packages) => {
// filter packagesForBoard selecting matches from the cli (installed packages)
// and matches based on the board name
// NOTE: this ensures the Deprecated & new packages are all in the array
// so that we can check if any of the valid packages is already installed
const packagesForBoard = packages.filter(pkg => BoardsPackage.contains(selectedBoard, pkg) || pkg.boards.some(board => board.name === selectedBoard.name));
const packagesForBoard = packages.filter(
(pkg) =>
BoardsPackage.contains(selectedBoard, pkg) ||
pkg.boards.some(
(board) => board.name === selectedBoard.name
)
);
// check if one of the packages for the board is already installed. if so, no hint
if (packagesForBoard.some(({ installedVersion }) => !!installedVersion)) { return; }
if (
packagesForBoard.some(
({ installedVersion }) => !!installedVersion
)
) {
return;
}
// filter the installable (not installed) packages,
// CLI returns the packages already sorted with the deprecated ones at the end of the list
// in order to ensure the new ones are preferred
const candidates = packagesForBoard
.filter(({ installable, installedVersion }) => installable && !installedVersion);
const candidates = packagesForBoard.filter(
({ installable, installedVersion }) =>
installable && !installedVersion
);
const candidate = candidates[0];
if (candidate) {
const version = candidate.availableVersions[0] ? `[v ${candidate.availableVersions[0]}]` : '';
const version = candidate.availableVersions[0]
? `[v ${candidate.availableVersions[0]}]`
: '';
// tslint:disable-next-line:max-line-length
this.messageService.info(`The \`"${candidate.name} ${version}"\` core has to be installed for the currently selected \`"${selectedBoard.name}"\` board. Do you want to install it now?`, 'Install Manually', 'Yes').then(async answer => {
const index = this.notifications.findIndex(board => Board.sameAs(board, selectedBoard));
if (index !== -1) {
this.notifications.splice(index, 1);
}
if (answer === 'Yes') {
await Installable.installWithProgress({
installable: this.boardsService,
item: candidate,
messageService: this.messageService,
responseService: this.responseService,
version: candidate.availableVersions[0]
});
return
}
if (answer) {
this.boardsManagerFrontendContribution.openView({ reveal: true }).then(widget => widget.refresh(candidate.name.toLocaleLowerCase()));
}
});
this.messageService
.info(
`The \`"${candidate.name} ${version}"\` core has to be installed for the currently selected \`"${selectedBoard.name}"\` board. Do you want to install it now?`,
'Install Manually',
'Yes'
)
.then(async (answer) => {
const index = this.notifications.findIndex(
(board) => Board.sameAs(board, selectedBoard)
);
if (index !== -1) {
this.notifications.splice(index, 1);
}
if (answer === 'Yes') {
await Installable.installWithProgress({
installable: this.boardsService,
item: candidate,
messageService: this.messageService,
responseService: this.responseService,
version: candidate.availableVersions[0],
});
return;
}
if (answer) {
this.boardsManagerFrontendContribution
.openView({ reveal: true })
.then((widget) =>
widget.refresh(
candidate.name.toLocaleLowerCase()
)
);
}
});
}
})
});
}
}
}

View File

@@ -9,7 +9,6 @@ import { NotificationCenter } from '../notification-center';
@injectable()
export class BoardsConfigDialogWidget extends ReactWidget {
@inject(BoardsService)
protected readonly boardsService: BoardsService;
@@ -20,7 +19,8 @@ export class BoardsConfigDialogWidget extends ReactWidget {
protected readonly notificationCenter: NotificationCenter;
protected readonly onFilterTextDidChangeEmitter = new Emitter<string>();
protected readonly onBoardConfigChangedEmitter = new Emitter<BoardsConfig.Config>();
protected readonly onBoardConfigChangedEmitter =
new Emitter<BoardsConfig.Config>();
readonly onBoardConfigChanged = this.onBoardConfigChangedEmitter.event;
protected focusNode: HTMLElement | undefined;
@@ -30,7 +30,7 @@ export class BoardsConfigDialogWidget extends ReactWidget {
this.id = 'select-board-dialog';
this.toDispose.pushAll([
this.onBoardConfigChangedEmitter,
this.onFilterTextDidChangeEmitter
this.onFilterTextDidChangeEmitter,
]);
}
@@ -40,21 +40,26 @@ export class BoardsConfigDialogWidget extends ReactWidget {
protected fireConfigChanged = (config: BoardsConfig.Config) => {
this.onBoardConfigChangedEmitter.fire(config);
}
};
protected setFocusNode = (element: HTMLElement | undefined) => {
this.focusNode = element;
}
};
protected render(): React.ReactNode {
return <div className='selectBoardContainer'>
<BoardsConfig
boardsServiceProvider={this.boardsServiceClient}
notificationCenter={this.notificationCenter}
onConfigChange={this.fireConfigChanged}
onFocusNodeSet={this.setFocusNode}
onFilteredTextDidChangeEvent={this.onFilterTextDidChangeEmitter.event} />
</div>;
return (
<div className="selectBoardContainer">
<BoardsConfig
boardsServiceProvider={this.boardsServiceClient}
notificationCenter={this.notificationCenter}
onConfigChange={this.fireConfigChanged}
onFocusNodeSet={this.setFocusNode}
onFilteredTextDidChangeEvent={
this.onFilterTextDidChangeEmitter.event
}
/>
</div>
);
}
protected onActivateRequest(msg: Message): void {
@@ -64,5 +69,4 @@ export class BoardsConfigDialogWidget extends ReactWidget {
}
(this.focusNode || this.node).focus();
}
}

View File

@@ -1,18 +1,21 @@
import { injectable, inject, postConstruct } from 'inversify';
import { Message } from '@phosphor/messaging';
import { AbstractDialog, DialogProps, Widget, DialogError } from '@theia/core/lib/browser';
import {
AbstractDialog,
DialogProps,
Widget,
DialogError,
} from '@theia/core/lib/browser';
import { BoardsConfig } from './boards-config';
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 {
}
export class BoardsConfigDialogProps extends DialogProps {}
@injectable()
export class BoardsConfigDialog extends AbstractDialog<BoardsConfig.Config> {
@inject(BoardsConfigDialogWidget)
protected readonly widget: BoardsConfigDialogWidget;
@@ -24,7 +27,10 @@ export class BoardsConfigDialog extends AbstractDialog<BoardsConfig.Config> {
protected config: BoardsConfig.Config = {};
constructor(@inject(BoardsConfigDialogProps) protected readonly props: BoardsConfigDialogProps) {
constructor(
@inject(BoardsConfigDialogProps)
protected readonly props: BoardsConfigDialogProps
) {
super(props);
this.contentNode.classList.add('select-board-dialog');
@@ -36,16 +42,20 @@ export class BoardsConfigDialog extends AbstractDialog<BoardsConfig.Config> {
@postConstruct()
protected init(): void {
this.toDispose.push(this.boardsServiceClient.onBoardsConfigChanged(config => {
this.config = config;
this.update();
}));
this.toDispose.push(
this.boardsServiceClient.onBoardsConfigChanged((config) => {
this.config = config;
this.update();
})
);
}
/**
* 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> {
async open(
query: string | undefined = undefined
): Promise<BoardsConfig.Config | undefined> {
if (typeof query === 'string') {
this.widget.search(query);
}
@@ -67,7 +77,7 @@ export class BoardsConfigDialog extends AbstractDialog<BoardsConfig.Config> {
for (const paragraph of [
'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.'
'If you only select a Board you will be able just to compile, but not to upload your sketch.',
]) {
const p = document.createElement('div');
p.textContent = paragraph;
@@ -82,10 +92,12 @@ export class BoardsConfigDialog extends AbstractDialog<BoardsConfig.Config> {
Widget.detach(this.widget);
}
Widget.attach(this.widget, this.contentNode);
this.toDisposeOnDetach.push(this.widget.onBoardConfigChanged(config => {
this.config = config;
this.update();
}));
this.toDisposeOnDetach.push(
this.widget.onBoardConfigChanged((config) => {
this.config = config;
this.update();
})
);
super.onAfterAttach(msg);
this.update();
}
@@ -119,5 +131,4 @@ export class BoardsConfigDialog extends AbstractDialog<BoardsConfig.Config> {
get value(): BoardsConfig.Config {
return this.config;
}
}

View File

@@ -3,12 +3,16 @@ 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, BoardWithPackage } from '../../common/protocol/boards-service';
import {
Board,
Port,
AttachedBoardsChangeEvent,
BoardWithPackage,
} from '../../common/protocol/boards-service';
import { NotificationCenter } from '../notification-center';
import { BoardsServiceProvider } from './boards-service-provider';
export namespace BoardsConfig {
export interface Config {
selectedBoard?: Board;
selectedPort?: Port;
@@ -28,18 +32,16 @@ export namespace BoardsConfig {
showAllPorts: boolean;
query: string;
}
}
export abstract class Item<T> extends React.Component<{
item: T,
label: string,
selected: boolean,
onClick: (item: T) => void,
missing?: boolean,
details?: string
item: T;
label: string;
selected: boolean;
onClick: (item: T) => void;
missing?: boolean;
details?: string;
}> {
render(): React.ReactNode {
const { selected, label, missing, details } = this.props;
const classNames = ['item'];
@@ -47,25 +49,36 @@ export abstract class Item<T> extends React.Component<{
classNames.push('selected');
}
if (missing === true) {
classNames.push('missing')
classNames.push('missing');
}
return <div onClick={this.onClick} className={classNames.join(' ')} title={`${label}${!details ? '' : details}`}>
<div className='label'>
{label}
return (
<div
onClick={this.onClick}
className={classNames.join(' ')}
title={`${label}${!details ? '' : details}`}
>
<div className="label">{label}</div>
{!details ? '' : <div className="details">{details}</div>}
{!selected ? (
''
) : (
<div className="selected-icon">
<i className="fa fa-check" />
</div>
)}
</div>
{!details ? '' : <div className='details'>{details}</div>}
{!selected ? '' : <div className='selected-icon'><i className='fa fa-check' /></div>}
</div>;
);
}
protected onClick = () => {
this.props.onClick(this.props.item);
}
};
}
export class BoardsConfig extends React.Component<BoardsConfig.Props, BoardsConfig.State> {
export class BoardsConfig extends React.Component<
BoardsConfig.Props,
BoardsConfig.State
> {
protected toDispose = new DisposableCollection();
constructor(props: BoardsConfig.Props) {
@@ -77,24 +90,51 @@ export class BoardsConfig extends React.Component<BoardsConfig.Props, BoardsConf
knownPorts: [],
showAllPorts: false,
query: '',
...boardsConfig
}
...boardsConfig,
};
}
componentDidMount() {
this.updateBoards();
this.updatePorts(this.props.boardsServiceProvider.availableBoards.map(({ port }) => port).filter(notEmpty));
this.updatePorts(
this.props.boardsServiceProvider.availableBoards
.map(({ port }) => port)
.filter(notEmpty)
);
this.toDispose.pushAll([
this.props.notificationCenter.onAttachedBoardsChanged(event => this.updatePorts(event.newState.ports, AttachedBoardsChangeEvent.diff(event).detached.ports)),
this.props.boardsServiceProvider.onBoardsConfigChanged(({ selectedBoard, selectedPort }) => {
this.setState({ selectedBoard, selectedPort }, () => this.fireConfigChanged());
}),
this.props.notificationCenter.onPlatformInstalled(() => this.updateBoards(this.state.query)),
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.onFilteredTextDidChangeEvent(query => this.setState({ query }, () => this.updateBoards(this.state.query)))
this.props.notificationCenter.onAttachedBoardsChanged((event) =>
this.updatePorts(
event.newState.ports,
AttachedBoardsChangeEvent.diff(event).detached.ports
)
),
this.props.boardsServiceProvider.onBoardsConfigChanged(
({ selectedBoard, selectedPort }) => {
this.setState({ selectedBoard, selectedPort }, () =>
this.fireConfigChanged()
);
}
),
this.props.notificationCenter.onPlatformInstalled(() =>
this.updateBoards(this.state.query)
),
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.onFilteredTextDidChangeEvent((query) =>
this.setState({ query }, () =>
this.updateBoards(this.state.query)
)
),
]);
}
@@ -107,73 +147,96 @@ export class BoardsConfig extends React.Component<BoardsConfig.Props, BoardsConf
this.props.onConfigChange({ selectedBoard, selectedPort });
}
protected updateBoards = (eventOrQuery: React.ChangeEvent<HTMLInputElement> | string = '') => {
const query = typeof eventOrQuery === 'string'
? eventOrQuery
: eventOrQuery.target.value.toLowerCase();
protected updateBoards = (
eventOrQuery: React.ChangeEvent<HTMLInputElement> | string = ''
) => {
const query =
typeof eventOrQuery === 'string'
? eventOrQuery
: eventOrQuery.target.value.toLowerCase();
this.setState({ query });
this.queryBoards({ query }).then(searchResults => this.setState({ searchResults }));
}
this.queryBoards({ query }).then((searchResults) =>
this.setState({ searchResults })
);
};
protected updatePorts = (ports: Port[] = [], removedPorts: Port[] = []) => {
this.queryPorts(Promise.resolve(ports)).then(({ knownPorts }) => {
let { selectedPort } = this.state;
// If the currently selected port is not available anymore, unset the selected port.
if (removedPorts.some(port => Port.equals(port, selectedPort))) {
if (removedPorts.some((port) => Port.equals(port, selectedPort))) {
selectedPort = undefined;
}
this.setState({ knownPorts, selectedPort }, () => this.fireConfigChanged());
this.setState({ knownPorts, selectedPort }, () =>
this.fireConfigChanged()
);
});
}
};
protected queryBoards = (options: { query?: string } = {}): Promise<Array<BoardWithPackage>> => {
protected queryBoards = (
options: { query?: string } = {}
): Promise<Array<BoardWithPackage>> => {
return this.props.boardsServiceProvider.searchBoards(options);
}
};
protected get availablePorts(): MaybePromise<Port[]> {
return this.props.boardsServiceProvider.availableBoards.map(({ port }) => port).filter(notEmpty);
return this.props.boardsServiceProvider.availableBoards
.map(({ port }) => port)
.filter(notEmpty);
}
protected queryPorts = async (availablePorts: MaybePromise<Port[]> = this.availablePorts) => {
protected queryPorts = async (
availablePorts: MaybePromise<Port[]> = this.availablePorts
) => {
const ports = await availablePorts;
return { knownPorts: ports.sort(Port.compare) };
}
};
protected toggleFilterPorts = () => {
this.setState({ showAllPorts: !this.state.showAllPorts });
}
};
protected selectPort = (selectedPort: Port | undefined) => {
this.setState({ selectedPort }, () => this.fireConfigChanged());
}
};
protected selectBoard = (selectedBoard: BoardWithPackage | undefined) => {
this.setState({ selectedBoard }, () => this.fireConfigChanged());
}
};
protected focusNodeSet = (element: HTMLElement | null) => {
this.props.onFocusNodeSet(element || undefined);
}
};
render(): React.ReactNode {
return <div className='body'>
{this.renderContainer('boards', this.renderBoards.bind(this))}
{this.renderContainer('ports', this.renderPorts.bind(this), this.renderPortsFooter.bind(this))}
</div>;
return (
<div className="body">
{this.renderContainer('boards', this.renderBoards.bind(this))}
{this.renderContainer(
'ports',
this.renderPorts.bind(this),
this.renderPortsFooter.bind(this)
)}
</div>
);
}
protected renderContainer(title: string, contentRenderer: () => React.ReactNode, footerRenderer?: () => React.ReactNode): React.ReactNode {
return <div className='container'>
<div className='content'>
<div className='title'>
{title}
</div>
{contentRenderer()}
<div className='footer'>
{(footerRenderer ? footerRenderer() : '')}
protected renderContainer(
title: string,
contentRenderer: () => React.ReactNode,
footerRenderer?: () => React.ReactNode
): React.ReactNode {
return (
<div className="container">
<div className="content">
<div className="title">{title}</div>
{contentRenderer()}
<div className="footer">
{footerRenderer ? footerRenderer() : ''}
</div>
</div>
</div>
</div>;
);
}
protected renderBoards(): React.ReactNode {
@@ -181,98 +244,111 @@ export class BoardsConfig extends React.Component<BoardsConfig.Props, BoardsConf
// 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>();
const toKey = ({ name, packageName, fqbn }: Board.Detailed) => !!fqbn ? `${name}-${packageName}-${fqbn}` : `${name}-${packageName}`;
for (const board of Board.decorateBoards(selectedBoard, searchResults)) {
const toKey = ({ name, packageName, fqbn }: Board.Detailed) =>
!!fqbn
? `${name}-${packageName}-${fqbn}`
: `${name}-${packageName}`;
for (const board of Board.decorateBoards(
selectedBoard,
searchResults
)) {
const key = toKey(board);
if (!distinctBoards.has(key)) {
distinctBoards.set(key, board);
}
}
return <React.Fragment>
<div className='search'>
<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<BoardWithPackage>
key={`${board.name}-${board.packageName}`}
item={board}
label={board.name}
details={board.details}
selected={board.selected}
onClick={this.selectBoard}
missing={board.missing}
/>)}
</div>
</React.Fragment>;
return (
<React.Fragment>
<div className="search">
<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<BoardWithPackage>
key={`${board.name}-${board.packageName}`}
item={board}
label={board.name}
details={board.details}
selected={board.selected}
onClick={this.selectBoard}
missing={board.missing}
/>
))}
</div>
</React.Fragment>
);
}
protected renderPorts(): React.ReactNode {
const filter = this.state.showAllPorts ? () => true : Port.isBoardPort;
const ports = this.state.knownPorts.filter(filter);
return !ports.length ?
(
<div className='loading noselect'>
No ports discovered
</div>
) :
(
<div className='ports list'>
{ports.map(port => <Item<Port>
return !ports.length ? (
<div className="loading noselect">No ports discovered</div>
) : (
<div className="ports list">
{ports.map((port) => (
<Item<Port>
key={Port.toString(port)}
item={port}
label={Port.toString(port)}
selected={Port.equals(this.state.selectedPort, port)}
onClick={this.selectPort}
/>)}
</div>
);
/>
))}
</div>
);
}
protected renderPortsFooter(): React.ReactNode {
return <div className='noselect'>
<label
title='Shows all available ports when enabled'>
<input
type='checkbox'
defaultChecked={this.state.showAllPorts}
onChange={this.toggleFilterPorts}
/>
<span>Show all ports</span>
</label>
</div>;
return (
<div className="noselect">
<label title="Shows all available ports when enabled">
<input
type="checkbox"
defaultChecked={this.state.showAllPorts}
onChange={this.toggleFilterPorts}
/>
<span>Show all ports</span>
</label>
</div>
);
}
}
export namespace BoardsConfig {
export namespace Config {
export function sameAs(config: Config, other: Config | Board): boolean {
const { selectedBoard, selectedPort } = config;
if (Board.is(other)) {
return !!selectedBoard
&& Board.equals(other, selectedBoard)
&& Port.sameAs(selectedPort, other.port);
return (
!!selectedBoard &&
Board.equals(other, selectedBoard) &&
Port.sameAs(selectedPort, other.port)
);
}
return sameAs(config, other);
}
export function equals(left: Config, right: Config): boolean {
return left.selectedBoard === right.selectedBoard
&& left.selectedPort === right.selectedPort;
return (
left.selectedBoard === right.selectedBoard &&
left.selectedPort === right.selectedPort
);
}
export function toString(config: Config, options: { default: string } = { default: '' }): string {
export function toString(
config: Config,
options: { default: string } = { default: '' }
): string {
const { selectedBoard, selectedPort: port } = config;
if (!selectedBoard) {
return options.default;
@@ -281,17 +357,33 @@ export namespace BoardsConfig {
return `${name}${port ? ' at ' + Port.toString(port) : ''}`;
}
export function setConfig(config: Config | undefined, urlToAttachTo: URL): URL {
export function setConfig(
config: Config | undefined,
urlToAttachTo: URL
): URL {
const copy = new URL(urlToAttachTo.toString());
if (!config) {
copy.searchParams.delete('boards-config');
return copy;
}
const selectedBoard = config.selectedBoard ? { name: config.selectedBoard.name, fqbn: config.selectedBoard.fqbn } : undefined;
const selectedPort = config.selectedPort ? { protocol: config.selectedPort.protocol, address: config.selectedPort.address } : undefined;
const selectedBoard = config.selectedBoard
? {
name: config.selectedBoard.name,
fqbn: config.selectedBoard.fqbn,
}
: undefined;
const selectedPort = config.selectedPort
? {
protocol: config.selectedPort.protocol,
address: config.selectedPort.address,
}
: undefined;
const jsonConfig = JSON.stringify({ selectedBoard, selectedPort });
copy.searchParams.set('boards-config', encodeURIComponent(jsonConfig));
copy.searchParams.set(
'boards-config',
encodeURIComponent(jsonConfig)
);
return copy;
}
@@ -306,14 +398,14 @@ export namespace BoardsConfig {
if (typeof candidate === 'object') {
return candidate;
}
console.warn(`Expected candidate to be an object. It was ${typeof candidate}. URL was: ${url}`);
console.warn(
`Expected candidate to be an object. It was ${typeof candidate}. URL was: ${url}`
);
return undefined;
} catch (e) {
console.log(`Could not get board config from URL: ${url}.`, e);
return undefined;
}
}
}
}

View File

@@ -2,7 +2,10 @@ import * as PQueue from 'p-queue';
import { inject, injectable } from 'inversify';
import { CommandRegistry } from '@theia/core/lib/common/command';
import { MenuModelRegistry } from '@theia/core/lib/common/menu';
import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable';
import {
Disposable,
DisposableCollection,
} from '@theia/core/lib/common/disposable';
import { BoardsServiceProvider } from './boards-service-provider';
import { Board, ConfigOption, Programmer } from '../../common/protocol';
import { FrontendApplicationContribution } from '@theia/core/lib/browser';
@@ -12,7 +15,6 @@ import { ArduinoMenus, unregisterSubmenu } from '../menu/arduino-menus';
@injectable()
export class BoardsDataMenuUpdater implements FrontendApplicationContribution {
@inject(CommandRegistry)
protected readonly commandRegistry: CommandRegistry;
@@ -32,63 +34,153 @@ export class BoardsDataMenuUpdater implements FrontendApplicationContribution {
protected readonly toDisposeOnBoardChange = new DisposableCollection();
async onStart(): Promise<void> {
this.updateMenuActions(this.boardsServiceClient.boardsConfig.selectedBoard);
this.boardsDataStore.onChanged(() => this.updateMenuActions(this.boardsServiceClient.boardsConfig.selectedBoard));
this.boardsServiceClient.onBoardsConfigChanged(({ selectedBoard }) => this.updateMenuActions(selectedBoard));
this.updateMenuActions(
this.boardsServiceClient.boardsConfig.selectedBoard
);
this.boardsDataStore.onChanged(() =>
this.updateMenuActions(
this.boardsServiceClient.boardsConfig.selectedBoard
)
);
this.boardsServiceClient.onBoardsConfigChanged(({ selectedBoard }) =>
this.updateMenuActions(selectedBoard)
);
}
protected async updateMenuActions(selectedBoard: Board | undefined): Promise<void> {
protected async updateMenuActions(
selectedBoard: Board | undefined
): Promise<void> {
return this.queue.add(async () => {
this.toDisposeOnBoardChange.dispose();
this.mainMenuManager.update();
if (selectedBoard) {
const { fqbn } = selectedBoard;
if (fqbn) {
const { configOptions, programmers, selectedProgrammer } = await this.boardsDataStore.getData(fqbn);
const { configOptions, programmers, selectedProgrammer } =
await this.boardsDataStore.getData(fqbn);
if (configOptions.length) {
const boardsConfigMenuPath = [...ArduinoMenus.TOOLS__BOARD_SETTINGS_GROUP, 'z01_boardsConfig']; // `z_` is for ordering.
for (const { label, option, values } of configOptions.sort(ConfigOption.LABEL_COMPARATOR)) {
const menuPath = [...boardsConfigMenuPath, `${option}`];
const commands = new Map<string, Disposable & { label: string }>()
const boardsConfigMenuPath = [
...ArduinoMenus.TOOLS__BOARD_SETTINGS_GROUP,
'z01_boardsConfig',
]; // `z_` is for ordering.
for (const {
label,
option,
values,
} of configOptions.sort(
ConfigOption.LABEL_COMPARATOR
)) {
const menuPath = [
...boardsConfigMenuPath,
`${option}`,
];
const commands = new Map<
string,
Disposable & { label: string }
>();
for (const value of values) {
const id = `${fqbn}-${option}--${value.value}`;
const command = { id };
const selectedValue = value.value;
const handler = {
execute: () => this.boardsDataStore.selectConfigOption({ fqbn, option, selectedValue }),
isToggled: () => value.selected
execute: () =>
this.boardsDataStore.selectConfigOption(
{ fqbn, option, selectedValue }
),
isToggled: () => value.selected,
};
commands.set(id, Object.assign(this.commandRegistry.registerCommand(command, handler), { label: value.label }));
commands.set(
id,
Object.assign(
this.commandRegistry.registerCommand(
command,
handler
),
{ label: value.label }
)
);
}
this.menuRegistry.registerSubmenu(menuPath, label);
this.toDisposeOnBoardChange.pushAll([
...commands.values(),
Disposable.create(() => unregisterSubmenu(menuPath, this.menuRegistry)),
...Array.from(commands.keys()).map((commandId, i) => {
const { label } = commands.get(commandId)!;
this.menuRegistry.registerMenuAction(menuPath, { commandId, order: `${i}`, label });
return Disposable.create(() => this.menuRegistry.unregisterMenuAction(commandId));
})
Disposable.create(() =>
unregisterSubmenu(
menuPath,
this.menuRegistry
)
),
...Array.from(commands.keys()).map(
(commandId, i) => {
const { label } =
commands.get(commandId)!;
this.menuRegistry.registerMenuAction(
menuPath,
{ commandId, order: `${i}`, label }
);
return Disposable.create(() =>
this.menuRegistry.unregisterMenuAction(
commandId
)
);
}
),
]);
}
}
if (programmers.length) {
const programmersMenuPath = [...ArduinoMenus.TOOLS__BOARD_SETTINGS_GROUP, 'z02_programmers'];
const label = selectedProgrammer ? `Programmer: "${selectedProgrammer.name}"` : 'Programmer'
this.menuRegistry.registerSubmenu(programmersMenuPath, label);
this.toDisposeOnBoardChange.push(Disposable.create(() => unregisterSubmenu(programmersMenuPath, this.menuRegistry)));
const programmersMenuPath = [
...ArduinoMenus.TOOLS__BOARD_SETTINGS_GROUP,
'z02_programmers',
];
const label = selectedProgrammer
? `Programmer: "${selectedProgrammer.name}"`
: 'Programmer';
this.menuRegistry.registerSubmenu(
programmersMenuPath,
label
);
this.toDisposeOnBoardChange.push(
Disposable.create(() =>
unregisterSubmenu(
programmersMenuPath,
this.menuRegistry
)
)
);
for (const programmer of programmers) {
const { id, name } = programmer;
const command = { id: `${fqbn}-programmer--${id}` };
const handler = {
execute: () => this.boardsDataStore.selectProgrammer({ fqbn, selectedProgrammer: programmer }),
isToggled: () => Programmer.equals(programmer, selectedProgrammer)
execute: () =>
this.boardsDataStore.selectProgrammer({
fqbn,
selectedProgrammer: programmer,
}),
isToggled: () =>
Programmer.equals(
programmer,
selectedProgrammer
),
};
this.menuRegistry.registerMenuAction(programmersMenuPath, { commandId: command.id, label: name });
this.commandRegistry.registerCommand(command, handler);
this.menuRegistry.registerMenuAction(
programmersMenuPath,
{ commandId: command.id, label: name }
);
this.commandRegistry.registerCommand(
command,
handler
);
this.toDisposeOnBoardChange.pushAll([
Disposable.create(() => this.commandRegistry.unregisterCommand(command)),
Disposable.create(() => this.menuRegistry.unregisterMenuAction(command.id))
Disposable.create(() =>
this.commandRegistry.unregisterCommand(
command
)
),
Disposable.create(() =>
this.menuRegistry.unregisterMenuAction(
command.id
)
),
]);
}
}
@@ -97,5 +189,4 @@ export class BoardsDataMenuUpdater implements FrontendApplicationContribution {
}
});
}
}

View File

@@ -3,14 +3,22 @@ import { ILogger } from '@theia/core/lib/common/logger';
import { deepClone } from '@theia/core/lib/common/objects';
import { MaybePromise } from '@theia/core/lib/common/types';
import { Event, Emitter } from '@theia/core/lib/common/event';
import { FrontendApplicationContribution, LocalStorageService } from '@theia/core/lib/browser';
import {
FrontendApplicationContribution,
LocalStorageService,
} from '@theia/core/lib/browser';
import { notEmpty } from '../../common/utils';
import { BoardsService, ConfigOption, Installable, BoardDetails, Programmer } from '../../common/protocol';
import {
BoardsService,
ConfigOption,
Installable,
BoardDetails,
Programmer,
} from '../../common/protocol';
import { NotificationCenter } from '../notification-center';
@injectable()
export class BoardsDataStore implements FrontendApplicationContribution {
@inject(ILogger)
@named('store')
protected readonly logger: ILogger;
@@ -33,9 +41,14 @@ export class BoardsDataStore implements FrontendApplicationContribution {
return;
}
let shouldFireChanged = false;
for (const fqbn of item.boards.map(({ fqbn }) => fqbn).filter(notEmpty).filter(fqbn => !!fqbn)) {
for (const fqbn of item.boards
.map(({ fqbn }) => fqbn)
.filter(notEmpty)
.filter((fqbn) => !!fqbn)) {
const key = this.getStorageKey(fqbn, version);
let data = await this.storageService.getData<ConfigOption[] | undefined>(key);
let data = await this.storageService.getData<
ConfigOption[] | undefined
>(key);
if (!data || !data.length) {
const details = await this.getBoardDetailsSafe(fqbn);
if (details) {
@@ -59,20 +72,27 @@ export class BoardsDataStore implements FrontendApplicationContribution {
async appendConfigToFqbn(
fqbn: string | undefined,
boardsPackageVersion: MaybePromise<Installable.Version | undefined> = this.getBoardsPackageVersion(fqbn)): Promise<string | undefined> {
boardsPackageVersion: MaybePromise<
Installable.Version | undefined
> = this.getBoardsPackageVersion(fqbn)
): Promise<string | undefined> {
if (!fqbn) {
return undefined;
}
const { configOptions } = await this.getData(fqbn, boardsPackageVersion);
const { configOptions } = await this.getData(
fqbn,
boardsPackageVersion
);
return ConfigOption.decorate(fqbn, configOptions);
}
async getData(
fqbn: string | undefined,
boardsPackageVersion: MaybePromise<Installable.Version | undefined> = this.getBoardsPackageVersion(fqbn)): Promise<BoardsDataStore.Data> {
boardsPackageVersion: MaybePromise<
Installable.Version | undefined
> = this.getBoardsPackageVersion(fqbn)
): Promise<BoardsDataStore.Data> {
if (!fqbn) {
return BoardsDataStore.Data.EMPTY;
}
@@ -82,7 +102,9 @@ export class BoardsDataStore implements FrontendApplicationContribution {
return BoardsDataStore.Data.EMPTY;
}
const key = this.getStorageKey(fqbn, version);
let data = await this.storageService.getData<BoardsDataStore.Data | undefined>(key, undefined);
let data = await this.storageService.getData<
BoardsDataStore.Data | undefined
>(key, undefined);
if (BoardsDataStore.Data.is(data)) {
return data;
}
@@ -92,18 +114,28 @@ export class BoardsDataStore implements FrontendApplicationContribution {
return BoardsDataStore.Data.EMPTY;
}
data = { configOptions: boardDetails.configOptions, programmers: boardDetails.programmers };
data = {
configOptions: boardDetails.configOptions,
programmers: boardDetails.programmers,
};
await this.storageService.setData(key, data);
return data;
}
async selectProgrammer(
{ fqbn, selectedProgrammer }: { fqbn: string, selectedProgrammer: Programmer },
boardsPackageVersion: MaybePromise<Installable.Version | undefined> = this.getBoardsPackageVersion(fqbn)): Promise<boolean> {
{
fqbn,
selectedProgrammer,
}: { fqbn: string; selectedProgrammer: Programmer },
boardsPackageVersion: MaybePromise<
Installable.Version | undefined
> = this.getBoardsPackageVersion(fqbn)
): Promise<boolean> {
const data = deepClone(await this.getData(fqbn, boardsPackageVersion));
const { programmers } = data;
if (!programmers.find(p => Programmer.equals(selectedProgrammer, p))) {
if (
!programmers.find((p) => Programmer.equals(selectedProgrammer, p))
) {
return false;
}
@@ -112,18 +144,28 @@ export class BoardsDataStore implements FrontendApplicationContribution {
return false;
}
await this.setData({ fqbn, data: { ...data, selectedProgrammer }, version });
await this.setData({
fqbn,
data: { ...data, selectedProgrammer },
version,
});
this.fireChanged();
return true;
}
async selectConfigOption(
{ fqbn, option, selectedValue }: { fqbn: string, option: string, selectedValue: string },
boardsPackageVersion: MaybePromise<Installable.Version | undefined> = this.getBoardsPackageVersion(fqbn)): Promise<boolean> {
{
fqbn,
option,
selectedValue,
}: { fqbn: string; option: string; selectedValue: string },
boardsPackageVersion: MaybePromise<
Installable.Version | undefined
> = this.getBoardsPackageVersion(fqbn)
): Promise<boolean> {
const data = deepClone(await this.getData(fqbn, boardsPackageVersion));
const { configOptions } = data;
const configOption = configOptions.find(c => c.option === option);
const configOption = configOptions.find((c) => c.option === option);
if (!configOption) {
return false;
}
@@ -149,26 +191,46 @@ export class BoardsDataStore implements FrontendApplicationContribution {
return true;
}
protected async setData(
{ fqbn, data, version }: { fqbn: string, data: BoardsDataStore.Data, version: Installable.Version }): Promise<void> {
protected async setData({
fqbn,
data,
version,
}: {
fqbn: string;
data: BoardsDataStore.Data;
version: Installable.Version;
}): Promise<void> {
const key = this.getStorageKey(fqbn, version);
return this.storageService.setData(key, data);
}
protected getStorageKey(fqbn: string, version: Installable.Version): string {
protected getStorageKey(
fqbn: string,
version: Installable.Version
): string {
return `.arduinoIDE-configOptions-${version}-${fqbn}`;
}
protected async getBoardDetailsSafe(fqbn: string): Promise<BoardDetails | undefined> {
protected async getBoardDetailsSafe(
fqbn: string
): Promise<BoardDetails | undefined> {
try {
const details = this.boardsService.getBoardDetails({ fqbn });
return details;
} catch (err) {
if (err instanceof Error && err.message.includes('loading board data') && err.message.includes('is not installed')) {
this.logger.warn(`The boards package is not installed for board with FQBN: ${fqbn}`);
if (
err instanceof Error &&
err.message.includes('loading board data') &&
err.message.includes('is not installed')
) {
this.logger.warn(
`The boards package is not installed for board with FQBN: ${fqbn}`
);
} else {
this.logger.error(`An unexpected error occurred while retrieving the board details for ${fqbn}.`, err);
this.logger.error(
`An unexpected error occurred while retrieving the board details for ${fqbn}.`,
err
);
}
return undefined;
}
@@ -178,17 +240,20 @@ export class BoardsDataStore implements FrontendApplicationContribution {
this.onChangedEmitter.fire();
}
protected async getBoardsPackageVersion(fqbn: string | undefined): Promise<Installable.Version | undefined> {
protected async getBoardsPackageVersion(
fqbn: string | undefined
): Promise<Installable.Version | undefined> {
if (!fqbn) {
return undefined;
}
const boardsPackage = await this.boardsService.getContainerBoardPackage({ fqbn });
const boardsPackage = await this.boardsService.getContainerBoardPackage(
{ fqbn }
);
if (!boardsPackage) {
return undefined;
}
return boardsPackage.installedVersion;
}
}
export namespace BoardsDataStore {
@@ -200,12 +265,16 @@ export namespace BoardsDataStore {
export namespace Data {
export const EMPTY: Data = {
configOptions: [],
programmers: []
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'])
return (
!!arg &&
'configOptions' in arg &&
Array.isArray(arg['configOptions']) &&
'programmers' in arg &&
Array.isArray(arg['programmers'])
);
}
}
}

View File

@@ -1,18 +1,21 @@
import { inject, injectable, postConstruct } from 'inversify';
import { BoardsPackage, BoardsService } from '../../common/protocol/boards-service';
import {
BoardsPackage,
BoardsService,
} from '../../common/protocol/boards-service';
import { ListWidget } from '../widgets/component-list/list-widget';
import { ListItemRenderer } from '../widgets/component-list/list-item-renderer';
@injectable()
export class BoardsListWidget extends ListWidget<BoardsPackage> {
static WIDGET_ID = 'boards-list-widget';
static WIDGET_LABEL = 'Boards Manager';
constructor(
@inject(BoardsService) protected service: BoardsService,
@inject(ListItemRenderer) protected itemRenderer: ListItemRenderer<BoardsPackage>) {
@inject(ListItemRenderer)
protected itemRenderer: ListItemRenderer<BoardsPackage>
) {
super({
id: BoardsListWidget.WIDGET_ID,
label: BoardsListWidget.WIDGET_LABEL,
@@ -21,7 +24,7 @@ export class BoardsListWidget extends ListWidget<BoardsPackage> {
installable: service,
itemLabel: (item: BoardsPackage) => item.name,
itemDeprecated: (item: BoardsPackage) => item.deprecated,
itemRenderer
itemRenderer,
});
}
@@ -29,19 +32,42 @@ export class BoardsListWidget extends ListWidget<BoardsPackage> {
protected init(): void {
super.init();
this.toDispose.pushAll([
this.notificationCenter.onPlatformInstalled(() => this.refresh(undefined)),
this.notificationCenter.onPlatformUninstalled(() => this.refresh(undefined)),
this.notificationCenter.onPlatformInstalled(() =>
this.refresh(undefined)
),
this.notificationCenter.onPlatformUninstalled(() =>
this.refresh(undefined)
),
]);
}
protected async install({ item, progressId, version }: { item: BoardsPackage, progressId: string, version: string; }): Promise<void> {
protected async install({
item,
progressId,
version,
}: {
item: BoardsPackage;
progressId: string;
version: string;
}): Promise<void> {
await super.install({ item, progressId, version });
this.messageService.info(`Successfully installed platform ${item.name}:${version}`, { timeout: 3000 });
this.messageService.info(
`Successfully installed platform ${item.name}:${version}`,
{ timeout: 3000 }
);
}
protected async uninstall({ item, progressId }: { item: BoardsPackage, progressId: string }): Promise<void> {
protected async uninstall({
item,
progressId,
}: {
item: BoardsPackage;
progressId: string;
}): Promise<void> {
await super.uninstall({ item, progressId });
this.messageService.info(`Successfully uninstalled platform ${item.name}:${item.installedVersion}`, { timeout: 3000 });
this.messageService.info(
`Successfully uninstalled platform ${item.name}:${item.installedVersion}`,
{ timeout: 3000 }
);
}
}

View File

@@ -11,7 +11,7 @@ import {
BoardsService,
BoardsPackage,
AttachedBoardsChangeEvent,
BoardWithPackage
BoardWithPackage,
} from '../../common/protocol';
import { BoardsConfig } from './boards-config';
import { naturalCompare } from '../../common/utils';
@@ -21,14 +21,12 @@ import { StorageWrapper } from '../storage-wrapper';
@injectable()
export class BoardsServiceProvider implements FrontendApplicationContribution {
@inject(ILogger)
protected logger: ILogger;
@inject(MessageService)
protected messageService: MessageService;
@inject(BoardsService)
protected boardsService: BoardsService;
@@ -38,8 +36,11 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
@inject(NotificationCenter)
protected notificationCenter: NotificationCenter;
protected readonly onBoardsConfigChangedEmitter = new Emitter<BoardsConfig.Config>();
protected readonly onAvailableBoardsChangedEmitter = new Emitter<AvailableBoard[]>();
protected readonly onBoardsConfigChangedEmitter =
new Emitter<BoardsConfig.Config>();
protected readonly onAvailableBoardsChangedEmitter = new Emitter<
AvailableBoard[]
>();
/**
* Used for the auto-reconnecting. Sometimes, the attached board gets disconnected after uploading something to it.
@@ -48,7 +49,9 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
* We have to listen on such changes and auto-reconnect the same board on another port.
* See: https://arduino.slack.com/archives/CJJHJCJSJ/p1568645417013000?thread_ts=1568640504.009400&cid=CJJHJCJSJ
*/
protected latestValidBoardsConfig: RecursiveRequired<BoardsConfig.Config> | undefined = undefined;
protected latestValidBoardsConfig:
| RecursiveRequired<BoardsConfig.Config>
| undefined = undefined;
protected latestBoardsConfig: BoardsConfig.Config | undefined = undefined;
protected _boardsConfig: BoardsConfig.Config = {};
protected _attachedBoards: Board[] = []; // This does not contain the `Unknown` boards. They're visible from the available ports only.
@@ -63,17 +66,24 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
* This even also emitted when the board package for the currently selected board was uninstalled.
*/
readonly onBoardsConfigChanged = this.onBoardsConfigChangedEmitter.event;
readonly onAvailableBoardsChanged = this.onAvailableBoardsChangedEmitter.event;
readonly onAvailableBoardsChanged =
this.onAvailableBoardsChangedEmitter.event;
onStart(): void {
this.notificationCenter.onAttachedBoardsChanged(this.notifyAttachedBoardsChanged.bind(this));
this.notificationCenter.onPlatformInstalled(this.notifyPlatformInstalled.bind(this));
this.notificationCenter.onPlatformUninstalled(this.notifyPlatformUninstalled.bind(this));
this.notificationCenter.onAttachedBoardsChanged(
this.notifyAttachedBoardsChanged.bind(this)
);
this.notificationCenter.onPlatformInstalled(
this.notifyPlatformInstalled.bind(this)
);
this.notificationCenter.onPlatformUninstalled(
this.notifyPlatformUninstalled.bind(this)
);
Promise.all([
this.boardsService.getAttachedBoards(),
this.boardsService.getAvailablePorts(),
this.loadState()
this.loadState(),
]).then(([attachedBoards, availablePorts]) => {
this._attachedBoards = attachedBoards;
this._availablePorts = availablePorts;
@@ -81,7 +91,9 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
});
}
protected notifyAttachedBoardsChanged(event: AttachedBoardsChangeEvent): void {
protected notifyAttachedBoardsChanged(
event: AttachedBoardsChangeEvent
): void {
if (!AttachedBoardsChangeEvent.isEmpty(event)) {
this.logger.info('Attached boards and available ports changed:');
this.logger.info(AttachedBoardsChangeEvent.toString(event));
@@ -97,12 +109,20 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
const { selectedBoard } = this.boardsConfig;
const { installedVersion, id } = event.item;
if (selectedBoard) {
const installedBoard = event.item.boards.find(({ name }) => name === selectedBoard.name);
if (installedBoard && (!selectedBoard.fqbn || selectedBoard.fqbn === installedBoard.fqbn)) {
this.logger.info(`Board package ${id}[${installedVersion}] was installed. Updating the FQBN of the currently selected ${selectedBoard.name} board. [FQBN: ${installedBoard.fqbn}]`);
const installedBoard = event.item.boards.find(
({ name }) => name === selectedBoard.name
);
if (
installedBoard &&
(!selectedBoard.fqbn ||
selectedBoard.fqbn === installedBoard.fqbn)
) {
this.logger.info(
`Board package ${id}[${installedVersion}] was installed. Updating the FQBN of the currently selected ${selectedBoard.name} board. [FQBN: ${installedBoard.fqbn}]`
);
this.boardsConfig = {
...this.boardsConfig,
selectedBoard: installedBoard
selectedBoard: installedBoard,
};
return;
}
@@ -110,13 +130,26 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
// 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 = {}
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
@@ -129,8 +162,13 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
this.logger.info('Boards package uninstalled: ', JSON.stringify(event));
const { selectedBoard } = this.boardsConfig;
if (selectedBoard && selectedBoard.fqbn) {
const uninstalledBoard = event.item.boards.find(({ name }) => name === selectedBoard.name);
if (uninstalledBoard && uninstalledBoard.fqbn === selectedBoard.fqbn) {
const uninstalledBoard = event.item.boards.find(
({ name }) => name === selectedBoard.name
);
if (
uninstalledBoard &&
uninstalledBoard.fqbn === selectedBoard.fqbn
) {
// We should not unset the FQBN, if the selected board is an attached, recognized board.
// Attach Uno and install AVR, select Uno. Uninstall the AVR core while Uno is selected. We do not want to discard the FQBN of the Uno board.
// Dev note: We cannot assume the `selectedBoard` is a type of `AvailableBoard`.
@@ -138,43 +176,68 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
// it is just a FQBN, so we need to find the `selected` board among the `AvailableBoards`
const selectedAvailableBoard = AvailableBoard.is(selectedBoard)
? selectedBoard
: this._availableBoards.find(availableBoard => Board.sameAs(availableBoard, selectedBoard));
if (selectedAvailableBoard && selectedAvailableBoard.selected && selectedAvailableBoard.state === AvailableBoard.State.recognized) {
: this._availableBoards.find((availableBoard) =>
Board.sameAs(availableBoard, selectedBoard)
);
if (
selectedAvailableBoard &&
selectedAvailableBoard.selected &&
selectedAvailableBoard.state ===
AvailableBoard.State.recognized
) {
return;
}
this.logger.info(`Board package ${event.item.id} was uninstalled. Discarding the FQBN of the currently selected ${selectedBoard.name} board.`);
this.logger.info(
`Board package ${event.item.id} was uninstalled. Discarding the FQBN of the currently selected ${selectedBoard.name} board.`
);
const selectedBoardWithoutFqbn = {
name: selectedBoard.name
name: selectedBoard.name,
// No FQBN
};
this.boardsConfig = {
...this.boardsConfig,
selectedBoard: selectedBoardWithoutFqbn
selectedBoard: selectedBoardWithoutFqbn,
};
}
}
}
protected async tryReconnect(): Promise<boolean> {
if (this.latestValidBoardsConfig && !this.canUploadTo(this.boardsConfig)) {
for (const board of this.availableBoards.filter(({ state }) => state !== AvailableBoard.State.incomplete)) {
if (this.latestValidBoardsConfig.selectedBoard.fqbn === board.fqbn
&& this.latestValidBoardsConfig.selectedBoard.name === board.name
&& Port.sameAs(this.latestValidBoardsConfig.selectedPort, board.port)) {
if (
this.latestValidBoardsConfig &&
!this.canUploadTo(this.boardsConfig)
) {
for (const board of this.availableBoards.filter(
({ state }) => state !== AvailableBoard.State.incomplete
)) {
if (
this.latestValidBoardsConfig.selectedBoard.fqbn ===
board.fqbn &&
this.latestValidBoardsConfig.selectedBoard.name ===
board.name &&
Port.sameAs(
this.latestValidBoardsConfig.selectedPort,
board.port
)
) {
this.boardsConfig = this.latestValidBoardsConfig;
return true;
}
}
// If we could not find an exact match, we compare the board FQBN-name pairs and ignore the port, as it might have changed.
// See documentation on `latestValidBoardsConfig`.
for (const board of this.availableBoards.filter(({ state }) => state !== AvailableBoard.State.incomplete)) {
if (this.latestValidBoardsConfig.selectedBoard.fqbn === board.fqbn
&& this.latestValidBoardsConfig.selectedBoard.name === board.name) {
for (const board of this.availableBoards.filter(
({ state }) => state !== AvailableBoard.State.incomplete
)) {
if (
this.latestValidBoardsConfig.selectedBoard.fqbn ===
board.fqbn &&
this.latestValidBoardsConfig.selectedBoard.name ===
board.name
) {
this.boardsConfig = {
...this.latestValidBoardsConfig,
selectedPort: board.port
selectedPort: board.port,
};
return true;
}
@@ -185,7 +248,15 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
set boardsConfig(config: BoardsConfig.Config) {
this.doSetBoardsConfig(config);
this.saveState().finally(() => this.reconcileAvailableBoards().finally(() => this.onBoardsConfigChangedEmitter.fire(this._boardsConfig)));
this.saveState().finally(() =>
this.reconcileAvailableBoards().finally(() =>
this.onBoardsConfigChangedEmitter.fire(this._boardsConfig)
)
);
}
get boardsConfig(): BoardsConfig.Config {
return this._boardsConfig;
}
protected doSetBoardsConfig(config: BoardsConfig.Config): void {
@@ -197,29 +268,33 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
}
}
async searchBoards({ query, cores }: { query?: string, cores?: string[] }): Promise<BoardWithPackage[]> {
async searchBoards({
query,
cores,
}: {
query?: string;
cores?: string[];
}): Promise<BoardWithPackage[]> {
const boards = await this.boardsService.searchBoards({ query });
return boards;
}
get boardsConfig(): BoardsConfig.Config {
return this._boardsConfig;
}
/**
* `true` if the `config.selectedBoard` is defined; hence can compile against the board. Otherwise, `false`.
*/
canVerify(
config: BoardsConfig.Config | undefined = this.boardsConfig,
options: { silent: boolean } = { silent: true }): config is BoardsConfig.Config & { selectedBoard: Board } {
options: { silent: boolean } = { silent: true }
): config is BoardsConfig.Config & { selectedBoard: Board } {
if (!config) {
return false;
}
if (!config.selectedBoard) {
if (!options.silent) {
this.messageService.warn('No boards selected.', { timeout: 3000 });
this.messageService.warn('No boards selected.', {
timeout: 3000,
});
}
return false;
}
@@ -232,8 +307,8 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
*/
canUploadTo(
config: BoardsConfig.Config | undefined = this.boardsConfig,
options: { silent: boolean } = { silent: true }): config is RecursiveRequired<BoardsConfig.Config> {
options: { silent: boolean } = { silent: true }
): config is RecursiveRequired<BoardsConfig.Config> {
if (!this.canVerify(config, options)) {
return false;
}
@@ -241,14 +316,20 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
const { name } = config.selectedBoard;
if (!config.selectedPort) {
if (!options.silent) {
this.messageService.warn(`No ports selected for board: '${name}'.`, { timeout: 3000 });
this.messageService.warn(
`No ports selected for board: '${name}'.`,
{ timeout: 3000 }
);
}
return false;
}
if (!config.selectedBoard.fqbn) {
if (!options.silent) {
this.messageService.warn(`The FQBN is not available for the selected board ${name}. Do you have the corresponding core installed?`, { timeout: 3000 });
this.messageService.warn(
`The FQBN is not available for the selected board ${name}. Do you have the corresponding core installed?`,
{ timeout: 3000 }
);
}
return false;
}
@@ -260,25 +341,46 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
return this._availableBoards;
}
async waitUntilAvailable(what: Board & { port: Port }, timeout?: number): Promise<void> {
const find = (needle: Board & { port: Port }, haystack: AvailableBoard[]) =>
haystack.find(board => Board.equals(needle, board) && Port.equals(needle.port, board.port));
const timeoutTask = !!timeout && timeout > 0
? new Promise<void>((_, reject) => setTimeout(() => reject(new Error(`Timeout after ${timeout} ms.`)), timeout))
: new Promise<void>(() => { /* never */ });
const waitUntilTask = new Promise<void>(resolve => {
async waitUntilAvailable(
what: Board & { port: Port },
timeout?: number
): Promise<void> {
const find = (
needle: Board & { port: Port },
haystack: AvailableBoard[]
) =>
haystack.find(
(board) =>
Board.equals(needle, board) &&
Port.equals(needle.port, board.port)
);
const timeoutTask =
!!timeout && timeout > 0
? new Promise<void>((_, reject) =>
setTimeout(
() =>
reject(new Error(`Timeout after ${timeout} ms.`)),
timeout
)
)
: new Promise<void>(() => {
/* never */
});
const waitUntilTask = new Promise<void>((resolve) => {
let candidate = find(what, this.availableBoards);
if (candidate) {
resolve();
return;
}
const disposable = this.onAvailableBoardsChanged(availableBoards => {
candidate = find(what, availableBoards);
if (candidate) {
disposable.dispose();
resolve();
const disposable = this.onAvailableBoardsChanged(
(availableBoards) => {
candidate = find(what, availableBoards);
if (candidate) {
disposable.dispose();
resolve();
}
}
});
);
});
return await Promise.race([waitUntilTask, timeoutTask]);
}
@@ -287,54 +389,90 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
const attachedBoards = this._attachedBoards;
const availablePorts = this._availablePorts;
// Unset the port on the user's config, if it is not available anymore.
if (this.boardsConfig.selectedPort && !availablePorts.some(port => Port.sameAs(port, this.boardsConfig.selectedPort))) {
this.doSetBoardsConfig({ selectedBoard: this.boardsConfig.selectedBoard, selectedPort: undefined });
if (
this.boardsConfig.selectedPort &&
!availablePorts.some((port) =>
Port.sameAs(port, this.boardsConfig.selectedPort)
)
) {
this.doSetBoardsConfig({
selectedBoard: this.boardsConfig.selectedBoard,
selectedPort: undefined,
});
this.onBoardsConfigChangedEmitter.fire(this._boardsConfig);
}
const boardsConfig = this.boardsConfig;
const currentAvailableBoards = this._availableBoards;
const availableBoards: AvailableBoard[] = [];
const availableBoardPorts = availablePorts.filter(Port.isBoardPort);
const attachedSerialBoards = attachedBoards.filter(({ port }) => !!port);
const attachedSerialBoards = attachedBoards.filter(
({ port }) => !!port
);
for (const boardPort of availableBoardPorts) {
let state = AvailableBoard.State.incomplete; // Initial pessimism.
let board = attachedSerialBoards.find(({ port }) => Port.sameAs(boardPort, port));
let board = attachedSerialBoards.find(({ port }) =>
Port.sameAs(boardPort, port)
);
if (board) {
state = AvailableBoard.State.recognized;
} else {
// If the selected board is not recognized because it is a 3rd party board: https://github.com/arduino/arduino-cli/issues/623
// We still want to show it without the red X in the boards toolbar: https://github.com/arduino/arduino-pro-ide/issues/198#issuecomment-599355836
const lastSelectedBoard = await this.getLastSelectedBoardOnPort(boardPort);
const lastSelectedBoard = await this.getLastSelectedBoardOnPort(
boardPort
);
if (lastSelectedBoard) {
board = {
...lastSelectedBoard,
port: boardPort
port: boardPort,
};
state = AvailableBoard.State.guessed;
}
}
if (!board) {
availableBoards.push({ name: 'Unknown', port: boardPort, state });
availableBoards.push({
name: 'Unknown',
port: boardPort,
state,
});
} else {
const selected = BoardsConfig.Config.sameAs(boardsConfig, board);
availableBoards.push({ ...board, state, selected, port: boardPort });
const selected = BoardsConfig.Config.sameAs(
boardsConfig,
board
);
availableBoards.push({
...board,
state,
selected,
port: boardPort,
});
}
}
if (boardsConfig.selectedBoard && !availableBoards.some(({ selected }) => selected)) {
if (
boardsConfig.selectedBoard &&
!availableBoards.some(({ selected }) => selected)
) {
availableBoards.push({
...boardsConfig.selectedBoard,
port: boardsConfig.selectedPort,
selected: true,
state: AvailableBoard.State.incomplete
state: AvailableBoard.State.incomplete,
});
}
const sortedAvailableBoards = availableBoards.sort(AvailableBoard.compare);
let hasChanged = sortedAvailableBoards.length !== currentAvailableBoards.length;
const sortedAvailableBoards = availableBoards.sort(
AvailableBoard.compare
);
let hasChanged =
sortedAvailableBoards.length !== currentAvailableBoards.length;
for (let i = 0; !hasChanged && i < sortedAvailableBoards.length; i++) {
hasChanged = AvailableBoard.compare(sortedAvailableBoards[i], currentAvailableBoards[i]) !== 0;
hasChanged =
AvailableBoard.compare(
sortedAvailableBoards[i],
currentAvailableBoards[i]
) !== 0;
}
if (hasChanged) {
this._availableBoards = sortedAvailableBoards;
@@ -342,7 +480,9 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
}
}
protected async getLastSelectedBoardOnPort(port: Port | string | undefined): Promise<Board | undefined> {
protected async getLastSelectedBoardOnPort(
port: Port | string | undefined
): Promise<Board | undefined> {
if (!port) {
return undefined;
}
@@ -361,18 +501,25 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
await this.setData(key, selectedBoard);
}
await Promise.all([
this.setData('latest-valid-boards-config', this.latestValidBoardsConfig),
this.setData('latest-boards-config', this.latestBoardsConfig)
this.setData(
'latest-valid-boards-config',
this.latestValidBoardsConfig
),
this.setData('latest-boards-config', this.latestBoardsConfig),
]);
}
protected getLastSelectedBoardOnPortKey(port: Port | string): string {
// TODO: we lose the port's `protocol` info (`serial`, `network`, etc.) here if the `port` is a `string`.
return `last-selected-board-on-port:${typeof port === 'string' ? port : Port.toString(port)}`;
return `last-selected-board-on-port:${
typeof port === 'string' ? port : Port.toString(port)
}`;
}
protected async loadState(): Promise<void> {
const storedLatestValidBoardsConfig = await this.getData<RecursiveRequired<BoardsConfig.Config>>('latest-valid-boards-config');
const storedLatestValidBoardsConfig = await this.getData<
RecursiveRequired<BoardsConfig.Config>
>('latest-valid-boards-config');
if (storedLatestValidBoardsConfig) {
this.latestValidBoardsConfig = storedLatestValidBoardsConfig;
if (this.canUploadTo(this.latestValidBoardsConfig)) {
@@ -380,10 +527,14 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
}
} else {
// If we could not restore the latest valid config, try to restore something, the board at least.
let storedLatestBoardsConfig = await this.getData<BoardsConfig.Config | undefined>('latest-boards-config');
let storedLatestBoardsConfig = await this.getData<
BoardsConfig.Config | undefined
>('latest-boards-config');
// Try to get from the URL if it was not persisted.
if (!storedLatestBoardsConfig) {
storedLatestBoardsConfig = BoardsConfig.Config.getConfig(new URL(window.location.href));
storedLatestBoardsConfig = BoardsConfig.Config.getConfig(
new URL(window.location.href)
);
}
if (storedLatestBoardsConfig) {
this.latestBoardsConfig = storedLatestBoardsConfig;
@@ -393,11 +544,18 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
}
private setData<T>(key: string, value: T): Promise<void> {
return this.commandService.executeCommand(StorageWrapper.Commands.SET_DATA.id, key, value);
return this.commandService.executeCommand(
StorageWrapper.Commands.SET_DATA.id,
key,
value
);
}
private getData<T>(key: string): Promise<T | undefined> {
return this.commandService.executeCommand<T>(StorageWrapper.Commands.GET_DATA.id, key);
return this.commandService.executeCommand<T>(
StorageWrapper.Commands.GET_DATA.id,
key
);
}
}
@@ -414,7 +572,6 @@ export interface AvailableBoard extends Board {
}
export namespace AvailableBoard {
export enum State {
/**
* Retrieved from the CLI via the `board list` command.
@@ -427,14 +584,16 @@ export namespace AvailableBoard {
/**
* We do not know anything about this board, probably a 3rd party. The user has not selected a board for this port yet.
*/
'incomplete'
'incomplete',
}
export function is(board: any): board is AvailableBoard {
return Board.is(board) && 'state' in board;
}
export function hasPort(board: AvailableBoard): board is AvailableBoard & { port: Port } {
export function hasPort(
board: AvailableBoard
): board is AvailableBoard & { port: Port } {
return !!board.port;
}
@@ -468,6 +627,5 @@ export namespace AvailableBoard {
return 1;
}
return left.state - right.state;
}
};
}

View File

@@ -5,7 +5,10 @@ import { DisposableCollection } from '@theia/core/lib/common/disposable';
import { Port } from '../../common/protocol';
import { BoardsConfig } from './boards-config';
import { ArduinoCommands } from '../arduino-commands';
import { BoardsServiceProvider, AvailableBoard } from './boards-service-provider';
import {
BoardsServiceProvider,
AvailableBoard,
} from './boards-service-provider';
export interface BoardsDropDownListCoords {
readonly top: number;
@@ -17,13 +20,14 @@ export interface BoardsDropDownListCoords {
export namespace BoardsDropDown {
export interface Props {
readonly coords: BoardsDropDownListCoords | 'hidden';
readonly items: Array<AvailableBoard & { onClick: () => void, port: Port }>;
readonly items: Array<
AvailableBoard & { onClick: () => void; port: Port }
>;
readonly openBoardsConfig: () => void;
}
}
export class BoardsDropDown extends React.Component<BoardsDropDown.Props> {
protected dropdownElement: HTMLElement;
constructor(props: BoardsDropDown.Props) {
@@ -47,35 +51,61 @@ export class BoardsDropDown extends React.Component<BoardsDropDown.Props> {
if (coords === 'hidden') {
return '';
}
return <div className='arduino-boards-dropdown-list'
style={{
position: 'absolute',
...coords
}}>
{this.renderItem({
label: 'Select Other Board & Port',
onClick: () => this.props.openBoardsConfig()
})}
{items.map(({ name, port, selected, onClick }) => ({ label: `${name} at ${Port.toString(port)}`, selected, onClick })).map(this.renderItem)}
</div>
}
protected renderItem({ label, selected, onClick }: { label: string, selected?: boolean, onClick: () => void }): React.ReactNode {
return <div key={label} className={`arduino-boards-dropdown-item ${selected ? 'selected' : ''}`} onClick={onClick}>
<div>
{label}
return (
<div
className="arduino-boards-dropdown-list"
style={{
position: 'absolute',
...coords,
}}
>
{this.renderItem({
label: 'Select Other Board & Port',
onClick: () => this.props.openBoardsConfig(),
})}
{items
.map(({ name, port, selected, onClick }) => ({
label: `${name} at ${Port.toString(port)}`,
selected,
onClick,
}))
.map(this.renderItem)}
</div>
{selected ? <span className='fa fa-check' /> : ''}
</div>
);
}
protected renderItem({
label,
selected,
onClick,
}: {
label: string;
selected?: boolean;
onClick: () => void;
}): React.ReactNode {
return (
<div
key={label}
className={`arduino-boards-dropdown-item ${
selected ? 'selected' : ''
}`}
onClick={onClick}
>
<div>{label}</div>
{selected ? <span className="fa fa-check" /> : ''}
</div>
);
}
}
export class BoardsToolBarItem extends React.Component<BoardsToolBarItem.Props, BoardsToolBarItem.State> {
export class BoardsToolBarItem extends React.Component<
BoardsToolBarItem.Props,
BoardsToolBarItem.State
> {
static TOOLBAR_ID: 'boards-toolbar';
protected readonly toDispose: DisposableCollection = new DisposableCollection();
protected readonly toDispose: DisposableCollection =
new DisposableCollection();
constructor(props: BoardsToolBarItem.Props) {
super(props);
@@ -83,7 +113,7 @@ export class BoardsToolBarItem extends React.Component<BoardsToolBarItem.Props,
const { availableBoards } = props.boardsServiceClient;
this.state = {
availableBoards,
coords: 'hidden'
coords: 'hidden',
};
document.addEventListener('click', () => {
@@ -92,7 +122,9 @@ export class BoardsToolBarItem extends React.Component<BoardsToolBarItem.Props,
}
componentDidMount() {
this.props.boardsServiceClient.onAvailableBoardsChanged(availableBoards => this.setState({ availableBoards }));
this.props.boardsServiceClient.onAvailableBoardsChanged(
(availableBoards) => this.setState({ availableBoards })
);
}
componentWillUnmount(): void {
@@ -109,8 +141,8 @@ export class BoardsToolBarItem extends React.Component<BoardsToolBarItem.Props,
top: rect.top,
left: rect.left,
width: rect.width,
paddingTop: rect.height
}
paddingTop: rect.height,
},
});
} else {
this.setState({ coords: 'hidden' });
@@ -123,63 +155,76 @@ export class BoardsToolBarItem extends React.Component<BoardsToolBarItem.Props,
render(): React.ReactNode {
const { coords, availableBoards } = this.state;
const boardsConfig = this.props.boardsServiceClient.boardsConfig;
const title = BoardsConfig.Config.toString(boardsConfig, { default: 'no board selected' });
const title = BoardsConfig.Config.toString(boardsConfig, {
default: 'no board selected',
});
const decorator = (() => {
const selectedBoard = availableBoards.find(({ selected }) => selected);
const selectedBoard = availableBoards.find(
({ selected }) => selected
);
if (!selectedBoard || !selectedBoard.port) {
return 'fa fa-times notAttached'
return 'fa fa-times notAttached';
}
if (selectedBoard.state === AvailableBoard.State.guessed) {
return 'fa fa-exclamation-triangle guessed'
return 'fa fa-exclamation-triangle guessed';
}
return ''
return '';
})();
return <React.Fragment>
<div className='arduino-boards-toolbar-item-container'>
<div className='arduino-boards-toolbar-item' title={title}>
<div className='inner-container' onClick={this.show}>
<span className={decorator} />
<div className='label noWrapInfo'>
<div className='noWrapInfo noselect'>
{title}
return (
<React.Fragment>
<div className="arduino-boards-toolbar-item-container">
<div className="arduino-boards-toolbar-item" title={title}>
<div className="inner-container" onClick={this.show}>
<span className={decorator} />
<div className="label noWrapInfo">
<div className="noWrapInfo noselect">
{title}
</div>
</div>
<span className="fa fa-caret-down caret" />
</div>
<span className='fa fa-caret-down caret' />
</div>
</div>
</div>
<BoardsDropDown
coords={coords}
items={availableBoards.filter(AvailableBoard.hasPort).map(board => ({
...board,
onClick: () => {
if (board.state === AvailableBoard.State.incomplete) {
this.props.boardsServiceClient.boardsConfig = {
selectedPort: board.port
};
this.openDialog();
} else {
this.props.boardsServiceClient.boardsConfig = {
selectedBoard: board,
selectedPort: board.port
}
}
}
}))}
openBoardsConfig={this.openDialog}>
</BoardsDropDown>
</React.Fragment>;
<BoardsDropDown
coords={coords}
items={availableBoards
.filter(AvailableBoard.hasPort)
.map((board) => ({
...board,
onClick: () => {
if (
board.state ===
AvailableBoard.State.incomplete
) {
this.props.boardsServiceClient.boardsConfig =
{
selectedPort: board.port,
};
this.openDialog();
} else {
this.props.boardsServiceClient.boardsConfig =
{
selectedBoard: board,
selectedPort: board.port,
};
}
},
}))}
openBoardsConfig={this.openDialog}
></BoardsDropDown>
</React.Fragment>
);
}
protected openDialog = () => {
this.props.commands.executeCommand(ArduinoCommands.OPEN_BOARDS_DIALOG.id);
this.props.commands.executeCommand(
ArduinoCommands.OPEN_BOARDS_DIALOG.id
);
this.setState({ coords: 'hidden' });
};
}
export namespace BoardsToolBarItem {
export interface Props {
readonly boardsServiceClient: BoardsServiceProvider;
readonly commands: CommandRegistry;
@@ -189,5 +234,4 @@ export namespace BoardsToolBarItem {
availableBoards: AvailableBoard[];
coords: BoardsDropDownListCoords | 'hidden';
}
}

View File

@@ -5,22 +5,20 @@ import { ListWidgetFrontendContribution } from '../widgets/component-list/list-w
@injectable()
export class BoardsListWidgetFrontendContribution extends ListWidgetFrontendContribution<BoardsPackage> {
constructor() {
super({
widgetId: BoardsListWidget.WIDGET_ID,
widgetName: BoardsListWidget.WIDGET_LABEL,
defaultWidgetOptions: {
area: 'left',
rank: 2
rank: 2,
},
toggleCommandId: `${BoardsListWidget.WIDGET_ID}:toggle`,
toggleKeybinding: 'CtrlCmd+Shift+B'
toggleKeybinding: 'CtrlCmd+Shift+B',
});
}
async initializeLayout(): Promise<void> {
this.openView();
}
}