From e0ceddce0976ab18061b032ae6e8d702abc31e18 Mon Sep 17 00:00:00 2001 From: Chris LaFreniere <40371649+chlafreniere@users.noreply.github.com> Date: Mon, 14 Jan 2019 17:29:06 -0800 Subject: [PATCH] Notebooks: Add Placeholder Cell, Fix Link Styling (#3728) * Placeholder cell to add new real cells * Fix links in notebooks to show correct color, rely on angular ngif for placeholder * Fix failing test where one cell was expected by default * Remove unnecessary TODO --- .../parts/notebook/cellViews/placeholder.css | 20 +++++ .../cellViews/placeholderCell.component.html | 13 +++ .../cellViews/placeholderCell.component.ts | 82 +++++++++++++++++++ .../parts/notebook/models/notebookModel.ts | 7 +- .../parts/notebook/notebook.component.html | 4 + src/sql/parts/notebook/notebook.component.ts | 5 -- src/sql/parts/notebook/notebook.css | 1 + src/sql/parts/notebook/notebook.module.ts | 4 +- src/sql/parts/notebook/notebookStyles.ts | 14 +++- .../parts/notebook/outputs/style/index.css | 3 - .../notebook/model/notebookModel.test.ts | 7 +- 11 files changed, 140 insertions(+), 20 deletions(-) create mode 100644 src/sql/parts/notebook/cellViews/placeholder.css create mode 100644 src/sql/parts/notebook/cellViews/placeholderCell.component.html create mode 100644 src/sql/parts/notebook/cellViews/placeholderCell.component.ts diff --git a/src/sql/parts/notebook/cellViews/placeholder.css b/src/sql/parts/notebook/cellViews/placeholder.css new file mode 100644 index 0000000000..976b01e323 --- /dev/null +++ b/src/sql/parts/notebook/cellViews/placeholder.css @@ -0,0 +1,20 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + + placeholder-cell-component { + height: 50px; + width: 100%; + display: block; + box-shadow: 0px 4px 6px 0px rgba(0,0,0,0.14); +} + +placeholder-cell-component .text { + display: flex; + align-items: center; + justify-content: center; + height: 50px; + -webkit-margin-before: 0em; + -webkit-margin-after: 0em; +} \ No newline at end of file diff --git a/src/sql/parts/notebook/cellViews/placeholderCell.component.html b/src/sql/parts/notebook/cellViews/placeholderCell.component.html new file mode 100644 index 0000000000..5679ce16bc --- /dev/null +++ b/src/sql/parts/notebook/cellViews/placeholderCell.component.html @@ -0,0 +1,13 @@ + +
+
+
+

{{clickOn}} {{plusCode}} {{or}} {{plusText}} {{toAddCell}}

+
+
+
\ No newline at end of file diff --git a/src/sql/parts/notebook/cellViews/placeholderCell.component.ts b/src/sql/parts/notebook/cellViews/placeholderCell.component.ts new file mode 100644 index 0000000000..1fe4ab13e3 --- /dev/null +++ b/src/sql/parts/notebook/cellViews/placeholderCell.component.ts @@ -0,0 +1,82 @@ +/*--------------------------------------------------------------------------------------------- +* 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!./placeholder'; + +import { OnInit, Component, Input, Inject, forwardRef, ElementRef, ChangeDetectorRef, OnDestroy, ViewChild, SimpleChange, OnChanges } from '@angular/core'; +import { CellView } from 'sql/parts/notebook/cellViews/interfaces'; +import { ICellModel } from 'sql/parts/notebook/models/modelInterfaces'; +import { NotebookModel } from 'sql/parts/notebook/models/notebookModel'; +import { localize } from 'vs/nls'; +import { CellType } from 'sql/parts/notebook/models/contracts'; + + +export const PLACEHOLDER_SELECTOR: string = 'placeholder-cell-component'; + +@Component({ + selector: PLACEHOLDER_SELECTOR, + templateUrl: decodeURI(require.toUrl('./placeholderCell.component.html')) +}) + +export class PlaceholderCellComponent extends CellView implements OnInit, OnChanges { + @Input() cellModel: ICellModel; + @Input() set model(value: NotebookModel) { + this._model = value; + } + + private _model: NotebookModel; + + constructor( + @Inject(forwardRef(() => ChangeDetectorRef)) private _changeRef: ChangeDetectorRef, + ) { + super(); + } + + ngOnInit() { + if (this.cellModel) { + this._register(this.cellModel.onOutputsChanged(() => { + this._changeRef.detectChanges(); + })); + } + } + + ngOnChanges(changes: { [propKey: string]: SimpleChange }) { + } + + get model(): NotebookModel { + return this._model; + } + + get clickOn(): string { + return localize('clickOn','Click on'); + } + + get plusCode(): string { + return localize('plusCode', '+ Code'); + } + + get or(): string { + return localize('or', 'or'); + } + + get plusText(): string { + return localize('plusText', '+ Text'); + } + + get toAddCell(): string { + return localize('toAddCell', 'to add a code or text cell'); + } + + public addCell(cellType: string): void { + let type: CellType = cellType; + if (!type) { + type = 'code'; + } + this._model.addCell(cellType); + } + + public layout() { + + } +} diff --git a/src/sql/parts/notebook/models/notebookModel.ts b/src/sql/parts/notebook/models/notebookModel.ts index e159cdccce..e264af8a24 100644 --- a/src/sql/parts/notebook/models/notebookModel.ts +++ b/src/sql/parts/notebook/models/notebookModel.ts @@ -217,7 +217,7 @@ export class NotebookModel extends Disposable implements INotebookModel { } let factory = this.notebookOptions.factory; // if cells already exist, create them with language info (if it is saved) - this._cells = undefined; + this._cells = []; this._defaultLanguageInfo = { name: this._providerId === SQL_NOTEBOOK_PROVIDER ? 'sql' : 'python', version: '' @@ -230,9 +230,6 @@ export class NotebookModel extends Disposable implements INotebookModel { this._cells = contents.cells.map(c => factory.createCell(c, { notebook: this, isTrusted: isTrusted })); } } - if (!this._cells) { - this._cells = [this.createCell(CellTypes.Code)]; - } } catch (error) { this._inErrorState = true; throw error; @@ -244,7 +241,7 @@ export class NotebookModel extends Disposable implements INotebookModel { } public addCell(cellType: CellType, index?: number): ICellModel { - if (this.inErrorState || !this._cells) { + if (this.inErrorState) { return null; } let cell = this.createCell(cellType); diff --git a/src/sql/parts/notebook/notebook.component.html b/src/sql/parts/notebook/notebook.component.html index 6028e19591..230e0b40fa 100644 --- a/src/sql/parts/notebook/notebook.component.html +++ b/src/sql/parts/notebook/notebook.component.html @@ -15,5 +15,9 @@ +
+ + +
diff --git a/src/sql/parts/notebook/notebook.component.ts b/src/sql/parts/notebook/notebook.component.ts index edfb37625a..0bcdc965b5 100644 --- a/src/sql/parts/notebook/notebook.component.ts +++ b/src/sql/parts/notebook/notebook.component.ts @@ -250,11 +250,6 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe this.updateToolbarComponents(this._model.trustedMode); this._modelRegisteredDeferred.resolve(this._model); model.backgroundStartSession(); - // Set first cell as default active cell if user creates new notebook - // Otherwise, don't select any cells by default - if (this._model && this._model.cells && this._model.cells[0] && this._model.isNewNotebook) { - this.selectCell(model.cells[0]); - } this._changeRef.detectChanges(); } diff --git a/src/sql/parts/notebook/notebook.css b/src/sql/parts/notebook/notebook.css index c8afd50d70..2cc8d13314 100644 --- a/src/sql/parts/notebook/notebook.css +++ b/src/sql/parts/notebook/notebook.css @@ -11,6 +11,7 @@ margin: 10px 20px 10px; border-width: 1px; border-style: solid; + border-radius: 3px; } .notebookEditor .notebook-info-label { diff --git a/src/sql/parts/notebook/notebook.module.ts b/src/sql/parts/notebook/notebook.module.ts index d60a84edbe..ac87813e23 100644 --- a/src/sql/parts/notebook/notebook.module.ts +++ b/src/sql/parts/notebook/notebook.module.ts @@ -11,7 +11,6 @@ import { CommonModule, APP_BASE_HREF } from '@angular/common'; import { BrowserModule } from '@angular/platform-browser'; -import { Extensions, IComponentRegistry } from 'sql/platform/dashboard/common/modelComponentRegistry'; import { ComponentHostDirective } from 'sql/parts/dashboard/common/componentHost.directive'; import { IBootstrapParams, ISelector, providerIterator } from 'sql/services/bootstrap/bootstrapService'; import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service'; @@ -22,12 +21,12 @@ import { InputBox } from 'sql/base/browser/ui/inputBox/inputBox.component'; import { NotebookComponent } from 'sql/parts/notebook/notebook.component'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { Registry } from 'vs/platform/registry/common/platform'; import { CodeComponent } from 'sql/parts/notebook/cellViews/code.component'; import { CodeCellComponent } from 'sql/parts/notebook/cellViews/codeCell.component'; import { TextCellComponent } from 'sql/parts/notebook/cellViews/textCell.component'; import { OutputAreaComponent } from 'sql/parts/notebook/cellViews/outputArea.component'; import { OutputComponent } from 'sql/parts/notebook/cellViews/output.component'; +import { PlaceholderCellComponent } from 'sql/parts/notebook/cellViews/placeholderCell.component'; import LoadingSpinner from 'sql/parts/modelComponents/loadingSpinner.component'; export const NotebookModule = (params, selector: string, instantiationService: IInstantiationService): any => { @@ -41,6 +40,7 @@ export const NotebookModule = (params, selector: string, instantiationService: I CodeComponent, CodeCellComponent, TextCellComponent, + PlaceholderCellComponent, NotebookComponent, ComponentHostDirective, OutputAreaComponent, diff --git a/src/sql/parts/notebook/notebookStyles.ts b/src/sql/parts/notebook/notebookStyles.ts index 3f7bf648d9..16d4512d06 100644 --- a/src/sql/parts/notebook/notebookStyles.ts +++ b/src/sql/parts/notebook/notebookStyles.ts @@ -6,7 +6,7 @@ import 'vs/css!./notebook'; import { registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; -import { activeContrastBorder, buttonBackground } from 'vs/platform/theme/common/colorRegistry'; +import { activeContrastBorder, buttonBackground, textLinkForeground } from 'vs/platform/theme/common/colorRegistry'; registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { @@ -48,4 +48,16 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { } `); } + + // Styling for all links in notebooks + const linkForeground = theme.getColor(textLinkForeground); + if (linkForeground) { + collector.addRule(` + .notebookEditor a:link { + text-decoration: none; + font-weight: bold; + color: ${linkForeground}; + } + `); + } }); diff --git a/src/sql/parts/notebook/outputs/style/index.css b/src/sql/parts/notebook/outputs/style/index.css index dc234e6af3..c71a80a415 100644 --- a/src/sql/parts/notebook/outputs/style/index.css +++ b/src/sql/parts/notebook/outputs/style/index.css @@ -172,17 +172,14 @@ output-component .jp-RenderedHTMLCommon u { output-component .jp-RenderedHTMLCommon a:link { text-decoration: none; - color: var(--jp-content-link-color); } output-component .jp-RenderedHTMLCommon a:hover { text-decoration: underline; - color: var(--jp-content-link-color); } output-component .jp-RenderedHTMLCommon a:visited { text-decoration: none; - color: var(--jp-content-link-color); } /* Headings */ diff --git a/src/sqltest/parts/notebook/model/notebookModel.test.ts b/src/sqltest/parts/notebook/model/notebookModel.test.ts index f91d50c343..7f638870e2 100644 --- a/src/sqltest/parts/notebook/model/notebookModel.test.ts +++ b/src/sqltest/parts/notebook/model/notebookModel.test.ts @@ -103,7 +103,7 @@ describe('notebook model', function(): void { }); }); - it('Should create single cell if model has no contents', async function(): Promise { + it('Should create no cells if model has no contents', async function(): Promise { // Given an empty notebook let emptyNotebook: nb.INotebookContents = { cells: [], @@ -125,9 +125,8 @@ describe('notebook model', function(): void { let model = new NotebookModel(defaultModelOptions); await model.requestModelLoad(); - // Then I expect to have 1 code cell as the contents - should(model.cells).have.length(1); - should(model.cells[0].source).be.empty(); + // Then I expect to have 0 code cell as the contents + should(model.cells).have.length(0); }); it('Should throw if model load fails', async function(): Promise {