mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -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:
@@ -0,0 +1,280 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import * as TypeMoq from 'typemoq';
|
||||||
|
import { nb } from 'azdata';
|
||||||
|
import * as assert from 'assert';
|
||||||
|
|
||||||
|
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||||
|
import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService';
|
||||||
|
import { URI } from 'vs/base/common/uri';
|
||||||
|
|
||||||
|
import { NotebookManagerStub } from 'sql/workbench/contrib/notebook/test/stubs';
|
||||||
|
import { NotebookModel } from 'sql/workbench/services/notebook/browser/models/notebookModel';
|
||||||
|
import { ModelFactory } from 'sql/workbench/services/notebook/browser/models/modelFactory';
|
||||||
|
import { INotebookModelOptions } from 'sql/workbench/services/notebook/browser/models/modelInterfaces';
|
||||||
|
import { Memento } from 'vs/workbench/common/memento';
|
||||||
|
import { TestCapabilitiesService } from 'sql/platform/capabilities/test/common/testCapabilitiesService';
|
||||||
|
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
|
||||||
|
import { TestStorageService } from 'vs/workbench/test/common/workbenchTestServices';
|
||||||
|
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||||
|
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
|
||||||
|
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||||
|
import { NullLogService } from 'vs/platform/log/common/log';
|
||||||
|
import { TestConnectionManagementService } from 'sql/platform/connection/test/common/testConnectionManagementService';
|
||||||
|
import { NotebookEditorContentManager } from 'sql/workbench/contrib/notebook/browser/models/notebookInput';
|
||||||
|
import { SessionManager } from 'sql/workbench/contrib/notebook/test/emptySessionClasses';
|
||||||
|
import { NullAdsTelemetryService } from 'sql/platform/telemetry/common/adsTelemetryService';
|
||||||
|
import { CellTypes } from 'sql/workbench/services/notebook/common/contracts';
|
||||||
|
import { NotebookViewsExtension } from 'sql/workbench/services/notebook/browser/notebookViews/notebookViewsExtension';
|
||||||
|
import { TestConfigurationService } from 'sql/platform/connection/test/common/testConfigurationService';
|
||||||
|
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||||
|
import { NotebookViewModel } from 'sql/workbench/services/notebook/browser/notebookViews/notebookViewModel';
|
||||||
|
import { isUndefinedOrNull } from 'vs/base/common/types';
|
||||||
|
|
||||||
|
let initialNotebookContent: nb.INotebookContents = {
|
||||||
|
cells: [{
|
||||||
|
cell_type: CellTypes.Code,
|
||||||
|
source: ['insert into t1 values (c1, c2)'],
|
||||||
|
metadata: { language: 'python' },
|
||||||
|
execution_count: 1
|
||||||
|
}, {
|
||||||
|
cell_type: CellTypes.Markdown,
|
||||||
|
source: ['I am *markdown*'],
|
||||||
|
metadata: { language: 'python' },
|
||||||
|
execution_count: 1
|
||||||
|
}],
|
||||||
|
metadata: {
|
||||||
|
kernelspec: {
|
||||||
|
name: 'mssql',
|
||||||
|
language: 'sql'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nbformat: 4,
|
||||||
|
nbformat_minor: 5
|
||||||
|
};
|
||||||
|
|
||||||
|
let notebookContentWithoutMeta: nb.INotebookContents = {
|
||||||
|
cells: [{
|
||||||
|
cell_type: CellTypes.Code,
|
||||||
|
source: ['insert into t1 values (c1, c2)'],
|
||||||
|
execution_count: 1
|
||||||
|
}, {
|
||||||
|
cell_type: CellTypes.Markdown,
|
||||||
|
source: ['I am *markdown*'],
|
||||||
|
execution_count: 1
|
||||||
|
}],
|
||||||
|
metadata: {},
|
||||||
|
nbformat: 4,
|
||||||
|
nbformat_minor: 5
|
||||||
|
};
|
||||||
|
|
||||||
|
let defaultUri = URI.file('/some/path.ipynb');
|
||||||
|
let notificationService: TypeMoq.Mock<INotificationService>;
|
||||||
|
let capabilitiesService: TypeMoq.Mock<ICapabilitiesService>;
|
||||||
|
let instantiationService: IInstantiationService;
|
||||||
|
let configurationService: IConfigurationService;
|
||||||
|
|
||||||
|
suite('NotebookViewModel', function (): void {
|
||||||
|
let defaultViewName = 'Default New View';
|
||||||
|
let notebookManagers = [new NotebookManagerStub()];
|
||||||
|
let mockSessionManager: TypeMoq.Mock<nb.SessionManager>;
|
||||||
|
let memento: TypeMoq.Mock<Memento>;
|
||||||
|
let queryConnectionService: TypeMoq.Mock<TestConnectionManagementService>;
|
||||||
|
let defaultModelOptions: INotebookModelOptions;
|
||||||
|
const logService = new NullLogService();
|
||||||
|
setup(() => {
|
||||||
|
setupServices();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('initialize', async function (): Promise<void> {
|
||||||
|
let notebookViews = await initializeNotebookViewsExtension(initialNotebookContent);
|
||||||
|
let viewModel = new NotebookViewModel(defaultViewName, notebookViews);
|
||||||
|
viewModel.initialize();
|
||||||
|
|
||||||
|
let cellsWithNewView = notebookViews.getCells().filter(cell => cell.views.find(v => v.guid === viewModel.guid));
|
||||||
|
|
||||||
|
assert.equal(cellsWithNewView.length, 2);
|
||||||
|
assert.equal(viewModel.cells.length, 2);
|
||||||
|
assert.equal(viewModel.hiddenCells.length, 0);
|
||||||
|
assert.equal(viewModel.name, defaultViewName);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('initialize notebook with no metadata', async function (): Promise<void> {
|
||||||
|
let notebookViews = await initializeNotebookViewsExtension(notebookContentWithoutMeta);
|
||||||
|
let viewModel = new NotebookViewModel(defaultViewName, notebookViews);
|
||||||
|
viewModel.initialize();
|
||||||
|
|
||||||
|
let cellsWithNewView = notebookViews.getCells().filter(cell => cell.views.find(v => v.guid === viewModel.guid));
|
||||||
|
|
||||||
|
assert.equal(cellsWithNewView.length, 2);
|
||||||
|
assert.equal(viewModel.cells.length, 2);
|
||||||
|
assert.equal(viewModel.hiddenCells.length, 0);
|
||||||
|
assert.equal(viewModel.name, defaultViewName);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('rename', async function (): Promise<void> {
|
||||||
|
let exceptionThrown = false;
|
||||||
|
let notebookViews = await initializeNotebookViewsExtension(initialNotebookContent);
|
||||||
|
|
||||||
|
const view = notebookViews.createNewView(defaultViewName);
|
||||||
|
|
||||||
|
try {
|
||||||
|
view.name = `${defaultViewName} 1`;
|
||||||
|
} catch (e) {
|
||||||
|
exceptionThrown = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.equal(view.name, `${defaultViewName} 1`);
|
||||||
|
assert(!exceptionThrown);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('duplicate name', async function (): Promise<void> {
|
||||||
|
let exceptionThrown = false;
|
||||||
|
let notebookViews = await initializeNotebookViewsExtension(initialNotebookContent);
|
||||||
|
|
||||||
|
notebookViews.createNewView(defaultViewName);
|
||||||
|
let viewModel2 = notebookViews.createNewView(`${defaultViewName} 1`);
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
viewModel2.name = defaultViewName;
|
||||||
|
} catch (e) {
|
||||||
|
exceptionThrown = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(exceptionThrown);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('hide cell', async function (): Promise<void> {
|
||||||
|
let notebookViews = await initializeNotebookViewsExtension(initialNotebookContent);
|
||||||
|
let viewModel = new NotebookViewModel(defaultViewName, notebookViews);
|
||||||
|
viewModel.initialize();
|
||||||
|
|
||||||
|
let cellToHide = viewModel.cells[0];
|
||||||
|
|
||||||
|
viewModel.hideCell(cellToHide);
|
||||||
|
|
||||||
|
assert.equal(viewModel.hiddenCells.length, 1);
|
||||||
|
assert(viewModel.hiddenCells.includes(cellToHide));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('insert cell', async function (): Promise<void> {
|
||||||
|
let notebookViews = await initializeNotebookViewsExtension(initialNotebookContent);
|
||||||
|
let viewModel = new NotebookViewModel(defaultViewName, notebookViews);
|
||||||
|
viewModel.initialize();
|
||||||
|
|
||||||
|
let cellToInsert = viewModel.cells[0];
|
||||||
|
|
||||||
|
viewModel.hideCell(cellToInsert);
|
||||||
|
assert(viewModel.hiddenCells.includes(cellToInsert));
|
||||||
|
|
||||||
|
viewModel.insertCell(cellToInsert);
|
||||||
|
assert(!viewModel.hiddenCells.includes(cellToInsert));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('move cell', async function (): Promise<void> {
|
||||||
|
let notebookViews = await initializeNotebookViewsExtension(initialNotebookContent);
|
||||||
|
let viewModel = new NotebookViewModel(defaultViewName, notebookViews);
|
||||||
|
viewModel.initialize();
|
||||||
|
|
||||||
|
let cellToMove = viewModel.cells[0];
|
||||||
|
|
||||||
|
viewModel.moveCell(cellToMove, 98, 99);
|
||||||
|
let cellMeta = viewModel.getCellMetadata(cellToMove);
|
||||||
|
|
||||||
|
assert.equal(cellMeta.x, 98);
|
||||||
|
assert.equal(cellMeta.y, 99);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('resize cell', async function (): Promise<void> {
|
||||||
|
let notebookViews = await initializeNotebookViewsExtension(initialNotebookContent);
|
||||||
|
let viewModel = new NotebookViewModel(defaultViewName, notebookViews);
|
||||||
|
viewModel.initialize();
|
||||||
|
|
||||||
|
let cellToResize = viewModel.cells[0];
|
||||||
|
|
||||||
|
viewModel.resizeCell(cellToResize, 3, 4);
|
||||||
|
let cellMeta = viewModel.getCellMetadata(cellToResize);
|
||||||
|
|
||||||
|
assert.equal(cellMeta.width, 3);
|
||||||
|
assert.equal(cellMeta.height, 4);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('get cell metadata', async function (): Promise<void> {
|
||||||
|
let notebookViews = await initializeNotebookViewsExtension(initialNotebookContent);
|
||||||
|
let viewModel = new NotebookViewModel(defaultViewName, notebookViews);
|
||||||
|
viewModel.initialize();
|
||||||
|
|
||||||
|
let cell = viewModel.cells[0];
|
||||||
|
let cellMeta = notebookViews.getCellMetadata(cell);
|
||||||
|
|
||||||
|
assert(!isUndefinedOrNull(cellMeta.views.find(v => v.guid === viewModel.guid)));
|
||||||
|
assert.deepEqual(viewModel.getCellMetadata(cell), cellMeta.views.find(v => v.guid === viewModel.guid));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('delete', async function (): Promise<void> {
|
||||||
|
let notebookViews = await initializeNotebookViewsExtension(initialNotebookContent);
|
||||||
|
let viewModel = new NotebookViewModel(defaultViewName, notebookViews);
|
||||||
|
viewModel.initialize();
|
||||||
|
|
||||||
|
let CreateOnDeletedPromise = () => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
setTimeout(() => resolve(false), 2000);
|
||||||
|
viewModel.onDeleted(() => {
|
||||||
|
resolve(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
let onDeletedPromise = CreateOnDeletedPromise();
|
||||||
|
viewModel.delete();
|
||||||
|
|
||||||
|
let onDeletedCalled = await onDeletedPromise;
|
||||||
|
let hasView = notebookViews.getViews().find(view => view.name === defaultViewName);
|
||||||
|
|
||||||
|
assert(onDeletedCalled, 'onDelete event not called');
|
||||||
|
assert(!hasView);
|
||||||
|
});
|
||||||
|
|
||||||
|
function setupServices() {
|
||||||
|
mockSessionManager = TypeMoq.Mock.ofType(SessionManager);
|
||||||
|
notebookManagers[0].sessionManager = mockSessionManager.object;
|
||||||
|
notificationService = TypeMoq.Mock.ofType(TestNotificationService, TypeMoq.MockBehavior.Loose);
|
||||||
|
capabilitiesService = TypeMoq.Mock.ofType(TestCapabilitiesService);
|
||||||
|
memento = TypeMoq.Mock.ofType(Memento, TypeMoq.MockBehavior.Loose, '');
|
||||||
|
memento.setup(x => x.getMemento(TypeMoq.It.isAny())).returns(() => void 0);
|
||||||
|
queryConnectionService = TypeMoq.Mock.ofType(TestConnectionManagementService, TypeMoq.MockBehavior.Loose, memento.object, undefined, new TestStorageService());
|
||||||
|
queryConnectionService.callBase = true;
|
||||||
|
let serviceCollection = new ServiceCollection();
|
||||||
|
instantiationService = new InstantiationService(serviceCollection, true);
|
||||||
|
configurationService = new TestConfigurationService();
|
||||||
|
defaultModelOptions = {
|
||||||
|
notebookUri: defaultUri,
|
||||||
|
factory: new ModelFactory(instantiationService),
|
||||||
|
notebookManagers,
|
||||||
|
contentManager: undefined,
|
||||||
|
notificationService: notificationService.object,
|
||||||
|
connectionService: queryConnectionService.object,
|
||||||
|
providerId: 'SQL',
|
||||||
|
cellMagicMapper: undefined,
|
||||||
|
defaultKernel: undefined,
|
||||||
|
layoutChanged: undefined,
|
||||||
|
capabilitiesService: capabilitiesService.object
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function initializeNotebookViewsExtension(contents: nb.INotebookContents): Promise<NotebookViewsExtension> {
|
||||||
|
let mockContentManager = TypeMoq.Mock.ofType(NotebookEditorContentManager);
|
||||||
|
mockContentManager.setup(c => c.loadContent()).returns(() => Promise.resolve(contents));
|
||||||
|
defaultModelOptions.contentManager = mockContentManager.object;
|
||||||
|
|
||||||
|
let model = new NotebookModel(defaultModelOptions, undefined, logService, undefined, new NullAdsTelemetryService(), queryConnectionService.object, configurationService);
|
||||||
|
await model.loadContents();
|
||||||
|
await model.requestModelLoad();
|
||||||
|
|
||||||
|
return new NotebookViewsExtension(model);
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import * as TypeMoq from 'typemoq';
|
||||||
|
import { nb } from 'azdata';
|
||||||
|
import * as assert from 'assert';
|
||||||
|
|
||||||
|
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||||
|
import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService';
|
||||||
|
import { URI } from 'vs/base/common/uri';
|
||||||
|
|
||||||
|
import { NotebookManagerStub } from 'sql/workbench/contrib/notebook/test/stubs';
|
||||||
|
import { NotebookModel } from 'sql/workbench/services/notebook/browser/models/notebookModel';
|
||||||
|
import { ModelFactory } from 'sql/workbench/services/notebook/browser/models/modelFactory';
|
||||||
|
import { INotebookModelOptions } from 'sql/workbench/services/notebook/browser/models/modelInterfaces';
|
||||||
|
import { Memento } from 'vs/workbench/common/memento';
|
||||||
|
import { TestCapabilitiesService } from 'sql/platform/capabilities/test/common/testCapabilitiesService';
|
||||||
|
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
|
||||||
|
import { TestStorageService } from 'vs/workbench/test/common/workbenchTestServices';
|
||||||
|
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||||
|
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
|
||||||
|
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||||
|
import { NullLogService } from 'vs/platform/log/common/log';
|
||||||
|
import { TestConnectionManagementService } from 'sql/platform/connection/test/common/testConnectionManagementService';
|
||||||
|
import { NotebookEditorContentManager } from 'sql/workbench/contrib/notebook/browser/models/notebookInput';
|
||||||
|
import { SessionManager } from 'sql/workbench/contrib/notebook/test/emptySessionClasses';
|
||||||
|
import { NullAdsTelemetryService } from 'sql/platform/telemetry/common/adsTelemetryService';
|
||||||
|
import { CellTypes } from 'sql/workbench/services/notebook/common/contracts';
|
||||||
|
import { NotebookViewsExtension } from 'sql/workbench/services/notebook/browser/notebookViews/notebookViewsExtension';
|
||||||
|
import { TestConfigurationService } from 'sql/platform/connection/test/common/testConfigurationService';
|
||||||
|
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||||
|
|
||||||
|
let initialNotebookContent: nb.INotebookContents = {
|
||||||
|
cells: [{
|
||||||
|
cell_type: CellTypes.Code,
|
||||||
|
source: ['insert into t1 values (c1, c2)'],
|
||||||
|
metadata: { language: 'python' },
|
||||||
|
execution_count: 1
|
||||||
|
}, {
|
||||||
|
cell_type: CellTypes.Markdown,
|
||||||
|
source: ['I am *markdown*'],
|
||||||
|
metadata: { language: 'python' },
|
||||||
|
execution_count: 1
|
||||||
|
}],
|
||||||
|
metadata: {
|
||||||
|
kernelspec: {
|
||||||
|
name: 'mssql',
|
||||||
|
language: 'sql'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nbformat: 4,
|
||||||
|
nbformat_minor: 5
|
||||||
|
};
|
||||||
|
|
||||||
|
let defaultUri = URI.file('/some/path.ipynb');
|
||||||
|
let notificationService: TypeMoq.Mock<INotificationService>;
|
||||||
|
let capabilitiesService: TypeMoq.Mock<ICapabilitiesService>;
|
||||||
|
let instantiationService: IInstantiationService;
|
||||||
|
let configurationService: IConfigurationService;
|
||||||
|
|
||||||
|
suite('NotebookViews', function (): void {
|
||||||
|
let defaultViewName = 'Default New View';
|
||||||
|
let notebookManagers = [new NotebookManagerStub()];
|
||||||
|
let mockSessionManager: TypeMoq.Mock<nb.SessionManager>;
|
||||||
|
let memento: TypeMoq.Mock<Memento>;
|
||||||
|
let queryConnectionService: TypeMoq.Mock<TestConnectionManagementService>;
|
||||||
|
let defaultModelOptions: INotebookModelOptions;
|
||||||
|
let serviceCollection = new ServiceCollection();
|
||||||
|
let logService = new NullLogService();
|
||||||
|
let notebookViews: NotebookViewsExtension;
|
||||||
|
setup(async () => {
|
||||||
|
setupServices();
|
||||||
|
notebookViews = await initializeExtension();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('create new view', async function (): Promise<void> {
|
||||||
|
assert.equal(notebookViews.getViews().length, 0, 'notebook should not initially generate any views');
|
||||||
|
|
||||||
|
let newView = notebookViews.createNewView(defaultViewName);
|
||||||
|
let cellsWithMatchingGuid = newView.cells.filter(cell => newView.getCellMetadata(cell).guid === newView.guid);
|
||||||
|
|
||||||
|
assert.equal(newView.name, defaultViewName, 'view was not created with its given name');
|
||||||
|
assert.equal(newView.cells.length, 2, 'view did not contain the same number of cells as the notebook used to create it');
|
||||||
|
assert.equal(cellsWithMatchingGuid.length, newView.cells.length, 'cell metadata was not created for all cells in view');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('remove view', async function (): Promise<void> {
|
||||||
|
let newView = notebookViews.createNewView(defaultViewName);
|
||||||
|
|
||||||
|
notebookViews.removeView(newView.guid);
|
||||||
|
|
||||||
|
let cellsWithNewView = notebookViews.getCells().filter(cell => cell.views.find(v => v.guid === newView.guid));
|
||||||
|
|
||||||
|
assert.equal(notebookViews.getViews().length, 0, 'view not removed from notebook metadata');
|
||||||
|
assert.equal(cellsWithNewView.length, 0, 'view not removed from cells');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('default view name', async function (): Promise<void> {
|
||||||
|
let newView = notebookViews.createNewView();
|
||||||
|
assert.equal(newView.name, NotebookViewsExtension.defaultViewName);
|
||||||
|
|
||||||
|
let newView1 = notebookViews.createNewView();
|
||||||
|
assert.equal(newView1.name, `${NotebookViewsExtension.defaultViewName} 1`);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('active view', async function (): Promise<void> {
|
||||||
|
let newView = notebookViews.createNewView();
|
||||||
|
notebookViews.setActiveView(newView);
|
||||||
|
|
||||||
|
assert.equal(notebookViews.getActiveView(), newView);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('update cell', async function (): Promise<void> {
|
||||||
|
let newView = notebookViews.createNewView();
|
||||||
|
let c1 = newView.cells[0];
|
||||||
|
|
||||||
|
let cellData = newView.getCellMetadata(c1);
|
||||||
|
cellData = { ...cellData, x: 0, y: 0, hidden: true, width: 0, height: 0 };
|
||||||
|
notebookViews.updateCell(c1, newView, cellData);
|
||||||
|
|
||||||
|
cellData = { ...cellData, x: 1, y: 1, hidden: false, width: 1, height: 1 };
|
||||||
|
notebookViews.updateCell(c1, newView, cellData);
|
||||||
|
assert.deepStrictEqual(newView.getCellMetadata(c1), cellData, 'update did not set all values');
|
||||||
|
|
||||||
|
cellData = { ...cellData, x: 3 };
|
||||||
|
notebookViews.updateCell(c1, newView, { x: 3 });
|
||||||
|
assert.deepStrictEqual(newView.getCellMetadata(c1), cellData, 'update should only override set values');
|
||||||
|
});
|
||||||
|
|
||||||
|
function setupServices() {
|
||||||
|
mockSessionManager = TypeMoq.Mock.ofType(SessionManager);
|
||||||
|
notebookManagers[0].sessionManager = mockSessionManager.object;
|
||||||
|
notificationService = TypeMoq.Mock.ofType(TestNotificationService, TypeMoq.MockBehavior.Loose);
|
||||||
|
capabilitiesService = TypeMoq.Mock.ofType(TestCapabilitiesService);
|
||||||
|
memento = TypeMoq.Mock.ofType(Memento, TypeMoq.MockBehavior.Loose, '');
|
||||||
|
memento.setup(x => x.getMemento(TypeMoq.It.isAny())).returns(() => void 0);
|
||||||
|
queryConnectionService = TypeMoq.Mock.ofType(TestConnectionManagementService, TypeMoq.MockBehavior.Loose, memento.object, undefined, new TestStorageService());
|
||||||
|
queryConnectionService.callBase = true;
|
||||||
|
|
||||||
|
instantiationService = new InstantiationService(serviceCollection, true);
|
||||||
|
configurationService = new TestConfigurationService();
|
||||||
|
defaultModelOptions = {
|
||||||
|
notebookUri: defaultUri,
|
||||||
|
factory: new ModelFactory(instantiationService),
|
||||||
|
notebookManagers,
|
||||||
|
contentManager: undefined,
|
||||||
|
notificationService: notificationService.object,
|
||||||
|
connectionService: queryConnectionService.object,
|
||||||
|
providerId: 'SQL',
|
||||||
|
cellMagicMapper: undefined,
|
||||||
|
defaultKernel: undefined,
|
||||||
|
layoutChanged: undefined,
|
||||||
|
capabilitiesService: capabilitiesService.object
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function initializeExtension(): Promise<NotebookViewsExtension> {
|
||||||
|
let mockContentManager = TypeMoq.Mock.ofType(NotebookEditorContentManager);
|
||||||
|
mockContentManager.setup(c => c.loadContent()).returns(() => Promise.resolve(initialNotebookContent));
|
||||||
|
defaultModelOptions.contentManager = mockContentManager.object;
|
||||||
|
|
||||||
|
let model = new NotebookModel(defaultModelOptions, undefined, logService, undefined, new NullAdsTelemetryService(), queryConnectionService.object, configurationService);
|
||||||
|
await model.loadContents();
|
||||||
|
await model.requestModelLoad();
|
||||||
|
|
||||||
|
return new NotebookViewsExtension(model);
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -368,7 +368,23 @@ suite('notebook model', function (): void {
|
|||||||
model.deleteCell(model.cells[0]);
|
model.deleteCell(model.cells[0]);
|
||||||
assert.equal(errorCount, 2, 'Error count should be 2 after trying to delete a cell that does not exist a second time');
|
assert.equal(errorCount, 2, 'Error count should be 2 after trying to delete a cell that does not exist a second time');
|
||||||
assert(isUndefinedOrNull(notebookContentChange), 'There still should be no content change after an error is recorded');
|
assert(isUndefinedOrNull(notebookContentChange), 'There still should be no content change after an error is recorded');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should notify cell on metadata change', async function (): Promise<void> {
|
||||||
|
let mockContentManager = TypeMoq.Mock.ofType(NotebookEditorContentManager);
|
||||||
|
mockContentManager.setup(c => c.loadContent()).returns(() => Promise.resolve(expectedNotebookContent));
|
||||||
|
defaultModelOptions.contentManager = mockContentManager.object;
|
||||||
|
|
||||||
|
// When I initalize the model
|
||||||
|
let model = new NotebookModel(defaultModelOptions, undefined, logService, undefined, new NullAdsTelemetryService(), queryConnectionService.object, configurationService);
|
||||||
|
await model.loadContents();
|
||||||
|
|
||||||
|
let notebookContentChange: NotebookContentChange;
|
||||||
|
model.contentChanged(c => notebookContentChange = c);
|
||||||
|
|
||||||
|
model.cells[0].metadata = { 'test-field': 'test-value' };
|
||||||
|
assert(!isUndefinedOrNull(notebookContentChange));
|
||||||
|
assert.equal(notebookContentChange.changeType, NotebookChangeType.CellMetadataUpdated, 'notebookContentChange changeType should indicate ');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Should load contents but then go to error state if client session startup fails', async function (): Promise<void> {
|
test('Should load contents but then go to error state if client session startup fails', async function (): Promise<void> {
|
||||||
|
|||||||
@@ -100,6 +100,12 @@ export class NotebookModelStub implements INotebookModel {
|
|||||||
set viewMode(mode: ViewMode) {
|
set viewMode(mode: ViewMode) {
|
||||||
throw new Error('Method not implemented.');
|
throw new Error('Method not implemented.');
|
||||||
}
|
}
|
||||||
|
setMetaValue(key: string, value: any) {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
getMetaValue(key: string) {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
addCell(cellType: CellType, index?: number): void {
|
addCell(cellType: CellType, index?: number): void {
|
||||||
throw new Error('Method not implemented.');
|
throw new Error('Method not implemented.');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -128,6 +128,15 @@ export class CellModel extends Disposable implements ICellModel {
|
|||||||
return this._onCellModeChanged.event;
|
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 {
|
public get isEditMode(): boolean {
|
||||||
return this._isEditMode;
|
return this._isEditMode;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -345,6 +345,16 @@ export interface INotebookModel {
|
|||||||
*/
|
*/
|
||||||
viewMode: ViewMode;
|
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
|
* Change the current kernel from the Kernel dropdown
|
||||||
* @param displayName kernel name (as displayed in Kernel dropdown)
|
* @param displayName kernel name (as displayed in Kernel dropdown)
|
||||||
@@ -476,6 +486,7 @@ export interface ICellModel {
|
|||||||
source: string | string[];
|
source: string | string[];
|
||||||
cellType: CellType;
|
cellType: CellType;
|
||||||
trustedMode: boolean;
|
trustedMode: boolean;
|
||||||
|
metadata: any | undefined;
|
||||||
active: boolean;
|
active: boolean;
|
||||||
hover: boolean;
|
hover: boolean;
|
||||||
executionCount: number | undefined;
|
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;
|
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) {
|
public set viewMode(mode: ViewMode) {
|
||||||
if (mode !== this._viewMode) {
|
if (mode !== this._viewMode) {
|
||||||
this._viewMode = mode;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -41,6 +41,7 @@ export enum NotebookChangeType {
|
|||||||
CellOutputUpdated,
|
CellOutputUpdated,
|
||||||
DirtyStateChanged,
|
DirtyStateChanged,
|
||||||
KernelChanged,
|
KernelChanged,
|
||||||
|
MetadataChanged,
|
||||||
TrustChanged,
|
TrustChanged,
|
||||||
Saved,
|
Saved,
|
||||||
CellExecuted,
|
CellExecuted,
|
||||||
|
|||||||
Reference in New Issue
Block a user