mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-07-24 11:46:32 +00:00
ATL-938: Added menu group categories.
Signed-off-by: Akos Kitta <kittaakos@typefox.io>
This commit is contained in:
parent
3e92567d52
commit
ba8885c8c8
@ -2,12 +2,13 @@ import { inject, injectable } from 'inversify';
|
||||
import { remote } from 'electron';
|
||||
import { MenuModelRegistry } from '@theia/core/lib/common/menu';
|
||||
import { DisposableCollection, Disposable } from '@theia/core/lib/common/disposable';
|
||||
import { firstToUpperCase } from '../../common/utils';
|
||||
import { BoardsConfig } from '../boards/boards-config';
|
||||
import { MainMenuManager } from '../../common/main-menu-manager';
|
||||
import { BoardsListWidget } from '../boards/boards-list-widget';
|
||||
import { NotificationCenter } from '../notification-center';
|
||||
import { BoardsServiceProvider } from '../boards/boards-service-provider';
|
||||
import { ArduinoMenus, unregisterSubmenu } from '../menu/arduino-menus';
|
||||
import { ArduinoMenus, PlaceholderMenuNode, unregisterSubmenu } from '../menu/arduino-menus';
|
||||
import { BoardsService, InstalledBoardWithPackage, AvailablePorts, Port } from '../../common/protocol';
|
||||
import { SketchContribution, Command, CommandRegistry } from './contribution';
|
||||
|
||||
@ -150,39 +151,61 @@ PID: ${PID}`;
|
||||
}
|
||||
|
||||
// Installed ports
|
||||
for (const address of Object.keys(availablePorts)) {
|
||||
if (!!availablePorts[address]) {
|
||||
const [port, boards] = availablePorts[address];
|
||||
if (!boards.length) {
|
||||
boards.push({
|
||||
name: ''
|
||||
});
|
||||
}
|
||||
for (const { name, fqbn } of boards) {
|
||||
const id = `arduino-select-port--${address}${fqbn ? `--${fqbn}` : ''}`;
|
||||
const command = { id };
|
||||
const handler = {
|
||||
execute: () => {
|
||||
if (!Port.equals(port, this.boardsServiceProvider.boardsConfig.selectedPort)) {
|
||||
this.boardsServiceProvider.boardsConfig = {
|
||||
selectedBoard: this.boardsServiceProvider.boardsConfig.selectedBoard,
|
||||
selectedPort: port
|
||||
const registerPorts = (ports: AvailablePorts) => {
|
||||
const addresses = Object.keys(ports);
|
||||
if (!addresses.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Register placeholder for protocol
|
||||
const [port] = ports[addresses[0]];
|
||||
const protocol = port.protocol;
|
||||
const menuPath = [...portsSubmenuPath, protocol];
|
||||
const placeholder = new PlaceholderMenuNode(menuPath, `${firstToUpperCase(port.protocol)} ports`);
|
||||
this.menuModelRegistry.registerMenuNode(menuPath, placeholder);
|
||||
this.toDisposeBeforeMenuRebuild.push(Disposable.create(() => this.menuModelRegistry.unregisterMenuNode(placeholder.id)));
|
||||
|
||||
for (const address of addresses) {
|
||||
if (!!ports[address]) {
|
||||
const [port, boards] = ports[address];
|
||||
if (!boards.length) {
|
||||
boards.push({
|
||||
name: ''
|
||||
});
|
||||
}
|
||||
for (const { name, fqbn } of boards) {
|
||||
const id = `arduino-select-port--${address}${fqbn ? `--${fqbn}` : ''}`;
|
||||
const command = { id };
|
||||
const handler = {
|
||||
execute: () => {
|
||||
if (!Port.equals(port, this.boardsServiceProvider.boardsConfig.selectedPort)) {
|
||||
this.boardsServiceProvider.boardsConfig = {
|
||||
selectedBoard: this.boardsServiceProvider.boardsConfig.selectedBoard,
|
||||
selectedPort: port
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
isToggled: () => Port.equals(port, this.boardsServiceProvider.boardsConfig.selectedPort)
|
||||
};
|
||||
const menuAction = {
|
||||
commandId: id,
|
||||
label: `${address}${name ? ` (${name})` : ''}`
|
||||
};
|
||||
this.commandRegistry.registerCommand(command, handler);
|
||||
this.toDisposeBeforeMenuRebuild.push(Disposable.create(() => this.commandRegistry.unregisterCommand(command)));
|
||||
this.menuModelRegistry.registerMenuAction(portsSubmenuPath, menuAction);
|
||||
},
|
||||
isToggled: () => Port.equals(port, this.boardsServiceProvider.boardsConfig.selectedPort)
|
||||
};
|
||||
const label = `${address}${name ? ` (${name})` : ''}`;
|
||||
const menuAction = {
|
||||
commandId: id,
|
||||
label,
|
||||
order: `1${label}` // `1` comes after the placeholder which has order `0`
|
||||
};
|
||||
this.commandRegistry.registerCommand(command, handler);
|
||||
this.toDisposeBeforeMenuRebuild.push(Disposable.create(() => this.commandRegistry.unregisterCommand(command)));
|
||||
this.menuModelRegistry.registerMenuAction(menuPath, menuAction);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const { serial, network, unknown } = AvailablePorts.groupByProtocol(availablePorts);
|
||||
registerPorts(serial);
|
||||
registerPorts(network);
|
||||
registerPorts(unknown);
|
||||
|
||||
this.mainMenuManager.update();
|
||||
}
|
||||
|
||||
|
@ -3,12 +3,13 @@ import { inject, injectable, postConstruct } from 'inversify';
|
||||
import { MenuPath, CompositeMenuNode } from '@theia/core/lib/common/menu';
|
||||
import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||
import { OpenSketch } from './open-sketch';
|
||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||
import { ArduinoMenus, PlaceholderMenuNode } from '../menu/arduino-menus';
|
||||
import { MainMenuManager } from '../../common/main-menu-manager';
|
||||
import { BoardsServiceProvider } from '../boards/boards-service-provider';
|
||||
import { ExamplesService, ExampleContainer } from '../../common/protocol/examples-service';
|
||||
import { SketchContribution, CommandRegistry, MenuModelRegistry } from './contribution';
|
||||
import { NotificationCenter } from '../notification-center';
|
||||
import { Board } from '../../common/protocol';
|
||||
|
||||
@injectable()
|
||||
export abstract class Examples extends SketchContribution {
|
||||
@ -32,10 +33,10 @@ export abstract class Examples extends SketchContribution {
|
||||
|
||||
@postConstruct()
|
||||
init(): void {
|
||||
this.boardsServiceClient.onBoardsConfigChanged(({ selectedBoard }) => this.handleBoardChanged(selectedBoard?.fqbn));
|
||||
this.boardsServiceClient.onBoardsConfigChanged(({ selectedBoard }) => this.handleBoardChanged(selectedBoard));
|
||||
}
|
||||
|
||||
protected handleBoardChanged(fqbn: string | undefined): void {
|
||||
protected handleBoardChanged(board: Board | undefined): void {
|
||||
// NOOP
|
||||
}
|
||||
|
||||
@ -58,27 +59,33 @@ export abstract class Examples extends SketchContribution {
|
||||
}
|
||||
|
||||
registerRecursively(
|
||||
exampleContainer: ExampleContainer,
|
||||
exampleContainerOrPlaceholder: ExampleContainer | string,
|
||||
menuPath: MenuPath,
|
||||
pushToDispose: DisposableCollection = new DisposableCollection()): void {
|
||||
|
||||
const { label, sketches, children } = exampleContainer;
|
||||
const submenuPath = [...menuPath, label];
|
||||
this.menuRegistry.registerSubmenu(submenuPath, label);
|
||||
children.forEach(child => this.registerRecursively(child, submenuPath, pushToDispose));
|
||||
for (const sketch of sketches) {
|
||||
const { uri } = sketch;
|
||||
const commandId = `arduino-open-example-${submenuPath.join(':')}--${uri}`;
|
||||
const command = { id: commandId };
|
||||
const handler = {
|
||||
execute: async () => {
|
||||
const sketch = await this.sketchService.cloneExample(uri);
|
||||
this.commandService.executeCommand(OpenSketch.Commands.OPEN_SKETCH.id, sketch);
|
||||
}
|
||||
};
|
||||
pushToDispose.push(this.commandRegistry.registerCommand(command, handler));
|
||||
this.menuRegistry.registerMenuAction(submenuPath, { commandId, label: sketch.name });
|
||||
pushToDispose.push(Disposable.create(() => this.menuRegistry.unregisterMenuAction(command)));
|
||||
if (typeof exampleContainerOrPlaceholder === 'string') {
|
||||
const placeholder = new PlaceholderMenuNode(menuPath, exampleContainerOrPlaceholder);
|
||||
this.menuRegistry.registerMenuNode(menuPath, placeholder);
|
||||
pushToDispose.push(Disposable.create(() => this.menuRegistry.unregisterMenuNode(placeholder.id)));
|
||||
} else {
|
||||
const { label, sketches, children } = exampleContainerOrPlaceholder;
|
||||
const submenuPath = [...menuPath, label];
|
||||
this.menuRegistry.registerSubmenu(submenuPath, label);
|
||||
children.forEach(child => this.registerRecursively(child, submenuPath, pushToDispose));
|
||||
for (const sketch of sketches) {
|
||||
const { uri } = sketch;
|
||||
const commandId = `arduino-open-example-${submenuPath.join(':')}--${uri}`;
|
||||
const command = { id: commandId };
|
||||
const handler = {
|
||||
execute: async () => {
|
||||
const sketch = await this.sketchService.cloneExample(uri);
|
||||
this.commandService.executeCommand(OpenSketch.Commands.OPEN_SKETCH.id, sketch);
|
||||
}
|
||||
};
|
||||
pushToDispose.push(this.commandRegistry.registerCommand(command, handler));
|
||||
this.menuRegistry.registerMenuAction(submenuPath, { commandId, label: sketch.name });
|
||||
pushToDispose.push(Disposable.create(() => this.menuRegistry.unregisterMenuAction(command)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -101,10 +108,12 @@ export class BuiltInExamples extends Examples {
|
||||
return;
|
||||
}
|
||||
this.toDispose.dispose();
|
||||
for (const container of exampleContainers) {
|
||||
for (const container of ['Built-in examples', ...exampleContainers]) {
|
||||
this.registerRecursively(container, ArduinoMenus.EXAMPLES__BUILT_IN_GROUP, this.toDispose);
|
||||
}
|
||||
this.menuManager.update();
|
||||
// TODO: remove
|
||||
console.log(typeof this.menuRegistry);
|
||||
}
|
||||
|
||||
}
|
||||
@ -123,17 +132,27 @@ export class LibraryExamples extends Examples {
|
||||
this.notificationCenter.onLibraryUninstalled(() => this.register());
|
||||
}
|
||||
|
||||
protected handleBoardChanged(fqbn: string | undefined): void {
|
||||
this.register(fqbn);
|
||||
protected handleBoardChanged(board: Board | undefined): void {
|
||||
this.register(board);
|
||||
}
|
||||
|
||||
protected async register(fqbn: string | undefined = this.boardsServiceClient.boardsConfig.selectedBoard?.fqbn) {
|
||||
protected async register(board: Board | undefined = this.boardsServiceClient.boardsConfig.selectedBoard) {
|
||||
return this.queue.add(async () => {
|
||||
this.toDispose.dispose();
|
||||
if (!fqbn) {
|
||||
if (!board || !board.fqbn) {
|
||||
return;
|
||||
}
|
||||
const { fqbn, name } = board;
|
||||
const { user, current, any } = await this.examplesService.installed({ fqbn });
|
||||
if (user.length) {
|
||||
(user as any).unshift('Examples from Custom Libraries');
|
||||
}
|
||||
if (current.length) {
|
||||
(current as any).unshift(`Examples for ${name}`);
|
||||
}
|
||||
if (any.length) {
|
||||
(any as any).unshift('Examples for any board');
|
||||
}
|
||||
for (const container of user) {
|
||||
this.registerRecursively(container, ArduinoMenus.EXAMPLES__USER_LIBS_GROUP, this.toDispose);
|
||||
}
|
||||
|
@ -5,8 +5,8 @@ import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor';
|
||||
import { EditorManager } from '@theia/editor/lib/browser';
|
||||
import { MenuModelRegistry, MenuPath } from '@theia/core/lib/common/menu';
|
||||
import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||
import { LibraryPackage, LibraryLocation, LibraryService } from '../../common/protocol';
|
||||
import { ArduinoMenus, PlaceholderMenuNode } from '../menu/arduino-menus';
|
||||
import { LibraryPackage, LibraryService } from '../../common/protocol';
|
||||
import { MainMenuManager } from '../../common/main-menu-manager';
|
||||
import { LibraryListWidget } from '../library/library-list-widget';
|
||||
import { BoardsServiceProvider } from '../boards/boards-service-provider';
|
||||
@ -84,19 +84,35 @@ export class IncludeLibrary extends SketchContribution {
|
||||
// `Arduino libraries`
|
||||
const packageMenuPath = [...includeLibMenuPath, '2_arduino'];
|
||||
const userMenuPath = [...includeLibMenuPath, '3_contributed'];
|
||||
for (const library of libraries) {
|
||||
this.toDispose.push(this.registerLibrary(library, library.location === LibraryLocation.USER ? userMenuPath : packageMenuPath));
|
||||
const { user, rest } = LibraryPackage.groupByLocation(libraries);
|
||||
if (rest.length) {
|
||||
(rest as any).unshift('Arduino libraries');
|
||||
}
|
||||
if (user.length) {
|
||||
(user as any).unshift('Contributed libraries');
|
||||
}
|
||||
|
||||
for (const library of user) {
|
||||
this.toDispose.push(this.registerLibrary(library, userMenuPath));
|
||||
}
|
||||
for (const library of rest) {
|
||||
this.toDispose.push(this.registerLibrary(library, packageMenuPath));
|
||||
}
|
||||
|
||||
this.mainMenuManager.update();
|
||||
});
|
||||
}
|
||||
|
||||
protected registerLibrary(library: LibraryPackage, menuPath: MenuPath): Disposable {
|
||||
const commandId = `arduino-include-library--${library.name}:${library.author}`;
|
||||
protected registerLibrary(libraryOrPlaceholder: LibraryPackage | string, menuPath: MenuPath): Disposable {
|
||||
if (typeof libraryOrPlaceholder === 'string') {
|
||||
const placeholder = new PlaceholderMenuNode(menuPath, libraryOrPlaceholder);
|
||||
this.menuRegistry.registerMenuNode(menuPath, placeholder);
|
||||
return Disposable.create(() => this.menuRegistry.unregisterMenuNode(placeholder.id));
|
||||
}
|
||||
const commandId = `arduino-include-library--${libraryOrPlaceholder.name}:${libraryOrPlaceholder.author}`;
|
||||
const command = { id: commandId };
|
||||
const handler = { execute: () => this.commandRegistry.executeCommand(IncludeLibrary.Commands.INCLUDE_LIBRARY.id, library) };
|
||||
const menuAction = { commandId, label: library.name };
|
||||
const handler = { execute: () => this.commandRegistry.executeCommand(IncludeLibrary.Commands.INCLUDE_LIBRARY.id, libraryOrPlaceholder) };
|
||||
const menuAction = { commandId, label: libraryOrPlaceholder.name };
|
||||
this.menuRegistry.registerMenuAction(menuPath, menuAction);
|
||||
return new DisposableCollection(
|
||||
this.commandRegistry.registerCommand(command, handler),
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { isOSX } from '@theia/core/lib/common/os';
|
||||
import { CommonMenus } from '@theia/core/lib/browser/common-frontend-contribution';
|
||||
import { MAIN_MENU_BAR, MenuModelRegistry, MenuNode } from '@theia/core/lib/common/menu';
|
||||
import { MAIN_MENU_BAR, MenuModelRegistry, MenuNode, MenuPath, SubMenuOptions } from '@theia/core/lib/common/menu';
|
||||
|
||||
export namespace ArduinoMenus {
|
||||
|
||||
@ -99,3 +99,24 @@ export function unregisterSubmenu(menuPath: string[], menuRegistry: MenuModelReg
|
||||
}
|
||||
(parent.children as Array<MenuNode>).splice(index, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Special menu node that is not backed by any commands and is always disabled.
|
||||
*/
|
||||
export class PlaceholderMenuNode implements MenuNode {
|
||||
|
||||
constructor(protected readonly menuPath: MenuPath, readonly label: string, protected options: SubMenuOptions = { order: '0' }) { }
|
||||
|
||||
get icon(): string | undefined {
|
||||
return this.options?.iconClass;
|
||||
}
|
||||
|
||||
get sortString(): string {
|
||||
return this.options?.order || this.label;
|
||||
}
|
||||
|
||||
get id(): string {
|
||||
return [...this.menuPath, 'placeholder'].join('-');
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -5,6 +5,25 @@ import { Installable } from './installable';
|
||||
import { ArduinoComponent } from './arduino-component';
|
||||
|
||||
export type AvailablePorts = Record<string, [Port, Array<Board>]>;
|
||||
export namespace AvailablePorts {
|
||||
export function groupByProtocol(availablePorts: AvailablePorts): { serial: AvailablePorts, network: AvailablePorts, unknown: AvailablePorts } {
|
||||
const serial: AvailablePorts = {};
|
||||
const network: AvailablePorts = {};
|
||||
const unknown: AvailablePorts = {};
|
||||
for (const key of Object.keys(availablePorts)) {
|
||||
const [port, boards] = availablePorts[key];
|
||||
const { protocol } = port;
|
||||
if (protocol === 'serial') {
|
||||
serial[key] = [port, boards];
|
||||
} else if (protocol === 'network') {
|
||||
network[key] = [port, boards];
|
||||
} else {
|
||||
unknown[key] = [port, boards];
|
||||
}
|
||||
}
|
||||
return { serial, network, unknown };
|
||||
}
|
||||
}
|
||||
|
||||
export interface AttachedBoardsChangeEvent {
|
||||
readonly oldState: Readonly<{ boards: Board[], ports: Port[] }>;
|
||||
|
@ -69,4 +69,17 @@ export namespace LibraryPackage {
|
||||
return left.name === right.name && left.author === right.author;
|
||||
}
|
||||
|
||||
export function groupByLocation(packages: LibraryPackage[]): { user: LibraryPackage[], rest: LibraryPackage[] } {
|
||||
const user: LibraryPackage[] = [];
|
||||
const rest: LibraryPackage[] = [];
|
||||
for (const pkg of packages) {
|
||||
if (pkg.location === LibraryLocation.USER) {
|
||||
user.push(pkg);
|
||||
} else {
|
||||
rest.push(pkg);
|
||||
}
|
||||
}
|
||||
return { user, rest };
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,8 +1,9 @@
|
||||
import { injectable } from 'inversify'
|
||||
import { remote } from 'electron';
|
||||
import { Keybinding } from '@theia/core/lib/common/keybinding';
|
||||
import { ElectronMainMenuFactory as TheiaElectronMainMenuFactory } from '@theia/core/lib/electron-browser/menu/electron-main-menu-factory';
|
||||
import { ArduinoMenus } from '../../../browser/menu/arduino-menus';
|
||||
import { CompositeMenuNode } from '@theia/core/lib/common/menu';
|
||||
import { ElectronMainMenuFactory as TheiaElectronMainMenuFactory, ElectronMenuOptions } from '@theia/core/lib/electron-browser/menu/electron-main-menu-factory';
|
||||
import { ArduinoMenus, PlaceholderMenuNode } from '../../../browser/menu/arduino-menus';
|
||||
|
||||
@injectable()
|
||||
export class ElectronMainMenuFactory extends TheiaElectronMainMenuFactory {
|
||||
@ -42,4 +43,15 @@ export class ElectronMainMenuFactory extends TheiaElectronMainMenuFactory {
|
||||
return { label, submenu };
|
||||
}
|
||||
|
||||
protected handleDefault(menuNode: CompositeMenuNode, args: any[] = [], options?: ElectronMenuOptions): Electron.MenuItemConstructorOptions[] {
|
||||
if (menuNode instanceof PlaceholderMenuNode) {
|
||||
return [{
|
||||
label: menuNode.label,
|
||||
enabled: false,
|
||||
visible: true
|
||||
}];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user