mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-11-25 01:47:14 +00:00
Use eslint&prettier for code linting&formatting
This commit is contained in:
committed by
Francesco Stasi
parent
2a3873a923
commit
0592199858
@@ -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()
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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'])
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user