diff --git a/src/sql/parts/notebook/notebook.component.ts b/src/sql/parts/notebook/notebook.component.ts index 2a3225b7af..add5631e96 100644 --- a/src/sql/parts/notebook/notebook.component.ts +++ b/src/sql/parts/notebook/notebook.component.ts @@ -32,6 +32,11 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { KernelsDropdown, AttachToDropdown, AddCellAction, TrustedAction, SaveNotebookAction } from 'sql/parts/notebook/notebookActions'; import { attachSelectBoxStyler } from 'vs/platform/theme/common/styler'; +import { MenuId, IMenuService, MenuItemAction } from 'vs/platform/actions/common/actions'; +import { IAction, Action, IActionItem } from 'vs/base/common/actions'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { fillInActions, LabeledMenuItemActionItem } from 'vs/platform/actions/browser/menuItemActionItem'; import { IObjectExplorerService } from 'sql/parts/objectExplorer/common/objectExplorerService'; import * as TaskUtilities from 'sql/workbench/common/taskUtilities'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -72,7 +77,10 @@ export class NotebookComponent extends AngularDisposable implements OnInit { @Inject(IInstantiationService) private instantiationService: IInstantiationService, @Inject(IContextMenuService) private contextMenuService: IContextMenuService, @Inject(IContextViewService) private contextViewService: IContextViewService, - @Inject(IConnectionDialogService) private connectionDialogService: IConnectionDialogService + @Inject(IConnectionDialogService) private connectionDialogService: IConnectionDialogService, + @Inject(IContextKeyService) private contextKeyService: IContextKeyService, + @Inject(IMenuService) private menuService: IMenuService, + @Inject(IKeybindingService) private keybindingService: IKeybindingService ) { super(); this.updateProfile(); @@ -273,8 +281,19 @@ export class NotebookComponent extends AngularDisposable implements OnInit { let saveNotebookButton = this.instantiationService.createInstance(SaveNotebookAction, 'notebook.SaveNotebook', localize('save', 'Save'), 'notebook-button icon-save'); + // Get all of the menu contributions that use the ID 'notebook/toolbar'. + // Then, find all groups (currently we don't leverage the contributed + // groups functionality for the notebook toolbar), and fill in the 'primary' + // array with items that don't list a group. Finally, add any actions from + // the primary array to the end of the toolbar. + const notebookBarMenu = this.menuService.createMenu(MenuId.NotebookToolbar, this.contextKeyService); + let groups = notebookBarMenu.getActions({ arg: null, shouldForwardArgs: true }); + let primary: IAction[] = []; + let secondary: IAction[] = []; + fillInActions(groups, {primary, secondary}, false, (group: string) => group === undefined); + let taskbar = this.toolbar.nativeElement; - this._actionBar = new Taskbar(taskbar, this.contextMenuService); + this._actionBar = new Taskbar(taskbar, this.contextMenuService, { actionItemProvider: action => this.actionItemProvider(action as Action)}); this._actionBar.context = this; this._actionBar.setContent([ { element: kernelContainer }, @@ -284,6 +303,12 @@ export class NotebookComponent extends AngularDisposable implements OnInit { { action: saveNotebookButton }, { action: this._trustedAction } ]); + + // Primary actions are categorized as those that are added to the 'horizontal' group. + // For the vertical toolbar, we can do the same thing and instead use the 'vertical' group. + for (let action of primary) { + this._actionBar.addAction(action); + } } public async save(): Promise { @@ -303,5 +328,13 @@ export class NotebookComponent extends AngularDisposable implements OnInit { // } } + private actionItemProvider(action: Action): IActionItem { + // Check extensions to create ActionItem; otherwise, return undefined + // This is similar behavior that exists in MenuItemActionItem + if (action instanceof MenuItemAction) { + return new LabeledMenuItemActionItem(action, this.keybindingService, this.notificationService, this.contextMenuService, 'notebook-button'); + } + return undefined; + } } diff --git a/src/vs/platform/actions/browser/menuItemActionItem.ts b/src/vs/platform/actions/browser/menuItemActionItem.ts index 033951baef..2069f981c0 100644 --- a/src/vs/platform/actions/browser/menuItemActionItem.ts +++ b/src/vs/platform/actions/browser/menuItemActionItem.ts @@ -288,3 +288,63 @@ export class ContextAwareMenuItemActionItem extends MenuItemActionItem { .done(undefined, err => this._notificationService.error(err)); } } + +// Always show label for action items, instead of whether they don't have +// an icon/CSS class. Useful for some toolbar scenarios in particular with +// contributed actions from other extensions +export class LabeledMenuItemActionItem extends MenuItemActionItem { + private _labeledItemClassDispose: IDisposable; + + constructor( + public _action: MenuItemAction, + @IKeybindingService private readonly _labeledkeybindingService: IKeybindingService, + @INotificationService protected _notificationService: INotificationService, + @IContextMenuService private readonly _labeledcontextMenuService: IContextMenuService, + private readonly _defaultCSSClassToAdd: string = '' + ) { + super(_action, _labeledkeybindingService, _notificationService, _labeledcontextMenuService); + } + _updateLabel(): void { + this.$e.text(this._commandAction.label); + } + + // Overwrite item class to ensure that we can pass in a CSS class that other items use + // Leverages the _defaultCSSClassToAdd property that's passed into the constructor + _updateItemClass(item: ICommandAction): void { + dispose(this._labeledItemClassDispose); + this._labeledItemClassDispose = undefined; + + if (item.iconLocation) { + let iconClass: string; + + const iconPathMapKey = item.iconLocation.dark.toString(); + + if (MenuItemActionItem.ICON_PATH_TO_CSS_RULES.has(iconPathMapKey)) { + iconClass = MenuItemActionItem.ICON_PATH_TO_CSS_RULES.get(iconPathMapKey); + } else { + iconClass = ids.nextId(); + createCSSRule(`.icon.${iconClass}`, `background-image: url("${(item.iconLocation.light || item.iconLocation.dark).toString()}")`); + createCSSRule(`.vs-dark .icon.${iconClass}, .hc-black .icon.${iconClass}`, `background-image: url("${item.iconLocation.dark.toString()}")`); + MenuItemActionItem.ICON_PATH_TO_CSS_RULES.set(iconPathMapKey, iconClass); + } + + this.$e.getHTMLElement().classList.add('icon', iconClass); + this.$e.getHTMLElement().classList.add(this._defaultCSSClassToAdd); + this._labeledItemClassDispose = { + dispose: () => { + this.$e.getHTMLElement().classList.remove('icon', iconClass); + this.$e.getHTMLElement().classList.remove(this._defaultCSSClassToAdd); + } + }; + } + } + + dispose(): void { + if (this._labeledItemClassDispose) { + dispose(this._labeledItemClassDispose); + this._labeledItemClassDispose = undefined; + } + + super.dispose(); + } +} diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 9068654866..e35a891056 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -102,6 +102,7 @@ export class MenuId { static readonly MenubarHelpMenu = new MenuId(); // {{SQL CARBON EDIT}} static readonly ObjectExplorerItemContext = new MenuId(); + static readonly NotebookToolbar = new MenuId(); readonly id: string = String(MenuId.ID++); diff --git a/src/vs/workbench/services/actions/electron-browser/menusExtensionPoint.ts b/src/vs/workbench/services/actions/electron-browser/menusExtensionPoint.ts index a7c043ae67..6cdfabb1ef 100644 --- a/src/vs/workbench/services/actions/electron-browser/menusExtensionPoint.ts +++ b/src/vs/workbench/services/actions/electron-browser/menusExtensionPoint.ts @@ -43,6 +43,7 @@ namespace schema { case 'view/item/context': return MenuId.ViewItemContext; // {{SQL CARBON EDIT}} case 'objectExplorer/item/context': return MenuId.ObjectExplorerItemContext; + case 'notebook/toolbar': return MenuId.NotebookToolbar; } return void 0;