1
1
// Copyright (c) Jupyter Development Team.
2
2
// Distributed under the terms of the Modified BSD License.
3
3
4
- import * as React from 'react ' ;
4
+ import { ReactWidget } from '@jupyterlab/apputils ' ;
5
5
6
- import { Awareness } from 'y-protocols/awareness ' ;
6
+ import { DocumentRegistry } from '@jupyterlab/docregistry ' ;
7
7
8
- import { Panel } from '@lumino/widgets ' ;
8
+ import { User } from '@jupyterlab/services ' ;
9
9
10
- import { ReactWidget } from '@jupyterlab/apputils ' ;
10
+ import { LabIcon , caretDownIcon , fileIcon } from '@jupyterlab/ui-components ' ;
11
11
12
- import { User } from '@jupyterlab/services ' ;
12
+ import { Signal , ISignal } from '@lumino/signaling ' ;
13
13
14
- import { PathExt } from '@jupyterlab/coreutils' ;
14
+ import { Panel } from '@lumino/widgets' ;
15
+
16
+ import React , { useState } from 'react' ;
17
+
18
+ import { Awareness } from 'y-protocols/awareness' ;
15
19
16
20
import { ICollaboratorAwareness } from './tokens' ;
17
21
@@ -31,7 +35,17 @@ const COLLABORATORS_LIST_CLASS = 'jp-CollaboratorsList';
31
35
const COLLABORATOR_CLASS = 'jp-Collaborator' ;
32
36
33
37
/**
34
- * The CSS class added to each collaborator element.
38
+ * The CSS class added to each collaborator header.
39
+ */
40
+ const COLLABORATOR_HEADER_CLASS = 'jp-CollaboratorHeader' ;
41
+
42
+ /**
43
+ * The CSS class added to each collaborator header collapser.
44
+ */
45
+ const COLLABORATOR_HEADER_COLLAPSER_CLASS = 'jp-CollaboratorHeaderCollapser' ;
46
+
47
+ /**
48
+ * The CSS class added to each collaborator header with document.
35
49
*/
36
50
const CLICKABLE_COLLABORATOR_CLASS = 'jp-ClickableCollaborator' ;
37
51
@@ -40,15 +54,22 @@ const CLICKABLE_COLLABORATOR_CLASS = 'jp-ClickableCollaborator';
40
54
*/
41
55
const COLLABORATOR_ICON_CLASS = 'jp-CollaboratorIcon' ;
42
56
43
- export class CollaboratorsPanel extends Panel {
44
- private _currentUser : User . IManager ;
45
- private _awareness : Awareness ;
46
- private _body : CollaboratorsBody ;
57
+ /**
58
+ * The CSS class added to the files list.
59
+ */
60
+ const COLLABORATOR_FILES_CLASS = 'jp-CollaboratorFiles' ;
47
61
62
+ /**
63
+ * The CSS class added to the files in the list.
64
+ */
65
+ const COLLABORATOR_FILE_CLASS = 'jp-CollaboratorFile' ;
66
+
67
+ export class CollaboratorsPanel extends Panel {
48
68
constructor (
49
69
currentUser : User . IManager ,
50
70
awareness : Awareness ,
51
- fileopener : ( path : string ) => void
71
+ fileopener : ( path : string ) => void ,
72
+ docRegistry ?: DocumentRegistry
52
73
) {
53
74
super ( { } ) ;
54
75
@@ -58,9 +79,15 @@ export class CollaboratorsPanel extends Panel {
58
79
59
80
this . addClass ( COLLABORATORS_PANEL_CLASS ) ;
60
81
61
- this . _body = new CollaboratorsBody ( fileopener ) ;
62
- this . addWidget ( this . _body ) ;
63
- this . update ( ) ;
82
+ this . addWidget (
83
+ ReactWidget . create (
84
+ < CollaboratorsBody
85
+ fileopener = { fileopener }
86
+ collaboratorsChanged = { this . _collaboratorsChanged }
87
+ docRegistry = { docRegistry }
88
+ > </ CollaboratorsBody >
89
+ )
90
+ ) ;
64
91
65
92
this . _awareness . on ( 'change' , this . _onAwarenessChanged ) ;
66
93
}
@@ -80,78 +107,148 @@ export class CollaboratorsPanel extends Panel {
80
107
collaborators . push ( value ) ;
81
108
}
82
109
} ) ;
83
-
84
- this . _body . collaborators = collaborators ;
110
+ this . _collaboratorsChanged . emit ( collaborators ) ;
85
111
} ;
112
+ private _currentUser : User . IManager ;
113
+ private _awareness : Awareness ;
114
+ private _collaboratorsChanged = new Signal < this, ICollaboratorAwareness [ ] > (
115
+ this
116
+ ) ;
86
117
}
87
118
88
- /**
89
- * The collaborators list.
90
- */
91
- export class CollaboratorsBody extends ReactWidget {
92
- private _collaborators : ICollaboratorAwareness [ ] = [ ] ;
93
- private _fileopener : ( path : string ) => void ;
94
-
95
- constructor ( fileopener : ( path : string ) => void ) {
96
- super ( ) ;
97
- this . _fileopener = fileopener ;
98
- this . addClass ( COLLABORATORS_LIST_CLASS ) ;
99
- }
100
-
101
- get collaborators ( ) : ICollaboratorAwareness [ ] {
102
- return this . _collaborators ;
103
- }
119
+ export function CollaboratorsBody ( props : {
120
+ collaboratorsChanged : ISignal < CollaboratorsPanel , ICollaboratorAwareness [ ] > ;
121
+ fileopener : ( path : string ) => void ;
122
+ docRegistry ?: DocumentRegistry ;
123
+ } ) : JSX . Element {
124
+ const [ collaborators , setCollaborators ] = useState < ICollaboratorAwareness [ ] > (
125
+ [ ]
126
+ ) ;
127
+
128
+ props . collaboratorsChanged . connect ( ( _ , value ) => {
129
+ setCollaborators ( value ) ;
130
+ } ) ;
131
+
132
+ return (
133
+ < div className = { COLLABORATORS_LIST_CLASS } >
134
+ { collaborators . map ( ( collaborator , i ) => {
135
+ return (
136
+ < Collaborator
137
+ collaborator = { collaborator }
138
+ fileopener = { props . fileopener }
139
+ docRegistry = { props . docRegistry }
140
+ > </ Collaborator >
141
+ ) ;
142
+ } ) }
143
+ </ div >
144
+ ) ;
145
+ }
104
146
105
- set collaborators ( value : ICollaboratorAwareness [ ] ) {
106
- this . _collaborators = value ;
107
- this . update ( ) ;
147
+ export function Collaborator ( props : {
148
+ collaborator : ICollaboratorAwareness ;
149
+ fileopener : ( path : string ) => void ;
150
+ docRegistry ?: DocumentRegistry ;
151
+ } ) : JSX . Element {
152
+ const [ open , setOpen ] = useState < boolean > ( false ) ;
153
+ const { collaborator, fileopener } = props ;
154
+ let currentMain = '' ;
155
+
156
+ if ( collaborator . current ) {
157
+ const path = collaborator . current . split ( ':' ) ;
158
+ currentMain = `${ path [ 1 ] } :${ path [ 2 ] } ` ;
108
159
}
109
160
110
- render ( ) : React . ReactElement < any > [ ] {
111
- return this . _collaborators . map ( ( value , i ) => {
112
- let canOpenCurrent = false ;
113
- let current = '' ;
114
- let separator = '' ;
115
- let currentFileLocation = '' ;
116
-
117
- if ( value . current ) {
118
- canOpenCurrent = true ;
119
- const path = value . current . split ( ':' ) ;
120
- currentFileLocation = `${ path [ 1 ] } :${ path [ 2 ] } ` ;
121
-
122
- current = PathExt . basename ( path [ 2 ] ) ;
123
- current =
124
- current . length > 25 ? current . slice ( 0 , 12 ) . concat ( '…' ) : current ;
125
- separator = '•' ;
126
- }
161
+ const documents : string [ ] = collaborator . documents || [ ] ;
162
+
163
+ const docs = documents . map ( document => {
164
+ const path = document . split ( ':' ) ;
165
+ const fileTypes = props . docRegistry
166
+ ?. getFileTypesForPath ( path [ 1 ] )
167
+ ?. filter ( ft => ft . icon !== undefined ) ;
168
+ const icon = fileTypes ? fileTypes [ 0 ] . icon ! : fileIcon ;
169
+ const iconClass : string | undefined = fileTypes
170
+ ? fileTypes [ 0 ] . iconClass
171
+ : undefined ;
172
+
173
+ return {
174
+ filepath : path [ 1 ] ,
175
+ filename :
176
+ path [ 1 ] . length > 40
177
+ ? path [ 1 ]
178
+ . slice ( 0 , 10 )
179
+ . concat ( '…' )
180
+ . concat ( path [ 1 ] . slice ( path [ 1 ] . length - 15 ) )
181
+ : path [ 1 ] ,
182
+ fileLocation : document ,
183
+ icon,
184
+ iconClass
185
+ } ;
186
+ } ) ;
187
+
188
+ const onClick = ( ) => {
189
+ if ( docs . length ) {
190
+ setOpen ( ! open ) ;
191
+ }
192
+ } ;
127
193
128
- const onClick = ( ) => {
129
- if ( canOpenCurrent ) {
130
- this . _fileopener ( currentFileLocation ) ;
194
+ return (
195
+ < div className = { COLLABORATOR_CLASS } >
196
+ < div
197
+ className = {
198
+ docs . length
199
+ ? `${ CLICKABLE_COLLABORATOR_CLASS } ${ COLLABORATOR_HEADER_CLASS } `
200
+ : COLLABORATOR_HEADER_CLASS
131
201
}
132
- } ;
133
-
134
- const displayName = `${ value . user . display_name } ${ separator } ${ current } ` ;
135
-
136
- return (
137
- < div
202
+ onClick = { documents ? onClick : undefined }
203
+ >
204
+ < LabIcon . resolveReact
205
+ icon = { caretDownIcon }
138
206
className = {
139
- canOpenCurrent
140
- ? `${ CLICKABLE_COLLABORATOR_CLASS } ${ COLLABORATOR_CLASS } `
141
- : COLLABORATOR_CLASS
207
+ COLLABORATOR_HEADER_COLLAPSER_CLASS +
208
+ ( open ? ' jp-mod-expanded' : '' )
142
209
}
143
- key = { i }
144
- onClick = { onClick }
210
+ tag = { 'div' }
211
+ />
212
+ < div
213
+ className = { COLLABORATOR_ICON_CLASS }
214
+ style = { { backgroundColor : collaborator . user . color } }
145
215
>
146
- < div
147
- className = { COLLABORATOR_ICON_CLASS }
148
- style = { { backgroundColor : value . user . color } }
149
- >
150
- < span > { value . user . initials } </ span >
151
- </ div >
152
- < span > { displayName } </ span >
216
+ < span > { collaborator . user . initials } </ span >
153
217
</ div >
154
- ) ;
155
- } ) ;
156
- }
218
+ < span > { collaborator . user . display_name } </ span >
219
+ </ div >
220
+ < div
221
+ className = { `${ COLLABORATOR_FILES_CLASS } jp-DirListing` }
222
+ style = { open ? { } : { display : 'none' } }
223
+ >
224
+ < ul className = { 'jp-DirListing-content' } >
225
+ { docs . map ( doc => {
226
+ return (
227
+ < li
228
+ className = {
229
+ 'jp-DirListing-item ' +
230
+ ( doc . fileLocation === currentMain
231
+ ? `${ COLLABORATOR_FILE_CLASS } jp-mod-running`
232
+ : COLLABORATOR_FILE_CLASS )
233
+ }
234
+ key = { doc . filename }
235
+ onClick = { ( ) => fileopener ( doc . fileLocation ) }
236
+ >
237
+ < LabIcon . resolveReact
238
+ icon = { doc . icon }
239
+ iconClass = { doc . iconClass }
240
+ tag = { 'span' }
241
+ className = { 'jp-DirListing-itemIcon' }
242
+ stylesheet = { 'listing' }
243
+ />
244
+ < span className = { 'jp-DirListing-itemText' } title = { doc . filepath } >
245
+ { doc . filename }
246
+ </ span >
247
+ </ li >
248
+ ) ;
249
+ } ) }
250
+ </ ul >
251
+ </ div >
252
+ </ div >
253
+ ) ;
157
254
}
0 commit comments