1 |
|
2 |
|
3 | import { Dialog, SessionContext, sessionContextDialogs, showDialog, showErrorMessage } from '@jupyterlab/apputils';
|
4 | import { PageConfig, PathExt } from '@jupyterlab/coreutils';
|
5 | import { ProviderMock } from '@jupyterlab/docprovider';
|
6 | import { RenderMimeRegistry } from '@jupyterlab/rendermime';
|
7 | import { nullTranslator } from '@jupyterlab/translation';
|
8 | import { PromiseDelegate } from '@lumino/coreutils';
|
9 | import { DisposableDelegate } from '@lumino/disposable';
|
10 | import { Signal } from '@lumino/signaling';
|
11 | import { Widget } from '@lumino/widgets';
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 | export class Context {
|
18 | |
19 |
|
20 |
|
21 | constructor(options) {
|
22 | this._path = '';
|
23 | this._lineEnding = null;
|
24 | this._contentsModel = null;
|
25 | this._populatedPromise = new PromiseDelegate();
|
26 | this._isPopulated = false;
|
27 | this._isReady = false;
|
28 | this._isDisposed = false;
|
29 | this._pathChanged = new Signal(this);
|
30 | this._fileChanged = new Signal(this);
|
31 | this._saveState = new Signal(this);
|
32 | this._disposed = new Signal(this);
|
33 | this._lastModifiedCheckMargin = 500;
|
34 | this._timeConflictModalIsOpen = false;
|
35 | const manager = (this._manager = options.manager);
|
36 | this.translator = options.translator || nullTranslator;
|
37 | this._trans = this.translator.load('jupyterlab');
|
38 | this._factory = options.factory;
|
39 | this._dialogs = options.sessionDialogs || sessionContextDialogs;
|
40 | this._opener = options.opener || Private.noOp;
|
41 | this._path = this._manager.contents.normalize(options.path);
|
42 | this._lastModifiedCheckMargin = options.lastModifiedCheckMargin || 500;
|
43 | const localPath = this._manager.contents.localPath(this._path);
|
44 | const lang = this._factory.preferredLanguage(PathExt.basename(localPath));
|
45 | const dbFactory = options.modelDBFactory;
|
46 | if (dbFactory) {
|
47 | const localPath = manager.contents.localPath(this._path);
|
48 | this._modelDB = dbFactory.createNew(localPath);
|
49 | this._model = this._factory.createNew(lang, this._modelDB, false, PageConfig.getOption('collaborative') === 'true');
|
50 | }
|
51 | else {
|
52 | this._model = this._factory.createNew(lang, undefined, false, PageConfig.getOption('collaborative') === 'true');
|
53 | }
|
54 | const docProviderFactory = options.docProviderFactory;
|
55 | this._provider = docProviderFactory
|
56 | ? docProviderFactory({
|
57 | path: this._path,
|
58 | contentType: this._factory.contentType,
|
59 | format: this._factory.fileFormat,
|
60 | model: this._model.sharedModel,
|
61 | collaborative: this._model.collaborative
|
62 | })
|
63 | : new ProviderMock();
|
64 | this._readyPromise = manager.ready.then(() => {
|
65 | return this._populatedPromise.promise;
|
66 | });
|
67 | const ext = PathExt.extname(this._path);
|
68 | this.sessionContext = new SessionContext({
|
69 | sessionManager: manager.sessions,
|
70 | specsManager: manager.kernelspecs,
|
71 | path: this._path,
|
72 | type: ext === '.ipynb' ? 'notebook' : 'file',
|
73 | name: PathExt.basename(localPath),
|
74 | kernelPreference: options.kernelPreference || { shouldStart: false },
|
75 | setBusy: options.setBusy
|
76 | });
|
77 | this.sessionContext.propertyChanged.connect(this._onSessionChanged, this);
|
78 | manager.contents.fileChanged.connect(this._onFileChanged, this);
|
79 | this.urlResolver = new RenderMimeRegistry.UrlResolver({
|
80 | path: this._path,
|
81 | contents: manager.contents
|
82 | });
|
83 | this.model.sharedModel.changed.connect(this.onStateChanged, this);
|
84 | }
|
85 | |
86 |
|
87 |
|
88 | get pathChanged() {
|
89 | return this._pathChanged;
|
90 | }
|
91 | |
92 |
|
93 |
|
94 | get fileChanged() {
|
95 | return this._fileChanged;
|
96 | }
|
97 | |
98 |
|
99 |
|
100 | get saveState() {
|
101 | return this._saveState;
|
102 | }
|
103 | |
104 |
|
105 |
|
106 | get disposed() {
|
107 | return this._disposed;
|
108 | }
|
109 | |
110 |
|
111 |
|
112 | get lastModifiedCheckMargin() {
|
113 | return this._lastModifiedCheckMargin;
|
114 | }
|
115 | set lastModifiedCheckMargin(value) {
|
116 | this._lastModifiedCheckMargin = value;
|
117 | }
|
118 | |
119 |
|
120 |
|
121 | get model() {
|
122 | return this._model;
|
123 | }
|
124 | |
125 |
|
126 |
|
127 | get path() {
|
128 | return this._path;
|
129 | }
|
130 | |
131 |
|
132 |
|
133 |
|
134 |
|
135 | get localPath() {
|
136 | return this._manager.contents.localPath(this._path);
|
137 | }
|
138 | |
139 |
|
140 |
|
141 |
|
142 |
|
143 |
|
144 |
|
145 | get contentsModel() {
|
146 | return this._contentsModel;
|
147 | }
|
148 | |
149 |
|
150 |
|
151 |
|
152 |
|
153 |
|
154 | get factoryName() {
|
155 | return this.isDisposed ? '' : this._factory.name;
|
156 | }
|
157 | |
158 |
|
159 |
|
160 | get isDisposed() {
|
161 | return this._isDisposed;
|
162 | }
|
163 | |
164 |
|
165 |
|
166 | dispose() {
|
167 | if (this.isDisposed) {
|
168 | return;
|
169 | }
|
170 | this._isDisposed = true;
|
171 | this.sessionContext.dispose();
|
172 | if (this._modelDB) {
|
173 | this._modelDB.dispose();
|
174 | }
|
175 | this._model.dispose();
|
176 | this._provider.dispose();
|
177 | this._model.sharedModel.dispose();
|
178 | this._disposed.emit(void 0);
|
179 | Signal.clearData(this);
|
180 | }
|
181 | |
182 |
|
183 |
|
184 | get isReady() {
|
185 | return this._isReady;
|
186 | }
|
187 | |
188 |
|
189 |
|
190 | get ready() {
|
191 | return this._readyPromise;
|
192 | }
|
193 | |
194 |
|
195 |
|
196 | get canSave() {
|
197 | var _a;
|
198 | return !!(((_a = this._contentsModel) === null || _a === void 0 ? void 0 : _a.writable) && !this._model.collaborative);
|
199 | }
|
200 | |
201 |
|
202 |
|
203 |
|
204 |
|
205 |
|
206 |
|
207 | async initialize(isNew) {
|
208 | if (this._model.collaborative) {
|
209 | await this._loadContext();
|
210 | }
|
211 | else {
|
212 | if (isNew) {
|
213 | await this._save();
|
214 | }
|
215 | else {
|
216 | await this._revert();
|
217 | }
|
218 | this._model.initialize();
|
219 | }
|
220 | this.model.sharedModel.clearUndoHistory();
|
221 | }
|
222 | |
223 |
|
224 |
|
225 |
|
226 |
|
227 | rename(newName) {
|
228 | return this.ready.then(() => {
|
229 | return this._manager.ready.then(() => {
|
230 | return this._rename(newName);
|
231 | });
|
232 | });
|
233 | }
|
234 | |
235 |
|
236 |
|
237 | async save() {
|
238 | await this.ready;
|
239 | await this._save();
|
240 | }
|
241 | |
242 |
|
243 |
|
244 |
|
245 |
|
246 | async saveAs() {
|
247 | await this.ready;
|
248 | const localPath = this._manager.contents.localPath(this.path);
|
249 | const newLocalPath = await Private.getSavePath(localPath);
|
250 | if (this.isDisposed || !newLocalPath) {
|
251 | return;
|
252 | }
|
253 | const drive = this._manager.contents.driveName(this.path);
|
254 | const newPath = drive == '' ? newLocalPath : `${drive}:${newLocalPath}`;
|
255 | if (newPath === this._path) {
|
256 | return this.save();
|
257 | }
|
258 |
|
259 | try {
|
260 | await this._manager.ready;
|
261 | await this._manager.contents.get(newPath);
|
262 | await this._maybeOverWrite(newPath);
|
263 | }
|
264 | catch (err) {
|
265 | if (!err.response || err.response.status !== 404) {
|
266 | throw err;
|
267 | }
|
268 | await this._finishSaveAs(newPath);
|
269 | }
|
270 | }
|
271 | |
272 |
|
273 |
|
274 |
|
275 |
|
276 |
|
277 |
|
278 |
|
279 | async download() {
|
280 | const url = await this._manager.contents.getDownloadUrl(this._path);
|
281 | const element = document.createElement('a');
|
282 | element.href = url;
|
283 | element.download = '';
|
284 | document.body.appendChild(element);
|
285 | element.click();
|
286 | document.body.removeChild(element);
|
287 | return void 0;
|
288 | }
|
289 | |
290 |
|
291 |
|
292 | async revert() {
|
293 | await this.ready;
|
294 | await this._revert();
|
295 | }
|
296 | |
297 |
|
298 |
|
299 | createCheckpoint() {
|
300 | const contents = this._manager.contents;
|
301 | return this._manager.ready.then(() => {
|
302 | return contents.createCheckpoint(this._path);
|
303 | });
|
304 | }
|
305 | |
306 |
|
307 |
|
308 | deleteCheckpoint(checkpointId) {
|
309 | const contents = this._manager.contents;
|
310 | return this._manager.ready.then(() => {
|
311 | return contents.deleteCheckpoint(this._path, checkpointId);
|
312 | });
|
313 | }
|
314 | |
315 |
|
316 |
|
317 | restoreCheckpoint(checkpointId) {
|
318 | const contents = this._manager.contents;
|
319 | const path = this._path;
|
320 | return this._manager.ready.then(() => {
|
321 | if (checkpointId) {
|
322 | return contents.restoreCheckpoint(path, checkpointId);
|
323 | }
|
324 | return this.listCheckpoints().then(checkpoints => {
|
325 | if (this.isDisposed || !checkpoints.length) {
|
326 | return;
|
327 | }
|
328 | checkpointId = checkpoints[checkpoints.length - 1].id;
|
329 | return contents.restoreCheckpoint(path, checkpointId);
|
330 | });
|
331 | });
|
332 | }
|
333 | |
334 |
|
335 |
|
336 | listCheckpoints() {
|
337 | const contents = this._manager.contents;
|
338 | return this._manager.ready.then(() => {
|
339 | return contents.listCheckpoints(this._path);
|
340 | });
|
341 | }
|
342 | |
343 |
|
344 |
|
345 |
|
346 |
|
347 |
|
348 |
|
349 |
|
350 |
|
351 |
|
352 |
|
353 |
|
354 |
|
355 | addSibling(widget, options = {}) {
|
356 | const opener = this._opener;
|
357 | if (opener) {
|
358 | opener(widget, options);
|
359 | }
|
360 | return new DisposableDelegate(() => {
|
361 | widget.close();
|
362 | });
|
363 | }
|
364 | onStateChanged(sender, changes) {
|
365 | if (changes.stateChange) {
|
366 | changes.stateChange.forEach(change => {
|
367 | var _a, _b;
|
368 | if (change.name === 'path') {
|
369 | const driveName = this._manager.contents.driveName(this._path);
|
370 | let newPath = change.newValue;
|
371 | if (driveName) {
|
372 | newPath = `${driveName}:${change.newValue}`;
|
373 | }
|
374 | if (this._path !== newPath) {
|
375 | this._path = newPath;
|
376 | const localPath = this._manager.contents.localPath(newPath);
|
377 | const name = PathExt.basename(localPath);
|
378 | (_a = this.sessionContext.session) === null || _a === void 0 ? void 0 : _a.setPath(newPath);
|
379 | void ((_b = this.sessionContext.session) === null || _b === void 0 ? void 0 : _b.setName(name));
|
380 | this.urlResolver.path = newPath;
|
381 | if (this._contentsModel) {
|
382 | const contentsModel = Object.assign(Object.assign({}, this._contentsModel), { name: name, path: newPath });
|
383 | this._updateContentsModel(contentsModel);
|
384 | }
|
385 | this._pathChanged.emit(newPath);
|
386 | }
|
387 | }
|
388 | });
|
389 | }
|
390 | }
|
391 | |
392 |
|
393 |
|
394 | _onFileChanged(sender, change) {
|
395 | var _a, _b, _c;
|
396 | if (change.type !== 'rename') {
|
397 | return;
|
398 | }
|
399 | let oldPath = change.oldValue && change.oldValue.path;
|
400 | let newPath = change.newValue && change.newValue.path;
|
401 | if (newPath && this._path.indexOf(oldPath || '') === 0) {
|
402 | let changeModel = change.newValue;
|
403 |
|
404 |
|
405 | if (oldPath !== this._path) {
|
406 | newPath = this._path.replace(new RegExp(`^${oldPath}/`), `${newPath}/`);
|
407 | oldPath = this._path;
|
408 |
|
409 | changeModel = {
|
410 | last_modified: (_a = change.newValue) === null || _a === void 0 ? void 0 : _a.created,
|
411 | path: newPath
|
412 | };
|
413 | }
|
414 | this._path = newPath;
|
415 | const updateModel = Object.assign(Object.assign({}, this._contentsModel), changeModel);
|
416 | const localPath = this._manager.contents.localPath(newPath);
|
417 | void ((_b = this.sessionContext.session) === null || _b === void 0 ? void 0 : _b.setPath(newPath));
|
418 | void ((_c = this.sessionContext.session) === null || _c === void 0 ? void 0 : _c.setName(PathExt.basename(localPath)));
|
419 | this.urlResolver.path = newPath;
|
420 | this._updateContentsModel(updateModel);
|
421 | this._model.sharedModel.setState('path', localPath);
|
422 | this._pathChanged.emit(newPath);
|
423 | }
|
424 | }
|
425 | |
426 |
|
427 |
|
428 | _onSessionChanged(sender, type) {
|
429 | if (type !== 'path') {
|
430 | return;
|
431 | }
|
432 | const path = this.sessionContext.session.path;
|
433 | if (path !== this._path) {
|
434 | this._path = path;
|
435 | const localPath = this._manager.contents.localPath(path);
|
436 | const name = PathExt.basename(localPath);
|
437 | this.urlResolver.path = path;
|
438 | if (this._contentsModel) {
|
439 | const contentsModel = Object.assign(Object.assign({}, this._contentsModel), { name: name, path: path });
|
440 | this._updateContentsModel(contentsModel);
|
441 | }
|
442 | this._model.sharedModel.setState('path', localPath);
|
443 | this._pathChanged.emit(path);
|
444 | }
|
445 | }
|
446 | |
447 |
|
448 |
|
449 | _updateContentsModel(model) {
|
450 | const writable = model.writable && !this._model.collaborative;
|
451 | const newModel = {
|
452 | path: model.path,
|
453 | name: model.name,
|
454 | type: model.type,
|
455 | content: undefined,
|
456 | writable,
|
457 | created: model.created,
|
458 | last_modified: model.last_modified,
|
459 | mimetype: model.mimetype,
|
460 | format: model.format
|
461 | };
|
462 | const mod = this._contentsModel ? this._contentsModel.last_modified : null;
|
463 | this._contentsModel = newModel;
|
464 | this._model.sharedModel.setState('last_modified', newModel.last_modified);
|
465 | if (!mod || newModel.last_modified !== mod) {
|
466 | this._fileChanged.emit(newModel);
|
467 | }
|
468 | }
|
469 | |
470 |
|
471 |
|
472 | async _populate() {
|
473 | await this._provider.ready;
|
474 | this._isPopulated = true;
|
475 | this._isReady = true;
|
476 | this._populatedPromise.resolve(void 0);
|
477 |
|
478 | await this._maybeCheckpoint(false);
|
479 | if (this.isDisposed) {
|
480 | return;
|
481 | }
|
482 |
|
483 | const name = this._model.defaultKernelName ||
|
484 | this.sessionContext.kernelPreference.name;
|
485 | this.sessionContext.kernelPreference = Object.assign(Object.assign({}, this.sessionContext.kernelPreference), { name, language: this._model.defaultKernelLanguage });
|
486 |
|
487 |
|
488 |
|
489 | void this.sessionContext.initialize().then(shouldSelect => {
|
490 | if (shouldSelect) {
|
491 | void this._dialogs.selectKernel(this.sessionContext, this.translator);
|
492 | }
|
493 | });
|
494 | }
|
495 | |
496 |
|
497 |
|
498 |
|
499 |
|
500 | async _rename(newName) {
|
501 | var _a, _b;
|
502 | const splitPath = this.localPath.split('/');
|
503 | splitPath[splitPath.length - 1] = newName;
|
504 | let newPath = PathExt.join(...splitPath);
|
505 | const driveName = this._manager.contents.driveName(this.path);
|
506 | if (driveName) {
|
507 | newPath = `${driveName}:${newPath}`;
|
508 | }
|
509 |
|
510 | await this._manager.contents.rename(this.path, newPath);
|
511 | await ((_a = this.sessionContext.session) === null || _a === void 0 ? void 0 : _a.setPath(newPath));
|
512 | await ((_b = this.sessionContext.session) === null || _b === void 0 ? void 0 : _b.setName(newName));
|
513 | this._path = newPath;
|
514 | const localPath = this._manager.contents.localPath(this._path);
|
515 | this.urlResolver.path = newPath;
|
516 | this._model.sharedModel.setState('path', localPath);
|
517 | this._pathChanged.emit(newPath);
|
518 | }
|
519 | |
520 |
|
521 |
|
522 | async _save() {
|
523 |
|
524 |
|
525 | if (this._model.collaborative) {
|
526 | return;
|
527 | }
|
528 | this._saveState.emit('started');
|
529 | const options = this._createSaveOptions();
|
530 | try {
|
531 | let value;
|
532 | await this._manager.ready;
|
533 | value = await this._maybeSave(options);
|
534 | if (this.isDisposed) {
|
535 | return;
|
536 | }
|
537 | this._model.dirty = false;
|
538 | this._updateContentsModel(value);
|
539 | if (!this._isPopulated) {
|
540 | await this._populate();
|
541 | }
|
542 |
|
543 | this._saveState.emit('completed');
|
544 | }
|
545 | catch (err) {
|
546 |
|
547 |
|
548 |
|
549 | if (err.message === 'Cancel' ||
|
550 | err.message === 'Modal is already displayed') {
|
551 | throw err;
|
552 | }
|
553 |
|
554 | const localPath = this._manager.contents.localPath(this._path);
|
555 | const name = PathExt.basename(localPath);
|
556 | void this._handleError(err, this._trans.__('File Save Error for %1', name));
|
557 |
|
558 | this._saveState.emit('failed');
|
559 | throw err;
|
560 | }
|
561 | }
|
562 | |
563 |
|
564 |
|
565 | _loadContext() {
|
566 | const opts = Object.assign({ type: this._factory.contentType, content: false }, (this._factory.fileFormat !== null
|
567 | ? { format: this._factory.fileFormat }
|
568 | : {}));
|
569 | const path = this._path;
|
570 | return this._manager.ready
|
571 | .then(() => {
|
572 | return this._manager.contents.get(path, opts);
|
573 | })
|
574 | .then(contents => {
|
575 | if (this.isDisposed) {
|
576 | return;
|
577 | }
|
578 | const model = Object.assign(Object.assign({}, contents), { format: this._factory.fileFormat });
|
579 | this._updateContentsModel(model);
|
580 | this._model.dirty = false;
|
581 | if (!this._isPopulated) {
|
582 | return this._populate();
|
583 | }
|
584 | })
|
585 | .catch(async (err) => {
|
586 | const localPath = this._manager.contents.localPath(this._path);
|
587 | const name = PathExt.basename(localPath);
|
588 | void this._handleError(err, this._trans.__('File Load Error for %1', name));
|
589 | throw err;
|
590 | });
|
591 | }
|
592 | |
593 |
|
594 |
|
595 |
|
596 |
|
597 |
|
598 | _revert(initializeModel = false) {
|
599 | const opts = Object.assign({ type: this._factory.contentType, content: this._factory.fileFormat !== null }, (this._factory.fileFormat !== null
|
600 | ? { format: this._factory.fileFormat }
|
601 | : {}));
|
602 | const path = this._path;
|
603 | const model = this._model;
|
604 | return this._manager.ready
|
605 | .then(() => {
|
606 | return this._manager.contents.get(path, opts);
|
607 | })
|
608 | .then(contents => {
|
609 | if (this.isDisposed) {
|
610 | return;
|
611 | }
|
612 | if (contents.format === 'json') {
|
613 | model.fromJSON(contents.content);
|
614 | }
|
615 | else {
|
616 | let content = contents.content;
|
617 |
|
618 |
|
619 | if (content.indexOf('\r\n') !== -1) {
|
620 | this._lineEnding = '\r\n';
|
621 | content = content.replace(/\r\n/g, '\n');
|
622 | }
|
623 | else if (content.indexOf('\r') !== -1) {
|
624 | this._lineEnding = '\r';
|
625 | content = content.replace(/\r/g, '\n');
|
626 | }
|
627 | else {
|
628 | this._lineEnding = null;
|
629 | }
|
630 | model.fromString(content);
|
631 | }
|
632 | this._updateContentsModel(contents);
|
633 | model.dirty = false;
|
634 | if (!this._isPopulated) {
|
635 | return this._populate();
|
636 | }
|
637 | })
|
638 | .catch(async (err) => {
|
639 | const localPath = this._manager.contents.localPath(this._path);
|
640 | const name = PathExt.basename(localPath);
|
641 | void this._handleError(err, this._trans.__('File Load Error for %1', name));
|
642 | throw err;
|
643 | });
|
644 | }
|
645 | |
646 |
|
647 |
|
648 | _maybeSave(options) {
|
649 | const path = this._path;
|
650 |
|
651 | const promise = this._manager.contents.get(path, { content: false });
|
652 | return promise.then(model => {
|
653 | var _a;
|
654 | if (this.isDisposed) {
|
655 | return Promise.reject(new Error('Disposed'));
|
656 | }
|
657 |
|
658 |
|
659 |
|
660 | const lastModifiedCheckMargin = this._lastModifiedCheckMargin;
|
661 | const ycontextModified = this._model.sharedModel.getState('last_modified');
|
662 |
|
663 | const modified = ycontextModified || ((_a = this.contentsModel) === null || _a === void 0 ? void 0 : _a.last_modified);
|
664 | const tClient = modified ? new Date(modified) : new Date();
|
665 | const tDisk = new Date(model.last_modified);
|
666 | if (modified &&
|
667 | tDisk.getTime() - tClient.getTime() > lastModifiedCheckMargin) {
|
668 | return this._timeConflict(tClient, model, options);
|
669 | }
|
670 | return this._manager.contents.save(path, options);
|
671 | }, err => {
|
672 | if (err.response && err.response.status === 404) {
|
673 | return this._manager.contents.save(path, options);
|
674 | }
|
675 | throw err;
|
676 | });
|
677 | }
|
678 | |
679 |
|
680 |
|
681 | async _handleError(err, title) {
|
682 | await showErrorMessage(title, err);
|
683 | return;
|
684 | }
|
685 | |
686 |
|
687 |
|
688 | _maybeCheckpoint(force) {
|
689 | let promise = Promise.resolve(void 0);
|
690 | if (!this.canSave) {
|
691 | return promise;
|
692 | }
|
693 | if (force) {
|
694 | promise = this.createCheckpoint().then( );
|
695 | }
|
696 | else {
|
697 | promise = this.listCheckpoints().then(checkpoints => {
|
698 | if (!this.isDisposed && !checkpoints.length && this.canSave) {
|
699 | return this.createCheckpoint().then( );
|
700 | }
|
701 | });
|
702 | }
|
703 | return promise.catch(err => {
|
704 |
|
705 | if (!err.response || err.response.status !== 403) {
|
706 | throw err;
|
707 | }
|
708 | });
|
709 | }
|
710 | |
711 |
|
712 |
|
713 | _timeConflict(tClient, model, options) {
|
714 | const tDisk = new Date(model.last_modified);
|
715 | console.warn(`Last saving performed ${tClient} ` +
|
716 | `while the current file seems to have been saved ` +
|
717 | `${tDisk}`);
|
718 | if (this._timeConflictModalIsOpen) {
|
719 | return Promise.reject(new Error('Modal is already displayed'));
|
720 | }
|
721 | const body = this._trans.__(`"%1" has changed on disk since the last time it was opened or saved.
|
722 | Do you want to overwrite the file on disk with the version open here,
|
723 | or load the version on disk (revert)?`, this.path);
|
724 | const revertBtn = Dialog.okButton({ label: this._trans.__('Revert') });
|
725 | const overwriteBtn = Dialog.warnButton({
|
726 | label: this._trans.__('Overwrite')
|
727 | });
|
728 | this._timeConflictModalIsOpen = true;
|
729 | return showDialog({
|
730 | title: this._trans.__('File Changed'),
|
731 | body,
|
732 | buttons: [Dialog.cancelButton(), revertBtn, overwriteBtn]
|
733 | }).then(result => {
|
734 | this._timeConflictModalIsOpen = false;
|
735 | if (this.isDisposed) {
|
736 | return Promise.reject(new Error('Disposed'));
|
737 | }
|
738 | if (result.button.label === this._trans.__('Overwrite')) {
|
739 | return this._manager.contents.save(this._path, options);
|
740 | }
|
741 |
|
742 | if (result.button.label === this._trans.__('Revert')) {
|
743 | return this.revert().then(() => {
|
744 | return model;
|
745 | });
|
746 | }
|
747 | return Promise.reject(new Error('Cancel'));
|
748 | });
|
749 | }
|
750 | |
751 |
|
752 |
|
753 | _maybeOverWrite(path) {
|
754 | const body = this._trans.__('"%1" already exists. Do you want to replace it?', path);
|
755 | const overwriteBtn = Dialog.warnButton({
|
756 | label: this._trans.__('Overwrite')
|
757 | });
|
758 | return showDialog({
|
759 | title: this._trans.__('File Overwrite?'),
|
760 | body,
|
761 | buttons: [Dialog.cancelButton(), overwriteBtn]
|
762 | }).then(result => {
|
763 | if (this.isDisposed) {
|
764 | return Promise.reject(new Error('Disposed'));
|
765 | }
|
766 |
|
767 | if (result.button.label === this._trans.__('Overwrite')) {
|
768 | return this._manager.contents.delete(path).then(() => {
|
769 | return this._finishSaveAs(path);
|
770 | });
|
771 | }
|
772 | });
|
773 | }
|
774 | |
775 |
|
776 |
|
777 | async _finishSaveAs(newPath) {
|
778 | this._saveState.emit('started');
|
779 | try {
|
780 | await this._manager.ready;
|
781 | const options = this._createSaveOptions();
|
782 | await this._manager.contents.save(newPath, options);
|
783 | await this._maybeCheckpoint(true);
|
784 |
|
785 | this._saveState.emit('completed');
|
786 | }
|
787 | catch (err) {
|
788 |
|
789 |
|
790 |
|
791 | if (err.message === 'Cancel' ||
|
792 | err.message === 'Modal is already displayed') {
|
793 | throw err;
|
794 | }
|
795 |
|
796 | const localPath = this._manager.contents.localPath(this._path);
|
797 | const name = PathExt.basename(localPath);
|
798 | void this._handleError(err, this._trans.__('File Save Error for %1', name));
|
799 |
|
800 | this._saveState.emit('failed');
|
801 | return;
|
802 | }
|
803 | }
|
804 | _createSaveOptions() {
|
805 | let content = null;
|
806 | if (this._factory.fileFormat === 'json') {
|
807 | content = this._model.toJSON();
|
808 | }
|
809 | else {
|
810 | content = this._model.toString();
|
811 | if (this._lineEnding) {
|
812 | content = content.replace(/\n/g, this._lineEnding);
|
813 | }
|
814 | }
|
815 | return {
|
816 | type: this._factory.contentType,
|
817 | format: this._factory.fileFormat,
|
818 | content
|
819 | };
|
820 | }
|
821 | }
|
822 |
|
823 |
|
824 |
|
825 | var Private;
|
826 | (function (Private) {
|
827 | |
828 |
|
829 |
|
830 | function getSavePath(path, translator) {
|
831 | translator = translator || nullTranslator;
|
832 | const trans = translator.load('jupyterlab');
|
833 | const saveBtn = Dialog.okButton({ label: trans.__('Save') });
|
834 | return showDialog({
|
835 | title: trans.__('Save File As..'),
|
836 | body: new SaveWidget(path),
|
837 | buttons: [Dialog.cancelButton(), saveBtn]
|
838 | }).then(result => {
|
839 | var _a;
|
840 |
|
841 | if (result.button.label === trans.__('Save')) {
|
842 | return (_a = result.value) !== null && _a !== void 0 ? _a : undefined;
|
843 | }
|
844 | return;
|
845 | });
|
846 | }
|
847 | Private.getSavePath = getSavePath;
|
848 | |
849 |
|
850 |
|
851 | function noOp() {
|
852 |
|
853 | }
|
854 | Private.noOp = noOp;
|
855 | |
856 |
|
857 |
|
858 | class SaveWidget extends Widget {
|
859 | |
860 |
|
861 |
|
862 | constructor(path) {
|
863 | super({ node: createSaveNode(path) });
|
864 | }
|
865 | |
866 |
|
867 |
|
868 | getValue() {
|
869 | return this.node.value;
|
870 | }
|
871 | }
|
872 | |
873 |
|
874 |
|
875 | function createSaveNode(path) {
|
876 | const input = document.createElement('input');
|
877 | input.value = path;
|
878 | return input;
|
879 | }
|
880 | })(Private || (Private = {}));
|
881 |
|
\ | No newline at end of file |