Skip to content

Commit 75ef8ea

Browse files
authored
Merge pull request #27 from bcmi-labs/electron-4x-menu
Several UI enhancements
2 parents 7760915 + 1b90b7a commit 75ef8ea

16 files changed

+773
-272
lines changed

arduino-ide-browser/package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,10 @@
3131
"frontend": {
3232
"config": {
3333
"applicationName": "Arduino-PoC",
34-
"defaultTheme": "arduino-theme"
34+
"defaultTheme": "arduino-theme",
35+
"preferences": {
36+
"editor.autoSave": "on"
37+
}
3538
}
3639
}
3740
}

arduino-ide-electron/package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,10 @@
3434
"frontend": {
3535
"config": {
3636
"applicationName": "Arduino-PoC",
37-
"defaultTheme": "arduino-theme"
37+
"defaultTheme": "arduino-theme",
38+
"preferences": {
39+
"editor.autoSave": "on"
40+
}
3841
}
3942
}
4043
}

arduino-ide-extension/src/browser/arduino-commands.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,23 @@ export namespace ArduinoCommands {
1212
label: 'Upload Sketch'
1313
}
1414

15+
export const SHOW_OPEN_CONTEXT_MENU: Command = {
16+
id: 'arduino-show-open-context-menu',
17+
label: 'Open Sketch'
18+
}
19+
20+
export const OPEN_FILE_NAVIGATOR: Command = {
21+
id: 'arduino-open-file-navigator'
22+
}
23+
24+
export const OPEN_SKETCH: Command = {
25+
id: 'arduino-open-file'
26+
}
27+
28+
export const SAVE_SKETCH: Command = {
29+
id: 'arduino-save-file'
30+
}
31+
1532
export const NEW_SKETCH: Command = {
1633
id: "arduino-new-sketch",
1734
label: 'New Sketch',
Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,67 @@
1-
import { injectable } from "inversify";
2-
import { MenuContribution, MenuModelRegistry } from "@theia/core";
1+
import { injectable, inject } from "inversify";
2+
import { MenuContribution, MenuModelRegistry, MenuPath, CommandRegistry, Command } from "@theia/core";
33
import { CommonMenus } from "@theia/core/lib/browser";
44
import { ArduinoCommands } from "./arduino-commands";
5+
import { SketchesService, Sketch } from "../common/protocol/sketches-service";
6+
import { AWorkspaceService } from "./arduino-workspace-service";
7+
8+
export namespace ArduinoOpenSketchContextMenu {
9+
export const PATH: MenuPath = ['arduino-open-sketch-context-menu'];
10+
export const OPEN_GROUP: MenuPath = [...PATH, '1_open'];
11+
export const WS_SKETCHES_GROUP: MenuPath = [...PATH, '2_sketches'];
12+
export const EXAMPLE_SKETCHES_GROUP: MenuPath = [...PATH, '3_examples'];
13+
}
514

615
@injectable()
716
export class ArduinoFileMenuContribution implements MenuContribution {
817

18+
@inject(CommandRegistry)
19+
protected readonly commands: CommandRegistry;
20+
21+
@inject(SketchesService)
22+
protected readonly sketches: SketchesService;
23+
24+
constructor(
25+
@inject(AWorkspaceService) protected readonly workspaceService: AWorkspaceService,
26+
@inject(MenuModelRegistry) protected readonly menuRegistry: MenuModelRegistry) {
27+
workspaceService.onWorkspaceChanged(() => {
28+
if (this.workspaceService.workspace) {
29+
this.registerSketchesInMenu(menuRegistry);
30+
}
31+
})
32+
}
33+
34+
protected registerSketchesInMenu(registry: MenuModelRegistry) {
35+
this.getWorkspaceSketches().then(sketches => {
36+
sketches.forEach(sketch => {
37+
const command: Command = {
38+
id: 'openSketch' + sketch.name
39+
}
40+
this.commands.registerCommand(command, {
41+
execute: () => this.commands.executeCommand(ArduinoCommands.OPEN_SKETCH.id, sketch)
42+
});
43+
44+
registry.registerMenuAction(ArduinoOpenSketchContextMenu.WS_SKETCHES_GROUP, {
45+
commandId: command.id,
46+
label: sketch.name
47+
});
48+
})
49+
})
50+
}
51+
52+
protected async getWorkspaceSketches(): Promise<Sketch[]> {
53+
const sketches = this.sketches.getSketches(this.workspaceService.workspace);
54+
return sketches;
55+
}
56+
957
registerMenus(registry: MenuModelRegistry) {
1058
registry.registerMenuAction([...CommonMenus.FILE, '0_new_sletch'], {
1159
commandId: ArduinoCommands.NEW_SKETCH.id
1260
})
13-
}
1461

62+
registry.registerMenuAction(ArduinoOpenSketchContextMenu.OPEN_GROUP, {
63+
commandId: ArduinoCommands.OPEN_FILE_NAVIGATOR.id,
64+
label: 'Open...'
65+
});
66+
}
1567
}

arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx

Lines changed: 115 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import URI from '@theia/core/lib/common/uri';
44
import { EditorWidget } from '@theia/editor/lib/browser/editor-widget';
55
import { MessageService } from '@theia/core/lib/common/message-service';
66
import { CommandContribution, CommandRegistry } from '@theia/core/lib/common/command';
7-
import { DefaultFrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
87
import { TabBarToolbarContribution, TabBarToolbarRegistry } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
98
import { BoardsService } from '../common/protocol/boards-service';
109
import { ArduinoCommands } from './arduino-commands';
@@ -15,15 +14,22 @@ import { ToolOutputServiceClient } from '../common/protocol/tool-output-service'
1514
import { QuickPickService } from '@theia/core/lib/common/quick-pick-service';
1615
import { BoardsListWidgetFrontendContribution } from './boards/boards-widget-frontend-contribution';
1716
import { BoardsNotificationService } from './boards-notification-service';
18-
import { WorkspaceRootUriAwareCommandHandler } from '@theia/workspace/lib/browser/workspace-commands';
17+
import { WorkspaceRootUriAwareCommandHandler, WorkspaceCommands } from '@theia/workspace/lib/browser/workspace-commands';
1918
import { SelectionService } from '@theia/core';
2019
import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service';
2120
import { SketchFactory } from './sketch-factory';
2221
import { ArduinoToolbar } from './toolbar/arduino-toolbar';
2322
import { EditorManager } from '@theia/editor/lib/browser';
23+
import { ContextMenuRenderer, OpenerService, Widget } from '@theia/core/lib/browser';
24+
import { OpenFileDialogProps, FileDialogService } from '@theia/filesystem/lib/browser/file-dialog';
25+
import { FileSystem } from '@theia/filesystem/lib/common';
26+
import { ArduinoOpenSketchContextMenu } from './arduino-file-menu';
27+
import { Sketch, SketchesService } from '../common/protocol/sketches-service';
28+
import { WindowService } from '@theia/core/lib/browser/window/window-service';
29+
import { CommonCommands } from '@theia/core/lib/browser/common-frontend-contribution'
2430

2531
@injectable()
26-
export class ArduinoFrontendContribution extends DefaultFrontendApplicationContribution implements TabBarToolbarContribution, CommandContribution {
32+
export class ArduinoFrontendContribution implements TabBarToolbarContribution, CommandContribution {
2733

2834
@inject(MessageService)
2935
protected readonly messageService: MessageService;
@@ -61,6 +67,24 @@ export class ArduinoFrontendContribution extends DefaultFrontendApplicationContr
6167
@inject(EditorManager)
6268
protected readonly editorManager: EditorManager;
6369

70+
@inject(ContextMenuRenderer)
71+
protected readonly contextMenuRenderer: ContextMenuRenderer;
72+
73+
@inject(FileDialogService)
74+
protected readonly fileDialogService: FileDialogService;
75+
76+
@inject(FileSystem)
77+
protected readonly fileSystem: FileSystem;
78+
79+
@inject(OpenerService)
80+
protected readonly openerService: OpenerService;
81+
82+
@inject(WindowService)
83+
protected readonly windowService: WindowService;
84+
85+
@inject(SketchesService)
86+
protected readonly sketches: SketchesService;
87+
6488
@postConstruct()
6589
protected async init(): Promise<void> {
6690
// This is a hack. Otherwise, the backend services won't bind.
@@ -72,18 +96,31 @@ export class ArduinoFrontendContribution extends DefaultFrontendApplicationContr
7296
id: ArduinoCommands.VERIFY.id,
7397
command: ArduinoCommands.VERIFY.id,
7498
tooltip: 'Verify',
75-
group: 'arduino',
7699
text: '$(check)'
77100
});
78101
registry.registerItem({
79102
id: ArduinoCommands.UPLOAD.id,
80103
command: ArduinoCommands.UPLOAD.id,
81104
tooltip: 'Upload',
82-
group: 'arduino',
83105
text: '$(arrow-right)'
84106
});
107+
registry.registerItem({
108+
id: ArduinoCommands.SHOW_OPEN_CONTEXT_MENU.id,
109+
command: ArduinoCommands.SHOW_OPEN_CONTEXT_MENU.id,
110+
tooltip: 'Open',
111+
text: '$(arrow-up)'
112+
});
113+
registry.registerItem({
114+
id: ArduinoCommands.SAVE_SKETCH.id,
115+
command: ArduinoCommands.SAVE_SKETCH.id,
116+
tooltip: 'Save',
117+
text: '$(arrow-down)'
118+
});
85119
registry.registerItem({
86120
id: ConnectedBoards.TOOLBAR_ID,
121+
// render: () => <BoardsToolBarItem
122+
// onNoBoardsInstalled={this.onNoBoardsInstalled.bind(this)}
123+
// onUnknownBoard={this.onUnknownBoard.bind(this)} />,
87124
render: () => <ConnectedBoards
88125
boardsService={this.boardService}
89126
boardsNotificationService={this.boardsNotificationService}
@@ -137,6 +174,36 @@ export class ArduinoFrontendContribution extends DefaultFrontendApplicationContr
137174
}
138175
}
139176
});
177+
registry.registerCommand(ArduinoCommands.SHOW_OPEN_CONTEXT_MENU, {
178+
isVisible: widget => this.isArduinoToolbar(widget),
179+
isEnabled: widget => this.isArduinoToolbar(widget),
180+
execute: async (widget: Widget, event: React.MouseEvent<HTMLElement>) => {
181+
const el = (event.target as HTMLElement).parentElement;
182+
if (el) {
183+
this.contextMenuRenderer.render(ArduinoOpenSketchContextMenu.PATH, {
184+
x: el.getBoundingClientRect().left,
185+
y: el.getBoundingClientRect().top + el.offsetHeight
186+
});
187+
}
188+
}
189+
});
190+
registry.registerCommand(ArduinoCommands.OPEN_FILE_NAVIGATOR, {
191+
isEnabled: () => true,
192+
execute: () => this.doOpenFile()
193+
})
194+
registry.registerCommand(ArduinoCommands.OPEN_SKETCH, {
195+
isEnabled: () => true,
196+
execute: async (sketch: Sketch) => {
197+
this.openSketchFilesInNewWindow(sketch.uri);
198+
}
199+
})
200+
registry.registerCommand(ArduinoCommands.SAVE_SKETCH, {
201+
isEnabled: widget => this.isArduinoToolbar(widget),
202+
isVisible: widget => this.isArduinoToolbar(widget),
203+
execute: async (sketch: Sketch) => {
204+
registry.executeCommand(CommonCommands.SAVE_ALL.id);
205+
}
206+
})
140207
registry.registerCommand(ArduinoCommands.NEW_SKETCH, new WorkspaceRootUriAwareCommandHandler(this.workspaceService, this.selectionService, {
141208
execute: async uri => {
142209
try {
@@ -157,6 +224,49 @@ export class ArduinoFrontendContribution extends DefaultFrontendApplicationContr
157224
})
158225
}
159226

227+
protected async openSketchFilesInNewWindow(uri: string) {
228+
const location = new URL(window.location.href);
229+
location.searchParams.set('sketch', uri);
230+
this.windowService.openNewWindow(location.toString());
231+
}
232+
233+
async openSketchFiles(uri: string) {
234+
const fileStat = await this.fileSystem.getFileStat(uri);
235+
if (fileStat) {
236+
const sketchFiles = await this.sketches.getSketchFiles(fileStat);
237+
sketchFiles.forEach(sketchFile => {
238+
const uri = new URI(sketchFile);
239+
this.editorManager.open(uri);
240+
});
241+
}
242+
}
243+
244+
/**
245+
* Opens a file after prompting the `Open File` dialog. Resolves to `undefined`, if
246+
* - the workspace root is not set,
247+
* - the file to open does not exist, or
248+
* - it was not a file, but a directory.
249+
*
250+
* Otherwise, resolves to the URI of the file.
251+
*/
252+
protected async doOpenFile(): Promise<URI | undefined> {
253+
const props: OpenFileDialogProps = {
254+
title: WorkspaceCommands.OPEN_FILE.dialogLabel,
255+
canSelectFolders: false,
256+
canSelectFiles: true
257+
};
258+
const [rootStat] = await this.workspaceService.roots;
259+
const destinationFileUri = await this.fileDialogService.showOpenDialog(props, rootStat);
260+
if (destinationFileUri) {
261+
const destinationFile = await this.fileSystem.getFileStat(destinationFileUri.toString());
262+
if (destinationFile && !destinationFile.isDirectory) {
263+
await this.openSketchFilesInNewWindow(destinationFileUri.toString());
264+
return destinationFileUri;
265+
}
266+
}
267+
return undefined;
268+
}
269+
160270
protected getCurrentWidget(): EditorWidget | undefined {
161271
let widget = this.editorManager.currentEditor;
162272
if (!widget) {

arduino-ide-extension/src/browser/arduino-frontend-module.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@ import { CommandContribution } from '@theia/core/lib/common/command';
55
import { bindViewContribution } from '@theia/core/lib/browser/shell/view-contribution';
66
import { TabBarToolbarContribution } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
77
import { WebSocketConnectionProvider } from '@theia/core/lib/browser/messaging/ws-connection-provider';
8-
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application'
8+
import { FrontendApplicationContribution, FrontendApplication } from '@theia/core/lib/browser/frontend-application'
99
import { LanguageGrammarDefinitionContribution } from '@theia/monaco/lib/browser/textmate';
1010
import { LibraryListWidget } from './library/library-list-widget';
1111
import { ArduinoFrontendContribution } from './arduino-frontend-contribution';
1212
import { ArduinoLanguageGrammarContribution } from './language/arduino-language-grammar-contribution';
1313
import { LibraryService, LibraryServicePath } from '../common/protocol/library-service';
1414
import { BoardsService, BoardsServicePath } from '../common/protocol/boards-service';
15+
import { SketchesService, SketchesServicePath } from '../common/protocol/sketches-service';
1516
import { LibraryListWidgetFrontendContribution } from './library/list-widget-frontend-contribution';
1617
import { CoreService, CoreServicePath } from '../common/protocol/core-service';
1718
import { BoardsListWidget } from './boards/boards-list-widget';
@@ -44,12 +45,16 @@ import { MonacoStatusBarContribution } from '@theia/monaco/lib/browser/monaco-st
4445
import { SilentMonacoStatusBarContribution } from './customization/silent-monaco-status-bar-contribution';
4546
import { ApplicationShell } from '@theia/core/lib/browser';
4647
import { CustomApplicationShell } from './customization/custom-application-shell';
48+
import { CustomFrontendApplication } from './customization/custom-frontend-application';
49+
import { EditorWidgetFactory } from '@theia/editor/lib/browser/editor-widget-factory';
50+
import { CustomEditorWidgetFactory } from './customization/custom-editor-widget-factory';
4751

4852
export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Unbind, isBound: interfaces.IsBound, rebind: interfaces.Rebind) => {
4953
// Commands and toolbar items
5054
bind(ArduinoFrontendContribution).toSelf().inSingletonScope();
5155
bind(CommandContribution).toService(ArduinoFrontendContribution);
5256
bind(TabBarToolbarContribution).toService(ArduinoFrontendContribution);
57+
bind(FrontendApplicationContribution).toService(ArduinoFrontendContribution);
5358
bind(MenuContribution).to(ArduinoFileMenuContribution).inSingletonScope();
5459

5560
bind(ArduinoToolbarContribution).toSelf().inSingletonScope();
@@ -70,6 +75,9 @@ export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Un
7075
}));
7176
bind(FrontendApplicationContribution).toService(LibraryListWidgetFrontendContribution);
7277

78+
// Sketch list service
79+
bind(SketchesService).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, SketchesServicePath)).inSingletonScope();
80+
7381
// Boards Notification service for updating boards list
7482
// TODO (post-PoC): move this to boards service/backend
7583
bind(BoardsNotificationService).toSelf().inSingletonScope();
@@ -106,9 +114,11 @@ export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Un
106114
container.get(CoreService);
107115
container.get(LibraryService);
108116
container.get(BoardsService);
117+
container.get(SketchesService);
109118
return workspaceServiceExt;
110119
});
111120

121+
bind(AWorkspaceService).toSelf().inSingletonScope();
112122
rebind(WorkspaceService).to(AWorkspaceService).inSingletonScope();
113123
bind(SketchFactory).toSelf().inSingletonScope();
114124

@@ -120,7 +130,6 @@ export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Un
120130
bind(OutlineViewContribution).to(SilentOutlineViewContribution).inSingletonScope();
121131
unbind(ProblemContribution);
122132
bind(ProblemContribution).to(SilentProblemContribution).inSingletonScope();
123-
124133
unbind(FileNavigatorContribution);
125134
bind(FileNavigatorContribution).to(SilentNavigatorContribution).inSingletonScope();
126135
unbind(OutputToolbarContribution);
@@ -131,4 +140,8 @@ export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Un
131140
bind(MonacoStatusBarContribution).to(SilentMonacoStatusBarContribution).inSingletonScope();
132141
unbind(ApplicationShell);
133142
bind(ApplicationShell).to(CustomApplicationShell).inSingletonScope();
143+
unbind(FrontendApplication);
144+
bind(FrontendApplication).to(CustomFrontendApplication).inSingletonScope();
145+
unbind(EditorWidgetFactory);
146+
bind(EditorWidgetFactory).to(CustomEditorWidgetFactory).inSingletonScope();
134147
});

0 commit comments

Comments
 (0)