From 7ef8acf04e0692e3680018e131a3b4cd3f0ca9c3 Mon Sep 17 00:00:00 2001 From: Hale Rankin Date: Fri, 26 Jun 2020 13:47:17 -0700 Subject: [PATCH] Markdown toolbar > Preview toggle feature (#10963) * Added toggle preview button to Markdown toolbar. Revised components, theme and styles to present the preview as a second column beside the markdown. * Added showPreview to model and began working on togglePreview. * Uncommented use of cellModel.showPreview * add cell model event for onPreviewChange * Renamed my showPreview boolean to prevent confusion with local boolean used in toogglePreview. * Added CSS class when preview is enabled. Adjusted styles accordingly. * Swapped icon show/hide references for correct sequence. Modified updatePreview to include state of doShowPreview. * Added check for isEditMode so we can run togglePreview and show it once editor closes. * Added listener to code.component that triggers layoutEmitter on changes to peview. * Renamed local boolean doShowPreview. Removed unneeded code. Fixed ambiguity in my use of booleans, adding a getter and setter to textCell. * Cleaned up implementation of new get/set for toggling preview. Co-authored-by: chlafreniere --- .../browser/cellViews/code.component.ts | 4 ++ .../cellViews/markdownToolbar.component.ts | 7 ++-- .../browser/cellViews/markdownToolbar.css | 11 ++++- .../browser/cellViews/textCell.component.html | 6 +-- .../browser/cellViews/textCell.component.ts | 27 ++++++++++-- .../notebook/browser/cellViews/textCell.css | 27 +++++++++--- .../browser/markdownToolbarActions.ts | 41 ++++++++++++++++++- .../contrib/notebook/browser/notebook.css | 19 +++------ .../notebook/browser/notebookStyles.ts | 4 +- .../services/notebook/browser/models/cell.ts | 17 ++++++++ .../browser/models/modelInterfaces.ts | 2 + 11 files changed, 130 insertions(+), 35 deletions(-) diff --git a/src/sql/workbench/contrib/notebook/browser/cellViews/code.component.ts b/src/sql/workbench/contrib/notebook/browser/cellViews/code.component.ts index 1cfececdb8..32a63b1762 100644 --- a/src/sql/workbench/contrib/notebook/browser/cellViews/code.component.ts +++ b/src/sql/workbench/contrib/notebook/browser/cellViews/code.component.ts @@ -253,6 +253,10 @@ export class CodeComponent extends CellView implements OnInit, OnChanges { this.onCellCollapse(isCollapsed); })); + this._register(this.cellModel.onCellPreviewChanged(() => { + this._layoutEmitter.fire(); + })); + this.layout(); if (this._cellModel.isCollapsed) { 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 52e8c2633a..a87c5fa40b 100644 --- a/src/sql/workbench/contrib/notebook/browser/cellViews/markdownToolbar.component.ts +++ b/src/sql/workbench/contrib/notebook/browser/cellViews/markdownToolbar.component.ts @@ -7,7 +7,7 @@ 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 { TransformMarkdownAction, MarkdownButtonType, TogglePreviewAction } from 'sql/workbench/contrib/notebook/browser/markdownToolbarActions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; export const MARKDOWN_TOOLBAR_SELECTOR: string = 'markdown-toolbar-component'; @@ -28,7 +28,6 @@ export class MarkdownToolbarComponent { 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; @@ -51,6 +50,7 @@ export class MarkdownToolbarComponent { 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 togglePreviewAction = this._instantiationService.createInstance(TogglePreviewAction, 'notebook.togglePreview', true, this.cellModel.showPreview); let taskbar = this.mdtoolbar.nativeElement; this._actionBar = new Taskbar(taskbar); @@ -64,7 +64,8 @@ export class MarkdownToolbarComponent { { action: linkButton }, { action: listButton }, { action: orderedListButton }, - { action: imageButton } + { action: imageButton }, + { action: togglePreviewAction } ]); } } diff --git a/src/sql/workbench/contrib/notebook/browser/cellViews/markdownToolbar.css b/src/sql/workbench/contrib/notebook/browser/cellViews/markdownToolbar.css index 5171bedb46..bcc90756a8 100644 --- a/src/sql/workbench/contrib/notebook/browser/cellViews/markdownToolbar.css +++ b/src/sql/workbench/contrib/notebook/browser/cellViews/markdownToolbar.css @@ -11,7 +11,6 @@ padding: 0; } - .markdown-toolbar { border-bottom-width: 1px; border-bottom-style: solid; @@ -20,6 +19,11 @@ margin: 0; padding: 10px 16px 4px 16px; } + +.markdown-toolbar ul { + position: relative; +} + .markdown-toolbar .carbon-taskbar li.action-item { display: inline-block; margin-right: 14px; @@ -30,6 +34,11 @@ .markdown-toolbar .carbon-taskbar li:nth-child(2) { margin-right: 9px; } +.markdown-toolbar .carbon-taskbar li:last-child { + margin-right: 0; + position: absolute; + right: 0; +} .markdown-toolbar .carbon-taskbar li a { display: inline-block; height: 20px; 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 edc7a11c30..b46dc4c0c9 100644 --- a/src/sql/workbench/contrib/notebook/browser/cellViews/textCell.component.html +++ b/src/sql/workbench/contrib/notebook/browser/cellViews/textCell.component.html @@ -6,12 +6,10 @@ -->
-
+
-
-
-
+
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 b2b4f88731..4b624f0ff2 100644 --- a/src/sql/workbench/contrib/notebook/browser/cellViews/textCell.component.ts +++ b/src/sql/workbench/contrib/notebook/browser/cellViews/textCell.component.ts @@ -69,6 +69,7 @@ export class TextCellComponent extends CellView implements OnInit, OnChanges { private _content: string | string[]; private _lastTrustedMode: boolean; private isEditMode: boolean; + private showPreview: boolean; private _sanitizer: ISanitizer; private _model: NotebookModel; private _activeCellId: string; @@ -86,6 +87,7 @@ export class TextCellComponent extends CellView implements OnInit, OnChanges { ) { super(); this.isEditMode = true; + this.showPreview = true; this.markdownRenderer = this._instantiationService.createInstance(NotebookMarkdownRenderer); this._register(toDisposable(() => { if (this.markdownResult) { @@ -139,6 +141,9 @@ export class TextCellComponent extends CellView implements OnInit, OnChanges { this.toggleEditMode(mode); } })); + this._register(this.cellModel.onCellPreviewChanged(preview => { + this.previewMode = preview; + })); } ngOnChanges(changes: { [propKey: string]: SimpleChange }) { @@ -171,14 +176,14 @@ export class TextCellComponent extends CellView implements OnInit, OnChanges { /** * Updates the preview of markdown component with latest changes - * If content is empty and in non-edit mode, default it to 'Double-click to edit' + * If content is empty and in non-edit mode, default it to 'Add content here...' * Sanitizes the data to be shown in markdown cell */ private updatePreview(): void { let trustedChanged = this.cellModel && this._lastTrustedMode !== this.cellModel.trustedMode; let cellModelSourceJoined = Array.isArray(this.cellModel.source) ? this.cellModel.source.join('') : this.cellModel.source; let contentJoined = Array.isArray(this._content) ? this._content.join('') : this._content; - let contentChanged = contentJoined !== cellModelSourceJoined || cellModelSourceJoined.length === 0; + let contentChanged = contentJoined !== cellModelSourceJoined || cellModelSourceJoined.length === 0 || this.showPreview === true; if (trustedChanged || contentChanged) { this._lastTrustedMode = this.cellModel.trustedMode; if ((!cellModelSourceJoined) && !this.isEditMode) { @@ -213,8 +218,10 @@ export class TextCellComponent extends CellView implements OnInit, OnChanges { } private updateTheme(theme: IColorTheme): void { - let outputElement = this.output.nativeElement; - outputElement.style.borderTopColor = theme.getColor(themeColors.SIDE_BAR_BACKGROUND, true).toString(); + let outputElement = this.output?.nativeElement; + if (outputElement) { + outputElement.style.borderTopColor = theme.getColor(themeColors.SIDE_BAR_BACKGROUND, true).toString(); + } } public handleContentChanged(): void { @@ -224,10 +231,22 @@ export class TextCellComponent extends CellView implements OnInit, OnChanges { public toggleEditMode(editMode?: boolean): void { this.isEditMode = editMode !== undefined ? editMode : !this.isEditMode; this.cellModel.isEditMode = this.isEditMode; + if (!this.isEditMode) { + this.previewMode = true; + } this.updatePreview(); this._changeRef.detectChanges(); } + public get previewMode(): boolean { + return this.showPreview; + } + public set previewMode(value: boolean) { + this.showPreview = value; + this._changeRef.detectChanges(); + this.updatePreview(); + } + private toggleUserSelect(userSelect: boolean): void { if (!this.output) { return; diff --git a/src/sql/workbench/contrib/notebook/browser/cellViews/textCell.css b/src/sql/workbench/contrib/notebook/browser/cellViews/textCell.css index c03714eb82..f3e05700cd 100644 --- a/src/sql/workbench/contrib/notebook/browser/cellViews/textCell.css +++ b/src/sql/workbench/contrib/notebook/browser/cellViews/textCell.css @@ -7,17 +7,34 @@ 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-text { + display: flex; +} +text-cell-component code-component { + flex-direction: column; } - text-cell-component .notebook-preview { + flex-direction: column; + width: 100%; user-select: none; padding-left: 8px; padding-right: 8px; } +text-cell-component .edit-mode code-component { + display: block; + width: 100%; +} +text-cell-component .show-preview.edit-mode code-component { + width: 50%; +} +text-cell-component .edit-mode .notebook-preview { + border-color: transparent; + border-left-width: 1px; + border-style: solid; + border-top-width: 0; + flex-direction: column; + width: 50%; +} .notebook-preview.actionselect { user-select: text; diff --git a/src/sql/workbench/contrib/notebook/browser/markdownToolbarActions.ts b/src/sql/workbench/contrib/notebook/browser/markdownToolbarActions.ts index c6b5998fca..7e9eff07bc 100644 --- a/src/sql/workbench/contrib/notebook/browser/markdownToolbarActions.ts +++ b/src/sql/workbench/contrib/notebook/browser/markdownToolbarActions.ts @@ -3,8 +3,8 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { localize } from 'vs/nls'; 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'; @@ -13,7 +13,7 @@ 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'; - +import { ToggleableAction } from 'sql/workbench/contrib/notebook/browser/notebookActions'; // Action to decorate markdown @@ -390,3 +390,40 @@ export enum MarkdownLineType { EVERY_LINE, WRAPPED_ABOVE_AND_BELOW } + +export class TogglePreviewAction extends ToggleableAction { + + private static readonly previewShowLabel = localize('previewShowLabel', "Show Preview"); + private static readonly previewHideLabel = localize('previewHideLabel', "Hide Preview"); + private static readonly baseClass = 'codicon'; + private static readonly previewShowCssClass = 'split-toggle-on'; + private static readonly previewHideCssClass = 'split-toggle-off'; + private static readonly maskedIconClass = 'masked-icon'; + + constructor( + id: string, toggleTooltip: boolean, showPreview: boolean + ) { + super(id, { + baseClass: TogglePreviewAction.baseClass, + toggleOffLabel: TogglePreviewAction.previewShowLabel, + toggleOffClass: TogglePreviewAction.previewShowCssClass, + toggleOnLabel: TogglePreviewAction.previewHideLabel, + toggleOnClass: TogglePreviewAction.previewHideCssClass, + maskedIconClass: TogglePreviewAction.maskedIconClass, + shouldToggleTooltip: toggleTooltip, + isOn: showPreview + }); + } + + public get previewMode(): boolean { + return this.state.isOn; + } + public set previewMode(value: boolean) { + this.toggle(value); + } + public async run(context: any): Promise { + this.previewMode = !this.previewMode; + context.cellModel.showPreview = this.previewMode; + return true; + } +} diff --git a/src/sql/workbench/contrib/notebook/browser/notebook.css b/src/sql/workbench/contrib/notebook/browser/notebook.css index e4432ed3a7..d80232f282 100644 --- a/src/sql/workbench/contrib/notebook/browser/notebook.css +++ b/src/sql/workbench/contrib/notebook/browser/notebook.css @@ -74,7 +74,7 @@ width: 16px; } .notebookEditor .in-preview .actions-container .action-item:last-child { - margin-right: 8px; + margin-right: 14px; } .notebookEditor .in-preview @@ -83,20 +83,11 @@ .notebook-button { margin-right: 0; } -.notebookEditor - .in-preview - .actions-container - .action-item:last-child - .notebook-button.fixed-width { + +.notebookEditor .in-preview .actions-container .action-item:last-child .notebook-button.fixed-width { + background-size: contain; margin-left: 8px; - margin-right: -28px; -} -.notebookEditor - .in-preview - .actions-container - .action-item - .notebook-button.fixed-width { - width: 34px; + padding: 0 0 0 18px; } .notebookEditor .labelOnLeftContainer { diff --git a/src/sql/workbench/contrib/notebook/browser/notebookStyles.ts b/src/sql/workbench/contrib/notebook/browser/notebookStyles.ts index a8b3d49936..b596d1fabc 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 } from 'vs/platform/theme/common/colorRegistry'; import { editorLineHighlight, editorLineHighlightBorder } from 'vs/editor/common/view/editorColorRegistry'; -import { cellBorder, notebookToolbarIcon, notebookToolbarLines, buttonMenuArrow, dropdownArrow, markdownEditorBackground, splitBorder, codeEditorBackground, codeEditorBackgroundActive, codeEditorLineNumber, codeEditorToolbarIcon, codeEditorToolbarBackground, codeEditorToolbarBorder, toolbarBackground, toolbarIcon, toolbarBottomBorder, notebookToolbarSelectBackground } from 'sql/platform/theme/common/colorRegistry'; +import { cellBorder, notebookToolbarIcon, notebookToolbarLines, buttonMenuArrow, dropdownArrow, markdownEditorBackground, codeEditorBackground, codeEditorBackgroundActive, codeEditorLineNumber, codeEditorToolbarIcon, codeEditorToolbarBackground, codeEditorToolbarBorder, toolbarBackground, toolbarIcon, toolbarBottomBorder, notebookToolbarSelectBackground, splitBorder } 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'; @@ -201,7 +201,7 @@ export function registerNotebookThemes(overrideEditorThemeSetting: boolean, conf } const splitBorderColor = theme.getColor(splitBorder); if (splitBorderColor) { - collector.addRule(`.notebookEditor .notebook-cell.active text-cell-component code-component { border-bottom-color: ${splitBorderColor}; }`); + collector.addRule(`.notebookEditor .notebook-cell.active text-cell-component .notebook-preview { border-left-color: ${splitBorderColor}; }`); } // Code editor colors diff --git a/src/sql/workbench/services/notebook/browser/models/cell.ts b/src/sql/workbench/services/notebook/browser/models/cell.ts index 654e49803d..528eeb529d 100644 --- a/src/sql/workbench/services/notebook/browser/models/cell.ts +++ b/src/sql/workbench/services/notebook/browser/models/cell.ts @@ -56,6 +56,8 @@ export class CellModel implements ICellModel { private _isCollapsed: boolean; private _onCollapseStateChanged = new Emitter(); private _modelContentChangedEvent: IModelContentChangedEvent; + private _showPreview: boolean = true; + private _onCellPreviewChanged = new Emitter(); constructor(cellData: nb.ICellContents, private _options: ICellModelOptions, @@ -275,6 +277,21 @@ export class CellModel implements ICellModel { this._stdInVisible = val; } + public get showPreview(): boolean { + return this._showPreview; + } + + public set showPreview(val: boolean) { + if (val !== this._showPreview) { + this._showPreview = val; + this._onCellPreviewChanged.fire(this._showPreview); + } + } + + public get onCellPreviewChanged(): Event { + return this._onCellPreviewChanged.event; + } + private notifyExecutionComplete(): void { if (this._notebookService) { this._notebookService.serializeNotebookStateChange(this.notebookModel.notebookUri, NotebookChangeType.CellExecuted, this) diff --git a/src/sql/workbench/services/notebook/browser/models/modelInterfaces.ts b/src/sql/workbench/services/notebook/browser/models/modelInterfaces.ts index 66e61d4a62..55fa0e7e58 100644 --- a/src/sql/workbench/services/notebook/browser/models/modelInterfaces.ts +++ b/src/sql/workbench/services/notebook/browser/models/modelInterfaces.ts @@ -461,6 +461,8 @@ export interface ICellModel { readonly onCellModeChanged: Event; modelContentChangedEvent: IModelContentChangedEvent; isEditMode: boolean; + showPreview: boolean; + readonly onCellPreviewChanged: Event; sendChangeToNotebook(change: NotebookChangeType): void; }