@@ -7,10 +7,18 @@ import React, { Ref, useCallback, useEffect, useMemo, useRef, useState } from 'r
7
7
import { useCombinedRefs } from '../../../shared/utils/useCombinedRefs' ;
8
8
import EmptyWhiteboard from '../EmptyWhiteboard' ;
9
9
import { ExcalidrawElement } from '@alkemio/excalidraw/types/element/types' ;
10
- import { CollabAPI } from './collab/Collab' ;
11
10
import { useUserContext } from '../../../community/user' ;
12
11
import { WhiteboardFilesManager } from './useWhiteboardFilesManager' ;
13
- import useCollab from './collab/useCollab' ;
12
+ import useCollab , { CollabAPI } from './collab/useCollab' ;
13
+ import Dialog from '@mui/material/Dialog' ;
14
+ import DialogHeader from '../../../../core/ui/dialog/DialogHeader' ;
15
+ import { DialogContent } from '../../../../core/ui/dialog/deprecated' ;
16
+ import WrapperMarkdown from '../../../../core/ui/markdown/WrapperMarkdown' ;
17
+ import { Text } from '../../../../core/ui/typography' ;
18
+ import { formatTimeElapsed } from '../../../shared/utils/formatTimeElapsed' ;
19
+ import { Button , DialogActions } from '@mui/material' ;
20
+ import { useTranslation } from 'react-i18next' ;
21
+ import { LoadingButton } from '@mui/lab' ;
14
22
15
23
const useActorWhiteboardStyles = makeStyles ( theme => ( {
16
24
container : {
@@ -30,6 +38,7 @@ const useActorWhiteboardStyles = makeStyles(theme => ({
30
38
export interface WhiteboardWhiteboardEntities {
31
39
whiteboard : { id ?: string ; content : string } | undefined ;
32
40
filesManager : WhiteboardFilesManager ;
41
+ lastSavedDate : Date | undefined ;
33
42
}
34
43
35
44
export interface WhiteboardWhiteboardActions {
@@ -38,32 +47,22 @@ export interface WhiteboardWhiteboardActions {
38
47
onSavedToDatabase ?: ( ) => void ;
39
48
}
40
49
41
- export interface WhiteboardWhiteboardEvents {
42
- onCollaborationEnabledChange ?: ( collaborationEnabled : boolean ) => void ;
43
- }
50
+ export interface WhiteboardWhiteboardEvents { }
44
51
45
- export interface WhiteboardWhiteboardOptions extends ExcalidrawProps {
46
- collaborationEnabled : boolean ;
47
- }
52
+ export interface WhiteboardWhiteboardOptions extends ExcalidrawProps { }
48
53
49
54
export interface WhiteboardWhiteboardProps {
50
55
entities : WhiteboardWhiteboardEntities ;
51
56
options : WhiteboardWhiteboardOptions ;
52
57
actions : WhiteboardWhiteboardActions ;
53
- events : WhiteboardWhiteboardEvents ;
58
+ events ? : WhiteboardWhiteboardEvents ;
54
59
collabApiRef ?: Ref < CollabAPI | null > ;
55
60
}
56
61
57
62
const WINDOW_SCROLL_HANDLER_DEBOUNCE_INTERVAL = 100 ;
58
63
59
- const CollaborativeExcalidrawWrapper = ( {
60
- entities,
61
- actions,
62
- options,
63
- events,
64
- collabApiRef,
65
- } : WhiteboardWhiteboardProps ) => {
66
- const { whiteboard, filesManager } = entities ;
64
+ const CollaborativeExcalidrawWrapper = ( { entities, actions, options, collabApiRef } : WhiteboardWhiteboardProps ) => {
65
+ const { whiteboard, filesManager, lastSavedDate } = entities ;
67
66
68
67
const combinedCollabApiRef = useCombinedRefs < CollabAPI | null > ( null , collabApiRef ) ;
69
68
@@ -116,11 +115,11 @@ const CollaborativeExcalidrawWrapper = ({
116
115
[ ]
117
116
) ;
118
117
119
- const { UIOptions : externalUIOptions , collaborationEnabled , ...restOptions } = options ;
118
+ const { UIOptions : externalUIOptions , ...restOptions } = options ;
120
119
121
120
const mergedUIOptions = useMemo ( ( ) => merge ( UIOptions , externalUIOptions ) , [ UIOptions , externalUIOptions ] ) ;
122
121
123
- const [ collabApi , initializeCollab ] = useCollab ( {
122
+ const [ collabApi , initializeCollab , { connecting , collaborating } ] = useCollab ( {
124
123
username,
125
124
onSavedToDatabase : actions . onSavedToDatabase ,
126
125
filesManager,
@@ -138,11 +137,10 @@ const CollaborativeExcalidrawWrapper = ({
138
137
return { success : false , errors : [ 'ExcalidrawAPI not yet ready' ] } ;
139
138
} ,
140
139
onCloseConnection : ( ) => {
141
- events . onCollaborationEnabledChange ?. ( false ) ;
140
+ setCollaborationStoppedNoticeOpen ( true ) ;
142
141
} ,
143
142
onInitialize : collabApi => {
144
143
combinedCollabApiRef . current = collabApi ;
145
- events . onCollaborationEnabledChange ?.( true ) ;
146
144
} ,
147
145
} ) ;
148
146
@@ -153,14 +151,26 @@ const CollaborativeExcalidrawWrapper = ({
153
151
154
152
const [ excalidrawApi , setExcalidrawApi ] = useState < ExcalidrawImperativeAPI | null > ( null ) ;
155
153
154
+ const [ collaborationStartTime , setCollaborationStartTime ] = useState < number | null > ( Date . now ( ) ) ;
155
+
156
+ const restartCollaboration = ( ) => {
157
+ setCollaborationStartTime ( Date . now ( ) ) ;
158
+ } ;
159
+
160
+ useEffect ( ( ) => {
161
+ if ( ! connecting && collaborating ) {
162
+ setCollaborationStoppedNoticeOpen ( false ) ;
163
+ }
164
+ } , [ connecting , collaborating ] ) ;
165
+
156
166
useEffect ( ( ) => {
157
- if ( excalidrawApi && whiteboard ?. id ) {
167
+ if ( excalidrawApi && whiteboard ?. id && collaborationStartTime !== null ) {
158
168
return initializeCollab ( {
159
169
excalidrawApi,
160
170
roomId : whiteboard . id ,
161
171
} ) ;
162
172
}
163
- } , [ excalidrawApi , whiteboard ?. id ] ) ;
173
+ } , [ excalidrawApi , whiteboard ?. id , collaborationStartTime ] ) ;
164
174
165
175
const handleInitializeApi = useCallback (
166
176
( excalidrawApi : ExcalidrawImperativeAPI ) => {
@@ -170,29 +180,68 @@ const CollaborativeExcalidrawWrapper = ({
170
180
[ actions . onInitApi ]
171
181
) ;
172
182
183
+ const [ collaborationStoppedNoticeOpen , setCollaborationStoppedNoticeOpen ] = useState ( false ) ;
184
+
185
+ const { t } = useTranslation ( ) ;
186
+
187
+ const [ isOnline , setIsOnline ] = useState ( navigator . onLine ) ;
188
+
189
+ useEffect ( ( ) => {
190
+ const handleOnlineChange = ( ) => setIsOnline ( navigator . onLine ) ;
191
+ window . addEventListener ( 'online' , handleOnlineChange ) ;
192
+ window . addEventListener ( 'offline' , handleOnlineChange ) ;
193
+ setIsOnline ( navigator . onLine ) ;
194
+ return ( ) => {
195
+ window . removeEventListener ( 'online' , handleOnlineChange ) ;
196
+ window . removeEventListener ( 'offline' , handleOnlineChange ) ;
197
+ } ;
198
+ } , [ ] ) ;
199
+
173
200
return (
174
- < div className = { styles . container } >
175
- { whiteboard && (
176
- < Excalidraw
177
- key = { whiteboard . id } // initializing a fresh Excalidraw for each whiteboard
178
- excalidrawAPI = { handleInitializeApi }
179
- initialData = { data }
180
- UIOptions = { mergedUIOptions }
181
- isCollaborating = { collaborationEnabled }
182
- viewModeEnabled = { ! collaborationEnabled }
183
- gridModeEnabled
184
- onChange = { onChange }
185
- onPointerUpdate = { collabApi ?. onPointerUpdate }
186
- detectScroll = { false }
187
- autoFocus
188
- generateIdForFile = { addNewFile }
189
- /*renderTopRightUI={_isMobile => {
190
- return <LiveCollaborationStatus />;
191
- }}*/
192
- { ...restOptions }
193
- />
194
- ) }
195
- </ div >
201
+ < >
202
+ < div className = { styles . container } >
203
+ { whiteboard && (
204
+ < Excalidraw
205
+ key = { whiteboard . id } // initializing a fresh Excalidraw for each whiteboard
206
+ excalidrawAPI = { handleInitializeApi }
207
+ initialData = { data }
208
+ UIOptions = { mergedUIOptions }
209
+ isCollaborating = { collaborating }
210
+ viewModeEnabled = { ! collaborating }
211
+ gridModeEnabled
212
+ onChange = { onChange }
213
+ onPointerUpdate = { collabApi ?. onPointerUpdate }
214
+ detectScroll = { false }
215
+ autoFocus
216
+ generateIdForFile = { addNewFile }
217
+ /*renderTopRightUI={_isMobile => {
218
+ return <LiveCollaborationStatus />;
219
+ }}*/
220
+ { ...restOptions }
221
+ />
222
+ ) }
223
+ </ div >
224
+ < Dialog open = { collaborationStoppedNoticeOpen } onClose = { ( ) => setCollaborationStoppedNoticeOpen ( false ) } >
225
+ < DialogHeader title = { t ( 'pages.whiteboard.whiteboardDisconnected.title' ) } />
226
+ < DialogContent >
227
+ { isOnline && < WrapperMarkdown > { t ( 'pages.whiteboard.whiteboardDisconnected.message' ) } </ WrapperMarkdown > }
228
+ { ! isOnline && < WrapperMarkdown > { t ( 'pages.whiteboard.whiteboardDisconnected.offline' ) } </ WrapperMarkdown > }
229
+ { lastSavedDate && (
230
+ < Text >
231
+ { t ( 'pages.whiteboard.whiteboardDisconnected.lastSaved' , {
232
+ lastSaved : formatTimeElapsed ( lastSavedDate , t ) ,
233
+ } ) }
234
+ </ Text >
235
+ ) }
236
+ </ DialogContent >
237
+ < DialogActions >
238
+ < LoadingButton onClick = { restartCollaboration } disabled = { ! isOnline } loading = { connecting } >
239
+ Reconnect
240
+ </ LoadingButton >
241
+ < Button onClick = { ( ) => setCollaborationStoppedNoticeOpen ( false ) } > { t ( 'buttons.ok' ) } </ Button >
242
+ </ DialogActions >
243
+ </ Dialog >
244
+ </ >
196
245
) ;
197
246
} ;
198
247
0 commit comments