@@ -23,12 +23,12 @@ import {
23
23
JupyterFrontEnd ,
24
24
JupyterFrontEndPlugin
25
25
} from '@jupyterlab/application' ;
26
- import { IToolbarWidgetRegistry } from '@jupyterlab/apputils' ;
26
+ import { Dialog , IToolbarWidgetRegistry } from '@jupyterlab/apputils' ;
27
27
import {
28
28
EditorExtensionRegistry ,
29
29
IEditorExtensionRegistry
30
30
} from '@jupyterlab/codemirror' ;
31
- import { WebSocketAwarenessProvider } from '@jupyter/docprovider' ;
31
+ import { requestDocMerge , WebSocketAwarenessProvider } from '@jupyter/docprovider' ;
32
32
import {
33
33
SidePanel ,
34
34
usersIcon ,
@@ -37,20 +37,21 @@ import {
37
37
import { URLExt } from '@jupyterlab/coreutils' ;
38
38
import { ServerConnection } from '@jupyterlab/services' ;
39
39
import { IStateDB , StateDB } from '@jupyterlab/statedb' ;
40
- import { ITranslator , nullTranslator } from '@jupyterlab/translation' ;
40
+ import { ITranslator , nullTranslator , TranslationBundle } from '@jupyterlab/translation' ;
41
41
42
42
import { Menu , MenuBar } from '@lumino/widgets' ;
43
43
44
- import { IAwareness } from '@jupyter/ydoc' ;
44
+ import { IAwareness , ISharedNotebook , ISuggestions , NotebookChange } from '@jupyter/ydoc' ;
45
45
46
46
import {
47
47
CollaboratorsPanel ,
48
+ SuggestionsPanel ,
48
49
IGlobalAwareness ,
49
50
IUserMenu ,
50
51
remoteUserCursors ,
51
52
RendererUserMenu ,
52
53
UserInfoPanel ,
53
- UserMenu
54
+ UserMenu ,
54
55
} from '@jupyter/collaboration' ;
55
56
56
57
import * as Y from 'yjs' ;
@@ -147,11 +148,12 @@ export const rtcPanelPlugin: JupyterFrontEndPlugin<void> = {
147
148
id : '@jupyter/collaboration-extension:rtcPanel' ,
148
149
description : 'Add side panel to display all currently connected users.' ,
149
150
autoStart : true ,
150
- requires : [ IGlobalAwareness ] ,
151
+ requires : [ IGlobalAwareness , ISuggestions ] ,
151
152
optional : [ ITranslator ] ,
152
153
activate : (
153
154
app : JupyterFrontEnd ,
154
155
awareness : Awareness ,
156
+ suggestions : ISuggestions ,
155
157
translator : ITranslator | null
156
158
) : void => {
157
159
const { user } = app . serviceManager ;
@@ -183,6 +185,10 @@ export const rtcPanelPlugin: JupyterFrontEndPlugin<void> = {
183
185
) ;
184
186
collaboratorsPanel . title . label = trans . __ ( 'Online Collaborators' ) ;
185
187
userPanel . addWidget ( collaboratorsPanel ) ;
188
+
189
+ const suggestionsPanel = new SuggestionsPanel ( fileopener , suggestions ) ;
190
+ suggestionsPanel . title . label = trans . __ ( 'Suggestions' ) ;
191
+ userPanel . addWidget ( suggestionsPanel ) ;
186
192
}
187
193
} ;
188
194
@@ -215,54 +221,217 @@ export const editingMode: JupyterFrontEndPlugin<void> = {
215
221
id : '@jupyter/collaboration-extension:editingMode' ,
216
222
description : 'A plugin to add editing mode to the notebook page.' ,
217
223
autoStart : true ,
218
- requires : [ ITranslator ] ,
224
+ optional : [ ITranslator ] ,
219
225
activate : (
220
226
app : JupyterFrontEnd ,
227
+ translator : ITranslator | null
221
228
) => {
222
- app . docRegistry . addWidgetExtension ( 'Notebook' , new EditingModeExtension ( ) ) ;
229
+ app . docRegistry . addWidgetExtension ( 'Notebook' , new EditingModeExtension ( translator ) ) ;
223
230
} ,
224
231
} ;
225
232
226
233
export class EditingModeExtension implements DocumentRegistry . IWidgetExtension < NotebookPanel , INotebookModel > {
227
- createNew ( panel : NotebookPanel , context : DocumentRegistry . IContext < INotebookModel > ) : IDisposable {
228
- const menubar = new MenuBar ( ) ;
229
- const commands = new CommandRegistry ( ) ;
230
- const menu = new Menu ( { commands } ) ;
231
- menu . title . label = 'Editing' ;
232
- menu . title . icon = caretDownIcon ;
233
- addMenuItem ( commands , menu , 'editing' , 'Editing' , context ) ;
234
- addMenuItem ( commands , menu , 'suggesting' , 'Suggesting' , context ) ;
235
- menubar . addMenu ( menu ) ;
236
-
237
- panel . toolbar . insertItem ( 990 , 'editingMode' , menubar ) ;
234
+ private _trans : TranslationBundle ;
235
+
236
+ constructor ( translator : ITranslator | null ) {
237
+ this . _trans = ( translator ?? nullTranslator ) . load ( 'jupyter_collaboration' ) ;
238
+ }
239
+
240
+ createNew (
241
+ panel : NotebookPanel ,
242
+ context : DocumentRegistry . IContext < INotebookModel >
243
+ ) : IDisposable {
244
+ const editingMenubar = new MenuBar ( ) ;
245
+ const suggestionMenubar = new MenuBar ( ) ;
246
+ const reviewMenubar = new MenuBar ( ) ;
247
+
248
+ const editingCommands = new CommandRegistry ( ) ;
249
+ const suggestionCommands = new CommandRegistry ( ) ;
250
+ const reviewCommands = new CommandRegistry ( ) ;
251
+
252
+ const editingMenu = new Menu ( { commands : editingCommands } ) ;
253
+ const suggestionMenu = new Menu ( { commands : suggestionCommands } ) ;
254
+ const reviewMenu = new Menu ( { commands : reviewCommands } ) ;
255
+
256
+ var myForkId = '' ; // curently allows only one suggestion per user
257
+
258
+ editingMenu . title . label = 'Editing' ;
259
+ editingMenu . title . icon = caretDownIcon ;
260
+
261
+ suggestionMenu . title . label = 'Root' ;
262
+ suggestionMenu . title . icon = caretDownIcon ;
263
+
264
+ reviewMenu . title . label = 'Review' ;
265
+ reviewMenu . title . icon = caretDownIcon ;
266
+
267
+ editingCommands . addCommand ( 'editing' , {
268
+ label : 'Editing' ,
269
+ execute : ( ) => {
270
+ editingMenu . title . label = 'Editing' ;
271
+ suggestionMenu . title . label = 'Root' ;
272
+ }
273
+ } ) ;
274
+ editingCommands . addCommand ( 'suggesting' , {
275
+ label : 'Suggesting' ,
276
+ execute : ( ) => {
277
+ editingMenu . title . label = 'Suggesting' ;
278
+ reviewMenu . clearItems ( ) ;
279
+ if ( myForkId === '' ) {
280
+ myForkId = 'pending' ;
281
+ const provider = context . model . sharedModel . provider ;
282
+ provider . fork ( ) . then ( newForkId => {
283
+ myForkId = newForkId ;
284
+ provider . connectFork ( newForkId ) ;
285
+ suggestionMenu . title . label = newForkId ;
286
+ } ) ;
287
+ }
288
+ else {
289
+ suggestionMenu . title . label = myForkId ;
290
+ context . model . sharedModel . provider . connectFork ( myForkId ) ;
291
+ }
292
+ }
293
+ } ) ;
294
+
295
+ suggestionCommands . addCommand ( 'root' , {
296
+ label : 'Root' ,
297
+ execute : ( ) => {
298
+ // we cannot review the root document
299
+ reviewMenu . clearItems ( ) ;
300
+ suggestionMenu . title . label = 'Root' ;
301
+ editingMenu . title . label = 'Editing' ;
302
+ context . model . sharedModel . provider . connectFork ( context . model . sharedModel . rootRoomId ) ;
303
+ }
304
+ } ) ;
305
+
306
+ reviewCommands . addCommand ( 'merge' , {
307
+ label : 'Merge' ,
308
+ execute : ( ) => {
309
+ console . log ( 'currentRoomId' , context . model . sharedModel . currentRoomId ) ;
310
+ console . log ( 'rootRoomId' , context . model . sharedModel . rootRoomId ) ;
311
+ requestDocMerge ( context . model . sharedModel . currentRoomId , context . model . sharedModel . rootRoomId ) ;
312
+ }
313
+ } ) ;
314
+ reviewCommands . addCommand ( 'discard' , {
315
+ label : 'Discard' ,
316
+ execute : ( ) => {
317
+ }
318
+ } ) ;
319
+
320
+ editingMenu . addItem ( { type : 'command' , command : 'editing' } ) ;
321
+ editingMenu . addItem ( { type : 'command' , command : 'suggesting' } ) ;
322
+
323
+ suggestionMenu . addItem ( { type : 'command' , command : 'root' } ) ;
324
+
325
+ const _onStateChanged = ( sender : ISharedNotebook , changes : NotebookChange ) => {
326
+ if ( changes . stateChange ) {
327
+ changes . stateChange . forEach ( value => {
328
+ const forkPrefix = 'fork_' ;
329
+ if ( value . name . startsWith ( forkPrefix ) ) {
330
+ const newForkId = value . name . slice ( forkPrefix . length ) ;
331
+ suggestionCommands . addCommand ( newForkId , {
332
+ label : newForkId ,
333
+ execute : ( ) => {
334
+ if ( myForkId === newForkId ) {
335
+ editingMenu . title . label = 'Suggesting' ;
336
+ // our suggestion, cannot be reviewed
337
+ reviewMenu . clearItems ( ) ;
338
+ }
339
+ else {
340
+ editingMenu . title . label = 'Editing' ;
341
+ // not our suggestion, can be reviewed
342
+ reviewMenu . clearItems ( ) ;
343
+ reviewMenu . addItem ( { type : 'command' , command : 'merge' } ) ;
344
+ reviewMenu . addItem ( { type : 'command' , command : 'discard' } ) ;
345
+ }
346
+ suggestionMenu . title . label = newForkId ;
347
+ context . model . sharedModel . provider . connectFork ( newForkId ) ;
348
+ const dialog = new Dialog ( {
349
+ title : this . _trans . __ ( 'Suggestion' ) ,
350
+ body : this . _trans . __ ( 'Your are now viewing the suggestion.' ) ,
351
+ buttons : [ Dialog . okButton ( { label : 'OK' } ) ] ,
352
+ } ) ;
353
+ dialog . launch ( ) . then ( resp => { dialog . close ( ) ; } ) ;
354
+ }
355
+ } ) ;
356
+ suggestionMenu . addItem ( { type : 'command' , command : newForkId } ) ;
357
+ if ( ( myForkId !== 'pending' ) && ( myForkId !== newForkId ) ) {
358
+ const dialog = new Dialog ( {
359
+ title : this . _trans . __ ( 'New suggestion' ) ,
360
+ body : this . _trans . __ ( 'Open notebook for suggestion?' ) ,
361
+ buttons : [
362
+ Dialog . okButton ( { label : 'Open' } ) ,
363
+ Dialog . cancelButton ( { label : 'Discard' } ) ,
364
+ ] ,
365
+ } ) ;
366
+ dialog . launch ( ) . then ( resp => {
367
+ dialog . close ( ) ;
368
+ if ( resp . button . label === 'Open' ) {
369
+ context . model . sharedModel . provider . connectFork ( newForkId ) ;
370
+ suggestionMenu . title . label = newForkId ;
371
+ editingMenu . title . label = 'Editing' ;
372
+ reviewMenu . clearItems ( ) ;
373
+ reviewMenu . addItem ( { type : 'command' , command : 'merge' } ) ;
374
+ reviewMenu . addItem ( { type : 'command' , command : 'discard' } ) ;
375
+ }
376
+ } ) ;
377
+ }
378
+ }
379
+ } ) ;
380
+ }
381
+ } ;
382
+
383
+ context . model . sharedModel . changed . connect ( _onStateChanged , this ) ;
384
+
385
+ editingMenubar . addMenu ( editingMenu ) ;
386
+ suggestionMenubar . addMenu ( suggestionMenu ) ;
387
+ reviewMenubar . addMenu ( reviewMenu ) ;
388
+
389
+ panel . toolbar . insertItem ( 997 , 'editingMode' , editingMenubar ) ;
390
+ panel . toolbar . insertItem ( 998 , 'suggestions' , suggestionMenubar ) ;
391
+ panel . toolbar . insertItem ( 999 , 'review' , reviewMenubar ) ;
238
392
return new DisposableDelegate ( ( ) => {
239
- menubar . dispose ( ) ;
393
+ editingMenubar . dispose ( ) ;
394
+ suggestionMenubar . dispose ( ) ;
395
+ reviewMenubar . dispose ( ) ;
240
396
} ) ;
241
397
}
242
398
}
243
399
244
400
/**
245
- * Helper Function to add menu items.
246
- */
247
- function addMenuItem (
248
- commands : CommandRegistry ,
249
- menu : Menu ,
250
- command : string ,
251
- label : string ,
252
- context : DocumentRegistry . IContext < INotebookModel > ,
253
- ) : void {
254
- commands . addCommand ( command , {
255
- label : label ,
256
- execute : ( ) => {
257
- menu . title . label = label ;
258
- if ( command == 'suggesting' ) {
259
- const provider = context . model . sharedModel . provider ;
260
- provider . fork ( ) . then ( forkId => { provider . connectFork ( forkId ) ; } ) ;
261
- }
401
+ * A plugin to provide shared document suggestions.
402
+ */
403
+ export const suggestions : JupyterFrontEndPlugin < ISuggestions > = {
404
+ id : '@jupyter/collaboration-extension:rtcGlobalSuggestions' ,
405
+ description : 'A plugin to provide shared document suggestions.' ,
406
+ autoStart : true ,
407
+ provides : ISuggestions ,
408
+ activate : ( app : JupyterFrontEnd ) : ISuggestions => {
409
+ console . log ( 'suggestions plugin activated' ) ;
410
+ return new Suggestions ( ) ;
411
+ } ,
412
+ } ;
413
+
414
+ export class Suggestions implements ISuggestions {
415
+ private _forkIds : string [ ] ;
416
+ private _callbacks : any [ ] ;
417
+
418
+ constructor ( ) {
419
+ this . _forkIds = [ ] ;
420
+ this . _callbacks = [ ] ;
421
+ }
422
+
423
+ addFork ( forkId : string ) {
424
+ this . _forkIds . push ( forkId ) ;
425
+ for ( const callback of this . _callbacks ) {
426
+ callback ( forkId ) ;
262
427
}
263
- } ) ;
264
- menu . addItem ( {
265
- type : 'command' ,
266
- command : command
267
- } ) ;
428
+ }
429
+
430
+ addCallback ( callback : any ) {
431
+ this . _callbacks . push ( callback ) ;
432
+ }
433
+
434
+ get forks ( ) : string [ ] {
435
+ return this . _forkIds ;
436
+ }
268
437
}
0 commit comments