@@ -20,29 +20,33 @@ import {
20
20
createToolbarFactory ,
21
21
showErrorMessage
22
22
} from '@jupyterlab/apputils' ;
23
- import { ILauncher } from '@jupyterlab/launcher ' ;
23
+ import { PathExt } from '@jupyterlab/coreutils ' ;
24
24
import { DocumentRegistry } from '@jupyterlab/docregistry' ;
25
+ import { ILauncher } from '@jupyterlab/launcher' ;
25
26
import { IObservableList } from '@jupyterlab/observables' ;
26
27
import { IRenderMimeRegistry } from '@jupyterlab/rendermime' ;
27
28
import { Contents } from '@jupyterlab/services' ;
28
29
import { ISettingRegistry } from '@jupyterlab/settingregistry' ;
29
30
import { ITranslator , nullTranslator } from '@jupyterlab/translation' ;
31
+ import { launchIcon } from '@jupyterlab/ui-components' ;
30
32
import { Awareness } from 'y-protocols/awareness' ;
31
33
32
34
import {
33
35
WidgetConfig ,
34
36
ChatWidgetFactory ,
35
37
CollaborativeChatModelFactory
36
38
} from './factory' ;
37
- import { chatFileType , CommandIDs , IWidgetConfig } from './token' ;
38
- import { CollaborativeChatWidget } from './widget' ;
39
+ import { CollaborativeChatModel } from './model' ;
40
+ import { chatFileType , CommandIDs , IChatPanel , IWidgetConfig } from './token' ;
41
+ import { ChatPanel , CollaborativeChatWidget } from './widget' ;
39
42
import { YChat } from './ychat' ;
40
43
41
44
const FACTORY = 'Chat' ;
42
45
43
46
const pluginIds = {
44
47
chatCommands : 'jupyterlab-collaborative-chat:commands' ,
45
- docFactories : 'jupyterlab-collaborative-chat:factories'
48
+ docFactories : 'jupyterlab-collaborative-chat:factories' ,
49
+ chatPanel : 'jupyterlab-collaborative-chat:chat-panel'
46
50
} ;
47
51
48
52
/**
@@ -106,7 +110,6 @@ export const docFactories: JupyterFrontEndPlugin<IWidgetConfig> = {
106
110
pluginIds . docFactories ,
107
111
translator
108
112
) ;
109
- console . log ( 'Create toolbarFactory' , toolbarFactory ) ;
110
113
}
111
114
112
115
// Wait for the application to be restored and
@@ -194,17 +197,20 @@ export const docFactories: JupyterFrontEndPlugin<IWidgetConfig> = {
194
197
} ;
195
198
196
199
/**
197
- * Extension registering the chat file type .
200
+ * Extension providing the commands, menu and laucher .
198
201
*/
199
- export const chatCommands : JupyterFrontEndPlugin < void > = {
202
+ const chatCommands : JupyterFrontEndPlugin < void > = {
200
203
id : pluginIds . chatCommands ,
201
204
description : 'The commands to create or open a chat' ,
202
205
autoStart : true ,
203
- requires : [ ICollaborativeDrive ] ,
204
- optional : [ ICommandPalette , ILauncher ] ,
206
+ requires : [ ICollaborativeDrive , IGlobalAwareness , IWidgetConfig ] ,
207
+ optional : [ IChatPanel , ICommandPalette , ILauncher ] ,
205
208
activate : (
206
209
app : JupyterFrontEnd ,
207
210
drive : ICollaborativeDrive ,
211
+ awareness : Awareness ,
212
+ widgetConfig : IWidgetConfig ,
213
+ chatPanel : ChatPanel | null ,
208
214
commandPalette : ICommandPalette | null ,
209
215
launcher : ILauncher | null
210
216
) => {
@@ -221,6 +227,7 @@ export const chatCommands: JupyterFrontEndPlugin<void> = {
221
227
caption : 'Create a chat' ,
222
228
icon : args => ( args . isPalette ? undefined : chatIcon ) ,
223
229
execute : async args => {
230
+ const inSidePanel : boolean = ( args . inSidePanel as boolean ) ?? false ;
224
231
let name : string | null = ( args . name as string ) ?? null ;
225
232
let filepath = '' ;
226
233
if ( ! name ) {
@@ -276,11 +283,11 @@ export const chatCommands: JupyterFrontEndPlugin<void> = {
276
283
filepath = model . path ;
277
284
}
278
285
279
- return commands . execute ( CommandIDs . openChat , { filepath } ) ;
286
+ return commands . execute ( CommandIDs . openChat , { filepath, inSidePanel } ) ;
280
287
}
281
288
} ) ;
282
289
283
- /**
290
+ /*
284
291
* Command to open a chat.
285
292
*
286
293
* args:
@@ -289,6 +296,7 @@ export const chatCommands: JupyterFrontEndPlugin<void> = {
289
296
commands . addCommand ( CommandIDs . openChat , {
290
297
label : 'Open a chat' ,
291
298
execute : async args => {
299
+ const inSidePanel : boolean = ( args . inSidePanel as boolean ) ?? false ;
292
300
let filepath : string | null = ( args . filepath as string ) ?? null ;
293
301
if ( filepath === null ) {
294
302
filepath = (
@@ -317,10 +325,44 @@ export const chatCommands: JupyterFrontEndPlugin<void> = {
317
325
return ;
318
326
}
319
327
320
- commands . execute ( 'docmanager:open' , {
321
- path : `RTC:${ filepath } ` ,
322
- factory : FACTORY
323
- } ) ;
328
+ if ( inSidePanel && chatPanel ) {
329
+ // The chat is opened in the chat panel.
330
+ app . shell . activateById ( chatPanel . id ) ;
331
+ const model = await drive . get ( filepath ) ;
332
+
333
+ /**
334
+ * Create a share model from the chat file
335
+ */
336
+ const sharedModel = drive . sharedModelFactory . createNew ( {
337
+ path : model . path ,
338
+ format : model . format ,
339
+ contentType : chatFileType . contentType ,
340
+ collaborative : true
341
+ } ) as YChat ;
342
+
343
+ /**
344
+ * Initialize the chat model with the share model
345
+ */
346
+ const chat = new CollaborativeChatModel ( {
347
+ awareness,
348
+ sharedModel,
349
+ widgetConfig
350
+ } ) ;
351
+
352
+ /**
353
+ * Add a chat widget to the side panel.
354
+ */
355
+ chatPanel . addChat (
356
+ chat ,
357
+ PathExt . basename ( model . name , chatFileType . extensions [ 0 ] )
358
+ ) ;
359
+ } else {
360
+ // The chat is opened in the main area
361
+ commands . execute ( 'docmanager:open' , {
362
+ path : `RTC:${ filepath } ` ,
363
+ factory : FACTORY
364
+ } ) ;
365
+ }
324
366
}
325
367
} ) ;
326
368
@@ -348,4 +390,92 @@ export const chatCommands: JupyterFrontEndPlugin<void> = {
348
390
}
349
391
} ;
350
392
351
- export default [ chatCommands , docFactories ] ;
393
+ /*
394
+ * Extension providing a chat panel.
395
+ */
396
+ const chatPanel : JupyterFrontEndPlugin < ChatPanel > = {
397
+ id : pluginIds . chatPanel ,
398
+ description : 'A chat extension for Jupyter' ,
399
+ autoStart : true ,
400
+ provides : IChatPanel ,
401
+ requires : [ ICollaborativeDrive , IRenderMimeRegistry ] ,
402
+ optional : [ ILayoutRestorer , IThemeManager ] ,
403
+ activate : (
404
+ app : JupyterFrontEnd ,
405
+ drive : ICollaborativeDrive ,
406
+ rmRegistry : IRenderMimeRegistry ,
407
+ restorer : ILayoutRestorer | null ,
408
+ themeManager : IThemeManager | null
409
+ ) : ChatPanel => {
410
+ const { commands } = app ;
411
+
412
+ /**
413
+ * Add Chat widget to left sidebar
414
+ */
415
+ const chatPanel = new ChatPanel ( {
416
+ commands,
417
+ drive,
418
+ rmRegistry,
419
+ themeManager
420
+ } ) ;
421
+ chatPanel . id = 'JupyterCollaborationChat:sidepanel' ;
422
+ chatPanel . title . icon = chatIcon ;
423
+ chatPanel . title . caption = 'Jupyter Chat' ; // TODO: i18n/
424
+
425
+ app . shell . add ( chatPanel , 'left' , {
426
+ rank : 2000
427
+ } ) ;
428
+
429
+ if ( restorer ) {
430
+ restorer . add ( chatPanel , 'jupyter-chat' ) ;
431
+ }
432
+
433
+ // Use events system to watch changes on files.
434
+ const schemaID =
435
+ 'https://events.jupyter.org/jupyter_server/contents_service/v1' ;
436
+ const actions = [ 'create' , 'delete' , 'rename' ] ;
437
+ app . serviceManager . events . stream . connect ( ( _ , emission ) => {
438
+ if ( emission . schema_id === schemaID ) {
439
+ const action = emission . action as string ;
440
+ if ( actions . includes ( action ) ) {
441
+ chatPanel . updateChatNames ( ) ;
442
+ }
443
+ }
444
+ } ) ;
445
+
446
+ /*
447
+ * Command to move a chat from the main area to the side panel.
448
+ *
449
+ */
450
+ commands . addCommand ( CommandIDs . moveToSide , {
451
+ label : 'Move the chat to the side panel' ,
452
+ caption : 'Move the chat to the side panel' ,
453
+ icon : launchIcon ,
454
+ execute : async ( ) => {
455
+ const widget = app . shell . currentWidget ;
456
+ // Ensure widget is a CollaborativeChatWidget and is in main area
457
+ if (
458
+ ! widget ||
459
+ ! ( widget instanceof CollaborativeChatWidget ) ||
460
+ ! Array . from ( app . shell . widgets ( 'main' ) ) . includes ( widget )
461
+ ) {
462
+ console . error (
463
+ `The command '${ CommandIDs . moveToSide } ' should be executed from the toolbar button only`
464
+ ) ;
465
+ return ;
466
+ }
467
+ // Remove potential drive prefix
468
+ const filepath = widget . context . path . split ( ':' ) . pop ( ) ;
469
+ commands . execute ( CommandIDs . openChat , {
470
+ filepath,
471
+ inSidePanel : true
472
+ } ) ;
473
+ widget . dispose ( ) ;
474
+ }
475
+ } ) ;
476
+
477
+ return chatPanel ;
478
+ }
479
+ } ;
480
+
481
+ export default [ chatCommands , docFactories , chatPanel ] ;
0 commit comments