diff --git a/packages/core/src/browser/context-menu-renderer.ts b/packages/core/src/browser/context-menu-renderer.ts index d0caac8e1a5bd..e8f88586f7c35 100644 --- a/packages/core/src/browser/context-menu-renderer.ts +++ b/packages/core/src/browser/context-menu-renderer.ts @@ -120,4 +120,9 @@ export interface RenderContextMenuOptions { context?: HTMLElement; contextKeyService?: ContextMatcher; onHide?: () => void; + /** + * If true a single submenu in the context menu is not rendered but its children are rendered on the top level. + * Default is `false`. + */ + skipSingleRootNode?: boolean; } diff --git a/packages/core/src/browser/menu/browser-context-menu-renderer.ts b/packages/core/src/browser/menu/browser-context-menu-renderer.ts index 63f3a35af36bc..775efaec0e28f 100644 --- a/packages/core/src/browser/menu/browser-context-menu-renderer.ts +++ b/packages/core/src/browser/menu/browser-context-menu-renderer.ts @@ -34,8 +34,8 @@ export class BrowserContextMenuRenderer extends ContextMenuRenderer { super(); } - protected doRender({ menuPath, anchor, args, onHide, context, contextKeyService }: RenderContextMenuOptions): ContextMenuAccess { - const contextMenu = this.menuFactory.createContextMenu(menuPath, args, context, contextKeyService); + protected doRender({ menuPath, anchor, args, onHide, context, contextKeyService, skipSingleRootNode }: RenderContextMenuOptions): ContextMenuAccess { + const contextMenu = this.menuFactory.createContextMenu(menuPath, args, context, contextKeyService, skipSingleRootNode); const { x, y } = coordinateFromAnchor(anchor); if (onHide) { contextMenu.aboutToClose.connect(() => onHide!()); diff --git a/packages/core/src/browser/menu/browser-menu-plugin.ts b/packages/core/src/browser/menu/browser-menu-plugin.ts index 5af54f00fa3ca..81b85487c9bec 100644 --- a/packages/core/src/browser/menu/browser-menu-plugin.ts +++ b/packages/core/src/browser/menu/browser-menu-plugin.ts @@ -108,8 +108,8 @@ export class BrowserMainMenuFactory implements MenuWidgetFactory { } } - createContextMenu(path: MenuPath, args?: unknown[], context?: HTMLElement, contextKeyService?: ContextMatcher): MenuWidget { - const menuModel = this.menuProvider.getMenu(path); + createContextMenu(path: MenuPath, args?: unknown[], context?: HTMLElement, contextKeyService?: ContextMatcher, skipSingleRootNode?: boolean): MenuWidget { + const menuModel = skipSingleRootNode ? this.menuProvider.removeSingleRootNode(this.menuProvider.getMenu(path), path) : this.menuProvider.getMenu(path); const menuCommandRegistry = this.createMenuCommandRegistry(menuModel, args).snapshot(path); const contextMenu = this.createMenuWidget(menuModel, { commands: menuCommandRegistry, context, rootMenuPath: path, contextKeyService }); return contextMenu; diff --git a/packages/core/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar.tsx b/packages/core/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar.tsx index dac2d86a66425..f06b1b82d833f 100644 --- a/packages/core/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar.tsx +++ b/packages/core/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar.tsx @@ -281,7 +281,8 @@ export class TabBarToolbar extends ReactWidget { args: [this.current], anchor, context: this.current?.node, - onHide: () => toDisposeOnHide.dispose() + onHide: () => toDisposeOnHide.dispose(), + skipSingleRootNode: true, }); } diff --git a/packages/core/src/common/menu/menu-model-registry.ts b/packages/core/src/common/menu/menu-model-registry.ts index 6bbb5e1d3a450..52005064d0db8 100644 --- a/packages/core/src/common/menu/menu-model-registry.ts +++ b/packages/core/src/common/menu/menu-model-registry.ts @@ -254,6 +254,42 @@ export class MenuModelRegistry { return this.findGroup(menuPath); } + /** + * Checks the given menu model whether it will show a menu with a single submenu. + * + * @param fullMenuModel the menu model to analyze + * @param menuPath the menu's path + * @returns if the menu will show a single submenu this returns a menu that will show the child elements of the submenu, + * otherwise the given `fullMenuModel` is return + */ + removeSingleRootNode(fullMenuModel: MutableCompoundMenuNode, menuPath: MenuPath): CompoundMenuNode { + let menuModel: MenuNode = fullMenuModel; + + // first find the menu node(s) under our menu path + const idToSkip = menuPath.length > 0 ? menuPath[menuPath.length - 1] : undefined; + if (idToSkip) { + menuModel = fullMenuModel; + while (menuModel.id === idToSkip) { + if (CompoundMenuNode.is(menuModel) && menuModel.children.length === 1) { + menuModel = menuModel.children[0]; + } else { + break; + } + } + } + + // inspect the node whether it is a single submenu + if (CompoundMenuNode.is(menuModel) && menuModel.children.length === 1) { + // yes -> only render the submenus children + menuModel = menuModel.children[0]; + } else { + // no -> render full menu + menuModel = fullMenuModel; + } + + return CompoundMenuNode.is(menuModel) ? menuModel : fullMenuModel; + } + /** * Returns the {@link MenuPath path} at which a given menu node can be accessed from this registry, if it can be determined. * Returns `undefined` if the `parent` of any node in the chain is unknown. diff --git a/packages/core/src/electron-browser/menu/electron-context-menu-renderer.ts b/packages/core/src/electron-browser/menu/electron-context-menu-renderer.ts index 4509570645d44..8ed8b1cebd1a8 100644 --- a/packages/core/src/electron-browser/menu/electron-context-menu-renderer.ts +++ b/packages/core/src/electron-browser/menu/electron-context-menu-renderer.ts @@ -100,8 +100,8 @@ export class ElectronContextMenuRenderer extends BrowserContextMenuRenderer { protected override doRender(options: RenderContextMenuOptions): ContextMenuAccess { if (this.useNativeStyle) { - const { menuPath, anchor, args, onHide, context, contextKeyService } = options; - const menu = this.electronMenuFactory.createElectronContextMenu(menuPath, args, context, contextKeyService); + const { menuPath, anchor, args, onHide, context, contextKeyService, skipSingleRootNode } = options; + const menu = this.electronMenuFactory.createElectronContextMenu(menuPath, args, context, contextKeyService, skipSingleRootNode); const { x, y } = coordinateFromAnchor(anchor); const menuHandle = window.electronTheiaCore.popup(menu, x, y, () => { diff --git a/packages/core/src/electron-browser/menu/electron-main-menu-factory.ts b/packages/core/src/electron-browser/menu/electron-main-menu-factory.ts index 4ff411d93c48d..162b4dca9235c 100644 --- a/packages/core/src/electron-browser/menu/electron-main-menu-factory.ts +++ b/packages/core/src/electron-browser/menu/electron-main-menu-factory.ts @@ -125,8 +125,8 @@ export class ElectronMainMenuFactory extends BrowserMainMenuFactory { return undefined; } - createElectronContextMenu(menuPath: MenuPath, args?: any[], context?: HTMLElement, contextKeyService?: ContextMatcher): MenuDto[] { - const menuModel = this.menuProvider.getMenu(menuPath); + createElectronContextMenu(menuPath: MenuPath, args?: any[], context?: HTMLElement, contextKeyService?: ContextMatcher, skipSingleRootNode?: boolean): MenuDto[] { + const menuModel = skipSingleRootNode ? this.menuProvider.removeSingleRootNode(this.menuProvider.getMenu(menuPath), menuPath) : this.menuProvider.getMenu(menuPath); return this.fillMenuTemplate([], menuModel, args, { showDisabled: true, context, rootMenuPath: menuPath, contextKeyService }); }