@100mslive/roomkit-react 0.1.14 → 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 (81) hide show
  1. package/dist/{HLSView-662T7R7H.js → HLSView-EMUOLCTM.js} +128 -39
  2. package/dist/HLSView-EMUOLCTM.js.map +7 -0
  3. package/dist/Prebuilt/common/PeersSorter.d.ts +1 -0
  4. package/dist/Prebuilt/common/constants.d.ts +9 -5
  5. package/dist/Prebuilt/common/hooks.d.ts +1 -0
  6. package/dist/Prebuilt/components/Footer/ParticipantList.d.ts +17 -0
  7. package/dist/Prebuilt/components/Footer/RoleAccordion.d.ts +3 -2
  8. package/dist/Prebuilt/components/Footer/WhiteboardToggle.d.ts +2 -0
  9. package/dist/Prebuilt/components/HMSVideo/HLSCaptionSelector.d.ts +5 -0
  10. package/dist/Prebuilt/components/Notifications/HandRaisedNotifications.d.ts +1 -0
  11. package/dist/Prebuilt/components/Polls/Voting/Leaderboard.d.ts +4 -0
  12. package/dist/Prebuilt/components/Polls/Voting/LeaderboardEntry.d.ts +9 -0
  13. package/dist/Prebuilt/components/Polls/Voting/PeerParticipationSummary.d.ts +5 -0
  14. package/dist/Prebuilt/components/PreviousRoleInMetadata.d.ts +1 -0
  15. package/dist/Prebuilt/components/RemoveParticipant.d.ts +5 -0
  16. package/dist/Prebuilt/components/hooks/useCloseScreenshareWhiteboard.d.ts +4 -0
  17. package/dist/Prebuilt/layouts/WhiteboardView.d.ts +2 -0
  18. package/dist/{chunk-2B7YYNHQ.js → chunk-ZYR4B4KQ.js} +2240 -1767
  19. package/dist/chunk-ZYR4B4KQ.js.map +7 -0
  20. package/dist/index.cjs.js +2805 -2172
  21. package/dist/index.cjs.js.map +4 -4
  22. package/dist/index.js +1 -1
  23. package/dist/meta.cjs.json +739 -177
  24. package/dist/meta.esbuild.json +749 -186
  25. package/package.json +7 -7
  26. package/src/Prebuilt/AppStateContext.tsx +1 -1
  27. package/src/Prebuilt/common/PeersSorter.ts +24 -8
  28. package/src/Prebuilt/common/constants.ts +6 -6
  29. package/src/Prebuilt/common/hooks.ts +16 -0
  30. package/src/Prebuilt/common/utils.js +33 -0
  31. package/src/Prebuilt/components/AppData/AppData.tsx +1 -16
  32. package/src/Prebuilt/components/Chat/Chat.jsx +10 -34
  33. package/src/Prebuilt/components/Chat/ChatBody.jsx +107 -66
  34. package/src/Prebuilt/components/Chat/ChatFooter.tsx +21 -12
  35. package/src/Prebuilt/components/Chat/ChatSelector.tsx +25 -25
  36. package/src/Prebuilt/components/Chat/ChatSelectorContainer.tsx +15 -16
  37. package/src/Prebuilt/components/Chat/PinnedMessage.tsx +7 -2
  38. package/src/Prebuilt/components/ConferenceScreen.tsx +2 -0
  39. package/src/Prebuilt/components/Footer/ChatToggle.tsx +30 -7
  40. package/src/Prebuilt/components/Footer/Footer.tsx +2 -1
  41. package/src/Prebuilt/components/Footer/PaginatedParticipants.tsx +0 -1
  42. package/src/Prebuilt/components/Footer/{ParticipantList.jsx → ParticipantList.tsx} +169 -127
  43. package/src/Prebuilt/components/Footer/RoleAccordion.tsx +23 -13
  44. package/src/Prebuilt/components/Footer/WhiteboardToggle.tsx +34 -0
  45. package/src/Prebuilt/components/HMSVideo/HLSCaptionSelector.tsx +13 -0
  46. package/src/Prebuilt/components/HMSVideo/HMSVideo.jsx +34 -2
  47. package/src/Prebuilt/components/Notifications/HandRaisedNotifications.tsx +35 -0
  48. package/src/Prebuilt/components/Notifications/Notifications.tsx +47 -14
  49. package/src/Prebuilt/components/Notifications/PeerNotifications.tsx +7 -2
  50. package/src/Prebuilt/components/Polls/CreatePollQuiz/PollsQuizMenu.jsx +3 -9
  51. package/src/Prebuilt/components/Polls/CreateQuestions/CreateQuestions.jsx +21 -1
  52. package/src/Prebuilt/components/Polls/CreateQuestions/QuestionForm.jsx +34 -7
  53. package/src/Prebuilt/components/Polls/CreateQuestions/SavedQuestion.jsx +2 -2
  54. package/src/Prebuilt/components/Polls/Polls.tsx +3 -0
  55. package/src/Prebuilt/components/Polls/Voting/Leaderboard.tsx +115 -0
  56. package/src/Prebuilt/components/Polls/Voting/LeaderboardEntry.tsx +63 -0
  57. package/src/Prebuilt/components/Polls/Voting/PeerParticipationSummary.tsx +38 -0
  58. package/src/Prebuilt/components/Polls/Voting/QuestionCard.jsx +33 -8
  59. package/src/Prebuilt/components/Polls/Voting/StandardVoting.jsx +7 -1
  60. package/src/Prebuilt/components/Polls/Voting/Voting.jsx +31 -13
  61. package/src/Prebuilt/components/Polls/common/MultipleChoiceOptions.jsx +33 -21
  62. package/src/Prebuilt/components/Polls/common/SingleChoiceOptions.jsx +47 -35
  63. package/src/Prebuilt/components/Polls/common/StatusIndicator.jsx +2 -22
  64. package/src/Prebuilt/components/Polls/common/VoteCount.jsx +1 -15
  65. package/src/Prebuilt/components/PreviousRoleInMetadata.tsx +21 -0
  66. package/src/Prebuilt/components/RemoveParticipant.tsx +35 -0
  67. package/src/Prebuilt/components/RoleChangeModal.jsx +1 -1
  68. package/src/Prebuilt/components/SidePaneTabs.tsx +0 -1
  69. package/src/Prebuilt/components/TileMenu/TileMenuContent.tsx +1 -1
  70. package/src/Prebuilt/components/Toast/ToastConfig.jsx +15 -3
  71. package/src/Prebuilt/components/VideoLayouts/EqualProminence.tsx +6 -5
  72. package/src/Prebuilt/components/VideoLayouts/GridLayout.tsx +27 -5
  73. package/src/Prebuilt/components/VideoLayouts/ScreenshareLayout.tsx +0 -1
  74. package/src/Prebuilt/components/hooks/useCloseScreenshareWhiteboard.tsx +24 -0
  75. package/src/Prebuilt/layouts/HLSView.jsx +51 -3
  76. package/src/Prebuilt/layouts/VideoStreamingSection.tsx +20 -3
  77. package/src/Prebuilt/layouts/WhiteboardView.tsx +66 -0
  78. package/dist/HLSView-662T7R7H.js.map +0 -7
  79. package/dist/chunk-2B7YYNHQ.js.map +0 -7
  80. package/src/Prebuilt/components/AppData/useAppLayout.js +0 -6
  81. package/src/Prebuilt/components/init/initUtils.js +0 -67
@@ -67,7 +67,6 @@ export const PinnedMessage = ({ clearPinnedMessage }: { clearPinnedMessage: (ind
67
67
  />
68
68
  ) : null}
69
69
  <Flex
70
- title={pinnedMessages[pinnedMessageIndex]?.text}
71
70
  css={{
72
71
  p: '$4',
73
72
  color: '$on_surface_medium',
@@ -95,7 +94,12 @@ export const PinnedMessage = ({ clearPinnedMessage }: { clearPinnedMessage: (ind
95
94
  },
96
95
  }}
97
96
  >
98
- <Text variant="sm" css={{ color: '$on_surface_medium' }} {...swipeHandlers}>
97
+ <Text
98
+ variant="sm"
99
+ css={{ color: '$on_surface_medium' }}
100
+ {...swipeHandlers}
101
+ title={pinnedMessages[pinnedMessageIndex]?.text}
102
+ >
99
103
  <AnnotisedMessage
100
104
  message={`${currentPinnedMessage.slice(
101
105
  0,
@@ -123,6 +127,7 @@ export const PinnedMessage = ({ clearPinnedMessage }: { clearPinnedMessage: (ind
123
127
  '&:hover .hide-on-hover': { display: 'none !important' },
124
128
  '&:hover .show-on-hover': { display: 'block !important' },
125
129
  }}
130
+ title="Unpin Message"
126
131
  >
127
132
  <UnpinIcon className="show-on-hover" style={{ display: 'none' }} height={20} width={20} />
128
133
  <PinIcon className="hide-on-hover" style={{ display: 'block' }} height={20} width={20} />
@@ -21,6 +21,7 @@ import { VideoStreamingSection } from '../layouts/VideoStreamingSection';
21
21
  // @ts-ignore: No implicit Any
22
22
  import FullPageProgress from './FullPageProgress';
23
23
  import { Header } from './Header';
24
+ import { PreviousRoleInMetadata } from './PreviousRoleInMetadata';
24
25
  import {
25
26
  useRoomLayoutConferencingScreen,
26
27
  useRoomLayoutPreviewScreen,
@@ -175,6 +176,7 @@ export const ConferenceScreen = () => {
175
176
  <RoleChangeRequestModal />
176
177
  <HLSFailureModal />
177
178
  <ActivatedPIP />
179
+ <PreviousRoleInMetadata />
178
180
  </Flex>
179
181
  </>
180
182
  );
@@ -1,7 +1,7 @@
1
1
  import React from 'react';
2
2
  import { selectUnreadHMSMessagesCount, useHMSStore } from '@100mslive/react-sdk';
3
- import { ChatIcon, ChatUnreadIcon } from '@100mslive/react-icons';
4
- import { Tooltip } from '../../..';
3
+ import { ChatIcon } from '@100mslive/react-icons';
4
+ import { Box, Flex, Text, Tooltip } from '../../..';
5
5
  // @ts-ignore: No implicit Any
6
6
  import IconButton from '../../IconButton';
7
7
  // @ts-ignore: No implicit Any
@@ -15,10 +15,33 @@ export const ChatToggle = () => {
15
15
  const toggleChat = useSidepaneToggle(SIDE_PANE_OPTIONS.CHAT);
16
16
 
17
17
  return (
18
- <Tooltip key="chat" title={`${isChatOpen ? 'Close' : 'Open'} chat`}>
19
- <IconButton onClick={toggleChat} active={!isChatOpen} data-testid="chat_btn">
20
- {countUnreadMessages === 0 ? <ChatIcon /> : <ChatUnreadIcon data-testid="chat_unread_btn" />}
21
- </IconButton>
22
- </Tooltip>
18
+ <Box
19
+ css={{
20
+ position: 'relative',
21
+ }}
22
+ >
23
+ <Tooltip key="chat" title={`${isChatOpen ? 'Close' : 'Open'} chat`}>
24
+ <IconButton onClick={toggleChat} active={!isChatOpen} data-testid="chat_btn">
25
+ <ChatIcon />
26
+ </IconButton>
27
+ </Tooltip>
28
+ {countUnreadMessages > 0 && (
29
+ <Flex
30
+ css={{
31
+ height: '$8',
32
+ p: '$4 4.5px',
33
+ justifyContent: 'center',
34
+ alignItems: 'center',
35
+ position: 'absolute',
36
+ top: '-$4',
37
+ right: '-$4',
38
+ borderRadius: '$space$14',
39
+ background: '$primary_default',
40
+ }}
41
+ >
42
+ <Text variant="overline">{countUnreadMessages > 99 ? '99+' : countUnreadMessages}</Text>
43
+ </Flex>
44
+ )}
45
+ </Box>
23
46
  );
24
47
  };
@@ -20,9 +20,9 @@ import { ScreenshareToggle } from '../ScreenShareToggle';
20
20
  import { VBToggle } from '../VirtualBackground/VBToggle';
21
21
  // @ts-ignore: No implicit Any
22
22
  import { ChatToggle } from './ChatToggle';
23
- // @ts-ignore: No implicit Any
24
23
  import { ParticipantCount } from './ParticipantList';
25
24
  import { PollsToggle } from './PollsToggle';
25
+ import { WhiteboardToggle } from './WhiteboardToggle';
26
26
  import { ConferencingScreenElements } from '../../provider/roomLayoutProvider/hooks/useRoomLayoutScreen';
27
27
  // @ts-ignore: No implicit Any
28
28
  import { useIsSidepaneTypeOpen, useSidepaneToggle } from '../AppData/useSidepane';
@@ -105,6 +105,7 @@ export const Footer = ({
105
105
  )}
106
106
  </AppFooter.Center>
107
107
  <AppFooter.Right>
108
+ <WhiteboardToggle />
108
109
  {showPolls && <PollsToggle />}
109
110
  {!isMobile && elements?.chat && <ChatToggle />}
110
111
  {elements?.participant_list && <ParticipantCount />}
@@ -8,7 +8,6 @@ import { IconButton } from '../../../IconButton';
8
8
  import { Box, Flex } from '../../../Layout';
9
9
  import { Loading } from '../../../Loading';
10
10
  import { Text } from '../../../Text';
11
- // @ts-ignore: No implicit Any
12
11
  import { Participant, ParticipantSearch } from './ParticipantList';
13
12
  import { ItemData, itemKey, ROW_HEIGHT } from './RoleAccordion';
14
13
  // @ts-ignore: No implicit Any
@@ -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,13 @@
1
+ import React from 'react';
2
+ import { ClosedCaptionIcon, OpenCaptionIcon } from '@100mslive/react-icons';
3
+ import { IconButton, Tooltip } from '../../../';
4
+
5
+ export function HLSCaptionSelector({ isEnabled, onClick }: { isEnabled: boolean; onClick: () => void }) {
6
+ return (
7
+ <Tooltip title="Subtitles/closed captions" side="top">
8
+ <IconButton css={{ p: '$2' }} onClick={() => onClick()}>
9
+ {isEnabled ? <ClosedCaptionIcon width="20" height="20px" /> : <OpenCaptionIcon width="20" height="20px" />}
10
+ </IconButton>
11
+ </Tooltip>
12
+ );
13
+ }