UNPKG

11.5 kBJavaScriptView Raw
1// Copyright (c) Jupyter Development Team.
2// Distributed under the terms of the Modified BSD License.
3import { Dialog, showDialog } from '@jupyterlab/apputils';
4import * as nbformat from '@jupyterlab/nbformat';
5import { YNotebook } from '@jupyter/ydoc';
6import { nullTranslator } from '@jupyterlab/translation';
7import { JSONExt } from '@lumino/coreutils';
8import { Signal } from '@lumino/signaling';
9import { CellList } from './celllist';
10/**
11 * An implementation of a notebook Model.
12 */
13export class NotebookModel {
14 /**
15 * Construct a new notebook model.
16 */
17 constructor(options = {}) {
18 var _a, _b;
19 /**
20 * Whether the model should disposed the shared model on disposal or not.
21 */
22 this.standaloneModel = false;
23 this._dirty = false;
24 this._readOnly = false;
25 this._contentChanged = new Signal(this);
26 this._stateChanged = new Signal(this);
27 this._isDisposed = false;
28 this._metadataChanged = new Signal(this);
29 this.standaloneModel = typeof options.sharedModel === 'undefined';
30 if (options.sharedModel) {
31 this.sharedModel = options.sharedModel;
32 }
33 else {
34 this.sharedModel = YNotebook.create({
35 disableDocumentWideUndoRedo: (_a = options.disableDocumentWideUndoRedo) !== null && _a !== void 0 ? _a : true,
36 data: {
37 nbformat: nbformat.MAJOR_VERSION,
38 nbformat_minor: nbformat.MINOR_VERSION,
39 metadata: {
40 kernelspec: { name: '', display_name: '' },
41 language_info: { name: (_b = options.languagePreference) !== null && _b !== void 0 ? _b : '' }
42 }
43 }
44 });
45 }
46 this._cells = new CellList(this.sharedModel);
47 this._trans = (options.translator || nullTranslator).load('jupyterlab');
48 this._deletedCells = [];
49 this._collaborationEnabled = !!(options === null || options === void 0 ? void 0 : options.collaborationEnabled);
50 this._cells.changed.connect(this._onCellsChanged, this);
51 this.sharedModel.changed.connect(this._onStateChanged, this);
52 this.sharedModel.metadataChanged.connect(this._onMetadataChanged, this);
53 }
54 /**
55 * A signal emitted when the document content changes.
56 */
57 get contentChanged() {
58 return this._contentChanged;
59 }
60 /**
61 * Signal emitted when notebook metadata changes.
62 */
63 get metadataChanged() {
64 return this._metadataChanged;
65 }
66 /**
67 * A signal emitted when the document state changes.
68 */
69 get stateChanged() {
70 return this._stateChanged;
71 }
72 /**
73 * Get the observable list of notebook cells.
74 */
75 get cells() {
76 return this._cells;
77 }
78 /**
79 * The dirty state of the document.
80 */
81 get dirty() {
82 return this._dirty;
83 }
84 set dirty(newValue) {
85 const oldValue = this._dirty;
86 if (newValue === oldValue) {
87 return;
88 }
89 this._dirty = newValue;
90 this.triggerStateChange({
91 name: 'dirty',
92 oldValue,
93 newValue
94 });
95 }
96 /**
97 * The read only state of the document.
98 */
99 get readOnly() {
100 return this._readOnly;
101 }
102 set readOnly(newValue) {
103 if (newValue === this._readOnly) {
104 return;
105 }
106 const oldValue = this._readOnly;
107 this._readOnly = newValue;
108 this.triggerStateChange({ name: 'readOnly', oldValue, newValue });
109 }
110 /**
111 * The metadata associated with the notebook.
112 *
113 * ### Notes
114 * This is a copy of the metadata. Changing a part of it
115 * won't affect the model.
116 * As this returns a copy of all metadata, it is advised to
117 * use `getMetadata` to speed up the process of getting a single key.
118 */
119 get metadata() {
120 return this.sharedModel.metadata;
121 }
122 /**
123 * The major version number of the nbformat.
124 */
125 get nbformat() {
126 return this.sharedModel.nbformat;
127 }
128 /**
129 * The minor version number of the nbformat.
130 */
131 get nbformatMinor() {
132 return this.sharedModel.nbformat_minor;
133 }
134 /**
135 * The default kernel name of the document.
136 */
137 get defaultKernelName() {
138 var _a;
139 const spec = this.getMetadata('kernelspec');
140 return (_a = spec === null || spec === void 0 ? void 0 : spec.name) !== null && _a !== void 0 ? _a : '';
141 }
142 /**
143 * A list of deleted cells for the notebook..
144 */
145 get deletedCells() {
146 return this._deletedCells;
147 }
148 /**
149 * The default kernel language of the document.
150 */
151 get defaultKernelLanguage() {
152 var _a;
153 const info = this.getMetadata('language_info');
154 return (_a = info === null || info === void 0 ? void 0 : info.name) !== null && _a !== void 0 ? _a : '';
155 }
156 /**
157 * Whether the model is collaborative or not.
158 */
159 get collaborative() {
160 return this._collaborationEnabled;
161 }
162 /**
163 * Dispose of the resources held by the model.
164 */
165 dispose() {
166 // Do nothing if already disposed.
167 if (this.isDisposed) {
168 return;
169 }
170 this._isDisposed = true;
171 const cells = this.cells;
172 this._cells = null;
173 cells.dispose();
174 if (this.standaloneModel) {
175 this.sharedModel.dispose();
176 }
177 Signal.clearData(this);
178 }
179 /**
180 * Delete a metadata
181 *
182 * @param key Metadata key
183 */
184 deleteMetadata(key) {
185 return this.sharedModel.deleteMetadata(key);
186 }
187 /**
188 * Get a metadata
189 *
190 * ### Notes
191 * This returns a copy of the key value.
192 *
193 * @param key Metadata key
194 */
195 getMetadata(key) {
196 return this.sharedModel.getMetadata(key);
197 }
198 /**
199 * Set a metadata
200 *
201 * @param key Metadata key
202 * @param value Metadata value
203 */
204 setMetadata(key, value) {
205 if (typeof value === 'undefined') {
206 this.sharedModel.deleteMetadata(key);
207 }
208 else {
209 this.sharedModel.setMetadata(key, value);
210 }
211 }
212 /**
213 * Serialize the model to a string.
214 */
215 toString() {
216 return JSON.stringify(this.toJSON());
217 }
218 /**
219 * Deserialize the model from a string.
220 *
221 * #### Notes
222 * Should emit a [contentChanged] signal.
223 */
224 fromString(value) {
225 this.fromJSON(JSON.parse(value));
226 }
227 /**
228 * Serialize the model to JSON.
229 */
230 toJSON() {
231 this._ensureMetadata();
232 return this.sharedModel.toJSON();
233 }
234 /**
235 * Deserialize the model from JSON.
236 *
237 * #### Notes
238 * Should emit a [contentChanged] signal.
239 */
240 fromJSON(value) {
241 var _a, _b;
242 const copy = JSONExt.deepCopy(value);
243 const origNbformat = value.metadata.orig_nbformat;
244 // Alert the user if the format changes.
245 copy.nbformat = Math.max(value.nbformat, nbformat.MAJOR_VERSION);
246 if (copy.nbformat !== value.nbformat ||
247 copy.nbformat_minor < nbformat.MINOR_VERSION) {
248 copy.nbformat_minor = nbformat.MINOR_VERSION;
249 }
250 if (origNbformat !== undefined && copy.nbformat !== origNbformat) {
251 const newer = copy.nbformat > origNbformat;
252 let msg;
253 if (newer) {
254 msg = this._trans.__(`This notebook has been converted from an older notebook format (v%1)
255to the current notebook format (v%2).
256The next time you save this notebook, the current notebook format (v%2) will be used.
257'Older versions of Jupyter may not be able to read the new format.' To preserve the original format version,
258close the notebook without saving it.`, origNbformat, copy.nbformat);
259 }
260 else {
261 msg = this._trans.__(`This notebook has been converted from an newer notebook format (v%1)
262to the current notebook format (v%2).
263The next time you save this notebook, the current notebook format (v%2) will be used.
264Some features of the original notebook may not be available.' To preserve the original format version,
265close the notebook without saving it.`, origNbformat, copy.nbformat);
266 }
267 void showDialog({
268 title: this._trans.__('Notebook converted'),
269 body: msg,
270 buttons: [Dialog.okButton({ label: this._trans.__('Ok') })]
271 });
272 }
273 // Ensure there is at least one cell
274 if (((_b = (_a = copy.cells) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0) === 0) {
275 copy['cells'] = [
276 { cell_type: 'code', source: '', metadata: { trusted: true } }
277 ];
278 }
279 this.sharedModel.fromJSON(copy);
280 this._ensureMetadata();
281 this.dirty = true;
282 }
283 /**
284 * Handle a change in the cells list.
285 */
286 _onCellsChanged(list, change) {
287 switch (change.type) {
288 case 'add':
289 change.newValues.forEach(cell => {
290 cell.contentChanged.connect(this.triggerContentChange, this);
291 });
292 break;
293 case 'remove':
294 break;
295 case 'set':
296 change.newValues.forEach(cell => {
297 cell.contentChanged.connect(this.triggerContentChange, this);
298 });
299 break;
300 default:
301 break;
302 }
303 this.triggerContentChange();
304 }
305 _onMetadataChanged(sender, changes) {
306 this._metadataChanged.emit(changes);
307 this.triggerContentChange();
308 }
309 _onStateChanged(sender, changes) {
310 if (changes.stateChange) {
311 changes.stateChange.forEach(value => {
312 if (value.name === 'dirty') {
313 // Setting `dirty` will trigger the state change.
314 // We always set `dirty` because the shared model state
315 // and the local attribute are synchronized one way shared model -> _dirty
316 this.dirty = value.newValue;
317 }
318 else if (value.oldValue !== value.newValue) {
319 this.triggerStateChange({
320 newValue: undefined,
321 oldValue: undefined,
322 ...value
323 });
324 }
325 });
326 }
327 }
328 /**
329 * Make sure we have the required metadata fields.
330 */
331 _ensureMetadata(languageName = '') {
332 if (!this.getMetadata('language_info')) {
333 this.sharedModel.setMetadata('language_info', { name: languageName });
334 }
335 if (!this.getMetadata('kernelspec')) {
336 this.sharedModel.setMetadata('kernelspec', {
337 name: '',
338 display_name: ''
339 });
340 }
341 }
342 /**
343 * Trigger a state change signal.
344 */
345 triggerStateChange(args) {
346 this._stateChanged.emit(args);
347 }
348 /**
349 * Trigger a content changed signal.
350 */
351 triggerContentChange() {
352 this._contentChanged.emit(void 0);
353 this.dirty = true;
354 }
355 /**
356 * Whether the model is disposed.
357 */
358 get isDisposed() {
359 return this._isDisposed;
360 }
361}
362//# sourceMappingURL=model.js.map
\No newline at end of file