UNPKG

13.6 kBJavaScriptView Raw
1// Copyright (c) Jupyter Development Team.
2// Distributed under the terms of the Modified BSD License.
3import { MainAreaWidget, setToolbar } from '@jupyterlab/apputils';
4import { CodeEditor } from '@jupyterlab/codeeditor';
5import { Mode } from '@jupyterlab/codemirror';
6import { PathExt } from '@jupyterlab/coreutils';
7import * as models from '@jupyter/ydoc';
8import { nullTranslator } from '@jupyterlab/translation';
9import { Signal } from '@lumino/signaling';
10/**
11 * The default implementation of a document model.
12 */
13export class DocumentModel extends CodeEditor.Model {
14 /**
15 * Construct a new document model.
16 */
17 constructor(languagePreference, modelDB, collaborationEnabled) {
18 super({ modelDB });
19 this._defaultLang = '';
20 this._dirty = false;
21 this._readOnly = false;
22 this._contentChanged = new Signal(this);
23 this._stateChanged = new Signal(this);
24 this._defaultLang = languagePreference || '';
25 const filemodel = new models.YFile();
26 this.switchSharedModel(filemodel, true);
27 this.value.changed.connect(this.triggerContentChange, this);
28 this.sharedModel.changed.connect(this._onStateChanged, this);
29 this._collaborationEnabled = !!collaborationEnabled;
30 }
31 /**
32 * A signal emitted when the document content changes.
33 */
34 get contentChanged() {
35 return this._contentChanged;
36 }
37 /**
38 * A signal emitted when the document state changes.
39 */
40 get stateChanged() {
41 return this._stateChanged;
42 }
43 /**
44 * The dirty state of the document.
45 */
46 get dirty() {
47 return this._dirty;
48 }
49 set dirty(newValue) {
50 const oldValue = this._dirty;
51 if (newValue === oldValue) {
52 return;
53 }
54 this._dirty = newValue;
55 this.triggerStateChange({
56 name: 'dirty',
57 oldValue,
58 newValue
59 });
60 }
61 /**
62 * The read only state of the document.
63 */
64 get readOnly() {
65 return this._readOnly;
66 }
67 set readOnly(newValue) {
68 if (newValue === this._readOnly) {
69 return;
70 }
71 const oldValue = this._readOnly;
72 this._readOnly = newValue;
73 this.triggerStateChange({ name: 'readOnly', oldValue, newValue });
74 }
75 /**
76 * The default kernel name of the document.
77 *
78 * #### Notes
79 * This is a read-only property.
80 */
81 get defaultKernelName() {
82 return '';
83 }
84 /**
85 * The default kernel language of the document.
86 *
87 * #### Notes
88 * This is a read-only property.
89 */
90 get defaultKernelLanguage() {
91 return this._defaultLang;
92 }
93 /**
94 * Whether the model is collaborative or not.
95 */
96 get collaborative() {
97 return this._collaborationEnabled;
98 }
99 /**
100 * Serialize the model to a string.
101 */
102 toString() {
103 return this.value.text;
104 }
105 /**
106 * Deserialize the model from a string.
107 *
108 * #### Notes
109 * Should emit a [contentChanged] signal.
110 */
111 fromString(value) {
112 this.value.text = value;
113 }
114 /**
115 * Serialize the model to JSON.
116 */
117 toJSON() {
118 return JSON.parse(this.value.text || 'null');
119 }
120 /**
121 * Deserialize the model from JSON.
122 *
123 * #### Notes
124 * Should emit a [contentChanged] signal.
125 */
126 fromJSON(value) {
127 this.fromString(JSON.stringify(value));
128 }
129 /**
130 * Initialize the model with its current state.
131 */
132 initialize() {
133 return;
134 }
135 /**
136 * Trigger a state change signal.
137 */
138 triggerStateChange(args) {
139 this._stateChanged.emit(args);
140 }
141 /**
142 * Trigger a content changed signal.
143 */
144 triggerContentChange() {
145 this._contentChanged.emit(void 0);
146 this.dirty = true;
147 }
148 _onStateChanged(sender, changes) {
149 if (changes.sourceChange) {
150 this.triggerContentChange();
151 }
152 if (changes.stateChange) {
153 changes.stateChange.forEach(value => {
154 if (value.name === 'dirty') {
155 // Setting `dirty` will trigger the state change.
156 // We always set `dirty` because the shared model state
157 // and the local attribute are synchronized one way shared model -> _dirty
158 this.dirty = value.newValue;
159 }
160 else if (value.oldValue !== value.newValue) {
161 this.triggerStateChange(Object.assign({ newValue: undefined, oldValue: undefined }, value));
162 }
163 });
164 }
165 }
166}
167/**
168 * An implementation of a model factory for text files.
169 */
170export class TextModelFactory {
171 /**
172 * Instantiates a TextModelFactory.
173 */
174 constructor(collaborative) {
175 this._isDisposed = false;
176 this._collaborative = collaborative !== null && collaborative !== void 0 ? collaborative : true;
177 }
178 /**
179 * The name of the model type.
180 *
181 * #### Notes
182 * This is a read-only property.
183 */
184 get name() {
185 return 'text';
186 }
187 /**
188 * The type of the file.
189 *
190 * #### Notes
191 * This is a read-only property.
192 */
193 get contentType() {
194 return 'file';
195 }
196 /**
197 * The format of the file.
198 *
199 * This is a read-only property.
200 */
201 get fileFormat() {
202 return 'text';
203 }
204 /**
205 * Whether the model is collaborative or not.
206 */
207 get collaborative() {
208 return this._collaborative;
209 }
210 /**
211 * Get whether the model factory has been disposed.
212 */
213 get isDisposed() {
214 return this._isDisposed;
215 }
216 /**
217 * Dispose of the resources held by the model factory.
218 */
219 dispose() {
220 this._isDisposed = true;
221 }
222 /**
223 * Create a new model.
224 *
225 * @param languagePreference - An optional kernel language preference.
226 * @param modelDB - An optional model storage.
227 * @param isInitialized - Whether the model is initialized or not.
228 * @param collaborationEnabled - Whether collaboration is enabled at the application level or not (default `false`).
229 *
230 * @returns A new document model.
231 */
232 createNew(languagePreference, modelDB, isInitialized, collaborationEnabled) {
233 const collaborative = collaborationEnabled && this.collaborative;
234 return new DocumentModel(languagePreference, modelDB, collaborative);
235 }
236 /**
237 * Get the preferred kernel language given a file path.
238 */
239 preferredLanguage(path) {
240 const mode = Mode.findByFileName(path);
241 return mode && mode.mode;
242 }
243}
244/**
245 * An implementation of a model factory for base64 files.
246 */
247export class Base64ModelFactory extends TextModelFactory {
248 /**
249 * The name of the model type.
250 *
251 * #### Notes
252 * This is a read-only property.
253 */
254 get name() {
255 return 'base64';
256 }
257 /**
258 * The type of the file.
259 *
260 * #### Notes
261 * This is a read-only property.
262 */
263 get contentType() {
264 return 'file';
265 }
266 /**
267 * The format of the file.
268 *
269 * This is a read-only property.
270 */
271 get fileFormat() {
272 return 'base64';
273 }
274}
275/**
276 * The default implementation of a widget factory.
277 */
278export class ABCWidgetFactory {
279 /**
280 * Construct a new `ABCWidgetFactory`.
281 */
282 constructor(options) {
283 this._isDisposed = false;
284 this._widgetCreated = new Signal(this);
285 this._translator = options.translator || nullTranslator;
286 this._name = options.name;
287 this._readOnly = options.readOnly === undefined ? false : options.readOnly;
288 this._defaultFor = options.defaultFor ? options.defaultFor.slice() : [];
289 this._defaultRendered = (options.defaultRendered || []).slice();
290 this._fileTypes = options.fileTypes.slice();
291 this._modelName = options.modelName || 'text';
292 this._preferKernel = !!options.preferKernel;
293 this._canStartKernel = !!options.canStartKernel;
294 this._shutdownOnClose = !!options.shutdownOnClose;
295 this._toolbarFactory = options.toolbarFactory;
296 }
297 /**
298 * A signal emitted when a widget is created.
299 */
300 get widgetCreated() {
301 return this._widgetCreated;
302 }
303 /**
304 * Get whether the model factory has been disposed.
305 */
306 get isDisposed() {
307 return this._isDisposed;
308 }
309 /**
310 * Dispose of the resources used by the document manager.
311 */
312 dispose() {
313 if (this.isDisposed) {
314 return;
315 }
316 this._isDisposed = true;
317 Signal.clearData(this);
318 }
319 /**
320 * Whether the widget factory is read only.
321 */
322 get readOnly() {
323 return this._readOnly;
324 }
325 /**
326 * The name of the widget to display in dialogs.
327 */
328 get name() {
329 return this._name;
330 }
331 /**
332 * The file types the widget can view.
333 */
334 get fileTypes() {
335 return this._fileTypes.slice();
336 }
337 /**
338 * The registered name of the model type used to create the widgets.
339 */
340 get modelName() {
341 return this._modelName;
342 }
343 /**
344 * The file types for which the factory should be the default.
345 */
346 get defaultFor() {
347 return this._defaultFor.slice();
348 }
349 /**
350 * The file types for which the factory should be the default for
351 * rendering a document model, if different from editing.
352 */
353 get defaultRendered() {
354 return this._defaultRendered.slice();
355 }
356 /**
357 * Whether the widgets prefer having a kernel started.
358 */
359 get preferKernel() {
360 return this._preferKernel;
361 }
362 /**
363 * Whether the widgets can start a kernel when opened.
364 */
365 get canStartKernel() {
366 return this._canStartKernel;
367 }
368 /**
369 * The application language translator.
370 */
371 get translator() {
372 return this._translator;
373 }
374 /**
375 * Whether the kernel should be shutdown when the widget is closed.
376 */
377 get shutdownOnClose() {
378 return this._shutdownOnClose;
379 }
380 set shutdownOnClose(value) {
381 this._shutdownOnClose = value;
382 }
383 /**
384 * Create a new widget given a document model and a context.
385 *
386 * #### Notes
387 * It should emit the [widgetCreated] signal with the new widget.
388 */
389 createNew(context, source) {
390 var _a;
391 // Create the new widget
392 const widget = this.createNewWidget(context, source);
393 // Add toolbar
394 setToolbar(widget, (_a = this._toolbarFactory) !== null && _a !== void 0 ? _a : this.defaultToolbarFactory.bind(this));
395 // Emit widget created signal
396 this._widgetCreated.emit(widget);
397 return widget;
398 }
399 /**
400 * Default factory for toolbar items to be added after the widget is created.
401 */
402 defaultToolbarFactory(widget) {
403 return [];
404 }
405}
406/**
407 * The class name added to a dirty widget.
408 */
409const DIRTY_CLASS = 'jp-mod-dirty';
410/**
411 * A document widget implementation.
412 */
413export class DocumentWidget extends MainAreaWidget {
414 constructor(options) {
415 // Include the context ready promise in the widget reveal promise
416 options.reveal = Promise.all([options.reveal, options.context.ready]);
417 super(options);
418 this.context = options.context;
419 // Handle context path changes
420 this.context.pathChanged.connect(this._onPathChanged, this);
421 this._onPathChanged(this.context, this.context.path);
422 // Listen for changes in the dirty state.
423 this.context.model.stateChanged.connect(this._onModelStateChanged, this);
424 void this.context.ready.then(() => {
425 this._handleDirtyState();
426 });
427 // listen for changes to the title object
428 this.title.changed.connect(this._onTitleChanged, this);
429 }
430 /**
431 * Set URI fragment identifier.
432 */
433 setFragment(fragment) {
434 /* no-op */
435 }
436 /**
437 * Handle a title change.
438 */
439 async _onTitleChanged(_sender) {
440 const validNameExp = /[\/\\:]/;
441 const name = this.title.label;
442 const filename = this.context.path.split('/').pop();
443 if (name === filename) {
444 return;
445 }
446 if (name.length > 0 && !validNameExp.test(name)) {
447 const oldPath = this.context.path;
448 await this.context.rename(name);
449 if (this.context.path !== oldPath) {
450 // Rename succeeded
451 return;
452 }
453 }
454 // Reset title if name is invalid or rename fails
455 this.title.label = filename;
456 }
457 /**
458 * Handle a path change.
459 */
460 _onPathChanged(sender, path) {
461 this.title.label = PathExt.basename(sender.localPath);
462 // The document is not untitled any more.
463 this.isUntitled = false;
464 }
465 /**
466 * Handle a change to the context model state.
467 */
468 _onModelStateChanged(sender, args) {
469 if (args.name === 'dirty') {
470 this._handleDirtyState();
471 }
472 }
473 /**
474 * Handle the dirty state of the context model.
475 */
476 _handleDirtyState() {
477 if (this.context.model.dirty &&
478 !this.title.className.includes(DIRTY_CLASS)) {
479 this.title.className += ` ${DIRTY_CLASS}`;
480 }
481 else {
482 this.title.className = this.title.className.replace(DIRTY_CLASS, '');
483 }
484 }
485}
486//# sourceMappingURL=default.js.map
\No newline at end of file