/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import 'vs/css!./toolbar'; import * as nls from 'vs/nls'; import { Action, IActionRunner, IAction, IActionViewItemProvider, SubmenuAction } from 'vs/base/common/actions'; import { ActionBar, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; import { ResolvedKeybinding } from 'vs/base/common/keyCodes'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; import { withNullAsUndefined } from 'vs/base/common/types'; import { Codicon, registerIcon } from 'vs/base/common/codicons'; import { EventMultiplexer } from 'vs/base/common/event'; import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdownActionViewItem'; import { IContextMenuProvider } from 'vs/base/browser/contextmenu'; const toolBarMoreIcon = registerIcon('toolbar-more', Codicon.more); export interface IToolBarOptions { orientation?: ActionsOrientation; actionViewItemProvider?: IActionViewItemProvider; ariaLabel?: string; getKeyBinding?: (action: IAction) => ResolvedKeybinding | undefined; actionRunner?: IActionRunner; toggleMenuTitle?: string; anchorAlignmentProvider?: () => AnchorAlignment; renderDropdownAsChildElement?: boolean; } /** * A widget that combines an action bar for primary actions and a dropdown for secondary actions. */ export class ToolBar extends Disposable { private options: IToolBarOptions; private actionBar: ActionBar; private toggleMenuAction: ToggleMenuAction; private toggleMenuActionViewItem: DropdownMenuActionViewItem | undefined; private submenuActionViewItems: DropdownMenuActionViewItem[] = []; private hasSecondaryActions: boolean = false; private lookupKeybindings: boolean; private element: HTMLElement; private _onDidChangeDropdownVisibility = this._register(new EventMultiplexer()); readonly onDidChangeDropdownVisibility = this._onDidChangeDropdownVisibility.event; private disposables = new DisposableStore(); constructor(container: HTMLElement, contextMenuProvider: IContextMenuProvider, options: IToolBarOptions = { orientation: ActionsOrientation.HORIZONTAL }) { super(); this.options = options; this.lookupKeybindings = typeof this.options.getKeyBinding === 'function'; this.toggleMenuAction = this._register(new ToggleMenuAction(() => this.toggleMenuActionViewItem?.show(), options.toggleMenuTitle)); this.element = document.createElement('div'); this.element.className = 'monaco-toolbar'; container.appendChild(this.element); this.actionBar = this._register(new ActionBar(this.element, { orientation: options.orientation, ariaLabel: options.ariaLabel, actionRunner: options.actionRunner, actionViewItemProvider: (action: IAction) => { if (action.id === ToggleMenuAction.ID) { this.toggleMenuActionViewItem = new DropdownMenuActionViewItem( action, (action).menuActions, contextMenuProvider, { actionViewItemProvider: this.options.actionViewItemProvider, actionRunner: this.actionRunner, keybindingProvider: this.options.getKeyBinding, classNames: toolBarMoreIcon.classNamesArray, anchorAlignmentProvider: this.options.anchorAlignmentProvider, menuAsChild: !!this.options.renderDropdownAsChildElement } ); this.toggleMenuActionViewItem.setActionContext(this.actionBar.context); this.disposables.add(this._onDidChangeDropdownVisibility.add(this.toggleMenuActionViewItem.onDidChangeVisibility)); return this.toggleMenuActionViewItem; } if (options.actionViewItemProvider) { const result = options.actionViewItemProvider(action); if (result) { return result; } } if (action instanceof SubmenuAction) { const result = new DropdownMenuActionViewItem( action, action.actions, contextMenuProvider, { actionViewItemProvider: this.options.actionViewItemProvider, actionRunner: this.actionRunner, keybindingProvider: this.options.getKeyBinding, classNames: action.class, anchorAlignmentProvider: this.options.anchorAlignmentProvider, menuAsChild: true } ); result.setActionContext(this.actionBar.context); this.submenuActionViewItems.push(result); this.disposables.add(this._onDidChangeDropdownVisibility.add(result.onDidChangeVisibility)); return result; } return undefined; } })); } set actionRunner(actionRunner: IActionRunner) { this.actionBar.actionRunner = actionRunner; } get actionRunner(): IActionRunner { return this.actionBar.actionRunner; } set context(context: unknown) { this.actionBar.context = context; if (this.toggleMenuActionViewItem) { this.toggleMenuActionViewItem.setActionContext(context); } for (const actionViewItem of this.submenuActionViewItems) { actionViewItem.setActionContext(context); } } getElement(): HTMLElement { return this.element; } getItemsWidth(): number { let itemsWidth = 0; for (let i = 0; i < this.actionBar.length(); i++) { itemsWidth += this.actionBar.getWidth(i); } return itemsWidth; } setAriaLabel(label: string): void { this.actionBar.setAriaLabel(label); } setActions(primaryActions: ReadonlyArray, secondaryActions?: ReadonlyArray): void { this.clear(); let primaryActionsToSet = primaryActions ? primaryActions.slice(0) : []; // Inject additional action to open secondary actions if present this.hasSecondaryActions = !!(secondaryActions && secondaryActions.length > 0); if (this.hasSecondaryActions && secondaryActions) { this.toggleMenuAction.menuActions = secondaryActions.slice(0); primaryActionsToSet.push(this.toggleMenuAction); } primaryActionsToSet.forEach(action => { this.actionBar.push(action, { icon: true, label: false, keybinding: this.getKeybindingLabel(action) }); }); } private getKeybindingLabel(action: IAction): string | undefined { const key = this.lookupKeybindings ? this.options.getKeyBinding?.(action) : undefined; return withNullAsUndefined(key?.getLabel()); } private clear(): void { this.submenuActionViewItems = []; this.disposables.clear(); this.actionBar.clear(); } dispose(): void { this.clear(); super.dispose(); } } class ToggleMenuAction extends Action { static readonly ID = 'toolbar.toggle.more'; private _menuActions: ReadonlyArray; private toggleDropdownMenu: () => void; constructor(toggleDropdownMenu: () => void, title?: string) { title = title || nls.localize('moreActions', "More Actions..."); super(ToggleMenuAction.ID, title, undefined, true); this._menuActions = []; this.toggleDropdownMenu = toggleDropdownMenu; } async run(): Promise { this.toggleDropdownMenu(); } get menuActions(): ReadonlyArray { return this._menuActions; } set menuActions(actions: ReadonlyArray) { this._menuActions = actions; } }