@jupyter/docprovider 5.0.0-alpha.0 → 5.0.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/yprovider.d.ts +23 -0
- package/lib/yprovider.js +89 -4
- package/package.json +12 -12
package/lib/yprovider.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { WebsocketProvider as YWebsocketProvider } from 'y-websocket';
|
|
2
|
+
import { JSONValue } from '@lumino/coreutils';
|
|
2
3
|
import { DocumentChange, YDocument } from '@jupyter/ydoc';
|
|
3
4
|
import { IDocumentProvider } from '@jupyter/collaborative-drive';
|
|
4
5
|
import { ServerConnection, User } from '@jupyterlab/services';
|
|
@@ -35,6 +36,20 @@ export declare class WebSocketProvider implements IDocumentProvider, IForkProvid
|
|
|
35
36
|
save(): Promise<void>;
|
|
36
37
|
private get _serverUrl();
|
|
37
38
|
private _connect;
|
|
39
|
+
/**
|
|
40
|
+
* Start the load timeout that shows a retry dialog if the document
|
|
41
|
+
* hasn't synced within LOAD_TIMEOUT milliseconds.
|
|
42
|
+
*/
|
|
43
|
+
private _startLoadTimeout;
|
|
44
|
+
/**
|
|
45
|
+
* Clear the load timeout.
|
|
46
|
+
*/
|
|
47
|
+
private _clearLoadTimeout;
|
|
48
|
+
/**
|
|
49
|
+
* Callback when the load timeout fires.
|
|
50
|
+
* Shows a dialog asking the user if they want to retry loading.
|
|
51
|
+
*/
|
|
52
|
+
private _onLoadTimeout;
|
|
38
53
|
connectToForkDoc(forkRoomId: string, sessionId: string): Promise<void>;
|
|
39
54
|
get wsProvider(): YWebsocketProvider | null;
|
|
40
55
|
private _disconnect;
|
|
@@ -61,6 +76,9 @@ export declare class WebSocketProvider implements IDocumentProvider, IForkProvid
|
|
|
61
76
|
private _conflictWs;
|
|
62
77
|
private _onConflictSaveAs?;
|
|
63
78
|
private _onConflictRevert?;
|
|
79
|
+
private _loadTimeoutId;
|
|
80
|
+
private _isShowingDialog;
|
|
81
|
+
private _onConflictShowDiff?;
|
|
64
82
|
}
|
|
65
83
|
/**
|
|
66
84
|
* A namespace for WebSocketProvider statics.
|
|
@@ -110,5 +128,10 @@ export declare namespace WebSocketProvider {
|
|
|
110
128
|
* Called when the user chooses "Revert" from the conflict dialog.
|
|
111
129
|
*/
|
|
112
130
|
onConflictRevert?: () => Promise<void>;
|
|
131
|
+
/**
|
|
132
|
+
* Called when the user chooses "Show Diff" from the conflict dialog.
|
|
133
|
+
* Receives the current local document content as JSON.
|
|
134
|
+
*/
|
|
135
|
+
onConflictShowDiff?: (localContent: JSONValue) => Promise<void>;
|
|
113
136
|
}
|
|
114
137
|
}
|
package/lib/yprovider.js
CHANGED
|
@@ -15,6 +15,11 @@ import { requestDocSession } from './requests';
|
|
|
15
15
|
* The url for the default drive service.
|
|
16
16
|
*/
|
|
17
17
|
const DOCUMENT_PROVIDER_URL = 'api/collaboration/room';
|
|
18
|
+
/**
|
|
19
|
+
* Timeout in milliseconds after which a loading dialog is shown
|
|
20
|
+
* if the shared document has not yet been synchronized.
|
|
21
|
+
*/
|
|
22
|
+
const LOAD_TIMEOUT = 5000;
|
|
18
23
|
/**
|
|
19
24
|
* The raw message type.
|
|
20
25
|
*/
|
|
@@ -33,6 +38,48 @@ export class WebSocketProvider {
|
|
|
33
38
|
*/
|
|
34
39
|
constructor(options) {
|
|
35
40
|
var _a;
|
|
41
|
+
/**
|
|
42
|
+
* Callback when the load timeout fires.
|
|
43
|
+
* Shows a dialog asking the user if they want to retry loading.
|
|
44
|
+
*/
|
|
45
|
+
this._onLoadTimeout = async () => {
|
|
46
|
+
if (this._isDisposed || this._hasSynced || this._isShowingDialog) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
this._isShowingDialog = true;
|
|
50
|
+
const result = await showDialog({
|
|
51
|
+
title: this._trans.__('Loading'),
|
|
52
|
+
body: this._trans.__('The document is taking some time to load. What would you like to do?'),
|
|
53
|
+
buttons: [
|
|
54
|
+
Dialog.cancelButton({ label: this._trans.__('Cancel') }),
|
|
55
|
+
Dialog.okButton({
|
|
56
|
+
label: this._trans.__('Continue waiting'),
|
|
57
|
+
actions: ['continue']
|
|
58
|
+
}),
|
|
59
|
+
Dialog.warnButton({
|
|
60
|
+
label: this._trans.__('Retry'),
|
|
61
|
+
actions: ['retry']
|
|
62
|
+
})
|
|
63
|
+
]
|
|
64
|
+
});
|
|
65
|
+
this._isShowingDialog = false;
|
|
66
|
+
if (this._isDisposed || this._hasSynced) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (result.button.actions.includes('continue')) {
|
|
70
|
+
this._startLoadTimeout();
|
|
71
|
+
}
|
|
72
|
+
else if (result.button.actions.includes('retry')) {
|
|
73
|
+
await this.reconnect();
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
this._disconnect();
|
|
77
|
+
const msg = this._trans.__('The document failed to load. Please try opening it again.');
|
|
78
|
+
this._ready.reject(msg);
|
|
79
|
+
this._ready.promise.catch(() => undefined);
|
|
80
|
+
this._sharedModel.dispose();
|
|
81
|
+
}
|
|
82
|
+
};
|
|
36
83
|
this._onConnectionClosed = async (event) => {
|
|
37
84
|
if ([4400, 4404, 4500].includes(event.code)) {
|
|
38
85
|
if (!this._hasSynced) {
|
|
@@ -79,7 +126,7 @@ export class WebSocketProvider {
|
|
|
79
126
|
}
|
|
80
127
|
};
|
|
81
128
|
this._handleConflictMessage = async (event) => {
|
|
82
|
-
var _a, _b;
|
|
129
|
+
var _a, _b, _c;
|
|
83
130
|
if (!(event.data instanceof ArrayBuffer)) {
|
|
84
131
|
return;
|
|
85
132
|
}
|
|
@@ -97,7 +144,7 @@ export class WebSocketProvider {
|
|
|
97
144
|
return;
|
|
98
145
|
}
|
|
99
146
|
}
|
|
100
|
-
catch (
|
|
147
|
+
catch (_d) {
|
|
101
148
|
return;
|
|
102
149
|
}
|
|
103
150
|
const buttons = [
|
|
@@ -109,6 +156,12 @@ export class WebSocketProvider {
|
|
|
109
156
|
actions: ['revert']
|
|
110
157
|
}));
|
|
111
158
|
}
|
|
159
|
+
if (this._onConflictShowDiff) {
|
|
160
|
+
buttons.push(Dialog.okButton({
|
|
161
|
+
label: this._trans.__('Show Diff'),
|
|
162
|
+
actions: ['show-diff']
|
|
163
|
+
}));
|
|
164
|
+
}
|
|
112
165
|
if (this._onConflictSaveAs) {
|
|
113
166
|
buttons.push(Dialog.okButton({
|
|
114
167
|
label: this._trans.__('Save As'),
|
|
@@ -126,13 +179,17 @@ export class WebSocketProvider {
|
|
|
126
179
|
if (result.button.actions.includes('revert')) {
|
|
127
180
|
await ((_a = this._onConflictRevert) === null || _a === void 0 ? void 0 : _a.call(this));
|
|
128
181
|
}
|
|
182
|
+
else if (result.button.actions.includes('show-diff')) {
|
|
183
|
+
await ((_b = this._onConflictShowDiff) === null || _b === void 0 ? void 0 : _b.call(this, this._sharedModel.getSource()));
|
|
184
|
+
}
|
|
129
185
|
else if (result.button.actions.includes('save-as')) {
|
|
130
|
-
await ((
|
|
186
|
+
await ((_c = this._onConflictSaveAs) === null || _c === void 0 ? void 0 : _c.call(this));
|
|
131
187
|
}
|
|
132
188
|
};
|
|
133
189
|
this._onSync = (isSynced) => {
|
|
134
190
|
if (isSynced) {
|
|
135
191
|
this._hasSynced = true;
|
|
192
|
+
this._clearLoadTimeout();
|
|
136
193
|
if (this._yWebsocketProvider) {
|
|
137
194
|
this._yWebsocketProvider.off('sync', this._onSync);
|
|
138
195
|
const state = this._sharedModel.ydoc.getMap('state');
|
|
@@ -145,6 +202,8 @@ export class WebSocketProvider {
|
|
|
145
202
|
this._hasSynced = false;
|
|
146
203
|
this._saveCounter = 0;
|
|
147
204
|
this._conflictWs = null;
|
|
205
|
+
this._loadTimeoutId = null;
|
|
206
|
+
this._isShowingDialog = false;
|
|
148
207
|
this._isDisposed = false;
|
|
149
208
|
this._path = options.path;
|
|
150
209
|
this._contentType = options.contentType;
|
|
@@ -158,6 +217,7 @@ export class WebSocketProvider {
|
|
|
158
217
|
this._trans = options.translator;
|
|
159
218
|
this._onConflictSaveAs = options.onConflictSaveAs;
|
|
160
219
|
this._onConflictRevert = options.onConflictRevert;
|
|
220
|
+
this._onConflictShowDiff = options.onConflictShowDiff;
|
|
161
221
|
const user = options.user;
|
|
162
222
|
user.ready
|
|
163
223
|
.then(() => {
|
|
@@ -166,6 +226,7 @@ export class WebSocketProvider {
|
|
|
166
226
|
.catch(e => console.error(e));
|
|
167
227
|
user.userChanged.connect(this._onUserChanged, this);
|
|
168
228
|
this._connect().catch(e => console.warn(e));
|
|
229
|
+
this._startLoadTimeout();
|
|
169
230
|
}
|
|
170
231
|
/**
|
|
171
232
|
* Test whether the object has been disposed.
|
|
@@ -194,6 +255,7 @@ export class WebSocketProvider {
|
|
|
194
255
|
return;
|
|
195
256
|
}
|
|
196
257
|
this._isDisposed = true;
|
|
258
|
+
this._clearLoadTimeout();
|
|
197
259
|
if (this._conflictWs) {
|
|
198
260
|
this._conflictWs.removeEventListener('message', this._handleConflictMessage);
|
|
199
261
|
this._conflictWs = null;
|
|
@@ -205,8 +267,9 @@ export class WebSocketProvider {
|
|
|
205
267
|
Signal.clearData(this);
|
|
206
268
|
}
|
|
207
269
|
async reconnect() {
|
|
270
|
+
this._clearLoadTimeout();
|
|
208
271
|
this._disconnect();
|
|
209
|
-
this._connect();
|
|
272
|
+
this._connect().catch(e => console.warn(e));
|
|
210
273
|
}
|
|
211
274
|
async save() {
|
|
212
275
|
var _a;
|
|
@@ -290,6 +353,28 @@ export class WebSocketProvider {
|
|
|
290
353
|
this._attachConflictListener();
|
|
291
354
|
}
|
|
292
355
|
});
|
|
356
|
+
this._startLoadTimeout();
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Start the load timeout that shows a retry dialog if the document
|
|
360
|
+
* hasn't synced within LOAD_TIMEOUT milliseconds.
|
|
361
|
+
*/
|
|
362
|
+
_startLoadTimeout() {
|
|
363
|
+
this._clearLoadTimeout();
|
|
364
|
+
if (!this._isDisposed) {
|
|
365
|
+
this._loadTimeoutId = window.setTimeout(() => {
|
|
366
|
+
this._onLoadTimeout();
|
|
367
|
+
}, LOAD_TIMEOUT);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Clear the load timeout.
|
|
372
|
+
*/
|
|
373
|
+
_clearLoadTimeout() {
|
|
374
|
+
if (this._loadTimeoutId !== null) {
|
|
375
|
+
clearTimeout(this._loadTimeoutId);
|
|
376
|
+
this._loadTimeoutId = null;
|
|
377
|
+
}
|
|
293
378
|
}
|
|
294
379
|
async connectToForkDoc(forkRoomId, sessionId) {
|
|
295
380
|
const token = this._serverSettings.token;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jupyter/docprovider",
|
|
3
|
-
"version": "5.0.0-
|
|
3
|
+
"version": "5.0.0-beta.0",
|
|
4
4
|
"description": "JupyterLab - Document Provider",
|
|
5
5
|
"homepage": "https://github.com/jupyterlab/jupyter-collaboration",
|
|
6
6
|
"bugs": {
|
|
@@ -41,15 +41,15 @@
|
|
|
41
41
|
"watch": "tsc -b --watch"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@jupyter/collaborative-drive": "^5.0.0-
|
|
45
|
-
"@jupyter/ydoc": "^4.0.0
|
|
46
|
-
"@jupyterlab/apputils": "^4.7.0
|
|
47
|
-
"@jupyterlab/cells": "^4.6.0
|
|
48
|
-
"@jupyterlab/coreutils": "^6.6.0
|
|
49
|
-
"@jupyterlab/docmanager": "^4.6.0
|
|
50
|
-
"@jupyterlab/notebook": "^4.6.0
|
|
51
|
-
"@jupyterlab/services": "^7.6.0
|
|
52
|
-
"@jupyterlab/translation": "^4.6.0
|
|
44
|
+
"@jupyter/collaborative-drive": "^5.0.0-beta.0",
|
|
45
|
+
"@jupyter/ydoc": "^4.0.0",
|
|
46
|
+
"@jupyterlab/apputils": "^4.7.0",
|
|
47
|
+
"@jupyterlab/cells": "^4.6.0",
|
|
48
|
+
"@jupyterlab/coreutils": "^6.6.0",
|
|
49
|
+
"@jupyterlab/docmanager": "^4.6.0",
|
|
50
|
+
"@jupyterlab/notebook": "^4.6.0",
|
|
51
|
+
"@jupyterlab/services": "^7.6.0",
|
|
52
|
+
"@jupyterlab/translation": "^4.6.0",
|
|
53
53
|
"@lumino/coreutils": "^2.2.1",
|
|
54
54
|
"@lumino/disposable": "^2.1.4",
|
|
55
55
|
"@lumino/signaling": "^2.1.4",
|
|
@@ -59,8 +59,8 @@
|
|
|
59
59
|
"yjs": "^13.5.40"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
|
-
"@jupyterlab/testing": "^4.6.0
|
|
63
|
-
"@jupyterlab/testutils": "^4.6.0
|
|
62
|
+
"@jupyterlab/testing": "^4.6.0",
|
|
63
|
+
"@jupyterlab/testutils": "^4.6.0",
|
|
64
64
|
"@types/jest": "^29.2.0",
|
|
65
65
|
"jest": "^29.5.0",
|
|
66
66
|
"rimraf": "^4.1.2",
|