1 |
|
2 |
|
3 | import { MainAreaWidget, setToolbar } from '@jupyterlab/apputils';
|
4 | import { CodeEditor } from '@jupyterlab/codeeditor';
|
5 | import { Mode } from '@jupyterlab/codemirror';
|
6 | import { PathExt } from '@jupyterlab/coreutils';
|
7 | import * as models from '@jupyterlab/shared-models';
|
8 | import { nullTranslator } from '@jupyterlab/translation';
|
9 | import { Signal } from '@lumino/signaling';
|
10 |
|
11 |
|
12 |
|
13 | export class DocumentModel extends CodeEditor.Model {
|
14 | |
15 |
|
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 |
|
32 |
|
33 | get contentChanged() {
|
34 | return this._contentChanged;
|
35 | }
|
36 | |
37 |
|
38 |
|
39 | get stateChanged() {
|
40 | return this._stateChanged;
|
41 | }
|
42 | |
43 |
|
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 |
|
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 |
|
70 |
|
71 |
|
72 |
|
73 |
|
74 | get defaultKernelName() {
|
75 | return '';
|
76 | }
|
77 | |
78 |
|
79 |
|
80 |
|
81 |
|
82 |
|
83 | get defaultKernelLanguage() {
|
84 | return this._defaultLang;
|
85 | }
|
86 | |
87 |
|
88 |
|
89 | toString() {
|
90 | return this.value.text;
|
91 | }
|
92 | |
93 |
|
94 |
|
95 |
|
96 |
|
97 |
|
98 | fromString(value) {
|
99 | this.value.text = value;
|
100 | }
|
101 | |
102 |
|
103 |
|
104 | toJSON() {
|
105 | return JSON.parse(this.value.text || 'null');
|
106 | }
|
107 | |
108 |
|
109 |
|
110 |
|
111 |
|
112 |
|
113 | fromJSON(value) {
|
114 | this.fromString(JSON.stringify(value));
|
115 | }
|
116 | |
117 |
|
118 |
|
119 | initialize() {
|
120 | return;
|
121 | }
|
122 | |
123 |
|
124 |
|
125 | triggerStateChange(args) {
|
126 | this._stateChanged.emit(args);
|
127 | }
|
128 | |
129 |
|
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 |
|
147 |
|
148 | export class TextModelFactory {
|
149 | constructor() {
|
150 | this._isDisposed = false;
|
151 | }
|
152 | |
153 |
|
154 |
|
155 |
|
156 |
|
157 |
|
158 | get name() {
|
159 | return 'text';
|
160 | }
|
161 | |
162 |
|
163 |
|
164 |
|
165 |
|
166 |
|
167 | get contentType() {
|
168 | return 'file';
|
169 | }
|
170 | |
171 |
|
172 |
|
173 |
|
174 |
|
175 | get fileFormat() {
|
176 | return 'text';
|
177 | }
|
178 | |
179 |
|
180 |
|
181 | get isDisposed() {
|
182 | return this._isDisposed;
|
183 | }
|
184 | |
185 |
|
186 |
|
187 | dispose() {
|
188 | this._isDisposed = true;
|
189 | }
|
190 | |
191 |
|
192 |
|
193 |
|
194 |
|
195 |
|
196 |
|
197 |
|
198 |
|
199 | createNew(languagePreference, modelDB, isInitialized) {
|
200 | return new DocumentModel(languagePreference, modelDB);
|
201 | }
|
202 | |
203 |
|
204 |
|
205 | preferredLanguage(path) {
|
206 | const mode = Mode.findByFileName(path);
|
207 | return mode && mode.mode;
|
208 | }
|
209 | }
|
210 |
|
211 |
|
212 |
|
213 | export class Base64ModelFactory extends TextModelFactory {
|
214 | |
215 |
|
216 |
|
217 |
|
218 |
|
219 |
|
220 | get name() {
|
221 | return 'base64';
|
222 | }
|
223 | |
224 |
|
225 |
|
226 |
|
227 |
|
228 |
|
229 | get contentType() {
|
230 | return 'file';
|
231 | }
|
232 | |
233 |
|
234 |
|
235 |
|
236 |
|
237 | get fileFormat() {
|
238 | return 'base64';
|
239 | }
|
240 | }
|
241 |
|
242 |
|
243 |
|
244 | export class ABCWidgetFactory {
|
245 | |
246 |
|
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 |
|
265 |
|
266 | get widgetCreated() {
|
267 | return this._widgetCreated;
|
268 | }
|
269 | |
270 |
|
271 |
|
272 | get isDisposed() {
|
273 | return this._isDisposed;
|
274 | }
|
275 | |
276 |
|
277 |
|
278 | dispose() {
|
279 | if (this.isDisposed) {
|
280 | return;
|
281 | }
|
282 | this._isDisposed = true;
|
283 | Signal.clearData(this);
|
284 | }
|
285 | |
286 |
|
287 |
|
288 | get readOnly() {
|
289 | return this._readOnly;
|
290 | }
|
291 | |
292 |
|
293 |
|
294 | get name() {
|
295 | return this._name;
|
296 | }
|
297 | |
298 |
|
299 |
|
300 | get fileTypes() {
|
301 | return this._fileTypes.slice();
|
302 | }
|
303 | |
304 |
|
305 |
|
306 | get modelName() {
|
307 | return this._modelName;
|
308 | }
|
309 | |
310 |
|
311 |
|
312 | get defaultFor() {
|
313 | return this._defaultFor.slice();
|
314 | }
|
315 | |
316 |
|
317 |
|
318 |
|
319 | get defaultRendered() {
|
320 | return this._defaultRendered.slice();
|
321 | }
|
322 | |
323 |
|
324 |
|
325 | get preferKernel() {
|
326 | return this._preferKernel;
|
327 | }
|
328 | |
329 |
|
330 |
|
331 | get canStartKernel() {
|
332 | return this._canStartKernel;
|
333 | }
|
334 | |
335 |
|
336 |
|
337 | get translator() {
|
338 | return this._translator;
|
339 | }
|
340 | |
341 |
|
342 |
|
343 | get shutdownOnClose() {
|
344 | return this._shutdownOnClose;
|
345 | }
|
346 | set shutdownOnClose(value) {
|
347 | this._shutdownOnClose = value;
|
348 | }
|
349 | |
350 |
|
351 |
|
352 |
|
353 |
|
354 |
|
355 | createNew(context, source) {
|
356 | var _a;
|
357 |
|
358 | const widget = this.createNewWidget(context, source);
|
359 |
|
360 | setToolbar(widget, (_a = this._toolbarFactory) !== null && _a !== void 0 ? _a : this.defaultToolbarFactory.bind(this));
|
361 |
|
362 | this._widgetCreated.emit(widget);
|
363 | return widget;
|
364 | }
|
365 | |
366 |
|
367 |
|
368 | defaultToolbarFactory(widget) {
|
369 | return [];
|
370 | }
|
371 | }
|
372 |
|
373 |
|
374 |
|
375 | const DIRTY_CLASS = 'jp-mod-dirty';
|
376 |
|
377 |
|
378 |
|
379 | export class DocumentWidget extends MainAreaWidget {
|
380 | constructor(options) {
|
381 |
|
382 | options.reveal = Promise.all([options.reveal, options.context.ready]);
|
383 | super(options);
|
384 | this.context = options.context;
|
385 |
|
386 | this.context.pathChanged.connect(this._onPathChanged, this);
|
387 | this._onPathChanged(this.context, this.context.path);
|
388 |
|
389 | this.context.model.stateChanged.connect(this._onModelStateChanged, this);
|
390 | void this.context.ready.then(() => {
|
391 | this._handleDirtyState();
|
392 | });
|
393 |
|
394 | this.title.changed.connect(this._onTitleChanged, this);
|
395 | }
|
396 | |
397 |
|
398 |
|
399 | setFragment(fragment) {
|
400 |
|
401 | }
|
402 | |
403 |
|
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 |
|
417 | return;
|
418 | }
|
419 | }
|
420 |
|
421 | this.title.label = filename;
|
422 | }
|
423 | |
424 |
|
425 |
|
426 | _onPathChanged(sender, path) {
|
427 | this.title.label = PathExt.basename(sender.localPath);
|
428 | }
|
429 | |
430 |
|
431 |
|
432 | _onModelStateChanged(sender, args) {
|
433 | if (args.name === 'dirty') {
|
434 | this._handleDirtyState();
|
435 | }
|
436 | }
|
437 | |
438 |
|
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 |
|
\ | No newline at end of file |