Skip to content

Commit 3a794f9

Browse files
authored
Merge pull request #5601 from alkem-io/develop
Release
2 parents c39321a + 86f1081 commit 3a794f9

File tree

17 files changed

+976
-712
lines changed

17 files changed

+976
-712
lines changed

package-lock.json

+594-318
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@alkemio/client-web",
3-
"version": "0.53.5",
3+
"version": "0.53.6",
44
"description": "Alkemio client, enabling users to interact with Challenges hosted on the Alkemio platform.",
55
"author": "Alkemio Foundation",
66
"repository": {
@@ -34,7 +34,7 @@
3434
"@emotion/react": "^11.5.0",
3535
"@emotion/styled": "^11.3.0",
3636
"@mui/base": "^5.0.0-beta.23",
37-
"@mui/icons-material": "^5.11.16",
37+
"@mui/icons-material": "^5.15.10",
3838
"@mui/lab": "^5.0.0-alpha.56",
3939
"@mui/material": "5.13.x",
4040
"@mui/styles": "^5.2.0",

src/core/apollo/generated/apollo-hooks.ts

-15
Original file line numberDiff line numberDiff line change
@@ -11140,21 +11140,6 @@ export const CommunityUserPrivilegesWithParentCommunityDocument = gql`
1114011140
}
1114111141
}
1114211142
}
11143-
adminUsers: usersInRole(role: ADMIN) {
11144-
id
11145-
profile {
11146-
id
11147-
displayName
11148-
avatar: visual(type: AVATAR) {
11149-
...VisualUri
11150-
}
11151-
location {
11152-
id
11153-
country
11154-
city
11155-
}
11156-
}
11157-
}
1115811143
}
1115911144
}
1116011145
}

src/core/apollo/generated/graphql-schema.ts

-13
Original file line numberDiff line numberDiff line change
@@ -17597,19 +17597,6 @@ export type CommunityUserPrivilegesWithParentCommunityQuery = {
1759717597
};
1759817598
}>
1759917599
| undefined;
17600-
adminUsers?:
17601-
| Array<{
17602-
__typename?: 'User';
17603-
id: string;
17604-
profile: {
17605-
__typename?: 'Profile';
17606-
id: string;
17607-
displayName: string;
17608-
avatar?: { __typename?: 'Visual'; id: string; uri: string; name: string } | undefined;
17609-
location?: { __typename?: 'Location'; id: string; country: string; city: string } | undefined;
17610-
};
17611-
}>
17612-
| undefined;
1761317600
}
1761417601
| undefined;
1761517602
};

src/core/i18n/en/translation.en.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -2281,7 +2281,8 @@
22812281
"whiteboardDisconnected": {
22822282
"title": "Whiteboard connection lost",
22832283
"message": "Apologies for the inconvenience! The whiteboard isn't available for editing right now, possibly due to a network issue or loss of access. Please try closing and reopening the whiteboard or refreshing the page. If the issue persists, reach out to your Space administrator for assistance.",
2284-
"lastSaved": "The content of this whiteboard was last saved {{lastSaved}}."
2284+
"lastSaved": "The content of this whiteboard was last saved {{lastSaved}}.",
2285+
"offline": "You are currently offline. The whiteboard will try to reconnect when you are back online."
22852286
},
22862287
"readonlyReason": {
22872288
"contentUpdatePolicy": "You don't have the rights to edit this whiteboard. Ask the owner, <ownerlink>{{ownerName}}</ownerlink>, to change the settings if you want to contribute.",

src/domain/collaboration/whiteboard/WhiteboardDialog/WhiteboardRtDialog.tsx

+5-34
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { DialogContent } from '../../../../core/ui/dialog/deprecated';
1111
import CollaborativeExcalidrawWrapper from '../../../common/whiteboard/excalidraw/CollaborativeExcalidrawWrapper';
1212
import { ExportedDataState } from '@alkemio/excalidraw/types/data/types';
1313
import DialogHeader from '../../../../core/ui/dialog/DialogHeader';
14-
import { Box, Button, DialogActions } from '@mui/material';
14+
import { Box } from '@mui/material';
1515
import { gutters } from '../../../../core/ui/grid/utils';
1616
import whiteboardSchema from '../validation/whiteboardSchema';
1717
import FormikInputField from '../../../../core/ui/forms/FormikInputField/FormikInputField';
@@ -25,12 +25,9 @@ import {
2525
generateWhiteboardPreviewImages,
2626
WhiteboardPreviewImage,
2727
} from '../WhiteboardPreviewImages/WhiteboardPreviewImages';
28-
import { Text } from '../../../../core/ui/typography';
29-
import { formatTimeElapsed } from '../../../shared/utils/formatTimeElapsed';
3028
import { useWhiteboardRtLastUpdatedDateQuery } from '../../../../core/apollo/generated/apollo-hooks';
31-
import { CollabAPI } from '../../../common/whiteboard/excalidraw/collab/Collab';
29+
import { CollabAPI } from '../../../common/whiteboard/excalidraw/collab/useCollab';
3230
import useWhiteboardFilesManager from '../../../common/whiteboard/excalidraw/useWhiteboardFilesManager';
33-
import WrapperMarkdown from '../../../../core/ui/markdown/WrapperMarkdown';
3431
import WhiteboardDialogFooter from './WhiteboardDialogFooter';
3532
import { useLocation } from 'react-router-dom';
3633
import { ExcalidrawElement, ExcalidrawImageElement } from '@alkemio/excalidraw/types/element/types';
@@ -129,9 +126,7 @@ const WhiteboardRtDialog = <Whiteboard extends WhiteboardRtWithContent>({
129126

130127
const [excalidrawAPI, setExcalidrawAPI] = useState<ExcalidrawImperativeAPI | null>(null);
131128
const collabApiRef = useRef<CollabAPI>(null);
132-
const [collaborationEnabled, setCollaborationEnabled] = useState(true);
133-
const [collaborationStoppedNoticeOpen, setCollaborationStoppedNoticeOpen] = useState(false);
134-
const editModeEnabled = options.canEdit && collaborationEnabled;
129+
const editModeEnabled = options.canEdit;
135130

136131
const styles = useStyles();
137132

@@ -226,7 +221,7 @@ const WhiteboardRtDialog = <Whiteboard extends WhiteboardRtWithContent>({
226221
};
227222

228223
const onClose = async () => {
229-
if (editModeEnabled && collaborationEnabled && whiteboard) {
224+
if (editModeEnabled && collabApiRef.current?.isCollaborating() && whiteboard) {
230225
const whiteboardState = await getWhiteboardState();
231226
const { whiteboard: updatedWhiteboard, previewImages } = await prepareWhiteboardForUpdate(
232227
whiteboard,
@@ -307,11 +302,9 @@ const WhiteboardRtDialog = <Whiteboard extends WhiteboardRtWithContent>({
307302
<DialogContent classes={{ root: styles.dialogContent }}>
308303
{!state?.loadingWhiteboardValue && whiteboard && (
309304
<CollaborativeExcalidrawWrapper
310-
entities={{ whiteboard, filesManager }}
305+
entities={{ whiteboard, filesManager, lastSavedDate }}
311306
collabApiRef={collabApiRef}
312307
options={{
313-
collaborationEnabled,
314-
viewModeEnabled: !editModeEnabled,
315308
UIOptions: {
316309
canvasActions: {
317310
export: {
@@ -336,12 +329,6 @@ const WhiteboardRtDialog = <Whiteboard extends WhiteboardRtWithContent>({
336329
});
337330
},
338331
}}
339-
events={{
340-
onCollaborationEnabledChange: enabled => {
341-
setCollaborationEnabled(enabled);
342-
setCollaborationStoppedNoticeOpen(!enabled);
343-
},
344-
}}
345332
/>
346333
)}
347334
{state?.loadingWhiteboardValue && <Loading text="Loading whiteboard..." />}
@@ -358,22 +345,6 @@ const WhiteboardRtDialog = <Whiteboard extends WhiteboardRtWithContent>({
358345
)}
359346
</Formik>
360347
</Dialog>
361-
<Dialog open={collaborationStoppedNoticeOpen} onClose={() => setCollaborationStoppedNoticeOpen(false)}>
362-
<DialogHeader title={t('pages.whiteboard.whiteboardDisconnected.title')} />
363-
<DialogContent>
364-
<WrapperMarkdown>{t('pages.whiteboard.whiteboardDisconnected.message')}</WrapperMarkdown>
365-
{lastSavedDate && (
366-
<Text>
367-
{t('pages.whiteboard.whiteboardDisconnected.lastSaved', {
368-
lastSaved: formatTimeElapsed(lastSavedDate, t),
369-
})}
370-
</Text>
371-
)}
372-
</DialogContent>
373-
<DialogActions>
374-
<Button onClick={() => setCollaborationStoppedNoticeOpen(false)}>{t('buttons.ok')}</Button>
375-
</DialogActions>
376-
</Dialog>
377348
</>
378349
);
379350
};

src/domain/common/whiteboard/excalidraw/CollaborativeExcalidrawWrapper.tsx

+94-45
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,18 @@ import React, { Ref, useCallback, useEffect, useMemo, useRef, useState } from 'r
77
import { useCombinedRefs } from '../../../shared/utils/useCombinedRefs';
88
import EmptyWhiteboard from '../EmptyWhiteboard';
99
import { ExcalidrawElement } from '@alkemio/excalidraw/types/element/types';
10-
import { CollabAPI } from './collab/Collab';
1110
import { useUserContext } from '../../../community/user';
1211
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';
1422

1523
const useActorWhiteboardStyles = makeStyles(theme => ({
1624
container: {
@@ -30,6 +38,7 @@ const useActorWhiteboardStyles = makeStyles(theme => ({
3038
export interface WhiteboardWhiteboardEntities {
3139
whiteboard: { id?: string; content: string } | undefined;
3240
filesManager: WhiteboardFilesManager;
41+
lastSavedDate: Date | undefined;
3342
}
3443

3544
export interface WhiteboardWhiteboardActions {
@@ -38,32 +47,22 @@ export interface WhiteboardWhiteboardActions {
3847
onSavedToDatabase?: () => void;
3948
}
4049

41-
export interface WhiteboardWhiteboardEvents {
42-
onCollaborationEnabledChange?: (collaborationEnabled: boolean) => void;
43-
}
50+
export interface WhiteboardWhiteboardEvents {}
4451

45-
export interface WhiteboardWhiteboardOptions extends ExcalidrawProps {
46-
collaborationEnabled: boolean;
47-
}
52+
export interface WhiteboardWhiteboardOptions extends ExcalidrawProps {}
4853

4954
export interface WhiteboardWhiteboardProps {
5055
entities: WhiteboardWhiteboardEntities;
5156
options: WhiteboardWhiteboardOptions;
5257
actions: WhiteboardWhiteboardActions;
53-
events: WhiteboardWhiteboardEvents;
58+
events?: WhiteboardWhiteboardEvents;
5459
collabApiRef?: Ref<CollabAPI | null>;
5560
}
5661

5762
const WINDOW_SCROLL_HANDLER_DEBOUNCE_INTERVAL = 100;
5863

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;
6766

6867
const combinedCollabApiRef = useCombinedRefs<CollabAPI | null>(null, collabApiRef);
6968

@@ -116,11 +115,11 @@ const CollaborativeExcalidrawWrapper = ({
116115
[]
117116
);
118117

119-
const { UIOptions: externalUIOptions, collaborationEnabled, ...restOptions } = options;
118+
const { UIOptions: externalUIOptions, ...restOptions } = options;
120119

121120
const mergedUIOptions = useMemo(() => merge(UIOptions, externalUIOptions), [UIOptions, externalUIOptions]);
122121

123-
const [collabApi, initializeCollab] = useCollab({
122+
const [collabApi, initializeCollab, { connecting, collaborating }] = useCollab({
124123
username,
125124
onSavedToDatabase: actions.onSavedToDatabase,
126125
filesManager,
@@ -138,11 +137,10 @@ const CollaborativeExcalidrawWrapper = ({
138137
return { success: false, errors: ['ExcalidrawAPI not yet ready'] };
139138
},
140139
onCloseConnection: () => {
141-
events.onCollaborationEnabledChange?.(false);
140+
setCollaborationStoppedNoticeOpen(true);
142141
},
143142
onInitialize: collabApi => {
144143
combinedCollabApiRef.current = collabApi;
145-
events.onCollaborationEnabledChange?.(true);
146144
},
147145
});
148146

@@ -153,14 +151,26 @@ const CollaborativeExcalidrawWrapper = ({
153151

154152
const [excalidrawApi, setExcalidrawApi] = useState<ExcalidrawImperativeAPI | null>(null);
155153

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+
156166
useEffect(() => {
157-
if (excalidrawApi && whiteboard?.id) {
167+
if (excalidrawApi && whiteboard?.id && collaborationStartTime !== null) {
158168
return initializeCollab({
159169
excalidrawApi,
160170
roomId: whiteboard.id,
161171
});
162172
}
163-
}, [excalidrawApi, whiteboard?.id]);
173+
}, [excalidrawApi, whiteboard?.id, collaborationStartTime]);
164174

165175
const handleInitializeApi = useCallback(
166176
(excalidrawApi: ExcalidrawImperativeAPI) => {
@@ -170,29 +180,68 @@ const CollaborativeExcalidrawWrapper = ({
170180
[actions.onInitApi]
171181
);
172182

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+
173200
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+
</>
196245
);
197246
};
198247

0 commit comments

Comments
 (0)