Notebook views UI 2 (#15865)

Provide access to the Notebook Views UI. Feature is hidden behind a feature flag.

Co-authored-by: rkselfhost <rkselfhost@outlook.com>
This commit is contained in:
Daniel Grajeda
2021-08-17 15:15:28 -06:00
committed by GitHub
parent 82576e54a4
commit 640c6f30f4
29 changed files with 989 additions and 140 deletions

View File

@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.nb-grid-stack .grid-stack-item.notebook-cell .actionbar {
.nb-grid-stack .grid-stack-item .notebook-cell .actionbar {
border-width: 1px;
border-style: solid;
position: absolute;
@@ -11,48 +11,48 @@
top: -20px;
}
.nb-grid-stack .grid-stack-item.notebook-cell .actionbar .carbon-taskbar .monaco-action-bar.animated {
.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 {
.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 {
.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 {
.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 {
.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 {
.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 {
.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 {
.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 {
.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 {
.nb-grid-stack .grid-stack-item .notebook-cell .actionbar .notebook-button.toolbarIconStop {
margin: 0 2px;
background-size: 20px 25px;
height: 24px;

View File

@@ -7,9 +7,12 @@
<div style="overflow: hidden; width: 100%; height: 100%; display: flex; flex-flow: column">
<div #viewsToolbar class="editor-toolbar actionbar-container" style="flex: 0 0 auto; display: flex; flex-flow: row; width: 100%; align-items: center;"></div>
<div #container class="scrollable" style="flex: 1 1 auto; position: relative; outline: none" (click)="unselectActiveCell()" (scroll)="scrollHandler($event)">
<loading-spinner [loading]="isLoading"></loading-spinner>
<notebook-views-grid-component #gridstack [views]="views" [cells]="cells" [model]="model"></notebook-views-grid-component>
<div class="book-nav" #bookNav [style.visibility]="navigationVisibility">
</div>
<notebook-views-grid-component #gridstack [views]="views" [activeView]="activeView" [cells]="cells" [model]="model"></notebook-views-grid-component>
<ng-container *ngFor="let cell of cellsAwaitingInput">
<notebook-views-modal-component class="modal" [title]="cellsAwaitingInputModalTitle">
<views-code-cell-component *ngIf="cell.cellType === 'code'" [cellModel]="cell" [model]="model" [activeCellId]="activeCellId"></views-code-cell-component>
</notebook-views-modal-component>
</ng-container>
</div>
</div>

View File

@@ -2,8 +2,9 @@
* 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!./notebookViewsGrid';
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 { ICellModel, INotebookModel, ISingleNotebookEditOperation, NotebookContentChange, } 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';
@@ -11,18 +12,18 @@ import { NotebookModel } from 'sql/workbench/services/notebook/browser/models/no
import * as notebookUtils from 'sql/workbench/services/notebook/browser/models/notebookUtils';
import { IBootstrapParams } from 'sql/workbench/services/bootstrap/common/bootstrapParams';
import { Action } 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 { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
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 { CellType, CellTypes, NotebookChangeType } 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';
@@ -30,7 +31,11 @@ import { INotebookView, INotebookViewMetadata } from 'sql/workbench/services/not
import { NotebookViewsGridComponent } from 'sql/workbench/contrib/notebook/browser/notebookViews/notebookViewsGrid.component';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { DeleteViewAction, InsertCellAction, ViewSettingsAction } from 'sql/workbench/contrib/notebook/browser/notebookViews/notebookViewsActions';
import { RunAllCellsAction } from 'sql/workbench/contrib/notebook/browser/notebookActions';
import { DropdownMenuActionViewItem } from 'sql/base/browser/ui/buttonMenu/buttonMenu';
import { NotebookViewsActionProvider, AddCellAction, RunAllCellsAction } from 'sql/workbench/contrib/notebook/browser/notebookActions';
import * as DOM from 'vs/base/browser/dom';
import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview';
import { LabeledMenuItemActionItem } from 'sql/platform/actions/browser/menuEntryActionViewItem';
import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar';
export const NOTEBOOKVIEWS_SELECTOR: string = 'notebook-view-component';
@@ -44,7 +49,7 @@ export class NotebookViewComponent extends AngularDisposable implements INoteboo
@Input() model: NotebookModel;
@Input() activeView: INotebookView;
@Input() views: NotebookViewsExtension;
@Input() notebookMeta: INotebookViewMetadata;
@Input() notebookMetadata: INotebookViewMetadata;
@ViewChild('container', { read: ElementRef }) private _container: ElementRef;
@ViewChild('viewsToolbar', { read: ElementRef }) private _viewsToolbar: ElementRef;
@@ -57,12 +62,16 @@ export class NotebookViewComponent extends AngularDisposable implements INoteboo
private _modelReadyDeferred = new Deferred<NotebookModel>();
private _runAllCellsAction: RunAllCellsAction;
private _scrollTop: number;
public _cellsAwaitingInput: ICellModel[] = [];
public readonly cellsAwaitingInputModalTitle: string = localize('cellAwaitingInputTitle', "Cell Awaiting Input");
public readonly loadingMessage = localize('loading', "Loading");
constructor(
@Inject(IBootstrapParams) private _notebookParams: INotebookParams,
@Inject(forwardRef(() => ChangeDetectorRef)) private _changeRef: ChangeDetectorRef,
@Inject(IInstantiationService) private _instantiationService: IInstantiationService,
@Inject(IKeybindingService) private _keybindingService: IKeybindingService,
@Inject(IContextMenuService) private _contextMenuService: IContextMenuService,
@Inject(INotificationService) private _notificationService: INotificationService,
@Inject(INotebookService) private _notebookService: INotebookService,
@Inject(IConnectionManagementService) private _connectionManagementService: IConnectionManagementService,
@@ -120,12 +129,19 @@ export class NotebookViewComponent extends AngularDisposable implements INoteboo
if (!isUndefinedOrNull(endCell)) {
endIndex = codeCells.findIndex(c => c.id === endCell.id);
}
let statusNotification = this._notificationService.notify({ severity: Severity.Info, message: localize('startingExecution', "Starting execution"), progress: { total: endIndex + 1, worked: 0 } });
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."));
statusNotification.updateMessage(localize('runningCell', "Running cell {0} of {1}", (i + 1), (endIndex)));
statusNotification.progress.worked(i);
try {
await this.runCell(codeCells[i]);
} catch (error) {
statusNotification.updateSeverity(Severity.Error);
statusNotification.updateMessage(localize('cellRunFailed', "Run Cells failed - See error in output of the currently selected cell for more information."));
return false;
}
}
statusNotification.close();
}
return true;
}
@@ -153,10 +169,11 @@ export class NotebookViewComponent extends AngularDisposable implements INoteboo
ngOnInit() {
this.initViewsToolbar();
this._notebookService.addNotebookEditor(this);
this._modelReadyDeferred.resolve(this.model);
this._notebookService.addNotebookEditor(this);
this.setScrollPosition();
this._register(this.model.contentChanged((e) => this.handleContentChanged(e)));
this.doLoad().catch(e => onUnexpectedError(e));
}
@@ -249,6 +266,25 @@ export class NotebookViewComponent extends AngularDisposable implements INoteboo
let viewOptions = this._instantiationService.createInstance(ViewSettingsAction, this.views);
let viewsContainer = document.createElement('li');
let viewsActionsProvider = new NotebookViewsActionProvider(viewsContainer, this.views, this.modelReady, this._notebookService, this._notificationService, this._instantiationService);
let viewsButton = this._instantiationService.createInstance(AddCellAction, 'notebook.OpenViews', undefined, 'notebook-button masked-pseudo code');
let viewsDropdownContainer = DOM.$('li.action-item');
viewsDropdownContainer.setAttribute('role', 'presentation');
let viewsDropdownMenuActionViewItem = new DropdownMenuActionViewItem(
viewsButton,
viewsActionsProvider,
this._contextMenuService,
undefined,
this._actionBar.actionRunner,
undefined,
'codicon notebook-button masked-pseudo masked-pseudo-after icon-dashboard-view dropdown-arrow',
this.activeView?.name,
() => AnchorAlignment.RIGHT
);
viewsDropdownMenuActionViewItem.render(viewsDropdownContainer);
viewsDropdownMenuActionViewItem.setActionContext(this._notebookParams.notebookUri);
let deleteView = this._instantiationService.createInstance(DeleteViewAction, this.views);
this._actionBar.setContent([
@@ -257,6 +293,7 @@ export class NotebookViewComponent extends AngularDisposable implements INoteboo
{ action: insertCellsAction },
{ action: this._runAllCellsAction },
{ element: spacerElement },
{ element: viewsDropdownContainer },
{ action: viewOptions },
{ action: deleteView }
]);
@@ -276,6 +313,27 @@ export class NotebookViewComponent extends AngularDisposable implements INoteboo
return undefined;
}
private handleContentChanged(change: NotebookContentChange) {
switch (change.changeType) {
case NotebookChangeType.CellAwaitingInput: {
const cell: ICellModel = change?.cells[0];
this._cellsAwaitingInput.push(cell);
this.detectChanges();
break;
}
case NotebookChangeType.CellExecuted: {
const cell: ICellModel = change?.cells[0];
const indexToRemove = this._cellsAwaitingInput.findIndex(c => c.id === cell.id);
if (indexToRemove >= 0) {
this._cellsAwaitingInput.splice(indexToRemove, 1);
this.model.serializationStateChanged(NotebookChangeType.CellsModified, cell);
this.detectChanges();
}
break;
}
}
}
private detectChanges(): void {
if (!(this._changeRef['destroyed'])) {
this._changeRef.detectChanges();
@@ -297,4 +355,8 @@ export class NotebookViewComponent extends AngularDisposable implements INoteboo
}
return editors;
}
public get cellsAwaitingInput(): ICellModel[] {
return this._gridstack.hiddenItems.filter(i => this._cellsAwaitingInput.includes(i.cell)).map(i => i.cell);
}
}

View File

@@ -6,13 +6,13 @@
-->
<ng-template #templateRef>
<div
*ngIf="display"
*ngIf="visible"
class="grid-stack-item"
attr.data-cell-id="{{cell.cellGuid}}"
attr.data-gs-width="{{width}}"
attr.data-gs-height="{{height}}"
attr.data-gs-x="{{x}}"
attr.data-gs-y="{{y}}"
attr.gs-w="{{width}}"
attr.gs-h="{{height}}"
attr.gs-x="{{x}}"
attr.gs-y="{{y}}"
(click)="selectCell(cell, $event)"
>
<div
@@ -23,7 +23,7 @@
<div #actionbar [style.display]="showActionBar ? 'block' : 'none'" class="actionbar"></div>
<div class="grid-stack-content-wrapper">
<div class="grid-stack-content-wrapper-inner">
<views-code-cell-component *ngIf="cell.cellType === 'code'" [cellModel]="cell" [model]="model" [activeCellId]="activeCellId">
<views-code-cell-component *ngIf="cell.cellType === 'code'" [cellModel]="cell" [model]="model" [activeCellId]="activeCellId" [visible]="visible">
</views-code-cell-component>
<text-cell-component *ngIf="cell.cellType === 'markdown'" [cellModel]="cell" [model]="model" [activeCellId]="activeCellId">
</text-cell-component>

View File

@@ -4,32 +4,36 @@
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./cellToolbar';
import * as DOM from 'vs/base/browser/dom';
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 { Component, OnInit, Input, ViewChild, TemplateRef, ElementRef, Inject, Output, EventEmitter, ChangeDetectorRef, forwardRef, SimpleChanges } from '@angular/core';
import { CellExecutionState, 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';
import { CellChangeEventType, INotebookView, INotebookViewCell } from 'sql/workbench/services/notebook/browser/notebookViews/notebookViews';
import { ITaskbarContent, Taskbar } from 'sql/base/browser/ui/taskbar/taskbar';
import { CellContext } from 'sql/workbench/contrib/notebook/browser/cellViews/codeActions';
import { RunCellAction, HideCellAction, ViewCellToggleMoreActions } from 'sql/workbench/contrib/notebook/browser/notebookViews/notebookViewsActions';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { CellTypes } from 'sql/workbench/services/notebook/common/contracts';
import { AngularDisposable } from 'sql/base/browser/lifecycle';
import { IColorTheme, ICssStyleCollector, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { cellBorder, notebookToolbarSelectBackground } from 'sql/platform/theme/common/colorRegistry';
@Component({
selector: 'view-card-component',
templateUrl: decodeURI(require.toUrl('./notebookViewsCard.component.html'))
})
export class NotebookViewsCardComponent implements OnInit {
public _cellToggleMoreActions: ViewCellToggleMoreActions;
export class NotebookViewsCardComponent extends AngularDisposable implements OnInit {
private _actionbar: Taskbar;
private _metadata: INotebookViewCellMetadata;
private _activeView: INotebookView;
private _metadata: INotebookViewCell;
private _executionState: CellExecutionState;
private _pendingReinitialize: boolean = false;
public _cellToggleMoreActions: ViewCellToggleMoreActions;
@Input() cell: ICellModel;
@Input() model: NotebookModel;
@Input() views: NotebookViewsExtension;
@Input() activeView: INotebookView;
@Input() meta: boolean;
@Input() ready: boolean;
@Output() onChange: EventEmitter<any> = new EventEmitter();
@@ -39,28 +43,50 @@ export class NotebookViewsCardComponent implements OnInit {
constructor(
@Inject(forwardRef(() => ChangeDetectorRef)) private _changeRef: ChangeDetectorRef,
@Inject(IInstantiationService) private _instantiationService: IInstantiationService
) { }
@Inject(IInstantiationService) private _instantiationService: IInstantiationService,
) {
super();
}
ngOnInit() {
this.initActionBar();
}
ngOnChanges() {
if (this.views) {
this._activeView = this.views.getActiveView();
this._metadata = this.views.getCellMetadata(this.cell);
ngAfterViewInit() {
this.initialize();
}
ngOnChanges(changes: SimpleChanges) {
if (this.activeView && changes['activeView'] && changes['activeView'].currentValue?.guid !== changes['activeView'].previousValue?.guid) {
this._metadata = this.activeView.getCellMetadata(this.cell);
this._pendingReinitialize = true;
}
this.detectChanges();
}
ngAfterContentInit() {
if (this.views) {
this._activeView = this.views.getActiveView();
this._metadata = this.views.getCellMetadata(this.cell);
if (this.activeView) {
this._metadata = this.activeView.getCellMetadata(this.cell);
this._pendingReinitialize = true;
}
this.detectChanges();
}
ngAfterViewChecked() {
if (this._pendingReinitialize) {
this._pendingReinitialize = false;
this.initialize();
}
}
ngAfterViewInit() {
override ngOnDestroy() {
if (this._actionbar) {
this._actionbar.dispose();
}
}
public initialize(): void {
this.initActionBar();
this.detectChanges();
}
@@ -69,6 +95,10 @@ export class NotebookViewsCardComponent implements OnInit {
let taskbarContent: ITaskbarContent[] = [];
let context = new CellContext(this.model, this.cell);
if (this._actionbar) {
this._actionbar.dispose();
}
this._actionbar = new Taskbar(this._actionbarRef.nativeElement);
this._actionbar.context = { target: this._actionbarRef.nativeElement };
@@ -97,6 +127,10 @@ export class NotebookViewsCardComponent implements OnInit {
this.onChange.emit({ cell: this.cell, event: event });
}
get displayInputModal(): boolean {
return this.awaitingInput;
}
detectChanges() {
this._changeRef.detectChanges();
}
@@ -110,39 +144,77 @@ export class NotebookViewsCardComponent implements OnInit {
}
}
public set executionState(state: CellExecutionState) {
if (this._executionState !== state) {
this._executionState = state;
}
}
public get executionState(): CellExecutionState {
return this._executionState;
}
public hide(): void {
this.changed('hide');
}
public get data(): any {
return this._metadata?.views?.find(v => v.guid === this._activeView.guid);
public get metadata(): INotebookViewCell {
return this._metadata;
}
public get width(): number {
return this.data?.width ? this.data.width : DEFAULT_VIEW_CARD_WIDTH;
return this.metadata?.width ? this.metadata.width : DEFAULT_VIEW_CARD_WIDTH;
}
public get height(): number {
return this.data.height ? this.data.height : DEFAULT_VIEW_CARD_HEIGHT;
return this.metadata?.height ? this.metadata.height : DEFAULT_VIEW_CARD_HEIGHT;
}
public get x(): number {
return this.data?.x;
return this.metadata?.x;
}
public get y(): number {
return this.data?.y;
return this.metadata?.y;
}
public get display(): boolean {
if (!this._metadata || !this._activeView) {
/**
* Whether to display the card
*/
public get visible(): boolean {
if (!this.activeView) {
return true;
}
return !this.data?.hidden;
if (!this._metadata) { //Means not initialized
return false;
}
return !!this.activeView.displayedCells.find(c => c.cellGuid === this.cell.cellGuid);
}
/**
* Is the cell expecting input
*/
public get awaitingInput(): boolean {
return this.cell.future && this.cell.future.inProgress;
}
public get showActionBar(): boolean {
return this.cell.active;
}
}
registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => {
const cellBorderColor = theme.getColor(cellBorder);
if (cellBorderColor) {
collector.addRule(`.notebookEditor .nb-grid-stack .notebook-cell.active .actionbar { border-color: ${cellBorderColor};}`);
collector.addRule(`.notebookEditor .nb-grid-stack .notebook-cell.active .actionbar .codicon:before { background-color: ${cellBorderColor};}`);
}
// Cell toolbar background
const notebookToolbarSelectBackgroundColor = theme.getColor(notebookToolbarSelectBackground);
if (notebookToolbarSelectBackgroundColor) {
collector.addRule(`.notebookEditor .nb-grid-stack .notebook-cell.active .actionbar { background-color: ${notebookToolbarSelectBackgroundColor};}`);
}
});

View File

@@ -6,11 +6,11 @@
-->
<div style="width: 100%; height: 100%; display: flex; flex-flow: column">
<div style="flex: 0 0 auto; width: 100%; height: 100%; display: block">
<output-area-component *ngIf="cellModel.outputs && cellModel.outputs.length > 0" [cellModel]="cellModel" [activeCellId]="activeCellId">
<output-area-component *ngIf="viewCellModel.outputs && viewCellModel.outputs.length > 0" [cellModel]="viewCellModel" [activeCellId]="activeCellId">
</output-area-component>
<div *ngIf="!cellModel.outputs || !cellModel.outputs.length">
<div *ngIf="(!viewCellModel.outputs || !viewCellModel.outputs.length) && !isStdInVisible">
{{emptyCellText}}
</div>
<stdin-component *ngIf="isStdInVisible" [onSendInput]="inputDeferred" [stdIn]="stdIn" [cellModel]="cellModel"></stdin-component>
<stdin-component *ngIf="isStdInVisible" [onSendInput]="inputDeferred" [stdIn]="stdIn" [cellModel]="viewCellModel"></stdin-component>
</div>
</div>

View File

@@ -4,26 +4,89 @@
*--------------------------------------------------------------------------------------------*/
import { nb } from 'azdata';
import { ChangeDetectorRef, Component, forwardRef, Inject } from '@angular/core';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, forwardRef, Inject, Input, OnChanges, SimpleChange } from '@angular/core';
import { CodeCellComponent } from 'sql/workbench/contrib/notebook/browser/cellViews/codeCell.component';
import { ICellModel } from 'sql/workbench/services/notebook/browser/models/modelInterfaces';
import { CellModel } from 'sql/workbench/services/notebook/browser/models/cell';
import { localize } from 'vs/nls';
export const CODE_SELECTOR: string = 'views-code-cell-component';
@Component({
selector: CODE_SELECTOR,
changeDetection: ChangeDetectionStrategy.OnPush,
templateUrl: decodeURI(require.toUrl('./notebookViewsCodeCell.component.html'))
})
export class NotebookViewsCodeCellComponent extends CodeCellComponent {
public readonly emptyCellText: string = localize('viewsCodeCell.emptyCellText', "Please run this cell to view outputs.");
export class NotebookViewsCodeCellComponent extends CodeCellComponent implements OnChanges {
@Input() visible: boolean;
@Input() modal: boolean;
@Input() override cellModel: ICellModel;
public override stdIn: nb.IStdinMessage;
constructor(@Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef) {
super(changeRef);
}
get outputs(): nb.ICellOutput[] {
return this.cellModel.outputs.filter((output: nb.IDisplayResult) => output.data && output.data['text/plain'] !== '<IPython.core.display.HTML object>');
override ngOnInit() {
if (this.cellModel) {
this._register(this.cellModel.onCollapseStateChanged((state) => {
this._changeRef.detectChanges();
}));
this._register(this.cellModel.onParameterStateChanged((state) => {
this._changeRef.detectChanges();
}));
this._register(this.cellModel.onOutputsChanged(() => {
this._changeRef.detectChanges();
}));
// If we have a pre-existing message, listen to that
if (this.cellModel?.future?.msg) {
this.handleStdIn(this.cellModel.future.msg as nb.IStdinMessage);
}
// Register request handler, cleanup on dispose of this component
this.cellModel.setStdInHandler({ handle: (msg) => super.handleStdIn(msg) });
this._register({ dispose: () => this.cellModel.setStdInHandler(undefined) });
}
}
override ngOnChanges(changes: { [propKey: string]: SimpleChange }) {
for (let propName in changes) {
if (propName === 'activeCellId') {
let changedProp = changes[propName];
super.activeCellId = changedProp.currentValue;
break;
}
}
}
get outputs(): readonly nb.ICellOutput[] {
return this.cellModel.outputs;
}
/**
* Override the cell model for Views.
*/
get viewCellModel(): ICellModel {
return new NotebookViewsCellModel(this.cellModel.toJSON(), { notebook: this.cellModel.notebookModel, isTrusted: this.cellModel.trustedMode });
}
get emptyCellText(): string {
return localize('viewsCodeCell.emptyCellText', "Please run this cell to view outputs.");
}
}
export class NotebookViewsCellModel extends CellModel {
/**
* Override the cell output.
* - Hide plain text messages
*/
public override get outputs(): Array<nb.ICellOutput> {
return super.outputs
.filter((output: nb.IDisplayResult) => output.data === undefined || output?.data['text/plain'] !== '<IPython.core.display.HTML object>')
.map((output: nb.ICellOutput) => ({ ...output }))
.map((output: nb.ICellOutput) => { output.metadata = { ...output.metadata }; return output; });
}
}

View File

@@ -6,10 +6,15 @@
-->
<div class="nb-grid-stack grid-stack">
<ng-container *ngFor="let cell of cells">
<view-card-component #wrapper [ready]="loaded" [views]="views" [cell]="cell" [model]="model" (onChange)="onCellChanged($event)"></view-card-component>
<view-card-component #wrapper [ready]="loaded" [activeView]="activeView" [meta]="cell.metadata" [cell]="cell" [model]="model" (onChange)="onCellChanged($event)"></view-card-component>
<ng-content *ngTemplateOutlet='wrapper.templateRef'></ng-content>
</ng-container>
</div>
<div class="empty-message" *ngIf="empty && loaded">
{{emptyText}}
</div>
<div *ngIf="!loaded" style="display: block; position: absolute; height: 100%; width: 100%; z-index: 9999; background: #fff;">
<loading-spinner [loading]="!loaded" [loadingMessage]="loadingMessage"></loading-spinner>
</div>

View File

@@ -36,6 +36,8 @@ export class NotebookViewsGridComponent extends AngularDisposable implements OnI
protected _grid: GridStack;
protected _gridEnabled: boolean;
protected _loaded: boolean;
protected _gridView: INotebookView;
protected _activeCell: ICellModel;
protected _options: INotebookViewsGridOptions = {
cellHeight: 60
@@ -49,11 +51,11 @@ export class NotebookViewsGridComponent extends AngularDisposable implements OnI
}
public get empty(): boolean {
return !this._items || !this._items.find(item => item.display);
return !this._items || !this._items.find(item => item.visible);
}
public get hiddenItems(): NotebookViewsCardComponent[] {
return this._items?.filter(item => !item.display) ?? [];
return this._items?.filter(item => !item.visible) ?? [];
}
public get emptyText(): String {
@@ -77,17 +79,26 @@ export class NotebookViewsGridComponent extends AngularDisposable implements OnI
ngAfterContentChecked() {
//If activeView has changed or not present, we will destroy the grid in order to rebuild it later.
if (!this.activeView || this.activeView.guid !== this.activeView.guid) {
if (!this.activeView || this.activeView.guid !== this._gridView?.guid) {
if (this._grid) {
this.destroyGrid();
this._grid = undefined;
}
}
if (this.activeView && this.activeView.guid !== this._gridView?.guid) {
this.activeView.initialize();
}
if (this.model?.activeCell?.id !== this._activeCell?.id) {
this._activeCell = this.model.activeCell;
this.detectChanges();
}
}
ngAfterViewChecked() {
// If activeView has changed, rebuild the grid
if (!this.activeView || this.activeView.guid !== this.activeView.guid) {
if (this.activeView && this.activeView.guid !== this._gridView?.guid) {
this._gridView = this.activeView;
if (!this._grid) {
this.createGrid();
@@ -103,12 +114,15 @@ export class NotebookViewsGridComponent extends AngularDisposable implements OnI
}
private destroyGrid() {
this._gridEnabled = false;
this._grid.destroy(false);
if (this._grid) {
this._gridEnabled = false;
this._grid.destroy(false);
}
}
private createGrid() {
const isNew = this.activeView.isNew;
if (this._grid) {
this.destroyGrid();
}
@@ -191,6 +205,8 @@ export class NotebookViewsGridComponent extends AngularDisposable implements OnI
}
if (e.cell && e.event === 'insert') {
const component = this._items.find(x => x.cell.cellGuid === e.cell.cellGuid);
this.activeView.insertCell(e.cell);
this.detectChanges();
@@ -200,6 +216,18 @@ export class NotebookViewsGridComponent extends AngularDisposable implements OnI
this._grid.update(el, { x: 0, y: 0 });
this._grid.resizable(el, true);
this._grid.movable(el, true);
component.initialize();
}
if (e.cell && e.event === 'update') {
const el = this._grid.getGridItems().find(x => x.getAttribute('data-cell-id') === e.cell.cellGuid);
const cellData = this.activeView.getCellMetadata(e.cell);
this._grid.update(el, { x: cellData.x, y: cellData.y, w: cellData.width, h: cellData.height });
}
if (e.event === 'active') {
this._activeCell = e.cell;
}
this.detectChanges();

View File

@@ -2,9 +2,305 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
:root .nb-grid-stack .grid-stack-item > .ui-resizable-handle {
filter: none;
}
.nb-grid-stack {
position: relative;
}
.nb-grid-stack.grid-stack-rtl {
direction: ltr;
}
.nb-grid-stack.grid-stack-rtl > .grid-stack-item {
direction: rtl;
}
.nb-grid-stack .grid-stack-placeholder > .placeholder-content {
border: 1px dashed lightgray;
margin: 0;
position: absolute;
width: auto;
z-index: 0 !important;
text-align: center;
}
.nb-grid-stack > .grid-stack-item {
min-width: 8.3333333333%;
position: absolute;
padding: 0;
}
.nb-grid-stack > .grid-stack-item > .grid-stack-item-content {
margin: 0;
position: absolute;
width: auto;
overflow-x: hidden;
overflow-y: auto;
}
.nb-grid-stack > .grid-stack-item > .ui-resizable-handle {
position: absolute;
font-size: 0.1px;
display: block;
-ms-touch-action: none;
touch-action: none;
}
.nb-grid-stack > .grid-stack-item.ui-resizable-disabled > .ui-resizable-handle, .nb-grid-stack > .grid-stack-item.ui-resizable-autohide > .ui-resizable-handle {
display: none;
}
.nb-grid-stack > .grid-stack-item.ui-draggable-dragging, .nb-grid-stack > .grid-stack-item.ui-resizable-resizing {
z-index: 100;
}
.nb-grid-stack > .grid-stack-item.ui-draggable-dragging > .grid-stack-item-content, .nb-grid-stack > .grid-stack-item.ui-resizable-resizing > .grid-stack-item-content {
box-shadow: 1px 4px 6px rgba(0, 0, 0, 0.2);
opacity: 0.8;
}
.nb-grid-stack > .grid-stack-item > .ui-resizable-se, .nb-grid-stack > .grid-stack-item > .ui-resizable-sw {
background-image: url("../media/light/notebook_views_card_handle.svg");
background-repeat: no-repeat;
background-position: center;
-webkit-transform: rotate(45deg);
-moz-transform: rotate(45deg);
-ms-transform: rotate(45deg);
-o-transform: rotate(45deg);
transform: rotate(45deg);
}
.nb-grid-stack > .grid-stack-item > .ui-resizable-se {
-webkit-transform: rotate(-45deg);
-moz-transform: rotate(-45deg);
-ms-transform: rotate(-45deg);
-o-transform: rotate(-45deg);
transform: rotate(-45deg);
}
.nb-grid-stack > .grid-stack-item > .ui-resizable-nw {
cursor: nw-resize;
width: 20px;
height: 20px;
top: 0;
}
.nb-grid-stack > .grid-stack-item > .ui-resizable-n {
cursor: n-resize;
height: 10px;
top: 0;
left: 25px;
right: 25px;
}
.nb-grid-stack > .grid-stack-item > .ui-resizable-ne {
cursor: ne-resize;
width: 20px;
height: 20px;
top: 0;
}
.nb-grid-stack > .grid-stack-item > .ui-resizable-e {
cursor: e-resize;
width: 10px;
top: 15px;
bottom: 15px;
}
.nb-grid-stack > .grid-stack-item > .ui-resizable-se {
cursor: se-resize;
width: 20px;
height: 20px;
}
.nb-grid-stack > .grid-stack-item > .ui-resizable-s {
cursor: s-resize;
height: 10px;
left: 25px;
bottom: 0;
right: 25px;
}
.nb-grid-stack > .grid-stack-item > .ui-resizable-sw {
cursor: sw-resize;
width: 20px;
height: 20px;
bottom: 0;
}
.nb-grid-stack > .grid-stack-item > .ui-resizable-w {
cursor: w-resize;
width: 10px;
top: 15px;
bottom: 15px;
}
.nb-grid-stack > .grid-stack-item.ui-draggable-dragging > .ui-resizable-handle {
display: none !important;
}
.nb-grid-stack > .grid-stack-item[gs-w='1'] {
width: 8.3333333333%;
}
.nb-grid-stack > .grid-stack-item[gs-x='1'] {
left: 8.3333333333%;
}
.nb-grid-stack > .grid-stack-item[gs-min-w='1'] {
min-width: 8.3333333333%;
}
.nb-grid-stack > .grid-stack-item[gs-max-w='1'] {
max-width: 8.3333333333%;
}
.nb-grid-stack > .grid-stack-item[gs-w='2'] {
width: 16.6666666667%;
}
.nb-grid-stack > .grid-stack-item[gs-x='2'] {
left: 16.6666666667%;
}
.nb-grid-stack > .grid-stack-item[gs-min-w='2'] {
min-width: 16.6666666667%;
}
.nb-grid-stack > .grid-stack-item[gs-max-w='2'] {
max-width: 16.6666666667%;
}
.nb-grid-stack > .grid-stack-item[gs-w='3'] {
width: 25%;
}
.nb-grid-stack > .grid-stack-item[gs-x='3'] {
left: 25%;
}
.nb-grid-stack > .grid-stack-item[gs-min-w='3'] {
min-width: 25%;
}
.nb-grid-stack > .grid-stack-item[gs-max-w='3'] {
max-width: 25%;
}
.nb-grid-stack > .grid-stack-item[gs-w='4'] {
width: 33.3333333333%;
}
.nb-grid-stack > .grid-stack-item[gs-x='4'] {
left: 33.3333333333%;
}
.nb-grid-stack > .grid-stack-item[gs-min-w='4'] {
min-width: 33.3333333333%;
}
.nb-grid-stack > .grid-stack-item[gs-max-w='4'] {
max-width: 33.3333333333%;
}
.nb-grid-stack > .grid-stack-item[gs-w='5'] {
width: 41.6666666667%;
}
.nb-grid-stack > .grid-stack-item[gs-x='5'] {
left: 41.6666666667%;
}
.nb-grid-stack > .grid-stack-item[gs-min-w='5'] {
min-width: 41.6666666667%;
}
.nb-grid-stack > .grid-stack-item[gs-max-w='5'] {
max-width: 41.6666666667%;
}
.nb-grid-stack > .grid-stack-item[gs-w='6'] {
width: 50%;
}
.nb-grid-stack > .grid-stack-item[gs-x='6'] {
left: 50%;
}
.nb-grid-stack > .grid-stack-item[gs-min-w='6'] {
min-width: 50%;
}
.nb-grid-stack > .grid-stack-item[gs-max-w='6'] {
max-width: 50%;
}
.nb-grid-stack > .grid-stack-item[gs-w='7'] {
width: 58.3333333333%;
}
.nb-grid-stack > .grid-stack-item[gs-x='7'] {
left: 58.3333333333%;
}
.nb-grid-stack > .grid-stack-item[gs-min-w='7'] {
min-width: 58.3333333333%;
}
.nb-grid-stack > .grid-stack-item[gs-max-w='7'] {
max-width: 58.3333333333%;
}
.nb-grid-stack > .grid-stack-item[gs-w='8'] {
width: 66.6666666667%;
}
.nb-grid-stack > .grid-stack-item[gs-x='8'] {
left: 66.6666666667%;
}
.nb-grid-stack > .grid-stack-item[gs-min-w='8'] {
min-width: 66.6666666667%;
}
.nb-grid-stack > .grid-stack-item[gs-max-w='8'] {
max-width: 66.6666666667%;
}
.nb-grid-stack > .grid-stack-item[gs-w='9'] {
width: 75%;
}
.nb-grid-stack > .grid-stack-item[gs-x='9'] {
left: 75%;
}
.nb-grid-stack > .grid-stack-item[gs-min-w='9'] {
min-width: 75%;
}
.nb-grid-stack > .grid-stack-item[gs-max-w='9'] {
max-width: 75%;
}
.nb-grid-stack > .grid-stack-item[gs-w='10'] {
width: 83.3333333333%;
}
.nb-grid-stack > .grid-stack-item[gs-x='10'] {
left: 83.3333333333%;
}
.nb-grid-stack > .grid-stack-item[gs-min-w='10'] {
min-width: 83.3333333333%;
}
.nb-grid-stack > .grid-stack-item[gs-max-w='10'] {
max-width: 83.3333333333%;
}
.nb-grid-stack > .grid-stack-item[gs-w='11'] {
width: 91.6666666667%;
}
.nb-grid-stack > .grid-stack-item[gs-x='11'] {
left: 91.6666666667%;
}
.nb-grid-stack > .grid-stack-item[gs-min-w='11'] {
min-width: 91.6666666667%;
}
.nb-grid-stack > .grid-stack-item[gs-max-w='11'] {
max-width: 91.6666666667%;
}
.nb-grid-stack > .grid-stack-item[gs-w='12'] {
width: 100%;
}
.nb-grid-stack > .grid-stack-item[gs-x='12'] {
left: 100%;
}
.nb-grid-stack > .grid-stack-item[gs-min-w='12'] {
min-width: 100%;
}
.nb-grid-stack > .grid-stack-item[gs-max-w='12'] {
max-width: 100%;
}
.nb-grid-stack.grid-stack-1 > .grid-stack-item {
min-width: 100%;
}
.nb-grid-stack.grid-stack-1 > .grid-stack-item[gs-w='1'] {
width: 100%;
}
.nb-grid-stack.grid-stack-1 > .grid-stack-item[gs-x='1'] {
left: 100%;
}
.nb-grid-stack.grid-stack-1 > .grid-stack-item[gs-min-w='1'] {
min-width: 100%;
}
.nb-grid-stack.grid-stack-1 > .grid-stack-item[gs-max-w='1'] {
max-width: 100%;
}
.nb-grid-stack.grid-stack-animate, .nb-grid-stack.grid-stack-animate .grid-stack-item {
-webkit-transition: left 0.3s, top 0.3s, height 0.3s, width 0.3s;
-moz-transition: left 0.3s, top 0.3s, height 0.3s, width 0.3s;
-ms-transition: left 0.3s, top 0.3s, height 0.3s, width 0.3s;
-o-transition: left 0.3s, top 0.3s, height 0.3s, width 0.3s;
transition: left 0.3s, top 0.3s, height 0.3s, width 0.3s;
}
.nb-grid-stack.grid-stack-animate .grid-stack-item.ui-draggable-dragging, .nb-grid-stack.grid-stack-animate .grid-stack-item.ui-resizable-resizing, .nb-grid-stack.grid-stack-animate .grid-stack-item.grid-stack-placeholder {
-webkit-transition: left 0s, top 0s, height 0s, width 0s;
-moz-transition: left 0s, top 0s, height 0s, width 0s;
-ms-transition: left 0s, top 0s, height 0s, width 0s;
-o-transition: left 0s, top 0s, height 0s, width 0s;
transition: left 0s, top 0s, height 0s, width 0s;
}
.nb-grid-stack.ui-droppable.ui-droppable-over > *:not(.ui-droppable) {
pointer-events: none;
}
.nb-grid-stack {
margin: 0 8px;
margin: 0 2px;
}
.nb-grid-stack + .empty-message{
@@ -26,7 +322,7 @@
display: flex;
flex-direction: column;
flex: 1;
margin-bottom: 20px;
margin-bottom: 0px;
}
.nb-grid-stack > .grid-stack-item .grid-stack-item-content .grid-stack-content-wrapper-inner {
overflow-y: auto;
@@ -53,18 +349,14 @@
font-weight: 400;
}
.nb-grid-stack > .grid-stack-item .grid-stack-item-content .actionbar {
top: -14px;
z-index: 999;
}
.nb-grid-stack > .grid-stack-item.notebook-cell.active .actionbar {
z-index: 1;
transform: translate(0px, 7px);
}
.nb-grid-stack > .grid-stack-item.ui-draggable-dragging > .grid-stack-item-content,.nb-grid-stack > .grid-stack-item.ui-resizable-resizing > .grid-stack-item-content{
box-shadow:1px 4px 6px rgba(0,0,0,.2);
opacity:.8
}
.nb-grid-stack > .grid-stack-item > .ui-resizable-se,.nb-grid-stack > .grid-stack-item > .ui-resizable-sw{
.nb-grid-stack > .grid-stack-item > .ui-resizable-se,.nb-grid-stack > .grid-stack-item > .ui-resizable-sw {
background-image:url("../media/light/notebook_views_card_handle.svg");
background-repeat:no-repeat;
background-position:center;
@@ -102,8 +394,6 @@
cursor:se-resize;
width:20px;
height:20px;
right:5px;
bottom:0
}
.nb-grid-stack > .grid-stack-item > .ui-resizable-s{
cursor:s-resize;
@@ -126,3 +416,11 @@
top:15px;
bottom:15px
}
.nb-grid-stack text-cell-component .notebook-preview {
padding: 0px 0px 0px;
}
.nb-grid-stack output-area-component .notebook-output {
padding: 0px 0px 0px;
}

View File

@@ -0,0 +1,41 @@
/*---------------------------------------------------------------------------------------------
* 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!./notebookViewsModal';
import { Component, Input } from '@angular/core';
import { calloutDialogBodyBackground, calloutDialogForeground, calloutDialogInteriorBorder } from 'sql/platform/theme/common/colorRegistry';
import { IColorTheme, ICssStyleCollector, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
@Component({
selector: 'notebook-views-modal-component',
template: `
<div [class.modal]="displayInputModal">
<div class="content">
<div class="title">{{title}}</div>
<ng-content></ng-content>
</div>
</div>
`
})
export class NotebookViewsModalComponent {
@Input() title: boolean;
}
registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => {
const foreground = theme.getColor(calloutDialogForeground);
if (foreground) {
collector.addRule(`notebook-views-grid-component .modal { position: absolute; background: ${foreground};}`);
}
const internalBorder = theme.getColor(calloutDialogInteriorBorder);
if (internalBorder) {
collector.addRule(`notebook-views-grid-component .modal .content { border-color: ${internalBorder}; }`);
collector.addRule(`notebook-views-grid-component .modal .content .title { border-color: ${internalBorder}; }`);
}
const bodyBackground = theme.getColor(calloutDialogBodyBackground);
if (bodyBackground) {
collector.addRule(`notebook-views-grid-component .modal .content { background: ${bodyBackground}; }`);
}
});

View File

@@ -0,0 +1,18 @@
notebook-view-component .modal{
position: absolute;
opacity: .3;
}
notebook-view-component .modal .content {
height: calc(100% - 60px);
width: calc(100% - 60px);
position: absolute;
top: 30px;
left: 30px;
border: 1px solid;
}
notebook-view-component .modal .content .title {
padding: 10px;
border-bottom: 1px solid;
}

View File

@@ -0,0 +1,141 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Button } from 'sql/base/browser/ui/button/button';
import { IClipboardService } from 'sql/platform/clipboard/common/clipboardService';
import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry';
import { Modal } from 'sql/workbench/browser/modal/modal';
import { attachModalDialogStyler } from 'sql/workbench/common/styler';
import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { ILayoutService } from 'vs/platform/layout/browser/layoutService';
import { ILogService } from 'vs/platform/log/common/log';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import * as DOM from 'vs/base/browser/dom';
import { attachInputBoxStyler } from 'sql/platform/theme/common/styler';
import { localize } from 'vs/nls';
import { IInputOptions, MessageType } from 'vs/base/browser/ui/inputbox/inputBox';
import { InputBox } from 'sql/base/browser/ui/inputBox/inputBox';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { INotebookView } from 'sql/workbench/services/notebook/browser/notebookViews/notebookViews';
import { attachButtonStyler } from 'vs/platform/theme/common/styler';
export class ViewOptionsModal extends Modal {
private _submitButton: Button;
private _cancelButton: Button;
private _viewNameInput: InputBox;
constructor(
private _view: INotebookView,
@ILogService logService: ILogService,
@IThemeService themeService: IThemeService,
@ILayoutService layoutService: ILayoutService,
@IClipboardService clipboardService: IClipboardService,
@IContextKeyService contextKeyService: IContextKeyService,
@IAdsTelemetryService telemetryService: IAdsTelemetryService,
@IContextViewService private _contextViewService: IContextViewService,
@ITextResourcePropertiesService textResourcePropertiesService: ITextResourcePropertiesService,
) {
super(
localize("viewOptionsModal.title", "Configure View"),
'ViewOptionsModal',
telemetryService,
layoutService,
clipboardService,
themeService,
logService,
textResourcePropertiesService,
contextKeyService,
{ hasErrors: true, hasSpinner: true }
);
}
protected renderBody(container: HTMLElement): void {
const formWrapper = DOM.$<HTMLDivElement>('div#notebookviews-options-form');
formWrapper.style.padding = '10px';
DOM.append(container, formWrapper);
this._viewNameInput = this.createNameInput(formWrapper);
}
protected layout(height: number): void {
}
protected createNameInput(container: HTMLElement): InputBox {
return this.createInputBoxHelper(container, localize('viewOptionsModal.name', "View Name"), this._view.name, {
validationOptions: {
validation: (value: string) => {
if (!value) {
return ({ type: MessageType.ERROR, content: localize('viewOptionsModal.missingRequireField', "This field is required.") });
}
if (this._view.name !== value && !this._view.nameAvailable(value)) {
return ({ type: MessageType.ERROR, content: localize('viewOptionsModal.nameTaken', "This view name has already been taken.") });
}
return undefined;
}
},
ariaLabel: localize('viewOptionsModal.name', "View Name")
});
}
private createInputBoxHelper(container: HTMLElement, label: string, defaultValue: string = '', options?: IInputOptions): InputBox {
const inputContainer = DOM.append(container, DOM.$('.dialog-input-section'));
DOM.append(inputContainer, DOM.$('.dialog-label')).innerText = label;
const input = new InputBox(DOM.append(inputContainer, DOM.$('.dialog-input')), this._contextViewService, options);
input.value = defaultValue;
return input;
}
public override render() {
super.render();
this._submitButton = this.addFooterButton(localize('save', "Save"), () => this.onSubmitHandler());
this._cancelButton = this.addFooterButton(localize('cancel', "Cancel"), () => this.onCancelHandler());
this._register(attachInputBoxStyler(this._viewNameInput!, this._themeService));
this._register(attachButtonStyler(this._submitButton, this._themeService));
this._register(attachButtonStyler(this._cancelButton, this._themeService));
this._register(this._viewNameInput.onDidChange(v => this.validate()));
attachModalDialogStyler(this, this._themeService);
this.validate();
}
private validate() {
let valid = true;
if (!this._viewNameInput.validate()) {
valid = false;
}
this._submitButton.enabled = valid;
}
private onSubmitHandler() {
this._view.name = this._viewNameInput.value;
this._view.save();
this.close();
}
private onCancelHandler() {
this.close();
}
public close(): void {
return this.hide();
}
public open(): void {
this.show();
}
public override dispose(): void {
super.dispose();
}
}