UNPKG

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