diff --git a/examples/api-samples/src/browser/api-samples-frontend-module.ts b/examples/api-samples/src/browser/api-samples-frontend-module.ts index 2fac6d83ffaeb..1a397ab0a4852 100644 --- a/examples/api-samples/src/browser/api-samples-frontend-module.ts +++ b/examples/api-samples/src/browser/api-samples-frontend-module.ts @@ -18,9 +18,11 @@ import { ContainerModule } from 'inversify'; import { bindDynamicLabelProvider } from './label/sample-dynamic-label-provider-command-contribution'; import { bindSampleUnclosableView } from './view/sample-unclosable-view-contribution'; import { bindSampleOutputChannelWithSeverity } from './output/sample-output-channel-with-severity'; +import { bindSampleMenu } from './menu/sample-menu-contribution'; export default new ContainerModule(bind => { bindDynamicLabelProvider(bind); bindSampleUnclosableView(bind); bindSampleOutputChannelWithSeverity(bind); + bindSampleMenu(bind); }); diff --git a/examples/api-samples/src/browser/menu/sample-menu-contribution.ts b/examples/api-samples/src/browser/menu/sample-menu-contribution.ts new file mode 100644 index 0000000000000..ec1e4ac111151 --- /dev/null +++ b/examples/api-samples/src/browser/menu/sample-menu-contribution.ts @@ -0,0 +1,53 @@ +/******************************************************************************** + * Copyright (C) 2020 TORO Limited and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { Command, CommandContribution, CommandRegistry, MAIN_MENU_BAR, MenuContribution, MenuModelRegistry } from '@theia/core/lib/common'; +import { injectable, interfaces } from 'inversify'; + +const SampleCommand: Command = { + id: 'sample-command', + label: 'Sample Command' +}; + +@injectable() +export class SampleCommandContribution implements CommandContribution { + registerCommands(commands: CommandRegistry): void { + commands.registerCommand(SampleCommand, { + execute: () => { + alert('This is a sample command!'); + } + }); + } + +} + +@injectable() +export class SampleMenuContribution implements MenuContribution { + registerMenus(menus: MenuModelRegistry): void { + const subMenuPath = [...MAIN_MENU_BAR, 'sample-menu']; + menus.registerSubmenu(subMenuPath, 'Sample Menu', { + order: '2' // that should put the menu right next to the File menu + }); + menus.registerMenuAction(subMenuPath, { + commandId: SampleCommand.id + }); + } +} + +export const bindSampleMenu = (bind: interfaces.Bind) => { + bind(CommandContribution).to(SampleCommandContribution).inSingletonScope(); + bind(MenuContribution).to(SampleMenuContribution).inSingletonScope(); +}; diff --git a/packages/core/src/common/menu.ts b/packages/core/src/common/menu.ts index d3250ba7ee2c2..35ae10b0d20fc 100644 --- a/packages/core/src/common/menu.ts +++ b/packages/core/src/common/menu.ts @@ -41,7 +41,8 @@ export namespace MenuAction { } export interface SubMenuOptions { - iconClass: string + iconClass?: string + order?: string } export type MenuPath = string[]; @@ -93,7 +94,7 @@ export class MenuModelRegistry { const parent = this.findGroup(groupPath); let groupNode = this.findSubMenu(parent, menuId); if (!groupNode) { - groupNode = new CompositeMenuNode(menuId, label, options ? options.iconClass : undefined); + groupNode = new CompositeMenuNode(menuId, label, options); return parent.addNode(groupNode); } else { if (!groupNode.label) { @@ -101,8 +102,13 @@ export class MenuModelRegistry { } else if (groupNode.label !== label) { throw new Error("The group '" + menuPath.join('/') + "' already has a different label."); } - if (!groupNode.iconClass && options) { - groupNode.iconClass = options.iconClass; + if (options) { + if (!groupNode.iconClass) { + groupNode.iconClass = options.iconClass; + } + if (!groupNode.order) { + groupNode.order = options.order; + } } return { dispose: () => { } }; } @@ -187,11 +193,19 @@ export interface MenuNode { export class CompositeMenuNode implements MenuNode { protected readonly _children: MenuNode[] = []; + public iconClass?: string; + public order?: string; + constructor( public readonly id: string, public label?: string, - public iconClass?: string - ) { } + options?: SubMenuOptions + ) { + if (options) { + this.iconClass = options.iconClass; + this.order = options.order; + } + } get children(): ReadonlyArray { return this._children; @@ -236,7 +250,7 @@ export class CompositeMenuNode implements MenuNode { } get sortString(): string { - return this.id; + return this.order || this.id; } get isSubmenu(): boolean {