@@ -71,6 +71,7 @@ import { Coordinate } from 'ol/coordinate';
71
71
import AnnotationFloater from '../annotations/components/AnnotationFloater' ;
72
72
import { CommandIDs } from '../constants' ;
73
73
import { FollowIndicator } from './FollowIndicator' ;
74
+ import CollaboratorPointers , { ClientPointer } from './CollaboratorPointers' ;
74
75
75
76
interface IProps {
76
77
viewModel : MainViewModel ;
@@ -84,6 +85,7 @@ interface IStates {
84
85
remoteUser ?: User . IIdentity | null ;
85
86
firstLoad : boolean ;
86
87
annotations : IDict < IAnnotation > ;
88
+ clientPointers : IDict < ClientPointer > ;
87
89
}
88
90
89
91
export class MainView extends React . Component < IProps , IStates > {
@@ -95,6 +97,7 @@ export class MainView extends React.Component<IProps, IStates> {
95
97
96
98
this . _mainViewModel = this . props . viewModel ;
97
99
this . _mainViewModel . viewSettingChanged . connect ( this . _onViewChanged , this ) ;
100
+
98
101
this . _model = this . _mainViewModel . jGISModel ;
99
102
this . _model . themeChanged . connect ( this . _handleThemeChange , this ) ;
100
103
@@ -106,7 +109,6 @@ export class MainView extends React.Component<IProps, IStates> {
106
109
this . _onClientSharedStateChanged ,
107
110
this
108
111
) ;
109
-
110
112
this . _model . sharedLayersChanged . connect ( this . _onLayersChanged , this ) ;
111
113
this . _model . sharedLayerTreeChanged . connect ( this . _onLayerTreeChange , this ) ;
112
114
this . _model . sharedSourcesChanged . connect ( this . _onSourcesChange , this ) ;
@@ -122,7 +124,8 @@ export class MainView extends React.Component<IProps, IStates> {
122
124
lightTheme : isLightTheme ( ) ,
123
125
loading : true ,
124
126
firstLoad : true ,
125
- annotations : { }
127
+ annotations : { } ,
128
+ clientPointers : { }
126
129
} ;
127
130
128
131
this . _sources = [ ] ;
@@ -273,6 +276,10 @@ export class MainView extends React.Component<IProps, IStates> {
273
276
} ) ;
274
277
} ) ;
275
278
279
+ this . _Map
280
+ . getViewport ( )
281
+ . addEventListener ( 'pointermove' , this . _onPointerMove . bind ( this ) ) ;
282
+
276
283
if ( JupyterGISModel . getOrderedLayerIds ( this . _model ) . length !== 0 ) {
277
284
await this . _updateLayersImpl (
278
285
JupyterGISModel . getOrderedLayerIds ( this . _model )
@@ -282,15 +289,15 @@ export class MainView extends React.Component<IProps, IStates> {
282
289
this . _initializedPosition = true ;
283
290
}
284
291
285
- this . setState ( old => ( { ...old , loading : false } ) ) ;
286
-
287
292
this . _Map . getViewport ( ) . addEventListener ( 'contextmenu' , event => {
288
293
event . preventDefault ( ) ;
289
294
event . stopPropagation ( ) ;
290
295
const coordinate = this . _Map . getEventCoordinate ( event ) ;
291
296
this . _clickCoords = coordinate ;
292
297
this . _contextMenu . open ( event ) ;
293
298
} ) ;
299
+
300
+ this . setState ( old => ( { ...old , loading : false } ) ) ;
294
301
}
295
302
}
296
303
@@ -969,6 +976,50 @@ export class MainView extends React.Component<IProps, IStates> {
969
976
}
970
977
}
971
978
}
979
+
980
+ // cursors
981
+ clients . forEach ( ( client , clientId ) => {
982
+ if ( ! client ?. user ) {
983
+ return ;
984
+ }
985
+
986
+ const pointer = client . pointer ?. value ;
987
+
988
+ // We already display our own cursor on mouse move
989
+ if ( this . _model . getClientId ( ) === clientId ) {
990
+ return ;
991
+ }
992
+
993
+ const clientPointers = this . state . clientPointers ;
994
+ let currentClientPointer = clientPointers [ clientId ] ;
995
+
996
+ if ( pointer ) {
997
+ const pixel = this . _Map . getPixelFromCoordinate ( [
998
+ pointer . coordinates . x ,
999
+ pointer . coordinates . y
1000
+ ] ) ;
1001
+
1002
+ const lonLat = toLonLat ( [ pointer . coordinates . x , pointer . coordinates . y ] ) ;
1003
+
1004
+ if ( ! currentClientPointer ) {
1005
+ currentClientPointer = clientPointers [ clientId ] = {
1006
+ username : client . user . username ,
1007
+ displayName : client . user . display_name ,
1008
+ color : client . user . color ,
1009
+ coordinates : { x : pixel [ 0 ] , y : pixel [ 1 ] } ,
1010
+ lonLat : { longitude : lonLat [ 0 ] , latitude : lonLat [ 1 ] }
1011
+ } ;
1012
+ }
1013
+
1014
+ currentClientPointer . coordinates . x = pixel [ 0 ] ;
1015
+ currentClientPointer . coordinates . y = pixel [ 1 ] ;
1016
+ clientPointers [ clientId ] = currentClientPointer ;
1017
+ } else {
1018
+ delete clientPointers [ clientId ] ;
1019
+ }
1020
+
1021
+ this . setState ( old => ( { ...old , clientPointers : clientPointers } ) ) ;
1022
+ } ) ;
972
1023
} ;
973
1024
974
1025
private _onSharedOptionsChanged (
@@ -1213,6 +1264,20 @@ export class MainView extends React.Component<IProps, IStates> {
1213
1264
}
1214
1265
}
1215
1266
1267
+ private _onPointerMove ( e : MouseEvent ) {
1268
+ const pixel = this . _Map . getEventPixel ( e ) ;
1269
+ const coordinates = this . _Map . getCoordinateFromPixel ( pixel ) ;
1270
+
1271
+ this . _syncPointer ( coordinates ) ;
1272
+ }
1273
+
1274
+ private _syncPointer = throttle ( ( coordinates : Coordinate ) => {
1275
+ const pointer = {
1276
+ coordinates : { x : coordinates [ 0 ] , y : coordinates [ 1 ] }
1277
+ } ;
1278
+ this . _model . syncPointer ( pointer ) ;
1279
+ } ) ;
1280
+
1216
1281
private _handleThemeChange = ( ) : void => {
1217
1282
const lightTheme = isLightTheme ( ) ;
1218
1283
@@ -1242,7 +1307,7 @@ export class MainView extends React.Component<IProps, IStates> {
1242
1307
left : screenPosition . x ,
1243
1308
top : screenPosition . y
1244
1309
} }
1245
- className = { 'jGIS-Annotation -Wrapper' }
1310
+ className = { 'jGIS-Popup -Wrapper' }
1246
1311
>
1247
1312
< AnnotationFloater
1248
1313
itemId = { key }
@@ -1253,6 +1318,7 @@ export class MainView extends React.Component<IProps, IStates> {
1253
1318
)
1254
1319
) ;
1255
1320
} ) }
1321
+
1256
1322
< div
1257
1323
className = "jGIS-Mainview"
1258
1324
style = { {
@@ -1263,6 +1329,7 @@ export class MainView extends React.Component<IProps, IStates> {
1263
1329
>
1264
1330
< Spinner loading = { this . state . loading } />
1265
1331
< FollowIndicator remoteUser = { this . state . remoteUser } />
1332
+ < CollaboratorPointers clients = { this . state . clientPointers } />
1266
1333
1267
1334
< div
1268
1335
ref = { this . divRef }
0 commit comments