@100mslive/roomkit-react 0.1.15 → 0.1.16

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. package/dist/{HLSView-MXBOUQBG.js → HLSView-EMUOLCTM.js} +2 -2
  2. package/dist/Prebuilt/common/PeersSorter.d.ts +1 -0
  3. package/dist/Prebuilt/common/constants.d.ts +7 -4
  4. package/dist/Prebuilt/common/hooks.d.ts +1 -0
  5. package/dist/Prebuilt/components/Footer/ParticipantList.d.ts +17 -0
  6. package/dist/Prebuilt/components/Footer/RoleAccordion.d.ts +3 -2
  7. package/dist/Prebuilt/components/Footer/WhiteboardToggle.d.ts +2 -0
  8. package/dist/Prebuilt/components/Notifications/HandRaisedNotifications.d.ts +1 -0
  9. package/dist/Prebuilt/components/PreviousRoleInMetadata.d.ts +1 -0
  10. package/dist/Prebuilt/components/RemoveParticipant.d.ts +5 -0
  11. package/dist/Prebuilt/components/hooks/useCloseScreenshareWhiteboard.d.ts +4 -0
  12. package/dist/Prebuilt/layouts/WhiteboardView.d.ts +2 -0
  13. package/dist/{chunk-HEOH5H43.js → chunk-ZYR4B4KQ.js} +1886 -7116
  14. package/dist/chunk-ZYR4B4KQ.js.map +7 -0
  15. package/dist/index.cjs.js +2477 -7662
  16. package/dist/index.cjs.js.map +4 -4
  17. package/dist/index.js +1 -1
  18. package/dist/meta.cjs.json +438 -161
  19. package/dist/meta.esbuild.json +443 -166
  20. package/package.json +7 -7
  21. package/src/Prebuilt/AppStateContext.tsx +1 -1
  22. package/src/Prebuilt/common/PeersSorter.ts +12 -5
  23. package/src/Prebuilt/common/constants.ts +5 -6
  24. package/src/Prebuilt/common/hooks.ts +16 -0
  25. package/src/Prebuilt/common/utils.js +5 -6
  26. package/src/Prebuilt/components/AppData/AppData.tsx +1 -16
  27. package/src/Prebuilt/components/Chat/Chat.jsx +7 -30
  28. package/src/Prebuilt/components/Chat/ChatBody.jsx +107 -66
  29. package/src/Prebuilt/components/Chat/ChatFooter.tsx +21 -12
  30. package/src/Prebuilt/components/Chat/ChatSelector.tsx +25 -25
  31. package/src/Prebuilt/components/Chat/ChatSelectorContainer.tsx +15 -16
  32. package/src/Prebuilt/components/Chat/PinnedMessage.tsx +7 -2
  33. package/src/Prebuilt/components/ConferenceScreen.tsx +2 -0
  34. package/src/Prebuilt/components/Footer/ChatToggle.tsx +30 -7
  35. package/src/Prebuilt/components/Footer/Footer.tsx +2 -1
  36. package/src/Prebuilt/components/Footer/PaginatedParticipants.tsx +0 -1
  37. package/src/Prebuilt/components/Footer/{ParticipantList.jsx → ParticipantList.tsx} +169 -127
  38. package/src/Prebuilt/components/Footer/RoleAccordion.tsx +23 -13
  39. package/src/Prebuilt/components/Footer/WhiteboardToggle.tsx +34 -0
  40. package/src/Prebuilt/components/Notifications/HandRaisedNotifications.tsx +35 -0
  41. package/src/Prebuilt/components/Notifications/Notifications.tsx +14 -12
  42. package/src/Prebuilt/components/Notifications/PeerNotifications.tsx +7 -2
  43. package/src/Prebuilt/components/Polls/Voting/QuestionCard.jsx +10 -2
  44. package/src/Prebuilt/components/PreviousRoleInMetadata.tsx +21 -0
  45. package/src/Prebuilt/components/RemoveParticipant.tsx +35 -0
  46. package/src/Prebuilt/components/RoleChangeModal.jsx +1 -1
  47. package/src/Prebuilt/components/SidePaneTabs.tsx +0 -1
  48. package/src/Prebuilt/components/TileMenu/TileMenuContent.tsx +1 -1
  49. package/src/Prebuilt/components/Toast/ToastConfig.jsx +15 -3
  50. package/src/Prebuilt/components/VideoLayouts/GridLayout.tsx +5 -2
  51. package/src/Prebuilt/components/hooks/useCloseScreenshareWhiteboard.tsx +24 -0
  52. package/src/Prebuilt/layouts/VideoStreamingSection.tsx +20 -3
  53. package/src/Prebuilt/layouts/WhiteboardView.tsx +66 -0
  54. package/dist/chunk-HEOH5H43.js.map +0 -7
  55. package/src/Prebuilt/components/AppData/useAppLayout.js +0 -6
  56. package/src/Prebuilt/components/init/initUtils.js +0 -67
  57. /package/dist/{HLSView-MXBOUQBG.js.map → HLSView-EMUOLCTM.js.map} +0 -0
@@ -1,6 +1,8 @@
1
1
  import React, { Fragment, useCallback, useState } from 'react';
2
2
  import { useDebounce, useMedia } from 'react-use';
3
3
  import {
4
+ HMSPeer,
5
+ HMSRoleName,
4
6
  selectHandRaisedPeers,
5
7
  selectHasPeerHandRaised,
6
8
  selectIsLargeRoom,
@@ -12,39 +14,45 @@ import {
12
14
  useHMSActions,
13
15
  useHMSStore,
14
16
  } from '@100mslive/react-sdk';
15
- import {
16
- ChangeRoleIcon,
17
- HandIcon,
18
- MicOffIcon,
19
- PeopleIcon,
20
- PeopleRemoveIcon,
21
- SearchIcon,
22
- VerticalMenuIcon,
23
- } from '@100mslive/react-icons';
17
+ import { ChangeRoleIcon, HandIcon, MicOffIcon, PeopleIcon, SearchIcon, VerticalMenuIcon } from '@100mslive/react-icons';
24
18
  import { Accordion, Box, config as cssConfig, Dropdown, Flex, Input, Text, textEllipsis } from '../../..';
19
+ // @ts-ignore: No implicit Any
25
20
  import IconButton from '../../IconButton';
26
21
  import { ConnectionIndicator } from '../Connection/ConnectionIndicator';
27
- import { ToastManager } from '../Toast/ToastManager';
22
+ import { RemoveParticipant } from '../RemoveParticipant';
28
23
  import { RoleAccordion } from './RoleAccordion';
29
- import { useRoomLayoutConferencingScreen } from '../../provider/roomLayoutProvider/hooks/useRoomLayoutScreen';
24
+ import {
25
+ ConferencingScreenElements,
26
+ useRoomLayoutConferencingScreen,
27
+ } from '../../provider/roomLayoutProvider/hooks/useRoomLayoutScreen';
28
+ // @ts-ignore: No implicit Any
30
29
  import { useIsSidepaneTypeOpen, useSidepaneToggle } from '../AppData/useSidepane';
31
30
  import { useParticipants } from '../../common/hooks';
31
+ // @ts-ignore: No implicit Any
32
32
  import { getFormattedCount } from '../../common/utils';
33
33
  import { SIDE_PANE_OPTIONS } from '../../common/constants';
34
34
 
35
- export const ParticipantList = ({ offStageRoles = [], onActive }) => {
36
- const [filter, setFilter] = useState();
35
+ export const ParticipantList = ({
36
+ offStageRoles = [],
37
+ onActive,
38
+ }: {
39
+ offStageRoles: HMSRoleName[];
40
+ onActive: (role: string) => void;
41
+ }) => {
42
+ const [filter, setFilter] = useState<{ search?: string } | undefined>();
37
43
  const { participants, isConnected, peerCount } = useParticipants(filter);
38
44
  const isLargeRoom = useHMSStore(selectIsLargeRoom);
39
- const peersOrderedByRoles = {};
45
+ const peersOrderedByRoles: Record<string, HMSPeer[]> = {};
40
46
 
41
47
  const handRaisedPeers = useHMSStore(selectHandRaisedPeers);
42
48
 
43
49
  participants.forEach(participant => {
44
- if (peersOrderedByRoles[participant.roleName] === undefined) {
45
- peersOrderedByRoles[participant.roleName] = [];
50
+ if (participant.roleName) {
51
+ if (peersOrderedByRoles[participant.roleName] === undefined) {
52
+ peersOrderedByRoles[participant.roleName] = [];
53
+ }
54
+ peersOrderedByRoles[participant.roleName].push(participant);
46
55
  }
47
- peersOrderedByRoles[participant.roleName].push(participant);
48
56
  });
49
57
 
50
58
  // prefill off_stage roles of large rooms to load more peers
@@ -56,7 +64,7 @@ export const ParticipantList = ({ offStageRoles = [], onActive }) => {
56
64
  });
57
65
  }
58
66
 
59
- const onSearch = useCallback(value => {
67
+ const onSearch = useCallback((value: string) => {
60
68
  setFilter(filterValue => {
61
69
  if (!filterValue) {
62
70
  filterValue = {};
@@ -71,22 +79,34 @@ export const ParticipantList = ({ offStageRoles = [], onActive }) => {
71
79
 
72
80
  return (
73
81
  <Fragment>
74
- <Flex direction="column" css={{ size: '100%', gap: '$4' }}>
82
+ <Flex
83
+ direction="column"
84
+ css={{
85
+ size: '100%',
86
+ gap: '$4',
87
+ }}
88
+ >
75
89
  {!filter?.search && participants.length === 0 ? null : <ParticipantSearch onSearch={onSearch} inSidePane />}
76
- {participants.length === 0 ? (
77
- <Flex align="center" justify="center" css={{ w: '100%', p: '$8 0' }}>
78
- <Text variant="sm">{!filter ? 'No participants' : 'No matching participants'}</Text>
79
- </Flex>
80
- ) : null}
81
90
  <VirtualizedParticipants
82
91
  peersOrderedByRoles={peersOrderedByRoles}
83
92
  handRaisedList={handRaisedPeers}
84
- isConnected={isConnected}
93
+ isConnected={!!isConnected}
85
94
  filter={filter}
86
95
  offStageRoles={offStageRoles}
87
96
  isLargeRoom={isLargeRoom}
88
97
  onActive={onActive}
89
- />
98
+ >
99
+ {participants.length === 0 ? (
100
+ <Flex
101
+ align="center"
102
+ justify="center"
103
+ className="emptyParticipants"
104
+ css={{ w: '100%', p: '$8 0', display: 'none' }}
105
+ >
106
+ <Text variant="sm">{!filter ? 'No participants' : 'No matching participants'}</Text>
107
+ </Flex>
108
+ ) : null}
109
+ </VirtualizedParticipants>
90
110
  </Flex>
91
111
  </Fragment>
92
112
  );
@@ -123,6 +143,44 @@ export const ParticipantCount = () => {
123
143
  );
124
144
  };
125
145
 
146
+ export const Participant = ({
147
+ peer,
148
+ isConnected,
149
+ style,
150
+ }: {
151
+ peer: HMSPeer;
152
+ isConnected: boolean;
153
+ style: React.CSSProperties;
154
+ }) => {
155
+ const localPeerId = useHMSStore(selectLocalPeerID);
156
+ return (
157
+ <Flex
158
+ key={peer.id}
159
+ css={{
160
+ w: '100%',
161
+ p: '$4 $8',
162
+ pr: '$6',
163
+ h: '$16',
164
+ '&:hover .participant_item': { display: 'flex' },
165
+ }}
166
+ align="center"
167
+ justify="between"
168
+ data-testid={'participant_' + peer.name}
169
+ style={style}
170
+ >
171
+ <Text
172
+ variant="sm"
173
+ css={{ ...textEllipsis('100%'), flex: '1 1 0', mr: '$8', fontWeight: '$semiBold', color: '$on_surface_high' }}
174
+ >
175
+ {peer.name} {localPeerId === peer.id ? '(You)' : ''}
176
+ </Text>
177
+ {isConnected && peer.roleName ? (
178
+ <ParticipantActions peerId={peer.id} isLocal={peer.id === localPeerId} role={peer.roleName} />
179
+ ) : null}
180
+ </Flex>
181
+ );
182
+ };
183
+
126
184
  const VirtualizedParticipants = ({
127
185
  peersOrderedByRoles = {},
128
186
  isConnected,
@@ -131,6 +189,16 @@ const VirtualizedParticipants = ({
131
189
  offStageRoles,
132
190
  isLargeRoom,
133
191
  onActive,
192
+ children,
193
+ }: {
194
+ peersOrderedByRoles: Record<string, HMSPeer[]>;
195
+ isConnected: boolean;
196
+ filter: undefined | { search?: string };
197
+ handRaisedList: HMSPeer[];
198
+ offStageRoles: HMSRoleName[];
199
+ isLargeRoom: boolean;
200
+ onActive: (role: string) => void;
201
+ children: React.ReactNode;
134
202
  }) => {
135
203
  return (
136
204
  <Flex
@@ -142,6 +210,9 @@ const VirtualizedParticipants = ({
142
210
  pr: '$10',
143
211
  mr: '-$10',
144
212
  flex: '1 1 0',
213
+ '& > div:empty ~ .emptyParticipants': {
214
+ display: 'flex',
215
+ },
145
216
  }}
146
217
  >
147
218
  <Accordion.Root type={isLargeRoom ? 'single' : 'multiple'} collapsible>
@@ -167,36 +238,7 @@ const VirtualizedParticipants = ({
167
238
  />
168
239
  ))}
169
240
  </Accordion.Root>
170
- </Flex>
171
- );
172
- };
173
-
174
- export const Participant = ({ peer, isConnected, style }) => {
175
- const localPeerId = useHMSStore(selectLocalPeerID);
176
- return (
177
- <Flex
178
- key={peer.id}
179
- css={{
180
- w: '100%',
181
- p: '$4 $8',
182
- pr: '$6',
183
- h: '$16',
184
- '&:hover .participant_item': { display: 'flex' },
185
- }}
186
- align="center"
187
- justify="between"
188
- data-testid={'participant_' + peer.name}
189
- style={style}
190
- >
191
- <Text
192
- variant="sm"
193
- css={{ ...textEllipsis('100%'), flex: '1 1 0', mr: '$8', fontWeight: '$semiBold', color: '$on_surface_high' }}
194
- >
195
- {peer.name} {localPeerId === peer.id ? '(You)' : ''}
196
- </Text>
197
- {isConnected ? (
198
- <ParticipantActions peerId={peer.id} isLocal={peer.id === localPeerId} role={peer.roleName} />
199
- ) : null}
241
+ {children}
200
242
  </Flex>
201
243
  );
202
244
  };
@@ -204,78 +246,86 @@ export const Participant = ({ peer, isConnected, style }) => {
204
246
  /**
205
247
  * shows settings to change for a participant like changing their role
206
248
  */
207
- const ParticipantActions = React.memo(({ peerId, role, isLocal }) => {
208
- const isHandRaised = useHMSStore(selectHasPeerHandRaised(peerId));
209
- const canChangeRole = useHMSStore(selectPermissions)?.changeRole;
210
- const canRemoveOthers = useHMSStore(selectPermissions)?.removeOthers;
211
- const { elements } = useRoomLayoutConferencingScreen();
212
- const { on_stage_exp } = elements || {};
213
- const shouldShowMoreActions = (on_stage_exp && canChangeRole) || canRemoveOthers;
214
- const isAudioMuted = !useHMSStore(selectIsPeerAudioEnabled(peerId));
249
+ const ParticipantActions = React.memo(
250
+ ({ peerId, role, isLocal }: { peerId: string; role: string; isLocal: boolean }) => {
251
+ const isHandRaised = useHMSStore(selectHasPeerHandRaised(peerId));
252
+ const canChangeRole = useHMSStore(selectPermissions)?.changeRole;
253
+ const canRemoveOthers = useHMSStore(selectPermissions)?.removeOthers;
254
+ const { elements } = useRoomLayoutConferencingScreen();
255
+ const { on_stage_exp } = elements || {};
256
+ const shouldShowMoreActions = (on_stage_exp && canChangeRole) || canRemoveOthers;
257
+ const isAudioMuted = !useHMSStore(selectIsPeerAudioEnabled(peerId));
215
258
 
216
- return (
217
- <Flex
218
- align="center"
219
- css={{
220
- flexShrink: 0,
221
- gap: '$8',
222
- }}
223
- >
224
- <ConnectionIndicator peerId={peerId} />
225
- {isHandRaised && (
226
- <Flex
227
- align="center"
228
- justify="center"
229
- css={{ p: '$1', c: '$on_surface_high', bg: '$surface_bright', borderRadius: '$round' }}
230
- >
231
- <HandIcon height={19} width={19} />
232
- </Flex>
233
- )}
234
- {isAudioMuted ? (
235
- <Flex
236
- align="center"
237
- justify="center"
238
- css={{ p: '$2', c: '$on_surface_high', bg: '$surface_bright', borderRadius: '$round' }}
239
- >
240
- <MicOffIcon height={19} width={19} />
241
- </Flex>
242
- ) : null}
259
+ return (
260
+ <Flex
261
+ align="center"
262
+ css={{
263
+ flexShrink: 0,
264
+ gap: '$8',
265
+ }}
266
+ >
267
+ <ConnectionIndicator peerId={peerId} />
268
+ {isHandRaised && (
269
+ <Flex
270
+ align="center"
271
+ justify="center"
272
+ css={{ p: '$1', c: '$on_surface_high', bg: '$surface_bright', borderRadius: '$round' }}
273
+ >
274
+ <HandIcon height={19} width={19} />
275
+ </Flex>
276
+ )}
277
+ {isAudioMuted ? (
278
+ <Flex
279
+ align="center"
280
+ justify="center"
281
+ css={{ p: '$2', c: '$on_surface_high', bg: '$surface_bright', borderRadius: '$round' }}
282
+ >
283
+ <MicOffIcon height={19} width={19} />
284
+ </Flex>
285
+ ) : null}
243
286
 
244
- {shouldShowMoreActions && !isLocal ? (
245
- <ParticipantMoreActions
246
- peerId={peerId}
247
- role={role}
248
- elements={elements}
249
- canChangeRole={canChangeRole}
250
- canRemoveOthers={canRemoveOthers}
251
- />
252
- ) : null}
253
- </Flex>
254
- );
255
- });
287
+ {shouldShowMoreActions && !isLocal ? (
288
+ <ParticipantMoreActions peerId={peerId} role={role} elements={elements} canChangeRole={!!canChangeRole} />
289
+ ) : null}
290
+ </Flex>
291
+ );
292
+ },
293
+ );
256
294
 
257
- const ParticipantMoreActions = ({ peerId, role, elements, canChangeRole, canRemoveOthers }) => {
295
+ const ParticipantMoreActions = ({
296
+ peerId,
297
+ role,
298
+ elements,
299
+ canChangeRole,
300
+ }: {
301
+ peerId: string;
302
+ role: string;
303
+ canChangeRole: boolean;
304
+ elements: ConferencingScreenElements;
305
+ }) => {
258
306
  const hmsActions = useHMSActions();
259
307
  const {
260
308
  bring_to_stage_label,
261
309
  remove_from_stage_label,
262
310
  on_stage_role,
263
311
  off_stage_roles = [],
312
+ skip_preview_for_role_change = false,
264
313
  } = elements.on_stage_exp || {};
265
314
  const isInStage = role === on_stage_role;
266
315
  const shouldShowStageRoleChange =
267
316
  canChangeRole &&
268
317
  ((isInStage && remove_from_stage_label) || (off_stage_roles?.includes(role) && bring_to_stage_label));
269
318
  const prevRole = useHMSStore(selectPeerMetadata(peerId))?.prevRole;
270
- const localPeerId = useHMSStore(selectLocalPeerID);
271
- const isLocal = localPeerId === peerId;
272
319
  const [open, setOpen] = useState(false);
273
320
 
274
321
  const handleStageAction = async () => {
275
322
  if (isInStage) {
276
323
  prevRole && hmsActions.changeRoleOfPeer(peerId, prevRole, true);
277
- } else {
278
- await hmsActions.changeRoleOfPeer(peerId, on_stage_role);
324
+ } else if (on_stage_role) {
325
+ await hmsActions.changeRoleOfPeer(peerId, on_stage_role, skip_preview_for_role_change);
326
+ if (skip_preview_for_role_change) {
327
+ await hmsActions.lowerRemotePeerHand(peerId);
328
+ }
279
329
  }
280
330
  setOpen(false);
281
331
  };
@@ -315,30 +365,22 @@ const ParticipantMoreActions = ({ peerId, role, elements, canChangeRole, canRemo
315
365
  </Dropdown.Item>
316
366
  ) : null}
317
367
 
318
- {!isLocal && canRemoveOthers && (
319
- <Dropdown.Item
320
- css={{ color: '$alert_error_default', bg: '$surface_default' }}
321
- onClick={async () => {
322
- try {
323
- await hmsActions.removePeer(peerId, '');
324
- } catch (error) {
325
- ToastManager.addToast({ title: error.message, variant: 'error' });
326
- }
327
- }}
328
- >
329
- <PeopleRemoveIcon />
330
- <Text variant="sm" css={{ ml: '$4', color: 'inherit', fontWeight: '$semiBold' }}>
331
- Remove Participant
332
- </Text>
333
- </Dropdown.Item>
334
- )}
368
+ <RemoveParticipant peerId={peerId} />
335
369
  </Dropdown.Content>
336
370
  </Dropdown.Portal>
337
371
  </Dropdown.Root>
338
372
  );
339
373
  };
340
374
 
341
- export const ParticipantSearch = ({ onSearch, placeholder, inSidePane = false }) => {
375
+ export const ParticipantSearch = ({
376
+ onSearch,
377
+ placeholder = 'Search for participants',
378
+ inSidePane = false,
379
+ }: {
380
+ inSidePane?: boolean;
381
+ placeholder?: string;
382
+ onSearch: (val: string) => void;
383
+ }) => {
342
384
  const [value, setValue] = React.useState('');
343
385
  const isMobile = useMedia(cssConfig.media.md);
344
386
 
@@ -364,7 +406,7 @@ export const ParticipantSearch = ({ onSearch, placeholder, inSidePane = false })
364
406
  <SearchIcon style={{ position: 'absolute', left: '0.5rem' }} />
365
407
  <Input
366
408
  type="text"
367
- placeholder={placeholder || 'Search for participants'}
409
+ placeholder={placeholder}
368
410
  css={{ w: '100%', p: '$6', pl: '$14', bg: inSidePane ? '$surface_default' : '$surface_dim' }}
369
411
  value={value}
370
412
  onKeyDown={event => {
@@ -6,7 +6,6 @@ import { ChevronRightIcon } from '@100mslive/react-icons';
6
6
  import { Accordion } from '../../../Accordion';
7
7
  import { Flex } from '../../../Layout';
8
8
  import { Text } from '../../../Text';
9
- // @ts-ignore: No implicit Any
10
9
  import { Participant } from './ParticipantList';
11
10
  import { RoleOptions } from './RoleOptions';
12
11
  // @ts-ignore: No implicit Any
@@ -24,9 +23,18 @@ export function itemKey(index: number, data: ItemData) {
24
23
  return data.peerList[index]?.id;
25
24
  }
26
25
 
27
- export const VirtualizedParticipantItem = React.memo(({ index, data }: { index: number; data: ItemData }) => {
28
- return <Participant key={data.peerList[index].id} peer={data.peerList[index]} isConnected={data.isConnected} />;
29
- });
26
+ export const VirtualizedParticipantItem = React.memo(
27
+ ({ index, data, style }: { index: number; data: ItemData; style: React.CSSProperties }) => {
28
+ return (
29
+ <Participant
30
+ key={data.peerList[index].id}
31
+ peer={data.peerList[index]}
32
+ isConnected={data.isConnected}
33
+ style={style}
34
+ />
35
+ );
36
+ },
37
+ );
30
38
 
31
39
  export const RoleAccordion = ({
32
40
  peerList = [],
@@ -39,15 +47,22 @@ export const RoleAccordion = ({
39
47
  }: ItemData & {
40
48
  roleName: string;
41
49
  isHandRaisedAccordion?: boolean;
42
- filter?: { search: string };
50
+ filter?: { search?: string };
43
51
  offStageRoles: string[];
44
52
  onActive?: (role: string) => void;
45
53
  }) => {
46
54
  const [ref, { width }] = useMeasure<HTMLDivElement>();
47
- const showAcordion = filter?.search ? peerList.some(peer => peer.name.toLowerCase().includes(filter.search)) : true;
48
55
  const isLargeRoom = useHMSStore(selectIsLargeRoom);
49
56
  const { peers, total, loadPeers } = usePaginatedParticipants({ role: roleName, limit: 10 });
50
57
  const isOffStageRole = roleName && offStageRoles.includes(roleName);
58
+ let peersInAccordion = peerList;
59
+ // for large rooms, peer list would be empty
60
+ if (isOffStageRole && isLargeRoom) {
61
+ peersInAccordion = peers;
62
+ if (filter?.search) {
63
+ peersInAccordion = peersInAccordion.filter(peer => peer.name.toLowerCase().includes(filter.search || ''));
64
+ }
65
+ }
51
66
 
52
67
  useEffect(() => {
53
68
  if (!isOffStageRole || !isLargeRoom) {
@@ -60,17 +75,12 @@ export const RoleAccordion = ({
60
75
  return () => clearInterval(interval);
61
76
  }, [isOffStageRole, isLargeRoom]); //eslint-disable-line
62
77
 
63
- if (!showAcordion || (isHandRaisedAccordion && filter?.search) || (peerList.length === 0 && filter?.search)) {
78
+ if (peersInAccordion.length === 0 || (isHandRaisedAccordion && filter?.search)) {
64
79
  return null;
65
80
  }
66
81
 
67
- const peersInAccordion = isOffStageRole && isLargeRoom ? peers : peerList;
68
82
  const height = ROW_HEIGHT * peersInAccordion.length;
69
- const hasNext = total > peersInAccordion.length;
70
-
71
- if (peersInAccordion.length === 0) {
72
- return null;
73
- }
83
+ const hasNext = total > peersInAccordion.length && !filter?.search;
74
84
 
75
85
  return (
76
86
  <Accordion.Item value={roleName} css={{ '&:hover .role_actions': { visibility: 'visible' }, mb: '$8' }} ref={ref}>
@@ -0,0 +1,34 @@
1
+ import React from 'react';
2
+ import { useWhiteboard } from '@100mslive/react-sdk';
3
+ import { PencilDrawIcon } from '@100mslive/react-icons';
4
+ import { Tooltip } from '../../..';
5
+ // @ts-ignore: No implicit Any
6
+ import IconButton from '../../IconButton';
7
+ // @ts-ignore: No implicit Any
8
+ import { ToastManager } from '../Toast/ToastManager';
9
+
10
+ export const WhiteboardToggle = () => {
11
+ const { toggle, open, isOwner } = useWhiteboard();
12
+ if (!toggle) {
13
+ return null;
14
+ }
15
+
16
+ return (
17
+ <Tooltip key="whiteboard" title={`${open ? 'Close' : 'Open'} Whiteboard`}>
18
+ <IconButton
19
+ onClick={async () => {
20
+ try {
21
+ await toggle();
22
+ } catch (error) {
23
+ ToastManager.addToast({ title: (error as Error).message, variant: 'error' });
24
+ }
25
+ }}
26
+ active={!open}
27
+ disabled={open && !isOwner}
28
+ data-testid="whiteboard_btn"
29
+ >
30
+ <PencilDrawIcon />
31
+ </IconButton>
32
+ </Tooltip>
33
+ );
34
+ };
@@ -0,0 +1,35 @@
1
+ import { useEffect } from 'react';
2
+ import {
3
+ HMSNotificationTypes,
4
+ HMSRoomState,
5
+ selectHasPeerHandRaised,
6
+ selectRoomState,
7
+ useHMSNotifications,
8
+ useHMSStore,
9
+ useHMSVanillaStore,
10
+ } from '@100mslive/react-sdk';
11
+ // @ts-ignore: No implicit Any
12
+ import { ToastBatcher } from '../Toast/ToastBatcher';
13
+ import { useRoomLayoutConferencingScreen } from '../../provider/roomLayoutProvider/hooks/useRoomLayoutScreen';
14
+
15
+ export const HandRaisedNotifications = () => {
16
+ const notification = useHMSNotifications(HMSNotificationTypes.HAND_RAISE_CHANGED);
17
+ const roomState = useHMSStore(selectRoomState);
18
+ const vanillaStore = useHMSVanillaStore();
19
+ const { on_stage_exp } = useRoomLayoutConferencingScreen().elements || {};
20
+
21
+ useEffect(() => {
22
+ if (!notification?.data) {
23
+ return;
24
+ }
25
+ if (roomState !== HMSRoomState.Connected || notification.data.isLocal || !on_stage_exp) {
26
+ return;
27
+ }
28
+ const hasPeerHandRaised = vanillaStore.getState(selectHasPeerHandRaised(notification.data.id));
29
+ if (hasPeerHandRaised) {
30
+ ToastBatcher.showToast({ notification, type: 'RAISE_HAND' });
31
+ }
32
+ }, [notification, on_stage_exp, roomState, vanillaStore]);
33
+
34
+ return null;
35
+ };
@@ -4,7 +4,6 @@ import {
4
4
  HMSNotificationTypes,
5
5
  HMSRoleChangeRequest,
6
6
  HMSRoomState,
7
- selectHasPeerHandRaised,
8
7
  selectLocalPeerID,
9
8
  selectPeerNameByID,
10
9
  selectRoomState,
@@ -13,7 +12,8 @@ import {
13
12
  useHMSStore,
14
13
  useHMSVanillaStore,
15
14
  } from '@100mslive/react-sdk';
16
- import { Button } from '../../..';
15
+ import { GroupIcon } from '@100mslive/react-icons';
16
+ import { Box, Button } from '../../..';
17
17
  import { useUpdateRoomLayout } from '../../provider/roomLayoutProvider';
18
18
  // @ts-ignore: No implicit Any
19
19
  import { ToastBatcher } from '../Toast/ToastBatcher';
@@ -21,6 +21,7 @@ import { ToastBatcher } from '../Toast/ToastBatcher';
21
21
  import { ToastManager } from '../Toast/ToastManager';
22
22
  import { AutoplayBlockedModal } from './AutoplayBlockedModal';
23
23
  import { ChatNotifications } from './ChatNotifications';
24
+ import { HandRaisedNotifications } from './HandRaisedNotifications';
24
25
  import { InitErrorModal } from './InitErrorModal';
25
26
  import { PeerNotifications } from './PeerNotifications';
26
27
  import { PermissionErrorModal } from './PermissionErrorModal';
@@ -91,16 +92,6 @@ export function Notifications() {
91
92
  return;
92
93
  }
93
94
  switch (notification.type) {
94
- case HMSNotificationTypes.HAND_RAISE_CHANGED: {
95
- if (roomState !== HMSRoomState.Connected || notification.data.isLocal) {
96
- return;
97
- }
98
- const hasPeerHandRaised = vanillaStore.getState(selectHasPeerHandRaised(notification.data.id));
99
- if (hasPeerHandRaised) {
100
- ToastBatcher.showToast({ notification, type: 'RAISE_HAND' });
101
- }
102
- break;
103
- }
104
95
  case HMSNotificationTypes.METADATA_UPDATED:
105
96
  if (roomState !== HMSRoomState.Connected) {
106
97
  return;
@@ -123,6 +114,16 @@ export function Notifications() {
123
114
  ToastManager.addToast({
124
115
  title: `Error: ${notification.data?.message}`,
125
116
  });
117
+ } else if (notification.data?.message === 'role limit reached') {
118
+ ToastManager.addToast({
119
+ title: 'The room is currently full, try joining later',
120
+ close: true,
121
+ icon: (
122
+ <Box css={{ color: '$alert_error_default' }}>
123
+ <GroupIcon />
124
+ </Box>
125
+ ),
126
+ });
126
127
  } else {
127
128
  ToastManager.addToast({
128
129
  title:
@@ -219,6 +220,7 @@ export function Notifications() {
219
220
  <PermissionErrorModal />
220
221
  <InitErrorModal />
221
222
  <ChatNotifications />
223
+ <HandRaisedNotifications />
222
224
  </>
223
225
  );
224
226
  }
@@ -3,9 +3,9 @@ import { HMSNotificationTypes, useHMSNotifications } from '@100mslive/react-sdk'
3
3
  // @ts-ignore: No implicit Any
4
4
  import { ToastBatcher } from '../Toast/ToastBatcher';
5
5
  // @ts-ignore: No implicit Any
6
- import { useSubscribedNotifications } from '../AppData/useUISettings';
6
+ import { useSetSubscribedChatSelector, useSubscribedNotifications } from '../AppData/useUISettings';
7
7
  // @ts-ignore: No implicit Any
8
- import { SUBSCRIBED_NOTIFICATIONS } from '../../common/constants';
8
+ import { CHAT_SELECTOR, SUBSCRIBED_NOTIFICATIONS } from '../../common/constants';
9
9
 
10
10
  const notificationTypes = [
11
11
  HMSNotificationTypes.PEER_LIST,
@@ -17,6 +17,8 @@ export const PeerNotifications = () => {
17
17
  const notification = useHMSNotifications(notificationTypes);
18
18
  const isPeerJoinSubscribed = useSubscribedNotifications(SUBSCRIBED_NOTIFICATIONS.PEER_JOINED);
19
19
  const isPeerLeftSubscribed = useSubscribedNotifications(SUBSCRIBED_NOTIFICATIONS.PEER_LEFT);
20
+ const [selectedPeer, setPeerSelector] = useSetSubscribedChatSelector(CHAT_SELECTOR.PEER);
21
+
20
22
  useEffect(() => {
21
23
  if (!notification?.data) {
22
24
  return;
@@ -35,6 +37,9 @@ export const PeerNotifications = () => {
35
37
  }
36
38
  break;
37
39
  case HMSNotificationTypes.PEER_LEFT:
40
+ if (selectedPeer.id === notification.data.id) {
41
+ setPeerSelector({});
42
+ }
38
43
  if (!isPeerLeftSubscribed) {
39
44
  return;
40
45
  }