Skip to content

Commit

Permalink
Refactor menu nodes
Browse files Browse the repository at this point in the history
Fixes eclipse-theia#14217

Makes menu nodes active object that can decide on visibility,
enablement, etc. themselves.

Contributed on behalf of STMicroelectronics

Signed-off-by: Thomas Mäder <[email protected]>
  • Loading branch information
tsmaeder committed Jan 6, 2025
1 parent 202ab63 commit b0e5fee
Show file tree
Hide file tree
Showing 75 changed files with 1,954 additions and 2,281 deletions.
6 changes: 1 addition & 5 deletions examples/api-samples/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,6 @@
"frontend": "lib/browser/api-samples-frontend-module",
"backend": "lib/node/api-samples-backend-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 Expand Up @@ -62,4 +58,4 @@
"devDependencies": {
"@theia/ext-scripts": "1.57.0"
}
}
}

This file was deleted.

39 changes: 26 additions & 13 deletions examples/api-samples/src/browser/menu/sample-menu-contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ import { ConfirmDialog, Dialog, QuickInputService } from '@theia/core/lib/browse
import { ReactDialog } from '@theia/core/lib/browser/dialogs/react-dialog';
import { SelectComponent } from '@theia/core/lib/browser/widgets/select-component';
import {
Command, CommandContribution, CommandRegistry, MAIN_MENU_BAR,
MenuContribution, MenuModelRegistry, MenuNode, MessageService, SubMenuOptions
Command, CommandContribution, CommandMenu, CommandRegistry, ContextExpressionMatcher, MAIN_MENU_BAR,
MenuContribution, MenuModelRegistry, MenuPath, MessageService
} from '@theia/core/lib/common';
import { inject, injectable, interfaces } from '@theia/core/shared/inversify';
import * as React from '@theia/core/shared/react';
Expand Down Expand Up @@ -226,9 +226,8 @@ export class SampleCommandContribution implements CommandContribution {
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.registerSubmenu(subMenuPath, 'Sample Menu', '2'); // that should put the menu right next to the File menu

menus.registerMenuAction(subMenuPath, {
commandId: SampleCommand.id,
order: '0'
Expand All @@ -238,7 +237,7 @@ export class SampleMenuContribution implements MenuContribution {
order: '2'
});
const subSubMenuPath = [...subMenuPath, 'sample-sub-menu'];
menus.registerSubmenu(subSubMenuPath, 'Sample sub menu', { order: '2' });
menus.registerSubmenu(subSubMenuPath, 'Sample sub menu', '2');
menus.registerMenuAction(subSubMenuPath, {
commandId: SampleCommand.id,
order: '1'
Expand All @@ -247,8 +246,8 @@ export class SampleMenuContribution implements MenuContribution {
commandId: SampleCommand2.id,
order: '3'
});
const placeholder = new PlaceholderMenuNode([...subSubMenuPath, 'placeholder'].join('-'), 'Placeholder', { order: '0' });
menus.registerMenuNode(subSubMenuPath, placeholder);
const placeholder = new PlaceholderMenuNode([...subSubMenuPath, 'placeholder'].join('-'), 'Placeholder', '0');
menus.registerCommandMenu(subSubMenuPath, placeholder);

/**
* Register an action menu with an invalid command (un-registered and without a label) in order
Expand All @@ -262,16 +261,30 @@ export class SampleMenuContribution implements MenuContribution {
/**
* Special menu node that is not backed by any commands and is always disabled.
*/
export class PlaceholderMenuNode implements MenuNode {
export class PlaceholderMenuNode implements CommandMenu {

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

isEnabled(effectiveMenuPath: MenuPath, ...args: any[]): boolean {
return false;
}

get icon(): string | undefined {
return this.options?.iconClass;
isToggled(effectiveMenuPath: MenuPath): boolean {
return false;
}
run(effectiveMenuPath: MenuPath, ...args: any[]): Promise<void> {
throw new Error('Should never happen');
}
getAccelerator(context: HTMLElement | undefined): string[] {
return [];
}

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

isVisible<T>(effectiveMenuPath: MenuPath, contextMatcher: ContextExpressionMatcher<T>, context: T | undefined, ...args: any[]): boolean {
return true;
}

}
Expand Down

This file was deleted.

28 changes: 14 additions & 14 deletions packages/core/src/browser/common-frontend-contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

import debounce = require('lodash.debounce');
import { injectable, inject, optional } from 'inversify';
import { MAIN_MENU_BAR, MANAGE_MENU, MenuContribution, MenuModelRegistry, ACCOUNTS_MENU } from '../common/menu';
import { MAIN_MENU_BAR, MANAGE_MENU, MenuContribution, MenuModelRegistry, ACCOUNTS_MENU, CompoundMenuNode, CommandMenu, Group, Submenu } from '../common/menu';
import { KeybindingContribution, KeybindingRegistry } from './keybinding';
import { FrontendApplication } from './frontend-application';
import { FrontendApplicationContribution, OnWillStopAction } from './frontend-application-contribution';
Expand Down Expand Up @@ -83,7 +83,7 @@ export namespace CommonMenus {
export const FILE_SETTINGS_SUBMENU_THEME = [...FILE_SETTINGS_SUBMENU, '2_settings_submenu_theme'];
export const FILE_CLOSE = [...FILE, '6_close'];

export const FILE_NEW_CONTRIBUTIONS = 'file/newFile';
export const FILE_NEW_CONTRIBUTIONS = ['file', 'newFile'];

export const EDIT = [...MAIN_MENU_BAR, '2_edit'];
export const EDIT_UNDO = [...EDIT, '1_undo'];
Expand Down Expand Up @@ -606,7 +606,7 @@ export class CommonFrontendContribution implements FrontendApplicationContributi
registry.registerSubmenu(CommonMenus.HELP, nls.localizeByDefault('Help'));

// For plugins contributing create new file commands/menu-actions
registry.registerIndependentSubmenu(CommonMenus.FILE_NEW_CONTRIBUTIONS, nls.localizeByDefault('New File...'));
registry.registerSubmenu(CommonMenus.FILE_NEW_CONTRIBUTIONS, nls.localizeByDefault('New File...'));

registry.registerMenuAction(CommonMenus.FILE_SAVE, {
commandId: CommonCommands.SAVE.id
Expand Down Expand Up @@ -747,7 +747,7 @@ export class CommonFrontendContribution implements FrontendApplicationContributi
commandId: CommonCommands.SELECT_ICON_THEME.id
});

registry.registerSubmenu(CommonMenus.MANAGE_SETTINGS_THEMES, nls.localizeByDefault('Themes'), { order: 'a50' });
registry.registerSubmenu(CommonMenus.MANAGE_SETTINGS_THEMES, nls.localizeByDefault('Themes'), 'a50');
registry.registerMenuAction(CommonMenus.MANAGE_SETTINGS_THEMES, {
commandId: CommonCommands.SELECT_COLOR_THEME.id,
order: '0'
Expand Down Expand Up @@ -1458,7 +1458,7 @@ export class CommonFrontendContribution implements FrontendApplicationContributi
* @todo https://github.com/eclipse-theia/theia/issues/12824
*/
protected async showNewFilePicker(): Promise<void> {
const newFileContributions = this.menuRegistry.getMenuNode(CommonMenus.FILE_NEW_CONTRIBUTIONS); // Add menus
const newFileContributions = this.menuRegistry.getMenuNode(CommonMenus.FILE_NEW_CONTRIBUTIONS) as Submenu; // Add menus
const items: QuickPickItemOrSeparator[] = [
{
label: nls.localizeByDefault('New Text File'),
Expand All @@ -1467,22 +1467,22 @@ export class CommonFrontendContribution implements FrontendApplicationContributi
},
...newFileContributions.children
.flatMap(node => {
if (node.children && node.children.length > 0) {
if (CompoundMenuNode.is(node) && node.children.length > 0) {
return node.children;
}
return node;
})
.filter(node => node.role || node.command)
.filter(node => Group.is(node) || CommandMenu.is(node))
.map(node => {
if (node.role) {
if (Group.is(node)) {
return { type: 'separator' } as QuickPickSeparator;
} else {
const item = node as CommandMenu;
return {
label: item.label,
execute: () => item.run(CommonMenus.FILE_NEW_CONTRIBUTIONS)
};
}
const command = this.commandRegistry.getCommand(node.command!);
return {
label: command!.label!,
execute: async () => this.commandRegistry.executeCommand(command!.id!)
};

})
];

Expand Down
30 changes: 25 additions & 5 deletions packages/core/src/browser/context-menu-renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@

/* eslint-disable @typescript-eslint/no-explicit-any */

import { injectable } from 'inversify';
import { MenuPath } from '../common/menu';
import { injectable, inject } from 'inversify';
import { CompoundMenuNode, MenuModelRegistry, MenuPath } from '../common/menu';
import { Disposable, DisposableCollection } from '../common/disposable';
import { ContextMatcher } from './context-key-service';
import { ContextKeyService, ContextMatcher } from './context-key-service';

export interface Coordinate { x: number; y: number; }
export const Coordinate = Symbol('Coordinate');
Expand Down Expand Up @@ -53,6 +53,10 @@ export abstract class ContextMenuAccess implements Disposable {
@injectable()
export abstract class ContextMenuRenderer {

@inject(MenuModelRegistry) menuRegistry: MenuModelRegistry;
@inject(ContextKeyService)
protected readonly contextKeyService: ContextKeyService;

protected _current: ContextMenuAccess | undefined;
protected readonly toDisposeOnSetCurrent = new DisposableCollection();
/**
Expand All @@ -77,13 +81,28 @@ export abstract class ContextMenuRenderer {
}

render(options: RenderContextMenuOptions): ContextMenuAccess {
let menu = CompoundMenuNode.is(options.menu) ? options.menu : this.menuRegistry.getMenu(options.menuPath);

const resolvedOptions = this.resolve(options);
const access = this.doRender(resolvedOptions);

if (resolvedOptions.skipSingleRootNode) {
menu = MenuModelRegistry.removeSingleRootNode(menu);
}

const access = this.doRender(options.menuPath, menu, resolvedOptions.anchor, options.contextKeyService || this.contextKeyService, resolvedOptions.args, resolvedOptions.context, resolvedOptions.onHide);
this.setCurrent(access);
return access;
}

protected abstract doRender(options: RenderContextMenuOptions): ContextMenuAccess;
protected abstract doRender(
menuPath: MenuPath,
menu: CompoundMenuNode,
anchor: Anchor,
contextMatcher: ContextMatcher,
args?: any[],
context?: HTMLElement,
onHide?: () => void
): ContextMenuAccess;

protected resolve(options: RenderContextMenuOptions): RenderContextMenuOptions {
const args: any[] = options.args ? options.args.slice() : [];
Expand All @@ -99,6 +118,7 @@ export abstract class ContextMenuRenderer {
}

export interface RenderContextMenuOptions {
menu?: CompoundMenuNode,
menuPath: MenuPath;
anchor: Anchor;
args?: any[];
Expand Down
6 changes: 0 additions & 6 deletions packages/core/src/browser/frontend-application-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,6 @@ import {
messageServicePath,
InMemoryTextResourceResolver,
UntitledResourceResolver,
MenuCommandAdapterRegistry,
MenuCommandExecutor,
MenuCommandAdapterRegistryImpl,
MenuCommandExecutorImpl,
MenuPath
} from '../common';
import { KeybindingRegistry, KeybindingContext, KeybindingContribution } from './keybinding';
Expand Down Expand Up @@ -271,8 +267,6 @@ export const frontendApplicationModule = new ContainerModule((bind, _unbind, _is

bind(MenuModelRegistry).toSelf().inSingletonScope();
bindContributionProvider(bind, MenuContribution);
bind(MenuCommandAdapterRegistry).to(MenuCommandAdapterRegistryImpl).inSingletonScope();
bind(MenuCommandExecutor).to(MenuCommandExecutorImpl).inSingletonScope();

bind(KeyboardLayoutService).toSelf().inSingletonScope();
bind(KeybindingRegistry).toSelf().inSingletonScope();
Expand Down
Loading

0 comments on commit b0e5fee

Please sign in to comment.