mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-17 09:35:37 -05:00
Notebook Views autodash feature (#16238)
The autodash feature in notebook views creates an initial grid layout for users when a view is created. It is intended to reduce the effort required by the user to start editing their view. Instead of displaying every cell and stacking them vertically like the default notebook layout, we use guidelines to determine which cells are worth displaying and how to arrange them.
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
import { nb } from 'azdata';
|
||||
import { ICellModel } from 'sql/workbench/services/notebook/browser/models/modelInterfaces';
|
||||
import { INotebookView } from 'sql/workbench/services/notebook/browser/notebookViews/notebookViews';
|
||||
import { CellTypes } from 'sql/workbench/services/notebook/common/contracts';
|
||||
|
||||
class VisInfo<T> {
|
||||
public width: number;
|
||||
public height: number;
|
||||
public orderRank: number;
|
||||
public display: boolean;
|
||||
public cell: T;
|
||||
}
|
||||
|
||||
class DisplayCell<T> {
|
||||
constructor(private _item: T) { }
|
||||
|
||||
get item(): T {
|
||||
return this._item;
|
||||
}
|
||||
}
|
||||
|
||||
abstract class DisplayGroup<T> {
|
||||
public width: number;
|
||||
public height: number;
|
||||
public orderRank: number;
|
||||
public display: boolean;
|
||||
private _displayCells: DisplayCell<T>[] = [];
|
||||
private _visInfos: VisInfo<T>[] = [];
|
||||
|
||||
constructor() { }
|
||||
|
||||
addCell(cell: T, initialView: INotebookView) {
|
||||
const dCell = new DisplayCell<T>(cell);
|
||||
this._displayCells.push(dCell);
|
||||
this._visInfos.push(this.evaluateCell(cell, initialView));
|
||||
}
|
||||
|
||||
get visInfos(): VisInfo<T>[] {
|
||||
return this._visInfos;
|
||||
}
|
||||
|
||||
get displayCells(): DisplayCell<T>[] {
|
||||
return this._displayCells;
|
||||
}
|
||||
|
||||
abstract evaluateCell(cell: T, view: INotebookView): VisInfo<T>;
|
||||
}
|
||||
|
||||
class CellDisplayGroup extends DisplayGroup<ICellModel> {
|
||||
evaluateCell(cell: ICellModel, view: INotebookView): VisInfo<ICellModel> {
|
||||
let meta = view.getCellMetadata(cell);
|
||||
let visInfo = new VisInfo<ICellModel>();
|
||||
visInfo.cell = cell;
|
||||
|
||||
if (cell.cellType !== CellTypes.Code && !this.isHeader(cell)) {
|
||||
visInfo.display = false;
|
||||
return visInfo;
|
||||
}
|
||||
|
||||
if (cell.cellType === CellTypes.Code && (!cell.outputs || !cell.outputs.length)) {
|
||||
visInfo.display = false;
|
||||
return visInfo;
|
||||
}
|
||||
|
||||
//For headers
|
||||
if (this.isHeader(cell)) {
|
||||
visInfo.height = 1;
|
||||
}
|
||||
//For graphs
|
||||
if (this.hasGraph(cell)) {
|
||||
visInfo.width = 6;
|
||||
visInfo.height = 4;
|
||||
}
|
||||
//For tables
|
||||
else if (this.hasTable(cell)) {
|
||||
visInfo.height = Math.min(meta?.height, 3);
|
||||
} else {
|
||||
visInfo.height = Math.min(meta?.height, 3);
|
||||
}
|
||||
|
||||
visInfo.display = true;
|
||||
return visInfo;
|
||||
}
|
||||
|
||||
isHeader(cell: ICellModel): boolean {
|
||||
return cell.cellType === 'markdown' && cell.source.length === 1 && cell.source[0].startsWith('#');
|
||||
}
|
||||
|
||||
hasGraph(cell: ICellModel): boolean {
|
||||
return !!cell.outputs.find((o: nb.IDisplayResult) => o?.output_type === 'display_data' && o?.data.hasOwnProperty('application/vnd.plotly.v1+json'));
|
||||
}
|
||||
|
||||
hasTable(cell: ICellModel): boolean {
|
||||
return !!cell.outputs.find((o: nb.IDisplayResult) => o?.output_type === 'display_data' && o?.data.hasOwnProperty('application/vnd.dataresource+json'));
|
||||
}
|
||||
}
|
||||
|
||||
export function generateLayout(initialView: INotebookView): void {
|
||||
let displayGroup: CellDisplayGroup = new CellDisplayGroup();
|
||||
|
||||
const cells = initialView.cells;
|
||||
|
||||
cells.forEach((cell, idx) => {
|
||||
displayGroup.addCell(cell, initialView);
|
||||
});
|
||||
|
||||
displayGroup.visInfos.forEach((v) => {
|
||||
if (!v.display) {
|
||||
initialView.hideCell(v.cell);
|
||||
}
|
||||
|
||||
if (v.width || v.height) {
|
||||
initialView.resizeCell(v.cell, v.width, v.height);
|
||||
}
|
||||
});
|
||||
|
||||
initialView.compactCells();
|
||||
}
|
||||
@@ -11,23 +11,31 @@ import { generateUuid } from 'vs/base/common/uuid';
|
||||
|
||||
export const DEFAULT_VIEW_CARD_HEIGHT = 4;
|
||||
export const DEFAULT_VIEW_CARD_WIDTH = 12;
|
||||
export const GRID_COLUMNS = 12;
|
||||
|
||||
export class ViewNameTakenError extends Error { }
|
||||
|
||||
function cellCollides(c1: INotebookViewCell, c2: INotebookViewCell): boolean {
|
||||
return !((c1.y + c1.height <= c2.y) || (c1.x + c1.width <= c2.x) || (c1.x + c1.width <= c2.x) || (c2.x + c2.width <= c1.x));
|
||||
}
|
||||
|
||||
export class NotebookViewModel implements INotebookView {
|
||||
private _onDeleted = new Emitter<INotebookView>();
|
||||
private _isNew: boolean = false;
|
||||
|
||||
public readonly guid: string;
|
||||
public readonly onDeleted = this._onDeleted.event;
|
||||
|
||||
constructor(
|
||||
protected _name: string,
|
||||
private _notebookViews: NotebookViewsExtension
|
||||
private _notebookViews: NotebookViewsExtension,
|
||||
guid?: string
|
||||
) {
|
||||
this.guid = generateUuid();
|
||||
}
|
||||
|
||||
public initialize(): void {
|
||||
this._isNew = true;
|
||||
const cells = this._notebookViews.notebook.cells;
|
||||
cells.forEach((cell, idx) => { this.initializeCell(cell, idx); });
|
||||
}
|
||||
@@ -45,6 +53,8 @@ export class NotebookViewModel implements INotebookView {
|
||||
hidden: false,
|
||||
y: idx * DEFAULT_VIEW_CARD_HEIGHT,
|
||||
x: 0,
|
||||
width: DEFAULT_VIEW_CARD_WIDTH,
|
||||
height: DEFAULT_VIEW_CARD_HEIGHT
|
||||
});
|
||||
}
|
||||
|
||||
@@ -76,6 +86,10 @@ export class NotebookViewModel implements INotebookView {
|
||||
return this._notebookViews.notebook.cells;
|
||||
}
|
||||
|
||||
public get displayedCells(): Readonly<ICellModel[]> {
|
||||
return this.cells.filter(cell => !this.getCellMetadata(cell)?.hidden);
|
||||
}
|
||||
|
||||
public getCell(guid: string): Readonly<ICellModel> {
|
||||
return this._notebookViews.notebook.cells.find(cell => cell.cellGuid === guid);
|
||||
}
|
||||
@@ -92,8 +106,46 @@ export class NotebookViewModel implements INotebookView {
|
||||
this._notebookViews.updateCell(cell, this, { x, y });
|
||||
}
|
||||
|
||||
public resizeCell(cell: ICellModel, width: number, height: number) {
|
||||
this._notebookViews.updateCell(cell, this, { width, height });
|
||||
public resizeCell(cell: ICellModel, width?: number, height?: number) {
|
||||
let data: INotebookViewCell = {};
|
||||
|
||||
if (width) {
|
||||
data.width = width;
|
||||
}
|
||||
|
||||
if (height) {
|
||||
data.height = height;
|
||||
}
|
||||
|
||||
this._notebookViews.updateCell(cell, this, data);
|
||||
}
|
||||
|
||||
public getCellSize(cell: ICellModel): any {
|
||||
const meta = this.getCellMetadata(cell);
|
||||
return { width: meta.width, height: meta.height };
|
||||
}
|
||||
|
||||
public compactCells() {
|
||||
let cellsPlaced: INotebookViewCell[] = [];
|
||||
|
||||
this.displayedCells.forEach((cell: ICellModel) => {
|
||||
const c1 = this.getCellMetadata(cell);
|
||||
|
||||
for (let i = 0; ; i++) {
|
||||
const row = i % GRID_COLUMNS;
|
||||
const column = Math.floor(i / GRID_COLUMNS);
|
||||
|
||||
if (row + c1.width > GRID_COLUMNS) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!cellsPlaced.find((c2) => cellCollides(c2, { ...c1, x: row, y: column }))) {
|
||||
this._notebookViews.updateCell(cell, this, { x: row, y: column });
|
||||
cellsPlaced.push({ ...c1, x: row, y: column });
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public save() {
|
||||
@@ -105,6 +157,14 @@ export class NotebookViewModel implements INotebookView {
|
||||
this._onDeleted.fire(this);
|
||||
}
|
||||
|
||||
public get isNew(): boolean {
|
||||
return this._isNew;
|
||||
}
|
||||
|
||||
public markAsViewed() {
|
||||
this._isNew = false;
|
||||
}
|
||||
|
||||
public toJSON() {
|
||||
return { guid: this.guid, name: this._name } as NotebookViewModel;
|
||||
}
|
||||
|
||||
@@ -17,17 +17,21 @@ export interface INotebookView {
|
||||
readonly guid: string;
|
||||
readonly onDeleted: Event<INotebookView>;
|
||||
|
||||
isNew: boolean;
|
||||
cells: Readonly<ICellModel[]>;
|
||||
hiddenCells: Readonly<ICellModel[]>;
|
||||
displayedCells: 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;
|
||||
compactCells();
|
||||
resizeCell(cell: ICellModel, width: number, height: number): void;
|
||||
getCell(guid: string): Readonly<ICellModel>;
|
||||
insertCell(cell: ICellModel): void;
|
||||
markAsViewed(): void;
|
||||
save(): void;
|
||||
delete(): void;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user