mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-06 01:25:38 -05:00
Notebook Views Models (#13884)
* Add notebook editor Introduce notebook editor component to allow for separate notebook displays in order to accomodate notebook views * Localize notebook views configuration title * Refactor view mode and remove the views configuration while it is unused * Only fire view mode changed event when the value has been changed * Remove notebook views contribution * Add metadata capabilities * Notebook views definitions * Add notebook views models * Views test * Rename type arguments * Additional tests * Fix unused import * Update resize cell test
This commit is contained in:
@@ -128,6 +128,15 @@ export class CellModel extends Disposable implements ICellModel {
|
||||
return this._onCellModeChanged.event;
|
||||
}
|
||||
|
||||
public set metadata(data: any) {
|
||||
this._metadata = data;
|
||||
this.sendChangeToNotebook(NotebookChangeType.CellMetadataUpdated);
|
||||
}
|
||||
|
||||
public get metadata(): any {
|
||||
return this._metadata;
|
||||
}
|
||||
|
||||
public get isEditMode(): boolean {
|
||||
return this._isEditMode;
|
||||
}
|
||||
|
||||
@@ -345,6 +345,16 @@ export interface INotebookModel {
|
||||
*/
|
||||
viewMode: ViewMode;
|
||||
|
||||
/**
|
||||
* Add custom metadata values to the notebook
|
||||
*/
|
||||
setMetaValue(key: string, value: any);
|
||||
|
||||
/**
|
||||
* Get a custom metadata value from the notebook
|
||||
*/
|
||||
getMetaValue(key: string): any;
|
||||
|
||||
/**
|
||||
* Change the current kernel from the Kernel dropdown
|
||||
* @param displayName kernel name (as displayed in Kernel dropdown)
|
||||
@@ -476,6 +486,7 @@ export interface ICellModel {
|
||||
source: string | string[];
|
||||
cellType: CellType;
|
||||
trustedMode: boolean;
|
||||
metadata: any | undefined;
|
||||
active: boolean;
|
||||
hover: boolean;
|
||||
executionCount: number | undefined;
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { INotebookModel, ICellModel } from 'sql/workbench/services/notebook/browser/models/modelInterfaces';
|
||||
import { NotebookChangeType } from 'sql/workbench/services/notebook/common/contracts';
|
||||
|
||||
export class NotebookExtension<TNotebookMeta, TCellMeta> {
|
||||
readonly version = 1;
|
||||
readonly extensionName = 'azuredatastudio';
|
||||
readonly extensionNamespace = 'extensions';
|
||||
|
||||
public getNotebookMetadata(notebook: INotebookModel): TNotebookMeta {
|
||||
const metadata = notebook.getMetaValue(this.extensionNamespace) || {};
|
||||
return metadata[this.extensionName] as TNotebookMeta;
|
||||
}
|
||||
|
||||
public setNotebookMetadata(notebook: INotebookModel, metadata: TNotebookMeta) {
|
||||
const meta = {};
|
||||
meta[this.extensionName] = metadata;
|
||||
notebook.setMetaValue(this.extensionNamespace, meta);
|
||||
notebook.serializationStateChanged(NotebookChangeType.MetadataChanged);
|
||||
}
|
||||
|
||||
public getCellMetadata(cell: ICellModel): TCellMeta {
|
||||
const namespaceMeta = cell.metadata[this.extensionNamespace] || {};
|
||||
return namespaceMeta[this.extensionName] as TCellMeta;
|
||||
}
|
||||
|
||||
public setCellMetadata(cell: ICellModel, metadata: TCellMeta) {
|
||||
const meta = {};
|
||||
meta[this.extensionName] = metadata;
|
||||
cell.metadata[this.extensionNamespace] = meta;
|
||||
cell.sendChangeToNotebook(NotebookChangeType.CellsModified);
|
||||
}
|
||||
}
|
||||
@@ -284,6 +284,26 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
||||
return this._viewMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add custom metadata values to the notebook
|
||||
*/
|
||||
public setMetaValue(key: string, value: any) {
|
||||
this._existingMetadata[key] = value;
|
||||
let changeInfo: NotebookContentChange = {
|
||||
changeType: NotebookChangeType.MetadataChanged,
|
||||
isDirty: true,
|
||||
cells: [],
|
||||
};
|
||||
this._contentChangedEmitter.fire(changeInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a custom metadata value from the notebook
|
||||
*/
|
||||
public getMetaValue(key: string): any {
|
||||
return this._existingMetadata[key];
|
||||
}
|
||||
|
||||
public set viewMode(mode: ViewMode) {
|
||||
if (mode !== this._viewMode) {
|
||||
this._viewMode = mode;
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import { NotebookViewsExtension } from 'sql/workbench/services/notebook/browser/notebookViews/notebookViewsExtension';
|
||||
import { ICellModel } from 'sql/workbench/services/notebook/browser/models/modelInterfaces';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { localize } from 'vs/nls';
|
||||
import { INotebookView, INotebookViewCell } from 'sql/workbench/services/notebook/browser/notebookViews/notebookViews';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
|
||||
export const DEFAULT_VIEW_CARD_HEIGHT = 4;
|
||||
export const DEFAULT_VIEW_CARD_WIDTH = 12;
|
||||
|
||||
export class ViewNameTakenError extends Error { }
|
||||
|
||||
export class NotebookViewModel implements INotebookView {
|
||||
private _onDeleted = new Emitter<INotebookView>();
|
||||
|
||||
public readonly guid: string;
|
||||
public readonly onDeleted = this._onDeleted.event;
|
||||
|
||||
constructor(
|
||||
protected _name: string,
|
||||
private _notebookViews: NotebookViewsExtension
|
||||
) {
|
||||
this.guid = generateUuid();
|
||||
}
|
||||
|
||||
public initialize(): void {
|
||||
const cells = this._notebookViews.notebook.cells;
|
||||
cells.forEach((cell, idx) => { this.initializeCell(cell, idx); });
|
||||
}
|
||||
|
||||
protected initializeCell(cell: ICellModel, idx: number) {
|
||||
let meta = this._notebookViews.getCellMetadata(cell);
|
||||
|
||||
if (!meta) {
|
||||
this._notebookViews.initializeCell(cell);
|
||||
meta = this._notebookViews.getCellMetadata(cell);
|
||||
}
|
||||
|
||||
meta.views.push({
|
||||
guid: this.guid,
|
||||
hidden: false,
|
||||
y: idx * DEFAULT_VIEW_CARD_HEIGHT,
|
||||
x: 0,
|
||||
});
|
||||
}
|
||||
|
||||
public get name(): string {
|
||||
return this._name;
|
||||
}
|
||||
|
||||
public set name(name: string) {
|
||||
if (this.name !== name && this._notebookViews.viewNameIsTaken(name)) {
|
||||
throw new ViewNameTakenError(localize('notebookView.nameTaken', 'A view with the name {0} already exists in this notebook.', name));
|
||||
}
|
||||
this._name = name;
|
||||
}
|
||||
|
||||
public nameAvailable(name: string): boolean {
|
||||
return !this._notebookViews.viewNameIsTaken(name);
|
||||
}
|
||||
|
||||
public getCellMetadata(cell: ICellModel): INotebookViewCell {
|
||||
const meta = this._notebookViews.getCellMetadata(cell);
|
||||
return meta?.views?.find(view => view.guid === this.guid);
|
||||
}
|
||||
|
||||
public get hiddenCells(): Readonly<ICellModel[]> {
|
||||
return this.cells.filter(cell => this.getCellMetadata(cell)?.hidden);
|
||||
}
|
||||
|
||||
public get cells(): Readonly<ICellModel[]> {
|
||||
return this._notebookViews.notebook.cells;
|
||||
}
|
||||
|
||||
public getCell(guid: string): Readonly<ICellModel> {
|
||||
return this._notebookViews.notebook.cells.find(cell => cell.cellGuid === guid);
|
||||
}
|
||||
|
||||
public insertCell(cell: ICellModel) {
|
||||
this._notebookViews.updateCell(cell, this, { hidden: false });
|
||||
}
|
||||
|
||||
public hideCell(cell: ICellModel) {
|
||||
this._notebookViews.updateCell(cell, this, { hidden: true });
|
||||
}
|
||||
|
||||
public moveCell(cell: ICellModel, x: number, y: number) {
|
||||
this._notebookViews.updateCell(cell, this, { x, y });
|
||||
}
|
||||
|
||||
public resizeCell(cell: ICellModel, width: number, height: number) {
|
||||
this._notebookViews.updateCell(cell, this, { width, height });
|
||||
}
|
||||
|
||||
public save() {
|
||||
this._notebookViews.commit();
|
||||
}
|
||||
|
||||
public delete() {
|
||||
this._notebookViews.removeView(this.guid);
|
||||
this._onDeleted.fire(this);
|
||||
}
|
||||
}
|
||||
60
src/sql/workbench/services/notebook/browser/notebookViews/notebookViews.d.ts
vendored
Normal file
60
src/sql/workbench/services/notebook/browser/notebookViews/notebookViews.d.ts
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ICellModel } from 'sql/workbench/services/notebook/browser/models/modelInterfaces';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
|
||||
export type CellChangeEventType = 'hide' | 'insert' | 'active';
|
||||
|
||||
export type CellChangeEvent = {
|
||||
cell: ICellModel,
|
||||
event: CellChangeEventType
|
||||
};
|
||||
|
||||
export interface INotebookView {
|
||||
readonly guid: string;
|
||||
readonly onDeleted: Event<INotebookView>;
|
||||
|
||||
cells: Readonly<ICellModel[]>;
|
||||
hiddenCells: Readonly<ICellModel[]>;
|
||||
name: string;
|
||||
initialize(): void;
|
||||
nameAvailable(name: string): boolean;
|
||||
getCellMetadata(cell: ICellModel): INotebookViewCell;
|
||||
hideCell(cell: ICellModel): void;
|
||||
moveCell(cell: ICellModel, x: number, y: number): void;
|
||||
resizeCell(cell: ICellModel, width: number, height: number): void;
|
||||
getCell(guid: string): Readonly<ICellModel>;
|
||||
insertCell(cell: ICellModel): void;
|
||||
save(): void;
|
||||
delete(): void;
|
||||
}
|
||||
|
||||
export interface INotebookViewCell {
|
||||
readonly guid?: string;
|
||||
hidden?: boolean;
|
||||
x?: number;
|
||||
y?: number;
|
||||
width?: number;
|
||||
height?: number;
|
||||
}
|
||||
|
||||
/*
|
||||
* Represents the metadata that will be stored for the
|
||||
* view at the notebook level.
|
||||
*/
|
||||
export interface INotebookViewMetadata {
|
||||
version: number;
|
||||
activeView: string;
|
||||
views: INotebookView[];
|
||||
}
|
||||
|
||||
/*
|
||||
* Represents the metadata that will be stored for the
|
||||
* view at the cell level.
|
||||
*/
|
||||
export interface INotebookViewCellMetadata {
|
||||
views: INotebookViewCell[];
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { INotebookModel, ICellModel } from 'sql/workbench/services/notebook/browser/models/modelInterfaces';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { localize } from 'vs/nls';
|
||||
import { NotebookViewModel } from 'sql/workbench/services/notebook/browser/notebookViews/notebookViewModel';
|
||||
import { NotebookExtension } from 'sql/workbench/services/notebook/browser/models/notebookExtension';
|
||||
import { INotebookView, INotebookViewCell, INotebookViewCellMetadata, INotebookViewMetadata } from 'sql/workbench/services/notebook/browser/notebookViews/notebookViews';
|
||||
|
||||
export class NotebookViewsExtension extends NotebookExtension<INotebookViewMetadata, INotebookViewCellMetadata> {
|
||||
static readonly defaultViewName = localize('notebookView.untitledView', "Untitled View");
|
||||
|
||||
readonly maxNameIterationAttempts = 100;
|
||||
readonly extension = 'azuredatastudio';
|
||||
readonly version = 1;
|
||||
|
||||
protected _metadata: INotebookViewMetadata;
|
||||
private _onViewDeleted = new Emitter<void>();
|
||||
|
||||
constructor(protected _notebook: INotebookModel) {
|
||||
super();
|
||||
this.loadOrInitialize();
|
||||
}
|
||||
|
||||
public loadOrInitialize() {
|
||||
this._metadata = this.getNotebookMetadata(this._notebook);
|
||||
|
||||
if (!this._metadata) {
|
||||
this.initializeNotebook();
|
||||
this.initializeCells();
|
||||
this.commit();
|
||||
}
|
||||
}
|
||||
|
||||
protected initializeNotebook() {
|
||||
this._metadata = {
|
||||
version: this.version,
|
||||
activeView: undefined,
|
||||
views: []
|
||||
};
|
||||
}
|
||||
|
||||
protected initializeCells() {
|
||||
const cells = this._notebook.cells;
|
||||
cells.forEach((cell) => {
|
||||
this.initializeCell(cell);
|
||||
});
|
||||
}
|
||||
|
||||
public initializeCell(cell: ICellModel) {
|
||||
const meta: INotebookViewCellMetadata = {
|
||||
views: []
|
||||
};
|
||||
|
||||
this.setCellMetadata(cell, meta);
|
||||
}
|
||||
|
||||
public createNewView(name?: string): INotebookView {
|
||||
const viewName = name || this.generateDefaultViewName();
|
||||
|
||||
const view = new NotebookViewModel(viewName, this);
|
||||
view.initialize();
|
||||
|
||||
this._metadata.views.push(view);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
public removeView(guid: string) {
|
||||
let viewToRemove = this._metadata.views.findIndex(view => view.guid === guid);
|
||||
if (viewToRemove !== -1) {
|
||||
let removedView = this._metadata.views.splice(viewToRemove, 1);
|
||||
|
||||
// Remove view data for each cell
|
||||
if (removedView.length) {
|
||||
this._notebook?.cells.forEach((cell) => {
|
||||
let meta = this.getCellMetadata(cell);
|
||||
meta.views.splice(viewToRemove, 1);
|
||||
this.setCellMetadata(cell, meta);
|
||||
});
|
||||
}
|
||||
|
||||
this.setNotebookMetadata(this.notebook, this._metadata);
|
||||
}
|
||||
|
||||
if (guid === this._metadata.activeView) {
|
||||
this._metadata.activeView = undefined;
|
||||
}
|
||||
|
||||
this._onViewDeleted.fire();
|
||||
this.commit();
|
||||
}
|
||||
|
||||
public generateDefaultViewName(): string {
|
||||
let i = 1;
|
||||
let name = NotebookViewsExtension.defaultViewName;
|
||||
|
||||
while (this.viewNameIsTaken(name) && i <= this.maxNameIterationAttempts) {
|
||||
name = `${NotebookViewsExtension.defaultViewName} ${i++}`;
|
||||
}
|
||||
|
||||
return i <= this.maxNameIterationAttempts ? name : generateUuid();
|
||||
}
|
||||
|
||||
public updateCell(cell: ICellModel, currentView: INotebookView, cellData: INotebookViewCell, override: boolean = false) {
|
||||
const cellMetadata = this.getCellMetadata(cell);
|
||||
const viewToUpdate = cellMetadata.views.findIndex(view => view.guid === currentView.guid);
|
||||
|
||||
if (viewToUpdate >= 0) {
|
||||
cellMetadata.views[viewToUpdate] = override ? cellData : { ...cellMetadata.views[viewToUpdate], ...cellData };
|
||||
this.setCellMetadata(cell, cellMetadata);
|
||||
}
|
||||
}
|
||||
|
||||
public get notebook(): INotebookModel {
|
||||
return this._notebook;
|
||||
}
|
||||
|
||||
public getViews(): INotebookView[] {
|
||||
return this._metadata.views;
|
||||
}
|
||||
|
||||
public getCells(): INotebookViewCellMetadata[] {
|
||||
return this._notebook.cells.map(cell => this.getCellMetadata(cell));
|
||||
}
|
||||
|
||||
public getActiveView(): INotebookView {
|
||||
return this.getViews().find(view => view.guid === this._metadata.activeView);
|
||||
}
|
||||
|
||||
public setActiveView(view: INotebookView) {
|
||||
this._metadata.activeView = view.guid;
|
||||
}
|
||||
|
||||
public commit() {
|
||||
this.setNotebookMetadata(this._notebook, this._metadata);
|
||||
}
|
||||
|
||||
public viewNameIsTaken(name: string): boolean {
|
||||
return !!this.getViews().find(v => v.name.toLowerCase() === name.toLowerCase());
|
||||
}
|
||||
|
||||
public get onViewDeleted(): Event<void> {
|
||||
return this._onViewDeleted.event;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user