diff --git a/src/sql/media/icons/chevron_down.svg b/src/sql/media/icons/chevron_down.svg new file mode 100644 index 0000000000..3a454b37ab --- /dev/null +++ b/src/sql/media/icons/chevron_down.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/sql/media/icons/chevron_down_inverse.svg b/src/sql/media/icons/chevron_down_inverse.svg new file mode 100644 index 0000000000..b09a9f4e6e --- /dev/null +++ b/src/sql/media/icons/chevron_down_inverse.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/sql/media/icons/chevron_up.svg b/src/sql/media/icons/chevron_up.svg new file mode 100644 index 0000000000..b3fbc2bbe6 --- /dev/null +++ b/src/sql/media/icons/chevron_up.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/sql/media/icons/chevron_up_inverse.svg b/src/sql/media/icons/chevron_up_inverse.svg new file mode 100644 index 0000000000..f59d76d752 --- /dev/null +++ b/src/sql/media/icons/chevron_up_inverse.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/sql/media/icons/close-blue.svg b/src/sql/media/icons/close-blue.svg new file mode 100644 index 0000000000..44dcff7ea9 --- /dev/null +++ b/src/sql/media/icons/close-blue.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/sql/media/icons/common-icons.css b/src/sql/media/icons/common-icons.css index b8c6d54340..1a0b985de0 100644 --- a/src/sql/media/icons/common-icons.css +++ b/src/sql/media/icons/common-icons.css @@ -278,6 +278,119 @@ background-image: url('stop_inverse.svg') } +/* Notebook cells */ +.codicon.toolbarIconRunInactive { + background-image: url('execute_cell_grey.svg'); +} +.codicon.toolbarIconRun { + background-image: url('execute_cell.svg'); +} +.codicon.toolbarIconRunError { + background-image: url('execute_cell_error.svg'); +} +.codicon.toolbarIconStop { + background-image: url('stop_cell_solidanimation.svg'); +} +.vs-dark .codicon.toolbarIconRunInactive { + background-image: url('execute_cell_dark.svg'); +} +.vs-dark .codicon.toolbarIconRun { + background-image: url('execute_cell_white.svg'); +} +.hc-black .codicon.toolbarIconRunInactive { + background-image: url('execute_cell_hc.svg'); +} +.hc-black .codicon.toolbarIconRun { + background-image: url('execute_cell_orange_hc.svg'); +} +.vs-dark .codicon.toolbarIconStop, +.hc-black .codicon.toolbarIconStop { + background-image: url('stop_cell_solidanimation_inverse.svg'); +} + +.codicon.arrow-up { + background-image: url("chevron_up.svg"); +} +.vs-dark .codicon.arrow-up, +.hc-black .codicon.arrow-up { + background-image: url("chevron_up_inverse.svg"); +} + +.codicon.arrow-down { + background-image: url("chevron_down.svg"); +} +.vs-dark .codicon.arrow-down, +.hc-black .codicon.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'); +} + +/* Cell toolbar icons */ +.cell-tool-close { + background-image: url('close-blue.svg'); +} +.cell-tool-edit { + background-image: url('edit.svg'); +} +.cell-tool-add { + background-image: url('new-blue.svg'); +} +.cell-tool-move-up { + background-image: url('down-arrow-blue.svg'); + transform: scale(-1); +} +.cell-tool-move-down { + background-image: url('down-arrow-blue.svg'); +} +.cell-tool-delete { + background-image: url('garbage-can-blue.svg'); +} +.cell-tool-more { + background-image: url('ellipsis-blue.svg'); +} + + .small { width: 16px; height: 16px; diff --git a/src/sql/media/icons/down-arrow-blue.svg b/src/sql/media/icons/down-arrow-blue.svg new file mode 100644 index 0000000000..fc472f1410 --- /dev/null +++ b/src/sql/media/icons/down-arrow-blue.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/sql/media/icons/ellipsis-blue.svg b/src/sql/media/icons/ellipsis-blue.svg new file mode 100644 index 0000000000..0c6d78f456 --- /dev/null +++ b/src/sql/media/icons/ellipsis-blue.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/sql/media/icons/execute_cell.svg b/src/sql/media/icons/execute_cell.svg new file mode 100644 index 0000000000..d83854d930 --- /dev/null +++ b/src/sql/media/icons/execute_cell.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/sql/media/icons/execute_cell_dark.svg b/src/sql/media/icons/execute_cell_dark.svg new file mode 100644 index 0000000000..b4d7cac08c --- /dev/null +++ b/src/sql/media/icons/execute_cell_dark.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/sql/media/icons/execute_cell_error.svg b/src/sql/media/icons/execute_cell_error.svg new file mode 100644 index 0000000000..03de5943ed --- /dev/null +++ b/src/sql/media/icons/execute_cell_error.svg @@ -0,0 +1 @@ +execute_cell_error \ No newline at end of file diff --git a/src/sql/media/icons/execute_cell_grey.svg b/src/sql/media/icons/execute_cell_grey.svg new file mode 100644 index 0000000000..6e4c627a97 --- /dev/null +++ b/src/sql/media/icons/execute_cell_grey.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/sql/media/icons/execute_cell_hc.svg b/src/sql/media/icons/execute_cell_hc.svg new file mode 100644 index 0000000000..d1ffb0988f --- /dev/null +++ b/src/sql/media/icons/execute_cell_hc.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/sql/media/icons/execute_cell_orange_hc.svg b/src/sql/media/icons/execute_cell_orange_hc.svg new file mode 100644 index 0000000000..afb33e8cdf --- /dev/null +++ b/src/sql/media/icons/execute_cell_orange_hc.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/sql/media/icons/execute_cell_white.svg b/src/sql/media/icons/execute_cell_white.svg new file mode 100644 index 0000000000..0af50fc2b8 --- /dev/null +++ b/src/sql/media/icons/execute_cell_white.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/sql/media/icons/garbage-can-blue.svg b/src/sql/media/icons/garbage-can-blue.svg new file mode 100644 index 0000000000..ab482a4fff --- /dev/null +++ b/src/sql/media/icons/garbage-can-blue.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/sql/media/icons/new-blue.svg b/src/sql/media/icons/new-blue.svg new file mode 100644 index 0000000000..0ebe28953d --- /dev/null +++ b/src/sql/media/icons/new-blue.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/sql/media/icons/stop_cell_solidanimation.svg b/src/sql/media/icons/stop_cell_solidanimation.svg new file mode 100644 index 0000000000..38b4eaac4c --- /dev/null +++ b/src/sql/media/icons/stop_cell_solidanimation.svg @@ -0,0 +1,16 @@ + + +stop_cell_solidanimation + + + + + + \ No newline at end of file diff --git a/src/sql/media/icons/stop_cell_solidanimation_inverse.svg b/src/sql/media/icons/stop_cell_solidanimation_inverse.svg new file mode 100644 index 0000000000..1a3943d55d --- /dev/null +++ b/src/sql/media/icons/stop_cell_solidanimation_inverse.svg @@ -0,0 +1,16 @@ + + +stop_cell_solidanimation_inverse + + + + + + \ No newline at end of file diff --git a/src/sql/media/icons/toolbar-bold.svg b/src/sql/media/icons/toolbar-bold.svg new file mode 100644 index 0000000000..8cb47f4d5b --- /dev/null +++ b/src/sql/media/icons/toolbar-bold.svg @@ -0,0 +1,4 @@ + +bold + + diff --git a/src/sql/media/icons/toolbar-code.svg b/src/sql/media/icons/toolbar-code.svg new file mode 100644 index 0000000000..44ede8af94 --- /dev/null +++ b/src/sql/media/icons/toolbar-code.svg @@ -0,0 +1,4 @@ + +preformatted + + diff --git a/src/sql/media/icons/toolbar-highlight.svg b/src/sql/media/icons/toolbar-highlight.svg new file mode 100644 index 0000000000..e235385408 --- /dev/null +++ b/src/sql/media/icons/toolbar-highlight.svg @@ -0,0 +1,4 @@ + +highlight + + diff --git a/src/sql/media/icons/toolbar-image.svg b/src/sql/media/icons/toolbar-image.svg new file mode 100644 index 0000000000..df9c8276b0 --- /dev/null +++ b/src/sql/media/icons/toolbar-image.svg @@ -0,0 +1,4 @@ + +add inline image + + diff --git a/src/sql/media/icons/toolbar-italic.svg b/src/sql/media/icons/toolbar-italic.svg new file mode 100644 index 0000000000..6cfd388eb5 --- /dev/null +++ b/src/sql/media/icons/toolbar-italic.svg @@ -0,0 +1,4 @@ + +italics + + diff --git a/src/sql/media/icons/toolbar-link.svg b/src/sql/media/icons/toolbar-link.svg new file mode 100644 index 0000000000..d4da1b11e2 --- /dev/null +++ b/src/sql/media/icons/toolbar-link.svg @@ -0,0 +1,4 @@ + +link + + diff --git a/src/sql/media/icons/toolbar-list.svg b/src/sql/media/icons/toolbar-list.svg new file mode 100644 index 0000000000..59bb14a7ce --- /dev/null +++ b/src/sql/media/icons/toolbar-list.svg @@ -0,0 +1,4 @@ + +unordered list + + diff --git a/src/sql/media/icons/toolbar-ordered-list.svg b/src/sql/media/icons/toolbar-ordered-list.svg new file mode 100644 index 0000000000..60158dff0c --- /dev/null +++ b/src/sql/media/icons/toolbar-ordered-list.svg @@ -0,0 +1,4 @@ + +Ordered list + + diff --git a/src/sql/media/icons/toolbar-preview-toggle-off.svg b/src/sql/media/icons/toolbar-preview-toggle-off.svg new file mode 100644 index 0000000000..b5a8538ea6 --- /dev/null +++ b/src/sql/media/icons/toolbar-preview-toggle-off.svg @@ -0,0 +1,4 @@ + +markdown preview toggle - off + + diff --git a/src/sql/media/icons/toolbar-preview-toggle-on.svg b/src/sql/media/icons/toolbar-preview-toggle-on.svg new file mode 100644 index 0000000000..9294a58edc --- /dev/null +++ b/src/sql/media/icons/toolbar-preview-toggle-on.svg @@ -0,0 +1,5 @@ + +markdown preview toggle - on + + + diff --git a/src/sql/platform/theme/common/colorRegistry.ts b/src/sql/platform/theme/common/colorRegistry.ts index f6814a52f0..296055663c 100644 --- a/src/sql/platform/theme/common/colorRegistry.ts +++ b/src/sql/platform/theme/common/colorRegistry.ts @@ -41,4 +41,20 @@ export const gradientOne = registerColor('gradientOne', { light: '#f0f0f0', dark export const gradientTwo = registerColor('gradientTwo', { light: gradientTwoColorOne, dark: gradientTwoColorTwo, hc: gradientTwoColorTwo }, nls.localize('gradientTwo', "The bottom color for the banner image gradient")); 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 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 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")); +// 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 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 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/cellToolbar.component.html b/src/sql/workbench/contrib/notebook/browser/cellViews/cellToolbar.component.html new file mode 100644 index 0000000000..43bea07c7f --- /dev/null +++ b/src/sql/workbench/contrib/notebook/browser/cellViews/cellToolbar.component.html @@ -0,0 +1,15 @@ + + diff --git a/src/sql/workbench/contrib/notebook/browser/cellViews/cellToolbar.component.ts b/src/sql/workbench/contrib/notebook/browser/cellViews/cellToolbar.component.ts new file mode 100644 index 0000000000..28e07d8aa1 --- /dev/null +++ b/src/sql/workbench/contrib/notebook/browser/cellViews/cellToolbar.component.ts @@ -0,0 +1,26 @@ +/*--------------------------------------------------------------------------------------------- + * 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!./cellToolbar'; + +import { Component } from '@angular/core'; +import { localize } from 'vs/nls'; + +export const CELL_TOOLBAR_SELECTOR: string = 'cell-toolbar-component'; + +@Component({ + selector: CELL_TOOLBAR_SELECTOR, + templateUrl: decodeURI(require.toUrl('./cellToolbar.component.html')) +}) +export class CellToolbarComponent { + public buttonEdit = localize('buttonEdit', "Edit"); + public buttonClose = localize('buttonClose', "Close"); + public buttonAdd = localize('buttonAdd', "Add new cell"); + public buttonMoveDown = localize('buttonMoveDown', "Move cell down"); + public buttonMoveUp = localize('buttonMoveUp', "Move cell up"); + public buttonDelete = localize('buttonDelete', "Delete cell"); + + constructor() { + } +} diff --git a/src/sql/workbench/contrib/notebook/browser/cellViews/cellToolbar.css b/src/sql/workbench/contrib/notebook/browser/cellViews/cellToolbar.css new file mode 100644 index 0000000000..68bf5e2cf9 --- /dev/null +++ b/src/sql/workbench/contrib/notebook/browser/cellViews/cellToolbar.css @@ -0,0 +1,47 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +cell-toolbar-component { + position: absolute; + left: 25px; + top: -21px; +} + +cell-toolbar-component ul { + display: inline-block; + list-style: none; + margin: 0; + padding: 5px 10px 0 10px; +} + +cell-toolbar-component li { + display: inline-block; + margin-right: 4px; + text-align: center; +} + +cell-toolbar-component li:last-child { + margin-right: 0; +} + +cell-toolbar-component li a { + background: 50% 50% no-repeat; + display: block; + height: 16px; + width: 16px; +} + +cell-toolbar-component li div { + background: 50% 50% no-repeat; + height: 16px; + width: 16px; +} + +cell-toolbar-component .offscreen { + height: 1px; + text-indent: -999999px; + margin-top: -1px; + position: absolute; +} diff --git a/src/sql/workbench/contrib/notebook/browser/cellViews/code.css b/src/sql/workbench/contrib/notebook/browser/cellViews/code.css index 3a51cbf1f1..d1ebc48294 100644 --- a/src/sql/workbench/contrib/notebook/browser/cellViews/code.css +++ b/src/sql/workbench/contrib/notebook/browser/cellViews/code.css @@ -9,14 +9,20 @@ code-component { display: block; } +.notebook-cell:not(.active) code-component .toolbar { + border-right-color: transparent!important; +} + code-component .toolbar { + border-right-style: solid; border-right-width: 1px; - flex: 0 0 auto; + box-sizing: border-box; display: flex; + flex: 0 0 auto; flex-flow:column; - width: 40px; min-height: 40px; - orientation: portrait + orientation: portrait; + width: 52px; } code-component .toolbar.markdown { @@ -29,36 +35,37 @@ code-component .toolbar .carbon-taskbar { margin-top: 5px; } -code-component .toolbarIconRun { +code-component .toolbar .codicon { height: 20px; - background-image: url('./media/light/execute_cell.svg'); padding-bottom: 10px; } -.vs-dark code-component .toolbarIconRun, -.hc-black code-component .toolbarIconRun { - background-image: url('./media/dark/execute_cell_inverse.svg'); +.notebook-cell:not(.active):hover code-component .toolbarIconRun { + background-image: url('./media/execute_cell_grey.svg'); } - -code-component .toolbarIconRunError { - height: 20px; - background-image: url('./media/light/execute_cell_error.svg'); - padding-bottom: 10px; +.vs-dark .notebook-cell:not(.active):hover code-component .toolbarIconRun { + background-image: url('./media/execute_cell_dark.svg'); } - -code-component .toolbarIconStop { - height: 20px; - background-image: url('./media/light/stop_cell_solidanimation.svg'); - padding-bottom: 10px; -} - -.vs-dark code-component .toolbarIconStop, -.hc-black code-component .toolbarIconStop { - background-image: url('./media/dark/stop_cell_solidanimation_inverse.svg'); +.hc-black .notebook-cell:not(.active):hover code-component .toolbarIconRun { + background-image: url('./media/execute_cell_hc.svg'); } code-component .editor { - padding: 5px 0px 5px 0px + margin: 14px 0px 5px 0px +} + +code-cell-component code-component .monaco-editor .margin-view-overlays .line-numbers { + left: 0!important; +} +code-cell-component code-component .monaco-scrollable-element.editor-scrollable.vs { + left: 40px!important; +} + +code-cell-component .monaco-editor .margin, +code-cell-component code-component .monaco-editor, +code-cell-component code-component .monaco-editor-background, +code-cell-component code-component .monaco-editor .inputarea.ime-input { + background-color: transparent; } /* overview ruler */ @@ -84,7 +91,7 @@ code-component .carbon-taskbar .codicon.hideIcon { padding-left: 0px; padding-top: 6px; font-family: monospace; - font-size: 12px; + font-size: 14px; } code-component .carbon-taskbar .codicon.hideIcon.execCountTen { @@ -95,10 +102,6 @@ code-component .carbon-taskbar .codicon.hideIcon.execCountHundred { margin-left: -6px; } -code-component .carbon-taskbar.monaco-toolbar .monaco-action-bar.animated .actions-container { - padding-left: 10px -} - code-component .hide-component-button { height: 16px; width: 100%; @@ -106,23 +109,5 @@ code-component .hide-component-button { border-width: 0px; background-repeat: no-repeat; background-position: center; - background-color: inherit; -} - -code-component .hide-component-button.icon-hide-cell { - background-image: url("./media/light/chevron_up.svg"); -} - -code-component .hide-component-button.icon-show-cell { - background-image: url("./media/light/chevron_down.svg"); -} - -.vs-dark code-component .hide-component-button.icon-hide-cell, -.hc-black code-component .hide-component-button.icon-hide-cell { - background-image: url("./media/dark/chevron_up_inverse.svg"); -} - -.vs-dark code-component .hide-component-button.icon-show-cell, -.hc-black code-component .hide-component-button.icon-show-cell { - background-image: url("./media/dark/chevron_down_inverse.svg"); + background-color: transparent; } diff --git a/src/sql/workbench/contrib/notebook/browser/cellViews/collapse.component.html b/src/sql/workbench/contrib/notebook/browser/cellViews/collapse.component.html index 0a5d144eb1..73fcf69d1e 100644 --- a/src/sql/workbench/contrib/notebook/browser/cellViews/collapse.component.html +++ b/src/sql/workbench/contrib/notebook/browser/cellViews/collapse.component.html @@ -5,5 +5,5 @@ *--------------------------------------------------------------------------------------------*/ -->
- +
diff --git a/src/sql/workbench/contrib/notebook/browser/cellViews/collapse.component.ts b/src/sql/workbench/contrib/notebook/browser/cellViews/collapse.component.ts index 6a9ef95efa..32745c21d0 100644 --- a/src/sql/workbench/contrib/notebook/browser/cellViews/collapse.component.ts +++ b/src/sql/workbench/contrib/notebook/browser/cellViews/collapse.component.ts @@ -20,10 +20,10 @@ export class CollapseComponent extends CellView implements OnInit, OnChanges { @ViewChild('collapseCellButton', { read: ElementRef }) private collapseCellButtonElement: ElementRef; private readonly expandButtonTitle = localize('expandCellContents', "Expand code cell contents"); - private readonly expandButtonClass = 'icon-show-cell'; + private readonly expandButtonClass = 'arrow-down'; private readonly collapseButtonTitle = localize('collapseCellContents', "Collapse code cell contents"); - private readonly collapseButtonClass = 'icon-hide-cell'; + private readonly collapseButtonClass = 'arrow-up'; @Input() cellModel: ICellModel; @Input() activeCellId: string; diff --git a/src/sql/workbench/contrib/notebook/browser/cellViews/markdownToolbar.component.html b/src/sql/workbench/contrib/notebook/browser/cellViews/markdownToolbar.component.html new file mode 100644 index 0000000000..3a80f4cc3a --- /dev/null +++ b/src/sql/workbench/contrib/notebook/browser/cellViews/markdownToolbar.component.html @@ -0,0 +1,7 @@ + + diff --git a/src/sql/workbench/contrib/notebook/browser/cellViews/markdownToolbar.component.ts b/src/sql/workbench/contrib/notebook/browser/cellViews/markdownToolbar.component.ts new file mode 100644 index 0000000000..ed9cf102ab --- /dev/null +++ b/src/sql/workbench/contrib/notebook/browser/cellViews/markdownToolbar.component.ts @@ -0,0 +1,67 @@ +/*--------------------------------------------------------------------------------------------- + * 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!./markdownToolbar'; +import { Component, Input, Inject, ViewChild, ElementRef } from '@angular/core'; +import { localize } from 'vs/nls'; +import { ICellModel } from 'sql/workbench/services/notebook/browser/models/modelInterfaces'; +import { Taskbar } from 'sql/base/browser/ui/taskbar/taskbar'; +import { TransformMarkdownAction, MarkdownButtonType } from 'sql/workbench/contrib/notebook/browser/markdownToolbarActions'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; + +export const MARKDOWN_TOOLBAR_SELECTOR: string = 'markdown-toolbar-component'; + +@Component({ + selector: MARKDOWN_TOOLBAR_SELECTOR, + templateUrl: decodeURI(require.toUrl('./markdownToolbar.component.html')) +}) +export class MarkdownToolbarComponent { + @ViewChild('mdtoolbar', { read: ElementRef }) private mdtoolbar: ElementRef; + + public buttonBold = localize('buttonBold', "Bold"); + public buttonItalic = localize('buttonItalic', "Italic"); + public buttonHighlight = localize('buttonHighlight', "Highlight"); + public buttonCode = localize('buttonCode', "Code"); + public buttonLink = localize('buttonLink', "Link"); + public buttonList = localize('buttonList', "List"); + public buttonOrderedList = localize('buttonOrderedList', "Ordered list"); + public buttonImage = localize('buttonImage', "Image"); + public buttonPreview = localize('buttonPreview', "Markdown preview toggle - off"); + + @Input() public cellModel: ICellModel; + private _actionBar: Taskbar; + + constructor( + @Inject(IInstantiationService) private _instantiationService: IInstantiationService + ) { } + + ngOnInit() { + this.initActionBar(); + } + + 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 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); + let listButton = this._instantiationService.createInstance(TransformMarkdownAction, 'notebook.listText', '', 'list', this.buttonList, this.cellModel, MarkdownButtonType.UNORDERED_LIST); + let orderedListButton = this._instantiationService.createInstance(TransformMarkdownAction, 'notebook.orderedText', '', 'ordered-list', this.buttonOrderedList, this.cellModel, MarkdownButtonType.ORDERED_LIST); + let imageButton = this._instantiationService.createInstance(TransformMarkdownAction, 'notebook.imageText', '', 'insert-image', this.buttonImage, this.cellModel, MarkdownButtonType.IMAGE); + + let taskbar = this.mdtoolbar.nativeElement; + this._actionBar = new Taskbar(taskbar); + this._actionBar.context = this; + this._actionBar.setContent([ + { action: boldButton }, + { action: italicButton }, + { action: highlightButton }, + { action: codeButton }, + { action: linkButton }, + { action: listButton }, + { action: orderedListButton }, + { action: imageButton } + ]); + } +} diff --git a/src/sql/workbench/contrib/notebook/browser/cellViews/markdownToolbar.css b/src/sql/workbench/contrib/notebook/browser/cellViews/markdownToolbar.css new file mode 100644 index 0000000000..3a37406edd --- /dev/null +++ b/src/sql/workbench/contrib/notebook/browser/cellViews/markdownToolbar.css @@ -0,0 +1,89 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +/* Resets */ +.markdown-toolbar .carbon-taskbar li a.action-label { + margin: 0; + padding: 0; +} +.markdown-toolbar .carbon-taskbar.monaco-toolbar .monaco-action-bar.animated .actions-container { + padding: 0; +} + + +.markdown-toolbar { + border-bottom-width: 1px; + border-bottom-style: solid; + display: block; + list-style: none; + margin: 0; + padding: 4px 16px; +} +.markdown-toolbar .carbon-taskbar li.action-item { + display: inline-block; + margin-right: 14px; +} +.markdown-toolbar .carbon-taskbar li:nth-child(1) { + margin-right: 9px; +} +.markdown-toolbar .carbon-taskbar li:nth-child(2) { + margin-right: 9px; +} +.markdown-toolbar .carbon-taskbar li a { + display: inline-block; + height: 20px; + width: 20px; + -webkit-mask-position: center; + -webkit-mask-repeat: no-repeat; + mask-position: center; + mask-repeat: no-repeat; +} + +.markdown-toolbar li a.codicon.bold { + -webkit-mask-size: 50% 100%; + mask-size: 50% 100%; +} +.markdown-toolbar li a.codicon.italic { + -webkit-mask-size: 60% 100%; + mask-size: 60% 100%; +} +.markdown-toolbar li a.codicon.highlight { + -webkit-mask-size: 65% 100%; + mask-size: 65% 100%; +} +.markdown-toolbar li a.codicon.code { + -webkit-mask-size: 88% 100%; + mask-size: 88% 100%; +} +.markdown-toolbar li a.codicon.insert-link { + -webkit-mask-size: 80% 100%; + mask-size: 80% 100%; +} +.markdown-toolbar li a.codicon.list { + -webkit-mask-size: 80% 100%; + mask-size: 80% 100%; +} +.markdown-toolbar li a.codicon.ordered-list { + -webkit-mask-size: 86% 100%; + mask-size: 86% 100%; +} +.markdown-toolbar li a.codicon.insertimage { + -webkit-mask-size: 86% 100%; + mask-size: 86% 100%; +} +.markdown-toolbar li a.codicon.split-toggle-on { + -webkit-mask-size: 75% 100%; + mask-size: 75% 100%; +} +.markdown-toolbar li a.codicon.split-toggle-off { + -webkit-mask-size: 75% 100%; + mask-size: 75% 100%; +} + +text-cell-component .offscreen { + height: 1px; + margin-top: -1px; + position: absolute; + text-indent: -999999px; +} diff --git a/src/sql/workbench/contrib/notebook/browser/cellViews/media/execute_cell_dark.svg b/src/sql/workbench/contrib/notebook/browser/cellViews/media/execute_cell_dark.svg new file mode 100644 index 0000000000..b4d7cac08c --- /dev/null +++ b/src/sql/workbench/contrib/notebook/browser/cellViews/media/execute_cell_dark.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/sql/workbench/contrib/notebook/browser/cellViews/media/execute_cell_grey.svg b/src/sql/workbench/contrib/notebook/browser/cellViews/media/execute_cell_grey.svg new file mode 100644 index 0000000000..6e4c627a97 --- /dev/null +++ b/src/sql/workbench/contrib/notebook/browser/cellViews/media/execute_cell_grey.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/sql/workbench/contrib/notebook/browser/cellViews/media/execute_cell_hc.svg b/src/sql/workbench/contrib/notebook/browser/cellViews/media/execute_cell_hc.svg new file mode 100644 index 0000000000..d1ffb0988f --- /dev/null +++ b/src/sql/workbench/contrib/notebook/browser/cellViews/media/execute_cell_hc.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/sql/workbench/contrib/notebook/browser/cellViews/media/light/execute_cell.svg b/src/sql/workbench/contrib/notebook/browser/cellViews/media/light/execute_cell.svg index 2a8f889c1e..9b4c857179 100644 --- a/src/sql/workbench/contrib/notebook/browser/cellViews/media/light/execute_cell.svg +++ b/src/sql/workbench/contrib/notebook/browser/cellViews/media/light/execute_cell.svg @@ -1 +1 @@ -execute_cell \ No newline at end of file +execute_cell diff --git a/src/sql/workbench/contrib/notebook/browser/cellViews/media/light/execute_cell_grey.svg b/src/sql/workbench/contrib/notebook/browser/cellViews/media/light/execute_cell_grey.svg new file mode 100644 index 0000000000..e1d4ff5773 --- /dev/null +++ b/src/sql/workbench/contrib/notebook/browser/cellViews/media/light/execute_cell_grey.svg @@ -0,0 +1 @@ +execute_cell diff --git a/src/sql/workbench/contrib/notebook/browser/cellViews/media/toolbar-bold.svg b/src/sql/workbench/contrib/notebook/browser/cellViews/media/toolbar-bold.svg new file mode 100644 index 0000000000..8cb47f4d5b --- /dev/null +++ b/src/sql/workbench/contrib/notebook/browser/cellViews/media/toolbar-bold.svg @@ -0,0 +1,4 @@ + +bold + + diff --git a/src/sql/workbench/contrib/notebook/browser/cellViews/media/toolbar-code.svg b/src/sql/workbench/contrib/notebook/browser/cellViews/media/toolbar-code.svg new file mode 100644 index 0000000000..44ede8af94 --- /dev/null +++ b/src/sql/workbench/contrib/notebook/browser/cellViews/media/toolbar-code.svg @@ -0,0 +1,4 @@ + +preformatted + + diff --git a/src/sql/workbench/contrib/notebook/browser/cellViews/media/toolbar-highlight.svg b/src/sql/workbench/contrib/notebook/browser/cellViews/media/toolbar-highlight.svg new file mode 100644 index 0000000000..e235385408 --- /dev/null +++ b/src/sql/workbench/contrib/notebook/browser/cellViews/media/toolbar-highlight.svg @@ -0,0 +1,4 @@ + +highlight + + diff --git a/src/sql/workbench/contrib/notebook/browser/cellViews/media/toolbar-image.svg b/src/sql/workbench/contrib/notebook/browser/cellViews/media/toolbar-image.svg new file mode 100644 index 0000000000..df9c8276b0 --- /dev/null +++ b/src/sql/workbench/contrib/notebook/browser/cellViews/media/toolbar-image.svg @@ -0,0 +1,4 @@ + +add inline image + + diff --git a/src/sql/workbench/contrib/notebook/browser/cellViews/media/toolbar-italic.svg b/src/sql/workbench/contrib/notebook/browser/cellViews/media/toolbar-italic.svg new file mode 100644 index 0000000000..6cfd388eb5 --- /dev/null +++ b/src/sql/workbench/contrib/notebook/browser/cellViews/media/toolbar-italic.svg @@ -0,0 +1,4 @@ + +italics + + diff --git a/src/sql/workbench/contrib/notebook/browser/cellViews/media/toolbar-link.svg b/src/sql/workbench/contrib/notebook/browser/cellViews/media/toolbar-link.svg new file mode 100644 index 0000000000..d4da1b11e2 --- /dev/null +++ b/src/sql/workbench/contrib/notebook/browser/cellViews/media/toolbar-link.svg @@ -0,0 +1,4 @@ + +link + + diff --git a/src/sql/workbench/contrib/notebook/browser/cellViews/media/toolbar-list.svg b/src/sql/workbench/contrib/notebook/browser/cellViews/media/toolbar-list.svg new file mode 100644 index 0000000000..59bb14a7ce --- /dev/null +++ b/src/sql/workbench/contrib/notebook/browser/cellViews/media/toolbar-list.svg @@ -0,0 +1,4 @@ + +unordered list + + diff --git a/src/sql/workbench/contrib/notebook/browser/cellViews/media/toolbar-ordered-list.svg b/src/sql/workbench/contrib/notebook/browser/cellViews/media/toolbar-ordered-list.svg new file mode 100644 index 0000000000..60158dff0c --- /dev/null +++ b/src/sql/workbench/contrib/notebook/browser/cellViews/media/toolbar-ordered-list.svg @@ -0,0 +1,4 @@ + +Ordered list + + diff --git a/src/sql/workbench/contrib/notebook/browser/cellViews/media/toolbar-preview-toggle-off.svg b/src/sql/workbench/contrib/notebook/browser/cellViews/media/toolbar-preview-toggle-off.svg new file mode 100644 index 0000000000..b5a8538ea6 --- /dev/null +++ b/src/sql/workbench/contrib/notebook/browser/cellViews/media/toolbar-preview-toggle-off.svg @@ -0,0 +1,4 @@ + +markdown preview toggle - off + + diff --git a/src/sql/workbench/contrib/notebook/browser/cellViews/media/toolbar-preview-toggle-on.svg b/src/sql/workbench/contrib/notebook/browser/cellViews/media/toolbar-preview-toggle-on.svg new file mode 100644 index 0000000000..9294a58edc --- /dev/null +++ b/src/sql/workbench/contrib/notebook/browser/cellViews/media/toolbar-preview-toggle-on.svg @@ -0,0 +1,5 @@ + +markdown preview toggle - on + + + diff --git a/src/sql/workbench/contrib/notebook/browser/cellViews/textCell.component.html b/src/sql/workbench/contrib/notebook/browser/cellViews/textCell.component.html index 72604439b1..9603ca29c3 100644 --- a/src/sql/workbench/contrib/notebook/browser/cellViews/textCell.component.html +++ b/src/sql/workbench/contrib/notebook/browser/cellViews/textCell.component.html @@ -5,6 +5,7 @@ *--------------------------------------------------------------------------------------------*/ -->
+
diff --git a/src/sql/workbench/contrib/notebook/browser/cellViews/textCell.component.ts b/src/sql/workbench/contrib/notebook/browser/cellViews/textCell.component.ts index b58d8d8fee..081e78d255 100644 --- a/src/sql/workbench/contrib/notebook/browser/cellViews/textCell.component.ts +++ b/src/sql/workbench/contrib/notebook/browser/cellViews/textCell.component.ts @@ -31,7 +31,6 @@ import { IColorTheme } from 'vs/platform/theme/common/themeService'; export const TEXT_SELECTOR: string = 'text-cell-component'; const USER_SELECT_CLASS = 'actionselect'; - @Component({ selector: TEXT_SELECTOR, templateUrl: decodeURI(require.toUrl('./textCell.component.html')) diff --git a/src/sql/workbench/contrib/notebook/browser/cellViews/textCell.css b/src/sql/workbench/contrib/notebook/browser/cellViews/textCell.css index 236b314921..c03714eb82 100644 --- a/src/sql/workbench/contrib/notebook/browser/cellViews/textCell.css +++ b/src/sql/workbench/contrib/notebook/browser/cellViews/textCell.css @@ -7,6 +7,12 @@ text-cell-component { display: block; } +.notebookEditor .notebook-cell.active text-cell-component code-component { + border-color: transparent; + border-bottom-width: 1px; + border-style: solid; +} + text-cell-component .notebook-preview { user-select: none; padding-left: 8px; @@ -17,6 +23,16 @@ text-cell-component .notebook-preview { user-select: text; } +text-cell-component code-component .monaco-scrollable-element.editor-scrollable.vs { + left: 16px!important; +} +text-cell-component .monaco-editor .margin, +text-cell-component code-component .monaco-editor, +text-cell-component code-component .monaco-editor-background, +text-cell-component code-component .monaco-editor .inputarea.ime-input { + background-color: transparent; +} + .vs .notebook-preview .rangeHighlight { background-color: rgba(255, 255, 0, 0.2) } diff --git a/src/sql/workbench/contrib/notebook/browser/markdownToolbarActions.ts b/src/sql/workbench/contrib/notebook/browser/markdownToolbarActions.ts new file mode 100644 index 0000000000..257ed782e7 --- /dev/null +++ b/src/sql/workbench/contrib/notebook/browser/markdownToolbarActions.ts @@ -0,0 +1,344 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Action } from 'vs/base/common/actions'; + +import { INotebookEditor, INotebookService } from 'sql/workbench/services/notebook/browser/notebookService'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { IRange } from 'vs/editor/common/core/range'; +import { IIdentifiedSingleEditOperation } from 'vs/editor/common/model'; +import { TextModel } from 'vs/editor/common/model/textModel'; +import { ICellModel } from 'sql/workbench/services/notebook/browser/models/modelInterfaces'; +import { QueryTextEditor } from 'sql/workbench/browser/modelComponents/queryTextEditor'; +import { Selection } from 'vs/editor/common/core/selection'; + + + +// Action to decorate markdown +export class TransformMarkdownAction extends Action { + + constructor( + id: string, + label: string, + cssClass: string, + tooltip: string, + private _cellModel: ICellModel, + private _type: MarkdownButtonType, + @INotebookService private _notebookService: INotebookService + ) { + super(id, label, cssClass); + this._tooltip = tooltip; + } + public run(context: any): Promise { + return new Promise((resolve, reject) => { + try { + let markdownTextTransformer = new MarkdownTextTransformer(this._notebookService, this._cellModel); + markdownTextTransformer.transformText(this._type); + resolve(true); + } catch (e) { + reject(e); + } + }); + } +} + +export class MarkdownTextTransformer { + + private _notebookEditor: INotebookEditor; + constructor(private _notebookService: INotebookService, private _cellModel: ICellModel) { } + + public transformText(type: MarkdownButtonType): void { + let editorControl = this.getEditorControl(); + if (editorControl) { + let selections = editorControl.getSelections(); + // TODO: Support replacement for multiple selections + let selection = selections[0]; + let nothingSelected = this.editorHasNoSelection(selection); + let startRange: IRange = { + startColumn: selection.startColumn, + endColumn: selection.startColumn, + startLineNumber: selection.startLineNumber, + endLineNumber: selection.startLineNumber + }; + + // Get text to insert before selection + let beginInsertedCode = this.getStartTextToInsert(type); + // Get text to insert after selection + let endInsertedCode = this.getEndTextToInsert(type); + + // endInsertedCode can be an empty string (e.g. for unordered list), so no need to check for that as well + if (beginInsertedCode) { + let endRange: IRange = { + startColumn: selection.endColumn, + endColumn: selection.endColumn, + startLineNumber: selection.endLineNumber, + endLineNumber: selection.endLineNumber + }; + let editorModel = editorControl.getModel() as TextModel; + let isUndo = false; + if (editorModel) { + let markdownLineType = this.getMarkdownLineType(type); + isUndo = this.isUndoOperation(selection, type, markdownLineType, editorModel); + if (isUndo) { + if (markdownLineType === MarkdownLineType.BEGIN_AND_END_LINES) { + startRange = this.getIRangeWithOffsets(startRange, -1 * beginInsertedCode.length, 0, 0, 0); + endRange = this.getIRangeWithOffsets(endRange, 0, 0, endInsertedCode.length, 0); + editorModel.pushEditOperations(selections, [{ range: endRange, text: '' }, { range: startRange, text: '' }], null); + } else { + let operations: IIdentifiedSingleEditOperation[] = []; + startRange = this.getIRangeWithOffsets(startRange, 0, 0, beginInsertedCode.length, 0); + for (let i = 0; i < selection.endLineNumber - selection.startLineNumber + 1; i++) { + operations.push({ range: this.transformRangeByLineOffset(startRange, i), text: '' }); + } + editorModel.pushEditOperations(selections, operations, null); + } + } else { + // If the markdown we're inserting only needs to be added to the begin and end lines, add those edit operations directly + if (markdownLineType === MarkdownLineType.BEGIN_AND_END_LINES) { + editorModel.pushEditOperations(selections, [{ range: startRange, text: beginInsertedCode }, { range: endRange, text: endInsertedCode }], null); + } else { // Otherwise, add an operation per line (plus the operation at the last column + line) + let operations: IIdentifiedSingleEditOperation[] = []; + for (let i = 0; i < selection.endLineNumber - selection.startLineNumber + 1; i++) { + operations.push({ range: this.transformRangeByLineOffset(startRange, i), text: beginInsertedCode }); + } + operations.push({ range: endRange, text: endInsertedCode }); + editorModel.pushEditOperations(selections, operations, null); + } + } + } + + // If selection end is on same line as beginning, need to add offset for number of characters inserted + // Otherwise, the selection will not be correct after the transformation + let offset = selection.startLineNumber === selection.endLineNumber ? beginInsertedCode.length : 0; + endRange = this.getIRangeWithOffsets(endRange, offset, 0, offset, 0); + this.setEndSelection(endRange, type, editorControl, nothingSelected, isUndo); + } + // Always give focus back to the editor after pressing the button + editorControl.focus(); + } + } + + // For items like lists (where we need to insert a character at the beginning of each line), create + // range object for that range + private transformRangeByLineOffset(range: IRange, lineOffset: number): IRange { + return { + startColumn: lineOffset === 0 ? range.startColumn : 1, + endColumn: range.endColumn, + startLineNumber: range.endLineNumber + lineOffset, + endLineNumber: range.endLineNumber + lineOffset + }; + } + + private getStartTextToInsert(type: MarkdownButtonType): string { + switch (type) { + case MarkdownButtonType.BOLD: + return '**'; + case MarkdownButtonType.ITALIC: + return '_'; + case MarkdownButtonType.CODE: + return '```\n'; + case MarkdownButtonType.LINK: + return '['; + case MarkdownButtonType.UNORDERED_LIST: + return '- '; + case MarkdownButtonType.ORDERED_LIST: + return '1. '; + case MarkdownButtonType.IMAGE: + return '!['; + case MarkdownButtonType.HIGHLIGHT: + return ''; + default: + return ''; + } + } + + private getEndTextToInsert(type: MarkdownButtonType): string { + switch (type) { + case MarkdownButtonType.BOLD: + return '**'; + case MarkdownButtonType.ITALIC: + return '_'; + case MarkdownButtonType.CODE: + return '\n```'; + case MarkdownButtonType.LINK: + case MarkdownButtonType.IMAGE: + return ']()'; + case MarkdownButtonType.HIGHLIGHT: + return ''; + case MarkdownButtonType.UNORDERED_LIST: + case MarkdownButtonType.ORDERED_LIST: + default: + return ''; + } + } + + private getMarkdownLineType(type: MarkdownButtonType): MarkdownLineType { + switch (type) { + case MarkdownButtonType.UNORDERED_LIST: + case MarkdownButtonType.ORDERED_LIST: + return MarkdownLineType.EVERY_LINE; + default: + return MarkdownLineType.BEGIN_AND_END_LINES; + } + } + + // Get offset from the end column for editor selection + // For example, when inserting a link, we want to have the cursor be present in between the brackets + private getColumnOffsetForSelection(type: MarkdownButtonType, nothingSelected: boolean): number { + if (nothingSelected) { + return 0; + } + switch (type) { + case MarkdownButtonType.LINK: + return 2; + case MarkdownButtonType.IMAGE: + return 2; + // -1 is considered as having no explicit offset, so do not do anything with selection + default: return -1; + } + } + + private getEditorControl(): CodeEditorWidget | undefined { + if (!this._notebookEditor) { + this._notebookEditor = this._notebookService.findNotebookEditor(this._cellModel.notebookModel.notebookUri); + } + if (this._notebookEditor?.cellEditors?.length > 0) { + // Find cell editor provider via cell guid + let cellEditorProvider = this._notebookEditor.cellEditors.find(e => e.cellGuid() === this._cellModel.cellGuid); + if (cellEditorProvider) { + let editor = cellEditorProvider.getEditor() as QueryTextEditor; + if (editor) { + let editorControl = editor.getControl() as CodeEditorWidget; + return editorControl; + } + } + } + return undefined; + } + + private editorHasNoSelection(selection: Selection): boolean { + return !selection || (selection.startLineNumber === selection.endLineNumber && selection.startColumn === selection.endColumn); + } + + /** + * Sets the end selection state after the transform has occurred + * @param endRange range for end text that was inserted + * @param type MarkdownButtonType + * @param editorControl code editor widget + * @param noSelection controls whether there was no previous selection in the editor + */ + private setEndSelection(endRange: IRange, type: MarkdownButtonType, editorControl: CodeEditorWidget, noSelection: boolean, isUndo: boolean): void { + if (!endRange || !editorControl || isUndo) { + return; + } + let offset = this.getColumnOffsetForSelection(type, noSelection); + if (offset > -1) { + let newRange: IRange = { + startColumn: endRange.startColumn + offset, + startLineNumber: endRange.startLineNumber, + endColumn: endRange.startColumn + offset, + endLineNumber: endRange.endLineNumber + }; + editorControl.setSelection(newRange); + } else { + if (this.getMarkdownLineType(type) === MarkdownLineType.BEGIN_AND_END_LINES) { + let currentSelection = editorControl.getSelection(); + editorControl.setSelection({ + startColumn: currentSelection.startColumn + this.getStartTextToInsert(type).length, + startLineNumber: currentSelection.startLineNumber, + endColumn: currentSelection.endColumn - this.getEndTextToInsert(type).length, + endLineNumber: currentSelection.endLineNumber + }); + } + } + } + + /** + * Determine if user wants to perform an undo operation + * @param selection current user selection + * @param type markdown button type + * @param lineType markdown line type + * @param editorModel text model for the cell + */ + private isUndoOperation(selection: Selection, type: MarkdownButtonType, lineType: MarkdownLineType, editorModel: TextModel): boolean { + if (lineType === MarkdownLineType.BEGIN_AND_END_LINES) { + let selectedText = this.getExtendedSelectedText(selection, type, lineType, editorModel); + return selectedText && selectedText.startsWith(this.getStartTextToInsert(type)) && selectedText.endsWith(this.getEndTextToInsert(type)); + } else { + return this.everyLineMatchesBeginString(selection, type, editorModel); + } + } + + /** + * Gets the extended selected text (current selection + potential beginning + ending transformed text) + * @param selection Current selection in editor + * @param type Markdown Button Type + * @param lineType Markdown Line Type + * @param editorModel TextModel + */ + private getExtendedSelectedText(selection: Selection, type: MarkdownButtonType, lineType: MarkdownLineType, editorModel: TextModel): string { + if (lineType === MarkdownLineType.BEGIN_AND_END_LINES) { + return editorModel.getValueInRange({ + startColumn: selection.startColumn - this.getStartTextToInsert(type).length, + startLineNumber: selection.startLineNumber, + endColumn: selection.endColumn + this.getEndTextToInsert(type).length, + endLineNumber: selection.endLineNumber + }); + } + return ''; + } + + /** + * Returns whether all lines start with the expected transformed text for actions that match the EVERY_LINE line type + * @param selection Current selection in editor + * @param type Markdown Button Type + * @param editorModel TextModel + */ + private everyLineMatchesBeginString(selection: Selection, type: MarkdownButtonType, editorModel: TextModel): boolean { + if (this.getMarkdownLineType(type) !== MarkdownLineType.EVERY_LINE) { + return false; + } + for (let selectionLine = selection.startLineNumber; selectionLine <= selection.endLineNumber; selectionLine++) { + if (!editorModel.getLineContent(selectionLine).startsWith(this.getStartTextToInsert(type))) { + return false; + } + } + return true; + } + + /** + * Create new IRange object with arbitrary offsets + * @param initialRange range object + * @param startColumnOffset + * @param startLineNumberOffset + * @param endColumnOffset + * @param endLineNumberOffset + */ + private getIRangeWithOffsets(initialRange: IRange, startColumnOffset = 0, startLineNumberOffset = 0, endColumnOffset = 0, endLineNumberOffset = 0): IRange { + return { + startColumn: initialRange.startColumn + startColumnOffset, + startLineNumber: initialRange.startLineNumber + startLineNumberOffset, + endColumn: initialRange.endColumn + endColumnOffset, + endLineNumber: initialRange.endLineNumber + endLineNumberOffset + }; + } +} + +export enum MarkdownButtonType { + BOLD, + ITALIC, + CODE, + HIGHLIGHT, + LINK, + UNORDERED_LIST, + ORDERED_LIST, + IMAGE +} + +// If ALL_LINES, we need to insert markdown at each line (e.g. lists) +export enum MarkdownLineType { + BEGIN_AND_END_LINES, + EVERY_LINE +} diff --git a/src/sql/workbench/contrib/notebook/browser/notebook.component.html b/src/sql/workbench/contrib/notebook/browser/notebook.component.html index bd3626aa07..723fe18a20 100644 --- a/src/sql/workbench/contrib/notebook/browser/notebook.component.html +++ b/src/sql/workbench/contrib/notebook/browser/notebook.component.html @@ -9,17 +9,6 @@
-
- - - -
@@ -27,17 +16,6 @@
-
- - - -
diff --git a/src/sql/workbench/contrib/notebook/browser/notebook.css b/src/sql/workbench/contrib/notebook/browser/notebook.css index 7fbb302a74..a997829229 100644 --- a/src/sql/workbench/contrib/notebook/browser/notebook.css +++ b/src/sql/workbench/contrib/notebook/browser/notebook.css @@ -2,16 +2,21 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +.notebookEditor .scrollable { + margin-top: 5px; +} + .notebookEditor .editor-toolbar { - border-bottom-width: 1px; border-bottom-style: solid; + border-width: 0px 0px 1px 0px; } .notebookEditor .notebook-cell { - margin: 1px 20px; - border-width: 1px; + border-color: transparent; border-style: solid; - border-radius: 3px; + border-width: 1px 1px 1px 4px; + margin: 24px 20px; + position: relative; } .notebookEditor .notebook-info-label { @@ -120,6 +125,9 @@ .moreActions.actionhidden { visibility: hidden } +.moreActions .monaco-action-bar { + margin-left: -12px; +} .notebookEditor .notebook-cellTable { margin-left: 20px; diff --git a/src/sql/workbench/contrib/notebook/browser/notebook.module.ts b/src/sql/workbench/contrib/notebook/browser/notebook.module.ts index 71ee5445ab..586ffb575e 100644 --- a/src/sql/workbench/contrib/notebook/browser/notebook.module.ts +++ b/src/sql/workbench/contrib/notebook/browser/notebook.module.ts @@ -30,6 +30,8 @@ import { LinkHandlerDirective } from 'sql/workbench/contrib/notebook/browser/cel import { IBootstrapParams, ISelector } from 'sql/workbench/services/bootstrap/common/bootstrapParams'; import { ICellComponenetRegistry, Extensions as OutputComponentExtensions } from 'sql/platform/notebooks/common/outputRegistry'; import { CollapseComponent } from 'sql/workbench/contrib/notebook/browser/cellViews/collapse.component'; +import { MarkdownToolbarComponent } from 'sql/workbench/contrib/notebook/browser/cellViews/markdownToolbar.component'; +import { CellToolbarComponent } from 'sql/workbench/contrib/notebook/browser/cellViews/cellToolbar.component'; const outputComponentRegistry = Registry.as(OutputComponentExtensions.CellComponentContributions); @@ -46,6 +48,8 @@ export const NotebookModule = (params, selector: string, instantiationService: I LoadingSpinner, CodeComponent, CodeCellComponent, + CellToolbarComponent, + MarkdownToolbarComponent, PlaceholderCellComponent, NotebookComponent, ComponentHostDirective, diff --git a/src/sql/workbench/contrib/notebook/browser/notebookStyles.ts b/src/sql/workbench/contrib/notebook/browser/notebookStyles.ts index 598868a2f6..347f613008 100644 --- a/src/sql/workbench/contrib/notebook/browser/notebookStyles.ts +++ b/src/sql/workbench/contrib/notebook/browser/notebookStyles.ts @@ -5,9 +5,10 @@ import 'vs/css!./notebook'; import { registerThemingParticipant, IColorTheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; -import { SIDE_BAR_BACKGROUND, SIDE_BAR_SECTION_HEADER_BACKGROUND, EDITOR_GROUP_HEADER_TABS_BACKGROUND } from 'vs/workbench/common/theme'; +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 { IDisposable } from 'vs/base/common/lifecycle'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { BareResultsGridInfo, getBareResultsGridInfoStyles } from 'sql/workbench/contrib/query/browser/queryResultsEditor'; @@ -17,10 +18,6 @@ import * as types from 'vs/base/common/types'; export function registerNotebookThemes(overrideEditorThemeSetting: boolean, configurationService: IConfigurationService): IDisposable { return registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { - let lightBoxShadow = '0px 4px 6px 0px rgba(0, 0, 0, 0.14)'; - let darkBoxShadow = '0px 4px 6px 0px rgba(0, 0, 0, 1)'; - let addBorderToInactiveCodeCells = true; - // Book Navigation Buttons const buttonForegroundColor = theme.getColor(buttonForeground); const buttonBackgroundColor = theme.getColor(buttonBackground); @@ -40,14 +37,6 @@ export function registerNotebookThemes(overrideEditorThemeSetting: boolean, conf if (buttonBackgroundColor) { let lighterBackgroundColor = lighten(buttonBackgroundColor, 0.825)(theme); collector.addRule(` - .notebookEditor .notebook-cell.active { - border-color: ${buttonBackgroundColor}; - border-width: 1px; - } - .notebookEditor .notebook-cell.active:hover { - border-color: ${buttonBackgroundColor}; - } - .notebookEditor .hoverButton { border-color: ${buttonBackgroundColor}; } @@ -58,7 +47,6 @@ export function registerNotebookThemes(overrideEditorThemeSetting: boolean, conf .notebookEditor .hoverButton { color: ${buttonBackgroundColor}; } - .vs-dark .notebookEditor .hoverButton { border-color: ${lighterBackgroundColor}; } @@ -89,38 +77,11 @@ export function registerNotebookThemes(overrideEditorThemeSetting: boolean, conf `); } - // Box shadow handling - collector.addRule(` - .notebookEditor .notebook-cell.active { - box-shadow: ${lightBoxShadow}; - } - - .vs-dark .notebookEditor .notebook-cell.active { - box-shadow: ${darkBoxShadow}; - } - - .hc-black .notebookEditor .notebook-cell.active { - box-shadow: 0; - } - - .notebookEditor .notebook-cell:hover:not(.active) { - box-shadow: ${lightBoxShadow}; - } - - .vs-dark .notebookEditor .notebook-cell:hover:not(.active) { - box-shadow: ${darkBoxShadow}; - } - - .hc-black .notebookEditor .notebook-cell:hover:not(.active) { - box-shadow: 0; - } - `); - const inactiveBorder = theme.getColor(SIDE_BAR_BACKGROUND); - const sidebarColor = theme.getColor(SIDE_BAR_SECTION_HEADER_BACKGROUND); const notebookLineHighlight = theme.getColor(EDITOR_GROUP_HEADER_TABS_BACKGROUND); // Code editor style overrides - only applied if user chooses this as preferred option if (overrideEditorThemeSetting) { + let lineHighlight = theme.getColor(editorLineHighlight); if (!lineHighlight || lineHighlight.isTransparent()) { // Use notebook color override @@ -135,43 +96,12 @@ export function registerNotebookThemes(overrideEditorThemeSetting: boolean, conf // Override values only for the children of code-component so regular editors aren't affected collector.addRule(`code-component .monaco-editor .view-overlays .current-line { border: 0px; }`); } - - // Override code editor background if color is defined - let codeBackground = inactiveBorder; // theme.getColor(EDITOR_GROUP_HEADER_TABS_BACKGROUND); - if (codeBackground) { - // Main background - collector.addRule(`.notebook-cell:not(.active) code-component { background-color: ${codeBackground}; }`); - collector.addRule(` - .notebook-cell:not(.active) code-component .monaco-editor, - .notebook-cell:not(.active) code-component .monaco-editor-background, - .notebook-cell:not(.active) code-component .monaco-editor .inputarea.ime-input, - .notebook-cell.active .hide-component-button:hover - { - background-color: ${codeBackground}; - }`); - - // Margin background will be the same (may override some styles) - collector.addRule(`.notebook-cell:not(.active) code-component .monaco-editor .margin { background-color: ${codeBackground}; }`); - addBorderToInactiveCodeCells = false; - } } // Inactive border if (inactiveBorder) { // Standard notebook cell behavior collector.addRule(` - .notebookEditor .notebook-cell { - border-color: transparent; - border-width: 1px; - } - .notebookEditor .notebook-cell.active { - border-width: 1px; - } - .notebookEditor .notebook-cell:hover { - border-color: ${inactiveBorder}; - border-width: 1px; - } - .notebookEditor .hoverButtonsContainer .containerBackground { background-color: ${inactiveBorder}; } @@ -181,35 +111,10 @@ export function registerNotebookThemes(overrideEditorThemeSetting: boolean, conf collector.addRule(` .notebookEditor .notebook-cell.active code-component { border-color: ${inactiveBorder}; - border-width: 0px 0px 1px 0px; - border-style: solid; - border-radius: 0; } `); - - if (addBorderToInactiveCodeCells) { - // Sets a border for the editor component if we don't have a custom line color for editor instead - collector.addRule(` - .notebookEditor .notebook-cell code-component { - border-color: ${inactiveBorder}; - border-width: 1px; - border-style: solid; - border-radius: 3px 3px 3px 3px; - } - .notebookEditor .notebook-cell:hover code-component { - border-width: 0px 0px 1px 0px; - border-radius: 0px; - } - `); - } } - // Sidebar and cell outline toolbar color set only when active - collector.addRule(` - .notebook-cell.active code-component .toolbar { - background-color: ${sidebarColor}; - } - `); // Styling with Outline color (e.g. high contrast theme) const outline = theme.getColor(activeContrastBorder); const hcOutline = theme.getColor(contrastBorder); @@ -219,21 +124,11 @@ export function registerNotebookThemes(overrideEditorThemeSetting: boolean, conf border-color: ${hcOutline}; border-width: 0px 0px 1px 0px; } - .hc-black .notebookEditor .notebook-cell.active code-component { - border-color: ${outline}; - border-width: 0px 0px 1px 0px; - } .hc-black .notebookEditor .notebook-cell:not(.active) { outline-color: ${hcOutline}; outline-width: 1px; outline-style: solid; } - .notebookEditor .notebook-cell.active { - outline-color: ${outline}; - outline-width: 1px; - outline-style: solid; - } - .hc-black .notebookEditor .hoverButton { color: ${hcOutline}; } @@ -283,8 +178,6 @@ export function registerNotebookThemes(overrideEditorThemeSetting: boolean, conf } `); } - - let blockQuoteBackground = theme.getColor(textBlockQuoteBackground); let blockQuoteBorder = theme.getColor(textBlockQuoteBorder); if (preformatForeground) { @@ -313,5 +206,64 @@ export function registerNotebookThemes(overrideEditorThemeSetting: boolean, conf .grid-panel .monaco-table, .message-tree { ${getBareResultsGridInfoStyles(rawOptions)} }`); + + + // Cell border + const cellBorderColor = theme.getColor(cellBorder); + if (cellBorderColor) { + collector.addRule(`.notebookEditor .notebook-cell.active { border-color: ${cellBorderColor};}`); + } + + // Markdown editor toolbar + const toolbarBackgroundColor = theme.getColor(toolbarBackground); + if (toolbarBackgroundColor) { + collector.addRule(`markdown-toolbar-component { background: ${toolbarBackgroundColor};}`); + } + const toolbarIconColor = theme.getColor(toolbarIcon); + if (toolbarIconColor) { + collector.addRule(`.markdown-toolbar li a { background-color: ${toolbarIconColor};}`); + } + const toolbarBottomBorderColor = theme.getColor(toolbarBottomBorder); + if (toolbarBottomBorderColor) { + collector.addRule(`.markdown-toolbar { border-bottom-color: ${toolbarBottomBorderColor};}`); + } + + // Markdwon editor colors + const markdownEditorBackgroundColor = theme.getColor(markdownEditorBackground); + if (markdownEditorBackgroundColor) { + collector.addRule(`text-cell-component code-component { background-color: ${markdownEditorBackgroundColor}; }`); + } + const splitBorderColor = theme.getColor(splitBorder); + if (splitBorderColor) { + collector.addRule(`.notebookEditor .notebook-cell.active text-cell-component code-component { border-bottom-color: ${splitBorderColor}; }`); + } + + // Code editor colors + const codeEditorBackgroundColor = theme.getColor(codeEditorBackground); + if (codeEditorBackgroundColor) { + collector.addRule(`code-cell-component code-component { background-color: ${codeEditorBackgroundColor}; }`); + } + const codeEditorBackgroundActiveColor = theme.getColor(codeEditorBackgroundActive); + if (codeEditorBackgroundActiveColor) { + collector.addRule(`.notebook-cell.active code-cell-component code-component { background-color: ${codeEditorBackgroundActiveColor}; }`); + } + const codeEditorLineNumberColor = theme.getColor(codeEditorLineNumber); + if (codeEditorLineNumberColor) { + collector.addRule(`code-cell-component code-component .editor .line-numbers { color: ${codeEditorLineNumberColor};}`); + } + const codeEditorToolbarIconColor = theme.getColor(codeEditorToolbarIcon); + if (codeEditorToolbarIconColor) { + collector.addRule( + `code-cell-component code-component .carbon-taskbar .codicon.hideIcon { color: ${codeEditorToolbarIconColor};}` + ); + } + const codeEditorToolbarBackgroundColor = theme.getColor(codeEditorToolbarBackground); + if (codeEditorToolbarBackgroundColor) { + collector.addRule(`.notebook-cell.active code-cell-component code-component .toolbar { background-color: ${codeEditorToolbarBackgroundColor};}`); + } + const codeEditorToolbarBorderColor = theme.getColor(codeEditorToolbarBorder); + if (codeEditorToolbarBorderColor) { + collector.addRule(`.notebook-cell.active code-cell-component code-component .toolbar { border-right-color: ${codeEditorToolbarBorderColor}!important;}`); + } }); }