From 47687ff6b2000fc28768113e9cef800226c7fec5 Mon Sep 17 00:00:00 2001 From: Hale Rankin Date: Fri, 15 May 2020 19:13:31 -0700 Subject: [PATCH] Notebook main toolbar additions (#10271) * Reworking notebook action bar functionality and appearance. * added separator * Revised notebookActions for collapse, clear and trusted such that they can be implemented with a boolean set to T of F and show labels or shift them into tooltip for accessibility. Updated styles for select boxes. Added toolbar icons to common icon location. Split icon definition for use as masked or background-image. * Completed styles for action icons: collapse, clear and trusted. Added theme colors. Simplified icon behavior styles. * Made maskedIconClass optional. Added theme colors for toolbar icons, select box border and dropdown arrow. Experimenting with adding masked icon to pseudo element so I can pull out label text from icons. * Added icons styles to handle masked SVG elements as pseudo element beside button text. Added icons using this method to respect the color theming system. * Adjusted styles for the cell and run all icons in notebook toolbar. * Prepped notebook toolbar with placeholder icon for Underline action. Implemented Underline action. Added custom --wip-- ButtonMenu control, a modified copy of DropDown. * Revised colorRegistry and corresponding notebook styles. Removed unused code from new custom control: buttonMneu. Revised icon styles to create a dropdown arrow for buttonMenu. * Added new icon for Underline action. * Removed comment from needed markup. * Replaced actionItemProvider with optional undefined per DropdownMenuActionViewItem constructor. * Cleaned up new control, removing unneeded code and referencing what the class needs. Corrected style declaration for overriding input box padding. Removed unused notebook color styles. Scoped element styles to the toolbar so others outside the toolbar are not affected. * Removed unnecessary !important from style override. * Removed reference to unused color entry. * Syntax cleanup. * Put notebook toolbar improvements behind the preview flag. This involves some conditionals and CSS classes. * Updated icon used for Manage Packages. Created and updated styles for notebook toolbar icon spacing. Modified notebook.component contributed actions so that the label text is shifted into the title attribute. Added new icon for Not Trusted toggle. * Replaced SVG code for not-trusted icon. * Addressed PR feedback: changed masked classname. Revised component and CSS accordingly. Removed unnecessary instance of in-preview class. Fixed code logic that assigns label text to tooltip on incoming contributed action --- extensions/notebook/package.json | 4 +- .../resources/dark/packages_inverse.svg | 3 + .../notebook/resources/light/packages.svg | 3 + .../base/browser/ui/buttonMenu/buttonMenu.css | 13 + .../base/browser/ui/buttonMenu/buttonMenu.ts | 113 +++++++ .../browser/ui/selectBox/media/selectBox.css | 8 +- .../base/browser/ui/taskbar/media/taskbar.css | 1 + src/sql/media/icons/action-collapse.svg | 3 + src/sql/media/icons/action-expand.svg | 5 + src/sql/media/icons/clear.svg | 8 + src/sql/media/icons/code.svg | 5 + src/sql/media/icons/common-icons.css | 308 +++++++++++++----- src/sql/media/icons/markdown.svg | 3 + src/sql/media/icons/packages.svg | 3 + src/sql/media/icons/shield-x.svg | 5 + src/sql/media/icons/shield.svg | 3 + src/sql/media/icons/start-outline.svg | 3 + src/sql/media/icons/toolbar-underline.svg | 3 + .../platform/theme/common/colorRegistry.ts | 19 +- .../cellViews/markdownToolbar.component.ts | 3 + .../browser/markdownToolbarActions.ts | 5 + .../notebook/browser/notebook.component.ts | 150 +++++++-- .../contrib/notebook/browser/notebook.css | 124 +++++-- .../notebook/browser/notebookActions.ts | 89 ++++- .../notebook/browser/notebookStyles.ts | 21 +- .../test/browser/notebookActions.test.ts | 6 +- 26 files changed, 733 insertions(+), 178 deletions(-) create mode 100644 extensions/notebook/resources/dark/packages_inverse.svg create mode 100644 extensions/notebook/resources/light/packages.svg create mode 100644 src/sql/base/browser/ui/buttonMenu/buttonMenu.css create mode 100644 src/sql/base/browser/ui/buttonMenu/buttonMenu.ts create mode 100644 src/sql/media/icons/action-collapse.svg create mode 100644 src/sql/media/icons/action-expand.svg create mode 100644 src/sql/media/icons/clear.svg create mode 100644 src/sql/media/icons/code.svg create mode 100644 src/sql/media/icons/markdown.svg create mode 100644 src/sql/media/icons/packages.svg create mode 100644 src/sql/media/icons/shield-x.svg create mode 100644 src/sql/media/icons/shield.svg create mode 100644 src/sql/media/icons/start-outline.svg create mode 100644 src/sql/media/icons/toolbar-underline.svg diff --git a/extensions/notebook/package.json b/extensions/notebook/package.json index 92243b400e..7825bedff5 100644 --- a/extensions/notebook/package.json +++ b/extensions/notebook/package.json @@ -131,8 +131,8 @@ "command": "jupyter.cmd.managePackages", "title": "%title.managePackages%", "icon": { - "dark": "resources/dark/manage_inverse.svg", - "light": "resources/light/manage.svg" + "dark": "resources/dark/packages_inverse.svg", + "light": "resources/light/packages.svg" } }, { diff --git a/extensions/notebook/resources/dark/packages_inverse.svg b/extensions/notebook/resources/dark/packages_inverse.svg new file mode 100644 index 0000000000..a46fb203d4 --- /dev/null +++ b/extensions/notebook/resources/dark/packages_inverse.svg @@ -0,0 +1,3 @@ + + + diff --git a/extensions/notebook/resources/light/packages.svg b/extensions/notebook/resources/light/packages.svg new file mode 100644 index 0000000000..5b75cda4c0 --- /dev/null +++ b/extensions/notebook/resources/light/packages.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/sql/base/browser/ui/buttonMenu/buttonMenu.css b/src/sql/base/browser/ui/buttonMenu/buttonMenu.css new file mode 100644 index 0000000000..3036c3afa3 --- /dev/null +++ b/src/sql/base/browser/ui/buttonMenu/buttonMenu.css @@ -0,0 +1,13 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.button-menu .in-preview .monaco-dropdown { + height: auto; + padding: 0; +} +.button-menu.masked-pseudo-after.dropdown-arrow:after { + right: -2px; + width: 20px; +} diff --git a/src/sql/base/browser/ui/buttonMenu/buttonMenu.ts b/src/sql/base/browser/ui/buttonMenu/buttonMenu.ts new file mode 100644 index 0000000000..81016ff455 --- /dev/null +++ b/src/sql/base/browser/ui/buttonMenu/buttonMenu.ts @@ -0,0 +1,113 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'vs/css!./buttonMenu'; +import { IAction, IActionRunner } from 'vs/base/common/actions'; +import { BaseActionViewItem, IActionViewItemProvider } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IDisposable } from 'vs/base/common/lifecycle'; +import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; +import { ResolvedKeybinding } from 'vs/base/common/keyCodes'; +import { append, $, addClasses } from 'vs/base/browser/dom'; +import { IDropdownMenuOptions, DropdownMenu, IActionProvider, IContextMenuProvider, ILabelRenderer } from 'vs/base/browser/ui/dropdown/dropdown'; + +export class DropdownMenuActionViewItem extends BaseActionViewItem { + private menuActionsOrProvider: ReadonlyArray | IActionProvider; + private dropdownMenu: DropdownMenu | undefined; + private menuLabel?: string | undefined; + private contextMenuProvider: IContextMenuProvider; + private actionViewItemProvider?: IActionViewItemProvider; + private keybindings?: (action: IAction) => ResolvedKeybinding | undefined; + private cssClass: string | undefined; + private anchorAlignmentProvider: (() => AnchorAlignment) | undefined; + + constructor( + action: IAction, + menuActionsOrProvider: ReadonlyArray | IActionProvider, + contextMenuProvider: IContextMenuProvider, + actionViewItemProvider: IActionViewItemProvider | undefined, + actionRunner: IActionRunner, + keybindings: ((action: IAction) => ResolvedKeybinding | undefined) | undefined, + cssClass: string | undefined, + menuLabel: string | undefined, + anchorAlignmentProvider?: () => AnchorAlignment) { + + super(null, action); + + this.menuActionsOrProvider = menuActionsOrProvider; + this.contextMenuProvider = contextMenuProvider; + this.actionViewItemProvider = actionViewItemProvider; + this.actionRunner = actionRunner; + this.keybindings = keybindings; + this.cssClass = cssClass; + this.menuLabel = menuLabel; + this.anchorAlignmentProvider = anchorAlignmentProvider; + } + + render(container: HTMLElement): void { + const labelRenderer: ILabelRenderer = (el: HTMLElement): IDisposable | null => { + this.element = append(el, $('a.action-label.button-menu')); + if (this.cssClass) { + addClasses(this.element, this.cssClass); + } + if (this.menuLabel) { + this.element.innerText = this.menuLabel; + } + + this.element.tabIndex = 0; + this.element.setAttribute('role', 'button'); + this.element.setAttribute('aria-haspopup', 'true'); + this.element.title = this._action.label || ''; + + return null; + }; + + const options: IDropdownMenuOptions = { + contextMenuProvider: this.contextMenuProvider, + labelRenderer: labelRenderer + }; + + // Render the DropdownMenu around a simple action to toggle it + if (Array.isArray(this.menuActionsOrProvider)) { + options.actions = this.menuActionsOrProvider; + } else { + options.actionProvider = this.menuActionsOrProvider as IActionProvider; + } + + this.dropdownMenu = this._register(new DropdownMenu(container, options)); + this.dropdownMenu.menuOptions = { + actionViewItemProvider: this.actionViewItemProvider, + actionRunner: this.actionRunner, + getKeyBinding: this.keybindings, + context: this._context + }; + + if (this.anchorAlignmentProvider) { + const that = this; + + this.dropdownMenu.menuOptions = { + ...this.dropdownMenu.menuOptions, + get anchorAlignment(): AnchorAlignment { + return that.anchorAlignmentProvider!(); + } + }; + } + } + + setActionContext(newContext: unknown): void { + super.setActionContext(newContext); + + if (this.dropdownMenu) { + if (this.dropdownMenu.menuOptions) { + this.dropdownMenu.menuOptions.context = newContext; + } else { + this.dropdownMenu.menuOptions = { context: newContext }; + } + } + } + + show(): void { + this.dropdownMenu?.show(); + } +} diff --git a/src/sql/base/browser/ui/selectBox/media/selectBox.css b/src/sql/base/browser/ui/selectBox/media/selectBox.css index 9263b51613..c8bb8fc244 100644 --- a/src/sql/base/browser/ui/selectBox/media/selectBox.css +++ b/src/sql/base/browser/ui/selectBox/media/selectBox.css @@ -15,6 +15,10 @@ } .monaco-select-box { - padding: 2px 8px; - padding: 0 22px 0 6px !important; /* I don't like this but for now its fine */ + padding: 0 22px 0 6px; +} + +.monaco-inputbox > .wrapper > .input, +.monaco-inputbox > .wrapper > .mirror { + padding-left: 6px !important; } diff --git a/src/sql/base/browser/ui/taskbar/media/taskbar.css b/src/sql/base/browser/ui/taskbar/media/taskbar.css index 3fcd52d63e..acec293244 100644 --- a/src/sql/base/browser/ui/taskbar/media/taskbar.css +++ b/src/sql/base/browser/ui/taskbar/media/taskbar.css @@ -26,6 +26,7 @@ } .carbon-taskbar.monaco-toolbar .monaco-action-bar.animated .actions-container { + box-sizing: border-box; justify-content: flex-start; padding-left: 15px; flex-wrap: wrap; diff --git a/src/sql/media/icons/action-collapse.svg b/src/sql/media/icons/action-collapse.svg new file mode 100644 index 0000000000..955f68a2af --- /dev/null +++ b/src/sql/media/icons/action-collapse.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/sql/media/icons/action-expand.svg b/src/sql/media/icons/action-expand.svg new file mode 100644 index 0000000000..6cf95b4505 --- /dev/null +++ b/src/sql/media/icons/action-expand.svg @@ -0,0 +1,5 @@ + + opac_command_icons_bv + + + diff --git a/src/sql/media/icons/clear.svg b/src/sql/media/icons/clear.svg new file mode 100644 index 0000000000..ad97fac74d --- /dev/null +++ b/src/sql/media/icons/clear.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/sql/media/icons/code.svg b/src/sql/media/icons/code.svg new file mode 100644 index 0000000000..083e3729dc --- /dev/null +++ b/src/sql/media/icons/code.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/sql/media/icons/common-icons.css b/src/sql/media/icons/common-icons.css index 467b2c3429..f85aae7cc2 100644 --- a/src/sql/media/icons/common-icons.css +++ b/src/sql/media/icons/common-icons.css @@ -4,12 +4,12 @@ *--------------------------------------------------------------------------------------------*/ .vs .codicon.settings { - background-image: url('settings.svg'); + background-image: url("settings.svg"); } .vs-dark .codicon.settings, .hc-black .codicon.settings { - background-image: url('settings_inverse.svg'); + background-image: url("settings_inverse.svg"); } .vs .codicon.backup, @@ -123,21 +123,21 @@ .vs .codicon.scriptToClipboard, .vs-dark .codicon.scriptToClipboard, .hc-black .codicon.scriptToClipboard { - background-image: url('script_to_clipboard.svg'); + background-image: url("script_to_clipboard.svg"); background-repeat: no-repeat; background-position: 2px center; } .vs .codicon.close, .vs .codicon.remove { - background: url('close.svg') center center no-repeat !important; + background: url("close.svg") center center no-repeat !important; } .vs-dark .codicon.close, .hc-black .codicon.close, .vs-dark .codicon.remove, .hc-black .codicon.remove { - background: url('close_inverse.svg') center center no-repeat !important; + background: url("close_inverse.svg") center center no-repeat !important; } .vs .codicon.filter { @@ -181,20 +181,20 @@ .hc-black .codicon.toggle-more, .vs-dark .codicon.toggle-more { - background: url('ellipsis-inverse.svg') center center no-repeat; + background: url("ellipsis-inverse.svg") center center no-repeat; } .vs .codicon.toggle-more { - background: url('ellipsis.svg') center center no-repeat; + background: url("ellipsis.svg") center center no-repeat; } .hc-black .codicon.new, .vs-dark .codicon.new { - background: url('new_inverse.svg') center center no-repeat; + background: url("new_inverse.svg") center center no-repeat; } .vs .codicon.new { - background: url('new.svg') center center no-repeat; + background: url("new.svg") center center no-repeat; } .vs .codicon.new-query, @@ -206,7 +206,7 @@ .vs .codicon.configure-dashboard, .hc-black .codicon.configure-dashboard, .vs-dark .codicon.configure-dashboard { - background: url('configuredashboard.svg') center center no-repeat; + background: url("configuredashboard.svg") center center no-repeat; } .vs .codicon.edit, @@ -217,95 +217,212 @@ .hc-black .codicon.pin, .vs-dark .codicon.pin { - background: url('pin_inverse.svg') center center no-repeat; + background: url("pin_inverse.svg") center center no-repeat; } .vs .codicon.pin { - background: url('pin.svg') center center no-repeat; + background: url("pin.svg") center center no-repeat; } .hc-black .codicon.unpin, .vs-dark .codicon.unpin { - background: url('unpin_inverse.svg') center center no-repeat; + background: url("unpin_inverse.svg") center center no-repeat; } .vs .codicon.unpin { - background: url('unpin.svg') center center no-repeat; + background: url("unpin.svg") center center no-repeat; } .vs .sql.codicon.pause { - background-image: url('pause.svg') + background-image: url("pause.svg"); } .vs-dark .sql.codicon.pause, .hc-black .sql.codicon.pause { - background-image: url('pause_inverse.svg') + background-image: url("pause_inverse.svg"); } .vs .sql.codicon.continue { - background-image: url('continue.svg') + background-image: url("continue.svg"); } .vs-dark .sql.codicon.continue, .hc-black .sql.codicon.continue { - background-image: url('continue_inverse.svg') + background-image: url("continue_inverse.svg"); } .vs .sql.codicon.checked { - background-image: url('check.svg') + background-image: url("check.svg"); } .vs-dark .sql.codicon.checked, .hc-black .sql.codicon.checked { - background-image: url('check_inverse.svg') + background-image: url("check_inverse.svg"); } .vs .sql.codicon.start { - background-image: url('start.svg') + background-image: url("start.svg"); } .vs-dark .sql.codicon.start, .hc-black .sql.codicon.start { - background-image: url('start_inverse.svg') + background-image: url("start_inverse.svg"); } .vs .sql.codicon.stop { - background-image: url('stop.svg') + background-image: url("stop.svg"); } .vs-dark .sql.codicon.stop, .hc-black .sql.codicon.stop { - background-image: url('stop_inverse.svg') + background-image: url("stop_inverse.svg"); } /* Notebook cells */ .codicon.toolbarIconRunInactive { - background-image: url('execute_cell_grey.svg'); + background-image: url("execute_cell_grey.svg"); } .codicon.toolbarIconRun { - background-image: url('execute_cell.svg'); + background-image: url("execute_cell.svg"); } .codicon.toolbarIconRunError { - background-image: url('execute_cell_error.svg'); + background-image: url("execute_cell_error.svg"); } .codicon.toolbarIconStop { - background-image: url('stop_cell_solidanimation.svg'); + background-image: url("stop_cell_solidanimation.svg"); } .vs-dark .codicon.toolbarIconRunInactive { - background-image: url('execute_cell_dark.svg'); + background-image: url("execute_cell_dark.svg"); } .vs-dark .codicon.toolbarIconRun { - background-image: url('execute_cell_white.svg'); + background-image: url("execute_cell_white.svg"); } .hc-black .codicon.toolbarIconRunInactive { - background-image: url('execute_cell_hc.svg'); + background-image: url("execute_cell_hc.svg"); } .hc-black .codicon.toolbarIconRun { - background-image: url('execute_cell_orange_hc.svg'); + background-image: url("execute_cell_orange_hc.svg"); } .vs-dark .codicon.toolbarIconStop, .hc-black .codicon.toolbarIconStop { - background-image: url('stop_cell_solidanimation_inverse.svg'); + background-image: url("stop_cell_solidanimation_inverse.svg"); +} + +/* Icons as masked elements for easy theme switching. +Includes non-masked style declarations. */ +.masked-icon { + display: inline-block; + height: 20px; + width: 20px; + -webkit-mask-position: center; + -webkit-mask-repeat: no-repeat; + mask-position: center; + mask-repeat: no-repeat; + -webkit-mask-size: 50% 100%; + mask-size: 50% 100%; +} + +.codicon.bold { + -webkit-mask-image: url("toolbar-bold.svg"); + mask-image: url("toolbar-bold.svg"); +} +.codicon.italic { + -webkit-mask-image: url("toolbar-italic.svg"); + mask-image: url("toolbar-italic.svg"); +} +.codicon.underline { + -webkit-mask-image: url("toolbar-underline.svg"); + mask-image: url("toolbar-underline.svg"); +} +.codicon.highlight { + -webkit-mask-image: url("toolbar-highlight.svg"); + mask-image: url("toolbar-highlight.svg"); +} +.codicon.code { + -webkit-mask-image: url("toolbar-code.svg"); + mask-image: url("toolbar-code.svg"); +} +.codicon.insert-link { + -webkit-mask-image: url("toolbar-link.svg"); + mask-image: url("toolbar-link.svg"); +} +.codicon.list { + -webkit-mask-image: url("toolbar-list.svg"); + mask-image: url("toolbar-list.svg"); +} +.codicon.ordered-list { + -webkit-mask-image: url("toolbar-ordered-list.svg"); + mask-image: url("toolbar-ordered-list.svg"); +} +.codicon.insert-image { + -webkit-mask-image: url("toolbar-image.svg"); + mask-image: url("toolbar-image.svg"); +} +.codicon.split-toggle-on { + -webkit-mask-image: url("toolbar-preview-toggle-on.svg"); + mask-image: url("toolbar-preview-toggle-on.svg"); +} +.codicon.split-toggle-off { + -webkit-mask-image: url("toolbar-preview-toggle-off.svg"); + mask-image: url("toolbar-preview-toggle-off.svg"); +} +.codicon.code { + -webkit-mask-image: url("code.svg"); + mask-image: url("code.svg"); +} +.codicon.markdown { + -webkit-mask-image: url("markdown.svg"); + mask-image: url("markdown.svg"); +} + +.codicon:not(.masked-icon).icon-expand-cells { + background-image: url("action-expand.svg"); +} +.codicon.masked-icon.icon-expand-cells { + background-image: none; + -webkit-mask-image: url("action-expand.svg"); + mask-image: url("action-expand.svg"); +} + +.codicon:not(.masked-icon).icon-collapse-cells { + background-image: url("action-collapse.svg"); +} +.codicon.masked-icon.icon-collapse-cells { + -webkit-mask-image: url("action-collapse.svg"); + mask-image: url("action-collapse.svg"); +} + +.codicon:not(.masked-icon).icon-clear-results { + background-image: url("clear.svg"); +} +.codicon.masked-icon.icon-clear-results { + -webkit-mask-image: url("clear.svg"); + mask-image: url("clear.svg"); +} + +.codicon:not(.masked-icon).icon-shield { + background-image: url("shield.svg"); +} +.codicon.masked-icon.icon-shield { + -webkit-mask-image: url("shield.svg"); + mask-image: url("shield.svg"); +} + +.codicon:not(.masked-icon).icon-shield-x { + background-image: url("shield-x.svg"); +} +.codicon.masked-icon.icon-shield-x { + -webkit-mask-image: url("shield-x.svg"); + mask-image: url("shield-x.svg"); +} + +.codicon:not(.masked-icon).packages { + background-image: url("packages.svg"); +} +.codicon.masked-icon.packages { + background-image: none; + -webkit-mask-image: url("packages.svg"); + mask-image: url("packages.svg"); } .codicon.arrow-up { @@ -316,78 +433,95 @@ background-image: url("chevron_up_inverse.svg"); } -.codicon.arrow-down { +.codicon:not(.masked-icon).arrow-down { background-image: url("chevron_down.svg"); } -.vs-dark .codicon.arrow-down, -.hc-black .codicon.arrow-down { +.vs-dark .codicon:not(.masked-icon).arrow-down, +.hc-black .codicon:not(.masked-icon).arrow-down { background-image: url("chevron_down_inverse.svg"); } - -/* Icons as masked elements for easy theme switching */ -.codicon.bold { - -webkit-mask-image: url('toolbar-bold.svg'); - mask-image: url('toolbar-bold.svg'); -} -.codicon.italic { - -webkit-mask-image: url('toolbar-italic.svg'); - mask-image: url('toolbar-italic.svg'); -} -.codicon.highlight { - -webkit-mask-image: url('toolbar-highlight.svg'); - mask-image: url('toolbar-highlight.svg'); -} -.codicon.code { - -webkit-mask-image: url('toolbar-code.svg'); - mask-image: url('toolbar-code.svg'); -} -.codicon.insert-link { - -webkit-mask-image: url('toolbar-link.svg'); - mask-image: url('toolbar-link.svg'); -} -.codicon.list { - -webkit-mask-image: url('toolbar-list.svg'); - mask-image: url('toolbar-list.svg'); -} -.codicon.ordered-list { - -webkit-mask-image: url('toolbar-ordered-list.svg'); - mask-image: url('toolbar-ordered-list.svg'); -} -.codicon.insert-image { - -webkit-mask-image: url('toolbar-image.svg'); - mask-image: url('toolbar-image.svg'); -} -.codicon.split-toggle-on { - -webkit-mask-image: url('toolbar-preview-toggle-on.svg'); - mask-image: url('toolbar-preview-toggle-on.svg'); -} -.codicon.split-toggle-off { - -webkit-mask-image: url('toolbar-preview-toggle-off.svg'); - mask-image: url('toolbar-preview-toggle-off.svg'); +.codicon.masked-icon.arrow-down { + background-image: none; + -webkit-mask-image: url("chevron_down.svg"); + mask-image: url("chevron_down.svg"); +} + +.vs .codicon.new-blue { + background-image: url("new-blue.svg"); +} +.vs .codicon.start-outline { + background-image: url("start-outline.svg"); +} + +/* Masked element inside pseudo element */ +.masked-pseudo { + background-image: none !important; +} +.masked-pseudo:before, +.masked-pseudo-after:after { + content: ""; + display: block; + position: absolute; + -webkit-mask-position: center; + -webkit-mask-repeat: no-repeat; + mask-position: center; + mask-repeat: no-repeat; + -webkit-mask-size: 50% 100%; + mask-size: 50% 100%; +} +.masked-pseudo:before { + height: 23px; + left: 0; + top: 2px; + width: 30px; +} +.masked-pseudo-after:after { + height: 23px; + right: 0; + top: 2px; + width: 30px; +} +.masked-pseudo-after.dropdown-arrow:after { + background-image: none; + -webkit-mask-image: url("chevron_down.svg"); + mask-image: url("chevron_down.svg"); +} +.masked-pseudo.add-new:before { + -webkit-mask-image: url("new.svg"); + mask-image: url("new.svg"); +} +.masked-pseudo.start-outline:before { + -webkit-mask-image: url("start-outline.svg"); + mask-image: url("start-outline.svg"); +} + +.masked-pseudo.code:before { + -webkit-mask-image: url("code.svg"); + mask-image: url("code.svg"); +} +.masked-pseudo.markdown:before { + -webkit-mask-image: url("markdown.svg"); + mask-image: url("markdown.svg"); } -/* Cell toolbar icons */ .cell-tool-close { - background-image: url('close-blue.svg'); + background-image: url("close-blue.svg"); } .cell-tool-edit { - background-image: url('edit.svg'); -} -.cell-tool-add { - background-image: url('new-blue.svg'); + background-image: url("edit.svg"); } .cell-tool-move-up { - background-image: url('down-arrow-blue.svg'); + background-image: url("down-arrow-blue.svg"); transform: scale(-1); } .cell-tool-move-down { - background-image: url('down-arrow-blue.svg'); + background-image: url("down-arrow-blue.svg"); } .cell-tool-delete { - background-image: url('garbage-can-blue.svg'); + background-image: url("garbage-can-blue.svg"); } .cell-tool-more { - background-image: url('ellipsis-blue.svg'); + background-image: url("ellipsis-blue.svg"); } .database-colored.codicon { diff --git a/src/sql/media/icons/markdown.svg b/src/sql/media/icons/markdown.svg new file mode 100644 index 0000000000..ce246b60cb --- /dev/null +++ b/src/sql/media/icons/markdown.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/sql/media/icons/packages.svg b/src/sql/media/icons/packages.svg new file mode 100644 index 0000000000..5b75cda4c0 --- /dev/null +++ b/src/sql/media/icons/packages.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/sql/media/icons/shield-x.svg b/src/sql/media/icons/shield-x.svg new file mode 100644 index 0000000000..9ae38feedf --- /dev/null +++ b/src/sql/media/icons/shield-x.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/sql/media/icons/shield.svg b/src/sql/media/icons/shield.svg new file mode 100644 index 0000000000..a458fa9c64 --- /dev/null +++ b/src/sql/media/icons/shield.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/sql/media/icons/start-outline.svg b/src/sql/media/icons/start-outline.svg new file mode 100644 index 0000000000..3464243804 --- /dev/null +++ b/src/sql/media/icons/start-outline.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/sql/media/icons/toolbar-underline.svg b/src/sql/media/icons/toolbar-underline.svg new file mode 100644 index 0000000000..0b3dc338a7 --- /dev/null +++ b/src/sql/media/icons/toolbar-underline.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/sql/platform/theme/common/colorRegistry.ts b/src/sql/platform/theme/common/colorRegistry.ts index 296055663c..15ea876d9e 100644 --- a/src/sql/platform/theme/common/colorRegistry.ts +++ b/src/sql/platform/theme/common/colorRegistry.ts @@ -42,19 +42,26 @@ export const gradientTwo = registerColor('gradientTwo', { light: gradientTwoColo export const gradientBackground = registerColor('gradientBackground', { light: '#fff', dark: 'transparent', hc: 'transparent' }, nls.localize('gradientBackground', "The background color for the banner image gradient")); // --- Notebook Colors +export const notebookToolbarIcon = registerColor('notebook.notebookToolbarIcon', { light: '#0078D4', dark: '#3AA0F3', hc: '#FFFFFF' }, nls.localize('notebook.notebookToolbarIcon', "Notebook: Main toolbar icons")); +export const notebookToolbarSelectBorder = registerColor('notebook.notebookToolbarSelectBorder', { light: '#A5A5A5', dark: '#8A8886', hc: '#2B56F2' }, nls.localize('notebook.notebookToolbarSelectBorder', "Notebook: Main toolbar select box border")); +export const notebookToolbarSelectBackground = registerColor('notebook.notebookToolbarSelectBackground', { light: '#FFFFFF', dark: '#1B1A19', hc: '#000000' }, nls.localize('notebook.notebookToolbarSelectBackground', "Notebook: Main toolbar select box background")); +export const notebookToolbarLines = registerColor('notebook.notebookToolbarLines', { light: '#D6D6D6', dark: '#323130', hc: '#2B56F2' }, nls.localize('notebook.notebookToolbarLines', "Notebook: Main toolbar bottom border and separator")); +export const dropdownArrow = registerColor('notebook.dropdownArrow', { light: '#A5A5A5', dark: '#FFFFFF', hc: '#FFFFFF' }, nls.localize('notebook.dropdownArrow', "Notebook: Main toolbar dropdown arrow")); +export const buttonMenuArrow = registerColor('notebook.buttonMenuArrow', { light: '#000000', dark: '#FFFFFF', hc: '#FFFFFF' }, nls.localize('notebook.buttonMenuArrow', "Notebook: Main toolbar custom buttonMenu dropdown arrow")); + export const toolbarBackground = registerColor('notebook.toolbarBackground', { light: '#F5F5F5', dark: '#252423', hc: '#000000' }, nls.localize('notebook.toolbarBackground', "Notebook: Markdown toolbar background")); -export const toolbarIcon = registerColor('notebook.toolbarIcon', { light: '#323130', dark: '#FFFFFe', hc: '#FFFFFe' }, nls.localize('notebook.toolbarIcon', "Notebook: Markdown toolbar icons")); +export const toolbarIcon = registerColor('notebook.toolbarIcon', { light: '#323130', dark: '#FFFFFF', hc: '#FFFFFF' }, nls.localize('notebook.toolbarIcon', "Notebook: Markdown toolbar icons")); export const toolbarBottomBorder = registerColor('notebook.toolbarBottomBorder', { light: '#D4D4D4', dark: '#323130', hc: '#E86E58' }, nls.localize('notebook.toolbarBottomBorder', "Notebook: Markdown toolbar bottom border")); // Notebook: All cells -export const cellBorder = registerColor('notebook.cellBorder', { light: '#0078D4', dark: '#0078D4', hc: '#E86E58' }, nls.localize('notebook.cellBorder', "Notebook: Active cell border")); +export const cellBorder = registerColor('notebook.cellBorder', { light: '#0078D4', dark: '#3AA0F3', hc: '#E86E58' }, nls.localize('notebook.cellBorder', "Notebook: Active cell border")); // Notebook: Markdown cell -export const markdownEditorBackground = registerColor('notebook.markdownEditorBackground', { light: '#FFFFFe', dark: '#1B1A19', hc: '#000000' }, nls.localize('notebook.markdownEditorBackground', "Notebook: Markdown editor background")); +export const markdownEditorBackground = registerColor('notebook.markdownEditorBackground', { light: '#FFFFFF', dark: '#1B1A19', hc: '#000000' }, nls.localize('notebook.markdownEditorBackground', "Notebook: Markdown editor background")); export const splitBorder = registerColor('notebook.splitBorder', { light: '#E6E6E6', dark: '#323130', hc: '#872412' }, nls.localize('notebook.splitBorder', "Notebook: Border between Markdown editor and preview")); // Notebook: Code cell export const codeEditorBackground = registerColor('notebook.codeEditorBackground', { light: '#F5F5F5', dark: '#333333', hc: '#000000' }, nls.localize('notebook.codeEditorBackground', "Notebook: Code editor background")); -export const codeEditorBackgroundActive = registerColor('notebook.codeEditorBackgroundActive', { light: '#FFFFFe', dark: null, hc: null }, nls.localize('notebook.codeEditorBackgroundActive', "Notebook: Code editor background of active cell")); -export const codeEditorLineNumber = registerColor('notebook.codeEditorLineNumber', { light: '#A19F9D', dark: '#A19F9D', hc: '#FFFFFe' }, nls.localize('notebook.codeEditorLineNumber', "Notebook: Code editor line numbers")); -export const codeEditorToolbarIcon = registerColor('notebook.codeEditorToolbarIcon', { light: '#999999', dark: '#A19F9D', hc: '#FFFFFe' }, nls.localize('notebook.codeEditorToolbarIcon', "Notebook: Code editor toolbar icons")); +export const codeEditorBackgroundActive = registerColor('notebook.codeEditorBackgroundActive', { light: '#FFFFFF', dark: null, hc: null }, nls.localize('notebook.codeEditorBackgroundActive', "Notebook: Code editor background of active cell")); +export const codeEditorLineNumber = registerColor('notebook.codeEditorLineNumber', { light: '#A19F9D', dark: '#A19F9D', hc: '#FFFFFF' }, nls.localize('notebook.codeEditorLineNumber', "Notebook: Code editor line numbers")); +export const codeEditorToolbarIcon = registerColor('notebook.codeEditorToolbarIcon', { light: '#999999', dark: '#A19F9D', hc: '#FFFFFF' }, nls.localize('notebook.codeEditorToolbarIcon', "Notebook: Code editor toolbar icons")); export const codeEditorToolbarBackground = registerColor('notebook.codeEditorToolbarBackground', { light: '#EEEEEE', dark: '#333333', hc: '#000000' }, nls.localize('notebook.codeEditorToolbarBackground', "Notebook: Code editor toolbar background")); export const codeEditorToolbarBorder = registerColor('notebook.codeEditorToolbarBorder', { light: '#C8C6C4', dark: '#333333', hc: '#000000' }, nls.localize('notebook.codeEditorToolbarBorder', "Notebook: Code editor toolbar right border")); diff --git a/src/sql/workbench/contrib/notebook/browser/cellViews/markdownToolbar.component.ts b/src/sql/workbench/contrib/notebook/browser/cellViews/markdownToolbar.component.ts index ed9cf102ab..52e8c2633a 100644 --- a/src/sql/workbench/contrib/notebook/browser/cellViews/markdownToolbar.component.ts +++ b/src/sql/workbench/contrib/notebook/browser/cellViews/markdownToolbar.component.ts @@ -21,6 +21,7 @@ export class MarkdownToolbarComponent { public buttonBold = localize('buttonBold', "Bold"); public buttonItalic = localize('buttonItalic', "Italic"); + public buttonUnderline = localize('buttonUnderline', "Underline"); public buttonHighlight = localize('buttonHighlight', "Highlight"); public buttonCode = localize('buttonCode', "Code"); public buttonLink = localize('buttonLink', "Link"); @@ -43,6 +44,7 @@ export class MarkdownToolbarComponent { private initActionBar() { let boldButton = this._instantiationService.createInstance(TransformMarkdownAction, 'notebook.boldText', '', 'bold', this.buttonBold, this.cellModel, MarkdownButtonType.BOLD); let italicButton = this._instantiationService.createInstance(TransformMarkdownAction, 'notebook.italicText', '', 'italic', this.buttonItalic, this.cellModel, MarkdownButtonType.ITALIC); + let underlineButton = this._instantiationService.createInstance(TransformMarkdownAction, 'notebook.underlineText', '', 'underline', this.buttonUnderline, this.cellModel, MarkdownButtonType.UNDERLINE); let highlightButton = this._instantiationService.createInstance(TransformMarkdownAction, 'notebook.highlightText', '', 'highlight', this.buttonHighlight, this.cellModel, MarkdownButtonType.HIGHLIGHT); let codeButton = this._instantiationService.createInstance(TransformMarkdownAction, 'notebook.codeText', '', 'code', this.buttonCode, this.cellModel, MarkdownButtonType.CODE); let linkButton = this._instantiationService.createInstance(TransformMarkdownAction, 'notebook.linkText', '', 'insert-link', this.buttonLink, this.cellModel, MarkdownButtonType.LINK); @@ -56,6 +58,7 @@ export class MarkdownToolbarComponent { this._actionBar.setContent([ { action: boldButton }, { action: italicButton }, + { action: underlineButton }, { action: highlightButton }, { action: codeButton }, { action: linkButton }, diff --git a/src/sql/workbench/contrib/notebook/browser/markdownToolbarActions.ts b/src/sql/workbench/contrib/notebook/browser/markdownToolbarActions.ts index ba371a20ae..c6b5998fca 100644 --- a/src/sql/workbench/contrib/notebook/browser/markdownToolbarActions.ts +++ b/src/sql/workbench/contrib/notebook/browser/markdownToolbarActions.ts @@ -119,6 +119,8 @@ export class MarkdownTextTransformer { return '**'; case MarkdownButtonType.ITALIC: return '_'; + case MarkdownButtonType.UNDERLINE: + return ''; case MarkdownButtonType.CODE: return '```\n'; case MarkdownButtonType.LINK: @@ -142,6 +144,8 @@ export class MarkdownTextTransformer { return '**'; case MarkdownButtonType.ITALIC: return '_'; + case MarkdownButtonType.UNDERLINE: + return ''; case MarkdownButtonType.CODE: return '\n```'; case MarkdownButtonType.LINK: @@ -370,6 +374,7 @@ export class MarkdownTextTransformer { export enum MarkdownButtonType { BOLD, ITALIC, + UNDERLINE, CODE, HIGHLIGHT, LINK, diff --git a/src/sql/workbench/contrib/notebook/browser/notebook.component.ts b/src/sql/workbench/contrib/notebook/browser/notebook.component.ts index 59b3090076..7997546a05 100644 --- a/src/sql/workbench/contrib/notebook/browser/notebook.component.ts +++ b/src/sql/workbench/contrib/notebook/browser/notebook.component.ts @@ -31,7 +31,8 @@ import * as notebookUtils from 'sql/workbench/services/notebook/browser/models/n import { Deferred } from 'sql/base/common/promise'; import { IConnectionProfile } from 'sql/platform/connection/common/interfaces'; import { Taskbar } from 'sql/base/browser/ui/taskbar/taskbar'; -import { KernelsDropdown, AttachToDropdown, AddCellAction, TrustedAction, RunAllCellsAction, ClearAllOutputsAction, CollapseCellsAction } from 'sql/workbench/contrib/notebook/browser/notebookActions'; +import { AddCellAction, KernelsDropdown, AttachToDropdown, TrustedAction, RunAllCellsAction, ClearAllOutputsAction, CollapseCellsAction } from 'sql/workbench/contrib/notebook/browser/notebookActions'; +import { DropdownMenuActionViewItem } from 'sql/base/browser/ui/buttonMenu/buttonMenu'; import { ISingleNotebookEditOperation } from 'sql/workbench/api/common/sqlExtHostTypes'; import { IConnectionDialogService } from 'sql/workbench/services/connection/common/connectionDialogService'; import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService'; @@ -56,6 +57,7 @@ import { NotebookInput } from 'sql/workbench/contrib/notebook/browser/models/not import { IColorTheme } from 'vs/platform/theme/common/themeService'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; export const NOTEBOOK_SELECTOR: string = 'notebook-component'; @@ -83,6 +85,7 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe private _scrollTop: number; private _navProvider: INavigationProvider; private navigationResult: nb.NavigationResult; + public previewFeaturesEnabled: boolean = false; constructor( @Inject(forwardRef(() => ChangeDetectorRef)) private _changeRef: ChangeDetectorRef, @@ -103,11 +106,15 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe @Inject(ITextFileService) private textFileService: ITextFileService, @Inject(ILogService) private readonly logService: ILogService, @Inject(ICommandService) private commandService: ICommandService, - @Inject(IAdsTelemetryService) private adstelemetryService: IAdsTelemetryService + @Inject(IAdsTelemetryService) private adstelemetryService: IAdsTelemetryService, + @Inject(IConfigurationService) private _configurationService: IConfigurationService ) { super(); this.updateProfile(); this.isLoading = true; + this._register(this._configurationService.onDidChangeConfiguration(e => { + this.previewFeaturesEnabled = this._configurationService.getValue('workbench.enablePreviewFeatures'); + })); } private updateProfile(): void { @@ -395,44 +402,114 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe } protected initActionBar(): void { - let kernelContainer = document.createElement('div'); - let kernelDropdown = this.instantiationService.createInstance(KernelsDropdown, kernelContainer, this.contextViewService, this.modelReady); - kernelDropdown.render(kernelContainer); - attachSelectBoxStyler(kernelDropdown, this.themeService); + this.previewFeaturesEnabled = this._configurationService.getValue('workbench.enablePreviewFeatures'); - let attachToContainer = document.createElement('div'); - let attachToDropdown = new AttachToDropdown(attachToContainer, this.contextViewService, this.modelReady, - this.connectionManagementService, this.connectionDialogService, this.notificationService, this.capabilitiesService); - attachToDropdown.render(attachToContainer); - attachSelectBoxStyler(attachToDropdown, this.themeService); + if (this.previewFeaturesEnabled) { + let kernelContainer = document.createElement('li'); + let kernelDropdown = this.instantiationService.createInstance(KernelsDropdown, kernelContainer, this.contextViewService, this.modelReady); + kernelDropdown.render(kernelContainer); + attachSelectBoxStyler(kernelDropdown, this.themeService); - let addCodeCellButton = new AddCellAction('notebook.AddCodeCell', localize('code', "Code"), 'notebook-button icon-add'); - addCodeCellButton.cellType = CellTypes.Code; + let attachToContainer = document.createElement('li'); + let attachToDropdown = new AttachToDropdown(attachToContainer, this.contextViewService, this.modelReady, + this.connectionManagementService, this.connectionDialogService, this.notificationService, this.capabilitiesService); + attachToDropdown.render(attachToContainer); + attachSelectBoxStyler(attachToDropdown, this.themeService); - let addTextCellButton = new AddCellAction('notebook.AddTextCell', localize('text', "Text"), 'notebook-button icon-add'); - addTextCellButton.cellType = CellTypes.Markdown; + let spacerElement = document.createElement('li'); + spacerElement.style.marginLeft = 'auto'; - this._runAllCellsAction = this.instantiationService.createInstance(RunAllCellsAction, 'notebook.runAllCells', localize('runAll', "Run Cells"), 'notebook-button icon-run-cells'); - let clearResultsButton = new ClearAllOutputsAction('notebook.ClearAllOutputs', localize('clearResults', "Clear Results"), 'notebook-button icon-clear-results'); + let addCodeCellButton = new AddCellAction('notebook.AddCodeCell', localize('codePreview', "Code cell"), 'notebook-button masked-pseudo code'); + addCodeCellButton.cellType = CellTypes.Code; - this._trustedAction = this.instantiationService.createInstance(TrustedAction, 'notebook.Trusted'); - this._trustedAction.enabled = false; + let addTextCellButton = new AddCellAction('notebook.AddTextCell', localize('textPreview', "Markdown cell"), 'notebook-button masked-pseudo markdown'); + addTextCellButton.cellType = CellTypes.Markdown; - let collapseCellsAction = this.instantiationService.createInstance(CollapseCellsAction, 'notebook.collapseCells'); - let taskbar = this.toolbar.nativeElement; - this._actionBar = new Taskbar(taskbar, { actionViewItemProvider: action => this.actionItemProvider(action as Action) }); - this._actionBar.context = this; - this._actionBar.setContent([ - { action: addCodeCellButton }, - { action: addTextCellButton }, - { element: kernelContainer }, - { element: attachToContainer }, - { action: this._trustedAction }, - { action: this._runAllCellsAction }, - { action: clearResultsButton }, - { action: collapseCellsAction } - ]); + this._runAllCellsAction = this.instantiationService.createInstance(RunAllCellsAction, 'notebook.runAllCells', localize('runAllPreview', "Run all"), 'notebook-button masked-pseudo start-outline'); + + let collapseCellsAction = this.instantiationService.createInstance(CollapseCellsAction, 'notebook.collapseCells', true); + + let clearResultsButton = new ClearAllOutputsAction('notebook.ClearAllOutputs', true); + + this._trustedAction = this.instantiationService.createInstance(TrustedAction, 'notebook.Trusted', true); + this._trustedAction.enabled = false; + + let taskbar = this.toolbar.nativeElement; + this._actionBar = new Taskbar(taskbar, { actionViewItemProvider: action => this.actionItemProvider(action as Action) }); + this._actionBar.context = this; + taskbar.classList.add('in-preview'); + + let buttonDropdownContainer = DOM.$('li.action-item'); + buttonDropdownContainer.setAttribute('role', 'presentation'); + let dropdownMenuActionViewItem = new DropdownMenuActionViewItem( + addCodeCellButton, + [addCodeCellButton, addTextCellButton], + this.contextMenuService, + undefined, + this._actionBar.actionRunner, + undefined, + 'codicon notebook-button masked-pseudo masked-pseudo-after add-new dropdown-arrow', + localize('addCell', "Cell"), + undefined + ); + dropdownMenuActionViewItem.render(buttonDropdownContainer); + dropdownMenuActionViewItem.setActionContext(this); + + this._actionBar.setContent([ + { element: buttonDropdownContainer }, + { action: this._runAllCellsAction }, + { element: Taskbar.createTaskbarSeparator() }, + { element: attachToContainer }, + { element: kernelContainer }, + { element: spacerElement }, + { action: collapseCellsAction }, + { action: clearResultsButton }, + { action: this._trustedAction }, + ]); + } else { + let kernelContainer = document.createElement('div'); + let kernelDropdown = this.instantiationService.createInstance(KernelsDropdown, kernelContainer, this.contextViewService, this.modelReady); + kernelDropdown.render(kernelContainer); + attachSelectBoxStyler(kernelDropdown, this.themeService); + + let attachToContainer = document.createElement('div'); + let attachToDropdown = new AttachToDropdown(attachToContainer, this.contextViewService, this.modelReady, + this.connectionManagementService, this.connectionDialogService, this.notificationService, this.capabilitiesService); + attachToDropdown.render(attachToContainer); + attachSelectBoxStyler(attachToDropdown, this.themeService); + + let addCodeCellButton = new AddCellAction('notebook.AddCodeCell', localize('code', "Code"), 'notebook-button icon-add'); + addCodeCellButton.cellType = CellTypes.Code; + + let addTextCellButton = new AddCellAction('notebook.AddTextCell', localize('text', "Text"), 'notebook-button icon-add'); + addTextCellButton.cellType = CellTypes.Markdown; + + this._runAllCellsAction = this.instantiationService.createInstance(RunAllCellsAction, 'notebook.runAllCells', localize('runAll', "Run Cells"), 'notebook-button icon-run-cells'); + + let clearResultsButton = new ClearAllOutputsAction('notebook.ClearAllOutputs', false); + + this._trustedAction = this.instantiationService.createInstance(TrustedAction, 'notebook.Trusted', false); + this._trustedAction.enabled = false; + + let collapseCellsAction = this.instantiationService.createInstance(CollapseCellsAction, 'notebook.collapseCells', false); + + let taskbar = this.toolbar.nativeElement; + this._actionBar = new Taskbar(taskbar, { actionViewItemProvider: action => this.actionItemProvider(action as Action) }); + this._actionBar.context = this; + + this._actionBar.setContent([ + { action: addCodeCellButton }, + { action: addTextCellButton }, + { element: kernelContainer }, + { element: attachToContainer }, + { action: this._trustedAction }, + { action: this._runAllCellsAction }, + { action: clearResultsButton }, + { action: collapseCellsAction } + ]); + } + } protected initNavSection(): void { @@ -478,7 +555,12 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe // 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.contextMenuService, this.notificationService, 'notebook-button'); + + if (action.item.id.includes('jupyter.cmd') && this.previewFeaturesEnabled) { + action.tooltip = action.label; + action.label = ''; + } + return new LabeledMenuItemActionItem(action, this.keybindingService, this.contextMenuService, this.notificationService, 'notebook-button fixed-width'); } return undefined; } diff --git a/src/sql/workbench/contrib/notebook/browser/notebook.css b/src/sql/workbench/contrib/notebook/browser/notebook.css index f5caf031b3..e55d22747d 100644 --- a/src/sql/workbench/contrib/notebook/browser/notebook.css +++ b/src/sql/workbench/contrib/notebook/browser/notebook.css @@ -6,6 +6,10 @@ margin-top: 5px; } +.notebookEditor .taskbarSeparator { + margin: 0 16px 0 -8px; +} + .notebookEditor .editor-toolbar { border-bottom-style: solid; border-width: 0px 0px 1px 0px; @@ -44,6 +48,52 @@ font-size: 13px; height: 21px; } +.notebookEditor .in-preview .actions-container .action-item .notebook-button { + display: flex; + background-size: 16px; +} + +.notebookEditor + .in-preview + .actions-container + .action-item + .notebook-button.masked-pseudo { + padding-left: 30px; +} +.notebookEditor + .in-preview + .actions-container + .action-item + .notebook-button.masked-icon { + margin-right: 0; + padding-left: 18px; + width: 16px; +} +.notebookEditor .in-preview .actions-container .action-item:last-child { + margin-right: 8px; +} +.notebookEditor + .in-preview + .actions-container + .action-item:last-child + .notebook-button { + margin-right: 0; +} +.notebookEditor + .in-preview + .actions-container + .action-item:last-child + .notebook-button.fixed-width { + margin-left: 8px; + margin-right: -28px; +} +.notebookEditor + .in-preview + .actions-container + .action-item + .notebook-button.fixed-width { + width: 34px; +} .notebookEditor .labelOnLeftContainer { min-width: 100px; @@ -54,68 +104,89 @@ vertical-align: bottom; } -.notebookEditor .notebook-button.icon-add { +.notebookEditor .in-preview .labelOnLeftContainer { + margin-right: 14px; +} + +/* non-preview */ +.notebookEditor :not(.in-preview) .notebook-button.icon-add { background-image: url("./media/light/add.svg"); } -.vs-dark .notebookEditor .notebook-button.icon-add, -.hc-black .notebookEditor .notebook-button.icon-add { +.vs-dark .notebookEditor :not(.in-preview) .notebook-button.icon-add, +.hc-black .notebookEditor :not(.in-preview) .notebook-button.icon-add { background-image: url("./media/dark/add_inverse.svg"); } -.notebookEditor .notebook-button.icon-run-cells { +.notebookEditor :not(.in-preview) .notebook-button.icon-run-cells { background-image: url("./media/light/run_cells.svg"); } -.vs-dark .notebookEditor .notebook-button.icon-run-cells, -.hc-black .notebookEditor .notebook-button.icon-run-cells { +.vs-dark .notebookEditor :not(.in-preview) .notebook-button.icon-run-cells, +.hc-black .notebookEditor :not(.in-preview) .notebook-button.icon-run-cells { background-image: url("./media/dark/run_cells_inverse.svg"); } -.notebookEditor .notebook-button.icon-trusted { +.notebookEditor :not(.in-preview) .notebook-button.icon-trusted { background-image: url("./media/light/trusted.svg"); } -.vs-dark .notebookEditor .notebook-button.icon-trusted, -.hc-black .notebookEditor .notebook-button.icon-trusted { +.vs-dark .notebookEditor :not(.in-preview) .notebook-button.icon-trusted, +.hc-black .notebookEditor :not(.in-preview) .notebook-button.icon-trusted { background-image: url("./media/dark/trusted_inverse.svg"); } - -.notebookEditor .notebook-button.icon-notTrusted { +.notebookEditor :not(.in-preview) .notebook-button.icon-notTrusted { background-image: url("./media/light/nottrusted.svg"); } -.vs-dark .notebookEditor .notebook-button.icon-notTrusted, -.hc-black .notebookEditor .notebook-button.icon-notTrusted { +.vs-dark .notebookEditor :not(.in-preview) .notebook-button.icon-notTrusted, +.hc-black .notebookEditor :not(.in-preview) .notebook-button.icon-notTrusted { background-image: url("./media/dark/nottrusted_inverse.svg"); } -.notebookEditor .notebook-button.icon-show-cells { +.notebookEditor :not(.in-preview) .notebook-button.icon-show-cells { background-image: url("./media/light/show_code.svg"); } -.vs-dark .notebookEditor .notebook-button.icon-show-cells, -.hc-black .notebookEditor .notebook-button.icon-show-cells { +.vs-dark .notebookEditor :not(.in-preview) .notebook-button.icon-show-cells, +.hc-black .notebookEditor :not(.in-preview) .notebook-button.icon-show-cells { background-image: url("./media/dark/show_code_inverse.svg"); } -.notebookEditor .notebook-button.icon-hide-cells { +.notebookEditor :not(.in-preview) .notebook-button.icon-hide-cells { background-image: url("./media/light/hide_code.svg"); } -.vs-dark .notebookEditor .notebook-button.icon-hide-cells, -.hc-black .notebookEditor .notebook-button.icon-hide-cells { +.vs-dark .notebookEditor :not(.in-preview) .notebook-button.icon-hide-cells, +.hc-black .notebookEditor :not(.in-preview) .notebook-button.icon-hide-cells { background-image: url("./media/dark/hide_code_inverse.svg"); } -.notebookEditor .notebook-button.icon-clear-results { +.notebookEditor :not(.in-preview) .notebook-button.icon-clear-results { background-image: url("./media/light/clear_results.svg"); } -.vs-dark .notebookEditor .notebook-button.icon-clear-results, -.hc-black .notebookEditor .notebook-button.icon-clear-results { +.vs-dark .notebookEditor :not(.in-preview) .notebook-button.icon-clear-results, +.hc-black + .notebookEditor + :not(.in-preview) + .notebook-button.icon-clear-results { background-image: url("./media/dark/clear_results_inverse.svg"); } +/* non-preview */ + +.notebookEditor .in-preview .carbon-taskbar.monaco-toolbar .monaco-select-box { + font-size: inherit; + border-radius: 0; + padding: 3px 22px 3px 6px; +} +.notebookEditor .in-preview .carbon-taskbar .action-item { + margin-right: 0; +} +.notebookEditor .in-preview .labelOnLeftContainer { + display: flex; + align-items: center; +} .moreActions .action-label.codicon.toggle-more { height: 20px; @@ -123,7 +194,7 @@ } .moreActions.actionhidden { - visibility: hidden + visibility: hidden; } .moreActions .monaco-action-bar { margin-left: -12px; @@ -144,12 +215,13 @@ } .monaco-workbench .notebook-action.new-notebook { - background: url('./media/light/new_notebook.svg') center center no-repeat; + background: url("./media/light/new_notebook.svg") center center no-repeat; } .vs-dark .monaco-workbench .notebook-action.new-notebook, .hc-black .monaco-workbench .notebook-action.new-notebook { - background: url('./media/dark/new_notebook_inverse.svg') center center no-repeat; + background: url("./media/dark/new_notebook_inverse.svg") center center + no-repeat; } .notebookEditor .book-nav { @@ -209,7 +281,7 @@ } .notebookEditor .hoverButton:active { - transform:scale(1.05); + transform: scale(1.05); } .notebookEditor .hoverButton .addCodeIcon, diff --git a/src/sql/workbench/contrib/notebook/browser/notebookActions.ts b/src/sql/workbench/contrib/notebook/browser/notebookActions.ts index 4777b31af9..6c2e12fa6a 100644 --- a/src/sql/workbench/contrib/notebook/browser/notebookActions.ts +++ b/src/sql/workbench/contrib/notebook/browser/notebookActions.ts @@ -31,8 +31,8 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur const msgLoading = localize('loading', "Loading kernels..."); const msgChanging = localize('changing', "Changing kernel..."); -const kernelLabel: string = localize('Kernel', "Kernel: "); -const attachToLabel: string = localize('AttachTo', "Attach To: "); +const attachToLabel: string = localize('AttachTo', "Attach to "); +const kernelLabel: string = localize('Kernel', "Kernel "); const msgLoadingContexts = localize('loadingContexts', "Loading contexts..."); const msgChangeConnection = localize('changeConnection', "Change Connection"); const msgSelectConnection = localize('selectConnection', "Select Connection"); @@ -64,13 +64,50 @@ export class AddCellAction extends Action { } } -// Action to clear outputs of all code cells. -export class ClearAllOutputsAction extends Action { - constructor( - id: string, label: string, cssClass: string - ) { - super(id, label, cssClass); +export interface ITooltipState { + label: string; + baseClass: string; + iconClass: string; + maskedIconClass: string; + shouldToggleTooltip?: boolean; +} +export abstract class TooltipFromLabelAction extends Action { + + constructor(id: string, protected state: ITooltipState) { + super(id, ''); + this.updateLabelAndIcon(); } + + private updateLabelAndIcon() { + if (this.state.shouldToggleTooltip) { + this.tooltip = this.state.label; + } else { + this.label = this.state.label; + } + let classes = this.state.baseClass ? `${this.state.baseClass} ${this.state.iconClass} ` : ''; + if (this.state.shouldToggleTooltip) { + classes += this.state.maskedIconClass; + } + this.class = classes; + } +} +// Action to clear outputs of all code cells. +export class ClearAllOutputsAction extends TooltipFromLabelAction { + private static readonly label = localize('clearResults', "Clear Results"); + private static readonly baseClass = 'notebook-button'; + private static readonly iconClass = 'icon-clear-results'; + private static readonly maskedIconClass = 'masked-icon'; + + constructor(id: string, toggleTooltip: boolean) { + super(id, { + label: ClearAllOutputsAction.label, + baseClass: ClearAllOutputsAction.baseClass, + iconClass: ClearAllOutputsAction.iconClass, + maskedIconClass: ClearAllOutputsAction.maskedIconClass, + shouldToggleTooltip: toggleTooltip + }); + } + public run(context: INotebookEditor): Promise { return context.clearAllOutputs(); } @@ -83,6 +120,7 @@ export interface IToggleableState { toggleOnLabel: string; toggleOffLabel: string; toggleOffClass: string; + maskedIconClass?: string; isOn: boolean; } @@ -99,7 +137,16 @@ export abstract class ToggleableAction extends Action { } else { this.label = this.state.isOn ? this.state.toggleOnLabel : this.state.toggleOffLabel; } - let classes = this.state.baseClass ? `${this.state.baseClass} ` : ''; + + let classes: string = ''; + + if (this.state.shouldToggleTooltip && this.state.maskedIconClass) { + //mask + classes = this.state.baseClass ? `${this.state.baseClass} ${this.state.maskedIconClass} ` : ''; + } else { + //no mask + classes = this.state.baseClass ? `${this.state.baseClass} ` : ''; + } classes += this.state.isOn ? this.state.toggleOnClass : this.state.toggleOffClass; this.class = classes; } @@ -115,20 +162,23 @@ export class TrustedAction extends ToggleableAction { private static readonly trustedLabel = localize('trustLabel', "Trusted"); private static readonly notTrustedLabel = localize('untrustLabel', "Not Trusted"); private static readonly baseClass = 'notebook-button'; + private static readonly previewTrustedCssClass = 'icon-shield'; private static readonly trustedCssClass = 'icon-trusted'; + private static readonly previewNotTrustedCssClass = 'icon-shield-x'; private static readonly notTrustedCssClass = 'icon-notTrusted'; - - // Properties + private static readonly maskedIconClass = 'masked-icon'; constructor( - id: string + id: string, toggleTooltip: boolean ) { super(id, { baseClass: TrustedAction.baseClass, toggleOnLabel: TrustedAction.trustedLabel, - toggleOnClass: TrustedAction.trustedCssClass, + toggleOnClass: toggleTooltip === true ? TrustedAction.previewTrustedCssClass : TrustedAction.trustedCssClass, toggleOffLabel: TrustedAction.notTrustedLabel, - toggleOffClass: TrustedAction.notTrustedCssClass, + toggleOffClass: toggleTooltip === true ? TrustedAction.previewNotTrustedCssClass : TrustedAction.notTrustedCssClass, + maskedIconClass: TrustedAction.maskedIconClass, + shouldToggleTooltip: toggleTooltip, isOn: false }); } @@ -177,16 +227,21 @@ export class CollapseCellsAction extends ToggleableAction { private static readonly collapseCells = localize('collapseAllCells', "Collapse Cells"); private static readonly expandCells = localize('expandAllCells', "Expand Cells"); private static readonly baseClass = 'notebook-button'; + private static readonly previewCollapseCssClass = 'icon-collapse-cells'; private static readonly collapseCssClass = 'icon-hide-cells'; + private static readonly previewExpandCssClass = 'icon-expand-cells'; private static readonly expandCssClass = 'icon-show-cells'; + private static readonly maskedIconClass = 'masked-icon'; - constructor(id: string) { + constructor(id: string, toggleTooltip: boolean) { super(id, { baseClass: CollapseCellsAction.baseClass, toggleOnLabel: CollapseCellsAction.expandCells, - toggleOnClass: CollapseCellsAction.expandCssClass, + toggleOnClass: toggleTooltip === true ? CollapseCellsAction.previewExpandCssClass : CollapseCellsAction.expandCssClass, toggleOffLabel: CollapseCellsAction.collapseCells, - toggleOffClass: CollapseCellsAction.collapseCssClass, + toggleOffClass: toggleTooltip === true ? CollapseCellsAction.previewCollapseCssClass : CollapseCellsAction.collapseCssClass, + maskedIconClass: CollapseCellsAction.maskedIconClass, + shouldToggleTooltip: toggleTooltip, isOn: false }); } diff --git a/src/sql/workbench/contrib/notebook/browser/notebookStyles.ts b/src/sql/workbench/contrib/notebook/browser/notebookStyles.ts index 347f613008..38f32f6bf9 100644 --- a/src/sql/workbench/contrib/notebook/browser/notebookStyles.ts +++ b/src/sql/workbench/contrib/notebook/browser/notebookStyles.ts @@ -8,7 +8,7 @@ import { registerThemingParticipant, IColorTheme, ICssStyleCollector } from 'vs/ import { SIDE_BAR_BACKGROUND, EDITOR_GROUP_HEADER_TABS_BACKGROUND } from 'vs/workbench/common/theme'; import { activeContrastBorder, contrastBorder, buttonBackground, textLinkForeground, textLinkActiveForeground, textPreformatForeground, textBlockQuoteBackground, textBlockQuoteBorder, buttonForeground, editorBackground, lighten } from 'vs/platform/theme/common/colorRegistry'; import { editorLineHighlight, editorLineHighlightBorder } from 'vs/editor/common/view/editorColorRegistry'; -import { cellBorder, markdownEditorBackground, splitBorder, codeEditorBackground, codeEditorBackgroundActive, codeEditorLineNumber, codeEditorToolbarIcon, codeEditorToolbarBackground, codeEditorToolbarBorder, toolbarBackground, toolbarIcon, toolbarBottomBorder } from 'sql/platform/theme/common/colorRegistry'; +import { cellBorder, notebookToolbarIcon, notebookToolbarLines, buttonMenuArrow, dropdownArrow, markdownEditorBackground, splitBorder, codeEditorBackground, codeEditorBackgroundActive, codeEditorLineNumber, codeEditorToolbarIcon, codeEditorToolbarBackground, codeEditorToolbarBorder, toolbarBackground, toolbarIcon, toolbarBottomBorder } from 'sql/platform/theme/common/colorRegistry'; import { IDisposable } from 'vs/base/common/lifecycle'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { BareResultsGridInfo, getBareResultsGridInfoStyles } from 'sql/workbench/contrib/query/browser/queryResultsEditor'; @@ -207,6 +207,25 @@ export function registerNotebookThemes(overrideEditorThemeSetting: boolean, conf ${getBareResultsGridInfoStyles(rawOptions)} }`); + //Notebook toolbar masked icons + const notebookToolbarIconColor = theme.getColor(notebookToolbarIcon); + if (notebookToolbarIconColor) { + collector.addRule(`.notebookEditor .notebook-button.masked-icon { background-color: ${notebookToolbarIconColor};}`); + collector.addRule(`.notebookEditor .notebook-button.masked-pseudo:before { background-color: ${notebookToolbarIconColor};}`); + } + const notebookToolbarLinesColor = theme.getColor(notebookToolbarLines); + if (notebookToolbarLinesColor) { + collector.addRule(`.notebookEditor .editor-toolbar.actionbar-container { border-bottom-color: ${notebookToolbarLinesColor}!important;}`); + collector.addRule(`.notebookEditor .taskbarSeparator { background-color: ${notebookToolbarLinesColor};}`); + } + const dropdownArrowColor = theme.getColor(dropdownArrow); + if (dropdownArrowColor) { + collector.addRule(`.monaco-workbench .notebookEditor .select-container:after { color: ${dropdownArrowColor};}`); + } + const buttonMenuArrowColor = theme.getColor(buttonMenuArrow); + if (buttonMenuArrowColor) { + collector.addRule(`.notebookEditor .notebook-button.masked-pseudo-after:after { background-color: ${buttonMenuArrowColor};}`); + } // Cell border const cellBorderColor = theme.getColor(cellBorder); diff --git a/src/sql/workbench/contrib/notebook/test/browser/notebookActions.test.ts b/src/sql/workbench/contrib/notebook/test/browser/notebookActions.test.ts index 38bf4362bc..7598ea75d7 100644 --- a/src/sql/workbench/contrib/notebook/test/browser/notebookActions.test.ts +++ b/src/sql/workbench/contrib/notebook/test/browser/notebookActions.test.ts @@ -40,7 +40,7 @@ suite('Notebook Actions', function (): void { }); test('Clear All Outputs Action', async function (): Promise { - let action = new ClearAllOutputsAction('TestId', 'TestLabel', 'TestClass'); + let action = new ClearAllOutputsAction('TestId', true); // Normal use case let mockNotebookComponent = TypeMoq.Mock.ofType(NotebookComponentStub); @@ -63,7 +63,7 @@ suite('Notebook Actions', function (): void { let mockNotification = TypeMoq.Mock.ofType(TestNotificationService); mockNotification.setup(n => n.notify(TypeMoq.It.isAny())); - let action = new TrustedAction('TestId'); + let action = new TrustedAction('TestId', true); assert.strictEqual(action.trusted, false, 'Should not be trusted by default'); // Normal use case @@ -105,7 +105,7 @@ suite('Notebook Actions', function (): void { }); test('Collapse Cells Action', async function (): Promise { - let action = new CollapseCellsAction('TestId'); + let action = new CollapseCellsAction('TestId', true); assert.strictEqual(action.isCollapsed, false, 'Should not be collapsed by default'); let context = {