mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-11-13 20:29:27 +00:00
Added support for 3rd party core settings.
Closes arduino/arduino-pro-ide#10. Signed-off-by: Akos Kitta <kittaakos@typefox.io>
This commit is contained in:
@@ -2,7 +2,7 @@ import { injectable, inject } from 'inversify';
|
||||
import { ILogger } from '@theia/core/lib/common/logger';
|
||||
import { Event, Emitter } from '@theia/core/lib/common/event';
|
||||
import { MessageService } from '@theia/core/lib/common/message-service';
|
||||
import { ArduinoDaemonClient } from '../common/protocol/arduino-daemon';
|
||||
import { ArduinoDaemonClient } from '../common/protocol';
|
||||
|
||||
@injectable()
|
||||
export class ArduinoDaemonClientImpl implements ArduinoDaemonClient {
|
||||
|
||||
@@ -5,9 +5,8 @@ import { EditorWidget } from '@theia/editor/lib/browser/editor-widget';
|
||||
import { MessageService } from '@theia/core/lib/common/message-service';
|
||||
import { CommandContribution, CommandRegistry, Command, CommandHandler } from '@theia/core/lib/common/command';
|
||||
import { TabBarToolbarContribution, TabBarToolbarRegistry } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
|
||||
import { BoardsService } from '../common/protocol/boards-service';
|
||||
import { BoardsService, BoardsServiceClient, CoreService, Sketch, SketchesService, ToolOutputServiceClient } from '../common/protocol';
|
||||
import { ArduinoCommands } from './arduino-commands';
|
||||
import { CoreService } from '../common/protocol/core-service';
|
||||
import { BoardsServiceClientImpl } from './boards/boards-service-client-impl';
|
||||
import { WorkspaceRootUriAwareCommandHandler, WorkspaceCommands } from '@theia/workspace/lib/browser/workspace-commands';
|
||||
import { SelectionService, MenuContribution, MenuModelRegistry, MAIN_MENU_BAR, MenuPath } from '@theia/core';
|
||||
@@ -19,8 +18,6 @@ import {
|
||||
} from '@theia/core/lib/browser';
|
||||
import { OpenFileDialogProps, FileDialogService } from '@theia/filesystem/lib/browser/file-dialog';
|
||||
import { FileSystem, FileStat } from '@theia/filesystem/lib/common';
|
||||
import { Sketch, SketchesService } from '../common/protocol/sketches-service';
|
||||
import { ToolOutputServiceClient } from '../common/protocol/tool-output-service';
|
||||
import { CommonCommands, CommonMenus } from '@theia/core/lib/browser/common-frontend-contribution';
|
||||
import { FileSystemCommands } from '@theia/filesystem/lib/browser/filesystem-frontend-contribution';
|
||||
import { FileDownloadCommands } from '@theia/filesystem/lib/browser/download/file-download-command-contribution';
|
||||
@@ -45,6 +42,8 @@ import { ColorContribution } from '@theia/core/lib/browser/color-application-con
|
||||
import { ColorRegistry } from '@theia/core/lib/browser/color-registry';
|
||||
import { ArduinoDaemon } from '../common/protocol/arduino-daemon';
|
||||
import { ConfigService } from '../common/protocol/config-service';
|
||||
import { BoardsConfigStore } from './boards/boards-config-store';
|
||||
import { MainMenuManager } from './menu/main-menu-manager';
|
||||
|
||||
export namespace ArduinoMenus {
|
||||
export const SKETCH = [...MAIN_MENU_BAR, '3_sketch'];
|
||||
@@ -75,7 +74,11 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut
|
||||
protected readonly toolOutputServiceClient: ToolOutputServiceClient;
|
||||
|
||||
@inject(BoardsServiceClientImpl)
|
||||
protected readonly boardsServiceClient: BoardsServiceClientImpl;
|
||||
protected readonly boardsServiceClientImpl: BoardsServiceClientImpl;
|
||||
|
||||
// Unused but do not remove it. It's required by DI, otherwise `init` method is not called.
|
||||
@inject(BoardsServiceClient)
|
||||
protected readonly boardsServiceClient: BoardsServiceClient;
|
||||
|
||||
@inject(SelectionService)
|
||||
protected readonly selectionService: SelectionService;
|
||||
@@ -143,6 +146,12 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut
|
||||
@inject(ConfigService)
|
||||
protected readonly configService: ConfigService;
|
||||
|
||||
@inject(BoardsConfigStore)
|
||||
protected readonly boardsConfigStore: BoardsConfigStore;
|
||||
|
||||
@inject(MainMenuManager)
|
||||
protected readonly mainMenuManager: MainMenuManager;
|
||||
|
||||
protected application: FrontendApplication;
|
||||
protected wsSketchCount: number = 0; // TODO: this does not belong here, does it?
|
||||
|
||||
@@ -154,15 +163,10 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut
|
||||
text: BoardsConfig.Config.toString(config)
|
||||
});
|
||||
}
|
||||
this.boardsServiceClient.onBoardsConfigChanged(updateStatusBar);
|
||||
updateStatusBar(this.boardsServiceClient.boardsConfig);
|
||||
this.boardsServiceClientImpl.onBoardsConfigChanged(updateStatusBar);
|
||||
updateStatusBar(this.boardsServiceClientImpl.boardsConfig);
|
||||
|
||||
this.registerSketchesInMenu(this.menuRegistry);
|
||||
|
||||
Promise.all([
|
||||
this.boardsService.getAttachedBoards(),
|
||||
this.boardsService.getAvailablePorts()
|
||||
]).then(([{ boards }, { ports }]) => this.boardsServiceClient.tryReconnect(boards, ports));
|
||||
}
|
||||
|
||||
onStart(app: FrontendApplication): void {
|
||||
@@ -210,8 +214,7 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut
|
||||
render: () => <BoardsToolBarItem
|
||||
key='boardsToolbarItem'
|
||||
commands={this.commandRegistry}
|
||||
boardsServiceClient={this.boardsServiceClient}
|
||||
boardService={this.boardsService} />,
|
||||
boardsServiceClient={this.boardsServiceClientImpl} />,
|
||||
isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'left',
|
||||
priority: 2
|
||||
});
|
||||
@@ -276,10 +279,7 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut
|
||||
});
|
||||
|
||||
registry.registerCommand(ArduinoCommands.TOGGLE_COMPILE_FOR_DEBUG, {
|
||||
execute: () => {
|
||||
this.editorMode.toggleCompileForDebug();
|
||||
this.editorMode.menuContentChanged.fire();
|
||||
},
|
||||
execute: () => this.editorMode.toggleCompileForDebug(),
|
||||
isToggled: () => this.editorMode.compileForDebug
|
||||
});
|
||||
|
||||
@@ -345,7 +345,7 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut
|
||||
execute: async () => {
|
||||
const boardsConfig = await this.boardsConfigDialog.open();
|
||||
if (boardsConfig) {
|
||||
this.boardsServiceClient.boardsConfig = boardsConfig;
|
||||
this.boardsServiceClientImpl.boardsConfig = boardsConfig;
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -377,18 +377,18 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut
|
||||
}
|
||||
|
||||
try {
|
||||
const { boardsConfig } = this.boardsServiceClient;
|
||||
const { boardsConfig } = this.boardsServiceClientImpl;
|
||||
if (!boardsConfig || !boardsConfig.selectedBoard) {
|
||||
throw new Error('No boards selected. Please select a board.');
|
||||
}
|
||||
if (!boardsConfig.selectedBoard.fqbn) {
|
||||
throw new Error(`No core is installed for ${boardsConfig.selectedBoard.name}. Please install the board.`);
|
||||
throw new Error(`No core is installed for the '${boardsConfig.selectedBoard.name}' board. Please install the core.`);
|
||||
}
|
||||
// Reveal the Output view asynchronously (don't await it)
|
||||
const fqbn = await this.boardsConfigStore.appendConfigToFqbn(boardsConfig.selectedBoard.fqbn);
|
||||
this.outputContribution.openView({ reveal: true });
|
||||
await this.coreService.compile({
|
||||
uri: uri.toString(),
|
||||
board: boardsConfig.selectedBoard,
|
||||
sketchUri: uri.toString(),
|
||||
fqbn,
|
||||
optimizeForDebug: this.editorMode.compileForDebug
|
||||
});
|
||||
} catch (e) {
|
||||
@@ -413,7 +413,7 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut
|
||||
}
|
||||
|
||||
try {
|
||||
const { boardsConfig } = this.boardsServiceClient;
|
||||
const { boardsConfig } = this.boardsServiceClientImpl;
|
||||
if (!boardsConfig || !boardsConfig.selectedBoard) {
|
||||
throw new Error('No boards selected. Please select a board.');
|
||||
}
|
||||
@@ -421,11 +421,14 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut
|
||||
if (!selectedPort) {
|
||||
throw new Error('No ports selected. Please select a port.');
|
||||
}
|
||||
// Reveal the Output view asynchronously (don't await it)
|
||||
if (!boardsConfig.selectedBoard.fqbn) {
|
||||
throw new Error(`No core is installed for the '${boardsConfig.selectedBoard.name}' board. Please install the core.`);
|
||||
}
|
||||
this.outputContribution.openView({ reveal: true });
|
||||
const fqbn = await this.boardsConfigStore.appendConfigToFqbn(boardsConfig.selectedBoard.fqbn);
|
||||
await this.coreService.upload({
|
||||
uri: uri.toString(),
|
||||
board: boardsConfig.selectedBoard,
|
||||
sketchUri: uri.toString(),
|
||||
fqbn,
|
||||
port: selectedPort.address,
|
||||
optimizeForDebug: this.editorMode.compileForDebug
|
||||
});
|
||||
|
||||
@@ -75,6 +75,9 @@ import { ArduinoFrontendConnectionStatusService, ArduinoApplicationConnectionSta
|
||||
import { FrontendConnectionStatusService, ApplicationConnectionStatusContribution } from '@theia/core/lib/browser/connection-status-service';
|
||||
import { ConfigServiceClientImpl } from './config-service-client-impl';
|
||||
import { CoreServiceClientImpl } from './core-service-client-impl';
|
||||
import { BoardsDetailsMenuUpdater } from './boards/boards-details-menu-updater';
|
||||
import { BoardsConfigStore } from './boards/boards-config-store';
|
||||
import { ILogger } from '@theia/core';
|
||||
|
||||
const ElementQueries = require('css-element-queries/src/ElementQueries');
|
||||
|
||||
@@ -144,12 +147,28 @@ export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Un
|
||||
// Boards service client to receive and delegate notifications from the backend.
|
||||
bind(BoardsServiceClientImpl).toSelf().inSingletonScope();
|
||||
bind(FrontendApplicationContribution).toService(BoardsServiceClientImpl);
|
||||
bind(BoardsServiceClient).toDynamicValue(context => {
|
||||
bind(BoardsServiceClient).toDynamicValue(async context => {
|
||||
const client = context.container.get(BoardsServiceClientImpl);
|
||||
const service = context.container.get<BoardsService>(BoardsService);
|
||||
const [attachedBoards, availablePorts] = await Promise.all([
|
||||
service.getAttachedBoards(),
|
||||
service.getAvailablePorts()
|
||||
]);
|
||||
client.init({ attachedBoards, availablePorts });
|
||||
WebSocketConnectionProvider.createProxy(context.container, BoardsServicePath, client);
|
||||
return client;
|
||||
}).inSingletonScope();
|
||||
|
||||
// To be able to track, and update the menu based on the core settings (aka. board details) of the currently selected board.
|
||||
bind(FrontendApplicationContribution).to(BoardsDetailsMenuUpdater).inSingletonScope();
|
||||
bind(BoardsConfigStore).toSelf().inSingletonScope();
|
||||
bind(FrontendApplicationContribution).toService(BoardsConfigStore);
|
||||
// Logger for the Arduino daemon
|
||||
bind(ILogger).toDynamicValue(ctx => {
|
||||
const parentLogger = ctx.container.get<ILogger>(ILogger);
|
||||
return parentLogger.child('store');
|
||||
}).inSingletonScope().whenTargetNamed('store');
|
||||
|
||||
// Boards auto-installer
|
||||
bind(BoardsAutoInstaller).toSelf().inSingletonScope();
|
||||
bind(FrontendApplicationContribution).toService(BoardsAutoInstaller);
|
||||
@@ -35,9 +35,9 @@ export class BoardsAutoInstaller implements FrontendApplicationContribution {
|
||||
protected ensureCoreExists(config: BoardsConfig.Config): void {
|
||||
const { selectedBoard } = config;
|
||||
if (selectedBoard) {
|
||||
this.boardsService.search({}).then(({ items }) => {
|
||||
const candidates = items
|
||||
.filter(item => item.boards.some(board => Board.sameAs(board, selectedBoard)))
|
||||
this.boardsService.search({}).then(packages => {
|
||||
const candidates = packages
|
||||
.filter(pkg => pkg.boards.some(board => Board.sameAs(board, selectedBoard)))
|
||||
.filter(({ installable, installedVersion }) => installable && !installedVersion);
|
||||
for (const candidate of candidates) {
|
||||
// tslint:disable-next-line:max-line-length
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { QuickOpenItem, QuickOpenModel } from '@theia/core/lib/common/quick-open-model';
|
||||
import { QuickOpenService, QuickOpenOptions } from '@theia/core/lib/browser/quick-open/quick-open-service';
|
||||
import { BoardsService, BoardsServiceClient } from '../../common/protocol';
|
||||
|
||||
@injectable()
|
||||
export class BoardsConfigQuickOpenService {
|
||||
|
||||
@inject(QuickOpenService)
|
||||
protected readonly quickOpenService: QuickOpenService;
|
||||
|
||||
@inject(BoardsService)
|
||||
protected readonly boardsService: BoardsService;
|
||||
|
||||
@inject(BoardsServiceClient)
|
||||
protected readonly boardsServiceClient: BoardsServiceClient;
|
||||
|
||||
async selectBoard(): Promise<void> {
|
||||
|
||||
}
|
||||
|
||||
protected open(items: QuickOpenItem | QuickOpenItem[], placeholder: string): void {
|
||||
this.quickOpenService.open(this.getModel(Array.isArray(items) ? items : [items]), this.getOptions(placeholder));
|
||||
}
|
||||
|
||||
protected getOptions(placeholder: string, fuzzyMatchLabel: boolean = true, onClose: (canceled: boolean) => void = () => { }): QuickOpenOptions {
|
||||
return QuickOpenOptions.resolve({
|
||||
placeholder,
|
||||
fuzzyMatchLabel,
|
||||
fuzzySort: false,
|
||||
onClose
|
||||
});
|
||||
}
|
||||
|
||||
protected getModel(items: QuickOpenItem | QuickOpenItem[]): QuickOpenModel {
|
||||
return {
|
||||
onType(_: string, acceptor: (items: QuickOpenItem[]) => void): void {
|
||||
acceptor(Array.isArray(items) ? items : [items]);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
161
arduino-ide-extension/src/browser/boards/boards-config-store.ts
Normal file
161
arduino-ide-extension/src/browser/boards/boards-config-store.ts
Normal file
@@ -0,0 +1,161 @@
|
||||
import { injectable, inject, named } from 'inversify';
|
||||
import { ILogger } from '@theia/core/lib/common/logger';
|
||||
import { MaybePromise } from '@theia/core/lib/common/types';
|
||||
import { Event, Emitter } from '@theia/core/lib/common/event';
|
||||
import { deepClone, notEmpty } from '@theia/core/lib/common/objects';
|
||||
import { FrontendApplicationContribution, LocalStorageService } from '@theia/core/lib/browser';
|
||||
import { BoardsService, ConfigOption, Installable, BoardDetails } from '../../common/protocol';
|
||||
import { BoardsServiceClientImpl } from './boards-service-client-impl';
|
||||
|
||||
@injectable()
|
||||
export class BoardsConfigStore implements FrontendApplicationContribution {
|
||||
|
||||
@inject(ILogger)
|
||||
@named('store')
|
||||
protected readonly logger: ILogger;
|
||||
|
||||
@inject(BoardsService)
|
||||
protected readonly boardsService: BoardsService;
|
||||
|
||||
@inject(BoardsServiceClientImpl)
|
||||
protected readonly boardsServiceClient: BoardsServiceClientImpl;
|
||||
|
||||
@inject(LocalStorageService)
|
||||
protected readonly storageService: LocalStorageService;
|
||||
|
||||
protected readonly onChangedEmitter = new Emitter<void>();
|
||||
|
||||
onStart(): void {
|
||||
this.boardsServiceClient.onBoardsPackageInstalled(async ({ pkg }) => {
|
||||
const { installedVersion: version } = pkg;
|
||||
if (!version) {
|
||||
return;
|
||||
}
|
||||
let shouldFireChanged = false;
|
||||
for (const fqbn of pkg.boards.map(({ fqbn }) => fqbn).filter(notEmpty).filter(fqbn => !!fqbn)) {
|
||||
const key = this.getStorageKey(fqbn, version);
|
||||
let data = await this.storageService.getData<ConfigOption[] | undefined>(key);
|
||||
if (!data || !data.length) {
|
||||
const details = await this.getBoardDetailsSafe(fqbn);
|
||||
if (details) {
|
||||
data = details.configOptions;
|
||||
if (data.length) {
|
||||
await this.storageService.setData(key, data);
|
||||
shouldFireChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (shouldFireChanged) {
|
||||
this.fireChanged();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
get onChanged(): Event<void> {
|
||||
return this.onChangedEmitter.event;
|
||||
}
|
||||
|
||||
async appendConfigToFqbn(
|
||||
fqbn: string,
|
||||
boardsPackageVersion: MaybePromise<Installable.Version | undefined> = this.getBoardsPackageVersion(fqbn)): Promise<string> {
|
||||
|
||||
const configOptions = await this.getConfig(fqbn, boardsPackageVersion);
|
||||
return ConfigOption.decorate(fqbn, configOptions);
|
||||
}
|
||||
|
||||
async getConfig(
|
||||
fqbn: string,
|
||||
boardsPackageVersion: MaybePromise<Installable.Version | undefined> = this.getBoardsPackageVersion(fqbn)): Promise<ConfigOption[]> {
|
||||
|
||||
const version = await boardsPackageVersion;
|
||||
if (!version) {
|
||||
return [];
|
||||
}
|
||||
const key = this.getStorageKey(fqbn, version);
|
||||
let configOptions = await this.storageService.getData<ConfigOption[] | undefined>(key, undefined);
|
||||
if (configOptions) {
|
||||
return configOptions;
|
||||
}
|
||||
|
||||
const details = await this.getBoardDetailsSafe(fqbn);
|
||||
if (!details) {
|
||||
return [];
|
||||
}
|
||||
|
||||
configOptions = details.configOptions;
|
||||
await this.storageService.setData(key, configOptions);
|
||||
return configOptions;
|
||||
}
|
||||
|
||||
async setSelected(
|
||||
{ fqbn, option, selectedValue }: { fqbn: string, option: string, selectedValue: string },
|
||||
boardsPackageVersion: MaybePromise<Installable.Version | undefined> = this.getBoardsPackageVersion(fqbn)): Promise<boolean> {
|
||||
|
||||
const configOptions = deepClone(await this.getConfig(fqbn, boardsPackageVersion));
|
||||
const configOption = configOptions.find(c => c.option === option);
|
||||
if (!configOption) {
|
||||
return false;
|
||||
}
|
||||
let updated = false;
|
||||
for (const value of configOption.values) {
|
||||
if (value.value === selectedValue) {
|
||||
(value as any).selected = true;
|
||||
updated = true;
|
||||
} else {
|
||||
(value as any).selected = false;
|
||||
}
|
||||
}
|
||||
if (!updated) {
|
||||
return false;
|
||||
}
|
||||
const version = await boardsPackageVersion;
|
||||
if (!version) {
|
||||
return false;
|
||||
}
|
||||
await this.setConfig({ fqbn, configOptions, version });
|
||||
this.fireChanged();
|
||||
return true;
|
||||
}
|
||||
|
||||
protected async setConfig(
|
||||
{ fqbn, configOptions, version }: { fqbn: string, configOptions: ConfigOption[], version: Installable.Version }): Promise<void> {
|
||||
|
||||
const key = this.getStorageKey(fqbn, version);
|
||||
return this.storageService.setData(key, configOptions);
|
||||
}
|
||||
|
||||
protected getStorageKey(fqbn: string, version: Installable.Version): string {
|
||||
return `.arduinoProIDE-configOptions-${version}-${fqbn}`;
|
||||
}
|
||||
|
||||
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}`);
|
||||
} else {
|
||||
this.logger.error(`An unexpected error occurred while retrieving the board details for ${fqbn}.`, err);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
protected fireChanged(): void {
|
||||
this.onChangedEmitter.fire();
|
||||
}
|
||||
|
||||
protected async getBoardsPackageVersion(fqbn: string): Promise<Installable.Version | undefined> {
|
||||
if (!fqbn) {
|
||||
return undefined;
|
||||
}
|
||||
const boardsPackage = await this.boardsService.getContainerBoardPackage({ fqbn });
|
||||
if (!boardsPackage) {
|
||||
return undefined;
|
||||
}
|
||||
return boardsPackage.installedVersion;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import { DisposableCollection } from '@theia/core';
|
||||
import { BoardsService, Board, Port, AttachedSerialBoard, AttachedBoardsChangeEvent } from '../../common/protocol/boards-service';
|
||||
import { BoardsService, Board, Port, AttachedBoardsChangeEvent } from '../../common/protocol/boards-service';
|
||||
import { BoardsServiceClientImpl } from './boards-service-client-impl';
|
||||
import { CoreServiceClientImpl } from '../core-service-client-impl';
|
||||
import { ArduinoDaemonClientImpl } from '../arduino-daemon-client-impl';
|
||||
@@ -36,11 +36,11 @@ export abstract class Item<T> extends React.Component<{
|
||||
selected: boolean,
|
||||
onClick: (item: T) => void,
|
||||
missing?: boolean,
|
||||
detail?: string
|
||||
details?: string
|
||||
}> {
|
||||
|
||||
render(): React.ReactNode {
|
||||
const { selected, label, missing, detail } = this.props;
|
||||
const { selected, label, missing, details } = this.props;
|
||||
const classNames = ['item'];
|
||||
if (selected) {
|
||||
classNames.push('selected');
|
||||
@@ -48,11 +48,11 @@ export abstract class Item<T> extends React.Component<{
|
||||
if (missing === true) {
|
||||
classNames.push('missing')
|
||||
}
|
||||
return <div onClick={this.onClick} className={classNames.join(' ')} title={`${label}${!detail ? '' : detail}`}>
|
||||
return <div onClick={this.onClick} className={classNames.join(' ')} title={`${label}${!details ? '' : details}`}>
|
||||
<div className='label'>
|
||||
{label}
|
||||
</div>
|
||||
{!detail ? '' : <div className='detail'>{detail}</div>}
|
||||
{!details ? '' : <div className='details'>{details}</div>}
|
||||
{!selected ? '' : <div className='selected-icon'><i className='fa fa-check' /></div>}
|
||||
</div>;
|
||||
}
|
||||
@@ -82,13 +82,15 @@ export class BoardsConfig extends React.Component<BoardsConfig.Props, BoardsConf
|
||||
|
||||
componentDidMount() {
|
||||
this.updateBoards();
|
||||
this.props.boardsService.getAvailablePorts().then(({ ports }) => this.updatePorts(ports));
|
||||
this.props.boardsService.getAvailablePorts().then(ports => this.updatePorts(ports));
|
||||
const { boardsServiceClient, coreServiceClient, daemonClient } = this.props;
|
||||
this.toDispose.pushAll([
|
||||
boardsServiceClient.onBoardsChanged(event => this.updatePorts(event.newState.ports, AttachedBoardsChangeEvent.diff(event).detached.ports)),
|
||||
boardsServiceClient.onAttachedBoardsChanged(event => this.updatePorts(event.newState.ports, AttachedBoardsChangeEvent.diff(event).detached.ports)),
|
||||
boardsServiceClient.onBoardsConfigChanged(({ selectedBoard, selectedPort }) => {
|
||||
this.setState({ selectedBoard, selectedPort }, () => this.fireConfigChanged());
|
||||
}),
|
||||
boardsServiceClient.onBoardsPackageInstalled(() => this.updateBoards(this.state.query)),
|
||||
boardsServiceClient.onBoardsPackageUninstalled(() => this.updateBoards(this.state.query)),
|
||||
coreServiceClient.onIndexUpdated(() => this.updateBoards(this.state.query)),
|
||||
daemonClient.onDaemonStarted(() => this.updateBoards(this.state.query)),
|
||||
daemonClient.onDaemonStopped(() => this.setState({ searchResults: [] }))
|
||||
@@ -110,11 +112,11 @@ export class BoardsConfig extends React.Component<BoardsConfig.Props, BoardsConf
|
||||
: eventOrQuery.target.value.toLowerCase()
|
||||
).trim();
|
||||
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 }) => {
|
||||
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))) {
|
||||
@@ -124,35 +126,17 @@ export class BoardsConfig extends React.Component<BoardsConfig.Props, BoardsConf
|
||||
});
|
||||
}
|
||||
|
||||
protected queryBoards = (options: { query?: string } = {}): Promise<{ searchResults: Array<Board & { packageName: string }> }> => {
|
||||
const { boardsService } = this.props;
|
||||
const query = (options.query || '').toLocaleLowerCase();
|
||||
return new Promise<{ searchResults: Array<Board & { packageName: string }> }>(resolve => {
|
||||
boardsService.search(options)
|
||||
.then(({ items }) => items
|
||||
.map(item => item.boards.map(board => ({ ...board, packageName: item.name })))
|
||||
.reduce((acc, curr) => acc.concat(curr), [])
|
||||
.filter(board => board.name.toLocaleLowerCase().indexOf(query) !== -1)
|
||||
.sort(Board.compare))
|
||||
.then(searchResults => resolve({ searchResults }));
|
||||
});
|
||||
protected queryBoards = (options: { query?: string } = {}): Promise<Array<Board & { packageName: string }>> => {
|
||||
return this.props.boardsService.searchBoards(options);
|
||||
}
|
||||
|
||||
protected get attachedBoards(): Promise<{ boards: Board[] }> {
|
||||
return this.props.boardsService.getAttachedBoards();
|
||||
}
|
||||
|
||||
protected get availablePorts(): Promise<{ ports: Port[] }> {
|
||||
protected get availablePorts(): Promise<Port[]> {
|
||||
return this.props.boardsService.getAvailablePorts();
|
||||
}
|
||||
|
||||
protected queryPorts = (availablePorts: Promise<{ ports: Port[] }> = this.availablePorts) => {
|
||||
return new Promise<{ knownPorts: Port[] }>(resolve => {
|
||||
availablePorts
|
||||
.then(({ ports }) => ports
|
||||
.sort(Port.compare))
|
||||
.then(knownPorts => resolve({ knownPorts }));
|
||||
});
|
||||
protected queryPorts = async (availablePorts: Promise<Port[]> = this.availablePorts) => {
|
||||
const ports = await availablePorts;
|
||||
return { knownPorts: ports.sort(Port.compare) };
|
||||
}
|
||||
|
||||
protected toggleFilterPorts = () => {
|
||||
@@ -194,41 +178,20 @@ export class BoardsConfig extends React.Component<BoardsConfig.Props, BoardsConf
|
||||
|
||||
protected renderBoards(): React.ReactNode {
|
||||
const { selectedBoard, searchResults } = this.state;
|
||||
// Board names are not unique. We show the corresponding core name as a detail.
|
||||
// https://github.com/arduino/arduino-cli/pull/294#issuecomment-513764948
|
||||
const distinctBoardNames = new Map<string, number>();
|
||||
for (const { name } of searchResults) {
|
||||
const counter = distinctBoardNames.get(name) || 0;
|
||||
distinctBoardNames.set(name, counter + 1);
|
||||
}
|
||||
|
||||
// Due to the non-unique board names, we have to check the package name as well.
|
||||
const selected = (board: Board & { packageName: string }) => {
|
||||
if (!!selectedBoard) {
|
||||
if (Board.equals(board, selectedBoard)) {
|
||||
if ('packageName' in selectedBoard) {
|
||||
return board.packageName === (selectedBoard as any).packageName;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return <React.Fragment>
|
||||
<div className='search'>
|
||||
<input type='search' className='theia-input' placeholder='SEARCH BOARD' onChange={this.updateBoards} ref={this.focusNodeSet} />
|
||||
<i className='fa fa-search'></i>
|
||||
</div>
|
||||
<div className='boards list'>
|
||||
{this.state.searchResults.map(board => <Item<Board & { packageName: string }>
|
||||
{Board.decorateBoards(selectedBoard, searchResults).map(board => <Item<Board & { packageName: string }>
|
||||
key={`${board.name}-${board.packageName}`}
|
||||
item={board}
|
||||
label={board.name}
|
||||
detail={(distinctBoardNames.get(board.name) || 0) > 1 ? ` - ${board.packageName}` : undefined}
|
||||
selected={selected(board)}
|
||||
details={board.details}
|
||||
selected={board.selected}
|
||||
onClick={this.selectBoard}
|
||||
missing={!Board.installed(board)}
|
||||
missing={board.missing}
|
||||
/>)}
|
||||
</div>
|
||||
</React.Fragment>;
|
||||
@@ -276,9 +239,9 @@ export namespace BoardsConfig {
|
||||
|
||||
export namespace Config {
|
||||
|
||||
export function sameAs(config: Config, other: Config | AttachedSerialBoard): boolean {
|
||||
export function sameAs(config: Config, other: Config | Board): boolean {
|
||||
const { selectedBoard, selectedPort } = config;
|
||||
if (AttachedSerialBoard.is(other)) {
|
||||
if (Board.is(other)) {
|
||||
return !!selectedBoard
|
||||
&& Board.equals(other, selectedBoard)
|
||||
&& Port.sameAs(selectedPort, other.port);
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { CommandRegistry } from '@theia/core/lib/common/command';
|
||||
import { MenuModelRegistry, MenuNode } from '@theia/core/lib/common/menu';
|
||||
import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||
import { BoardsServiceClientImpl } from './boards-service-client-impl';
|
||||
import { Board, ConfigOption } from '../../common/protocol';
|
||||
import { FrontendApplicationContribution } from '@theia/core/lib/browser';
|
||||
import { ArduinoMenus } from '../arduino-frontend-contribution';
|
||||
import { BoardsConfigStore } from './boards-config-store';
|
||||
import { MainMenuManager } from '../menu/main-menu-manager';
|
||||
|
||||
@injectable()
|
||||
export class BoardsDetailsMenuUpdater implements FrontendApplicationContribution {
|
||||
|
||||
@inject(CommandRegistry)
|
||||
protected readonly commandRegistry: CommandRegistry;
|
||||
|
||||
@inject(MenuModelRegistry)
|
||||
protected readonly menuRegistry: MenuModelRegistry;
|
||||
|
||||
@inject(MainMenuManager)
|
||||
protected readonly mainMenuManager: MainMenuManager;
|
||||
|
||||
@inject(BoardsConfigStore)
|
||||
protected readonly boardsConfigStore: BoardsConfigStore;
|
||||
|
||||
@inject(BoardsServiceClientImpl)
|
||||
protected readonly boardsServiceClient: BoardsServiceClientImpl;
|
||||
|
||||
protected readonly toDisposeOnBoardChange = new DisposableCollection();
|
||||
|
||||
onStart(): void {
|
||||
this.boardsConfigStore.onChanged(() => this.updateMenuActions(this.boardsServiceClient.boardsConfig.selectedBoard));
|
||||
this.boardsServiceClient.onBoardsConfigChanged(({ selectedBoard }) => this.updateMenuActions(selectedBoard));
|
||||
this.updateMenuActions(this.boardsServiceClient.boardsConfig.selectedBoard);
|
||||
}
|
||||
|
||||
protected async updateMenuActions(selectedBoard: Board | undefined): Promise<void> {
|
||||
if (selectedBoard) {
|
||||
this.toDisposeOnBoardChange.dispose();
|
||||
this.mainMenuManager.update();
|
||||
const { fqbn } = selectedBoard;
|
||||
if (fqbn) {
|
||||
const configOptions = await this.boardsConfigStore.getConfig(fqbn);
|
||||
const boardsConfigMenuPath = [...ArduinoMenus.TOOLS, 'z_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>()
|
||||
for (const value of values) {
|
||||
const id = `${fqbn}-${option}--${value.value}`;
|
||||
const command = { id, label: value.label };
|
||||
const selectedValue = value.value;
|
||||
const handler = {
|
||||
execute: () => this.boardsConfigStore.setSelected({ fqbn, option, selectedValue }),
|
||||
isToggled: () => value.selected
|
||||
};
|
||||
commands.set(id, this.commandRegistry.registerCommand(command, handler));
|
||||
}
|
||||
this.menuRegistry.registerSubmenu(menuPath, label);
|
||||
this.toDisposeOnBoardChange.pushAll([
|
||||
...commands.values(),
|
||||
Disposable.create(() => this.unregisterSubmenu(menuPath)), // We cannot dispose submenu entries: https://github.com/eclipse-theia/theia/issues/7299
|
||||
...Array.from(commands.keys()).map((commandId, index) => {
|
||||
this.menuRegistry.registerMenuAction(menuPath, { commandId, order: String(index) })
|
||||
return Disposable.create(() => this.menuRegistry.unregisterMenuAction(commandId))
|
||||
})
|
||||
]);
|
||||
}
|
||||
this.mainMenuManager.update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected unregisterSubmenu(menuPath: string[]): void {
|
||||
if (menuPath.length < 2) {
|
||||
throw new Error(`Expected at least two item as a menu-path. Got ${JSON.stringify(menuPath)} instead.`);
|
||||
}
|
||||
const toRemove = menuPath[menuPath.length - 1];
|
||||
const parentMenuPath = menuPath.slice(0, menuPath.length - 1);
|
||||
// This is unsafe. Calling `getMenu` with a non-existing menu-path will result in a new menu creation.
|
||||
// https://github.com/eclipse-theia/theia/issues/7300
|
||||
const parent = this.menuRegistry.getMenu(parentMenuPath);
|
||||
const index = parent.children.findIndex(({ id }) => id === toRemove);
|
||||
if (index === -1) {
|
||||
throw new Error(`Could not find menu with menu-path: ${JSON.stringify(menuPath)}.`);
|
||||
}
|
||||
(parent.children as Array<MenuNode>).splice(index, 1);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,17 +1,17 @@
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { BoardPackage, BoardsService } from '../../common/protocol/boards-service';
|
||||
import { BoardsPackage, BoardsService } from '../../common/protocol/boards-service';
|
||||
import { ListWidget } from '../components/component-list/list-widget';
|
||||
import { ListItemRenderer } from '../components/component-list/list-item-renderer';
|
||||
|
||||
@injectable()
|
||||
export class BoardsListWidget extends ListWidget<BoardPackage> {
|
||||
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<BoardPackage>) {
|
||||
@inject(ListItemRenderer) protected itemRenderer: ListItemRenderer<BoardsPackage>) {
|
||||
|
||||
super({
|
||||
id: BoardsListWidget.WIDGET_ID,
|
||||
@@ -19,7 +19,7 @@ export class BoardsListWidget extends ListWidget<BoardPackage> {
|
||||
iconClass: 'fa fa-microchip',
|
||||
searchable: service,
|
||||
installable: service,
|
||||
itemLabel: (item: BoardPackage) => item.name,
|
||||
itemLabel: (item: BoardsPackage) => item.name,
|
||||
itemRenderer
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { injectable, inject } from 'inversify';
|
||||
import { injectable, inject, optional } from 'inversify';
|
||||
import { Emitter } from '@theia/core/lib/common/event';
|
||||
import { ILogger } from '@theia/core/lib/common/logger';
|
||||
import { MessageService } from '@theia/core/lib/common/message-service';
|
||||
import { LocalStorageService } from '@theia/core/lib/browser/storage-service';
|
||||
import { StorageService } from '@theia/core/lib/browser/storage-service';
|
||||
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
|
||||
import { RecursiveRequired } from '../../common/types';
|
||||
import { BoardsServiceClient, AttachedBoardsChangeEvent, BoardInstalledEvent, AttachedSerialBoard, Board, Port, BoardUninstalledEvent } from '../../common/protocol/boards-service';
|
||||
import { BoardsServiceClient, AttachedBoardsChangeEvent, BoardInstalledEvent, Board, Port, BoardUninstalledEvent } from '../../common/protocol';
|
||||
import { BoardsConfig } from './boards-config';
|
||||
|
||||
@injectable()
|
||||
@@ -14,16 +14,18 @@ export class BoardsServiceClientImpl implements BoardsServiceClient, FrontendApp
|
||||
@inject(ILogger)
|
||||
protected logger: ILogger;
|
||||
|
||||
@optional()
|
||||
@inject(MessageService)
|
||||
protected messageService: MessageService;
|
||||
|
||||
@inject(LocalStorageService)
|
||||
protected storageService: LocalStorageService;
|
||||
@inject(StorageService)
|
||||
protected storageService: StorageService;
|
||||
|
||||
protected readonly onBoardInstalledEmitter = new Emitter<BoardInstalledEvent>();
|
||||
protected readonly onBoardUninstalledEmitter = new Emitter<BoardUninstalledEvent>();
|
||||
protected readonly onBoardsPackageInstalledEmitter = new Emitter<BoardInstalledEvent>();
|
||||
protected readonly onBoardsPackageUninstalledEmitter = new Emitter<BoardUninstalledEvent>();
|
||||
protected readonly onAttachedBoardsChangedEmitter = new Emitter<AttachedBoardsChangeEvent>();
|
||||
protected readonly onSelectedBoardsConfigChangedEmitter = new Emitter<BoardsConfig.Config>();
|
||||
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.
|
||||
@@ -34,35 +36,51 @@ export class BoardsServiceClientImpl implements BoardsServiceClient, FrontendApp
|
||||
*/
|
||||
protected latestValidBoardsConfig: RecursiveRequired<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.
|
||||
protected _availablePorts: Port[] = [];
|
||||
protected _availableBoards: AvailableBoard[] = [];
|
||||
|
||||
readonly onBoardsChanged = this.onAttachedBoardsChangedEmitter.event;
|
||||
readonly onBoardInstalled = this.onBoardInstalledEmitter.event;
|
||||
readonly onBoardUninstalled = this.onBoardUninstalledEmitter.event;
|
||||
readonly onBoardsConfigChanged = this.onSelectedBoardsConfigChangedEmitter.event;
|
||||
/**
|
||||
* Event when the state of the attached/detached boards has changed. For instance, the user have detached a physical board.
|
||||
*/
|
||||
readonly onAttachedBoardsChanged = this.onAttachedBoardsChangedEmitter.event;
|
||||
readonly onBoardsPackageInstalled = this.onBoardsPackageInstalledEmitter.event;
|
||||
readonly onBoardsPackageUninstalled = this.onBoardsPackageUninstalledEmitter.event;
|
||||
/**
|
||||
* Unlike `onAttachedBoardsChanged` this even fires when the user modifies the selected board in the IDE.\
|
||||
* This even also fires, when the boards package was not available for the currently selected board,
|
||||
* and the user installs the board package. Note: installing a board package will set the `fqbn` of the
|
||||
* currently selected board.\
|
||||
* 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;
|
||||
|
||||
async onStart(): Promise<void> {
|
||||
return this.loadState();
|
||||
}
|
||||
|
||||
notifyAttachedBoardsChanged(event: AttachedBoardsChangeEvent): void {
|
||||
this.logger.info('Attached boards and available ports changed: ', JSON.stringify(event));
|
||||
const { detached, attached } = AttachedBoardsChangeEvent.diff(event);
|
||||
const { selectedPort, selectedBoard } = this.boardsConfig;
|
||||
this.onAttachedBoardsChangedEmitter.fire(event);
|
||||
// Dynamically unset the port if is not available anymore. A port can be "detached" when removing a board.
|
||||
if (detached.ports.some(port => Port.equals(selectedPort, port))) {
|
||||
this.boardsConfig = {
|
||||
selectedBoard,
|
||||
selectedPort: undefined
|
||||
};
|
||||
}
|
||||
// Try to reconnect.
|
||||
this.tryReconnect(attached.boards, attached.ports);
|
||||
/**
|
||||
* When the FE connects to the BE, the BE stets the known boards and ports.\
|
||||
* This is a DI workaround for not being able to inject the service into the client.
|
||||
*/
|
||||
init({ attachedBoards, availablePorts }: { attachedBoards: Board[], availablePorts: Port[] }): void {
|
||||
this._attachedBoards = attachedBoards;
|
||||
this._availablePorts = availablePorts;
|
||||
this.reconcileAvailableBoards().then(() => this.tryReconnect());
|
||||
}
|
||||
|
||||
async tryReconnect(attachedBoards: Board[], availablePorts: Port[]): Promise<boolean> {
|
||||
notifyAttachedBoardsChanged(event: AttachedBoardsChangeEvent): void {
|
||||
this.logger.info('Attached boards and available ports changed: ', JSON.stringify(event));
|
||||
this._attachedBoards = event.newState.boards;
|
||||
this.onAttachedBoardsChangedEmitter.fire(event);
|
||||
this._availablePorts = event.newState.ports;
|
||||
this.reconcileAvailableBoards().then(() => this.tryReconnect());
|
||||
}
|
||||
|
||||
protected async tryReconnect(): Promise<boolean> {
|
||||
if (this.latestValidBoardsConfig && !this.canUploadTo(this.boardsConfig)) {
|
||||
for (const board of attachedBoards.filter(AttachedSerialBoard.is)) {
|
||||
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)) {
|
||||
@@ -73,13 +91,13 @@ export class BoardsServiceClientImpl implements BoardsServiceClient, FrontendApp
|
||||
}
|
||||
// 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 attachedBoards.filter(AttachedSerialBoard.is)) {
|
||||
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: availablePorts.find(port => Port.sameAs(port, board.port))
|
||||
selectedPort: board.port
|
||||
};
|
||||
return true;
|
||||
}
|
||||
@@ -90,21 +108,52 @@ export class BoardsServiceClientImpl implements BoardsServiceClient, FrontendApp
|
||||
|
||||
notifyBoardInstalled(event: BoardInstalledEvent): void {
|
||||
this.logger.info('Board installed: ', JSON.stringify(event));
|
||||
this.onBoardInstalledEmitter.fire(event);
|
||||
this.onBoardsPackageInstalledEmitter.fire(event);
|
||||
const { selectedBoard } = this.boardsConfig;
|
||||
const { installedVersion, id } = event.pkg;
|
||||
if (selectedBoard) {
|
||||
const installedBoard = event.pkg.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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
notifyBoardUninstalled(event: BoardUninstalledEvent): void {
|
||||
this.logger.info('Board uninstalled: ', JSON.stringify(event));
|
||||
this.onBoardUninstalledEmitter.fire(event);
|
||||
this.onBoardsPackageUninstalledEmitter.fire(event);
|
||||
const { selectedBoard } = this.boardsConfig;
|
||||
if (selectedBoard && selectedBoard.fqbn) {
|
||||
const uninstalledBoard = event.pkg.boards.find(({ name }) => name === selectedBoard.name);
|
||||
if (uninstalledBoard && uninstalledBoard.fqbn === selectedBoard.fqbn) {
|
||||
this.logger.info(`Board package ${event.pkg.id} was uninstalled. Discarding the FQBN of the currently selected ${selectedBoard.name} board.`);
|
||||
const selectedBoardWithoutFqbn = {
|
||||
name: selectedBoard.name
|
||||
// No FQBN
|
||||
};
|
||||
this.boardsConfig = {
|
||||
...this.boardsConfig,
|
||||
selectedBoard: selectedBoardWithoutFqbn
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
set boardsConfig(config: BoardsConfig.Config) {
|
||||
this.doSetBoardsConfig(config);
|
||||
this.saveState().finally(() => this.reconcileAvailableBoards().finally(() => this.onBoardsConfigChangedEmitter.fire(this._boardsConfig)));
|
||||
}
|
||||
|
||||
protected doSetBoardsConfig(config: BoardsConfig.Config): void {
|
||||
this.logger.info('Board config changed: ', JSON.stringify(config));
|
||||
this._boardsConfig = config;
|
||||
if (this.canUploadTo(this._boardsConfig)) {
|
||||
this.latestValidBoardsConfig = this._boardsConfig;
|
||||
}
|
||||
this.saveState().then(() => this.onSelectedBoardsConfigChangedEmitter.fire(this._boardsConfig));
|
||||
}
|
||||
|
||||
get boardsConfig(): BoardsConfig.Config {
|
||||
@@ -123,7 +172,7 @@ export class BoardsServiceClientImpl implements BoardsServiceClient, FrontendApp
|
||||
}
|
||||
|
||||
if (!config.selectedBoard) {
|
||||
if (!options.silent) {
|
||||
if (!options.silent && this.messageService) {
|
||||
this.messageService.warn('No boards selected.', { timeout: 3000 });
|
||||
}
|
||||
return false;
|
||||
@@ -133,7 +182,7 @@ export class BoardsServiceClientImpl implements BoardsServiceClient, FrontendApp
|
||||
}
|
||||
|
||||
/**
|
||||
* `true` if the `canVerify` and the `config.selectedPort` is also set with FQBN, hence can upload to board. Otherwise, `false`.
|
||||
* `true` if `canVerify`, the board has an FQBN and the `config.selectedPort` is also set, hence can upload to board. Otherwise, `false`.
|
||||
*/
|
||||
canUploadTo(
|
||||
config: BoardsConfig.Config | undefined = this.boardsConfig,
|
||||
@@ -145,14 +194,14 @@ export class BoardsServiceClientImpl implements BoardsServiceClient, FrontendApp
|
||||
|
||||
const { name } = config.selectedBoard;
|
||||
if (!config.selectedPort) {
|
||||
if (!options.silent) {
|
||||
if (!options.silent && this.messageService) {
|
||||
this.messageService.warn(`No ports selected for board: '${name}'.`, { timeout: 3000 });
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!config.selectedBoard.fqbn) {
|
||||
if (!options.silent) {
|
||||
if (!options.silent && this.messageService) {
|
||||
this.messageService.warn(`The FQBN is not available for the selected board ${name}. Do you have the corresponding core installed?`, { timeout: 3000 });
|
||||
}
|
||||
return false;
|
||||
@@ -161,8 +210,93 @@ export class BoardsServiceClientImpl implements BoardsServiceClient, FrontendApp
|
||||
return true;
|
||||
}
|
||||
|
||||
protected saveState(): Promise<void> {
|
||||
return this.storageService.setData('latest-valid-boards-config', this.latestValidBoardsConfig);
|
||||
get availableBoards(): AvailableBoard[] {
|
||||
return this._availableBoards;
|
||||
}
|
||||
|
||||
protected async reconcileAvailableBoards(): Promise<void> {
|
||||
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 });
|
||||
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);
|
||||
|
||||
for (const boardPort of availableBoardPorts) {
|
||||
let state = AvailableBoard.State.incomplete; // Initial pessimism.
|
||||
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);
|
||||
if (lastSelectedBoard) {
|
||||
board = {
|
||||
...lastSelectedBoard,
|
||||
port: boardPort
|
||||
};
|
||||
state = AvailableBoard.State.guessed;
|
||||
}
|
||||
}
|
||||
if (!board) {
|
||||
availableBoards.push({ name: 'Unknown', port: boardPort, state });
|
||||
} else {
|
||||
const selected = BoardsConfig.Config.sameAs(boardsConfig, board);
|
||||
availableBoards.push({ ...board, state, selected, port: boardPort });
|
||||
}
|
||||
}
|
||||
|
||||
if (boardsConfig.selectedBoard && !availableBoards.some(({ selected }) => selected)) {
|
||||
availableBoards.push({
|
||||
...boardsConfig.selectedBoard,
|
||||
port: boardsConfig.selectedPort,
|
||||
selected: true,
|
||||
state: AvailableBoard.State.incomplete
|
||||
});
|
||||
}
|
||||
|
||||
const sortedAvailableBoards = availableBoards.sort(AvailableBoard.COMPARATOR);
|
||||
let hasChanged = sortedAvailableBoards.length !== currentAvailableBoards.length;
|
||||
for (let i = 0; !hasChanged && i < sortedAvailableBoards.length; i++) {
|
||||
hasChanged = AvailableBoard.COMPARATOR(sortedAvailableBoards[i], currentAvailableBoards[i]) !== 0;
|
||||
}
|
||||
if (hasChanged) {
|
||||
this._availableBoards = sortedAvailableBoards;
|
||||
this.onAvailableBoardsChangedEmitter.fire(this._availableBoards);
|
||||
}
|
||||
}
|
||||
|
||||
protected async getLastSelectedBoardOnPort(port: Port | string | undefined): Promise<Board | undefined> {
|
||||
if (!port) {
|
||||
return undefined;
|
||||
}
|
||||
const key = this.getLastSelectedBoardOnPortKey(port);
|
||||
return this.storageService.getData<Board>(key);
|
||||
}
|
||||
|
||||
protected async saveState(): Promise<void> {
|
||||
// We save the port with the selected board name/FQBN, to be able to guess a better board name.
|
||||
// Required when the attached board belongs to a 3rd party boards package, and neither the name, nor
|
||||
// the FQBN can be retrieved with a `board list` command.
|
||||
// https://github.com/arduino/arduino-cli/issues/623
|
||||
const { selectedBoard, selectedPort } = this.boardsConfig;
|
||||
if (selectedBoard && selectedPort) {
|
||||
const key = this.getLastSelectedBoardOnPortKey(selectedPort);
|
||||
await this.storageService.setData(key, selectedBoard);
|
||||
}
|
||||
await this.storageService.setData('latest-valid-boards-config', this.latestValidBoardsConfig);
|
||||
}
|
||||
|
||||
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)}`;
|
||||
}
|
||||
|
||||
protected async loadState(): Promise<void> {
|
||||
@@ -176,3 +310,63 @@ export class BoardsServiceClientImpl implements BoardsServiceClient, FrontendApp
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Representation of a ready-to-use board, configured by the user. Not all of the available boards are
|
||||
* necessarily recognized by the CLI (e.g.: it is a 3rd party board) or correctly configured but ready for `verify`.
|
||||
* If it has the selected board and a associated port, it can be used for `upload`.
|
||||
*/
|
||||
export interface AvailableBoard extends Board {
|
||||
readonly state: AvailableBoard.State;
|
||||
readonly selected?: boolean;
|
||||
readonly port?: Port;
|
||||
}
|
||||
|
||||
export namespace AvailableBoard {
|
||||
|
||||
export enum State {
|
||||
/**
|
||||
* Retrieved from the CLI via the `board list` command.
|
||||
*/
|
||||
'recognized',
|
||||
/**
|
||||
* Guessed the name/FQBN of the board from the available board ports (3rd party).
|
||||
*/
|
||||
'guessed',
|
||||
/**
|
||||
* We do not know anything about this board, probably a 3rd party. The user has not selected a board for this port yet.
|
||||
*/
|
||||
'incomplete'
|
||||
}
|
||||
|
||||
export function isWithPort(board: AvailableBoard): board is AvailableBoard & { port: Port } {
|
||||
return !!board.port;
|
||||
}
|
||||
|
||||
export const COMPARATOR = (left: AvailableBoard, right: AvailableBoard) => {
|
||||
let result = left.name.localeCompare(right.name);
|
||||
if (result !== 0) {
|
||||
return result;
|
||||
}
|
||||
if (left.fqbn && right.fqbn) {
|
||||
result = left.name.localeCompare(right.name);
|
||||
if (result !== 0) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
if (left.port && right.port) {
|
||||
result = Port.compare(left.port, right.port);
|
||||
if (result !== 0) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
if (!!left.selected && !right.selected) {
|
||||
return -1;
|
||||
}
|
||||
if (!!right.selected && !left.selected) {
|
||||
return 1;
|
||||
}
|
||||
return left.state - right.state;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import * as React from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import { CommandRegistry, DisposableCollection } from '@theia/core';
|
||||
import { BoardsService, Board, AttachedSerialBoard, Port } from '../../common/protocol/boards-service';
|
||||
import { ArduinoCommands } from '../arduino-commands';
|
||||
import { BoardsServiceClientImpl } from './boards-service-client-impl';
|
||||
import { CommandRegistry } from '@theia/core/lib/common/command';
|
||||
import { DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||
import { Port } from '../../common/protocol';
|
||||
import { BoardsConfig } from './boards-config';
|
||||
import { ArduinoCommands } from '../arduino-commands';
|
||||
import { BoardsServiceClientImpl, AvailableBoard } from './boards-service-client-impl';
|
||||
|
||||
export interface BoardsDropDownListCoords {
|
||||
readonly top: number;
|
||||
@@ -16,14 +17,9 @@ export interface BoardsDropDownListCoords {
|
||||
export namespace BoardsDropDown {
|
||||
export interface Props {
|
||||
readonly coords: BoardsDropDownListCoords | 'hidden';
|
||||
readonly items: Item[];
|
||||
readonly items: Array<AvailableBoard & { onClick: () => void, port: Port }>;
|
||||
readonly openBoardsConfig: () => void;
|
||||
}
|
||||
export interface Item {
|
||||
readonly label: string;
|
||||
readonly selected: boolean;
|
||||
readonly onClick: () => void;
|
||||
}
|
||||
}
|
||||
|
||||
export class BoardsDropDown extends React.Component<BoardsDropDown.Props> {
|
||||
@@ -51,48 +47,30 @@ export class BoardsDropDown extends React.Component<BoardsDropDown.Props> {
|
||||
if (coords === 'hidden') {
|
||||
return '';
|
||||
}
|
||||
items.push({
|
||||
label: 'Select Other Board & Port',
|
||||
selected: false,
|
||||
onClick: () => this.props.openBoardsConfig()
|
||||
})
|
||||
return <div className='arduino-boards-dropdown-list'
|
||||
style={{
|
||||
position: 'absolute',
|
||||
...coords
|
||||
}}>
|
||||
{items.map(this.renderItem)}
|
||||
{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(item: BoardsDropDown.Item): React.ReactNode {
|
||||
const { label, selected, onClick } = item;
|
||||
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'/> : ''}
|
||||
{selected ? <span className='fa fa-check' /> : ''}
|
||||
</div>
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace BoardsToolBarItem {
|
||||
|
||||
export interface Props {
|
||||
readonly boardService: BoardsService;
|
||||
readonly boardsServiceClient: BoardsServiceClientImpl;
|
||||
readonly commands: CommandRegistry;
|
||||
}
|
||||
|
||||
export interface State {
|
||||
boardsConfig: BoardsConfig.Config;
|
||||
attachedBoards: Board[];
|
||||
availablePorts: Port[];
|
||||
coords: BoardsDropDownListCoords | 'hidden';
|
||||
}
|
||||
}
|
||||
|
||||
export class BoardsToolBarItem extends React.Component<BoardsToolBarItem.Props, BoardsToolBarItem.State> {
|
||||
|
||||
static TOOLBAR_ID: 'boards-toolbar';
|
||||
@@ -102,10 +80,9 @@ export class BoardsToolBarItem extends React.Component<BoardsToolBarItem.Props,
|
||||
constructor(props: BoardsToolBarItem.Props) {
|
||||
super(props);
|
||||
|
||||
const { availableBoards } = props.boardsServiceClient;
|
||||
this.state = {
|
||||
boardsConfig: this.props.boardsServiceClient.boardsConfig,
|
||||
attachedBoards: [],
|
||||
availablePorts: [],
|
||||
availableBoards,
|
||||
coords: 'hidden'
|
||||
};
|
||||
|
||||
@@ -115,17 +92,7 @@ export class BoardsToolBarItem extends React.Component<BoardsToolBarItem.Props,
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { boardsServiceClient: client, boardService } = this.props;
|
||||
this.toDispose.pushAll([
|
||||
client.onBoardsConfigChanged(boardsConfig => this.setState({ boardsConfig })),
|
||||
client.onBoardsChanged(({ newState }) => this.setState({ attachedBoards: newState.boards, availablePorts: newState.ports }))
|
||||
]);
|
||||
Promise.all([
|
||||
boardService.getAttachedBoards(),
|
||||
boardService.getAvailablePorts()
|
||||
]).then(([{boards: attachedBoards}, { ports: availablePorts }]) => {
|
||||
this.setState({ attachedBoards, availablePorts })
|
||||
});
|
||||
this.props.boardsServiceClient.onAvailableBoardsChanged(availableBoards => this.setState({ availableBoards }));
|
||||
}
|
||||
|
||||
componentWillUnmount(): void {
|
||||
@@ -146,7 +113,7 @@ export class BoardsToolBarItem extends React.Component<BoardsToolBarItem.Props,
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.setState({ coords: 'hidden'});
|
||||
this.setState({ coords: 'hidden' });
|
||||
}
|
||||
}
|
||||
event.stopPropagation();
|
||||
@@ -154,41 +121,52 @@ export class BoardsToolBarItem extends React.Component<BoardsToolBarItem.Props,
|
||||
};
|
||||
|
||||
render(): React.ReactNode {
|
||||
const { boardsConfig, coords, attachedBoards, availablePorts } = this.state;
|
||||
const { coords, availableBoards } = this.state;
|
||||
const boardsConfig = this.props.boardsServiceClient.boardsConfig;
|
||||
const title = BoardsConfig.Config.toString(boardsConfig, { default: 'no board selected' });
|
||||
const configuredBoard = attachedBoards
|
||||
.filter(AttachedSerialBoard.is)
|
||||
.filter(board => availablePorts.some(port => Port.sameAs(port, board.port)))
|
||||
.filter(board => BoardsConfig.Config.sameAs(boardsConfig, board)).shift();
|
||||
|
||||
const items = attachedBoards.filter(AttachedSerialBoard.is).map(board => ({
|
||||
label: `${board.name} at ${board.port}`,
|
||||
selected: configuredBoard === board,
|
||||
onClick: () => {
|
||||
this.props.boardsServiceClient.boardsConfig = {
|
||||
selectedBoard: board,
|
||||
selectedPort: availablePorts.find(port => Port.sameAs(port, board.port))
|
||||
}
|
||||
const decorator = (() => {
|
||||
const selectedBoard = availableBoards.find(({ selected }) => selected);
|
||||
if (!selectedBoard || !selectedBoard.port) {
|
||||
return 'fa fa-times notAttached'
|
||||
}
|
||||
}));
|
||||
if (selectedBoard.state === AvailableBoard.State.guessed) {
|
||||
return 'fa fa-exclamation-triangle guessed'
|
||||
}
|
||||
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={!configuredBoard ? 'fa fa-times notAttached' : ''}/>
|
||||
<span className={decorator} />
|
||||
<div className='label noWrapInfo'>
|
||||
<div className='noWrapInfo noselect'>
|
||||
{title}
|
||||
</div>
|
||||
</div>
|
||||
<span className='fa fa-caret-down caret'/>
|
||||
<span className='fa fa-caret-down caret' />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<BoardsDropDown
|
||||
coords={coords}
|
||||
items={items}
|
||||
items={availableBoards.filter(AvailableBoard.isWithPort).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>;
|
||||
@@ -200,3 +178,16 @@ export class BoardsToolBarItem extends React.Component<BoardsToolBarItem.Props,
|
||||
};
|
||||
|
||||
}
|
||||
export namespace BoardsToolBarItem {
|
||||
|
||||
export interface Props {
|
||||
readonly boardsServiceClient: BoardsServiceClientImpl;
|
||||
readonly commands: CommandRegistry;
|
||||
}
|
||||
|
||||
export interface State {
|
||||
availableBoards: AvailableBoard[];
|
||||
coords: BoardsDropDownListCoords | 'hidden';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,11 +2,11 @@ import { injectable } from 'inversify';
|
||||
import { MenuModelRegistry } from '@theia/core';
|
||||
import { BoardsListWidget } from './boards-list-widget';
|
||||
import { ArduinoMenus } from '../arduino-frontend-contribution';
|
||||
import { BoardPackage } from '../../common/protocol/boards-service';
|
||||
import { BoardsPackage } from '../../common/protocol/boards-service';
|
||||
import { ListWidgetFrontendContribution } from '../components/component-list/list-widget-frontend-contribution';
|
||||
|
||||
@injectable()
|
||||
export class BoardsListWidgetFrontendContribution extends ListWidgetFrontendContribution<BoardPackage> {
|
||||
export class BoardsListWidgetFrontendContribution extends ListWidgetFrontendContribution<BoardsPackage> {
|
||||
|
||||
static readonly OPEN_MANAGER = `${BoardsListWidget.WIDGET_ID}:toggle`;
|
||||
|
||||
|
||||
@@ -72,12 +72,7 @@ export class FilterableListContainer<T extends ArduinoComponent> extends React.C
|
||||
|
||||
protected search(query: string): void {
|
||||
const { searchable } = this.props;
|
||||
searchable.search({ query: query.trim() }).then(result => {
|
||||
const { items } = result;
|
||||
this.setState({
|
||||
items: this.sort(items)
|
||||
});
|
||||
});
|
||||
searchable.search({ query: query.trim() }).then(items => this.setState({ items: this.sort(items) }));
|
||||
}
|
||||
|
||||
protected sort(items: T[]): T[] {
|
||||
@@ -91,7 +86,7 @@ export class FilterableListContainer<T extends ArduinoComponent> extends React.C
|
||||
dialog.open();
|
||||
try {
|
||||
await installable.install({ item, version });
|
||||
const { items } = await searchable.search({ query: this.state.filterText });
|
||||
const items = await searchable.search({ query: this.state.filterText });
|
||||
this.setState({ items: this.sort(items) });
|
||||
} finally {
|
||||
dialog.close();
|
||||
@@ -113,7 +108,7 @@ export class FilterableListContainer<T extends ArduinoComponent> extends React.C
|
||||
dialog.open();
|
||||
try {
|
||||
await installable.uninstall({ item });
|
||||
const { items } = await searchable.search({ query: this.state.filterText });
|
||||
const items = await searchable.search({ query: this.state.filterText });
|
||||
this.setState({ items: this.sort(items) });
|
||||
} finally {
|
||||
dialog.close();
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import { injectable } from 'inversify';
|
||||
import { Emitter } from '@theia/core/lib/common/event';
|
||||
import { injectable, inject } from 'inversify';
|
||||
import { ApplicationShell, FrontendApplicationContribution, FrontendApplication, Widget } from '@theia/core/lib/browser';
|
||||
import { OutputWidget } from '@theia/output/lib/browser/output-widget';
|
||||
import { EditorWidget } from '@theia/editor/lib/browser';
|
||||
import { ArduinoShellLayoutRestorer } from './shell/arduino-shell-layout-restorer';
|
||||
import { OutputWidget } from '@theia/output/lib/browser/output-widget';
|
||||
import { MainMenuManager } from './menu/main-menu-manager';
|
||||
import { BoardsListWidget } from './boards/boards-list-widget';
|
||||
import { LibraryListWidget } from './library/library-list-widget';
|
||||
import { ArduinoShellLayoutRestorer } from './shell/arduino-shell-layout-restorer';
|
||||
|
||||
@injectable()
|
||||
export class EditorMode implements FrontendApplicationContribution {
|
||||
|
||||
readonly menuContentChanged = new Emitter<void>();
|
||||
@inject(MainMenuManager)
|
||||
protected readonly mainMenuManager: MainMenuManager;
|
||||
|
||||
protected app: FrontendApplication;
|
||||
|
||||
@@ -62,6 +63,7 @@ export class EditorMode implements FrontendApplicationContribution {
|
||||
const oldState = this.compileForDebug;
|
||||
const newState = !oldState;
|
||||
window.localStorage.setItem(EditorMode.COMPILE_FOR_DEBUG_KEY, String(newState));
|
||||
this.mainMenuManager.update();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ import { injectable, inject, postConstruct } from 'inversify';
|
||||
import { BaseLanguageClientContribution } from '@theia/languages/lib/browser';
|
||||
import { BoardsServiceClientImpl } from '../boards/boards-service-client-impl';
|
||||
import { BoardsConfig } from '../boards/boards-config';
|
||||
import { Board, BoardPackage } from '../../common/protocol/boards-service';
|
||||
|
||||
@injectable()
|
||||
export class ArduinoLanguageClientContribution extends BaseLanguageClientContribution {
|
||||
@@ -26,18 +25,6 @@ export class ArduinoLanguageClientContribution extends BaseLanguageClientContrib
|
||||
@postConstruct()
|
||||
protected init() {
|
||||
this.boardsServiceClient.onBoardsConfigChanged(this.selectBoard.bind(this));
|
||||
const restartIfAffected = (pkg: BoardPackage) => {
|
||||
if (!this.boardConfig) {
|
||||
this.restart();
|
||||
return;
|
||||
}
|
||||
const { selectedBoard } = this.boardConfig;
|
||||
if (selectedBoard && pkg.boards.some(board => Board.sameAs(board, selectedBoard))) {
|
||||
this.restart();
|
||||
}
|
||||
}
|
||||
this.boardsServiceClient.onBoardInstalled(({ pkg }) => restartIfAffected(pkg));
|
||||
this.boardsServiceClient.onBoardUninstalled(({ pkg }) => restartIfAffected(pkg));
|
||||
}
|
||||
|
||||
selectBoard(config: BoardsConfig.Config): void {
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { injectable } from 'inversify';
|
||||
import { BrowserMainMenuFactory, MenuBarWidget } from '@theia/core/lib/browser/menu/browser-menu-plugin';
|
||||
import { MainMenuManager } from './main-menu-manager';
|
||||
|
||||
@injectable()
|
||||
export class ArduinoBrowserMainMenuFactory extends BrowserMainMenuFactory implements MainMenuManager {
|
||||
|
||||
protected menuBar: MenuBarWidget | undefined;
|
||||
|
||||
createMenuBar(): MenuBarWidget {
|
||||
this.menuBar = super.createMenuBar();
|
||||
return this.menuBar;
|
||||
}
|
||||
|
||||
update() {
|
||||
if (this.menuBar) {
|
||||
this.menuBar.clearMenus();
|
||||
this.fillMenuBar(this.menuBar);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,10 +1,14 @@
|
||||
import { BrowserMenuBarContribution } from '@theia/core/lib/browser/menu/browser-menu-plugin';
|
||||
import { ArduinoMenuContribution } from './arduino-menu-contribution';
|
||||
import { ContainerModule, interfaces } from 'inversify';
|
||||
|
||||
import '../../../src/browser/style/browser-menu.css'
|
||||
import { ContainerModule } from 'inversify';
|
||||
import { BrowserMenuBarContribution, BrowserMainMenuFactory } from '@theia/core/lib/browser/menu/browser-menu-plugin';
|
||||
import { MainMenuManager } from './main-menu-manager';
|
||||
import { ArduinoMenuContribution } from './arduino-menu-contribution';
|
||||
import { ArduinoBrowserMainMenuFactory } from './arduino-browser-main-menu-factory';
|
||||
|
||||
export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Unbind) => {
|
||||
unbind(BrowserMenuBarContribution);
|
||||
bind(BrowserMenuBarContribution).to(ArduinoMenuContribution).inSingletonScope();
|
||||
|
||||
export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
bind(ArduinoBrowserMainMenuFactory).toSelf().inSingletonScope();
|
||||
bind(MainMenuManager).toService(ArduinoBrowserMainMenuFactory);
|
||||
rebind(BrowserMainMenuFactory).toService(ArduinoBrowserMainMenuFactory);
|
||||
rebind(BrowserMenuBarContribution).to(ArduinoMenuContribution).inSingletonScope();
|
||||
});
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
export const MainMenuManager = Symbol('MainMenuManager');
|
||||
export interface MainMenuManager {
|
||||
/**
|
||||
* Call this method if you have changed the content of the main menu (updated a toggle flag, removed/added new groups or menu items)
|
||||
* and you want to re-render it from scratch. Works for electron too.
|
||||
*/
|
||||
update(): void;
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import { MessageService } from '@theia/core/lib/common/message-service';
|
||||
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
|
||||
import { MonitorService, MonitorConfig, MonitorError, Status, MonitorReadEvent } from '../../common/protocol/monitor-service';
|
||||
import { BoardsServiceClientImpl } from '../boards/boards-service-client-impl';
|
||||
import { Port, Board, BoardsService, AttachedSerialBoard, AttachedBoardsChangeEvent } from '../../common/protocol/boards-service';
|
||||
import { Port, Board, BoardsService, AttachedBoardsChangeEvent } from '../../common/protocol/boards-service';
|
||||
import { MonitorServiceClientImpl } from './monitor-service-client-impl';
|
||||
import { BoardsConfig } from '../boards/boards-config';
|
||||
import { MonitorModel } from './monitor-model';
|
||||
@@ -110,12 +110,12 @@ export class MonitorConnection {
|
||||
}
|
||||
});
|
||||
this.boardsServiceClient.onBoardsConfigChanged(this.handleBoardConfigChange.bind(this));
|
||||
this.boardsServiceClient.onBoardsChanged(event => {
|
||||
this.boardsServiceClient.onAttachedBoardsChanged(event => {
|
||||
if (this.autoConnect && this.connected) {
|
||||
const { boardsConfig } = this.boardsServiceClient;
|
||||
if (this.boardsServiceClient.canUploadTo(boardsConfig, { silent: false })) {
|
||||
const { attached } = AttachedBoardsChangeEvent.diff(event);
|
||||
if (attached.boards.some(board => AttachedSerialBoard.is(board) && BoardsConfig.Config.sameAs(boardsConfig, board))) {
|
||||
if (attached.boards.some(board => !!board.port && BoardsConfig.Config.sameAs(boardsConfig, board))) {
|
||||
const { selectedBoard: board, selectedPort: port } = boardsConfig;
|
||||
const { baudRate } = this.monitorModel;
|
||||
this.disconnect()
|
||||
@@ -225,7 +225,7 @@ export class MonitorConnection {
|
||||
if (this.boardsServiceClient.canUploadTo(boardsConfig, { silent: false })) {
|
||||
// Instead of calling `getAttachedBoards` and filtering for `AttachedSerialBoard` we have to check the available ports.
|
||||
// The connected board might be unknown. See: https://github.com/arduino/arduino-pro-ide/issues/127#issuecomment-563251881
|
||||
this.boardsService.getAvailablePorts().then(({ ports }) => {
|
||||
this.boardsService.getAvailablePorts().then(ports => {
|
||||
if (ports.some(port => Port.equals(port, boardsConfig.selectedPort))) {
|
||||
new Promise<void>(resolve => {
|
||||
// First, disconnect if connected.
|
||||
|
||||
@@ -178,7 +178,7 @@ export class MonitorWidget extends ReactWidget {
|
||||
this.monitorModel.lineEnding = option.value;
|
||||
}
|
||||
|
||||
protected readonly onChangeBaudRate = async (option: SelectOption<MonitorConfig.BaudRate>) => {
|
||||
protected readonly onChangeBaudRate = (option: SelectOption<MonitorConfig.BaudRate>) => {
|
||||
this.monitorModel.baudRate = option.value;
|
||||
}
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ div#select-board-dialog .selectBoardContainer .body .list .item.selected i {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
#select-board-dialog .selectBoardContainer .body .list .item .detail {
|
||||
#select-board-dialog .selectBoardContainer .body .list .item .details {
|
||||
font-size: var(--theia-ui-font-size1);
|
||||
opacity: var(--theia-mod-disabled-opacity);
|
||||
width: 155px; /* used heuristics for the calculation */
|
||||
@@ -169,6 +169,13 @@ button.theia-button.main {
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
.arduino-boards-toolbar-item-container .arduino-boards-toolbar-item .inner-container .guessed {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
color: var(--theia-warningBackground);
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
.arduino-boards-toolbar-item-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
See above: `.filterable-list-container .items-container > div:nth-child(odd|event)`.
|
||||
We have to increase `z-index` of the scroll-bar thumb. Otherwise, the thumb is not visible.
|
||||
https://github.com/arduino/arduino-pro-ide/issues/82 */
|
||||
.arduino-list-widget .ps__rail-y > .ps__thumb-y {
|
||||
.arduino-list-widget .filterable-list-container .items-container .ps__rail-y {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user