diff --git a/src/sql/base/browser/ui/taskbar/taskbar.ts b/src/sql/base/browser/ui/taskbar/taskbar.ts index 2e2259c73c..250ffe6d15 100644 --- a/src/sql/base/browser/ui/taskbar/taskbar.ts +++ b/src/sql/base/browser/ui/taskbar/taskbar.ts @@ -146,4 +146,5 @@ export class Taskbar { public dispose(): void { this.actionBar.dispose(); } + } diff --git a/src/sql/parts/notebook/cellViews/codeCell.component.html b/src/sql/parts/notebook/cellViews/codeCell.component.html index 6c3fef99ca..a04b98a24b 100644 --- a/src/sql/parts/notebook/cellViews/codeCell.component.html +++ b/src/sql/parts/notebook/cellViews/codeCell.component.html @@ -8,7 +8,7 @@
-
+
diff --git a/src/sql/parts/notebook/cellViews/codeCell.component.ts b/src/sql/parts/notebook/cellViews/codeCell.component.ts index 700f6f273f..9aeff66b02 100644 --- a/src/sql/parts/notebook/cellViews/codeCell.component.ts +++ b/src/sql/parts/notebook/cellViews/codeCell.component.ts @@ -22,8 +22,8 @@ export const CODE_SELECTOR: string = 'code-cell-component'; templateUrl: decodeURI(require.toUrl('./codeCell.component.html')) }) export class CodeCellComponent extends CellView implements OnInit { + @ViewChild('codeCellOutput', { read: ElementRef }) private outputPreview: ElementRef; private _model: NotebookModel; - @Input() cellModel: ICellModel; @Input() set model(value: NotebookModel) { this._model = value; @@ -47,6 +47,8 @@ export class CodeCellComponent extends CellView implements OnInit { } private updateTheme(theme: IColorTheme): void { + let outputElement = this.outputPreview.nativeElement; + outputElement.style.borderTopColor = theme.getColor(themeColors.SIDE_BAR_BACKGROUND, true).toString(); } get model(): NotebookModel { diff --git a/src/sql/parts/notebook/cellViews/codeCell.css b/src/sql/parts/notebook/cellViews/codeCell.css index 8a9a18e0a7..ed8ad9bd4c 100644 --- a/src/sql/parts/notebook/cellViews/codeCell.css +++ b/src/sql/parts/notebook/cellViews/codeCell.css @@ -10,4 +10,5 @@ code-cell-component { code-cell-component .notebook-output { border-top-width: 1px; border-top-style: solid; -} + user-select: initial; +} \ No newline at end of file diff --git a/src/sql/parts/notebook/cellViews/output.component.ts b/src/sql/parts/notebook/cellViews/output.component.ts index b57a58e9d9..9d7de2c8c4 100644 --- a/src/sql/parts/notebook/cellViews/output.component.ts +++ b/src/sql/parts/notebook/cellViews/output.component.ts @@ -22,7 +22,8 @@ export const OUTPUT_SELECTOR: string = 'output-component'; export class OutputComponent extends AngularDisposable implements OnInit { @ViewChild('output', { read: ElementRef }) private outputElement: ElementRef; @Input() cellOutput: nb.ICellOutput; - @Input() trustedMode: boolean; + private _trusted: boolean; + private _initialized: boolean = false; private readonly _minimumHeight = 30; registry: RenderMimeRegistry; @@ -35,6 +36,11 @@ export class OutputComponent extends AngularDisposable implements OnInit { } ngOnInit() { + this.renderOutput(); + this._initialized = true; + } + + private renderOutput() { let node = this.outputElement.nativeElement; let output = this.cellOutput; let options = outputProcessor.getBundleOptions({ value: output, trusted: this.trustedMode }); @@ -45,6 +51,18 @@ export class OutputComponent extends AngularDisposable implements OnInit { public layout(): void { } + get trustedMode(): boolean { + return this._trusted; + } + + @Input() + set trustedMode(value: boolean) { + this._trusted = value; + if (this._initialized) { + this.renderOutput(); + } + } + protected createRenderedMimetype(options: MimeModel.IOptions, node: HTMLElement): void { let mimeType = this.registry.preferredMimeType( options.data, diff --git a/src/sql/parts/notebook/cellViews/outputArea.component.html b/src/sql/parts/notebook/cellViews/outputArea.component.html index 02f6d576b5..dafe51fb8f 100644 --- a/src/sql/parts/notebook/cellViews/outputArea.component.html +++ b/src/sql/parts/notebook/cellViews/outputArea.component.html @@ -5,7 +5,7 @@ *--------------------------------------------------------------------------------------------*/ -->
-
+
diff --git a/src/sql/parts/notebook/cellViews/textCell.component.ts b/src/sql/parts/notebook/cellViews/textCell.component.ts index f7299f78f7..6696a8541a 100644 --- a/src/sql/parts/notebook/cellViews/textCell.component.ts +++ b/src/sql/parts/notebook/cellViews/textCell.component.ts @@ -83,6 +83,9 @@ export class TextCellComponent extends CellView implements OnInit { this.updatePreview(); this._register(this.themeService.onDidColorThemeChange(this.updateTheme, this)); this.updateTheme(this.themeService.getColorTheme()); + this.cellModel.onOutputsChanged(e => { + this.updatePreview(); + }); } // Todo: implement layout diff --git a/src/sql/parts/notebook/media/dark/add_inverse.svg b/src/sql/parts/notebook/media/dark/add_inverse.svg new file mode 100644 index 0000000000..8542ea93aa --- /dev/null +++ b/src/sql/parts/notebook/media/dark/add_inverse.svg @@ -0,0 +1 @@ +add_12x12 \ No newline at end of file diff --git a/src/sql/parts/notebook/media/dark/nottrusted_inverse.svg b/src/sql/parts/notebook/media/dark/nottrusted_inverse.svg new file mode 100644 index 0000000000..bd3832e82a --- /dev/null +++ b/src/sql/parts/notebook/media/dark/nottrusted_inverse.svg @@ -0,0 +1 @@ +nontrust-inverse \ No newline at end of file diff --git a/src/sql/parts/notebook/media/dark/trusted_inverse.svg b/src/sql/parts/notebook/media/dark/trusted_inverse.svg new file mode 100644 index 0000000000..25967f45aa --- /dev/null +++ b/src/sql/parts/notebook/media/dark/trusted_inverse.svg @@ -0,0 +1 @@ +trust_inverse \ No newline at end of file diff --git a/src/sql/parts/notebook/media/light/add_code_cell.svg b/src/sql/parts/notebook/media/light/add_code_cell.svg deleted file mode 100644 index 375721a2d1..0000000000 --- a/src/sql/parts/notebook/media/light/add_code_cell.svg +++ /dev/null @@ -1 +0,0 @@ - code_cell \ No newline at end of file diff --git a/src/sql/parts/notebook/media/light/add_text_cell.svg b/src/sql/parts/notebook/media/light/add_text_cell.svg deleted file mode 100644 index f6c97e09f4..0000000000 --- a/src/sql/parts/notebook/media/light/add_text_cell.svg +++ /dev/null @@ -1 +0,0 @@ -text_cell \ No newline at end of file diff --git a/src/sql/parts/notebook/media/light/cell_output.svg b/src/sql/parts/notebook/media/light/cell_output.svg deleted file mode 100644 index 706b5a4f05..0000000000 --- a/src/sql/parts/notebook/media/light/cell_output.svg +++ /dev/null @@ -1 +0,0 @@ -cell_output \ No newline at end of file diff --git a/src/sql/parts/notebook/media/light/nottrusted.svg b/src/sql/parts/notebook/media/light/nottrusted.svg new file mode 100644 index 0000000000..cc199359bd --- /dev/null +++ b/src/sql/parts/notebook/media/light/nottrusted.svg @@ -0,0 +1 @@ +nontrust \ No newline at end of file diff --git a/src/sql/parts/notebook/media/light/trusted.svg b/src/sql/parts/notebook/media/light/trusted.svg new file mode 100644 index 0000000000..721fc0bd8f --- /dev/null +++ b/src/sql/parts/notebook/media/light/trusted.svg @@ -0,0 +1 @@ +trust \ No newline at end of file diff --git a/src/sql/parts/notebook/models/modelInterfaces.ts b/src/sql/parts/notebook/models/modelInterfaces.ts index 17baf6f61d..b94cb3608a 100644 --- a/src/sql/parts/notebook/models/modelInterfaces.ts +++ b/src/sql/parts/notebook/models/modelInterfaces.ts @@ -341,6 +341,7 @@ export interface ICellModel { readonly outputs: ReadonlyArray; equals(cellModel: ICellModel): boolean; toJSON(): nb.ICell; + onOutputsChanged: Event>; } export interface IModelFactory { diff --git a/src/sql/parts/notebook/notebook.component.ts b/src/sql/parts/notebook/notebook.component.ts index 5c05c767e6..0a8a32d294 100644 --- a/src/sql/parts/notebook/notebook.component.ts +++ b/src/sql/parts/notebook/notebook.component.ts @@ -31,7 +31,7 @@ import { IConnectionProfile } from 'sql/parts/connection/common/interfaces'; import { Taskbar } from 'sql/base/browser/ui/taskbar/taskbar'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; -import { KernelsDropdown, AttachToDropdown, AddCellAction } from 'sql/parts/notebook/notebookActions'; +import { KernelsDropdown, AttachToDropdown, AddCellAction, TrustedAction } from 'sql/parts/notebook/notebookActions'; import { attachSelectBoxStyler } from 'vs/platform/theme/common/styler'; export const NOTEBOOK_SELECTOR: string = 'notebook-component'; @@ -53,6 +53,7 @@ export class NotebookComponent extends AngularDisposable implements OnInit { private _modelReadyDeferred = new Deferred(); private _modelRegisteredDeferred = new Deferred(); private profile: IConnectionProfile; + private _trustedAction: TrustedAction; constructor( @@ -108,12 +109,23 @@ export class NotebookComponent extends AngularDisposable implements OnInit { } } - //Add cell based on cell type + // Add cell based on cell type public addCell(cellType: CellType) { this._model.addCell(cellType); } + // Updates Notebook model's trust details + public updateModelTrustDetails(isTrusted: boolean) + { + this._model.trustedMode = isTrusted; + this._model.cells.forEach(cell => { + cell.trustedMode = isTrusted; + }); + this.setDirty(true); + this._changeRef.detectChanges(); + } + public onKeyDown(event) { switch (event.key) { case 'ArrowDown': @@ -164,12 +176,23 @@ export class NotebookComponent extends AngularDisposable implements OnInit { await model.requestModelLoad(this.notebookParams.isTrusted); model.contentChanged((change) => this.handleContentChanged(change)); this._model = model; + this.updateToolbarComponents(this._model.trustedMode); this._register(model); this._modelRegisteredDeferred.resolve(this._model); model.backgroundStartSession(); this._changeRef.detectChanges(); } + // Updates toolbar components + private updateToolbarComponents(isTrusted: boolean) + { + if(this._trustedAction) + { + this._trustedAction.enabled = true; + this._trustedAction.trusted = isTrusted; + } + } + private get modelFactory(): IModelFactory { if (!this.notebookParams.modelFactory) { this.notebookParams.modelFactory = new ModelFactory(); @@ -212,12 +235,15 @@ export class NotebookComponent extends AngularDisposable implements OnInit { attachToInfoText.className = 'notebook-info-label'; attachToInfoText.innerText = 'Attach To: '; - let addCodeCellButton = new AddCellAction('notebook.AddCodeCell', localize('code', 'Code'), 'notebook-info-button'); + let addCodeCellButton = new AddCellAction('notebook.AddCodeCell', localize('code', 'Code'), 'notebook-button icon-add'); addCodeCellButton.cellType = CellTypes.Code; - let addTextCellButton = new AddCellAction('notebook.AddTextCell',localize('text', 'Text'), 'notebook-info-button'); + let addTextCellButton = new AddCellAction('notebook.AddTextCell',localize('text', 'Text'), 'notebook-button icon-add'); addTextCellButton.cellType = CellTypes.Markdown; + this._trustedAction = this.instantiationService.createInstance(TrustedAction, 'notebook.Trusted'); + this._trustedAction.enabled = false; + let taskbar = this.toolbar.nativeElement; this._actionBar = new Taskbar(taskbar, this.contextMenuService); this._actionBar.context = this; @@ -225,7 +251,8 @@ export class NotebookComponent extends AngularDisposable implements OnInit { { element: kernelContainer }, { element: attachToContainer }, { action: addCodeCellButton}, - { action: addTextCellButton} + { action: addTextCellButton}, + { action: this._trustedAction} ]); } diff --git a/src/sql/parts/notebook/notebook.css b/src/sql/parts/notebook/notebook.css index 5d1240483f..b745c693f4 100644 --- a/src/sql/parts/notebook/notebook.css +++ b/src/sql/parts/notebook/notebook.css @@ -24,7 +24,7 @@ padding-top: 0px; } -.notebookEditor .notebook-info-button { +.notebookEditor .notebook-button { display: inline-block; width: 100%; padding: 0px; @@ -34,5 +34,31 @@ background-size: 11px; margin-right: 0.3em; font-size: 11px; - background-image: url("./media/light/add.svg") +} + +.notebookEditor .notebook-button.icon-add{ + background-image: url("./media/light/add.svg"); +} + +.vs-dark .notebookEditor .notebook-button.icon-add, +.hc-black .notebookEditor .notebook-button.icon-add{ + background-image: url("./media/dark/add_inverse.svg"); +} + +.notebookEditor .notebook-button.icon-trusted{ + background-image: url("./media/light/trusted.svg"); +} + +.vs-dark .notebookEditor .notebook-button.icon-trusted, +.hc-black .notebookEditor .notebook-button.icon-trusted{ + background-image: url("./media/dark/trusted_inverse.svg"); +} + +.notebookEditor .notebook-button.icon-notTrusted{ + background-image: url("./media/light/nottrusted.svg"); +} + +.vs-dark .notebookEditor .notebook-button.icon-notTrusted, +.hc-black .notebookEditor .notebook-button.icon-notTrusted{ + background-image: url("./media/dark/nottrusted_inverse.svg"); } \ No newline at end of file diff --git a/src/sql/parts/notebook/notebookActions.ts b/src/sql/parts/notebook/notebookActions.ts index 2aa859d075..4e8184dafe 100644 --- a/src/sql/parts/notebook/notebookActions.ts +++ b/src/sql/parts/notebook/notebookActions.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as sqlops from 'sqlops'; +import * as sqlops from 'sqlops'; import { Action } from 'vs/base/common/actions'; import { TPromise } from 'vs/base/common/winjs.base'; @@ -14,13 +14,15 @@ import { SelectBox, ISelectBoxOptionsWithLabel } from 'sql/base/browser/ui/selec import { INotebookModel } from 'sql/parts/notebook/models/modelInterfaces'; import { CellTypes, CellType } from 'sql/parts/notebook/models/contracts'; import { NotebookComponent } from 'sql/parts/notebook/notebook.component'; +import { INotificationService, Severity, INotificationActions } from 'vs/platform/notification/common/notification'; +import { NotificationService } from 'vs/workbench/services/notification/common/notificationService'; const msgLoading = localize('loading', 'Loading kernels...'); const kernelLabel: string = localize('Kernel', 'Kernel: '); const attachToLabel: string = localize('AttachTo', 'Attach to: '); const msgLocalHost: string = localize('localhost', 'Localhost'); -//Action to add a cell to notebook based on cell type(code/markdown). +// Action to add a cell to notebook based on cell type(code/markdown). export class AddCellAction extends Action { public cellType: CellType; @@ -41,6 +43,51 @@ export class AddCellAction extends Action { } } +export class TrustedAction extends Action { + // Constants + private static readonly trustLabel = localize('trustLabel', 'Trusted'); + private static readonly notTrustLabel = localize('untrustLabel', 'Not Trusted'); + private static readonly alreadyTrustedMsg = localize('alreadyTrustedMsg', 'Notebook is already trusted.'); + private static readonly trustedCssClass = 'notebook-button icon-trusted'; + private static readonly notTrustedCssClass = 'notebook-button icon-notTrusted'; + // Properties + private _isTrusted: boolean = false; + public get trusted(): boolean { + return this._isTrusted; + } + public set trusted(value: boolean) { + this._isTrusted = value; + this._setClass(value ? TrustedAction.trustedCssClass : TrustedAction.notTrustedCssClass); + this._setLabel(value ? TrustedAction.trustLabel : TrustedAction.notTrustLabel); + } + + constructor( + id: string, + @INotificationService private _notificationService: INotificationService + ) { + super(id, TrustedAction.notTrustLabel, TrustedAction.notTrustedCssClass); + } + + public run(context: NotebookComponent): TPromise { + let self = this; + return new TPromise((resolve, reject) => { + try { + if (self._isTrusted) { + const actions: INotificationActions = { primary: [] }; + self._notificationService.notify({ severity: Severity.Info, message: TrustedAction.alreadyTrustedMsg, actions }); + } + else { + self.trusted = !self._isTrusted; + context.updateModelTrustDetails(self.trusted); + } + resolve(true); + } catch (e) { + reject(e); + } + }); + } +} + export class KernelsDropdown extends SelectBox { private model: INotebookModel; constructor(container: HTMLElement, contextViewProvider: IContextViewProvider, modelRegistered: Promise