自定义菜单

注册自定义菜单

注册自定义菜单,常见的有两种模式:

  • 注册新的菜单项
  • 向已有的菜单项注册子菜单

OpenSumi 提供了自定义菜单能力,基于 OpenSumi 的 Contribution 机制,实现 MenuContribution ,调用 menuRegistry 提供的方法即可。

interface MenuContribution {
  registerMenus?(menus: IMenuRegistry): void;
}

interface IMenuRegistry {
  // 注册新的菜单项
  registerMenubarItem(
    menuId: string,
    item: PartialBy<IMenubarItem, 'id'>
  ): IDisposable;
  // 向已有的菜单项注册子菜单
  registerMenuItem(
    menuId: MenuId | string,
    item: IMenuItem | ISubmenuItem | IInternalComponentMenuItem
  ): IDisposable;
}

注册新的菜单项

例如我们希望注册一个新的 终端 菜单项,并希望它展示在第一项,调用 registry.registerMenuBarItem, 同时传入 order: 0 表示其定位在第一项。

import {
  MenuContribution,
  IMenuRegistry,
  MenuId
} from '@opensumi/ide-core-browser/lib/menu/next';

const terminalMenuBarId = 'menubar/terminal';

@Domain(MenuContribution)
class MyMenusContribution implements MenuContribution {
  registerMenus(registry: IMenuRegistry) {
    registry.registerMenubarItem(terminalMenuBarId, {
      label: '终端',
      order: 0
    });
  }
}

Menu

向已有的菜单项注册子菜单

我们将终端菜单项注册在了菜单栏的第一项,但它还没有子菜单,点击后没有任何反应,我们需要再为其注册一组子菜单。调用 registryregisterMenuItem 可以注册单个菜单项,也可以使用 registerMenuItems 方法来批量注册多个子菜单项。 菜单点击后需要执行某些操作,在这个例子中,我们希望点击后拆分终端,需要为其绑定一个 CommandCommand 也一样可以通过实现 CommandContribution自定义,在这里我们使用内置的 terminal.split 命令。

注意,当绑定的 Command 在注册时也指定了 label 属性时,注册菜单项的 label 默认不会生效

registerMenus(registry: IMenuRegistry) {
  registry.registerMenubarItem(terminalMenuBarId, { label: '终端', order: 0 });

  registry.registerMenuItem(terminalMenuBarId, {
    command: 'terminal.split',
    group: '1_split',
  });
}

Split Terminal

子菜单分组

当注册的菜单较多时,我们可能希望将一些类似操作的子菜单与其他菜单间隔起来,可以使用 group 属性来为子菜单分组。具体来说,就是为这些类似操作的菜单使用相同的 group 值即可。这里我们使用 registry.registerMenuItems 来注册更多子菜单。

registerMenus(registry: IMenuRegistry) {
  registry.registerMenubarItem(terminalMenuBarId, { label: '终端', order: 0 });

  registry.registerMenuItems(terminalMenuBarId, [
    {
      command: 'terminal.split',
      group: '1_split',
    },
    {
      command: 'terminal.remove',
      group: '2_clear',
    },
    {
      command: 'terminal.clear',
      group: '2_clear',
    },
    {
      command: 'terminal.search',
      group: '3_search',
    },
    {
      command: 'terminal.search.next',
      group: '3_search',
    },
  ]);
}

More MenuItems

注册二级子菜单

对于同类型的菜单,除了使用 group 来将它们分组之外,还可以将其注册为二级子菜单,当子菜单较多时,使用二级菜单能有效的改善用户体验。例如我们希望将 搜索 以及 搜索下一个匹配项 都注册为 搜索 的二级菜单。

const searchSubMenuId = 'terminal/search';

registerMenus(registry: IMenuRegistry) {
  registry.registerMenubarItem(terminalMenuBarId, { label: '终端', order: 0 });

  registry.registerMenuItems(terminalMenuBarId, [
    {
      command: 'terminal.split',
      group: '1_split',
    },
    {
      command: 'terminal.remove',
      group: '2_clear',
    },
    {
      command: 'terminal.clear',
      group: '2_clear',
    },
    {
      label: '搜索',
      group: '3_search',
      submenu: searchSubMenuId,
    },
  ]);

  registry.registerMenuItems(searchSubMenuId, [
    {
      command: 'terminal.search',
    },
    {
      command: 'terminal.search.next',
    },
  ]);

}

submenu

反注册菜单或者菜单项

OpenSumi 也提供了反注册菜单或者菜单项的功能,如果你不需要界面上的某些按钮,可以反注册掉它们。

core 内部会预先注册了一些菜单,如:帮助;也为这些菜单预先配置了菜单项,如: 帮助 > 切换开发人员工具。

我们在 IMenuRegistry 中提供了两个方法: unregisterMenuIdunregisterMenuItem; 前者用来删除某个菜单,后者用来删除某个菜单的菜单项:

export abstract class IMenuRegistry {
  ...
  abstract unregisterMenuItem(menuId: MenuId | string, menuItemId: string): void;
  abstract unregisterMenuId(menuId: string): IDisposable;
}

比如我们想删除帮助菜单项,可以使用:

import {
  MenuContribution,
  IMenuRegistry,
  MenuId
} from '@opensumi/ide-core-browser/lib/menu/next';

const terminalMenuBarId = 'menubar/terminal';

@Domain(MenuContribution)
class MyMenusContribution implements MenuContribution {
  registerMenus(registry: IMenuRegistry) {
    registry.unregisterMenuId(MenuId.MenubarHelpMenu);
  }
}

如果我们想删除帮助菜单中的切换开发人员功能,我们需要先查到这个菜单项的 id,一般来说就是该菜单要执行的 command 的 id:

import {
  MenuContribution,
  IMenuRegistry,
  MenuId
} from '@opensumi/ide-core-browser/lib/menu/next';

const terminalMenuBarId = 'menubar/terminal';

@Domain(MenuContribution)
class MyMenusContribution implements MenuContribution {
  registerMenus(registry: IMenuRegistry) {
    registry.unregisterMenuItem(
      MenuId.MenubarHelpMenu,
      'electron.toggleDevTools'
    );
  }
}

core 内部注册的菜单你可以通过关键字 registerMenuItem 搜索到,比如 electron-basic 里注册的菜单项在这儿:packages/electron-basic/src/browser/index.ts#L159

使用图标菜单

除了自定义菜单,你还可以选择使用图标菜单,它是以 icon 图标的形式来显示菜单项。

submenu

使用方式

首先需要通过自定义视图的方式来自定义顶部 toolbar 的渲染器,见 自定义插槽

然后引入 <IconMenuBar /> 组件

import { IconMenuBar } from '@opensumi/ide-menu-bar/lib/browser/menu-bar.view';

/**
 * Custom menu bar component.
 * Add a logo in here, and keep
 * opensumi's original menubar.
 */
export const MenuBarView = () => (
  <div>
    <IconMenuBar />
  </div>
);

然后在 MenuContribution 里调用 menuRegistry 提供的 registerMenuItems 方法。

MenuId.IconMenubarContext 这个 context 上注册菜单项即可

registerMenus(registry: IMenuRegistry) {
  menus.registerMenuItems(MenuId.IconMenubarContext, [
    {
      command: EDITOR_COMMANDS.REDO.id,
      iconClass: getIcon('up'),
      group: '1_icon_menubar',
    },
    {
      command: EDITOR_COMMANDS.UNDO.id,
      iconClass: getIcon('down'),
      group: '2_icon_menubar',
    },
  ])
}

注意: iconClass 为必填,否则无法展示图标

其中 group 字段会自动帮你分组,在不同组之间会以分隔符 | 区分开