mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-07-10 21:06:33 +00:00
commit
94233a1a19
22
.vscode/settings.json
vendored
22
.vscode/settings.json
vendored
@ -1,3 +1,21 @@
|
||||
{
|
||||
"typescript.tsdk": "node_modules/typescript/lib"
|
||||
}
|
||||
"tslint.enable": true,
|
||||
"tslint.configFile": "./tslint.json",
|
||||
"editor.formatOnSave": true,
|
||||
"files.exclude": {
|
||||
"**/lib": false
|
||||
},
|
||||
"editor.insertSpaces": true,
|
||||
"editor.detectIndentation": false,
|
||||
"[typescript]": {
|
||||
"editor.tabSize": 4
|
||||
},
|
||||
"[json]": {
|
||||
"editor.tabSize": 2
|
||||
},
|
||||
"[jsonc]": {
|
||||
"editor.tabSize": 2
|
||||
},
|
||||
"files.insertFinalNewline": true,
|
||||
"typescript.tsdk": "node_modules/typescript/lib"
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "arduino-ide-extension",
|
||||
"version": "0.0.2",
|
||||
"version": "0.0.3",
|
||||
"description": "An extension for Theia building the Arduino IDE",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@ -58,7 +58,7 @@
|
||||
"rimraf": "^2.6.1",
|
||||
"shelljs": "^0.8.3",
|
||||
"tslint": "^5.5.0",
|
||||
"typescript": "2.9.1",
|
||||
"typescript": "3.5.3",
|
||||
"uuid": "^3.2.1",
|
||||
"yargs": "^11.1.0"
|
||||
},
|
||||
|
@ -3,7 +3,7 @@ import { injectable, inject, postConstruct } from 'inversify';
|
||||
import URI from '@theia/core/lib/common/uri';
|
||||
import { EditorWidget } from '@theia/editor/lib/browser/editor-widget';
|
||||
import { MessageService } from '@theia/core/lib/common/message-service';
|
||||
import { CommandContribution, CommandRegistry, Command } from '@theia/core/lib/common/command';
|
||||
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 { ArduinoCommands } from './arduino-commands';
|
||||
@ -22,9 +22,11 @@ import {
|
||||
OpenerService,
|
||||
Widget,
|
||||
StatusBar,
|
||||
ShellLayoutRestorer,
|
||||
StatusBarAlignment,
|
||||
QuickOpenService
|
||||
QuickOpenService,
|
||||
ApplicationShell,
|
||||
FrontendApplicationContribution,
|
||||
FrontendApplication
|
||||
} from '@theia/core/lib/browser';
|
||||
import { OpenFileDialogProps, FileDialogService } from '@theia/filesystem/lib/browser/file-dialog';
|
||||
import { FileSystem, FileStat } from '@theia/filesystem/lib/common';
|
||||
@ -44,6 +46,14 @@ import { ConfigService } from '../common/protocol/config-service';
|
||||
import { MonitorConnection } from './monitor/monitor-connection';
|
||||
import { MonitorViewContribution } from './monitor/monitor-view-contribution';
|
||||
import { ArduinoWorkspaceService } from './arduino-workspace-service';
|
||||
import { FileNavigatorContribution } from '@theia/navigator/lib/browser/navigator-contribution';
|
||||
import { OutlineViewContribution } from '@theia/outline-view/lib/browser/outline-view-contribution';
|
||||
import { ProblemContribution } from '@theia/markers/lib/browser/problem/problem-contribution';
|
||||
import { ScmContribution } from '@theia/scm/lib/browser/scm-contribution';
|
||||
import { SearchInWorkspaceFrontendContribution } from '@theia/search-in-workspace/lib/browser/search-in-workspace-frontend-contribution';
|
||||
import { FileNavigatorCommands } from '@theia/navigator/lib/browser/navigator-contribution';
|
||||
import { ArduinoShellLayoutRestorer } from './shell/arduino-shell-layout-restorer';
|
||||
import { EditorMode } from './editor-mode';
|
||||
|
||||
export namespace ArduinoMenus {
|
||||
export const SKETCH = [...MAIN_MENU_BAR, '3_sketch'];
|
||||
@ -57,16 +67,8 @@ export namespace ArduinoToolbarContextMenu {
|
||||
export const EXAMPLE_SKETCHES_GROUP: MenuPath = [...OPEN_SKETCH_PATH, '3_examples'];
|
||||
}
|
||||
|
||||
export namespace ArduinoAdvancedMode {
|
||||
export const LS_ID = 'arduino-advanced-mode';
|
||||
export const TOGGLED: boolean = (() => {
|
||||
const advancedModeStr = window.localStorage.getItem(LS_ID);
|
||||
return advancedModeStr === 'true';
|
||||
})();
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class ArduinoFrontendContribution implements TabBarToolbarContribution, CommandContribution, MenuContribution {
|
||||
export class ArduinoFrontendContribution implements FrontendApplicationContribution, TabBarToolbarContribution, CommandContribution, MenuContribution {
|
||||
|
||||
@inject(MessageService)
|
||||
protected readonly messageService: MessageService;
|
||||
@ -126,13 +128,13 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C
|
||||
protected readonly menuRegistry: MenuModelRegistry;
|
||||
|
||||
@inject(CommandRegistry)
|
||||
protected readonly commands: CommandRegistry;
|
||||
protected readonly commandRegistry: CommandRegistry;
|
||||
|
||||
@inject(StatusBar)
|
||||
protected readonly statusBar: StatusBar;
|
||||
|
||||
@inject(ShellLayoutRestorer)
|
||||
protected readonly layoutRestorer: ShellLayoutRestorer;
|
||||
@inject(ArduinoShellLayoutRestorer)
|
||||
protected readonly layoutRestorer: ArduinoShellLayoutRestorer;
|
||||
|
||||
@inject(QuickOpenService)
|
||||
protected readonly quickOpenService: QuickOpenService;
|
||||
@ -146,8 +148,29 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C
|
||||
@inject(MonitorConnection)
|
||||
protected readonly monitorConnection: MonitorConnection;
|
||||
|
||||
protected boardsToolbarItem: BoardsToolBarItem | null;
|
||||
protected wsSketchCount: number = 0;
|
||||
@inject(ApplicationShell)
|
||||
protected readonly shell: ApplicationShell;
|
||||
|
||||
@inject(FileNavigatorContribution)
|
||||
protected readonly fileNavigatorContributions: FileNavigatorContribution;
|
||||
|
||||
@inject(OutlineViewContribution)
|
||||
protected readonly outlineContribution: OutlineViewContribution;
|
||||
|
||||
@inject(ProblemContribution)
|
||||
protected readonly problemContribution: ProblemContribution;
|
||||
|
||||
@inject(ScmContribution)
|
||||
protected readonly scmContribution: ScmContribution;
|
||||
|
||||
@inject(SearchInWorkspaceFrontendContribution)
|
||||
protected readonly siwContribution: SearchInWorkspaceFrontendContribution;
|
||||
|
||||
@inject(EditorMode)
|
||||
protected readonly editorMode: EditorMode;
|
||||
|
||||
protected application: FrontendApplication;
|
||||
protected wsSketchCount: number = 0; // TODO: this does not belong here, does it?
|
||||
|
||||
@postConstruct()
|
||||
protected async init(): Promise<void> {
|
||||
@ -171,6 +194,22 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C
|
||||
]).then(([{ boards }, { ports }]) => this.boardsServiceClient.tryReconnect(boards, ports));
|
||||
}
|
||||
|
||||
onStart(app: FrontendApplication): void {
|
||||
this.application = app;
|
||||
// Initialize all `pro-mode` widgets. This is a NOOP if in normal mode.
|
||||
for (const viewContribution of [
|
||||
this.fileNavigatorContributions,
|
||||
this.outlineContribution,
|
||||
this.problemContribution,
|
||||
this.scmContribution,
|
||||
this.siwContribution] as Array<FrontendApplicationContribution>) {
|
||||
|
||||
if (viewContribution.initializeLayout) {
|
||||
viewContribution.initializeLayout(this.application);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
registerToolbarItems(registry: TabBarToolbarRegistry): void {
|
||||
registry.registerItem({
|
||||
id: ArduinoCommands.VERIFY.id,
|
||||
@ -196,8 +235,7 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C
|
||||
id: BoardsToolBarItem.TOOLBAR_ID,
|
||||
render: () => <BoardsToolBarItem
|
||||
key='boardsToolbarItem'
|
||||
ref={ref => this.boardsToolbarItem = ref}
|
||||
commands={this.commands}
|
||||
commands={this.commandRegistry}
|
||||
boardsServiceClient={this.boardsServiceClient}
|
||||
boardService={this.boardsService} />,
|
||||
isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'left'
|
||||
@ -213,12 +251,48 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C
|
||||
id: ArduinoCommands.TOGGLE_ADVANCED_MODE.id,
|
||||
command: ArduinoCommands.TOGGLE_ADVANCED_MODE.id,
|
||||
tooltip: 'Toggle Advanced Mode',
|
||||
text: (ArduinoAdvancedMode.TOGGLED ? '$(toggle-on)' : '$(toggle-off)'),
|
||||
text: (this.editorMode.proMode ? '$(toggle-on)' : '$(toggle-off)'),
|
||||
isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'right'
|
||||
});
|
||||
}
|
||||
|
||||
registerCommands(registry: CommandRegistry): void {
|
||||
// TODO: use proper API https://github.com/eclipse-theia/theia/pull/6599
|
||||
const allHandlers: { [id: string]: CommandHandler[] } = (registry as any)._handlers;
|
||||
// Make sure to reveal the `Explorer` before executing `New File` and `New Folder`.
|
||||
for (const command of [WorkspaceCommands.NEW_FILE, WorkspaceCommands.NEW_FOLDER]) {
|
||||
const { id } = command;
|
||||
const handlers = allHandlers[id].slice();
|
||||
registry.unregisterCommand(id);
|
||||
registry.registerCommand(command);
|
||||
for (const handler of handlers) {
|
||||
const wrapper: CommandHandler = {
|
||||
execute: (...args: any[]) => {
|
||||
this.fileNavigatorContributions.openView({ reveal: true }).then(() => handler.execute(args));
|
||||
},
|
||||
isVisible: (...args: any[]) => {
|
||||
return handler.isVisible!(args);
|
||||
},
|
||||
isEnabled: (args: any[]) => {
|
||||
return handler.isEnabled!(args);
|
||||
},
|
||||
isToggled: (args: any[]) => {
|
||||
return handler.isToggled!(args);
|
||||
}
|
||||
};
|
||||
if (!handler.isEnabled) {
|
||||
delete wrapper.isEnabled;
|
||||
}
|
||||
if (!handler.isToggled) {
|
||||
delete wrapper.isToggled;
|
||||
}
|
||||
if (!handler.isVisible) {
|
||||
delete wrapper.isVisible;
|
||||
}
|
||||
registry.registerHandler(id, wrapper);
|
||||
}
|
||||
}
|
||||
|
||||
registry.registerCommand(ArduinoCommands.VERIFY, {
|
||||
isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'left',
|
||||
isEnabled: widget => true,
|
||||
@ -296,7 +370,7 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.commands.executeCommand(ArduinoCommands.OPEN_FILE_NAVIGATOR.id);
|
||||
this.commandRegistry.executeCommand(ArduinoCommands.OPEN_FILE_NAVIGATOR.id);
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -342,18 +416,14 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C
|
||||
}
|
||||
})
|
||||
registry.registerCommand(ArduinoCommands.TOGGLE_ADVANCED_MODE, {
|
||||
execute: () => {
|
||||
const oldModeState = ArduinoAdvancedMode.TOGGLED;
|
||||
window.localStorage.setItem(ArduinoAdvancedMode.LS_ID, oldModeState ? 'false' : 'true');
|
||||
registry.executeCommand('reset.layout');
|
||||
},
|
||||
execute: () => this.editorMode.toggle(),
|
||||
isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'right',
|
||||
isToggled: () => ArduinoAdvancedMode.TOGGLED
|
||||
isToggled: () => this.editorMode.proMode
|
||||
})
|
||||
}
|
||||
|
||||
registerMenus(registry: MenuModelRegistry) {
|
||||
if (!ArduinoAdvancedMode.TOGGLED) {
|
||||
if (!this.editorMode.proMode) {
|
||||
// If are not in pro-mode, we have to disable the context menu for the tabs.
|
||||
// Such as `Close`, `Close All`, etc.
|
||||
for (const command of [
|
||||
@ -362,7 +432,8 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C
|
||||
CommonCommands.CLOSE_RIGHT_TABS,
|
||||
CommonCommands.CLOSE_ALL_TABS,
|
||||
CommonCommands.COLLAPSE_PANEL,
|
||||
CommonCommands.TOGGLE_MAXIMIZED
|
||||
CommonCommands.TOGGLE_MAXIMIZED,
|
||||
FileNavigatorCommands.REVEAL_IN_NAVIGATOR
|
||||
]) {
|
||||
registry.unregisterMenuAction(command);
|
||||
}
|
||||
@ -370,8 +441,6 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C
|
||||
registry.unregisterMenuAction(FileSystemCommands.UPLOAD);
|
||||
registry.unregisterMenuAction(FileDownloadCommands.DOWNLOAD);
|
||||
|
||||
registry.unregisterMenuAction(WorkspaceCommands.NEW_FOLDER);
|
||||
|
||||
registry.unregisterMenuAction(WorkspaceCommands.OPEN_FOLDER);
|
||||
registry.unregisterMenuAction(WorkspaceCommands.OPEN_WORKSPACE);
|
||||
registry.unregisterMenuAction(WorkspaceCommands.OPEN_RECENT_WORKSPACE);
|
||||
@ -425,8 +494,8 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C
|
||||
const command: Command = {
|
||||
id: 'openSketch' + sketch.name
|
||||
}
|
||||
this.commands.registerCommand(command, {
|
||||
execute: () => this.commands.executeCommand(ArduinoCommands.OPEN_SKETCH.id, sketch)
|
||||
this.commandRegistry.registerCommand(command, {
|
||||
execute: () => this.commandRegistry.executeCommand(ArduinoCommands.OPEN_SKETCH.id, sketch)
|
||||
});
|
||||
|
||||
registry.registerMenuAction(ArduinoToolbarContextMenu.WS_SKETCHES_GROUP, {
|
||||
@ -466,7 +535,7 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C
|
||||
if (destinationFile && !destinationFile.isDirectory) {
|
||||
const message = await this.validate(destinationFile);
|
||||
if (!message) {
|
||||
await this.workspaceService.open(destinationFileUri);
|
||||
this.workspaceService.open(destinationFileUri);
|
||||
return destinationFileUri;
|
||||
} else {
|
||||
this.messageService.warn(message);
|
||||
|
@ -10,7 +10,7 @@ import { LanguageGrammarDefinitionContribution } from '@theia/monaco/lib/browser
|
||||
import { LanguageClientContribution } from '@theia/languages/lib/browser';
|
||||
import { ArduinoLanguageClientContribution } from './language/arduino-language-client-contribution';
|
||||
import { LibraryListWidget } from './library/library-list-widget';
|
||||
import { ArduinoFrontendContribution, ArduinoAdvancedMode } from './arduino-frontend-contribution';
|
||||
import { ArduinoFrontendContribution } from './arduino-frontend-contribution';
|
||||
import { ArduinoLanguageGrammarContribution } from './language/arduino-language-grammar-contribution';
|
||||
import { LibraryService, LibraryServicePath } from '../common/protocol/library-service';
|
||||
import { BoardsService, BoardsServicePath, BoardsServiceClient } from '../common/protocol/boards-service';
|
||||
@ -30,30 +30,28 @@ import { ThemeService } from '@theia/core/lib/browser/theming';
|
||||
import { ArduinoTheme } from './arduino-theme';
|
||||
import { MenuContribution } from '@theia/core';
|
||||
import { OutlineViewContribution } from '@theia/outline-view/lib/browser/outline-view-contribution';
|
||||
import { SilentOutlineViewContribution } from './customization/silent-outline-contribution';
|
||||
import { ArduinoOutlineViewContribution } from './customization/arduino-outline-contribution';
|
||||
import { ProblemContribution } from '@theia/markers/lib/browser/problem/problem-contribution';
|
||||
import { SilentProblemContribution } from './customization/silent-problem-contribution';
|
||||
import { SilentNavigatorContribution } from './customization/silent-navigator-contribution';
|
||||
import { ArduinoProblemContribution } from './customization/arduino-problem-contribution';
|
||||
import { ArduinoNavigatorContribution } from './customization/arduino-navigator-contribution';
|
||||
import { FileNavigatorContribution } from '@theia/navigator/lib/browser/navigator-contribution';
|
||||
import { ArduinoToolbarContribution } from './toolbar/arduino-toolbar-contribution';
|
||||
import { OutputToolbarContribution } from '@theia/output/lib/browser/output-toolbar-contribution';
|
||||
import { ArduinoOutputToolContribution } from './customization/silent-output-tool-contribution';
|
||||
import { ArduinoOutputToolContribution } from './customization/arduino-output-tool-contribution';
|
||||
import { EditorContribution } from '@theia/editor/lib/browser/editor-contribution';
|
||||
import { ArduinoEditorContribution } from './customization/arduino-editor-contribution';
|
||||
import { MonacoStatusBarContribution } from '@theia/monaco/lib/browser/monaco-status-bar-contribution';
|
||||
import { ArduinoMonacoStatusBarContribution } from './customization/arduino-monaco-status-bar-contribution';
|
||||
import { ApplicationShell } from '@theia/core/lib/browser';
|
||||
import { ApplicationShell, ShellLayoutRestorer } from '@theia/core/lib/browser';
|
||||
import { ArduinoApplicationShell } from './customization/arduino-application-shell';
|
||||
import { ArduinoFrontendApplication } from './customization/arduino-frontend-application';
|
||||
import { BoardsConfigDialog, BoardsConfigDialogProps } from './boards/boards-config-dialog';
|
||||
import { BoardsConfigDialogWidget } from './boards/boards-config-dialog-widget';
|
||||
import { ScmContribution } from '@theia/scm/lib/browser/scm-contribution';
|
||||
import { SilentScmContribution } from './customization/silent-scm-contribution';
|
||||
import { ArduinoScmContribution } from './customization/arduino-scm-contribution';
|
||||
import { SearchInWorkspaceFrontendContribution } from '@theia/search-in-workspace/lib/browser/search-in-workspace-frontend-contribution';
|
||||
import { SilentSearchInWorkspaceContribution } from './customization/silent-search-in-workspace-contribution';
|
||||
import { ArduinoSearchInWorkspaceContribution } from './customization/arduino-search-in-workspace-contribution';
|
||||
import { LibraryListWidgetFrontendContribution } from './library/library-widget-frontend-contribution';
|
||||
import { LibraryItemRenderer } from './library/library-item-renderer';
|
||||
import { BoardItemRenderer } from './boards/boards-item-renderer';
|
||||
import { MonitorServiceClientImpl } from './monitor/monitor-service-client-impl';
|
||||
import { MonitorServicePath, MonitorService, MonitorServiceClient } from '../common/protocol/monitor-service';
|
||||
import { ConfigService, ConfigServicePath } from '../common/protocol/config-service';
|
||||
@ -70,6 +68,9 @@ import { ArduinoProblemManager } from './markers/arduino-problem-manager';
|
||||
import { BoardsAutoInstaller } from './boards/boards-auto-installer';
|
||||
import { AboutDialog } from '@theia/core/lib/browser/about-dialog';
|
||||
import { ArduinoAboutDialog } from './customization/arduino-about-dialog';
|
||||
import { ArduinoShellLayoutRestorer } from './shell/arduino-shell-layout-restorer';
|
||||
import { EditorMode } from './editor-mode';
|
||||
import { ListItemRenderer } from './components/component-list/list-item-renderer';
|
||||
const ElementQueries = require('css-element-queries/src/ElementQueries');
|
||||
|
||||
export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Unbind, isBound: interfaces.IsBound, rebind: interfaces.Rebind) => {
|
||||
@ -90,9 +91,11 @@ export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Un
|
||||
bind(LanguageGrammarDefinitionContribution).to(ArduinoLanguageGrammarContribution).inSingletonScope();
|
||||
bind(LanguageClientContribution).to(ArduinoLanguageClientContribution).inSingletonScope();
|
||||
|
||||
// Renderer for both the library and the core widgets.
|
||||
bind(ListItemRenderer).toSelf().inSingletonScope();
|
||||
|
||||
// Library service
|
||||
bind(LibraryService).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, LibraryServicePath)).inSingletonScope();
|
||||
|
||||
// Library list widget
|
||||
bind(LibraryListWidget).toSelf();
|
||||
bindViewContribution(bind, LibraryListWidgetFrontendContribution);
|
||||
@ -101,7 +104,6 @@ export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Un
|
||||
createWidget: () => context.container.get(LibraryListWidget)
|
||||
}));
|
||||
bind(FrontendApplicationContribution).toService(LibraryListWidgetFrontendContribution);
|
||||
bind(LibraryItemRenderer).toSelf().inSingletonScope();
|
||||
|
||||
// Sketch list service
|
||||
bind(SketchesService).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, SketchesServicePath)).inSingletonScope();
|
||||
@ -135,7 +137,6 @@ export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Un
|
||||
createWidget: () => context.container.get(BoardsListWidget)
|
||||
}));
|
||||
bind(FrontendApplicationContribution).toService(BoardsListWidgetFrontendContribution);
|
||||
bind(BoardItemRenderer).toSelf().inSingletonScope();
|
||||
|
||||
// Board select dialog
|
||||
bind(BoardsConfigDialogWidget).toSelf().inSingletonScope();
|
||||
@ -158,7 +159,7 @@ export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Un
|
||||
}).inSingletonScope();
|
||||
|
||||
// The workspace service extension
|
||||
bind(WorkspaceServiceExt).to(WorkspaceServiceExtImpl).inSingletonScope().onActivation(({ container }, workspaceServiceExt) => {
|
||||
bind(WorkspaceServiceExt).to(WorkspaceServiceExtImpl).inSingletonScope().onActivation(({ container }, workspaceServiceExt: WorkspaceServiceExt) => {
|
||||
WebSocketConnectionProvider.createProxy(container, WorkspaceServiceExtPath, workspaceServiceExt);
|
||||
// Eagerly active the core, library, and boards services.
|
||||
container.get(CoreService);
|
||||
@ -198,50 +199,37 @@ export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Un
|
||||
const themeService = ThemeService.get();
|
||||
themeService.register(...ArduinoTheme.themes);
|
||||
|
||||
// Customizing default Theia layout
|
||||
if (!ArduinoAdvancedMode.TOGGLED) {
|
||||
unbind(OutlineViewContribution);
|
||||
bind(OutlineViewContribution).to(SilentOutlineViewContribution).inSingletonScope();
|
||||
unbind(ProblemContribution);
|
||||
bind(ProblemContribution).to(SilentProblemContribution).inSingletonScope();
|
||||
unbind(FileNavigatorContribution);
|
||||
bind(FileNavigatorContribution).to(SilentNavigatorContribution).inSingletonScope();
|
||||
unbind(OutputToolbarContribution);
|
||||
bind(OutputToolbarContribution).to(ArduinoOutputToolContribution).inSingletonScope();
|
||||
unbind(EditorContribution);
|
||||
bind(EditorContribution).to(ArduinoEditorContribution).inSingletonScope();
|
||||
unbind(MonacoStatusBarContribution);
|
||||
bind(MonacoStatusBarContribution).to(ArduinoMonacoStatusBarContribution).inSingletonScope();
|
||||
unbind(ApplicationShell);
|
||||
bind(ApplicationShell).to(ArduinoApplicationShell).inSingletonScope();
|
||||
unbind(ScmContribution);
|
||||
bind(ScmContribution).to(SilentScmContribution).inSingletonScope();
|
||||
unbind(SearchInWorkspaceFrontendContribution);
|
||||
bind(SearchInWorkspaceFrontendContribution).to(SilentSearchInWorkspaceContribution).inSingletonScope();
|
||||
} else {
|
||||
// We use this CSS class on the body to modify the visibility of the close button for the editors and views.
|
||||
document.body.classList.add(ArduinoAdvancedMode.LS_ID);
|
||||
}
|
||||
unbind(FrontendApplication);
|
||||
bind(FrontendApplication).to(ArduinoFrontendApplication).inSingletonScope();
|
||||
// Customizing default Theia layout based on the editor mode: `pro-mode` or `classic`.
|
||||
bind(EditorMode).toSelf().inSingletonScope();
|
||||
bind(FrontendApplicationContribution).toService(EditorMode);
|
||||
rebind(OutlineViewContribution).to(ArduinoOutlineViewContribution).inSingletonScope();
|
||||
rebind(ProblemContribution).to(ArduinoProblemContribution).inSingletonScope();
|
||||
rebind(FileNavigatorContribution).to(ArduinoNavigatorContribution).inSingletonScope();
|
||||
rebind(OutputToolbarContribution).to(ArduinoOutputToolContribution).inSingletonScope();
|
||||
rebind(EditorContribution).to(ArduinoEditorContribution).inSingletonScope();
|
||||
rebind(MonacoStatusBarContribution).to(ArduinoMonacoStatusBarContribution).inSingletonScope();
|
||||
rebind(ApplicationShell).to(ArduinoApplicationShell).inSingletonScope();
|
||||
rebind(ScmContribution).to(ArduinoScmContribution).inSingletonScope();
|
||||
rebind(SearchInWorkspaceFrontendContribution).to(ArduinoSearchInWorkspaceContribution).inSingletonScope();
|
||||
rebind(FrontendApplication).to(ArduinoFrontendApplication).inSingletonScope();
|
||||
|
||||
// Monaco customizations
|
||||
unbind(MonacoEditorProvider);
|
||||
bind(ArduinoMonacoEditorProvider).toSelf().inSingletonScope();
|
||||
bind(MonacoEditorProvider).toService(ArduinoMonacoEditorProvider);
|
||||
rebind(MonacoEditorProvider).toService(ArduinoMonacoEditorProvider);
|
||||
|
||||
// Decorator customizations
|
||||
unbind(TabBarDecoratorService);
|
||||
bind(ArduinoTabBarDecoratorService).toSelf().inSingletonScope();
|
||||
bind(TabBarDecoratorService).toService(ArduinoTabBarDecoratorService);
|
||||
rebind(TabBarDecoratorService).toService(ArduinoTabBarDecoratorService);
|
||||
|
||||
// Problem markers
|
||||
unbind(ProblemManager);
|
||||
bind(ArduinoProblemManager).toSelf().inSingletonScope();
|
||||
bind(ProblemManager).toService(ArduinoProblemManager);
|
||||
rebind(ProblemManager).toService(ArduinoProblemManager);
|
||||
|
||||
// About dialog to show the CLI version
|
||||
unbind(AboutDialog);
|
||||
bind(ArduinoAboutDialog).toSelf().inSingletonScope();
|
||||
bind(AboutDialog).toService(ArduinoAboutDialog);
|
||||
rebind(AboutDialog).toService(ArduinoAboutDialog);
|
||||
|
||||
// Customized layout restorer that can restore the state in async way: https://github.com/eclipse-theia/theia/issues/6579
|
||||
bind(ArduinoShellLayoutRestorer).toSelf().inSingletonScope();
|
||||
rebind(ShellLayoutRestorer).toService(ArduinoShellLayoutRestorer);
|
||||
});
|
||||
|
@ -9,6 +9,7 @@ const ARDUINO_JSON = MonacoThemeRegistry.SINGLETON.register(
|
||||
export class ArduinoTheme {
|
||||
|
||||
static readonly arduino: Theme = {
|
||||
type: 'light',
|
||||
id: 'arduino-theme',
|
||||
label: 'Arduino Light Theme',
|
||||
description: 'Arduino Light Theme',
|
||||
|
@ -4,7 +4,7 @@ import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service
|
||||
import { ConfigService } from '../common/protocol/config-service';
|
||||
import { SketchesService } from '../common/protocol/sketches-service';
|
||||
import { ArduinoWorkspaceRootResolver } from './arduino-workspace-resolver';
|
||||
import { ArduinoAdvancedMode } from './arduino-frontend-contribution';
|
||||
import { EditorMode } from './editor-mode';
|
||||
|
||||
@injectable()
|
||||
export class ArduinoWorkspaceService extends WorkspaceService {
|
||||
@ -18,7 +18,10 @@ export class ArduinoWorkspaceService extends WorkspaceService {
|
||||
@inject(LabelProvider)
|
||||
protected readonly labelProvider: LabelProvider;
|
||||
|
||||
async getDefaultWorkspacePath(): Promise<string | undefined> {
|
||||
@inject(EditorMode)
|
||||
protected readonly editorMode: EditorMode;
|
||||
|
||||
async getDefaultWorkspaceUri(): Promise<string | undefined> {
|
||||
const [hash, recentWorkspaces, recentSketches] = await Promise.all([
|
||||
window.location.hash,
|
||||
this.sketchService.getSketches().then(sketches => sketches.map(({ uri }) => uri)),
|
||||
@ -36,7 +39,8 @@ export class ArduinoWorkspaceService extends WorkspaceService {
|
||||
await this.server.setMostRecentlyUsedWorkspace(uri);
|
||||
return toOpen.uri;
|
||||
}
|
||||
return (await this.sketchService.createNewSketch()).uri;
|
||||
const { sketchDirUri } = (await this.configService.getConfiguration());
|
||||
return (await this.sketchService.createNewSketch(sketchDirUri)).uri;
|
||||
}
|
||||
|
||||
private async isValid(uri: string): Promise<boolean> {
|
||||
@ -46,7 +50,7 @@ export class ArduinoWorkspaceService extends WorkspaceService {
|
||||
}
|
||||
// The workspace root location must exist. However, when opening a workspace root in pro-mode,
|
||||
// the workspace root must not be a sketch folder. It can be the default sketch directory, or any other directories, for instance.
|
||||
if (!ArduinoAdvancedMode.TOGGLED) {
|
||||
if (this.editorMode.proMode) {
|
||||
return true;
|
||||
}
|
||||
const sketchFolder = await this.sketchService.isSketchFolder(uri);
|
||||
|
@ -4,7 +4,7 @@ import { FrontendApplicationContribution } from '@theia/core/lib/browser/fronten
|
||||
import { BoardsService, Board } from '../../common/protocol/boards-service';
|
||||
import { BoardsServiceClientImpl } from './boards-service-client-impl';
|
||||
import { BoardsListWidgetFrontendContribution } from './boards-widget-frontend-contribution';
|
||||
import { InstallationProgressDialog } from '../components/installation-progress-dialog';
|
||||
import { InstallationProgressDialog } from '../components/progress-dialog';
|
||||
import { BoardsConfig } from './boards-config';
|
||||
|
||||
|
||||
@ -43,21 +43,21 @@ export class BoardsAutoInstaller implements FrontendApplicationContribution {
|
||||
// tslint:disable-next-line:max-line-length
|
||||
this.messageService.info(`The \`"${candidate.name}"\` core has to be installed for the currently selected \`"${selectedBoard.name}"\` board. Do you want to install it now?`, 'Yes', 'Install Manually').then(async answer => {
|
||||
if (answer === 'Yes') {
|
||||
const dialog = new InstallationProgressDialog(candidate.name);
|
||||
const dialog = new InstallationProgressDialog(candidate.name, candidate.availableVersions[0]);
|
||||
dialog.open();
|
||||
try {
|
||||
await this.boardsService.install(candidate);
|
||||
await this.boardsService.install({ item: candidate });
|
||||
} finally {
|
||||
dialog.close();
|
||||
}
|
||||
}
|
||||
if (answer) {
|
||||
this.boardsManagerFrontendContribution.openView({ reveal: true }).then(widget => widget.refresh(candidate.name.toLocaleLowerCase()));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -1,54 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import { injectable } from 'inversify';
|
||||
import { ListItemRenderer } from '../components/component-list/list-item-renderer';
|
||||
import { BoardPackage } from '../../common/protocol/boards-service';
|
||||
|
||||
@injectable()
|
||||
export class BoardItemRenderer extends ListItemRenderer<BoardPackage> {
|
||||
|
||||
renderItem(item: BoardPackage, install: (item: BoardPackage) => Promise<void>): React.ReactNode {
|
||||
const name = <span className='name'>{item.name}</span>;
|
||||
const author = <span className='author'>{item.author}</span>;
|
||||
const installedVersion = !!item.installedVersion && <div className='version-info'>
|
||||
<span className='version'>Version {item.installedVersion}</span>
|
||||
<span className='installed'>INSTALLED</span>
|
||||
</div>;
|
||||
|
||||
const summary = <div className='summary'>{item.summary}</div>;
|
||||
const description = <div className='summary'>{item.description}</div>;
|
||||
|
||||
const moreInfo = !!item.moreInfoLink && <a href={item.moreInfoLink} onClick={this.onClick}>More info</a>;
|
||||
const installButton = item.installable && !item.installedVersion &&
|
||||
<button className='install' onClick={install.bind(this, item)}>INSTALL</button>;
|
||||
|
||||
const versions = (() => {
|
||||
const { availableVersions } = item;
|
||||
if (!!item.installedVersion || availableVersions.length === 0) {
|
||||
return undefined;
|
||||
} else if (availableVersions.length === 1) {
|
||||
return <label>{availableVersions[0]}</label>
|
||||
} else {
|
||||
return <select>{item.availableVersions.map(version => <option value={version} key={version}>{version}</option>)}</select>;
|
||||
}
|
||||
})();
|
||||
|
||||
return <div className='component-list-item noselect'>
|
||||
<div className='header'>
|
||||
<span>{name} by {author}</span>
|
||||
{installedVersion}
|
||||
</div>
|
||||
<div className='content'>
|
||||
{summary}
|
||||
{description}
|
||||
</div>
|
||||
<div className='info'>
|
||||
{moreInfo}
|
||||
</div>
|
||||
<div className='footer'>
|
||||
{installButton}
|
||||
{versions}
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { BoardPackage, BoardsService } from '../../common/protocol/boards-service';
|
||||
import { ListWidget } from '../components/component-list/list-widget';
|
||||
import { BoardItemRenderer } from './boards-item-renderer';
|
||||
import { ListItemRenderer } from '../components/component-list/list-item-renderer';
|
||||
|
||||
@injectable()
|
||||
export class BoardsListWidget extends ListWidget<BoardPackage> {
|
||||
@ -11,7 +11,7 @@ export class BoardsListWidget extends ListWidget<BoardPackage> {
|
||||
|
||||
constructor(
|
||||
@inject(BoardsService) protected service: BoardsService,
|
||||
@inject(BoardItemRenderer) protected itemRenderer: BoardItemRenderer) {
|
||||
@inject(ListItemRenderer) protected itemRenderer: ListItemRenderer<BoardPackage>) {
|
||||
|
||||
super({
|
||||
id: BoardsListWidget.WIDGET_ID,
|
||||
|
@ -3,7 +3,7 @@ import { Emitter } from '@theia/core/lib/common/event';
|
||||
import { ILogger } from '@theia/core/lib/common/logger';
|
||||
import { LocalStorageService } from '@theia/core/lib/browser/storage-service';
|
||||
import { RecursiveRequired } from '../../common/types';
|
||||
import { BoardsServiceClient, AttachedBoardsChangeEvent, BoardInstalledEvent, AttachedSerialBoard, Board, Port } from '../../common/protocol/boards-service';
|
||||
import { BoardsServiceClient, AttachedBoardsChangeEvent, BoardInstalledEvent, AttachedSerialBoard, Board, Port, BoardUninstalledEvent } from '../../common/protocol/boards-service';
|
||||
import { BoardsConfig } from './boards-config';
|
||||
|
||||
@injectable()
|
||||
@ -16,6 +16,7 @@ export class BoardsServiceClientImpl implements BoardsServiceClient {
|
||||
protected storageService: LocalStorageService;
|
||||
|
||||
protected readonly onBoardInstalledEmitter = new Emitter<BoardInstalledEvent>();
|
||||
protected readonly onBoardUninstalledEmitter = new Emitter<BoardUninstalledEvent>();
|
||||
protected readonly onAttachedBoardsChangedEmitter = new Emitter<AttachedBoardsChangeEvent>();
|
||||
protected readonly onSelectedBoardsConfigChangedEmitter = new Emitter<BoardsConfig.Config>();
|
||||
|
||||
@ -31,6 +32,7 @@ export class BoardsServiceClientImpl implements BoardsServiceClient {
|
||||
|
||||
readonly onBoardsChanged = this.onAttachedBoardsChangedEmitter.event;
|
||||
readonly onBoardInstalled = this.onBoardInstalledEmitter.event;
|
||||
readonly onBoardUninstalled = this.onBoardUninstalledEmitter.event;
|
||||
readonly onBoardsConfigChanged = this.onSelectedBoardsConfigChangedEmitter.event;
|
||||
|
||||
@postConstruct()
|
||||
@ -87,6 +89,11 @@ export class BoardsServiceClientImpl implements BoardsServiceClient {
|
||||
this.onBoardInstalledEmitter.fire(event);
|
||||
}
|
||||
|
||||
notifyBoardUninstalled(event: BoardUninstalledEvent): void {
|
||||
this.logger.info('Board uninstalled: ', JSON.stringify(event));
|
||||
this.onBoardUninstalledEmitter.fire(event);
|
||||
}
|
||||
|
||||
set boardsConfig(config: BoardsConfig.Config) {
|
||||
this.logger.info('Board config changed: ', JSON.stringify(config));
|
||||
this._boardsConfig = config;
|
||||
|
@ -1,25 +1,66 @@
|
||||
import * as React from 'react';
|
||||
import { Installable } from '../../../common/protocol/installable';
|
||||
import { ArduinoComponent } from '../../../common/protocol/arduino-component';
|
||||
import { ListItemRenderer } from './list-item-renderer';
|
||||
|
||||
export class ComponentListItem<T> extends React.Component<ComponentListItem.Props<T>> {
|
||||
export class ComponentListItem<T extends ArduinoComponent> extends React.Component<ComponentListItem.Props<T>, ComponentListItem.State> {
|
||||
|
||||
constructor(props: ComponentListItem.Props<T>) {
|
||||
super(props);
|
||||
if (props.item.installable) {
|
||||
const version = props.item.availableVersions.filter(version => version !== props.item.installedVersion)[0];
|
||||
this.state = {
|
||||
selectedVersion: version
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
protected async install(item: T): Promise<void> {
|
||||
await this.props.install(item);
|
||||
const toInstall = this.state.selectedVersion;
|
||||
const version = this.props.item.availableVersions.filter(version => version !== this.state.selectedVersion)[0];
|
||||
this.setState({
|
||||
selectedVersion: version
|
||||
});
|
||||
try {
|
||||
await this.props.install(item, toInstall);
|
||||
} catch {
|
||||
this.setState({
|
||||
selectedVersion: toInstall
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected async uninstall(item: T): Promise<void> {
|
||||
await this.props.uninstall(item);
|
||||
}
|
||||
|
||||
protected onVersionChange(version: Installable.Version) {
|
||||
this.setState({ selectedVersion: version });
|
||||
}
|
||||
|
||||
render(): React.ReactNode {
|
||||
const { item, itemRenderer, install } = this.props;
|
||||
return itemRenderer.renderItem(item, install.bind(this));
|
||||
const { item, itemRenderer } = this.props;
|
||||
return itemRenderer.renderItem(
|
||||
Object.assign(this.state, { item }),
|
||||
this.install.bind(this),
|
||||
this.uninstall.bind(this),
|
||||
this.onVersionChange.bind(this)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace ComponentListItem {
|
||||
|
||||
export interface Props<T> {
|
||||
export interface Props<T extends ArduinoComponent> {
|
||||
readonly item: T;
|
||||
readonly install: (item: T) => Promise<void>;
|
||||
readonly install: (item: T, version?: Installable.Version) => Promise<void>;
|
||||
readonly uninstall: (item: T) => Promise<void>;
|
||||
readonly itemRenderer: ListItemRenderer<T>;
|
||||
}
|
||||
|
||||
export interface State {
|
||||
selectedVersion?: Installable.Version;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,8 +1,10 @@
|
||||
import * as React from 'react';
|
||||
import { Installable } from '../../../common/protocol/installable';
|
||||
import { ArduinoComponent } from '../../../common/protocol/arduino-component';
|
||||
import { ComponentListItem } from './component-list-item';
|
||||
import { ListItemRenderer } from './list-item-renderer';
|
||||
|
||||
export class ComponentList<T> extends React.Component<ComponentList.Props<T>> {
|
||||
export class ComponentList<T extends ArduinoComponent> extends React.Component<ComponentList.Props<T>> {
|
||||
|
||||
protected container?: HTMLElement;
|
||||
|
||||
@ -29,18 +31,20 @@ export class ComponentList<T> extends React.Component<ComponentList.Props<T>> {
|
||||
key={this.props.itemLabel(item)}
|
||||
item={item}
|
||||
itemRenderer={this.props.itemRenderer}
|
||||
install={this.props.install} />
|
||||
install={this.props.install}
|
||||
uninstall={this.props.uninstall} />
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace ComponentList {
|
||||
|
||||
export interface Props<T> {
|
||||
export interface Props<T extends ArduinoComponent> {
|
||||
readonly items: T[];
|
||||
readonly itemLabel: (item: T) => string;
|
||||
readonly itemRenderer: ListItemRenderer<T>;
|
||||
readonly install: (item: T) => Promise<void>;
|
||||
readonly install: (item: T, version?: Installable.Version) => Promise<void>;
|
||||
readonly uninstall: (item: T) => Promise<void>;
|
||||
readonly resolveContainer: (element: HTMLElement) => void;
|
||||
}
|
||||
|
||||
|
@ -1,14 +1,17 @@
|
||||
import * as React from 'react';
|
||||
import debounce = require('lodash.debounce');
|
||||
import { Event } from '@theia/core/lib/common/event';
|
||||
import { ConfirmDialog } from '@theia/core/lib/browser/dialogs';
|
||||
import { Searchable } from '../../../common/protocol/searchable';
|
||||
import { Installable } from '../../../common/protocol/installable';
|
||||
import { InstallationProgressDialog } from '../installation-progress-dialog';
|
||||
import { ArduinoComponent } from '../../../common/protocol/arduino-component';
|
||||
import { InstallationProgressDialog, UninstallationProgressDialog } from '../progress-dialog';
|
||||
import { SearchBar } from './search-bar';
|
||||
import { ListWidget } from './list-widget';
|
||||
import { ComponentList } from './component-list';
|
||||
import { ListItemRenderer } from './list-item-renderer';
|
||||
|
||||
export class FilterableListContainer<T> extends React.Component<FilterableListContainer.Props<T>, FilterableListContainer.State<T>> {
|
||||
export class FilterableListContainer<T extends ArduinoComponent> extends React.Component<FilterableListContainer.Props<T>, FilterableListContainer.State<T>> {
|
||||
|
||||
constructor(props: Readonly<FilterableListContainer.Props<T>>) {
|
||||
super(props);
|
||||
@ -18,12 +21,18 @@ export class FilterableListContainer<T> extends React.Component<FilterableListCo
|
||||
};
|
||||
}
|
||||
|
||||
componentWillMount(): void {
|
||||
componentDidMount(): void {
|
||||
this.search = debounce(this.search, 500);
|
||||
this.handleFilterTextChange('');
|
||||
this.props.filterTextChangeEvent(this.handleFilterTextChange.bind(this));
|
||||
}
|
||||
|
||||
componentDidUpdate(): void {
|
||||
// See: arduino/arduino-pro-ide#101
|
||||
// Resets the top of the perfect scroll-bar's thumb.
|
||||
this.props.container.updateScrollBar();
|
||||
}
|
||||
|
||||
render(): React.ReactNode {
|
||||
return <div className={'filterable-list-container'}>
|
||||
{this.renderSearchFilter()}
|
||||
@ -51,6 +60,7 @@ export class FilterableListContainer<T> extends React.Component<FilterableListCo
|
||||
itemLabel={itemLabel}
|
||||
itemRenderer={itemRenderer}
|
||||
install={this.install.bind(this)}
|
||||
uninstall={this.uninstall.bind(this)}
|
||||
resolveContainer={resolveContainer}
|
||||
/>
|
||||
}
|
||||
@ -75,12 +85,34 @@ export class FilterableListContainer<T> extends React.Component<FilterableListCo
|
||||
return items.sort((left, right) => itemLabel(left).localeCompare(itemLabel(right)));
|
||||
}
|
||||
|
||||
protected async install(item: T): Promise<void> {
|
||||
protected async install(item: T, version: Installable.Version): Promise<void> {
|
||||
const { installable, searchable, itemLabel } = this.props;
|
||||
const dialog = new InstallationProgressDialog(itemLabel(item));
|
||||
const dialog = new InstallationProgressDialog(itemLabel(item), version);
|
||||
dialog.open();
|
||||
try {
|
||||
await installable.install(item);
|
||||
await installable.install({ item, version });
|
||||
const { items } = await searchable.search({ query: this.state.filterText });
|
||||
this.setState({ items: this.sort(items) });
|
||||
} finally {
|
||||
dialog.close();
|
||||
}
|
||||
}
|
||||
|
||||
protected async uninstall(item: T): Promise<void> {
|
||||
const uninstall = await new ConfirmDialog({
|
||||
title: 'Uninstall',
|
||||
msg: `Do you want to uninstall ${item.name}?`,
|
||||
ok: 'Yes',
|
||||
cancel: 'No'
|
||||
}).open();
|
||||
if (!uninstall) {
|
||||
return;
|
||||
}
|
||||
const { installable, searchable, itemLabel } = this.props;
|
||||
const dialog = new UninstallationProgressDialog(itemLabel(item));
|
||||
dialog.open();
|
||||
try {
|
||||
await installable.uninstall({ item });
|
||||
const { items } = await searchable.search({ query: this.state.filterText });
|
||||
this.setState({ items: this.sort(items) });
|
||||
} finally {
|
||||
@ -92,7 +124,8 @@ export class FilterableListContainer<T> extends React.Component<FilterableListCo
|
||||
|
||||
export namespace FilterableListContainer {
|
||||
|
||||
export interface Props<T> {
|
||||
export interface Props<T extends ArduinoComponent> {
|
||||
readonly container: ListWidget<T>;
|
||||
readonly installable: Installable<T>;
|
||||
readonly searchable: Searchable<T>;
|
||||
readonly itemLabel: (item: T) => string;
|
||||
|
@ -1,14 +1,17 @@
|
||||
import * as React from 'react';
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { WindowService } from '@theia/core/lib/browser/window/window-service';
|
||||
import { Installable } from '../../../common/protocol/installable';
|
||||
import { ArduinoComponent } from '../../../common/protocol/arduino-component';
|
||||
import { ComponentListItem } from './component-list-item';
|
||||
|
||||
@injectable()
|
||||
export abstract class ListItemRenderer<T> {
|
||||
export class ListItemRenderer<T extends ArduinoComponent> {
|
||||
|
||||
@inject(WindowService)
|
||||
protected windowService: WindowService;
|
||||
|
||||
protected onClick = (event: React.SyntheticEvent<HTMLAnchorElement, Event>) => {
|
||||
protected onMoreInfoClick = (event: React.SyntheticEvent<HTMLAnchorElement, Event>) => {
|
||||
const { target } = event.nativeEvent;
|
||||
if (target instanceof HTMLAnchorElement) {
|
||||
this.windowService.openNewWindow(target.href, { external: true });
|
||||
@ -16,6 +19,73 @@ export abstract class ListItemRenderer<T> {
|
||||
}
|
||||
}
|
||||
|
||||
abstract renderItem(item: T, install: (item: T) => Promise<void>): React.ReactNode;
|
||||
renderItem(
|
||||
input: ComponentListItem.State & { item: T },
|
||||
install: (item: T) => Promise<void>,
|
||||
uninstall: (item: T) => Promise<void>,
|
||||
onVersionChange: (version: Installable.Version) => void
|
||||
): React.ReactNode {
|
||||
|
||||
}
|
||||
const { item } = input;
|
||||
const name = <span className='name'>{item.name}</span>;
|
||||
const author = <span className='author'>{item.author}</span>;
|
||||
const onClickUninstall = () => uninstall(item);
|
||||
const installedVersion = !!item.installedVersion && <div className='version-info'>
|
||||
<span className='version'>Version {item.installedVersion}</span>
|
||||
<span className='installed' onClick={onClickUninstall} />
|
||||
</div>;
|
||||
|
||||
const summary = <div className='summary'>{item.summary}</div>;
|
||||
const description = <div className='summary'>{item.description}</div>;
|
||||
|
||||
const moreInfo = !!item.moreInfoLink && <a href={item.moreInfoLink} onClick={this.onMoreInfoClick}>More info</a>;
|
||||
const onClickInstall = () => install(item);
|
||||
const installButton = item.installable &&
|
||||
<button className='install' onClick={onClickInstall}>INSTALL</button>;
|
||||
|
||||
const onSelectChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
const version = event.target.value;
|
||||
if (version) {
|
||||
onVersionChange(version);
|
||||
}
|
||||
}
|
||||
|
||||
const versions = (() => {
|
||||
const { availableVersions } = item;
|
||||
if (availableVersions.length === 0) {
|
||||
return undefined;
|
||||
} else if (availableVersions.length === 1) {
|
||||
return <label>{availableVersions[0]}</label>
|
||||
} else {
|
||||
return <select
|
||||
value={input.selectedVersion}
|
||||
onChange={onSelectChange}>
|
||||
{
|
||||
item.availableVersions
|
||||
.filter(version => version !== item.installedVersion) // Filter the version that is currently installed.
|
||||
.map(version => <option value={version} key={version}>{version}</option>)
|
||||
}
|
||||
</select>;
|
||||
}
|
||||
})();
|
||||
|
||||
return <div className='component-list-item noselect'>
|
||||
<div className='header'>
|
||||
<span>{name} by {author}</span>
|
||||
{installedVersion}
|
||||
</div>
|
||||
<div className='content'>
|
||||
{summary}
|
||||
{description}
|
||||
</div>
|
||||
<div className='info'>
|
||||
{moreInfo}
|
||||
</div>
|
||||
<div className='footer'>
|
||||
{installButton}
|
||||
{versions}
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,10 +1,11 @@
|
||||
import { injectable } from 'inversify';
|
||||
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
|
||||
import { AbstractViewContribution } from '@theia/core/lib/browser/shell/view-contribution';
|
||||
import { ArduinoComponent } from '../../../common/protocol/arduino-component';
|
||||
import { ListWidget } from './list-widget';
|
||||
|
||||
@injectable()
|
||||
export abstract class ListWidgetFrontendContribution<T> extends AbstractViewContribution<ListWidget<T>> implements FrontendApplicationContribution {
|
||||
export abstract class ListWidgetFrontendContribution<T extends ArduinoComponent> extends AbstractViewContribution<ListWidget<T>> implements FrontendApplicationContribution {
|
||||
|
||||
async initializeLayout(): Promise<void> {
|
||||
}
|
||||
|
@ -7,11 +7,12 @@ import { MaybePromise } from '@theia/core/lib/common/types';
|
||||
import { ReactWidget } from '@theia/core/lib/browser/widgets/react-widget';
|
||||
import { Installable } from '../../../common/protocol/installable';
|
||||
import { Searchable } from '../../../common/protocol/searchable';
|
||||
import { ArduinoComponent } from '../../../common/protocol/arduino-component';
|
||||
import { FilterableListContainer } from './filterable-list-container';
|
||||
import { ListItemRenderer } from './list-item-renderer';
|
||||
|
||||
@injectable()
|
||||
export abstract class ListWidget<T> extends ReactWidget {
|
||||
export abstract class ListWidget<T extends ArduinoComponent> extends ReactWidget {
|
||||
|
||||
/**
|
||||
* Do not touch or use it. It is for setting the focus on the `input` after the widget activation.
|
||||
@ -61,6 +62,7 @@ export abstract class ListWidget<T> extends ReactWidget {
|
||||
|
||||
render(): React.ReactNode {
|
||||
return <FilterableListContainer<T>
|
||||
container={this}
|
||||
resolveContainer={this.deferredContainer.resolve}
|
||||
resolveFocus={this.onFocusResolved}
|
||||
searchable={this.options.searchable}
|
||||
@ -74,10 +76,16 @@ export abstract class ListWidget<T> extends ReactWidget {
|
||||
this.deferredContainer.promise.then(() => this.filterTextChangeEmitter.fire(filterText));
|
||||
}
|
||||
|
||||
updateScrollBar(): void {
|
||||
if (this.scrollBar) {
|
||||
this.scrollBar.update();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace ListWidget {
|
||||
export interface Options<T> {
|
||||
export interface Options<T extends ArduinoComponent> {
|
||||
readonly id: string;
|
||||
readonly label: string;
|
||||
readonly iconClass: string;
|
||||
|
@ -1,12 +0,0 @@
|
||||
import { AbstractDialog } from '@theia/core/lib/browser';
|
||||
|
||||
export class InstallationProgressDialog extends AbstractDialog<undefined> {
|
||||
|
||||
readonly value = undefined;
|
||||
|
||||
constructor(componentName: string) {
|
||||
super({ title: 'Installation in progress' });
|
||||
this.contentNode.textContent = `Installing ${componentName}. Please wait.`;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
import { AbstractDialog } from '@theia/core/lib/browser';
|
||||
|
||||
export class InstallationProgressDialog extends AbstractDialog<undefined> {
|
||||
|
||||
readonly value = undefined;
|
||||
|
||||
constructor(componentName: string, version: string) {
|
||||
super({ title: 'Installation in progress' });
|
||||
this.contentNode.textContent = `Installing ${componentName} [${version}]. Please wait...`;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class UninstallationProgressDialog extends AbstractDialog<undefined> {
|
||||
|
||||
readonly value = undefined;
|
||||
|
||||
constructor(componentName: string) {
|
||||
super({ title: 'Uninstallation in progress' });
|
||||
this.contentNode.textContent = `Uninstalling ${componentName}. Please wait...`;
|
||||
}
|
||||
|
||||
}
|
@ -1,22 +1,35 @@
|
||||
import { ApplicationShell, Widget, Saveable, FocusTracker, Message } from '@theia/core/lib/browser';
|
||||
import { EditorWidget } from '@theia/editor/lib/browser';
|
||||
import { injectable, inject } from 'inversify';
|
||||
import { EditorMode } from '../editor-mode';
|
||||
|
||||
@injectable()
|
||||
export class ArduinoApplicationShell extends ApplicationShell {
|
||||
|
||||
@inject(EditorMode)
|
||||
protected readonly editorMode: EditorMode;
|
||||
|
||||
protected refreshBottomPanelToggleButton() {
|
||||
if (this.editorMode.proMode) {
|
||||
super.refreshBottomPanelToggleButton();
|
||||
}
|
||||
}
|
||||
|
||||
protected async track(widget: Widget): Promise<void> {
|
||||
const tracker = (this as any).tracker as FocusTracker<Widget>;
|
||||
tracker.add(widget);
|
||||
this.disableClose(Saveable.apply(widget));
|
||||
if (ApplicationShell.TrackableWidgetProvider.is(widget)) {
|
||||
for (const toTrack of await widget.getTrackableWidgets()) {
|
||||
tracker.add(toTrack);
|
||||
this.disableClose(Saveable.apply(toTrack));
|
||||
}
|
||||
if (widget.onDidChangeTrackableWidgets) {
|
||||
widget.onDidChangeTrackableWidgets(widgets => widgets.forEach(w => this.track(w)));
|
||||
if (this.editorMode.proMode) {
|
||||
super.track(widget);
|
||||
} else {
|
||||
const tracker = (this as any).tracker as FocusTracker<Widget>;
|
||||
tracker.add(widget);
|
||||
this.disableClose(Saveable.apply(widget));
|
||||
if (ApplicationShell.TrackableWidgetProvider.is(widget)) {
|
||||
for (const toTrack of await widget.getTrackableWidgets()) {
|
||||
tracker.add(toTrack);
|
||||
this.disableClose(Saveable.apply(toTrack));
|
||||
}
|
||||
if (widget.onDidChangeTrackableWidgets) {
|
||||
widget.onDidChangeTrackableWidgets(widgets => widgets.forEach(w => this.track(w)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +0,0 @@
|
||||
import { injectable } from 'inversify';
|
||||
import { FileMenuContribution } from '@theia/workspace/lib/browser';
|
||||
import { MenuModelRegistry } from '@theia/core';
|
||||
|
||||
@injectable()
|
||||
export class ArduinoFileMenuContribution extends FileMenuContribution {
|
||||
|
||||
registerMenus(registry: MenuModelRegistry) {
|
||||
}
|
||||
|
||||
}
|
@ -2,7 +2,8 @@ import { injectable, inject } from 'inversify';
|
||||
import { FileSystem } from '@theia/filesystem/lib/common/filesystem';
|
||||
import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service';
|
||||
import { FrontendApplication } from '@theia/core/lib/browser/frontend-application';
|
||||
import { ArduinoFrontendContribution, ArduinoAdvancedMode } from '../arduino-frontend-contribution';
|
||||
import { EditorMode } from '../editor-mode';
|
||||
import { ArduinoFrontendContribution } from '../arduino-frontend-contribution';
|
||||
|
||||
@injectable()
|
||||
export class ArduinoFrontendApplication extends FrontendApplication {
|
||||
@ -16,12 +17,15 @@ export class ArduinoFrontendApplication extends FrontendApplication {
|
||||
@inject(ArduinoFrontendContribution)
|
||||
protected readonly frontendContribution: ArduinoFrontendContribution;
|
||||
|
||||
@inject(EditorMode)
|
||||
protected readonly editorMode: EditorMode;
|
||||
|
||||
protected async initializeLayout(): Promise<void> {
|
||||
super.initializeLayout().then(() => {
|
||||
// If not in PRO mode, we open the sketch file with all the related files.
|
||||
// Otherwise, we reuse the workbench's restore functionality and we do not open anything at all.
|
||||
// TODO: check `otherwise`. Also, what if we check for opened editors, instead of blindly opening them?
|
||||
if (!ArduinoAdvancedMode.TOGGLED) {
|
||||
if (!this.editorMode.proMode) {
|
||||
this.workspaceService.roots.then(roots => {
|
||||
for (const root of roots) {
|
||||
this.fileSystem.exists(root.uri).then(exists => {
|
||||
|
@ -0,0 +1,18 @@
|
||||
import { injectable, inject } from 'inversify';
|
||||
import { FrontendApplication } from '@theia/core/lib/browser/frontend-application';
|
||||
import { FileNavigatorContribution } from '@theia/navigator/lib/browser/navigator-contribution';
|
||||
import { EditorMode } from '../editor-mode';
|
||||
|
||||
@injectable()
|
||||
export class ArduinoNavigatorContribution extends FileNavigatorContribution {
|
||||
|
||||
@inject(EditorMode)
|
||||
protected readonly editorMode: EditorMode;
|
||||
|
||||
async initializeLayout(app: FrontendApplication): Promise<void> {
|
||||
if (this.editorMode.proMode) {
|
||||
return super.initializeLayout(app);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
import { injectable, inject } from 'inversify';
|
||||
import { FrontendApplication } from '@theia/core/lib/browser/frontend-application';
|
||||
import { OutlineViewContribution } from '@theia/outline-view/lib/browser/outline-view-contribution';
|
||||
import { EditorMode } from '../editor-mode';
|
||||
|
||||
@injectable()
|
||||
export class ArduinoOutlineViewContribution extends OutlineViewContribution {
|
||||
|
||||
@inject(EditorMode)
|
||||
protected readonly editorMode: EditorMode;
|
||||
|
||||
async initializeLayout(app: FrontendApplication): Promise<void> {
|
||||
if (this.editorMode.proMode) {
|
||||
return super.initializeLayout(app);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,11 +1,18 @@
|
||||
import { OutputToolbarContribution } from '@theia/output/lib/browser/output-toolbar-contribution';
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { TabBarToolbarRegistry } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
|
||||
import { injectable } from 'inversify';
|
||||
import { OutputToolbarContribution } from '@theia/output/lib/browser/output-toolbar-contribution';
|
||||
import { EditorMode } from '../editor-mode';
|
||||
|
||||
@injectable()
|
||||
export class ArduinoOutputToolContribution extends OutputToolbarContribution {
|
||||
|
||||
@inject(EditorMode)
|
||||
protected readonly editorMode: EditorMode;
|
||||
|
||||
async registerToolbarItems(toolbarRegistry: TabBarToolbarRegistry): Promise<void> {
|
||||
if (this.editorMode.proMode) {
|
||||
super.registerToolbarItems(toolbarRegistry);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { ProblemStat } from '@theia/markers/lib/browser/problem/problem-manager';
|
||||
import { FrontendApplication } from '@theia/core/lib/browser/frontend-application';
|
||||
import { ProblemContribution } from '@theia/markers/lib/browser/problem/problem-contribution';
|
||||
import { EditorMode } from '../editor-mode';
|
||||
|
||||
@injectable()
|
||||
export class ArduinoProblemContribution extends ProblemContribution {
|
||||
|
||||
@inject(EditorMode)
|
||||
protected readonly editorMode: EditorMode;
|
||||
|
||||
async initializeLayout(app: FrontendApplication): Promise<void> {
|
||||
if (this.editorMode.proMode) {
|
||||
return super.initializeLayout(app);
|
||||
}
|
||||
}
|
||||
|
||||
protected setStatusBarElement(problemStat: ProblemStat): void {
|
||||
if (this.editorMode.proMode) {
|
||||
super.setStatusBarElement(problemStat);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { ScmContribution } from '@theia/scm/lib/browser/scm-contribution';
|
||||
import { StatusBarEntry } from '@theia/core/lib/browser/status-bar/status-bar';
|
||||
import { EditorMode } from '../editor-mode';
|
||||
|
||||
@injectable()
|
||||
export class ArduinoScmContribution extends ScmContribution {
|
||||
|
||||
@inject(EditorMode)
|
||||
protected readonly editorMode: EditorMode;
|
||||
|
||||
async initializeLayout(): Promise<void> {
|
||||
if (this.editorMode.proMode) {
|
||||
return super.initializeLayout();
|
||||
}
|
||||
}
|
||||
|
||||
protected setStatusBarEntry(id: string, entry: StatusBarEntry): void {
|
||||
if (this.editorMode.proMode) {
|
||||
super.setStatusBarEntry(id, entry);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { FrontendApplication } from '@theia/core/lib/browser/frontend-application';
|
||||
import { SearchInWorkspaceFrontendContribution } from '@theia/search-in-workspace/lib/browser/search-in-workspace-frontend-contribution';
|
||||
import { EditorMode } from '../editor-mode';
|
||||
|
||||
@injectable()
|
||||
export class ArduinoSearchInWorkspaceContribution extends SearchInWorkspaceFrontendContribution {
|
||||
|
||||
@inject(EditorMode)
|
||||
protected readonly editorMode: EditorMode;
|
||||
|
||||
async initializeLayout(app: FrontendApplication): Promise<void> {
|
||||
if (this.editorMode.proMode) {
|
||||
return super.initializeLayout(app);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
import { injectable } from 'inversify';
|
||||
import { FileNavigatorContribution } from '@theia/navigator/lib/browser/navigator-contribution';
|
||||
import { FrontendApplication } from '@theia/core/lib/browser';
|
||||
|
||||
@injectable()
|
||||
export class SilentNavigatorContribution extends FileNavigatorContribution {
|
||||
|
||||
async initializeLayout(app: FrontendApplication): Promise<void> {
|
||||
}
|
||||
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
import { injectable } from 'inversify';
|
||||
import { OutlineViewContribution } from '@theia/outline-view/lib/browser/outline-view-contribution';
|
||||
import { FrontendApplication } from '@theia/core/lib/browser';
|
||||
|
||||
@injectable()
|
||||
export class SilentOutlineViewContribution extends OutlineViewContribution {
|
||||
|
||||
async initializeLayout(app: FrontendApplication): Promise<void> {
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,15 +0,0 @@
|
||||
import { injectable } from 'inversify';
|
||||
import { ProblemContribution } from '@theia/markers/lib/browser/problem/problem-contribution';
|
||||
import { ProblemStat } from '@theia/markers/lib/browser/problem/problem-manager';
|
||||
import { FrontendApplication } from '@theia/core/lib/browser';
|
||||
|
||||
@injectable()
|
||||
export class SilentProblemContribution extends ProblemContribution {
|
||||
|
||||
async initializeLayout(app: FrontendApplication): Promise<void> {
|
||||
}
|
||||
|
||||
protected setStatusBarElement(problemStat: ProblemStat) {
|
||||
}
|
||||
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
import { injectable } from 'inversify';
|
||||
import { ScmContribution } from '@theia/scm/lib/browser/scm-contribution';
|
||||
import { StatusBarEntry } from '@theia/core/lib/browser';
|
||||
|
||||
@injectable()
|
||||
export class SilentScmContribution extends ScmContribution {
|
||||
|
||||
async initializeLayout(): Promise<void> {
|
||||
}
|
||||
|
||||
protected setStatusBarEntry(id: string, entry: StatusBarEntry): void {
|
||||
}
|
||||
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
import { injectable } from 'inversify';
|
||||
import { SearchInWorkspaceFrontendContribution } from '@theia/search-in-workspace/lib/browser/search-in-workspace-frontend-contribution';
|
||||
import { FrontendApplication } from '@theia/core/lib/browser';
|
||||
|
||||
@injectable()
|
||||
export class SilentSearchInWorkspaceContribution extends SearchInWorkspaceFrontendContribution {
|
||||
|
||||
async initializeLayout(app: FrontendApplication): Promise<void> {
|
||||
}
|
||||
|
||||
}
|
48
arduino-ide-extension/src/browser/editor-mode.ts
Normal file
48
arduino-ide-extension/src/browser/editor-mode.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import { injectable } from 'inversify';
|
||||
import { ApplicationShell, FrontendApplicationContribution, FrontendApplication } from '@theia/core/lib/browser';
|
||||
import { ArduinoShellLayoutRestorer } from './shell/arduino-shell-layout-restorer';
|
||||
import { OutputWidget } from '@theia/output/lib/browser/output-widget';
|
||||
import { EditorWidget } from '@theia/editor/lib/browser';
|
||||
|
||||
@injectable()
|
||||
export class EditorMode implements FrontendApplicationContribution {
|
||||
|
||||
protected app: FrontendApplication;
|
||||
|
||||
onStart(app: FrontendApplication): void {
|
||||
this.app = app;
|
||||
if (this.proMode) {
|
||||
// We use this CSS class on the body to modify the visibility of the close button for the editors and views.
|
||||
document.body.classList.add(EditorMode.PRO_MODE_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
get proMode(): boolean {
|
||||
const value = window.localStorage.getItem(EditorMode.PRO_MODE_KEY);
|
||||
return value === 'true';
|
||||
}
|
||||
|
||||
async toggle(): Promise<void> {
|
||||
const oldState = this.proMode;
|
||||
const inAdvancedMode = !oldState;
|
||||
window.localStorage.setItem(EditorMode.PRO_MODE_KEY, String(inAdvancedMode));
|
||||
if (!inAdvancedMode) {
|
||||
const { shell } = this.app;
|
||||
// Close all widget that is neither editor nor `Output`.
|
||||
for (const area of ['left', 'right', 'bottom', 'main'] as Array<ApplicationShell.Area>) {
|
||||
shell.closeTabs(area, ({ owner }) => !(owner instanceof EditorWidget || owner instanceof OutputWidget));
|
||||
}
|
||||
}
|
||||
// `storeLayout` has a sync API but the implementation is async, we store the layout manually before we reload the page.
|
||||
// See: https://github.com/eclipse-theia/theia/issues/6579
|
||||
// XXX: hack instead of injecting the `ArduinoShellLayoutRestorer` we have to retrieve it from the application to avoid DI cycle.
|
||||
const layoutRestorer = (this.app as any).layoutRestorer as ArduinoShellLayoutRestorer
|
||||
await layoutRestorer.storeLayoutAsync(this.app);
|
||||
window.location.reload(true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace EditorMode {
|
||||
export const PRO_MODE_KEY = 'arduino-advanced-mode';
|
||||
}
|
@ -2,6 +2,7 @@ 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 {
|
||||
@ -25,6 +26,18 @@ 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 {
|
||||
|
@ -1,52 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import { injectable } from 'inversify';
|
||||
import { Library } from '../../common/protocol/library-service';
|
||||
import { ListItemRenderer } from '../components/component-list/list-item-renderer';
|
||||
|
||||
@injectable()
|
||||
export class LibraryItemRenderer extends ListItemRenderer<Library> {
|
||||
|
||||
renderItem(item: Library, install: (item: Library) => Promise<void>): React.ReactNode {
|
||||
const name = <span className='name'>{item.name}</span>;
|
||||
const author = <span className='author'>by {item.author}</span>;
|
||||
const installedVersion = !!item.installedVersion && <div className='version-info'>
|
||||
<span className='version'>Version {item.installedVersion}</span>
|
||||
<span className='installed'>INSTALLED</span>
|
||||
</div>;
|
||||
|
||||
const summary = <div className='summary'>{item.summary}</div>;
|
||||
|
||||
const moreInfo = !!item.moreInfoLink && <a href={item.moreInfoLink} onClick={this.onClick}>More info</a>;
|
||||
const installButton = item.installable && !item.installedVersion &&
|
||||
<button className='install' onClick={install.bind(this, item)}>INSTALL</button>;
|
||||
|
||||
const versions = (() => {
|
||||
const { availableVersions } = item;
|
||||
if (!!item.installedVersion || availableVersions.length === 0) {
|
||||
return undefined;
|
||||
} else if (availableVersions.length === 1) {
|
||||
return <label>{availableVersions[0]}</label>
|
||||
} else {
|
||||
return <select>{item.availableVersions.map(version => <option value={version} key={version}>{version}</option>)}</select>;
|
||||
}
|
||||
})();
|
||||
|
||||
return <div className='component-list-item noselect'>
|
||||
<div className='header'>
|
||||
<span>{name} {author}</span>
|
||||
{installedVersion}
|
||||
</div>
|
||||
<div className='content'>
|
||||
{summary}
|
||||
</div>
|
||||
<div className='info'>
|
||||
{moreInfo}
|
||||
</div>
|
||||
<div className='footer'>
|
||||
{installButton}
|
||||
{versions}
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { Library, LibraryService } from '../../common/protocol/library-service';
|
||||
import { ListWidget } from '../components/component-list/list-widget';
|
||||
import { LibraryItemRenderer } from './library-item-renderer';
|
||||
import { ListItemRenderer } from '../components/component-list/list-item-renderer';
|
||||
|
||||
@injectable()
|
||||
export class LibraryListWidget extends ListWidget<Library> {
|
||||
@ -11,7 +11,7 @@ export class LibraryListWidget extends ListWidget<Library> {
|
||||
|
||||
constructor(
|
||||
@inject(LibraryService) protected service: LibraryService,
|
||||
@inject(LibraryItemRenderer) protected itemRenderer: LibraryItemRenderer) {
|
||||
@inject(ListItemRenderer) protected itemRenderer: ListItemRenderer<Library>) {
|
||||
|
||||
super({
|
||||
id: LibraryListWidget.WIDGET_ID,
|
||||
|
@ -0,0 +1,24 @@
|
||||
import { injectable } from 'inversify';
|
||||
import { FrontendApplication } from '@theia/core/lib/browser/frontend-application';
|
||||
import { ShellLayoutRestorer } from '@theia/core/lib/browser/shell/shell-layout-restorer';
|
||||
|
||||
@injectable()
|
||||
export class ArduinoShellLayoutRestorer extends ShellLayoutRestorer {
|
||||
|
||||
// Workaround for https://github.com/eclipse-theia/theia/issues/6579.
|
||||
async storeLayoutAsync(app: FrontendApplication): Promise<void> {
|
||||
if (this.shouldStoreLayout) {
|
||||
try {
|
||||
this.logger.info('>>> Storing the layout...');
|
||||
const layoutData = app.shell.getLayoutData();
|
||||
const serializedLayoutData = this.deflate(layoutData);
|
||||
await this.storageService.setData(this.storageKey, serializedLayoutData);
|
||||
this.logger.info('<<< The layout has been successfully stored.');
|
||||
} catch (error) {
|
||||
await this.storageService.setData(this.storageKey, undefined);
|
||||
this.logger.error('Error during serialization of layout data', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -35,7 +35,7 @@ div#select-board-dialog .selectBoardContainer .body .list .item.selected i{
|
||||
#select-board-dialog .selectBoardContainer .search input,
|
||||
#select-board-dialog .selectBoardContainer .list,
|
||||
#select-board-dialog .selectBoardContainer .list {
|
||||
background: white; /* TODO find a theia color instead! */
|
||||
background: var(--theia-layout-color0);
|
||||
}
|
||||
|
||||
#select-board-dialog .selectBoardContainer .body .search input {
|
||||
|
@ -61,6 +61,14 @@
|
||||
background-color: var(--theia-layout-color2);
|
||||
}
|
||||
|
||||
/* Perfect scrollbar does not like if we explicitly set the `background-color` of the contained elements.
|
||||
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 {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.component-list-item {
|
||||
padding: 10px 10px 10px 15px;
|
||||
font-size: var(--theia-ui-font-size1);
|
||||
@ -108,8 +116,9 @@
|
||||
color: var(--theia-ui-font-color2);
|
||||
}
|
||||
|
||||
.component-list-item .header .installed {
|
||||
.component-list-item .header .installed:before {
|
||||
margin-left: 4px;
|
||||
display: inline-block;
|
||||
justify-self: end;
|
||||
background-color: var(--theia-accent-color1);
|
||||
padding: 2px 4px 2px 4px;
|
||||
@ -117,6 +126,13 @@
|
||||
font-weight: bold;
|
||||
max-height: calc(1em + 4px);
|
||||
color: var(--theia-inverse-ui-font-color0);
|
||||
content: 'INSTALLED';
|
||||
}
|
||||
|
||||
.component-list-item .header .installed:hover:before {
|
||||
background-color: var(--theia-inverse-ui-font-color0);
|
||||
color: var(--theia-accent-color1);
|
||||
content: 'UNINSTALL';
|
||||
}
|
||||
|
||||
.component-list-item[min-width~="170px"] .footer {
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { Installable } from './installable';
|
||||
|
||||
export interface ArduinoComponent {
|
||||
readonly name: string;
|
||||
@ -6,8 +7,8 @@ export interface ArduinoComponent {
|
||||
readonly description: string;
|
||||
readonly moreInfoLink?: string;
|
||||
|
||||
readonly availableVersions: string[];
|
||||
readonly availableVersions: Installable.Version[];
|
||||
readonly installable: boolean;
|
||||
|
||||
readonly installedVersion?: string;
|
||||
readonly installedVersion?: Installable.Version;
|
||||
}
|
||||
|
@ -46,10 +46,15 @@ export interface BoardInstalledEvent {
|
||||
readonly pkg: Readonly<BoardPackage>;
|
||||
}
|
||||
|
||||
export interface BoardUninstalledEvent {
|
||||
readonly pkg: Readonly<BoardPackage>;
|
||||
}
|
||||
|
||||
export const BoardsServiceClient = Symbol('BoardsServiceClient');
|
||||
export interface BoardsServiceClient {
|
||||
notifyAttachedBoardsChanged(event: AttachedBoardsChangeEvent): void;
|
||||
notifyBoardInstalled(event: BoardInstalledEvent): void
|
||||
notifyBoardUninstalled(event: BoardUninstalledEvent): void
|
||||
}
|
||||
|
||||
export const BoardsServicePath = '/services/boards-service';
|
||||
@ -130,7 +135,7 @@ export namespace Port {
|
||||
}
|
||||
if (isOSX) {
|
||||
// Example: `/dev/cu.usbmodem14401`
|
||||
if (/(tty|cu)\..*/.test(address.substring('/dev/'.length))) {
|
||||
if (/(tty|cu)\..*/.test(address.substring('/dev/'.length))) {
|
||||
return [
|
||||
'/dev/cu.MALS',
|
||||
'/dev/cu.SOC',
|
||||
|
@ -1,3 +1,23 @@
|
||||
export interface Installable<T> {
|
||||
install(item: T): Promise<void>;
|
||||
}
|
||||
const naturalCompare: (left: string, right: string) => number = require('string-natural-compare').caseInsensitive;
|
||||
import { ArduinoComponent } from './arduino-component';
|
||||
|
||||
export interface Installable<T extends ArduinoComponent> {
|
||||
/**
|
||||
* If `options.version` is specified, that will be installed. Otherwise, `item.availableVersions[0]`.
|
||||
*/
|
||||
install(options: { item: T, version?: Installable.Version }): Promise<void>;
|
||||
|
||||
/**
|
||||
* Uninstalls the given component. It is a NOOP if not installed.
|
||||
*/
|
||||
uninstall(options: { item: T }): Promise<void>;
|
||||
}
|
||||
export namespace Installable {
|
||||
export type Version = string;
|
||||
export namespace Version {
|
||||
/**
|
||||
* Most recent version comes first, then the previous versions. (`1.8.1`, `1.6.3`, `1.6.2`, `1.6.1` and so on.)
|
||||
*/
|
||||
export const COMPARATOR = (left: Version, right: Version) => naturalCompare(right, left);
|
||||
}
|
||||
}
|
||||
|
@ -5,14 +5,9 @@ import { ArduinoComponent } from './arduino-component';
|
||||
export const LibraryServicePath = '/services/library-service';
|
||||
export const LibraryService = Symbol('LibraryService');
|
||||
export interface LibraryService extends Installable<Library>, Searchable<Library> {
|
||||
install(library: Library): Promise<void>;
|
||||
install(options: { item: Library, version?: Installable.Version }): Promise<void>;
|
||||
}
|
||||
|
||||
export interface Library extends ArduinoComponent {
|
||||
readonly builtIn?: boolean;
|
||||
}
|
||||
|
||||
export namespace Library {
|
||||
// TODO: figure out whether we need a dedicated `version` type.
|
||||
export type Version = string;
|
||||
}
|
@ -8,10 +8,11 @@ export interface SketchesService {
|
||||
getSketches(uri?: string): Promise<Sketch[]>
|
||||
getSketchFiles(uri: string): Promise<string[]>
|
||||
/**
|
||||
* Creates a new sketch folder in the `parentUri` location. If `parentUri` is not specified,
|
||||
* it falls back to the default `sketchDirUri` from the CLI.
|
||||
* Creates a new sketch folder in the `parentUri` location.
|
||||
* Normally, `parentUri` is the client's workspace root, or the default `sketchDirUri` from the CLI.
|
||||
* Note, `parentUri` and `sketchDirUri` can be the same.
|
||||
*/
|
||||
createNewSketch(parentUri?: string): Promise<Sketch>
|
||||
createNewSketch(parentUri: string): Promise<Sketch>
|
||||
isSketchFolder(uri: string): Promise<boolean>
|
||||
}
|
||||
|
||||
|
@ -38,10 +38,10 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
bind(ArduinoCliContribution).toSelf().inSingletonScope();
|
||||
bind(CliContribution).toService(ArduinoCliContribution);
|
||||
|
||||
// Provides the path of the Ardunio CLI.
|
||||
// Provides the path of the Arduino CLI.
|
||||
bind(ArduinoCli).toSelf().inSingletonScope();
|
||||
|
||||
// Shared daemonn
|
||||
// Shared daemon
|
||||
bind(ArduinoDaemon).toSelf().inSingletonScope();
|
||||
bind(BackendApplicationContribution).toService(ArduinoDaemon);
|
||||
|
||||
|
@ -26,30 +26,15 @@ export class ArduinoCli {
|
||||
const buildVersion = await this.spawn(`"${buildCli}"`, ['version']);
|
||||
const buildShortVersion = (buildVersion.match(version) || [])[0];
|
||||
this.execPath = buildCli;
|
||||
try {
|
||||
const pathCli = await new Promise<string>((resolve, reject) => {
|
||||
which(cli, (error, path) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
resolve(path);
|
||||
});
|
||||
});
|
||||
if (!pathCli) {
|
||||
return buildCli;
|
||||
}
|
||||
const pathVersion = await this.spawn(`"${pathCli}"`, ['version']);
|
||||
const pathShortVersion = (pathVersion.match(version) || [])[0];
|
||||
if (semver.gt(pathShortVersion, buildShortVersion)) {
|
||||
this.execPath = pathCli;
|
||||
return pathCli;
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.warn(`Could not check for Arduino CLI in $PATH, using embedded CLI instead:`, error);
|
||||
// Any errors here should be safe to ignore, e.g.:
|
||||
// - Could not search for CLI in $PATH
|
||||
// - Could not get version of CLI in $PATH
|
||||
const pathCli = await new Promise<string | undefined>(resolve => which(cli, (error, path) => resolve(error ? undefined : path)));
|
||||
if (!pathCli) {
|
||||
return buildCli;
|
||||
}
|
||||
const pathVersion = await this.spawn(`"${pathCli}"`, ['version']);
|
||||
const pathShortVersion = (pathVersion.match(version) || [])[0];
|
||||
if (semver.gt(pathShortVersion, buildShortVersion)) {
|
||||
this.execPath = pathCli;
|
||||
return pathCli;
|
||||
}
|
||||
return buildCli;
|
||||
}
|
||||
|
@ -2,14 +2,28 @@ import * as PQueue from 'p-queue';
|
||||
import { injectable, inject, postConstruct, named } from 'inversify';
|
||||
import { ILogger } from '@theia/core/lib/common/logger';
|
||||
import { BoardsService, AttachedSerialBoard, BoardPackage, Board, AttachedNetworkBoard, BoardsServiceClient, Port } from '../common/protocol/boards-service';
|
||||
import { PlatformSearchReq, PlatformSearchResp, PlatformInstallReq, PlatformInstallResp, PlatformListReq, PlatformListResp } from './cli-protocol/commands/core_pb';
|
||||
import {
|
||||
PlatformSearchReq,
|
||||
PlatformSearchResp,
|
||||
PlatformInstallReq,
|
||||
PlatformInstallResp,
|
||||
PlatformListReq,
|
||||
PlatformListResp,
|
||||
Platform,
|
||||
PlatformUninstallReq,
|
||||
PlatformUninstallResp
|
||||
} from './cli-protocol/commands/core_pb';
|
||||
import { CoreClientProvider } from './core-client-provider';
|
||||
import { BoardListReq, BoardListResp } from './cli-protocol/commands/board_pb';
|
||||
import { ToolOutputServiceServer } from '../common/protocol/tool-output-service';
|
||||
import { Installable } from '../common/protocol/installable';
|
||||
|
||||
@injectable()
|
||||
export class BoardsServiceImpl implements BoardsService {
|
||||
|
||||
@inject(ILogger)
|
||||
protected logger: ILogger;
|
||||
|
||||
@inject(ILogger)
|
||||
@named('discovery')
|
||||
protected discoveryLogger: ILogger;
|
||||
@ -24,11 +38,11 @@ export class BoardsServiceImpl implements BoardsService {
|
||||
protected discoveryTimer: NodeJS.Timeout | undefined;
|
||||
/**
|
||||
* Poor man's serial discovery:
|
||||
* Stores the state of the currently discovered, attached boards.
|
||||
* This state is updated via periodical polls.
|
||||
* Stores the state of the currently discovered and attached boards.
|
||||
* This state is updated via periodical polls. If there diff, a change event will be sent out to the frontend.
|
||||
*/
|
||||
protected _attachedBoards: { boards: Board[] } = { boards: [] };
|
||||
protected _availablePorts: { ports: Port[] } = { ports: [] };
|
||||
protected attachedBoards: { boards: Board[] } = { boards: [] };
|
||||
protected availablePorts: { ports: Port[] } = { ports: [] };
|
||||
protected client: BoardsServiceClient | undefined;
|
||||
protected readonly queue = new PQueue({ autoStart: true, concurrency: 1 });
|
||||
|
||||
@ -38,8 +52,8 @@ export class BoardsServiceImpl implements BoardsService {
|
||||
this.discoveryLogger.trace('Discovering attached boards and available ports...');
|
||||
this.doGetAttachedBoardsAndAvailablePorts().then(({ boards, ports }) => {
|
||||
const update = (oldBoards: Board[], newBoards: Board[], oldPorts: Port[], newPorts: Port[], message: string) => {
|
||||
this._attachedBoards = { boards: newBoards };
|
||||
this._availablePorts = { ports: newPorts };
|
||||
this.attachedBoards = { boards: newBoards };
|
||||
this.availablePorts = { ports: newPorts };
|
||||
this.discoveryLogger.info(`${message} - Discovered boards: ${JSON.stringify(newBoards)} and available ports: ${JSON.stringify(newPorts)}`);
|
||||
if (this.client) {
|
||||
this.client.notifyAttachedBoardsChanged({
|
||||
@ -95,17 +109,21 @@ export class BoardsServiceImpl implements BoardsService {
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.logger.info('>>> Disposing boards service...')
|
||||
this.queue.pause();
|
||||
this.queue.clear();
|
||||
if (this.discoveryTimer !== undefined) {
|
||||
clearInterval(this.discoveryTimer);
|
||||
}
|
||||
this.logger.info('<<< Disposed boards service.')
|
||||
}
|
||||
|
||||
async getAttachedBoards(): Promise<{ boards: Board[] }> {
|
||||
return this._attachedBoards;
|
||||
return this.attachedBoards;
|
||||
}
|
||||
|
||||
async getAvailablePorts(): Promise<{ ports: Port[] }> {
|
||||
return this._availablePorts;
|
||||
return this.availablePorts;
|
||||
}
|
||||
|
||||
private async doGetAttachedBoardsAndAvailablePorts(): Promise<{ boards: Board[], ports: Port[] }> {
|
||||
@ -207,35 +225,76 @@ export class BoardsServiceImpl implements BoardsService {
|
||||
|
||||
const req = new PlatformSearchReq();
|
||||
req.setSearchArgs(options.query || "");
|
||||
req.setAllVersions(true);
|
||||
req.setInstance(instance);
|
||||
const resp = await new Promise<PlatformSearchResp>((resolve, reject) => client.platformSearch(req, (err, resp) => (!!err ? reject : resolve)(!!err ? err : resp)));
|
||||
|
||||
let items = resp.getSearchOutputList().map(item => {
|
||||
const packages = new Map<string, BoardPackage>();
|
||||
const toPackage = (platform: Platform) => {
|
||||
let installedVersion: string | undefined;
|
||||
const matchingPlatform = installedPlatforms.find(ip => ip.getId() === item.getId());
|
||||
const matchingPlatform = installedPlatforms.find(ip => ip.getId() === platform.getId());
|
||||
if (!!matchingPlatform) {
|
||||
installedVersion = matchingPlatform.getInstalled();
|
||||
}
|
||||
|
||||
const result: BoardPackage = {
|
||||
id: item.getId(),
|
||||
name: item.getName(),
|
||||
author: item.getMaintainer(),
|
||||
availableVersions: [item.getLatest()],
|
||||
description: item.getBoardsList().map(b => b.getName()).join(", "),
|
||||
return {
|
||||
id: platform.getId(),
|
||||
name: platform.getName(),
|
||||
author: platform.getMaintainer(),
|
||||
availableVersions: [platform.getLatest()],
|
||||
description: platform.getBoardsList().map(b => b.getName()).join(", "),
|
||||
installable: true,
|
||||
summary: "Boards included in this package:",
|
||||
installedVersion,
|
||||
boards: item.getBoardsList().map(b => <Board>{ name: b.getName(), fqbn: b.getFqbn() }),
|
||||
moreInfoLink: item.getWebsite()
|
||||
boards: platform.getBoardsList().map(b => <Board>{ name: b.getName(), fqbn: b.getFqbn() }),
|
||||
moreInfoLink: platform.getWebsite()
|
||||
}
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
return { items };
|
||||
// We must group the cores by ID, and sort platforms by, first the installed version, then version alphabetical order.
|
||||
// Otherwise we lose the FQBN information.
|
||||
const groupedById: Map<string, Platform[]> = new Map();
|
||||
for (const platform of resp.getSearchOutputList()) {
|
||||
const id = platform.getId();
|
||||
if (groupedById.has(id)) {
|
||||
groupedById.get(id)!.push(platform);
|
||||
} else {
|
||||
groupedById.set(id, [platform]);
|
||||
}
|
||||
}
|
||||
const installedAwareVersionComparator = (left: Platform, right: Platform) => {
|
||||
// XXX: we cannot rely on `platform.getInstalled()`, it is always an empty string.
|
||||
const leftInstalled = !!installedPlatforms.find(ip => ip.getId() === left.getId() && ip.getInstalled() === left.getLatest());
|
||||
const rightInstalled = !!installedPlatforms.find(ip => ip.getId() === right.getId() && ip.getInstalled() === right.getLatest());
|
||||
if (leftInstalled && !rightInstalled) {
|
||||
return -1;
|
||||
}
|
||||
if (!leftInstalled && rightInstalled) {
|
||||
return 1;
|
||||
}
|
||||
return Installable.Version.COMPARATOR(right.getLatest(), left.getLatest()); // Higher version comes first.
|
||||
}
|
||||
for (const id of groupedById.keys()) {
|
||||
groupedById.get(id)!.sort(installedAwareVersionComparator);
|
||||
}
|
||||
|
||||
for (const id of groupedById.keys()) {
|
||||
for (const platform of groupedById.get(id)!) {
|
||||
const id = platform.getId();
|
||||
const pkg = packages.get(id);
|
||||
if (pkg) {
|
||||
pkg.availableVersions.push(platform.getLatest());
|
||||
pkg.availableVersions.sort(Installable.Version.COMPARATOR);
|
||||
} else {
|
||||
packages.set(id, toPackage(platform));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { items: [...packages.values()] };
|
||||
}
|
||||
|
||||
async install(pkg: BoardPackage): Promise<void> {
|
||||
async install(options: { item: BoardPackage, version?: Installable.Version }): Promise<void> {
|
||||
const pkg = options.item;
|
||||
const version = !!options.version ? options.version : pkg.availableVersions[0];
|
||||
const coreClient = await this.coreClientProvider.getClient();
|
||||
if (!coreClient) {
|
||||
return;
|
||||
@ -248,7 +307,7 @@ export class BoardsServiceImpl implements BoardsService {
|
||||
req.setInstance(instance);
|
||||
req.setArchitecture(boardName);
|
||||
req.setPlatformPackage(platform);
|
||||
req.setVersion(pkg.availableVersions[0]);
|
||||
req.setVersion(version);
|
||||
|
||||
console.info("Starting board installation", pkg);
|
||||
const resp = client.platformInstall(req);
|
||||
@ -268,4 +327,38 @@ export class BoardsServiceImpl implements BoardsService {
|
||||
console.info("Board installation done", pkg);
|
||||
}
|
||||
|
||||
async uninstall(options: { item: BoardPackage }): Promise<void> {
|
||||
const pkg = options.item;
|
||||
const coreClient = await this.coreClientProvider.getClient();
|
||||
if (!coreClient) {
|
||||
return;
|
||||
}
|
||||
const { client, instance } = coreClient;
|
||||
|
||||
const [platform, boardName] = pkg.id.split(":");
|
||||
|
||||
const req = new PlatformUninstallReq();
|
||||
req.setInstance(instance);
|
||||
req.setArchitecture(boardName);
|
||||
req.setPlatformPackage(platform);
|
||||
|
||||
console.info("Starting board uninstallation", pkg);
|
||||
let logged = false;
|
||||
const resp = client.platformUninstall(req);
|
||||
resp.on('data', (_: PlatformUninstallResp) => {
|
||||
if (!logged) {
|
||||
this.toolOutputService.publishNewOutput("board uninstall", `uninstalling ${pkg.id}\n`)
|
||||
logged = true;
|
||||
}
|
||||
})
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
resp.on('end', resolve);
|
||||
resp.on('error', reject);
|
||||
});
|
||||
if (this.client) {
|
||||
this.client.notifyBoardUninstalled({ pkg });
|
||||
}
|
||||
console.info("Board uninstallation done", pkg);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
// file: commands/board.proto
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
|
||||
import * as jspb from "google-protobuf";
|
||||
import * as commands_common_pb from "../commands/common_pb";
|
||||
|
@ -2,6 +2,7 @@
|
||||
// file: commands/commands.proto
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
|
||||
import * as grpc from "@grpc/grpc-js";
|
||||
import * as commands_commands_pb from "../commands/commands_pb";
|
||||
|
@ -2,6 +2,7 @@
|
||||
// file: commands/commands.proto
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
|
||||
import * as jspb from "google-protobuf";
|
||||
import * as commands_common_pb from "../commands/common_pb";
|
||||
|
@ -2,6 +2,7 @@
|
||||
// file: commands/common.proto
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
|
||||
import * as jspb from "google-protobuf";
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
// file: commands/compile.proto
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
|
||||
import * as jspb from "google-protobuf";
|
||||
import * as commands_common_pb from "../commands/common_pb";
|
||||
|
@ -2,6 +2,7 @@
|
||||
// file: commands/core.proto
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
|
||||
import * as jspb from "google-protobuf";
|
||||
import * as commands_common_pb from "../commands/common_pb";
|
||||
@ -262,6 +263,9 @@ export class PlatformSearchReq extends jspb.Message {
|
||||
getSearchArgs(): string;
|
||||
setSearchArgs(value: string): void;
|
||||
|
||||
getAllVersions(): boolean;
|
||||
setAllVersions(value: boolean): void;
|
||||
|
||||
|
||||
serializeBinary(): Uint8Array;
|
||||
toObject(includeInstance?: boolean): PlatformSearchReq.AsObject;
|
||||
@ -277,6 +281,7 @@ export namespace PlatformSearchReq {
|
||||
export type AsObject = {
|
||||
instance?: commands_common_pb.Instance.AsObject,
|
||||
searchArgs: string,
|
||||
allVersions: boolean,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1705,7 +1705,8 @@ proto.cc.arduino.cli.commands.PlatformSearchReq.prototype.toObject = function(op
|
||||
proto.cc.arduino.cli.commands.PlatformSearchReq.toObject = function(includeInstance, msg) {
|
||||
var f, obj = {
|
||||
instance: (f = msg.getInstance()) && commands_common_pb.Instance.toObject(includeInstance, f),
|
||||
searchArgs: jspb.Message.getFieldWithDefault(msg, 2, "")
|
||||
searchArgs: jspb.Message.getFieldWithDefault(msg, 2, ""),
|
||||
allVersions: jspb.Message.getFieldWithDefault(msg, 3, false)
|
||||
};
|
||||
|
||||
if (includeInstance) {
|
||||
@ -1751,6 +1752,10 @@ proto.cc.arduino.cli.commands.PlatformSearchReq.deserializeBinaryFromReader = fu
|
||||
var value = /** @type {string} */ (reader.readString());
|
||||
msg.setSearchArgs(value);
|
||||
break;
|
||||
case 3:
|
||||
var value = /** @type {boolean} */ (reader.readBool());
|
||||
msg.setAllVersions(value);
|
||||
break;
|
||||
default:
|
||||
reader.skipField();
|
||||
break;
|
||||
@ -1795,6 +1800,13 @@ proto.cc.arduino.cli.commands.PlatformSearchReq.serializeBinaryToWriter = functi
|
||||
f
|
||||
);
|
||||
}
|
||||
f = message.getAllVersions();
|
||||
if (f) {
|
||||
writer.writeBool(
|
||||
3,
|
||||
f
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -1843,6 +1855,23 @@ proto.cc.arduino.cli.commands.PlatformSearchReq.prototype.setSearchArgs = functi
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* optional bool all_versions = 3;
|
||||
* Note that Boolean fields may be set to 0/1 when serialized from a Java server.
|
||||
* You should avoid comparisons like {@code val === true/false} in those cases.
|
||||
* @return {boolean}
|
||||
*/
|
||||
proto.cc.arduino.cli.commands.PlatformSearchReq.prototype.getAllVersions = function() {
|
||||
return /** @type {boolean} */ (jspb.Message.getFieldWithDefault(this, 3, false));
|
||||
};
|
||||
|
||||
|
||||
/** @param {boolean} value */
|
||||
proto.cc.arduino.cli.commands.PlatformSearchReq.prototype.setAllVersions = function(value) {
|
||||
jspb.Message.setProto3BooleanField(this, 3, value);
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Generated by JsPbCodeGenerator.
|
||||
|
@ -2,6 +2,7 @@
|
||||
// file: commands/lib.proto
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
|
||||
import * as jspb from "google-protobuf";
|
||||
import * as commands_common_pb from "../commands/common_pb";
|
||||
|
@ -2,6 +2,7 @@
|
||||
// file: commands/upload.proto
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
|
||||
import * as jspb from "google-protobuf";
|
||||
import * as commands_common_pb from "../commands/common_pb";
|
||||
|
@ -2,6 +2,7 @@
|
||||
// file: monitor/monitor.proto
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
|
||||
import * as grpc from "@grpc/grpc-js";
|
||||
import * as monitor_monitor_pb from "../monitor/monitor_pb";
|
||||
|
@ -2,6 +2,7 @@
|
||||
// file: monitor/monitor.proto
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
|
||||
import * as jspb from "google-protobuf";
|
||||
import * as google_protobuf_struct_pb from "google-protobuf/google/protobuf/struct_pb";
|
||||
|
@ -42,7 +42,24 @@ export class CoreClientProviderImpl implements CoreClientProvider {
|
||||
|
||||
async getClient(workspaceRootOrResourceUri?: string): Promise<Client | undefined> {
|
||||
return this.clientRequestQueue.add(() => new Promise<Client | undefined>(async resolve => {
|
||||
const roots = await this.workspaceServiceExt.roots();
|
||||
let roots = undefined;
|
||||
try {
|
||||
roots = await this.workspaceServiceExt.roots();
|
||||
} catch (e) {
|
||||
if (e instanceof Error && e.message === 'Connection got disposed.') {
|
||||
console.info('The frontend has already disconnected.');
|
||||
// Ignore it for now: https://github.com/eclipse-theia/theia/issues/6499
|
||||
// Client has disconnected, and the server still runs the serial board poll.
|
||||
// The poll requires the client's workspace roots, but the client has disconnected :/
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
if (!roots) {
|
||||
resolve(undefined);
|
||||
return
|
||||
}
|
||||
|
||||
if (!workspaceRootOrResourceUri) {
|
||||
resolve(this.getOrCreateClient(roots[0]));
|
||||
return;
|
||||
|
@ -1,9 +1,20 @@
|
||||
import { injectable, inject } from 'inversify';
|
||||
import { Library, LibraryService } from '../common/protocol/library-service';
|
||||
import { CoreClientProvider } from './core-client-provider';
|
||||
import { LibrarySearchReq, LibrarySearchResp, LibraryListReq, LibraryListResp, LibraryRelease,
|
||||
InstalledLibrary, LibraryInstallReq, LibraryInstallResp } from './cli-protocol/commands/lib_pb';
|
||||
import {
|
||||
LibrarySearchReq,
|
||||
LibrarySearchResp,
|
||||
LibraryListReq,
|
||||
LibraryListResp,
|
||||
LibraryRelease,
|
||||
InstalledLibrary,
|
||||
LibraryInstallReq,
|
||||
LibraryInstallResp,
|
||||
LibraryUninstallReq,
|
||||
LibraryUninstallResp
|
||||
} from './cli-protocol/commands/lib_pb';
|
||||
import { ToolOutputServiceServer } from '../common/protocol/tool-output-service';
|
||||
import { Installable } from '../common/protocol/installable';
|
||||
|
||||
@injectable()
|
||||
export class LibraryServiceImpl implements LibraryService {
|
||||
@ -43,6 +54,8 @@ export class LibraryServiceImpl implements LibraryService {
|
||||
.filter(item => !!item.getLatest())
|
||||
.slice(0, 50)
|
||||
.map(item => {
|
||||
// TODO: This seems to contain only the latest item instead of all of the items.
|
||||
const availableVersions = item.getReleasesMap().getEntryList().map(([key, _]) => key).sort(Installable.Version.COMPARATOR);
|
||||
let installedVersion: string | undefined;
|
||||
const installed = installedLibsIdx.get(item.getName());
|
||||
if (installed) {
|
||||
@ -51,14 +64,16 @@ export class LibraryServiceImpl implements LibraryService {
|
||||
return toLibrary({
|
||||
name: item.getName(),
|
||||
installable: true,
|
||||
installedVersion
|
||||
}, item.getLatest()!)
|
||||
installedVersion,
|
||||
}, item.getLatest()!, availableVersions)
|
||||
})
|
||||
|
||||
return { items };
|
||||
}
|
||||
|
||||
async install(library: Library): Promise<void> {
|
||||
async install(options: { item: Library, version?: Installable.Version }): Promise<void> {
|
||||
const library = options.item;
|
||||
const version = !!options.version ? options.version : library.availableVersions[0];
|
||||
const coreClient = await this.coreClientProvider.getClient();
|
||||
if (!coreClient) {
|
||||
return;
|
||||
@ -68,7 +83,7 @@ export class LibraryServiceImpl implements LibraryService {
|
||||
const req = new LibraryInstallReq();
|
||||
req.setInstance(instance);
|
||||
req.setName(library.name);
|
||||
req.setVersion(library.availableVersions[0]);
|
||||
req.setVersion(version);
|
||||
|
||||
const resp = client.libraryInstall(req);
|
||||
resp.on('data', (r: LibraryInstallResp) => {
|
||||
@ -83,16 +98,43 @@ export class LibraryServiceImpl implements LibraryService {
|
||||
});
|
||||
}
|
||||
|
||||
async uninstall(options: { item: Library }): Promise<void> {
|
||||
const library = options.item;
|
||||
const coreClient = await this.coreClientProvider.getClient();
|
||||
if (!coreClient) {
|
||||
return;
|
||||
}
|
||||
const { client, instance } = coreClient;
|
||||
|
||||
const req = new LibraryUninstallReq();
|
||||
req.setInstance(instance);
|
||||
req.setName(library.name);
|
||||
req.setVersion(library.installedVersion!);
|
||||
|
||||
let logged = false;
|
||||
const resp = client.libraryUninstall(req);
|
||||
resp.on('data', (_: LibraryUninstallResp) => {
|
||||
if (!logged) {
|
||||
this.toolOutputService.publishNewOutput("library uninstall", `uninstalling ${library.name}:${library.installedVersion}%\n`)
|
||||
logged = true;
|
||||
}
|
||||
});
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
resp.on('end', resolve);
|
||||
resp.on('error', reject);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function toLibrary(tpl: Partial<Library>, release: LibraryRelease): Library {
|
||||
function toLibrary(tpl: Partial<Library>, release: LibraryRelease, availableVersions: string[]): Library {
|
||||
return {
|
||||
name: "",
|
||||
installable: false,
|
||||
...tpl,
|
||||
|
||||
author: release.getAuthor(),
|
||||
availableVersions: [release.getVersion()],
|
||||
availableVersions,
|
||||
description: release.getSentence(),
|
||||
moreInfoLink: release.getWebsite(),
|
||||
summary: release.getParagraph()
|
||||
|
@ -52,14 +52,16 @@ export class SketchesServiceImpl implements SketchesService {
|
||||
const uris: string[] = [];
|
||||
const fsPath = FileUri.fsPath(uri);
|
||||
const stats = fs.lstatSync(fsPath);
|
||||
if (stats.isDirectory && await this.isSketchFolder(uri)) {
|
||||
const fileNames = fs.readdirSync(fsPath);
|
||||
for (const fileName of fileNames) {
|
||||
const filePath = path.join(fsPath, fileName);
|
||||
if (ALLOWED_FILE_EXTENSIONS.indexOf(path.extname(filePath)) !== -1
|
||||
&& fs.existsSync(filePath)
|
||||
&& fs.lstatSync(filePath).isFile()) {
|
||||
uris.push(FileUri.create(filePath).toString())
|
||||
if (stats.isDirectory) {
|
||||
if (await this.isSketchFolder(uri)) {
|
||||
const fileNames = fs.readdirSync(fsPath);
|
||||
for (const fileName of fileNames) {
|
||||
const filePath = path.join(fsPath, fileName);
|
||||
if (ALLOWED_FILE_EXTENSIONS.indexOf(path.extname(filePath)) !== -1
|
||||
&& fs.existsSync(filePath)
|
||||
&& fs.lstatSync(filePath).isFile()) {
|
||||
uris.push(FileUri.create(filePath).toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
return uris;
|
||||
|
@ -28,4 +28,4 @@
|
||||
"files": [
|
||||
"../node_modules/@theia/monaco/src/typings/monaco/index.d.ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "browser-app",
|
||||
"version": "0.0.2",
|
||||
"version": "0.0.3",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@theia/core": "next",
|
||||
@ -18,7 +18,7 @@
|
||||
"@theia/terminal": "next",
|
||||
"@theia/workspace": "next",
|
||||
"@theia/textmate-grammars": "next",
|
||||
"arduino-ide-extension": "0.0.2"
|
||||
"arduino-ide-extension": "0.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@theia/cli": "next"
|
||||
|
@ -1,50 +1,49 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "electron-app",
|
||||
"version": "0.0.2",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@theia/core": "next",
|
||||
"@theia/cpp": "next",
|
||||
"@theia/editor": "next",
|
||||
"@theia/electron": "next",
|
||||
"@theia/file-search": "next",
|
||||
"@theia/filesystem": "next",
|
||||
"@theia/languages": "next",
|
||||
"@theia/messages": "next",
|
||||
"@theia/monaco": "next",
|
||||
"@theia/navigator": "next",
|
||||
"@theia/preferences": "next",
|
||||
"@theia/process": "next",
|
||||
"@theia/terminal": "next",
|
||||
"@theia/workspace": "next",
|
||||
"@theia/textmate-grammars": "next",
|
||||
"arduino-ide-extension": "0.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@theia/cli": "next",
|
||||
"electron": "^4.2.0"
|
||||
},
|
||||
"scripts": {
|
||||
"prepare": "theia build --mode development",
|
||||
"start": "theia start",
|
||||
"watch": "theia build --watch --mode development"
|
||||
},
|
||||
"theia": {
|
||||
"target": "electron",
|
||||
"frontend": {
|
||||
"config": {
|
||||
"applicationName": "Arduino Pro IDE",
|
||||
"defaultTheme": "arduino-theme",
|
||||
"preferences": {
|
||||
"editor.autoSave": "on"
|
||||
}
|
||||
}
|
||||
},
|
||||
"generator": {
|
||||
"config": {
|
||||
"preloadTemplate": "<div class='theia-preload' style='background-color: rgb(237, 241, 242);'></div>"
|
||||
}
|
||||
"private": true,
|
||||
"name": "electron-app",
|
||||
"version": "0.0.3",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@theia/core": "next",
|
||||
"@theia/cpp": "next",
|
||||
"@theia/editor": "next",
|
||||
"@theia/electron": "next",
|
||||
"@theia/file-search": "next",
|
||||
"@theia/filesystem": "next",
|
||||
"@theia/languages": "next",
|
||||
"@theia/messages": "next",
|
||||
"@theia/monaco": "next",
|
||||
"@theia/navigator": "next",
|
||||
"@theia/preferences": "next",
|
||||
"@theia/process": "next",
|
||||
"@theia/terminal": "next",
|
||||
"@theia/workspace": "next",
|
||||
"@theia/textmate-grammars": "next",
|
||||
"arduino-ide-extension": "0.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@theia/cli": "next"
|
||||
},
|
||||
"scripts": {
|
||||
"prepare": "theia build --mode development",
|
||||
"start": "theia start",
|
||||
"watch": "theia build --watch --mode development"
|
||||
},
|
||||
"theia": {
|
||||
"target": "electron",
|
||||
"frontend": {
|
||||
"config": {
|
||||
"applicationName": "Arduino Pro IDE",
|
||||
"defaultTheme": "arduino-theme",
|
||||
"preferences": {
|
||||
"editor.autoSave": "on"
|
||||
}
|
||||
}
|
||||
},
|
||||
"generator": {
|
||||
"config": {
|
||||
"preloadTemplate": "<div class='theia-preload' style='background-color: rgb(237, 241, 242);'></div>"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
"name": "arduino.Pro.IDE",
|
||||
"description": "Arduino Pro IDE",
|
||||
"main": "src-gen/frontend/electron-main.js",
|
||||
"author": "TypeFox",
|
||||
"author": "Arduino SA",
|
||||
"dependencies": {
|
||||
"google-protobuf": "^3.5.0",
|
||||
"arduino-ide-extension": "file:../working-copy/arduino-ide-extension"
|
||||
@ -27,14 +27,12 @@
|
||||
"url": "git+https://github.com/arduino/arduino-pro-ide.git"
|
||||
},
|
||||
"// Notes:": [
|
||||
"The `electronVersion` version was pinned for `@grpc/grpc-js` -> Node.js version constraints.",
|
||||
"`google-protobuf` was declared as it is not picked up by the `electron-builder` as a runtime dependency.",
|
||||
"The resolution for `fs-extra` was required due to this: https://spectrum.chat/theia/general/our-theia-electron-builder-app-no-longer-starts~f5cf09a0-6d88-448b-8818-24ad0ec2ee7c"
|
||||
],
|
||||
"build": {
|
||||
"productName": "Arduino Pro IDE",
|
||||
"appId": "arduino.Pro.IDE",
|
||||
"electronVersion": "4.2.0",
|
||||
"asar": false,
|
||||
"directories": {
|
||||
"buildResources": "resources"
|
||||
@ -46,7 +44,6 @@
|
||||
"!node_modules/**/*.spec.js",
|
||||
"!node_modules/@theia/**/test/*",
|
||||
"!node_modules/@theia/**/src/*.ts",
|
||||
"!node_modules/@theia/java/download",
|
||||
"!node_modules/@theia/**/lib/*browser/*",
|
||||
"!node_modules/@typefox/monaco-editor-core/*",
|
||||
"!node_modules/oniguruma/*",
|
||||
|
@ -9,8 +9,8 @@
|
||||
"cli": "./cli"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"author": "Arduino SA",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"depcheck": "^0.7.1",
|
||||
"shelljs": "^0.8.3",
|
||||
|
20
package.json
20
package.json
@ -1,25 +1,25 @@
|
||||
{
|
||||
"name": "arduino-editor",
|
||||
"version": "0.0.1",
|
||||
"description": "Arduino IDE built using Eclipse Theia",
|
||||
"version": "0.0.3",
|
||||
"description": "Arduino Pro IDE",
|
||||
"main": "index.js",
|
||||
"repository": "https://github.com/bcmi-labs/arduino-editor.git",
|
||||
"author": "Christian Weichel <christian.weichel@typefox.io>",
|
||||
"author": "Arduino SA",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"lerna": "^3.13.3"
|
||||
},
|
||||
"scripts": {
|
||||
"prepare": "lerna run prepare",
|
||||
"rebuild:browser": "theia rebuild:browser",
|
||||
"rebuild:electron": "theia rebuild:electron",
|
||||
"start": "yarn --cwd ./browser-app start",
|
||||
"watch": "lerna run watch --parallel"
|
||||
"prepare": "lerna run prepare",
|
||||
"rebuild:browser": "theia rebuild:browser",
|
||||
"rebuild:electron": "theia rebuild:electron",
|
||||
"start": "yarn --cwd ./browser-app start",
|
||||
"watch": "lerna run watch --parallel"
|
||||
},
|
||||
"workspaces": [
|
||||
"arduino-ide-extension",
|
||||
"electron-app",
|
||||
"browser-app"
|
||||
"electron-app",
|
||||
"browser-app"
|
||||
]
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user