@100mslive/roomkit-react 0.1.20-alpha.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- package/README.md +27 -2
- package/dist/{HLSView-BWR4T6PI.js → HLSView-ULB4DC6B.js} +2 -2
- package/dist/Input/Input.d.ts +3 -3
- package/dist/Prebuilt/components/Chat/ChatActions.d.ts +2 -1
- package/dist/Prebuilt/components/Polls/CreateQuestions/DeleteQuestionModal.d.ts +6 -0
- package/dist/Prebuilt/components/Polls/CreateQuestions/QuestionForm.d.ts +22 -0
- package/dist/Prebuilt/components/Polls/CreateQuestions/SavedQuestion.d.ts +11 -0
- package/dist/Prebuilt/components/Polls/Voting/StandardVoting.d.ts +5 -0
- package/dist/Prebuilt/components/Polls/Voting/TimedVoting.d.ts +5 -0
- package/dist/Prebuilt/components/Polls/Voting/Voting.d.ts +5 -0
- package/dist/Prebuilt/components/Polls/common/Line.d.ts +2 -0
- package/dist/Prebuilt/components/Polls/common/OptionInputWithDelete.d.ts +8 -0
- package/dist/Prebuilt/components/Polls/common/StatusIndicator.d.ts +4 -0
- package/dist/Prebuilt/components/Polls/common/VoteCount.d.ts +4 -0
- package/dist/Prebuilt/components/Polls/common/VoteProgress.d.ts +6 -0
- package/dist/Prebuilt/components/Polls/common/VoterList.d.ts +4 -0
- package/dist/Prebuilt/components/TileMenu/utils.d.ts +5 -0
- package/dist/Prebuilt/components/hooks/usePinnedBy.d.ts +1 -0
- package/dist/Prebuilt/components/hooks/{useSetPinnedMessages.d.ts → usePinnedMessages.d.ts} +6 -1
- package/dist/TextArea/TextArea.d.ts +441 -0
- package/dist/TextArea/index.d.ts +1 -0
- package/dist/Toast/Toast.d.ts +1 -1
- package/dist/{chunk-SYBH2G3R.js → chunk-GVA4I77Z.js} +2802 -2740
- package/dist/chunk-GVA4I77Z.js.map +7 -0
- package/dist/index.cjs.js +3035 -2967
- package/dist/index.cjs.js.map +4 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -1
- package/dist/meta.cjs.json +476 -394
- package/dist/meta.esbuild.json +486 -402
- package/package.json +7 -8
- package/src/Button/Button.tsx +4 -4
- package/src/Input/Input.tsx +1 -1
- package/src/Prebuilt/components/Chat/ChatActions.tsx +25 -8
- package/src/Prebuilt/components/Chat/ChatBody.tsx +64 -21
- package/src/Prebuilt/components/Chat/ChatFooter.tsx +1 -0
- package/src/Prebuilt/components/Chat/PinnedMessage.tsx +2 -2
- package/src/Prebuilt/components/Header/AdditionalRoomState.jsx +1 -38
- package/src/Prebuilt/components/Header/StreamActions.tsx +1 -1
- package/src/Prebuilt/components/Polls/CreatePollQuiz/PollsQuizMenu.jsx +11 -1
- package/src/Prebuilt/components/Polls/CreateQuestions/{DeleteQuestionModal.jsx → DeleteQuestionModal.tsx} +9 -1
- package/src/Prebuilt/components/Polls/CreateQuestions/{QuestionForm.jsx → QuestionForm.tsx} +71 -30
- package/src/Prebuilt/components/Polls/CreateQuestions/{SavedQuestion.jsx → SavedQuestion.tsx} +24 -15
- package/src/Prebuilt/components/Polls/Voting/LeaderboardSummary.tsx +1 -1
- package/src/Prebuilt/components/Polls/Voting/QuestionCard.jsx +61 -80
- package/src/Prebuilt/components/Polls/Voting/{StandardVoting.jsx → StandardVoting.tsx} +3 -7
- package/src/Prebuilt/components/Polls/Voting/{TimedVoting.jsx → TimedVoting.tsx} +4 -7
- package/src/Prebuilt/components/Polls/Voting/{Voting.jsx → Voting.tsx} +4 -3
- package/src/Prebuilt/components/Polls/common/Line.tsx +4 -0
- package/src/Prebuilt/components/Polls/common/{OptionInputWithDelete.jsx → OptionInputWithDelete.tsx} +14 -2
- package/src/Prebuilt/components/Polls/common/SingleChoiceOptions.jsx +1 -1
- package/src/Prebuilt/components/Polls/common/{StatusIndicator.jsx → StatusIndicator.tsx} +1 -2
- package/src/Prebuilt/components/Polls/common/{VoteCount.jsx → VoteCount.tsx} +1 -2
- package/src/Prebuilt/components/Polls/common/{VoteProgress.jsx → VoteProgress.tsx} +3 -2
- package/src/Prebuilt/components/Polls/common/{VoterList.jsx → VoterList.tsx} +1 -1
- package/src/Prebuilt/components/TileMenu/TileMenu.jsx +3 -1
- package/src/Prebuilt/components/TileMenu/TileMenuContent.tsx +15 -3
- package/src/Prebuilt/components/TileMenu/utils.ts +7 -0
- package/src/Prebuilt/components/VideoLayouts/GridLayout.tsx +7 -3
- package/src/Prebuilt/components/VideoTile.jsx +2 -4
- package/src/Prebuilt/components/hooks/usePinnedBy.tsx +22 -0
- package/src/Prebuilt/components/hooks/{useSetPinnedMessages.ts → usePinnedMessages.ts} +2 -2
- package/src/Prebuilt/components/hooks/useUnreadPollQuizPresent.tsx +1 -4
- package/src/Prebuilt/layouts/VideoStreamingSection.tsx +0 -1
- package/src/TextArea/TextArea.tsx +30 -0
- package/src/TextArea/index.tsx +1 -0
- package/src/index.ts +1 -0
- package/src/store/StorybookSDK.ts +3 -1
- package/dist/Prebuilt/plugins/whiteboard/ToggleWhiteboard.d.ts +0 -5
- package/dist/chunk-SYBH2G3R.js.map +0 -7
- package/src/Prebuilt/components/Polls/common/Votes.jsx +0 -72
- package/src/Prebuilt/layouts/WhiteboardView.jsx +0 -40
- package/src/Prebuilt/plugins/whiteboard/PusherCommunicationProvider.js +0 -110
- package/src/Prebuilt/plugins/whiteboard/README.md +0 -29
- package/src/Prebuilt/plugins/whiteboard/ToggleWhiteboard.tsx +0 -37
- package/src/Prebuilt/plugins/whiteboard/Whiteboard.css +0 -12
- package/src/Prebuilt/plugins/whiteboard/Whiteboard.jsx +0 -11
- package/src/Prebuilt/plugins/whiteboard/WhiteboardEvents.js +0 -8
- package/src/Prebuilt/plugins/whiteboard/index.js +0 -3
- package/src/Prebuilt/plugins/whiteboard/useMultiplayerState.js +0 -212
- package/src/Prebuilt/plugins/whiteboard/useWhiteboardMetadata.js +0 -47
- /package/dist/{HLSView-BWR4T6PI.js.map → HLSView-ULB4DC6B.js.map} +0 -0
@@ -1,72 +0,0 @@
|
|
1
|
-
import React from 'react';
|
2
|
-
import { Avatar, Box, Flex, Text, Tooltip } from '../../../../';
|
3
|
-
import { VoterList } from './VoterList';
|
4
|
-
|
5
|
-
// Shows number of votes, avatars of voters and list of voters on hover
|
6
|
-
export const Votes = ({ voters }) => {
|
7
|
-
const hiddenVotersCount = voters.length > 2 ? voters.length - 2 : 0;
|
8
|
-
|
9
|
-
return (
|
10
|
-
<Flex align="center" css={{ gap: '$4' }}>
|
11
|
-
<Text variant="sm" css={{ color: '$on_surface_medium' }}>
|
12
|
-
{voters.length}
|
13
|
-
{voters.length && voters.length !== 1 ? 'votes' : 'votes'}
|
14
|
-
</Text>
|
15
|
-
<Tooltip
|
16
|
-
side="bottom"
|
17
|
-
align="start"
|
18
|
-
disabled={hiddenVotersCount === 0}
|
19
|
-
boxCss={{
|
20
|
-
backgroundColor: '$surface_brighter',
|
21
|
-
borderRadius: '$1',
|
22
|
-
p: '$4 $6',
|
23
|
-
top: '$2',
|
24
|
-
zIndex: '20',
|
25
|
-
minWidth: '$44',
|
26
|
-
}}
|
27
|
-
title={<VoterList voters={voters} />}
|
28
|
-
>
|
29
|
-
<Flex align="center">
|
30
|
-
{voters.length
|
31
|
-
? voters.slice(0, 2).map((voter, index) => (
|
32
|
-
<Avatar
|
33
|
-
name={voter}
|
34
|
-
css={{
|
35
|
-
position: 'relative',
|
36
|
-
transform: 'unset',
|
37
|
-
left: `${index * -1}px`,
|
38
|
-
fontSize: '$tiny',
|
39
|
-
size: '$9',
|
40
|
-
p: '$4',
|
41
|
-
zIndex: '5',
|
42
|
-
}}
|
43
|
-
/>
|
44
|
-
))
|
45
|
-
: null}
|
46
|
-
{hiddenVotersCount ? (
|
47
|
-
<Box
|
48
|
-
css={{
|
49
|
-
backgroundColor: '$secondary_default',
|
50
|
-
borderRadius: '$round',
|
51
|
-
position: 'relative',
|
52
|
-
left: '-$2',
|
53
|
-
p: '$2 $3',
|
54
|
-
}}
|
55
|
-
>
|
56
|
-
<Text
|
57
|
-
variant="caption"
|
58
|
-
css={{
|
59
|
-
fontWeight: '$semiBold',
|
60
|
-
color: '$on_surface_high',
|
61
|
-
fontSize: '$tiny',
|
62
|
-
}}
|
63
|
-
>
|
64
|
-
+{hiddenVotersCount}
|
65
|
-
</Text>
|
66
|
-
</Box>
|
67
|
-
) : null}
|
68
|
-
</Flex>
|
69
|
-
</Tooltip>
|
70
|
-
</Flex>
|
71
|
-
);
|
72
|
-
};
|
@@ -1,40 +0,0 @@
|
|
1
|
-
import React from 'react';
|
2
|
-
import { selectPeers, selectRoomID, useHMSStore } from '@100mslive/react-sdk';
|
3
|
-
import { SecondaryTiles } from '../components/SecondaryTiles';
|
4
|
-
import { ProminenceLayout } from '../components/VideoLayouts/ProminenceLayout';
|
5
|
-
import { Box } from '../../Layout';
|
6
|
-
// import { Whiteboard } from '../plugins/whiteboard';
|
7
|
-
|
8
|
-
const Editor = React.memo(() => {
|
9
|
-
return (
|
10
|
-
<Box
|
11
|
-
css={{
|
12
|
-
mx: '$4',
|
13
|
-
flex: '3 1 0',
|
14
|
-
'@lg': {
|
15
|
-
flex: '2 1 0',
|
16
|
-
'& video': {
|
17
|
-
objectFit: 'contain',
|
18
|
-
},
|
19
|
-
},
|
20
|
-
}}
|
21
|
-
>
|
22
|
-
<Box css={{ position: 'relative', width: '100%', height: '100%' }}>{/* <Whiteboard roomId={roomId} /> */}</Box>
|
23
|
-
</Box>
|
24
|
-
);
|
25
|
-
});
|
26
|
-
|
27
|
-
const WhiteboardView = () => {
|
28
|
-
const peers = useHMSStore(selectPeers);
|
29
|
-
const roomId = useHMSStore(selectRoomID);
|
30
|
-
return (
|
31
|
-
<ProminenceLayout.Root>
|
32
|
-
<ProminenceLayout.ProminentSection>
|
33
|
-
<Editor roomId={roomId} />
|
34
|
-
</ProminenceLayout.ProminentSection>
|
35
|
-
<SecondaryTiles peers={peers} />
|
36
|
-
</ProminenceLayout.Root>
|
37
|
-
);
|
38
|
-
};
|
39
|
-
|
40
|
-
export default WhiteboardView;
|
@@ -1,110 +0,0 @@
|
|
1
|
-
// @ts-check
|
2
|
-
import Pusher from 'pusher-js';
|
3
|
-
|
4
|
-
const stringifyWithNull = obj => JSON.stringify(obj, (k, v) => (v === undefined ? null : v));
|
5
|
-
|
6
|
-
/**
|
7
|
-
* On whiteboard close, owner sends current state to remote peers.
|
8
|
-
* Remote peers tear down too quickly(unsubscribing listeners) and are unable to store the last state.
|
9
|
-
*
|
10
|
-
* Hack: To overcome this, attach 2 listeners:
|
11
|
-
* one for storing the message(won't be unsubscribed),
|
12
|
-
* one for calling the actual whiteboard callback(will be unsubscribed on whiteboard close)
|
13
|
-
*
|
14
|
-
* This way the last state is always received and stored
|
15
|
-
*/
|
16
|
-
|
17
|
-
/**
|
18
|
-
* Base class which can be extended to use various realtime communication services.
|
19
|
-
* Methods to broadcast and subscribe to events.
|
20
|
-
*
|
21
|
-
* Stores the last message received/broadcasted to resend when required(when board is ready)
|
22
|
-
*/
|
23
|
-
|
24
|
-
class PusherCommunicationProvider {
|
25
|
-
constructor() {
|
26
|
-
/** @private */
|
27
|
-
this.initialized = false;
|
28
|
-
/** @private */
|
29
|
-
this.lastMessage = {};
|
30
|
-
}
|
31
|
-
|
32
|
-
init = (roomId = '') => {
|
33
|
-
if (this.initialized) {
|
34
|
-
return;
|
35
|
-
}
|
36
|
-
|
37
|
-
/** @private */
|
38
|
-
this.pusher = new Pusher(process.env.REACT_APP_PUSHER_APP_KEY, {
|
39
|
-
cluster: process.env.REACT_APP_PUSHER_CLUSTER || 'ap2',
|
40
|
-
authEndpoint: process.env.REACT_APP_PUSHER_AUTHENDPOINT,
|
41
|
-
});
|
42
|
-
|
43
|
-
// Pusher.default.logToConsole = true;
|
44
|
-
|
45
|
-
/** @private */
|
46
|
-
this.channel = this.pusher.subscribe(`private-${roomId}`);
|
47
|
-
|
48
|
-
/**
|
49
|
-
* When events(peer-join) are sent too early before subscribing to a channel,
|
50
|
-
* resend last event after subscription has succeeded.
|
51
|
-
*/
|
52
|
-
this.channel.bind('pusher:subscription_succeeded', this.resendLastEvents);
|
53
|
-
|
54
|
-
console.log('Whiteboard initialized communication through Pusher');
|
55
|
-
this.initialized = true;
|
56
|
-
};
|
57
|
-
|
58
|
-
/**
|
59
|
-
* @param {string} eventName
|
60
|
-
* @param {any} message
|
61
|
-
*/
|
62
|
-
storeEvent = (eventName, message) => {
|
63
|
-
this.lastMessage[eventName] = { eventName, ...message };
|
64
|
-
};
|
65
|
-
|
66
|
-
/**
|
67
|
-
* @param {string} eventName
|
68
|
-
* @returns {any}
|
69
|
-
*/
|
70
|
-
getStoredEvent = eventName => {
|
71
|
-
return this.lastMessage[eventName];
|
72
|
-
};
|
73
|
-
|
74
|
-
/**
|
75
|
-
* @param {string} eventName
|
76
|
-
* @param {Object} arg
|
77
|
-
*/
|
78
|
-
broadcastEvent = (eventName, arg = {}) => {
|
79
|
-
this.storeEvent(eventName, arg);
|
80
|
-
|
81
|
-
this.channel?.trigger(`client-${eventName}`, stringifyWithNull({ eventName, ...arg }));
|
82
|
-
};
|
83
|
-
|
84
|
-
/**
|
85
|
-
*
|
86
|
-
* @param {string} eventName
|
87
|
-
* @param {Function} cb
|
88
|
-
*/
|
89
|
-
subscribe = (eventName, cb) => {
|
90
|
-
this.channel?.bind(`client-${eventName}`, message => this.storeEvent(eventName, message));
|
91
|
-
this.channel?.bind(`client-${eventName}`, cb);
|
92
|
-
return () => {
|
93
|
-
this.channel?.unbind(`client-${eventName}`, cb);
|
94
|
-
};
|
95
|
-
};
|
96
|
-
|
97
|
-
/** @private */
|
98
|
-
resendLastEvents = () => {
|
99
|
-
for (const eventName in this.lastMessage) {
|
100
|
-
if (this.lastMessage[eventName]) {
|
101
|
-
this.channel?.trigger(`client-${eventName}`, this.lastMessage[eventName]);
|
102
|
-
}
|
103
|
-
}
|
104
|
-
};
|
105
|
-
}
|
106
|
-
|
107
|
-
/**
|
108
|
-
* @type {PusherCommunicationProvider}
|
109
|
-
*/
|
110
|
-
export const provider = new PusherCommunicationProvider();
|
@@ -1,29 +0,0 @@
|
|
1
|
-
# Collaborative Whiteboard setup
|
2
|
-
|
3
|
-
## Pusher Account
|
4
|
-
|
5
|
-
- Create an Pusher account at https://pusher.com/.
|
6
|
-
- Create a new channels app.
|
7
|
-
- Go to the “App Keys” page for that app(apps list view at https://dashboard.pusher.com/apps), and make a note of your app_id, key, secret and cluster.
|
8
|
-
- Go to the "App Settings" page and enable client events.
|
9
|
-
|
10
|
-
## Whiteboard Server
|
11
|
-
|
12
|
-
- Fork the whiteboard pusher server from https://github.com/100mslive/whiteboard-server and deploy it using your preferred hosting provider.
|
13
|
-
- Add the pusher keys noted earlier to environment variables.
|
14
|
-
|
15
|
-
```
|
16
|
-
APP_CLUSTER="cluster"
|
17
|
-
APP_SECRET="secret"
|
18
|
-
APP_KEY="key"
|
19
|
-
APP_ID="app_id"
|
20
|
-
```
|
21
|
-
|
22
|
-
- The API path is `api/pusher/auth`, say your deployment URL is `whiteboard-server.vercel.app`, the Pusher Auth Endpoint is `https://whiteboard-server.vercel.app/api/pusher/auth`
|
23
|
-
|
24
|
-
## Whiteboard Client
|
25
|
-
|
26
|
-
- Copy the whole folder at `/src/plugins/whiteboard` into your live video conferencing apps using 100ms' SDKs.
|
27
|
-
- Add the pusher app key, auth endpoint and cluster to `REACT_APP_PUSHER_APP_KEY`, `REACT_APP_PUSHER_AUTHENDPOINT`, `REACT_APP_PUSHER_CLUSTER` environment variables.
|
28
|
-
- The `useWhiteboardMetadata` hook returns state such as the whiteboard owner(`whiteboardOwner`) and action to toggle the whiteboard(`toggleWhiteboard`). Refer usage in `ToggleWhiteboard.jsx` - an icon button to toggle the whiteboard based on the active state.
|
29
|
-
- When the whiteboard is active(`whiteboardOwner` from `useWhiteboardMetadata` is not null), render the `Whiteboard` component on your UI to let your users draw on the whiteboard. Refer `mainView.jsx` and `WhiteboardView.jsx`.
|
@@ -1,37 +0,0 @@
|
|
1
|
-
import React from 'react';
|
2
|
-
import { ConferencingScreen } from '@100mslive/types-prebuilt';
|
3
|
-
import { PencilDrawIcon } from '@100mslive/react-icons';
|
4
|
-
import { Tooltip } from '../../../Tooltip';
|
5
|
-
// @ts-ignore: No implicit any
|
6
|
-
import IconButton from '../../IconButton';
|
7
|
-
// @ts-ignore: No implicit any
|
8
|
-
import { useWhiteboardMetadata } from './useWhiteboardMetadata';
|
9
|
-
|
10
|
-
export const ToggleWhiteboard = ({ screenType }: { screenType: keyof ConferencingScreen }) => {
|
11
|
-
const {
|
12
|
-
whiteboardEnabled,
|
13
|
-
whiteboardOwner: whiteboardActive,
|
14
|
-
amIWhiteboardOwner,
|
15
|
-
toggleWhiteboard,
|
16
|
-
} = useWhiteboardMetadata();
|
17
|
-
|
18
|
-
if (!whiteboardEnabled || screenType === 'hls_live_streaming') {
|
19
|
-
return null;
|
20
|
-
}
|
21
|
-
|
22
|
-
return (
|
23
|
-
<Tooltip
|
24
|
-
title={whiteboardActive ? (amIWhiteboardOwner ? `Stop whiteboard` : `Can't stop whiteboard`) : 'Start whiteboard'}
|
25
|
-
key="whiteboard"
|
26
|
-
>
|
27
|
-
<IconButton
|
28
|
-
onClick={toggleWhiteboard}
|
29
|
-
active={!whiteboardActive}
|
30
|
-
disabled={whiteboardActive && !amIWhiteboardOwner}
|
31
|
-
data-testid="white_board_btn"
|
32
|
-
>
|
33
|
-
<PencilDrawIcon />
|
34
|
-
</IconButton>
|
35
|
-
</Tooltip>
|
36
|
-
);
|
37
|
-
};
|
@@ -1,11 +0,0 @@
|
|
1
|
-
import React from 'react';
|
2
|
-
import { Tldraw } from '@tldraw/tldraw';
|
3
|
-
import { useMultiplayerState } from './useMultiplayerState';
|
4
|
-
import './Whiteboard.css';
|
5
|
-
|
6
|
-
export const Whiteboard = React.memo(({ roomId }) => {
|
7
|
-
const events = useMultiplayerState(roomId);
|
8
|
-
return (
|
9
|
-
<Tldraw autofocus disableAssets={true} showSponsorLink={false} showPages={false} showMenu={false} {...events} />
|
10
|
-
);
|
11
|
-
});
|
@@ -1,8 +0,0 @@
|
|
1
|
-
export const WhiteboardEvents = {
|
2
|
-
// To broadcast new changes made in whiteboard
|
3
|
-
STATE_CHANGE: 'state-change',
|
4
|
-
// To broadcast the current whole state of the board by the owner
|
5
|
-
CURRENT_STATE: 'current-state',
|
6
|
-
// For newly joined peers to request current state from owner
|
7
|
-
REQUEST_STATE: 'request-state',
|
8
|
-
};
|
@@ -1,212 +0,0 @@
|
|
1
|
-
// @ts-check
|
2
|
-
import { useCallback, useEffect, useRef, useState } from 'react';
|
3
|
-
import { selectDidIJoinWithin, useHMSStore } from '@100mslive/react-sdk';
|
4
|
-
import { provider as room } from './PusherCommunicationProvider';
|
5
|
-
import { WhiteboardEvents as Events } from './WhiteboardEvents';
|
6
|
-
import { useWhiteboardMetadata } from './useWhiteboardMetadata';
|
7
|
-
|
8
|
-
const useWhiteboardState = () => {
|
9
|
-
const { amIWhiteboardOwner } = useWhiteboardMetadata();
|
10
|
-
/*
|
11
|
-
* LIVE-1470 state need to have some delay after join.
|
12
|
-
* It will initialize pusher room and send request state.
|
13
|
-
*/
|
14
|
-
const shouldRequestState = useHMSStore(selectDidIJoinWithin(2000));
|
15
|
-
|
16
|
-
return { shouldRequestState, amIWhiteboardOwner };
|
17
|
-
};
|
18
|
-
|
19
|
-
/**
|
20
|
-
* Ref: https://github.com/tldraw/tldraw/blob/main/apps/www/hooks/useMultiplayerState.ts
|
21
|
-
*/
|
22
|
-
export function useMultiplayerState(roomId) {
|
23
|
-
const [app, setApp] = useState(null);
|
24
|
-
const [isReady, setIsReady] = useState(false);
|
25
|
-
const { amIWhiteboardOwner, shouldRequestState } = useWhiteboardState();
|
26
|
-
|
27
|
-
/**
|
28
|
-
* Stores current state(shapes, bindings, [assets]) of the whiteboard
|
29
|
-
*/
|
30
|
-
const rLiveShapes = useRef(new Map());
|
31
|
-
const rLiveBindings = useRef(new Map());
|
32
|
-
|
33
|
-
const getCurrentState = useCallback(() => {
|
34
|
-
return {
|
35
|
-
shapes: rLiveShapes.current ? Object.fromEntries(rLiveShapes.current) : {},
|
36
|
-
bindings: rLiveBindings.current ? Object.fromEntries(rLiveBindings.current) : {},
|
37
|
-
};
|
38
|
-
}, []);
|
39
|
-
|
40
|
-
const sendCurrentState = useCallback(() => {
|
41
|
-
if (amIWhiteboardOwner && isReady) {
|
42
|
-
room.broadcastEvent(Events.CURRENT_STATE, getCurrentState());
|
43
|
-
}
|
44
|
-
}, [amIWhiteboardOwner, isReady, getCurrentState]);
|
45
|
-
|
46
|
-
const updateLocalState = useCallback(({ shapes, bindings, merge = true }) => {
|
47
|
-
if (!(shapes && bindings)) {
|
48
|
-
return;
|
49
|
-
}
|
50
|
-
|
51
|
-
if (merge) {
|
52
|
-
const lShapes = rLiveShapes.current;
|
53
|
-
const lBindings = rLiveBindings.current;
|
54
|
-
|
55
|
-
if (!(lShapes && lBindings)) {
|
56
|
-
return;
|
57
|
-
}
|
58
|
-
Object.entries(shapes).forEach(([id, shape]) => {
|
59
|
-
if (!shape) {
|
60
|
-
lShapes.delete(id);
|
61
|
-
} else {
|
62
|
-
lShapes.set(shape.id, shape);
|
63
|
-
}
|
64
|
-
});
|
65
|
-
|
66
|
-
Object.entries(bindings).forEach(([id, binding]) => {
|
67
|
-
if (!binding) {
|
68
|
-
lBindings.delete(id);
|
69
|
-
} else {
|
70
|
-
lBindings.set(binding.id, binding);
|
71
|
-
}
|
72
|
-
});
|
73
|
-
} else {
|
74
|
-
rLiveShapes.current = new Map(Object.entries(shapes));
|
75
|
-
rLiveBindings.current = new Map(Object.entries(bindings));
|
76
|
-
}
|
77
|
-
}, []);
|
78
|
-
|
79
|
-
const applyStateToBoard = useCallback(
|
80
|
-
state => {
|
81
|
-
app === null || app === void 0
|
82
|
-
? void 0
|
83
|
-
: // eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
84
|
-
// @ts-ignore
|
85
|
-
app.replacePageContent(
|
86
|
-
state.shapes,
|
87
|
-
state.bindings,
|
88
|
-
{}, // Object.fromEntries(lAssets.entries())
|
89
|
-
);
|
90
|
-
},
|
91
|
-
[app],
|
92
|
-
);
|
93
|
-
|
94
|
-
const handleChanges = useCallback(
|
95
|
-
state => {
|
96
|
-
if (!state) {
|
97
|
-
return;
|
98
|
-
}
|
99
|
-
|
100
|
-
const { shapes, bindings, eventName } = state;
|
101
|
-
updateLocalState({
|
102
|
-
shapes,
|
103
|
-
bindings,
|
104
|
-
merge: eventName === Events.STATE_CHANGE,
|
105
|
-
});
|
106
|
-
applyStateToBoard(getCurrentState());
|
107
|
-
},
|
108
|
-
[applyStateToBoard, getCurrentState, updateLocalState],
|
109
|
-
);
|
110
|
-
|
111
|
-
const setupInitialState = useCallback(() => {
|
112
|
-
if (!isReady) {
|
113
|
-
return;
|
114
|
-
}
|
115
|
-
|
116
|
-
if (amIWhiteboardOwner) {
|
117
|
-
// On board open, update the document with initial/stored content
|
118
|
-
handleChanges(room.getStoredEvent(Events.CURRENT_STATE));
|
119
|
-
// Send current state to other peers in the room currently
|
120
|
-
sendCurrentState();
|
121
|
-
} else if (shouldRequestState) {
|
122
|
-
/**
|
123
|
-
* Newly joined peers request the owner for current state
|
124
|
-
* and update their boards when they receive it
|
125
|
-
*/
|
126
|
-
room.broadcastEvent(Events.REQUEST_STATE);
|
127
|
-
}
|
128
|
-
}, [isReady, amIWhiteboardOwner, shouldRequestState, handleChanges, sendCurrentState]);
|
129
|
-
|
130
|
-
// Callbacks --------------
|
131
|
-
// Put the state into the window, for debugging.
|
132
|
-
const onMount = useCallback(
|
133
|
-
app => {
|
134
|
-
app.loadRoom(roomId);
|
135
|
-
app.pause(); // Turn off the app's own undo / redo stack
|
136
|
-
// window.app = app;
|
137
|
-
setApp(app);
|
138
|
-
},
|
139
|
-
[roomId],
|
140
|
-
);
|
141
|
-
|
142
|
-
// Update the live shapes when the app's shapes change.
|
143
|
-
const onChangePage = useCallback(
|
144
|
-
(_app, shapes, bindings) => {
|
145
|
-
updateLocalState({ shapes, bindings });
|
146
|
-
room.broadcastEvent(Events.STATE_CHANGE, { shapes, bindings });
|
147
|
-
|
148
|
-
/**
|
149
|
-
* Tldraw thinks that the next update passed to replacePageContent after onChangePage is the own update triggered by onChangePage
|
150
|
-
* and the replacePageContent doesn't have any effect if it is a valid update from remote.
|
151
|
-
*
|
152
|
-
* To overcome this replacePageContent locally onChangePage(not costly - returns from first line).
|
153
|
-
*
|
154
|
-
* Refer: https://github.com/tldraw/tldraw/blob/main/packages/tldraw/src/state/TldrawApp.ts#L684
|
155
|
-
*/
|
156
|
-
applyStateToBoard(getCurrentState());
|
157
|
-
},
|
158
|
-
[updateLocalState, applyStateToBoard, getCurrentState],
|
159
|
-
);
|
160
|
-
|
161
|
-
// Handle presence updates when the user's pointer / selection changes
|
162
|
-
// const onChangePresence = useCallback((app, user) => {
|
163
|
-
// updateMyPresence({ id: app.room?.userId, user });
|
164
|
-
// }, [][updateMyPresence]);
|
165
|
-
|
166
|
-
// Subscriptions and initial setup
|
167
|
-
useEffect(() => {
|
168
|
-
if (!app) {
|
169
|
-
return;
|
170
|
-
}
|
171
|
-
const unsubs = [];
|
172
|
-
|
173
|
-
let stillAlive = true;
|
174
|
-
|
175
|
-
// Setup the document's storage and subscriptions
|
176
|
-
function setupDocument() {
|
177
|
-
// Subscribe to changes
|
178
|
-
if (stillAlive) {
|
179
|
-
unsubs.push(room.subscribe(Events.STATE_CHANGE, handleChanges));
|
180
|
-
unsubs.push(room.subscribe(Events.CURRENT_STATE, handleChanges));
|
181
|
-
|
182
|
-
// On request state(peer join), send whole current state to update the new peer's whiteboard
|
183
|
-
unsubs.push(room.subscribe(Events.REQUEST_STATE, sendCurrentState));
|
184
|
-
|
185
|
-
setIsReady(true);
|
186
|
-
}
|
187
|
-
}
|
188
|
-
|
189
|
-
room.init(roomId);
|
190
|
-
setupDocument();
|
191
|
-
setupInitialState();
|
192
|
-
|
193
|
-
return () => {
|
194
|
-
stillAlive = false;
|
195
|
-
unsubs.forEach(unsub => unsub());
|
196
|
-
};
|
197
|
-
}, [app, roomId, setupInitialState, sendCurrentState, handleChanges]);
|
198
|
-
|
199
|
-
useEffect(() => {
|
200
|
-
// Store last state on closing whitboard so that when the board is reopened the state could be fetched and reapplied
|
201
|
-
const handleUnmount = () => {
|
202
|
-
if (isReady && !shouldRequestState) {
|
203
|
-
console.log('Whiteboard unmount storing', getCurrentState());
|
204
|
-
room.storeEvent(Events.CURRENT_STATE, getCurrentState());
|
205
|
-
}
|
206
|
-
};
|
207
|
-
|
208
|
-
return handleUnmount;
|
209
|
-
}, [isReady, shouldRequestState, getCurrentState]);
|
210
|
-
|
211
|
-
return { onMount, onChangePage };
|
212
|
-
}
|
@@ -1,47 +0,0 @@
|
|
1
|
-
import { useCallback, useEffect, useMemo } from 'react';
|
2
|
-
import { selectLocalPeerID, selectPeerByCondition, useHMSStore } from '@100mslive/react-sdk';
|
3
|
-
import { useMyMetadata } from '../../components/hooks/useMetadata';
|
4
|
-
import { getMetadata } from '../../common/utils';
|
5
|
-
import { FeatureFlags } from '../../services/FeatureFlags';
|
6
|
-
|
7
|
-
const isWhiteboardOwner = peer => {
|
8
|
-
return !!getMetadata(peer?.metadata).whiteboardOwner;
|
9
|
-
};
|
10
|
-
|
11
|
-
export const useWhiteboardMetadata = () => {
|
12
|
-
const localPeerID = useHMSStore(selectLocalPeerID);
|
13
|
-
const { updateMetaData } = useMyMetadata();
|
14
|
-
const whiteboardOwner = useHMSStore(selectPeerByCondition(isWhiteboardOwner));
|
15
|
-
const amIWhiteboardOwner = useMemo(() => localPeerID === whiteboardOwner?.id, [localPeerID, whiteboardOwner]);
|
16
|
-
|
17
|
-
/**
|
18
|
-
* @param enabled {boolean}
|
19
|
-
*/
|
20
|
-
const toggleWhiteboard = useCallback(async () => {
|
21
|
-
if (!process.env.REACT_APP_PUSHER_APP_KEY) {
|
22
|
-
console.error('Cannot start whiteboard - Pusher Key unavailable');
|
23
|
-
}
|
24
|
-
try {
|
25
|
-
if (!whiteboardOwner || amIWhiteboardOwner) {
|
26
|
-
await updateMetaData({ whiteboardOwner: !whiteboardOwner });
|
27
|
-
} else {
|
28
|
-
console.warn('Cannot toggle whiteboard as it was shared by another peer');
|
29
|
-
}
|
30
|
-
} catch (error) {
|
31
|
-
console.error('failed to toggle whiteboard to ', !whiteboardOwner, error);
|
32
|
-
}
|
33
|
-
}, [whiteboardOwner, updateMetaData, amIWhiteboardOwner]);
|
34
|
-
|
35
|
-
useEffect(() => {
|
36
|
-
window.toggleWhiteboard = toggleWhiteboard;
|
37
|
-
}, [toggleWhiteboard]);
|
38
|
-
|
39
|
-
return {
|
40
|
-
/** is whiteboard enabled for the room */
|
41
|
-
whiteboardEnabled: FeatureFlags.enableWhiteboard,
|
42
|
-
/** owner of the active whiteboard, can also be used to check if whiteboard is active */
|
43
|
-
whiteboardOwner,
|
44
|
-
amIWhiteboardOwner,
|
45
|
-
toggleWhiteboard,
|
46
|
-
};
|
47
|
-
};
|
File without changes
|