123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494 |
- // Copyright (c) Jupyter Development Team.
- // Distributed under the terms of the Modified BSD License.
- import {
- each
- } from '@phosphor/algorithm';
- import {
- utils
- } from '@jupyterlab/services';
- import {
- DocumentModel, DocumentRegistry
- } from '@jupyterlab/docregistry';
- import {
- ICellModel, ICodeCellModel, IRawCellModel, IMarkdownCellModel,
- CodeCellModel, RawCellModel, MarkdownCellModel, CellModel
- } from '@jupyterlab/cells';
- import {
- IObservableJSON, IObservableUndoableVector,
- IObservableVector, ObservableVector, nbformat, IModelDB
- } from '@jupyterlab/coreutils';
- import {
- CellList
- } from './celllist';
- /**
- * The definition of a model object for a notebook widget.
- */
- export
- interface INotebookModel extends DocumentRegistry.IModel {
- /**
- * The list of cells in the notebook.
- */
- readonly cells: IObservableUndoableVector<ICellModel>;
- /**
- * The cell model factory for the notebook.
- */
- readonly contentFactory: NotebookModel.IContentFactory;
- /**
- * The major version number of the nbformat.
- */
- readonly nbformat: number;
- /**
- * The minor version number of the nbformat.
- */
- readonly nbformatMinor: number;
- /**
- * The metadata associated with the notebook.
- */
- readonly metadata: IObservableJSON;
- }
- /**
- * An implementation of a notebook Model.
- */
- export
- class NotebookModel extends DocumentModel implements INotebookModel {
- /**
- * Construct a new notebook model.
- */
- constructor(options: NotebookModel.IOptions = {}) {
- super(options.languagePreference, options.modelDB);
- let factory = (
- options.contentFactory || NotebookModel.defaultContentFactory
- );
- let cellDB = this.modelDB.view('cells');
- factory.modelDB = cellDB;
- this.contentFactory = factory;
- this._cells = new CellList(this.modelDB, this.contentFactory);
- // Add an initial code cell by default.
- if (!this._cells.length) {
- this._cells.pushBack(factory.createCodeCell({}));
- }
- this._cells.changed.connect(this._onCellsChanged, this);
- // Handle initial metadata.
- let metadata = this.modelDB.createJSON('metadata');
- if (!metadata.has('language_info')) {
- let name = options.languagePreference || '';
- metadata.set('language_info', { name });
- }
- this._ensureMetadata();
- metadata.changed.connect(this.triggerContentChange, this);
- }
- /**
- * The cell model factory for the notebook.
- */
- readonly contentFactory: NotebookModel.IContentFactory;
- /**
- * The metadata associated with the notebook.
- */
- get metadata(): IObservableJSON {
- return this.modelDB.get('metadata') as IObservableJSON;
- }
- /**
- * Get the observable list of notebook cells.
- */
- get cells(): IObservableUndoableVector<ICellModel> {
- return this._cells;
- }
- /**
- * The major version number of the nbformat.
- */
- get nbformat(): number {
- return this._nbformat;
- }
- /**
- * The minor version number of the nbformat.
- */
- get nbformatMinor(): number {
- return this._nbformatMinor;
- }
- /**
- * The default kernel name of the document.
- */
- get defaultKernelName(): string {
- let spec = this.metadata.get('kernelspec') as nbformat.IKernelspecMetadata;
- return spec ? spec.name : '';
- }
- /**
- * The default kernel language of the document.
- */
- get defaultKernelLanguage(): string {
- let info = this.metadata.get('language_info') as nbformat.ILanguageInfoMetadata;
- return info ? info.name : '';
- }
- /**
- * Dispose of the resources held by the model.
- */
- dispose(): void {
- // Do nothing if already disposed.
- if (this.cells === null) {
- return;
- }
- let cells = this.cells;
- cells.dispose();
- super.dispose();
- }
- /**
- * Serialize the model to a string.
- */
- toString(): string {
- return JSON.stringify(this.toJSON());
- }
- /**
- * Deserialize the model from a string.
- *
- * #### Notes
- * Should emit a [contentChanged] signal.
- */
- fromString(value: string): void {
- this.fromJSON(JSON.parse(value));
- }
- /**
- * Serialize the model to JSON.
- */
- toJSON(): nbformat.INotebookContent {
- let cells: nbformat.ICell[] = [];
- for (let i = 0; i < this.cells.length; i++) {
- let cell = this.cells.at(i);
- cells.push(cell.toJSON());
- }
- this._ensureMetadata();
- let metadata = Object.create(null) as nbformat.INotebookMetadata;
- 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: nbformat.INotebookContent): void {
- let cells: ICellModel[] = [];
- 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;
- 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 });
- }
- // 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;
- }
- /**
- * Handle a change in the cells list.
- */
- private _onCellsChanged(list: IObservableVector<ICellModel>, change: ObservableVector.IChangedArgs<ICellModel>): void {
- switch (change.type) {
- case 'add':
- each(change.newValues, cell => {
- cell.contentChanged.connect(this.triggerContentChange, this);
- });
- break;
- case 'remove':
- each(change.oldValues, cell => {
- });
- break;
- case 'set':
- each(change.newValues, cell => {
- cell.contentChanged.connect(this.triggerContentChange, this);
- });
- each(change.oldValues, cell => {
- });
- break;
- default:
- return;
- }
- let factory = this.contentFactory;
- // Add code cell if there are no cells remaining.
- if (!this.cells.length) {
- // Add the cell in a new context to avoid triggering another
- // cell changed event during the handling of this signal.
- requestAnimationFrame(() => {
- if (!this.isDisposed && !this.cells.length) {
- this.cells.pushBack(factory.createCodeCell({}));
- }
- });
- }
- this.triggerContentChange();
- }
- /**
- * Make sure we have the required metadata fields.
- */
- private _ensureMetadata(): void {
- let metadata = this.metadata;
- if (!metadata.has('language_info')) {
- metadata.set('language_info', { name: '' });
- }
- if (!metadata.has('kernelspec')) {
- metadata.set('kernelspec', { name: '', display_name: '' });
- }
- }
- private _cells: CellList;
- private _nbformat = nbformat.MAJOR_VERSION;
- private _nbformatMinor = nbformat.MINOR_VERSION;
- }
- /**
- * The namespace for the `NotebookModel` class statics.
- */
- export
- namespace NotebookModel {
- /**
- * An options object for initializing a notebook model.
- */
- export
- interface IOptions {
- /**
- * The language preference for the model.
- */
- languagePreference?: string;
- /**
- * A factory for creating cell models.
- *
- * The default is a shared factory instance.
- */
- contentFactory?: IContentFactory;
- /**
- * An optional modelDB for storing notebook data.
- */
- modelDB?: IModelDB;
- }
- /**
- * A factory for creating notebook model content.
- */
- export
- interface IContentFactory {
- /**
- * The factory for output area models.
- */
- readonly codeCellContentFactory: CodeCellModel.IContentFactory;
- modelDB: IModelDB;
- /**
- * Create a new code cell.
- *
- * @param options - The options used to create the cell.
- *
- * @returns A new code cell. If a source cell is provided, the
- * new cell will be intialized with the data from the source.
- */
- createCodeCell(options: CodeCellModel.IOptions): ICodeCellModel;
- /**
- * Create a new markdown cell.
- *
- * @param options - The options used to create the cell.
- *
- * @returns A new markdown cell. If a source cell is provided, the
- * new cell will be intialized with the data from the source.
- */
- createMarkdownCell(options: CellModel.IOptions): IMarkdownCellModel;
- /**
- * Create a new raw cell.
- *
- * @param options - The options used to create the cell.
- *
- * @returns A new raw cell. If a source cell is provided, the
- * new cell will be intialized with the data from the source.
- */
- createRawCell(options: CellModel.IOptions): IRawCellModel;
- }
- /**
- * The default implementation of an `IContentFactory`.
- */
- export
- class ContentFactory {
- /**
- * Create a new cell model factory.
- */
- constructor(options: IContentFactoryOptions) {
- this.codeCellContentFactory = (options.codeCellContentFactory ||
- CodeCellModel.defaultContentFactory
- );
- this._modelDB = options.modelDB || null;
- }
- /**
- * The factory for code cell content.
- */
- readonly codeCellContentFactory: CodeCellModel.IContentFactory;
- get modelDB(): IModelDB {
- return this._modelDB;
- }
- set modelDB(db: IModelDB) {
- this._modelDB = db;
- }
- /**
- * 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 intialized with the data from the source.
- * If the contentFactory is not provided, the instance
- * `codeCellContentFactory` will be used.
- */
- createCodeCell(options: CodeCellModel.IOptions): ICodeCellModel {
- if (options.contentFactory) {
- options.contentFactory = this.codeCellContentFactory;
- }
- if (this._modelDB) {
- if (!options.id) {
- options.id = utils.uuid();
- }
- 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 intialized with the data from the source.
- */
- createMarkdownCell(options: CellModel.IOptions): IMarkdownCellModel {
- if (this._modelDB) {
- if (!options.id) {
- options.id = utils.uuid();
- }
- 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 intialized with the data from the source.
- */
- createRawCell(options: CellModel.IOptions): IRawCellModel {
- if (this._modelDB) {
- if (!options.id) {
- options.id = utils.uuid();
- }
- options.modelDB = this._modelDB.view(options.id);
- }
- return new RawCellModel(options);
- }
- private _modelDB: IModelDB;
- }
- /**
- * The options used to initialize a `ContentFactory`.
- */
- export
- interface IContentFactoryOptions {
- /**
- * The factory for code cell model content.
- */
- codeCellContentFactory?: CodeCellModel.IContentFactory;
- /**
- * The modelDB in which to place new content.
- */
- modelDB?: IModelDB;
- }
- /**
- * The default `ContentFactory` instance.
- */
- export
- const defaultContentFactory = new ContentFactory({});
- }
|