@100mslive/roomkit-react 0.1.15 → 0.1.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
  }