1 |
|
2 |
|
3 | import { Dialog, showDialog } from '@jupyterlab/apputils';
|
4 | import { CodeCellModel, MarkdownCellModel, RawCellModel } from '@jupyterlab/cells';
|
5 | import * as nbformat from '@jupyterlab/nbformat';
|
6 | import { ModelDB } from '@jupyterlab/observables';
|
7 | import * as models from '@jupyterlab/shared-models';
|
8 | import { nullTranslator } from '@jupyterlab/translation';
|
9 | import { UUID } from '@lumino/coreutils';
|
10 | import { Signal } from '@lumino/signaling';
|
11 | import { CellList } from './celllist';
|
12 | const UNSHARED_KEYS = ['kernelspec', 'language_info'];
|
13 |
|
14 |
|
15 |
|
16 | export class NotebookModel {
|
17 | |
18 |
|
19 |
|
20 | constructor(options = {}) {
|
21 | |
22 |
|
23 |
|
24 | this._modelDBMutex = models.createMutex();
|
25 | this._readOnly = false;
|
26 | this._contentChanged = new Signal(this);
|
27 | this._stateChanged = new Signal(this);
|
28 | this._nbformat = nbformat.MAJOR_VERSION;
|
29 | this._nbformatMinor = nbformat.MINOR_VERSION;
|
30 | this._isDisposed = false;
|
31 | if (options.modelDB) {
|
32 | this.modelDB = options.modelDB;
|
33 | }
|
34 | else {
|
35 | this.modelDB = new ModelDB();
|
36 | }
|
37 | this.sharedModel = models.YNotebook.create(options.disableDocumentWideUndoRedo || false);
|
38 | this._isInitialized = options.isInitialized === false ? false : true;
|
39 | const factory = options.contentFactory || NotebookModel.defaultContentFactory;
|
40 | this.contentFactory = factory.clone(this.modelDB.view('cells'));
|
41 | this._cells = new CellList(this.modelDB, this.contentFactory, this.sharedModel);
|
42 | this._trans = (options.translator || nullTranslator).load('jupyterlab');
|
43 | this._cells.changed.connect(this._onCellsChanged, this);
|
44 |
|
45 | const metadata = this.modelDB.createMap('metadata');
|
46 | if (!metadata.has('language_info')) {
|
47 | const name = options.languagePreference || '';
|
48 | metadata.set('language_info', { name });
|
49 | }
|
50 | this._ensureMetadata();
|
51 | metadata.changed.connect(this._onMetadataChanged, this);
|
52 | this._deletedCells = [];
|
53 | this.sharedModel.dirty = false;
|
54 | this.sharedModel.changed.connect(this._onStateChanged, this);
|
55 | }
|
56 | |
57 |
|
58 |
|
59 | get contentChanged() {
|
60 | return this._contentChanged;
|
61 | }
|
62 | |
63 |
|
64 |
|
65 | get stateChanged() {
|
66 | return this._stateChanged;
|
67 | }
|
68 | |
69 |
|
70 |
|
71 | get dirty() {
|
72 | return this.sharedModel.dirty;
|
73 | }
|
74 | set dirty(newValue) {
|
75 | if (newValue === this.dirty) {
|
76 | return;
|
77 | }
|
78 | this.sharedModel.dirty = newValue;
|
79 | }
|
80 | |
81 |
|
82 |
|
83 | get readOnly() {
|
84 | return this._readOnly;
|
85 | }
|
86 | set readOnly(newValue) {
|
87 | if (newValue === this._readOnly) {
|
88 | return;
|
89 | }
|
90 | const oldValue = this._readOnly;
|
91 | this._readOnly = newValue;
|
92 | this.triggerStateChange({ name: 'readOnly', oldValue, newValue });
|
93 | }
|
94 | |
95 |
|
96 |
|
97 | get metadata() {
|
98 | return this.modelDB.get('metadata');
|
99 | }
|
100 | |
101 |
|
102 |
|
103 | get cells() {
|
104 | return this._cells;
|
105 | }
|
106 | |
107 |
|
108 |
|
109 | get nbformat() {
|
110 | return this._nbformat;
|
111 | }
|
112 | |
113 |
|
114 |
|
115 | get nbformatMinor() {
|
116 | return this._nbformatMinor;
|
117 | }
|
118 | |
119 |
|
120 |
|
121 | get defaultKernelName() {
|
122 | const spec = this.metadata.get('kernelspec');
|
123 | return spec ? spec.name : '';
|
124 | }
|
125 | |
126 |
|
127 |
|
128 | get deletedCells() {
|
129 | return this._deletedCells;
|
130 | }
|
131 | |
132 |
|
133 |
|
134 | get isInitialized() {
|
135 | return this._isInitialized;
|
136 | }
|
137 | |
138 |
|
139 |
|
140 | get defaultKernelLanguage() {
|
141 | const info = this.metadata.get('language_info');
|
142 | return info ? info.name : '';
|
143 | }
|
144 | |
145 |
|
146 |
|
147 | dispose() {
|
148 |
|
149 | if (this.isDisposed) {
|
150 | return;
|
151 | }
|
152 | const cells = this.cells;
|
153 | this._cells = null;
|
154 | cells.dispose();
|
155 | this._isDisposed = true;
|
156 | this.modelDB.dispose();
|
157 | Signal.clearData(this);
|
158 | }
|
159 | |
160 |
|
161 |
|
162 | toString() {
|
163 | return JSON.stringify(this.toJSON());
|
164 | }
|
165 | |
166 |
|
167 |
|
168 |
|
169 |
|
170 |
|
171 | fromString(value) {
|
172 | this.fromJSON(JSON.parse(value));
|
173 | }
|
174 | |
175 |
|
176 |
|
177 | toJSON() {
|
178 | var _a, _b;
|
179 | const cells = [];
|
180 | for (let i = 0; i < ((_b = (_a = this.cells) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0); i++) {
|
181 | const cell = this.cells.get(i).toJSON();
|
182 | if (this._nbformat === 4 && this._nbformatMinor <= 4) {
|
183 |
|
184 | delete cell.id;
|
185 | }
|
186 | cells.push(cell);
|
187 | }
|
188 | this._ensureMetadata();
|
189 | const metadata = this.sharedModel.getMetadata();
|
190 | for (const key of this.metadata.keys()) {
|
191 | metadata[key] = JSON.parse(JSON.stringify(this.metadata.get(key)));
|
192 | }
|
193 | return {
|
194 | metadata,
|
195 | nbformat_minor: this._nbformatMinor,
|
196 | nbformat: this._nbformat,
|
197 | cells
|
198 | };
|
199 | }
|
200 | |
201 |
|
202 |
|
203 |
|
204 |
|
205 |
|
206 | fromJSON(value) {
|
207 | const cells = [];
|
208 | const factory = this.contentFactory;
|
209 | const useId = value.nbformat === 4 && value.nbformat_minor >= 5;
|
210 | for (const cell of value.cells) {
|
211 | const options = { cell };
|
212 | if (useId) {
|
213 | options.id = cell.id;
|
214 | }
|
215 | switch (cell.cell_type) {
|
216 | case 'code':
|
217 | cells.push(factory.createCodeCell(options));
|
218 | break;
|
219 | case 'markdown':
|
220 | cells.push(factory.createMarkdownCell(options));
|
221 | break;
|
222 | case 'raw':
|
223 | cells.push(factory.createRawCell(options));
|
224 | break;
|
225 | default:
|
226 | continue;
|
227 | }
|
228 | }
|
229 | this.cells.beginCompoundOperation();
|
230 | this.cells.clear();
|
231 | this.cells.pushAll(cells);
|
232 | this.cells.endCompoundOperation();
|
233 | this.sharedModel.nbformat_minor =
|
234 | nbformat.MINOR_VERSION;
|
235 | this.sharedModel.nbformat = nbformat.MAJOR_VERSION;
|
236 | const origNbformat = value.metadata.orig_nbformat;
|
237 | if (value.nbformat !== this._nbformat) {
|
238 | this.sharedModel.nbformat = value.nbformat;
|
239 | }
|
240 | if (value.nbformat_minor > this._nbformatMinor) {
|
241 | this.sharedModel.nbformat_minor =
|
242 | value.nbformat_minor;
|
243 | }
|
244 |
|
245 | if (origNbformat !== undefined && this._nbformat !== origNbformat) {
|
246 | const newer = this._nbformat > origNbformat;
|
247 | let msg;
|
248 | if (newer) {
|
249 | msg = this._trans.__(`This notebook has been converted from an older notebook format (v%1)
|
250 | to the current notebook format (v%2).
|
251 | The next time you save this notebook, the current notebook format (v%2) will be used.
|
252 | 'Older versions of Jupyter may not be able to read the new format.' To preserve the original format version,
|
253 | close the notebook without saving it.`, origNbformat, this._nbformat);
|
254 | }
|
255 | else {
|
256 | msg = this._trans.__(`This notebook has been converted from an newer notebook format (v%1)
|
257 | to the current notebook format (v%2).
|
258 | The next time you save this notebook, the current notebook format (v%2) will be used.
|
259 | Some features of the original notebook may not be available.' To preserve the original format version,
|
260 | close the notebook without saving it.`, origNbformat, this._nbformat);
|
261 | }
|
262 | void showDialog({
|
263 | title: this._trans.__('Notebook converted'),
|
264 | body: msg,
|
265 | buttons: [Dialog.okButton({ label: this._trans.__('Ok') })]
|
266 | });
|
267 | }
|
268 |
|
269 | this.metadata.clear();
|
270 | const metadata = value.metadata;
|
271 | for (const key in metadata) {
|
272 |
|
273 | if (key === 'orig_nbformat') {
|
274 | continue;
|
275 | }
|
276 | this.metadata.set(key, metadata[key]);
|
277 | }
|
278 | this._ensureMetadata();
|
279 | this.dirty = true;
|
280 | }
|
281 | |
282 |
|
283 |
|
284 |
|
285 |
|
286 |
|
287 |
|
288 | initialize() {
|
289 | if (!this.cells.length) {
|
290 | const factory = this.contentFactory;
|
291 | this.cells.push(factory.createCodeCell({}));
|
292 | }
|
293 | this._isInitialized = true;
|
294 | this.cells.clearUndo();
|
295 | }
|
296 | |
297 |
|
298 |
|
299 | _onCellsChanged(list, change) {
|
300 | switch (change.type) {
|
301 | case 'add':
|
302 | change.newValues.forEach(cell => {
|
303 | cell.contentChanged.connect(this.triggerContentChange, this);
|
304 | });
|
305 | break;
|
306 | case 'remove':
|
307 | break;
|
308 | case 'set':
|
309 | change.newValues.forEach(cell => {
|
310 | cell.contentChanged.connect(this.triggerContentChange, this);
|
311 | });
|
312 | break;
|
313 | default:
|
314 | break;
|
315 | }
|
316 | this.triggerContentChange();
|
317 | }
|
318 | _onStateChanged(sender, changes) {
|
319 | if (changes.stateChange) {
|
320 | changes.stateChange.forEach(value => {
|
321 | if (value.name === 'nbformat') {
|
322 | this._nbformat = value.newValue;
|
323 | }
|
324 | if (value.name === 'nbformatMinor') {
|
325 | this._nbformatMinor = value.newValue;
|
326 | }
|
327 | if (value.name !== 'dirty' || value.oldValue !== value.newValue) {
|
328 | this.triggerStateChange(value);
|
329 | }
|
330 | });
|
331 | }
|
332 | if (changes.metadataChange) {
|
333 | const metadata = changes.metadataChange.newValue;
|
334 | this._modelDBMutex(() => {
|
335 | Object.entries(metadata).forEach(([key, value]) => {
|
336 | this.metadata.set(key, value);
|
337 | });
|
338 | });
|
339 | }
|
340 | }
|
341 | _onMetadataChanged(metadata, change) {
|
342 | if (!UNSHARED_KEYS.includes(change.key)) {
|
343 | this._modelDBMutex(() => {
|
344 | this.sharedModel.updateMetadata(metadata.toJSON());
|
345 | });
|
346 | }
|
347 | this.triggerContentChange();
|
348 | }
|
349 | |
350 |
|
351 |
|
352 | _ensureMetadata() {
|
353 | const metadata = this.metadata;
|
354 | if (!metadata.has('language_info')) {
|
355 | metadata.set('language_info', { name: '' });
|
356 | }
|
357 | if (!metadata.has('kernelspec')) {
|
358 | metadata.set('kernelspec', { name: '', display_name: '' });
|
359 | }
|
360 | }
|
361 | |
362 |
|
363 |
|
364 | triggerStateChange(args) {
|
365 | this._stateChanged.emit(args);
|
366 | }
|
367 | |
368 |
|
369 |
|
370 | triggerContentChange() {
|
371 | this._contentChanged.emit(void 0);
|
372 | this.dirty = true;
|
373 | }
|
374 | |
375 |
|
376 |
|
377 | get isDisposed() {
|
378 | return this._isDisposed;
|
379 | }
|
380 | }
|
381 |
|
382 |
|
383 |
|
384 | (function (NotebookModel) {
|
385 | |
386 |
|
387 |
|
388 | class ContentFactory {
|
389 | |
390 |
|
391 |
|
392 | constructor(options) {
|
393 | this.codeCellContentFactory =
|
394 | options.codeCellContentFactory || CodeCellModel.defaultContentFactory;
|
395 | this.modelDB = options.modelDB;
|
396 | }
|
397 | |
398 |
|
399 |
|
400 |
|
401 |
|
402 |
|
403 |
|
404 |
|
405 |
|
406 |
|
407 |
|
408 | createCell(type, options) {
|
409 | switch (type) {
|
410 | case 'code':
|
411 | return this.createCodeCell(options);
|
412 | case 'markdown':
|
413 | return this.createMarkdownCell(options);
|
414 | case 'raw':
|
415 | default:
|
416 | return this.createRawCell(options);
|
417 | }
|
418 | }
|
419 | |
420 |
|
421 |
|
422 |
|
423 |
|
424 |
|
425 |
|
426 |
|
427 |
|
428 |
|
429 | createCodeCell(options) {
|
430 | if (options.contentFactory) {
|
431 | options.contentFactory = this.codeCellContentFactory;
|
432 | }
|
433 | if (this.modelDB) {
|
434 | if (!options.id) {
|
435 | options.id = UUID.uuid4();
|
436 | }
|
437 | options.modelDB = this.modelDB.view(options.id);
|
438 | }
|
439 | return new CodeCellModel(options);
|
440 | }
|
441 | |
442 |
|
443 |
|
444 |
|
445 |
|
446 |
|
447 |
|
448 |
|
449 | createMarkdownCell(options) {
|
450 | if (this.modelDB) {
|
451 | if (!options.id) {
|
452 | options.id = UUID.uuid4();
|
453 | }
|
454 | options.modelDB = this.modelDB.view(options.id);
|
455 | }
|
456 | return new MarkdownCellModel(options);
|
457 | }
|
458 | |
459 |
|
460 |
|
461 |
|
462 |
|
463 |
|
464 |
|
465 |
|
466 | createRawCell(options) {
|
467 | if (this.modelDB) {
|
468 | if (!options.id) {
|
469 | options.id = UUID.uuid4();
|
470 | }
|
471 | options.modelDB = this.modelDB.view(options.id);
|
472 | }
|
473 | return new RawCellModel(options);
|
474 | }
|
475 | |
476 |
|
477 |
|
478 | clone(modelDB) {
|
479 | return new ContentFactory({
|
480 | modelDB: modelDB,
|
481 | codeCellContentFactory: this.codeCellContentFactory
|
482 | });
|
483 | }
|
484 | }
|
485 | NotebookModel.ContentFactory = ContentFactory;
|
486 | |
487 |
|
488 |
|
489 | NotebookModel.defaultContentFactory = new ContentFactory({});
|
490 | })(NotebookModel || (NotebookModel = {}));
|
491 |
|
\ | No newline at end of file |