|
4 | 4 | import { PageConfig, URLExt } from '@jupyterlab/coreutils';
|
5 | 5 | import { TranslationBundle } from '@jupyterlab/translation';
|
6 | 6 | import { Contents, Drive, User } from '@jupyterlab/services';
|
| 7 | +import { ISignal, Signal } from '@lumino/signaling'; |
7 | 8 |
|
8 | 9 | import { DocumentChange, ISharedDocument, YDocument } from '@jupyter/ydoc';
|
9 | 10 |
|
@@ -45,6 +46,10 @@ export class YDrive extends Drive implements ICollaborativeDrive {
|
45 | 46 | this._providers = new Map<string, WebSocketProvider>();
|
46 | 47 |
|
47 | 48 | this.sharedModelFactory = new SharedModelFactory(this._onCreate);
|
| 49 | + super.fileChanged.connect((_, change) => { |
| 50 | + // pass through any events from the Drive superclass |
| 51 | + this._ydriveFileChanged.emit(change); |
| 52 | + }); |
48 | 53 | }
|
49 | 54 |
|
50 | 55 | /**
|
@@ -84,7 +89,7 @@ export class YDrive extends Drive implements ICollaborativeDrive {
|
84 | 89 | const provider = this._providers.get(key);
|
85 | 90 |
|
86 | 91 | if (provider) {
|
87 |
| - // If the document does't exist, `super.get` will reject with an |
| 92 | + // If the document doesn't exist, `super.get` will reject with an |
88 | 93 | // error and the provider will never be resolved.
|
89 | 94 | // Use `Promise.all` to reject as soon as possible. The Context will
|
90 | 95 | // show a dialog to the user.
|
@@ -132,6 +137,13 @@ export class YDrive extends Drive implements ICollaborativeDrive {
|
132 | 137 | return super.save(localPath, options);
|
133 | 138 | }
|
134 | 139 |
|
| 140 | + /** |
| 141 | + * A signal emitted when a file operation takes place. |
| 142 | + */ |
| 143 | + get fileChanged(): ISignal<this, Contents.IChangedArgs> { |
| 144 | + return this._ydriveFileChanged; |
| 145 | + } |
| 146 | + |
135 | 147 | private _onCreate = (
|
136 | 148 | options: Contents.ISharedFactoryOptions,
|
137 | 149 | sharedModel: YDocument<DocumentChange>
|
@@ -161,6 +173,51 @@ export class YDrive extends Drive implements ICollaborativeDrive {
|
161 | 173 | const key = `${options.format}:${options.contentType}:${options.path}`;
|
162 | 174 | this._providers.set(key, provider);
|
163 | 175 |
|
| 176 | + sharedModel.changed.connect(async (_, change) => { |
| 177 | + // TODO: make use of the hash |
| 178 | + if (!change.stateChange) { |
| 179 | + return; |
| 180 | + } |
| 181 | + const hashChanges = change.stateChange.filter( |
| 182 | + change => change.name === 'hash' |
| 183 | + ); |
| 184 | + if (hashChanges.length === 0) { |
| 185 | + return; |
| 186 | + } |
| 187 | + if (hashChanges.length > 1) { |
| 188 | + console.error( |
| 189 | + 'Unexpected multiple changes to hash value in a single transaction' |
| 190 | + ); |
| 191 | + } |
| 192 | + const hashChange = hashChanges[0]; |
| 193 | + |
| 194 | + // A change in hash signifies that a save occurred on the server-side |
| 195 | + // (e.g. a collaborator performed the save) - we want notify the observers |
| 196 | + // about this change so that they can store the new hash value. |
| 197 | + |
| 198 | + const model = await this.get(options.path, { content: false }); |
| 199 | + /* |
| 200 | + this._ydriveFileChanged.emit({ |
| 201 | + type: 'server-side-save', |
| 202 | + newValue: {...model, hash: hashChange.newValue}, |
| 203 | + // we do not have the old model because it was discarded when server made the change, |
| 204 | + // we only have the old hash here (which may be empty if the file was newly created!) |
| 205 | + oldValue: {hash: hashChange.oldValue} |
| 206 | + }); |
| 207 | + */ |
| 208 | + // TODO: add handler for `server-side-save` in |
| 209 | + // https://github.com/jupyterlab/jupyterlab/blob/dca1ec376c66038b8df7001d32cf058c70fcd717/packages/docregistry/src/context.ts#L410-L444 |
| 210 | + // For now, fake it: |
| 211 | + // it happens that "rename" will perform the update of context's internal |
| 212 | + // contentsModel (which we desire to solve the spurious "File Changed" dialog) |
| 213 | + // even if file path has not changed. |
| 214 | + this._ydriveFileChanged.emit({ |
| 215 | + type: 'rename', |
| 216 | + newValue: { ...model, hash: hashChange.newValue }, |
| 217 | + oldValue: { ...model, hash: hashChange.oldValue } |
| 218 | + }); |
| 219 | + }); |
| 220 | + |
164 | 221 | sharedModel.disposed.connect(() => {
|
165 | 222 | const provider = this._providers.get(key);
|
166 | 223 | if (provider) {
|
@@ -190,6 +247,7 @@ export class YDrive extends Drive implements ICollaborativeDrive {
|
190 | 247 | private _trans: TranslationBundle;
|
191 | 248 | private _providers: Map<string, WebSocketProvider>;
|
192 | 249 | private _globalAwareness: Awareness | null;
|
| 250 | + private _ydriveFileChanged = new Signal<this, Contents.IChangedArgs>(this); |
193 | 251 | }
|
194 | 252 |
|
195 | 253 | /**
|
|
0 commit comments