@jupyter/docprovider 5.0.0-alpha.0 → 5.0.0-rc.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.
@@ -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 (_c) {
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 ((_b = this._onConflictSaveAs) === null || _b === void 0 ? void 0 : _b.call(this));
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-alpha.0",
3
+ "version": "5.0.0-rc.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-alpha.0",
45
- "@jupyter/ydoc": "^4.0.0-a3",
46
- "@jupyterlab/apputils": "^4.7.0-beta.1",
47
- "@jupyterlab/cells": "^4.6.0-beta.1",
48
- "@jupyterlab/coreutils": "^6.6.0-beta.1",
49
- "@jupyterlab/docmanager": "^4.6.0-beta.1",
50
- "@jupyterlab/notebook": "^4.6.0-beta.1",
51
- "@jupyterlab/services": "^7.6.0-beta.1",
52
- "@jupyterlab/translation": "^4.6.0-beta.1",
44
+ "@jupyter/collaborative-drive": "^5.0.0-rc.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-beta.1",
63
- "@jupyterlab/testutils": "^4.6.0-beta.1",
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",