UNPKG

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