Skip to content

Commit

Permalink
Example of registering a custom menu node.
Browse files Browse the repository at this point in the history
`MenuModelRegistry#registerMenuNode`

Signed-off-by: Akos Kitta <kittaakos@typefox.io>
  • Loading branch information
Akos Kitta committed Aug 23, 2020
1 parent e5e96a0 commit b55b223
Show file tree
Hide file tree
Showing 4 changed files with 166 additions and 4 deletions.
4 changes: 4 additions & 0 deletions examples/api-samples/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
{
"frontend": "lib/browser/api-samples-frontend-module"
},
{
"frontend": "lib/browser/menu/sample-browser-menu-module",
"frontendElectron": "lib/electron-browser/menu/sample-electron-menu-module"
},
{
"electronMain": "lib/electron-main/update/sample-updater-main-module",
"frontendElectron": "lib/electron-browser/updater/sample-updater-frontend-module"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/********************************************************************************
* Copyright (C) 2020 TypeFox 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 { injectable, ContainerModule } from 'inversify';
import { Menu as MenuWidget } from '@phosphor/widgets';
import { Disposable } from '@theia/core/lib/common/disposable';
import { MenuNode, CompositeMenuNode } from '@theia/core/lib/common/menu';
import { BrowserMainMenuFactory, MenuCommandRegistry, DynamicMenuWidget } from '@theia/core/lib/browser/menu/browser-menu-plugin';
import { PlaceholderMenuNode } from './sample-menu-contribution';

export default new ContainerModule((bind, unbind, isBound, rebind) => {
rebind(BrowserMainMenuFactory).to(SampleBrowserMainMenuFactory).inSingletonScope();
});

@injectable()
class SampleBrowserMainMenuFactory extends BrowserMainMenuFactory {

protected handleDefault(menuCommandRegistry: MenuCommandRegistry, menuNode: MenuNode): void {
if (menuNode instanceof PlaceholderMenuNode && menuCommandRegistry instanceof SampleMenuCommandRegistry) {
menuCommandRegistry.registerPlaceholderMenu(menuNode);
}
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
protected createMenuCommandRegistry(menu: CompositeMenuNode, args: any[] = []): MenuCommandRegistry {
const menuCommandRegistry = new SampleMenuCommandRegistry(this.services);
this.registerMenu(menuCommandRegistry, menu, args);
return menuCommandRegistry;
}

createMenuWidget(menu: CompositeMenuNode, options: MenuWidget.IOptions & { commands: MenuCommandRegistry }): DynamicMenuWidget {
return new SampleDynamicMenuWidget(menu, options, this.services);
}

}

class SampleMenuCommandRegistry extends MenuCommandRegistry {

protected placeholders = new Map<string, PlaceholderMenuNode>();

// eslint-disable-next-line @typescript-eslint/no-explicit-any
registerPlaceholderMenu(menu: PlaceholderMenuNode): void {
const { id } = menu;
if (this.placeholders.has(id)) {
return;
}
this.placeholders.set(id, menu);
}

snapshot(): this {
super.snapshot();
for (const menu of this.placeholders.values()) {
this.toDispose.push(this.registerPlaceholder(menu));
}
return this;
}

protected registerPlaceholder(menu: PlaceholderMenuNode): Disposable {
const { id } = menu;
const unregisterCommand = this.addCommand(id, {
execute: () => { /* NOOP */ },
label: menu.label,
icon: menu.icon,
isEnabled: () => false,
isVisible: () => true
});
return Disposable.create(() => unregisterCommand.dispose());
}

}

class SampleDynamicMenuWidget extends DynamicMenuWidget {

protected handleDefault(menuNode: MenuNode): MenuWidget.IItemOptions[] {
if (menuNode instanceof PlaceholderMenuNode) {
return [{
command: menuNode.id,
type: 'command'
}];
}
return [];
}

}
28 changes: 24 additions & 4 deletions examples/api-samples/src/browser/menu/sample-menu-contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* 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 { Command, CommandContribution, CommandRegistry, MAIN_MENU_BAR, MenuContribution, MenuModelRegistry, MenuNode, SubMenuOptions } from '@theia/core/lib/common';
import { injectable, interfaces } from 'inversify';

const SampleCommand: Command = {
Expand Down Expand Up @@ -59,16 +59,36 @@ export class SampleMenuContribution implements MenuContribution {
order: '2'
});
const subSubMenuPath = [...subMenuPath, 'sample-sub-menu'];
menus.registerSubmenu(subSubMenuPath, 'Sample sub menu', { order: '1' });
menus.registerSubmenu(subSubMenuPath, 'Sample sub menu', { order: '2' });
menus.registerMenuAction(subSubMenuPath, {
commandId: SampleCommand.id,
order: '0'
order: '1'
});
menus.registerMenuAction(subSubMenuPath, {
commandId: SampleCommand2.id,
order: '2'
order: '3'
});
const placeholder = new PlaceholderMenuNode([...subSubMenuPath, 'placeholder'].join('-'), 'Placeholder', { order: '0' });
menus.registerMenuNode(subSubMenuPath, placeholder);
}

}

/**
* Special menu node that is not backed by any commands and is always disabled.
*/
export class PlaceholderMenuNode implements MenuNode {

constructor(readonly id: string, public readonly label: string, protected options?: SubMenuOptions) { }

get icon(): string | undefined {
return this.options?.iconClass;
}

get sortString(): string {
return this.options?.order || this.label;
}

}

export const bindSampleMenu = (bind: interfaces.Bind) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/********************************************************************************
* Copyright (C) 2020 TypeFox 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 { injectable, ContainerModule } from 'inversify';
import { CompositeMenuNode } from '@theia/core/lib/common/menu';
import { ElectronMainMenuFactory, ElectronMenuOptions } from '@theia/core/lib/electron-browser/menu/electron-main-menu-factory';
import { PlaceholderMenuNode } from '../../browser/menu/sample-menu-contribution';

export default new ContainerModule((bind, unbind, isBound, rebind) => {
rebind(ElectronMainMenuFactory).to(SampleElectronMainMenuFactory).inSingletonScope();
});

@injectable()
class SampleElectronMainMenuFactory extends ElectronMainMenuFactory {

// eslint-disable-next-line @typescript-eslint/no-explicit-any
protected handleDefault(menuNode: CompositeMenuNode, args: any[] = [], options?: ElectronMenuOptions): Electron.MenuItemConstructorOptions[] {
if (menuNode instanceof PlaceholderMenuNode) {
return [{
label: menuNode.label,
enabled: false,
visible: true
}];
}
return [];
}

}

0 comments on commit b55b223

Please sign in to comment.