sophon-notebook-notebook
Version:
JupyterLab - Notebook
349 lines • 11.8 kB
JavaScript
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
import { DocumentModel } from 'sophon-notebook-docregistry';
import { CodeCellModel, RawCellModel, MarkdownCellModel } from 'sophon-notebook-cells';
import { nbformat } from 'sophon-notebook-coreutils';
import { UUID } from '@phosphor/coreutils';
import { CellList } from './celllist';
import { showDialog, Dialog } from 'sophon-notebook-apputils';
/**
* An implementation of a notebook Model.
*/
export class NotebookModel extends DocumentModel {
/**
* Construct a new notebook model.
*/
constructor(options = {}) {
super(options.languagePreference, options.modelDB);
this._nbformat = nbformat.MAJOR_VERSION;
this._nbformatMinor = nbformat.MINOR_VERSION;
let factory = options.contentFactory || NotebookModel.defaultContentFactory;
this.contentFactory = factory.clone(this.modelDB.view('cells'));
this._cells = new CellList(this.modelDB, this.contentFactory);
this._cells.changed.connect(this._onCellsChanged, this);
// Handle initial metadata.
let metadata = this.modelDB.createMap('metadata');
if (!metadata.has('language_info')) {
let name = options.languagePreference || '';
metadata.set('language_info', { name });
}
this._ensureMetadata();
metadata.changed.connect(this.triggerContentChange, this);
this._deletedCells = [];
}
/**
* The metadata associated with the notebook.
*/
get metadata() {
return this.modelDB.get('metadata');
}
/**
* Get the observable list of notebook cells.
*/
get cells() {
return this._cells;
}
/**
* The major version number of the nbformat.
*/
get nbformat() {
return this._nbformat;
}
/**
* The minor version number of the nbformat.
*/
get nbformatMinor() {
return this._nbformatMinor;
}
/**
* The default kernel name of the document.
*/
get defaultKernelName() {
let spec = this.metadata.get('kernelspec');
return spec ? spec.name : '';
}
/**
* A list of deleted cells for the notebook..
*/
get deletedCells() {
return this._deletedCells;
}
/**
* The default kernel language of the document.
*/
get defaultKernelLanguage() {
let info = this.metadata.get('language_info');
return info ? info.name : '';
}
/**
* Dispose of the resources held by the model.
*/
dispose() {
// Do nothing if already disposed.
if (this.cells === null) {
return;
}
let cells = this.cells;
this._cells = null;
cells.dispose();
super.dispose();
}
/**
* Serialize the model to a string.
*/
toString() {
return JSON.stringify(this.toJSON());
}
/**
* Deserialize the model from a string.
*
* #### Notes
* Should emit a [contentChanged] signal.
*/
fromString(value) {
this.fromJSON(JSON.parse(value));
}
/**
* Serialize the model to JSON.
*/
toJSON() {
let cells = [];
for (let i = 0; i < this.cells.length; i++) {
let cell = this.cells.get(i);
cells.push(cell.toJSON());
}
this._ensureMetadata();
let metadata = Object.create(null);
for (let key of this.metadata.keys()) {
metadata[key] = JSON.parse(JSON.stringify(this.metadata.get(key)));
}
return {
metadata,
nbformat_minor: this._nbformatMinor,
nbformat: this._nbformat,
cells
};
}
/**
* Deserialize the model from JSON.
*
* #### Notes
* Should emit a [contentChanged] signal.
*/
fromJSON(value) {
let cells = [];
let factory = this.contentFactory;
for (let cell of value.cells) {
switch (cell.cell_type) {
case 'code':
cells.push(factory.createCodeCell({ cell }));
break;
case 'markdown':
cells.push(factory.createMarkdownCell({ cell }));
break;
case 'raw':
cells.push(factory.createRawCell({ cell }));
break;
default:
continue;
}
}
this.cells.beginCompoundOperation();
this.cells.clear();
this.cells.pushAll(cells);
this.cells.endCompoundOperation();
let oldValue = 0;
let newValue = 0;
this._nbformatMinor = nbformat.MINOR_VERSION;
this._nbformat = nbformat.MAJOR_VERSION;
const origNbformat = value.metadata.orig_nbformat;
if (value.nbformat !== this._nbformat) {
oldValue = this._nbformat;
this._nbformat = newValue = value.nbformat;
this.triggerStateChange({ name: 'nbformat', oldValue, newValue });
}
if (value.nbformat_minor > this._nbformatMinor) {
oldValue = this._nbformatMinor;
this._nbformatMinor = newValue = value.nbformat_minor;
this.triggerStateChange({ name: 'nbformatMinor', oldValue, newValue });
}
// Alert the user if the format changes.
if (origNbformat !== undefined && this._nbformat !== origNbformat) {
const newer = this._nbformat > origNbformat;
const msg = `This notebook has been converted from ${newer ? 'an older' : 'a newer'} notebook format (v${origNbformat}) to the current notebook format (v${this._nbformat}). The next time you save this notebook, the current notebook format (v${this._nbformat}) will be used. ${newer
? 'Older versions of Jupyter may not be able to read the new format.'
: 'Some features of the original notebook may not be available.'} To preserve the original format version, close the notebook without saving it.`;
void showDialog({
title: 'Notebook converted',
body: msg,
buttons: [Dialog.okButton()]
});
}
// Update the metadata.
this.metadata.clear();
let metadata = value.metadata;
for (let key in metadata) {
// orig_nbformat is not intended to be stored per spec.
if (key === 'orig_nbformat') {
continue;
}
this.metadata.set(key, metadata[key]);
}
this._ensureMetadata();
this.dirty = true;
}
/**
* Initialize the model with its current state.
*/
initialize() {
super.initialize();
this.cells.clearUndo();
}
/**
* Handle a change in the cells list.
*/
_onCellsChanged(list, change) {
switch (change.type) {
case 'add':
change.newValues.forEach(cell => {
cell.contentChanged.connect(this.triggerContentChange, this);
});
break;
case 'remove':
break;
case 'set':
change.newValues.forEach(cell => {
cell.contentChanged.connect(this.triggerContentChange, this);
});
break;
default:
break;
}
this.triggerContentChange();
}
/**
* Make sure we have the required metadata fields.
*/
_ensureMetadata() {
let metadata = this.metadata;
if (!metadata.has('language_info')) {
metadata.set('language_info', { name: '' });
}
if (!metadata.has('kernelspec')) {
metadata.set('kernelspec', { name: '', display_name: '' });
}
}
}
/**
* The namespace for the `NotebookModel` class statics.
*/
(function (NotebookModel) {
/**
* The default implementation of an `IContentFactory`.
*/
class ContentFactory {
/**
* Create a new cell model factory.
*/
constructor(options) {
this.codeCellContentFactory =
options.codeCellContentFactory || CodeCellModel.defaultContentFactory;
this.modelDB = options.modelDB;
}
/**
* Create a new cell by cell type.
*
* @param type: the type of the cell to create.
*
* @param options: the cell creation options.
*
* #### Notes
* This method is intended to be a convenience method to programmaticaly
* call the other cell creation methods in the factory.
*/
createCell(type, opts) {
switch (type) {
case 'code':
return this.createCodeCell(opts);
break;
case 'markdown':
return this.createMarkdownCell(opts);
break;
case 'raw':
default:
return this.createRawCell(opts);
}
}
/**
* Create a new code cell.
*
* @param source - The data to use for the original source data.
*
* @returns A new code cell. If a source cell is provided, the
* new cell will be initialized with the data from the source.
* If the contentFactory is not provided, the instance
* `codeCellContentFactory` will be used.
*/
createCodeCell(options) {
if (options.contentFactory) {
options.contentFactory = this.codeCellContentFactory;
}
if (this.modelDB) {
if (!options.id) {
options.id = UUID.uuid4();
}
options.modelDB = this.modelDB.view(options.id);
}
return new CodeCellModel(options);
}
/**
* Create a new markdown cell.
*
* @param source - The data to use for the original source data.
*
* @returns A new markdown cell. If a source cell is provided, the
* new cell will be initialized with the data from the source.
*/
createMarkdownCell(options) {
if (this.modelDB) {
if (!options.id) {
options.id = UUID.uuid4();
}
options.modelDB = this.modelDB.view(options.id);
}
return new MarkdownCellModel(options);
}
/**
* Create a new raw cell.
*
* @param source - The data to use for the original source data.
*
* @returns A new raw cell. If a source cell is provided, the
* new cell will be initialized with the data from the source.
*/
createRawCell(options) {
if (this.modelDB) {
if (!options.id) {
options.id = UUID.uuid4();
}
options.modelDB = this.modelDB.view(options.id);
}
return new RawCellModel(options);
}
/**
* Clone the content factory with a new IModelDB.
*/
clone(modelDB) {
return new ContentFactory({
modelDB: modelDB,
codeCellContentFactory: this.codeCellContentFactory
});
}
}
NotebookModel.ContentFactory = ContentFactory;
/**
* The default `ContentFactory` instance.
*/
NotebookModel.defaultContentFactory = new ContentFactory({});
})(NotebookModel || (NotebookModel = {}));
//# sourceMappingURL=model.js.map