diff --git a/.eslintrc.json b/.eslintrc.json
index fc9c8f771c..ad5f15a890 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -740,6 +740,8 @@
"angular2-grid",
"html-query-plan",
"turndown",
+ "gridstack",
+ "gridstack/**",
"mark.js",
"vscode-textmate",
"vscode-oniguruma",
diff --git a/ThirdPartyNotices.txt b/ThirdPartyNotices.txt
index 4c83c9d3de..deb81cda1a 100644
--- a/ThirdPartyNotices.txt
+++ b/ThirdPartyNotices.txt
@@ -29,6 +29,7 @@ expressly granted herein, whether by implication, estoppel or otherwise.
gc-signals: https://github.com/Microsoft/node-gc-signals
getmac: https://github.com/bevry/getmac
graceful-fs: https://github.com/isaacs/node-graceful-fs
+ gridstack: https://github.com/gridstack/gridstack.js
html-query-plan: https://github.com/JustinPealing/html-query-plan
http-proxy-agent: https://github.com/TooTallNate/node-https-proxy-agent
https-proxy-agent: https://github.com/TooTallNate/node-https-proxy-agent
@@ -493,6 +494,32 @@ IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
=========================================
END OF graceful-fs NOTICES AND INFORMATION
+%% gridstack NOTICES AND INFORMATION BEGIN HERE
+=========================================
+The MIT License (MIT)
+
+Copyright (c) 2014-2020 Alain Dumesny, Dylan Weiss, Pavel Reznikov
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+=========================================
+END OF gridstack NOTICES AND INFORMATION
+
%% html-query-plan NOTICES AND INFORMATION BEGIN HERE
=========================================
The MIT License (MIT)
diff --git a/package.json b/package.json
index 49844153c4..817fb23aa3 100644
--- a/package.json
+++ b/package.json
@@ -76,6 +76,7 @@
"chart.js": "^2.9.4",
"chokidar": "3.5.1",
"graceful-fs": "4.2.3",
+ "gridstack": "^3.1.3",
"html-query-plan": "git://github.com/kburtram/html-query-plan.git#2.6",
"http-proxy-agent": "^2.1.0",
"https-proxy-agent": "^2.2.3",
diff --git a/src/sql/workbench/contrib/notebook/browser/media/light/notebook_views_card_handle.svg b/src/sql/workbench/contrib/notebook/browser/media/light/notebook_views_card_handle.svg
new file mode 100644
index 0000000000..b48d5c1313
--- /dev/null
+++ b/src/sql/workbench/contrib/notebook/browser/media/light/notebook_views_card_handle.svg
@@ -0,0 +1 @@
+
diff --git a/src/sql/workbench/contrib/notebook/browser/notebook.module.ts b/src/sql/workbench/contrib/notebook/browser/notebook.module.ts
index d7d69a382c..37fa32300b 100644
--- a/src/sql/workbench/contrib/notebook/browser/notebook.module.ts
+++ b/src/sql/workbench/contrib/notebook/browser/notebook.module.ts
@@ -33,6 +33,10 @@ import { CollapseComponent } from 'sql/workbench/contrib/notebook/browser/cellVi
import { MarkdownToolbarComponent } from 'sql/workbench/contrib/notebook/browser/cellViews/markdownToolbar.component';
import { CellToolbarComponent } from 'sql/workbench/contrib/notebook/browser/cellViews/cellToolbar.component';
import { NotebookEditorComponent } from 'sql/workbench/contrib/notebook/browser/notebookEditor.component';
+import { NotebookViewComponent } from 'sql/workbench/contrib/notebook/browser/notebookViews/notebookViews.component';
+import { NotebookViewsCodeCellComponent } from 'sql/workbench/contrib/notebook/browser/notebookViews/notebookViewsCodeCell.component';
+import { NotebookViewsCardComponent } from 'sql/workbench/contrib/notebook/browser/notebookViews/notebookViewsCard.component';
+import { NotebookViewsGridComponent } from 'sql/workbench/contrib/notebook/browser/notebookViews/notebookViewsGrid.component';
const outputComponentRegistry = Registry.as(OutputComponentExtensions.CellComponentContributions);
@@ -54,6 +58,10 @@ export const NotebookModule = (params, selector: string, instantiationService: I
PlaceholderCellComponent,
NotebookComponent,
NotebookEditorComponent,
+ NotebookViewComponent,
+ NotebookViewsCardComponent,
+ NotebookViewsGridComponent,
+ NotebookViewsCodeCellComponent,
ComponentHostDirective,
OutputAreaComponent,
OutputComponent,
diff --git a/src/sql/workbench/contrib/notebook/browser/notebookViews/cellToolbar.css b/src/sql/workbench/contrib/notebook/browser/notebookViews/cellToolbar.css
new file mode 100644
index 0000000000..d7a20a413c
--- /dev/null
+++ b/src/sql/workbench/contrib/notebook/browser/notebookViews/cellToolbar.css
@@ -0,0 +1,61 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+.nb-grid-stack .grid-stack-item.notebook-cell .actionbar {
+ border-width: 1px;
+ border-style: solid;
+ position: absolute;
+ left: 20px;
+ top: -20px;
+}
+
+.nb-grid-stack .grid-stack-item.notebook-cell .actionbar .carbon-taskbar .monaco-action-bar.animated {
+ padding: 0;
+}
+
+.nb-grid-stack .grid-stack-item.notebook-cell .actionbar .carbon-taskbar.monaco-toolbar .monaco-action-bar.animated ul.actions-container {
+ display: flex;
+ list-style: none;
+ margin: 0;
+ padding: 0;
+}
+
+.nb-grid-stack .grid-stack-item.notebook-cell .actionbar ul.actions-container li.action-item {
+ display: inline-flex;
+ margin-right: 0;
+ text-align: center;
+}
+
+.nb-grid-stack .grid-stack-item.notebook-cell .actionbar ul.actions-container li:last-child {
+ margin-right: 0;
+}
+.nb-grid-stack .grid-stack-item.notebook-cell .actionbar .carbon-taskbar .action-label {
+ padding: 0;
+}
+.nb-grid-stack .grid-stack-item.notebook-cell .actionbar .monaco-action-bar .action-label {
+ margin-right: 0;
+}
+.nb-grid-stack .grid-stack-item.notebook-cell .actionbar ul.actions-container li a.masked-icon {
+ display: flex;
+}
+.nb-grid-stack .grid-stack-item.notebook-cell .actionbar ul.actions-container li a.masked-icon,
+.nb-grid-stack .grid-stack-item.notebook-cell .actionbar ul.actions-container li a.masked-icon:before {
+ height: 24px;
+ width: 29px;
+}
+.nb-grid-stack .grid-stack-item.notebook-cell .actionbar .codicon.masked-icon,
+.vs .nb-grid-stack .grid-stack-item.notebook-cell .actionbar .codicon.masked-icon,
+.vs-dark .nb-grid-stack .grid-stack-item.notebook-cell .actionbar .codicon.masked-icon,
+.hc-black .nb-grid-stack .grid-stack-item.notebook-cell .actionbar .codicon.masked-icon {
+ background-image: none;
+}
+
+.nb-grid-stack .grid-stack-item.notebook-cell .actionbar .notebook-button.toolbarIconStop {
+ margin: 0 2px;
+ background-size: 20px 25px;
+ height: 24px;
+ width: 29px;
+ background-repeat: no-repeat;
+}
diff --git a/src/sql/workbench/contrib/notebook/browser/notebookViews/notebookViews.component.html b/src/sql/workbench/contrib/notebook/browser/notebookViews/notebookViews.component.html
new file mode 100644
index 0000000000..14c14d7dfb
--- /dev/null
+++ b/src/sql/workbench/contrib/notebook/browser/notebookViews/notebookViews.component.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/src/sql/workbench/contrib/notebook/browser/notebookViews/notebookViews.component.ts b/src/sql/workbench/contrib/notebook/browser/notebookViews/notebookViews.component.ts
new file mode 100644
index 0000000000..84354f0e9f
--- /dev/null
+++ b/src/sql/workbench/contrib/notebook/browser/notebookViews/notebookViews.component.ts
@@ -0,0 +1,278 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+import { Component, Input, ViewChildren, QueryList, ChangeDetectorRef, forwardRef, Inject, ViewChild, ElementRef } from '@angular/core';
+import { ICellModel, INotebookModel, ISingleNotebookEditOperation } from 'sql/workbench/services/notebook/browser/models/modelInterfaces';
+import { CodeCellComponent } from 'sql/workbench/contrib/notebook/browser/cellViews/codeCell.component';
+import { TextCellComponent } from 'sql/workbench/contrib/notebook/browser/cellViews/textCell.component';
+import { ICellEditorProvider, INotebookParams, INotebookService, INotebookEditor, NotebookRange, INotebookSection, DEFAULT_NOTEBOOK_PROVIDER, SQL_NOTEBOOK_PROVIDER } from 'sql/workbench/services/notebook/browser/notebookService';
+import { NotebookModel } from 'sql/workbench/services/notebook/browser/models/notebookModel';
+import * as notebookUtils from 'sql/workbench/services/notebook/browser/models/notebookUtils';
+import { IBootstrapParams } from 'sql/workbench/services/bootstrap/common/bootstrapParams';
+import { Action, IActionViewItem } from 'vs/base/common/actions';
+import { LabeledMenuItemActionItem } from 'sql/platform/actions/browser/menuEntryActionViewItem';
+import { Taskbar } from 'sql/base/browser/ui/taskbar/taskbar';
+import { MenuItemAction } from 'vs/platform/actions/common/actions';
+import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
+import { INotificationService } from 'vs/platform/notification/common/notification';
+import { onUnexpectedError } from 'vs/base/common/errors';
+import { localize } from 'vs/nls';
+import { Deferred } from 'sql/base/common/promise';
+import { AngularDisposable } from 'sql/base/browser/lifecycle';
+import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
+import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
+import { CellType, CellTypes } from 'sql/workbench/services/notebook/common/contracts';
+import { isUndefinedOrNull } from 'vs/base/common/types';
+import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
+import { NotebookViewsExtension } from 'sql/workbench/services/notebook/browser/notebookViews/notebookViewsExtension';
+import { INotebookView } from 'sql/workbench/services/notebook/browser/notebookViews/notebookViews';
+import { NotebookViewsGridComponent } from 'sql/workbench/contrib/notebook/browser/notebookViews/notebookViewsGrid.component';
+
+export const NOTEBOOKVIEWS_SELECTOR: string = 'notebook-view-component';
+
+@Component({
+ selector: NOTEBOOKVIEWS_SELECTOR,
+ templateUrl: decodeURI(require.toUrl('./notebookViews.component.html'))
+})
+
+export class NotebookViewComponent extends AngularDisposable implements INotebookEditor {
+ @Input() model: NotebookModel;
+ @Input() activeView: INotebookView;
+ @Input() views: NotebookViewsExtension;
+
+ @ViewChild('container', { read: ElementRef }) private _container: ElementRef;
+ @ViewChild('viewsToolbar', { read: ElementRef }) private _viewsToolbar: ElementRef;
+ @ViewChild(NotebookViewsGridComponent) private _gridstack: NotebookViewsGridComponent;
+ @ViewChildren(CodeCellComponent) private _codeCells: QueryList;
+ @ViewChildren(TextCellComponent) private _textCells: QueryList;
+
+ protected _actionBar: Taskbar;
+ public previewFeaturesEnabled: boolean = false;
+ private _modelReadyDeferred = new Deferred();
+
+ private _scrollTop: number;
+
+ constructor(
+ @Inject(IBootstrapParams) private _notebookParams: INotebookParams,
+ @Inject(forwardRef(() => ChangeDetectorRef)) private _changeRef: ChangeDetectorRef,
+ @Inject(IKeybindingService) private _keybindingService: IKeybindingService,
+ @Inject(INotificationService) private _notificationService: INotificationService,
+ @Inject(INotebookService) private _notebookService: INotebookService,
+ @Inject(IConnectionManagementService) private _connectionManagementService: IConnectionManagementService,
+ @Inject(IConfigurationService) private _configurationService: IConfigurationService,
+ @Inject(IEditorService) private _editorService: IEditorService
+ ) {
+ super();
+ this._register(this._configurationService.onDidChangeConfiguration(e => {
+ this.previewFeaturesEnabled = this._configurationService.getValue('workbench.enablePreviewFeatures');
+ }));
+ }
+
+ public get notebookParams(): INotebookParams {
+ return this._notebookParams;
+ }
+
+ public get id(): string {
+ return this.notebookParams.notebookUri.toString();
+ }
+
+ isDirty(): boolean {
+ return this.notebookParams.input.isDirty();
+ }
+ isActive(): boolean {
+ return this._editorService.activeEditor ? this._editorService.activeEditor.matches(this.notebookParams.input) : false;
+ }
+ isVisible(): boolean {
+ let notebookEditor = this.notebookParams.input;
+ return this._editorService.visibleEditors.some(e => e.matches(notebookEditor));
+ }
+ executeEdits(edits: ISingleNotebookEditOperation[]): boolean {
+ throw new Error('Method not implemented.');
+ }
+ async runCell(cell: ICellModel): Promise {
+ await this.modelReady;
+ let uriString = cell.cellUri.toString();
+ if (this.model.cells.findIndex(c => c.cellUri.toString() === uriString) > -1) {
+ this.selectCell(cell);
+ return cell.runCell(this._notificationService, this._connectionManagementService);
+ } else {
+ throw new Error(localize('cellNotFound', "cell with URI {0} was not found in this model", uriString));
+ }
+ }
+
+ public async runAllCells(startCell?: ICellModel, endCell?: ICellModel): Promise {
+ await this.modelReady;
+ let codeCells = this.model.cells.filter(cell => cell.cellType === CellTypes.Code);
+ if (codeCells && codeCells.length) {
+ // For the run all cells scenario where neither startId not endId are provided, set defaults
+ let startIndex = 0;
+ let endIndex = codeCells.length;
+ if (!isUndefinedOrNull(startCell)) {
+ startIndex = codeCells.findIndex(c => c.id === startCell.id);
+ }
+ if (!isUndefinedOrNull(endCell)) {
+ endIndex = codeCells.findIndex(c => c.id === endCell.id);
+ }
+ for (let i = startIndex; i < endIndex; i++) {
+ let cellStatus = await this.runCell(codeCells[i]);
+ if (!cellStatus) {
+ throw new Error(localize('cellRunFailed', "Run Cells failed - See error in output of the currently selected cell for more information."));
+ }
+ }
+ }
+ return true;
+ }
+ clearOutput(cell: ICellModel): Promise {
+ throw new Error('Method not implemented.');
+ }
+ clearAllOutputs(): Promise {
+ throw new Error('Method not implemented.');
+ }
+ getSections(): INotebookSection[] {
+ throw new Error('Method not implemented.');
+ }
+ navigateToSection(sectionId: string): void {
+ throw new Error('Method not implemented.');
+ }
+ deltaDecorations(newDecorationRange: NotebookRange, oldDecorationRange: NotebookRange): void {
+ throw new Error('Method not implemented.');
+ }
+ addCell(cellType: CellType, index?: number, event?: UIEvent) {
+ throw new Error('Method not implemented.');
+ }
+ insertCell(cell: ICellModel) {
+ this._gridstack.onCellChanged({ cell: cell, event: 'insert' });
+ }
+
+ ngOnInit() {
+ this.initViewsToolbar();
+ this._notebookService.addNotebookEditor(this);
+ this._modelReadyDeferred.resolve(this.model);
+ this.setScrollPosition();
+
+ this.doLoad().catch(e => onUnexpectedError(e));
+ }
+
+ ngOnDestroy() {
+ this.dispose();
+ }
+
+ ngOnChanges() {
+ this.initViewsToolbar();
+ }
+
+ private async doLoad(): Promise {
+ await this.awaitNonDefaultProvider();
+ await this.model.requestModelLoad();
+ await this.model.onClientSessionReady;
+ this.detectChanges();
+ }
+
+ private async awaitNonDefaultProvider(): Promise {
+ // Wait on registration for now. Long-term would be good to cache and refresh
+ await this._notebookService.registrationComplete;
+ this.model.standardKernels = this._notebookParams.input.standardKernels;
+ // Refresh the provider if we had been using default
+ let providerInfo = await this._notebookParams.providerInfo;
+
+ if (DEFAULT_NOTEBOOK_PROVIDER === providerInfo.providerId) {
+ let providers = notebookUtils.getProvidersForFileName(this._notebookParams.notebookUri.fsPath, this._notebookService);
+ let tsqlProvider = providers.find(provider => provider === SQL_NOTEBOOK_PROVIDER);
+ providerInfo.providerId = tsqlProvider ? SQL_NOTEBOOK_PROVIDER : providers[0];
+ }
+ }
+
+ public get cells(): ICellModel[] {
+ return this.model ? this.model.cells : [];
+ }
+
+ public selectCell(cell: ICellModel, event?: Event) {
+ if (event) {
+ event.stopPropagation();
+ }
+ if (!this.model.activeCell || this.model.activeCell.id !== cell.id) {
+ this.model.updateActiveCell(cell);
+ this.detectChanges();
+ }
+ }
+
+ private setScrollPosition(): void {
+ if (this._notebookParams && this._notebookParams.input) {
+ this._register(this._notebookParams.input.layoutChanged(() => {
+ let containerElement = this._container.nativeElement;
+ containerElement.scrollTop = this._scrollTop;
+ }));
+ }
+ }
+
+ /**
+ * Saves scrollTop value on scroll change
+ */
+ public scrollHandler(event: Event) {
+ this._scrollTop = (event.srcElement).scrollTop;
+ }
+
+ public unselectActiveCell() {
+ this.model.updateActiveCell(undefined);
+ this.detectChanges();
+ }
+
+ protected initViewsToolbar() {
+ let taskbar = this._viewsToolbar.nativeElement;
+
+ if (!this._actionBar) {
+ this._actionBar = new Taskbar(taskbar, { actionViewItemProvider: action => this.actionItemProvider(action as Action) });
+ this._actionBar.context = this._notebookParams.notebookUri;//this.model;
+ taskbar.classList.add('in-preview');
+ }
+
+ let titleElement = document.createElement('li');
+ let titleText = document.createElement('span');
+ titleText.innerHTML = this.activeView?.name;
+ titleElement.appendChild(titleText);
+ titleElement.style.marginRight = '25px';
+ titleElement.style.minHeight = '25px';
+
+ this._actionBar.setContent([
+ { element: titleElement },
+ { element: Taskbar.createTaskbarSeparator() },
+ ]);
+ }
+
+ private actionItemProvider(action: Action): IActionViewItem {
+ // Check extensions to create ActionItem; otherwise, return undefined
+ // This is similar behavior that exists in MenuItemActionItem
+ if (action instanceof MenuItemAction) {
+
+ if (action.item.id.includes('jupyter.cmd') && this.previewFeaturesEnabled) {
+ action.tooltip = action.label;
+ action.label = '';
+ }
+ return new LabeledMenuItemActionItem(action, this._keybindingService, this._notificationService, 'notebook-button fixed-width');
+ }
+ return undefined;
+ }
+
+ private detectChanges(): void {
+ if (!(this._changeRef['destroyed'])) {
+ this._changeRef.detectChanges();
+ }
+ }
+
+ public get modelReady(): Promise {
+ return this._modelReadyDeferred.promise;
+ }
+
+ public get cellEditors(): ICellEditorProvider[] {
+ let editors: ICellEditorProvider[] = [];
+ if (this._codeCells) {
+ this._codeCells.toArray().forEach(cell => editors.push(...cell.cellEditors));
+ }
+ if (this._textCells) {
+ this._textCells.toArray().forEach(cell => editors.push(...cell.cellEditors));
+ editors.push(...this._textCells.toArray());
+ }
+ return editors;
+ }
+}
diff --git a/src/sql/workbench/contrib/notebook/browser/notebookViews/notebookViewsCard.component.html b/src/sql/workbench/contrib/notebook/browser/notebookViews/notebookViewsCard.component.html
new file mode 100644
index 0000000000..47c7fad7d3
--- /dev/null
+++ b/src/sql/workbench/contrib/notebook/browser/notebookViews/notebookViewsCard.component.html
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/sql/workbench/contrib/notebook/browser/notebookViews/notebookViewsCard.component.ts b/src/sql/workbench/contrib/notebook/browser/notebookViews/notebookViewsCard.component.ts
new file mode 100644
index 0000000000..cbe5e2d86c
--- /dev/null
+++ b/src/sql/workbench/contrib/notebook/browser/notebookViews/notebookViewsCard.component.ts
@@ -0,0 +1,110 @@
+/*---------------------------------------------------------------------------------------------
+ * 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, OnInit, Input, ViewChild, TemplateRef, ElementRef, Inject, Output, EventEmitter, ChangeDetectorRef, forwardRef } from '@angular/core';
+import { ICellModel } from 'sql/workbench/services/notebook/browser/models/modelInterfaces';
+import { NotebookModel } from 'sql/workbench/services/notebook/browser/models/notebookModel';
+import { DEFAULT_VIEW_CARD_HEIGHT, DEFAULT_VIEW_CARD_WIDTH } from 'sql/workbench/services/notebook/browser/notebookViews/notebookViewModel';
+import { NotebookViewsExtension } from 'sql/workbench/services/notebook/browser/notebookViews/notebookViewsExtension';
+import { CellChangeEventType, INotebookView, INotebookViewCellMetadata } from 'sql/workbench/services/notebook/browser/notebookViews/notebookViews';
+
+@Component({
+ selector: 'view-card-component',
+ templateUrl: decodeURI(require.toUrl('./notebookViewsCard.component.html'))
+})
+export class NotebookViewsCardComponent implements OnInit {
+ private _metadata: INotebookViewCellMetadata;
+ private _activeView: INotebookView;
+
+ @Input() cell: ICellModel;
+ @Input() model: NotebookModel;
+ @Input() views: NotebookViewsExtension;
+ @Input() ready: boolean;
+ @Output() onChange: EventEmitter = new EventEmitter();
+
+ @ViewChild('templateRef') templateRef: TemplateRef;
+ @ViewChild('item', { read: ElementRef }) private _item: ElementRef;
+
+ constructor(
+ @Inject(forwardRef(() => ChangeDetectorRef)) private _changeRef: ChangeDetectorRef,
+ ) { }
+
+ ngOnInit() { }
+
+ ngOnChanges() {
+ if (this.views) {
+ this._activeView = this.views.getActiveView();
+ this._metadata = this.views.getCellMetadata(this.cell);
+ }
+ }
+
+ ngAfterContentInit() {
+ if (this.views) {
+ this._activeView = this.views.getActiveView();
+ this._metadata = this.views.getCellMetadata(this.cell);
+ }
+ }
+
+ ngAfterViewInit() {
+ this.detectChanges();
+ }
+
+ get elementRef(): ElementRef {
+ return this._item;
+ }
+
+ changed(event: CellChangeEventType) {
+ this.onChange.emit({ cell: this.cell, event: event });
+ }
+
+ detectChanges() {
+ this._changeRef.detectChanges();
+ }
+
+ public selectCell(cell: ICellModel, event?: Event) {
+ event?.stopPropagation();
+
+ if (!this.model.activeCell || this.model.activeCell.id !== cell.id) {
+ this.model.updateActiveCell(cell);
+ this.changed('active');
+ }
+ }
+
+ public hide(): void {
+ this.changed('hide');
+ }
+
+ public get data(): any {
+ return this._metadata?.views?.find(v => v.guid === this._activeView.guid);
+ }
+
+ public get width(): number {
+ return this.data?.width ? this.data.width : DEFAULT_VIEW_CARD_WIDTH;
+ }
+
+ public get height(): number {
+ return this.data.height ? this.data.height : DEFAULT_VIEW_CARD_HEIGHT;
+ }
+
+ public get x(): number {
+ return this.data?.x;
+ }
+
+ public get y(): number {
+ return this.data?.y;
+ }
+
+ public get display(): boolean {
+ if (!this._metadata || !this._activeView) {
+ return true;
+ }
+
+ return !this.data?.hidden;
+ }
+
+ public get showActionBar(): boolean {
+ return this.cell.active;
+ }
+}
diff --git a/src/sql/workbench/contrib/notebook/browser/notebookViews/notebookViewsCodeCell.component.html b/src/sql/workbench/contrib/notebook/browser/notebookViews/notebookViewsCodeCell.component.html
new file mode 100644
index 0000000000..dbc2fd480a
--- /dev/null
+++ b/src/sql/workbench/contrib/notebook/browser/notebookViews/notebookViewsCodeCell.component.html
@@ -0,0 +1,16 @@
+
+