5
5
* @license MIT
6
6
*/
7
7
8
+ // TODO: support reconnects with same id
9
+
10
+ // TODO: We keep getting:
11
+ // connection to 'ws...' failed: Invalid frame header
12
+
8
13
import WebSocket from 'reconnecting-websocket' ;
9
14
import Logdown from 'logdown' ;
10
15
import sharedb from 'sharedb/lib/sharedb' ;
11
16
import type {
12
17
AceMultiCursorManager ,
13
18
AceMultiSelectionManager ,
19
+ AceRadarView ,
14
20
IRangeData
15
21
} from '@convergencelabs/ace-collab-ext' ;
16
- import { AceRangeUtil } from '@convergencelabs/ace-collab-ext' ;
22
+ import { IIndexRange } from '@convergencelabs/ace-collab-ext/dist/types/IndexRange' ;
23
+ import { AceViewportUtil , AceRangeUtil } from '@convergencelabs/ace-collab-ext' ;
17
24
import type { Ace , EditSession } from 'ace-builds' ;
18
25
import type { IAceEditor } from 'react-ace/lib/types' ;
19
26
import type { SharedbAcePlugin , SharedbAceUser } from './types' ;
@@ -31,6 +38,7 @@ interface SharedbAceBindingOptions {
31
38
user : SharedbAceUser ;
32
39
cursorManager : AceMultiCursorManager ;
33
40
selectionManager : AceMultiSelectionManager ;
41
+ radarManager : AceRadarView ;
34
42
usersPresence : sharedb . Presence ;
35
43
pluginWS ?: WebSocket ;
36
44
path : string [ ] ;
@@ -41,7 +49,9 @@ interface SharedbAceBindingOptions {
41
49
interface PresenceUpdate {
42
50
user : SharedbAceUser ;
43
51
cursorPos ?: Ace . Point ;
44
- ranges ?: IRangeData [ ] ;
52
+ selectionRange ?: IRangeData [ ] ;
53
+ radarViewRows ?: IIndexRange ;
54
+ radarCursorRow ?: number ;
45
55
}
46
56
47
57
class SharedbAceBinding {
@@ -55,6 +65,7 @@ class SharedbAceBinding {
55
65
56
66
cursorManager : AceMultiCursorManager ;
57
67
selectionManager : AceMultiSelectionManager ;
68
+ radarManager : AceRadarView ;
58
69
59
70
usersPresence : sharedb . Presence ;
60
71
@@ -64,7 +75,7 @@ class SharedbAceBinding {
64
75
// This events need to be suppressed to prevent infinite looping
65
76
suppress = false ;
66
77
67
- localPresence ?: sharedb . LocalPresence ;
78
+ localPresence ?: sharedb . LocalPresence < PresenceUpdate > ;
68
79
69
80
onError : ( err : unknown ) => unknown ;
70
81
@@ -112,6 +123,7 @@ class SharedbAceBinding {
112
123
this . user = options . user ;
113
124
this . cursorManager = options . cursorManager ;
114
125
this . selectionManager = options . selectionManager ;
126
+ this . radarManager = options . radarManager ;
115
127
this . usersPresence = options . usersPresence ;
116
128
this . onError = options . onError ;
117
129
this . logger = Logdown ( 'shareace' ) ;
@@ -139,7 +151,9 @@ class SharedbAceBinding {
139
151
140
152
this . cursorManager . removeAll ( ) ;
141
153
this . selectionManager . removeAll ( ) ;
142
- this . initializePresence ( ) ;
154
+ // TODO: Remove all views for radarManager
155
+ // this.radarManager.removeView();
156
+ this . initializeLocalPresence ( ) ;
143
157
for ( const [ id , update ] of Object . entries ( this . usersPresence . remotePresences ) ) {
144
158
this . initializeRemotePresence ( id , update ) ;
145
159
}
@@ -149,11 +163,13 @@ class SharedbAceBinding {
149
163
* Listens to the changes
150
164
*/
151
165
listen = ( ) => {
166
+ // TODO: Also update view on window resize
152
167
this . session . on ( 'change' , this . onLocalChange ) ;
168
+ this . session . on ( 'changeScrollTop' , this . onLocalChangeScrollTop ) ;
153
169
this . doc . on ( 'op' , this . onRemoteChange ) ;
154
170
this . doc . on ( 'load' , this . onRemoteReload ) ;
155
171
156
- this . usersPresence . on ( 'receive' , this . onRemotePresenceUpdate ) ;
172
+ this . usersPresence . on ( 'receive' , this . onPresenceUpdate ) ;
157
173
this . session . selection . on ( 'changeCursor' , this . onLocalCursorChange ) ;
158
174
this . session . selection . on ( 'changeSelection' , this . onLocalSelectionChange ) ;
159
175
} ;
@@ -163,10 +179,11 @@ class SharedbAceBinding {
163
179
*/
164
180
unlisten = ( ) => {
165
181
this . session . removeListener ( 'change' , this . onLocalChange ) ;
182
+ this . session . off ( 'changeScrollTop' , this . onLocalChangeScrollTop ) ;
166
183
this . doc . off ( 'op' , this . onRemoteChange ) ;
167
184
this . doc . off ( 'load' , this . onRemoteReload ) ;
168
185
169
- this . usersPresence . off ( 'receive' , this . onRemotePresenceUpdate ) ;
186
+ this . usersPresence . off ( 'receive' , this . onPresenceUpdate ) ;
170
187
this . session . selection . off ( 'changeCursor' , this . onLocalCursorChange ) ;
171
188
this . session . selection . off ( 'changeSelection' , this . onLocalSelectionChange ) ;
172
189
} ;
@@ -179,6 +196,7 @@ class SharedbAceBinding {
179
196
* @throws {Error } throws error if delta is malformed
180
197
*/
181
198
deltaTransform = ( delta : Ace . Delta ) : sharedb . Op => {
199
+ // TODO: Use SubtypeOp to declare new operations
182
200
const aceDoc = this . session . getDocument ( ) ;
183
201
const start = aceDoc . positionToIndex ( delta . start ) ;
184
202
const end = aceDoc . positionToIndex ( delta . end ) ;
@@ -329,7 +347,7 @@ class SharedbAceBinding {
329
347
}
330
348
} ;
331
349
332
- onRemotePresenceUpdate = ( id : string , update : PresenceUpdate ) => {
350
+ onPresenceUpdate = ( id : string , update : PresenceUpdate ) => {
333
351
// TODO: logger and error handling
334
352
// TODO: separate into multiple handlers
335
353
if ( update === null ) {
@@ -342,77 +360,136 @@ class SharedbAceBinding {
342
360
this . selectionManager . removeSelection ( id ) ;
343
361
// eslint-disable-next-line no-empty
344
362
} catch { }
345
- } else {
346
- if ( update . cursorPos ) {
347
- try {
348
- this . cursorManager . setCursor ( id , update . cursorPos ) ;
349
- } catch {
350
- this . cursorManager . addCursor ( id , update . user . name , update . user . color , update . cursorPos ) ;
351
- }
363
+
364
+ try {
365
+ this . radarManager . removeView ( id ) ;
366
+ // eslint-disable-next-line no-empty
367
+ } catch { }
368
+
369
+ return ;
370
+ }
371
+
372
+ if ( update . cursorPos ) {
373
+ try {
374
+ this . cursorManager . setCursor ( id , update . cursorPos ) ;
375
+ } catch {
376
+ this . cursorManager . addCursor ( id , update . user . name , update . user . color , update . cursorPos ) ;
352
377
}
378
+ }
353
379
354
- if ( update . ranges ) {
355
- const ranges = AceRangeUtil . fromJson ( update . ranges ) ;
356
- try {
357
- this . selectionManager . setSelection ( id , ranges ) ;
358
- } catch {
359
- this . selectionManager . addSelection ( id , update . user . name , update . user . color , ranges ) ;
360
- }
380
+ if ( update . selectionRange ) {
381
+ const ranges = AceRangeUtil . fromJson ( update . selectionRange ) ;
382
+ try {
383
+ this . selectionManager . setSelection ( id , ranges ) ;
384
+ } catch {
385
+ this . selectionManager . addSelection ( id , update . user . name , update . user . color , ranges ) ;
386
+ }
387
+ }
388
+
389
+ if ( update . radarViewRows ) {
390
+ const intialRows = AceViewportUtil . indicesToRows (
391
+ this . editor ,
392
+ update . radarViewRows . start ,
393
+ update . radarViewRows . end
394
+ ) ;
395
+ try {
396
+ this . radarManager . setViewRows ( id , intialRows ) ;
397
+ } catch {
398
+ this . radarManager . addView (
399
+ id ,
400
+ update . user . name ,
401
+ update . user . color ,
402
+ intialRows ,
403
+ update . radarCursorRow || 0
404
+ ) ;
361
405
}
362
406
}
363
407
} ;
364
408
409
+ onLocalChangeScrollTop = ( scrollTop : number ) => {
410
+ // TODO: logger and error handling
411
+ const viewportIndices = AceViewportUtil . getVisibleIndexRange ( this . editor ) ;
412
+ this . localPresence ?. submit ( {
413
+ user : this . user ,
414
+ radarViewRows : viewportIndices
415
+ } ) ;
416
+ } ;
417
+
365
418
onLocalCursorChange = ( ) => {
419
+ // TODO: logger and error handling
366
420
const pos = this . session . selection . getCursor ( ) ;
367
- this . updateCursorPresence ( pos ) ;
421
+ this . localPresence ?. submit ( {
422
+ user : this . user ,
423
+ cursorPos : pos ,
424
+ radarCursorRow : pos . row
425
+ } ) ;
368
426
} ;
369
427
370
428
onLocalSelectionChange = ( ) => {
429
+ // TODO: logger and error handling
371
430
const ranges = this . session . selection . getAllRanges ( ) ;
372
- this . updateSelectionPresence ( AceRangeUtil . toJson ( ranges ) ) ;
431
+ this . localPresence ?. submit ( {
432
+ user : this . user ,
433
+ selectionRange : AceRangeUtil . toJson ( ranges )
434
+ } ) ;
373
435
} ;
374
436
375
- initializePresence = ( ) => {
437
+ initializeLocalPresence = ( ) => {
376
438
// TODO: logger and error handling
377
439
this . localPresence = this . usersPresence . create ( ) ;
378
440
const cursorPos = this . session . selection . getCursor ( ) ;
379
441
const ranges = this . session . selection . getAllRanges ( ) ;
442
+
443
+ const initialIndices = AceViewportUtil . getVisibleIndexRange ( this . editor ) ;
444
+
380
445
this . localPresence . submit ( {
381
446
user : this . user ,
382
447
cursorPos,
383
- ranges
448
+ selectionRange : ranges ,
449
+ radarViewRows : initialIndices ,
450
+ radarCursorRow : cursorPos . row
384
451
} ) ;
385
452
} ;
386
453
387
- initializeRemotePresence = ( id : string , update : Required < PresenceUpdate > ) => {
388
- try {
389
- this . cursorManager . setCursor ( id , update . cursorPos ) ;
390
- } catch {
391
- this . cursorManager . addCursor ( id , update . user . name , update . user . color , update . cursorPos ) ;
454
+ // TODO: Actually the same as onPresenceUpdate
455
+ initializeRemotePresence = ( id : string , update : PresenceUpdate ) => {
456
+ if ( update . cursorPos ) {
457
+ try {
458
+ this . cursorManager . setCursor ( id , update . cursorPos ) ;
459
+ } catch {
460
+ this . cursorManager . addCursor ( id , update . user . name , update . user . color , update . cursorPos ) ;
461
+ }
392
462
}
393
463
394
- const ranges = AceRangeUtil . fromJson ( update . ranges ) ;
395
- try {
396
- this . selectionManager . setSelection ( id , ranges ) ;
397
- } catch {
398
- this . selectionManager . addSelection ( id , update . user . name , update . user . color , ranges ) ;
464
+ if ( update . selectionRange ) {
465
+ const ranges = AceRangeUtil . fromJson ( update . selectionRange ) ;
466
+ try {
467
+ this . selectionManager . setSelection ( id , ranges ) ;
468
+ } catch {
469
+ this . selectionManager . addSelection ( id , update . user . name , update . user . color , ranges ) ;
470
+ }
399
471
}
400
- } ;
401
472
402
- updateCursorPresence = ( newCursorPos : Ace . Point ) => {
403
- // TODO: logger and error handling
404
- this . localPresence ?. submit ( {
405
- user : this . user ,
406
- cursorPos : newCursorPos
407
- } ) ;
408
- } ;
473
+ if ( update . radarViewRows ) {
474
+ const rows = AceViewportUtil . indicesToRows (
475
+ this . editor ,
476
+ update . radarViewRows . start ,
477
+ update . radarViewRows . end
478
+ ) ;
409
479
410
- updateSelectionPresence = ( newRanges : IRangeData [ ] ) => {
411
- // TODO: logger and error handling
412
- this . localPresence ?. submit ( {
413
- user : this . user ,
414
- ranges : newRanges
415
- } ) ;
480
+ try {
481
+ this . radarManager . setViewRows ( id , rows ) ;
482
+ this . radarManager . setCursorRow ( id , update . radarCursorRow || 0 ) ;
483
+ } catch {
484
+ this . radarManager . addView (
485
+ id ,
486
+ update . user . name ,
487
+ update . user . color ,
488
+ rows ,
489
+ update . radarCursorRow || 0
490
+ ) ;
491
+ }
492
+ }
416
493
} ;
417
494
418
495
destroyPresence = ( ) => {
0 commit comments