@100mslive/roomkit-react 0.1.4 → 0.1.6-alpha.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (197) hide show
  1. package/dist/{HLSView-CTAJQUU4.js → HLSView-PY2FKWX3.js} +191 -123
  2. package/dist/HLSView-PY2FKWX3.js.map +7 -0
  3. package/dist/Prebuilt/App.d.ts +3 -0
  4. package/dist/Prebuilt/AppContext.d.ts +13 -0
  5. package/dist/Prebuilt/common/PeersSorter.d.ts +21 -0
  6. package/dist/Prebuilt/components/Footer/Footer.d.ts +6 -0
  7. package/dist/Prebuilt/components/Header/Header.d.ts +2 -0
  8. package/dist/Prebuilt/components/InsetTile.d.ts +2 -0
  9. package/dist/Prebuilt/components/Leave/DesktopLeaveRoom.d.ts +7 -0
  10. package/dist/Prebuilt/components/Leave/EndSessionContent.d.ts +8 -0
  11. package/dist/Prebuilt/components/Leave/LeaveAtoms.d.ts +2196 -0
  12. package/dist/Prebuilt/components/Leave/LeaveCard.d.ts +12 -0
  13. package/dist/Prebuilt/components/Leave/LeaveRoom.d.ts +5 -0
  14. package/dist/Prebuilt/components/Leave/LeaveSessionContent.d.ts +6 -0
  15. package/dist/Prebuilt/components/Leave/MwebLeaveRoom.d.ts +7 -0
  16. package/dist/Prebuilt/components/MoreSettings/MoreSettings.d.ts +6 -0
  17. package/dist/Prebuilt/components/MoreSettings/SplitComponents/DesktopOptions.d.ts +6 -0
  18. package/dist/Prebuilt/components/Pagination.d.ts +6 -0
  19. package/dist/Prebuilt/components/Preview/PreviewForm.d.ts +10 -0
  20. package/dist/Prebuilt/components/SecondaryTiles.d.ts +3 -0
  21. package/dist/Prebuilt/components/VideoLayouts/EqualProminence.d.ts +3 -0
  22. package/dist/Prebuilt/components/VideoLayouts/Grid.d.ts +5 -0
  23. package/dist/Prebuilt/components/VideoLayouts/GridLayout.d.ts +10 -0
  24. package/dist/Prebuilt/components/VideoLayouts/ProminenceLayout.d.ts +12 -0
  25. package/dist/Prebuilt/components/VideoLayouts/RoleProminence.d.ts +3 -0
  26. package/dist/Prebuilt/components/VideoLayouts/ScreenshareLayout.d.ts +3 -0
  27. package/dist/Prebuilt/components/VideoLayouts/interface.d.ts +8 -0
  28. package/dist/Prebuilt/components/hooks/useRoleProminencePeers.d.ts +5 -0
  29. package/dist/Prebuilt/components/hooks/useTileLayout.d.ts +12 -0
  30. package/dist/Prebuilt/components/hooks/useVideoTileLayout.d.ts +11 -0
  31. package/dist/Prebuilt/layouts/SidePane.d.ts +6 -0
  32. package/dist/Prebuilt/layouts/VideoStreamingSection.d.ts +6 -0
  33. package/dist/Prebuilt/plugins/whiteboard/ToggleWhiteboard.d.ts +5 -0
  34. package/dist/Prebuilt/provider/roomLayoutProvider/hooks/useFetchRoomLayout.d.ts +1 -0
  35. package/dist/Prebuilt/provider/roomLayoutProvider/hooks/useInsetEnabled.d.ts +1 -0
  36. package/dist/Prebuilt/provider/roomLayoutProvider/hooks/useRoomLayoutScreen.d.ts +17 -0
  37. package/dist/Prebuilt/provider/roomLayoutProvider/index.d.ts +6 -1
  38. package/dist/{VirtualBackground-GGGBJYVY.js → VirtualBackground-AYDHYLIZ.js} +5 -11
  39. package/dist/VirtualBackground-AYDHYLIZ.js.map +7 -0
  40. package/dist/{chunk-TJNDX446.js → chunk-E2M2ZSOL.js} +8 -5
  41. package/dist/chunk-E2M2ZSOL.js.map +7 -0
  42. package/dist/chunk-GQD2AGWW.js +888 -0
  43. package/dist/chunk-GQD2AGWW.js.map +7 -0
  44. package/dist/{chunk-L2SX7GBO.js → chunk-RXTHJUMZ.js} +2462 -4738
  45. package/dist/chunk-RXTHJUMZ.js.map +7 -0
  46. package/dist/conference-V2XZGTKU.js +5927 -0
  47. package/dist/conference-V2XZGTKU.js.map +7 -0
  48. package/dist/index.cjs.js +9414 -15534
  49. package/dist/index.cjs.js.map +4 -4
  50. package/dist/index.js +2 -2
  51. package/dist/meta.cjs.json +2156 -3347
  52. package/dist/meta.esbuild.json +2601 -3885
  53. package/package.json +7 -7
  54. package/src/Button/Button.tsx +2 -2
  55. package/src/Prebuilt/App.tsx +49 -33
  56. package/src/Prebuilt/{AppContext.jsx → AppContext.tsx} +11 -3
  57. package/src/Prebuilt/IconButton.jsx +1 -0
  58. package/src/Prebuilt/Prebuilt.stories.tsx +1 -0
  59. package/src/Prebuilt/common/{PeersSorter.js → PeersSorter.ts} +15 -10
  60. package/src/Prebuilt/common/constants.js +3 -112
  61. package/src/Prebuilt/common/hooks.js +34 -1
  62. package/src/Prebuilt/common/utils.js +0 -8
  63. package/src/Prebuilt/components/AppData/AppData.jsx +3 -13
  64. package/src/Prebuilt/components/AppData/useUISettings.js +0 -4
  65. package/src/Prebuilt/components/AudioVideoToggle.jsx +6 -0
  66. package/src/Prebuilt/components/AuthToken.jsx +11 -42
  67. package/src/Prebuilt/components/Chat/Chat.jsx +57 -26
  68. package/src/Prebuilt/components/Chat/ChatBody.jsx +92 -32
  69. package/src/Prebuilt/components/Chat/ChatFooter.jsx +72 -48
  70. package/src/Prebuilt/components/Chat/ChatParticipantHeader.jsx +73 -0
  71. package/src/Prebuilt/components/Chat/ChatSelector.jsx +16 -17
  72. package/src/Prebuilt/components/Chat/ChatSelectorContainer.jsx +81 -0
  73. package/src/Prebuilt/components/Connection/TileConnection.jsx +30 -12
  74. package/src/Prebuilt/components/EmojiReaction.jsx +18 -17
  75. package/src/Prebuilt/components/Footer/ChatToggle.jsx +1 -7
  76. package/src/Prebuilt/components/Footer/Footer.tsx +89 -0
  77. package/src/Prebuilt/components/Footer/ParticipantList.jsx +213 -173
  78. package/src/Prebuilt/components/Footer/RoleAccordion.jsx +78 -0
  79. package/src/Prebuilt/components/HMSVideo/Controls.jsx +2 -2
  80. package/src/Prebuilt/components/HMSVideo/HLSQualitySelector.jsx +33 -10
  81. package/src/Prebuilt/components/HMSVideo/PlayButton.jsx +1 -1
  82. package/src/Prebuilt/components/HMSVideo/VideoTime.jsx +3 -3
  83. package/src/Prebuilt/components/HMSVideo/VolumeControl.jsx +38 -9
  84. package/src/Prebuilt/components/Header/{ConferencingHeader.jsx → Header.tsx} +9 -7
  85. package/src/Prebuilt/components/Header/HeaderComponents.jsx +13 -4
  86. package/src/Prebuilt/components/Header/StreamActions.jsx +33 -60
  87. package/src/Prebuilt/components/Header/index.tsx +1 -0
  88. package/src/Prebuilt/components/IconButtonWithOptions/IconButtonWithOptions.jsx +17 -3
  89. package/src/Prebuilt/components/InsetTile.tsx +122 -0
  90. package/src/Prebuilt/components/{MoreSettings/SplitComponents/DesktopLeaveRoom.jsx → Leave/DesktopLeaveRoom.tsx} +50 -18
  91. package/src/Prebuilt/components/{EndSessionContent.jsx → Leave/EndSessionContent.tsx} +19 -9
  92. package/src/Prebuilt/components/Leave/LeaveAtoms.tsx +26 -0
  93. package/src/Prebuilt/components/{LeaveCard.jsx → Leave/LeaveCard.tsx} +22 -3
  94. package/src/Prebuilt/components/Leave/LeaveRoom.tsx +63 -0
  95. package/src/Prebuilt/components/{LeaveSessionContent.jsx → Leave/LeaveSessionContent.tsx} +13 -5
  96. package/src/Prebuilt/components/{MoreSettings/SplitComponents/MwebLeaveRoom.jsx → Leave/MwebLeaveRoom.tsx} +38 -13
  97. package/src/Prebuilt/components/MetaActions.jsx +15 -23
  98. package/src/Prebuilt/components/MoreSettings/ActionTile.jsx +5 -0
  99. package/src/Prebuilt/components/MoreSettings/ChangeNameContent.jsx +12 -7
  100. package/src/Prebuilt/components/MoreSettings/ChangeNameModal.jsx +1 -1
  101. package/src/Prebuilt/components/MoreSettings/FullScreenItem.jsx +1 -4
  102. package/src/Prebuilt/components/MoreSettings/MoreSettings.tsx +27 -0
  103. package/src/Prebuilt/components/MoreSettings/SplitComponents/{DesktopOptions.jsx → DesktopOptions.tsx} +86 -75
  104. package/src/Prebuilt/components/MoreSettings/SplitComponents/MwebOptions.jsx +20 -19
  105. package/src/Prebuilt/components/Notifications/HLSFailureModal.jsx +3 -1
  106. package/src/Prebuilt/components/Notifications/Notifications.jsx +18 -11
  107. package/src/Prebuilt/components/Notifications/PeerNotifications.jsx +14 -2
  108. package/src/Prebuilt/components/Notifications/PermissionErrorModal.jsx +10 -4
  109. package/src/Prebuilt/components/PIP/PIPComponent.jsx +7 -16
  110. package/src/Prebuilt/components/PIP/PIPManager.js +1 -0
  111. package/src/Prebuilt/components/{Pagination.jsx → Pagination.tsx} +35 -6
  112. package/src/Prebuilt/components/Playlist/Playlist.jsx +1 -6
  113. package/src/Prebuilt/components/PostLeave.jsx +7 -7
  114. package/src/Prebuilt/components/Preview/PreviewContainer.jsx +5 -13
  115. package/src/Prebuilt/components/Preview/{PreviewForm.jsx → PreviewForm.tsx} +14 -4
  116. package/src/Prebuilt/components/Preview/PreviewJoin.jsx +9 -7
  117. package/src/Prebuilt/components/RaiseHand.jsx +0 -7
  118. package/src/Prebuilt/components/RoleChangeRequestModal.jsx +82 -6
  119. package/src/Prebuilt/components/ScreenshareDisplay.jsx +4 -10
  120. package/src/Prebuilt/components/ScreenshareTile.jsx +41 -33
  121. package/src/Prebuilt/components/SecondaryTiles.tsx +34 -0
  122. package/src/Prebuilt/components/Settings/LayoutSettings.jsx +2 -12
  123. package/src/Prebuilt/components/Settings/NotificationSettings.jsx +3 -9
  124. package/src/Prebuilt/components/Settings/SettingsModal.jsx +3 -9
  125. package/src/Prebuilt/components/StatsForNerds.jsx +3 -1
  126. package/src/Prebuilt/components/TileMenu/TileMenu.jsx +15 -16
  127. package/src/Prebuilt/components/TileMenu/TileMenuContent.jsx +21 -19
  128. package/src/Prebuilt/components/Toast/ToastConfig.jsx +53 -11
  129. package/src/Prebuilt/components/VideoLayouts/EqualProminence.tsx +62 -0
  130. package/src/Prebuilt/components/VideoLayouts/Grid.tsx +41 -0
  131. package/src/Prebuilt/components/VideoLayouts/GridLayout.tsx +92 -0
  132. package/src/Prebuilt/components/VideoLayouts/ProminenceLayout.tsx +60 -0
  133. package/src/Prebuilt/components/VideoLayouts/RoleProminence.tsx +56 -0
  134. package/src/Prebuilt/components/VideoLayouts/ScreenshareLayout.tsx +36 -0
  135. package/src/Prebuilt/components/VideoLayouts/interface.ts +9 -0
  136. package/src/Prebuilt/components/VideoTile.jsx +93 -43
  137. package/src/Prebuilt/components/conference.jsx +24 -20
  138. package/src/Prebuilt/components/hooks/useMetadata.jsx +7 -0
  139. package/src/Prebuilt/components/hooks/useRoleProminencePeers.tsx +38 -0
  140. package/src/Prebuilt/components/hooks/useTileLayout.tsx +121 -0
  141. package/src/Prebuilt/components/hooks/useVideoTileLayout.ts +22 -0
  142. package/src/Prebuilt/components/pdfAnnotator/pdfFileOptions.jsx +5 -72
  143. package/src/Prebuilt/components/pdfAnnotator/submitPdf.jsx +4 -45
  144. package/src/Prebuilt/components/pdfAnnotator/uploadedFile.jsx +2 -17
  145. package/src/Prebuilt/components/peerTileUtils.jsx +1 -1
  146. package/src/Prebuilt/images/empty-chat.svg +12 -0
  147. package/src/Prebuilt/layouts/EmbedView.jsx +17 -40
  148. package/src/Prebuilt/layouts/HLSView.jsx +83 -66
  149. package/src/Prebuilt/layouts/PDFView.jsx +1 -11
  150. package/src/Prebuilt/layouts/SidePane.tsx +96 -0
  151. package/src/Prebuilt/layouts/{mainView.jsx → VideoStreamingSection.tsx} +38 -47
  152. package/src/Prebuilt/layouts/WhiteboardView.jsx +10 -34
  153. package/src/Prebuilt/plugins/VirtualBackground/VirtualBackground.jsx +1 -4
  154. package/src/Prebuilt/plugins/whiteboard/{ToggleWhiteboard.jsx → ToggleWhiteboard.tsx} +5 -9
  155. package/src/Prebuilt/primitives/DialogContent.jsx +15 -11
  156. package/src/Prebuilt/provider/roomLayoutProvider/constants/index.ts +17 -2
  157. package/src/Prebuilt/provider/roomLayoutProvider/hooks/useFetchRoomLayout.ts +36 -13
  158. package/src/Prebuilt/provider/roomLayoutProvider/hooks/useInsetEnabled.ts +10 -0
  159. package/src/Prebuilt/provider/roomLayoutProvider/hooks/useRoomLayoutScreen.ts +65 -0
  160. package/src/Prebuilt/provider/roomLayoutProvider/index.tsx +17 -6
  161. package/dist/HLSView-CTAJQUU4.js.map +0 -7
  162. package/dist/PinnedTrackView-CQKONH4O.js +0 -102
  163. package/dist/PinnedTrackView-CQKONH4O.js.map +0 -7
  164. package/dist/VirtualBackground-GGGBJYVY.js.map +0 -7
  165. package/dist/chunk-I2FJWE74.js +0 -827
  166. package/dist/chunk-I2FJWE74.js.map +0 -7
  167. package/dist/chunk-L2SX7GBO.js.map +0 -7
  168. package/dist/chunk-NOKIGB6Y.js +0 -1100
  169. package/dist/chunk-NOKIGB6Y.js.map +0 -7
  170. package/dist/chunk-TJNDX446.js.map +0 -7
  171. package/dist/conference-OEO7VOJD.js +0 -8995
  172. package/dist/conference-OEO7VOJD.js.map +0 -7
  173. package/src/Prebuilt/components/Chat/ChatHeader.jsx +0 -67
  174. package/src/Prebuilt/components/EqualProminence.jsx +0 -180
  175. package/src/Prebuilt/components/FirstPersonDisplay.jsx +0 -50
  176. package/src/Prebuilt/components/Footer/Footer.jsx +0 -73
  177. package/src/Prebuilt/components/Header/Header.jsx +0 -8
  178. package/src/Prebuilt/components/Header/StreamingHeader.jsx +0 -54
  179. package/src/Prebuilt/components/LeaveRoom.jsx +0 -94
  180. package/src/Prebuilt/components/MoreSettings/MoreSettings.jsx +0 -10
  181. package/src/Prebuilt/components/Notifications/MessageNotifications.jsx +0 -25
  182. package/src/Prebuilt/components/gridView.jsx +0 -85
  183. package/src/Prebuilt/components/hooks/useFeatures.js +0 -22
  184. package/src/Prebuilt/components/hooks/useNavigation.js +0 -19
  185. package/src/Prebuilt/components/hooks/useSkipPreview.jsx +0 -20
  186. package/src/Prebuilt/components/pdfAnnotator/pdfErrorView.jsx +0 -29
  187. package/src/Prebuilt/images/Logo.svg +0 -8
  188. package/src/Prebuilt/layouts/ActiveSpeakerView.jsx +0 -34
  189. package/src/Prebuilt/layouts/InsetView.jsx +0 -260
  190. package/src/Prebuilt/layouts/PinnedTrackView.jsx +0 -59
  191. package/src/Prebuilt/layouts/SidePane.jsx +0 -52
  192. package/src/Prebuilt/layouts/mainGridView.jsx +0 -98
  193. package/src/Prebuilt/layouts/screenShareView.jsx +0 -183
  194. /package/{src/Prebuilt/components/Header/index.jsx → dist/Prebuilt/components/Header/index.d.ts} +0 -0
  195. /package/src/Prebuilt/components/{ScreenShare.jsx → ScreenShareToggle.jsx} +0 -0
  196. /package/src/{assets → Prebuilt/images}/android-perm-1.png +0 -0
  197. /package/src/{assets → Prebuilt/images}/ios-perm-0.png +0 -0
@@ -1,46 +1,58 @@
1
1
  import React, { Fragment, useCallback, useEffect, useState } from 'react';
2
- import { useDebounce, useMeasure } from 'react-use';
3
- import { FixedSizeList } from 'react-window';
2
+ import { useDebounce, useMedia } from 'react-use';
4
3
  import {
5
- selectAudioTrackByPeerID,
4
+ selectIsPeerAudioEnabled,
6
5
  selectLocalPeerID,
7
6
  selectPeerCount,
8
7
  selectPeerMetadata,
8
+ selectPeersByCondition,
9
9
  selectPermissions,
10
10
  useHMSActions,
11
11
  useHMSStore,
12
- useParticipants,
13
12
  } from '@100mslive/react-sdk';
14
13
  import {
15
14
  ChangeRoleIcon,
16
- CrossIcon,
17
- HandRaiseIcon,
15
+ HandIcon,
16
+ MicOffIcon,
18
17
  PeopleIcon,
19
- RemoveUserIcon,
18
+ PeopleRemoveIcon,
20
19
  SearchIcon,
21
- SpeakerIcon,
22
20
  VerticalMenuIcon,
23
21
  } from '@100mslive/react-icons';
24
- import { Avatar, Box, Dropdown, Flex, Input, Slider, Text, textEllipsis } from '../../..';
22
+ import { Box, config as cssConfig, Dropdown, Flex, Input, Text, textEllipsis } from '../../..';
25
23
  import IconButton from '../../IconButton';
24
+ import { useRoomLayout } from '../../provider/roomLayoutProvider';
25
+ import { ChatParticipantHeader } from '../Chat/ChatParticipantHeader';
26
26
  import { ConnectionIndicator } from '../Connection/ConnectionIndicator';
27
- import { ParticipantFilter } from '../Header/ParticipantFilter';
28
27
  import { RoleChangeModal } from '../RoleChangeModal';
28
+ import { ToastManager } from '../Toast/ToastManager';
29
+ import { RoleAccordion } from './RoleAccordion';
29
30
  import { useIsSidepaneTypeOpen, useSidepaneToggle } from '../AppData/useSidepane';
31
+ import { useParticipants } from '../../common/hooks';
30
32
  import { isInternalRole } from '../../common/utils';
31
33
  import { SIDE_PANE_OPTIONS } from '../../common/constants';
32
34
 
33
35
  export const ParticipantList = () => {
34
36
  const [filter, setFilter] = useState();
35
- const { participants, isConnected, peerCount, rolesWithParticipants } = useParticipants(filter);
37
+ const { participants, isConnected, peerCount } = useParticipants(filter);
38
+ const peersOrderedByRoles = {};
39
+
40
+ const handRaisedPeers = useHMSStore(selectPeersByCondition(peer => JSON.parse(peer.metadata || '{}')?.isHandRaised));
41
+
42
+ participants.forEach(participant => {
43
+ if (peersOrderedByRoles[participant.roleName] === undefined) {
44
+ peersOrderedByRoles[participant.roleName] = [];
45
+ }
46
+ peersOrderedByRoles[participant.roleName].push(participant);
47
+ });
48
+
36
49
  const [selectedPeerId, setSelectedPeerId] = useState(null);
37
- const toggleSidepane = useSidepaneToggle(SIDE_PANE_OPTIONS.PARTICIPANTS);
38
50
  const onSearch = useCallback(value => {
39
51
  setFilter(filterValue => {
40
52
  if (!filterValue) {
41
53
  filterValue = {};
42
54
  }
43
- filterValue.search = value;
55
+ filterValue.search = value.toLowerCase();
44
56
  return { ...filterValue };
45
57
  });
46
58
  }, []);
@@ -50,39 +62,30 @@ export const ParticipantList = () => {
50
62
 
51
63
  return (
52
64
  <Fragment>
53
- <Flex direction="column" css={{ size: '100%' }}>
54
- <Flex align="center" css={{ w: '100%', mb: '$10' }}>
55
- <Text css={{ fontWeight: '$semiBold', mr: '$4' }}>Participants</Text>
56
- <ParticipantFilter
57
- selection={filter}
58
- onSelection={setFilter}
59
- isConnected={isConnected}
60
- roles={rolesWithParticipants}
61
- />
62
- <IconButton onClick={toggleSidepane} css={{ w: '$11', h: '$11', ml: 'auto' }}>
63
- <CrossIcon />
64
- </IconButton>
65
- </Flex>
66
- {!filter?.search && participants.length === 0 ? null : <ParticipantSearch onSearch={onSearch} />}
67
- {participants.length === 0 && (
65
+ <Flex direction="column" css={{ size: '100%', gap: '$4' }}>
66
+ <ChatParticipantHeader activeTabValue={SIDE_PANE_OPTIONS.PARTICIPANTS} />
67
+ {!filter?.search && participants.length === 0 ? null : <ParticipantSearch onSearch={onSearch} inSidePane />}
68
+ {participants.length === 0 ? (
68
69
  <Flex align="center" justify="center" css={{ w: '100%', p: '$8 0' }}>
69
70
  <Text variant="sm">{!filter ? 'No participants' : 'No matching participants'}</Text>
70
71
  </Flex>
71
- )}
72
+ ) : null}
72
73
  <VirtualizedParticipants
73
- participants={participants}
74
+ peersOrderedByRoles={peersOrderedByRoles}
75
+ handRaisedList={handRaisedPeers}
74
76
  isConnected={isConnected}
77
+ filter={filter}
75
78
  setSelectedPeerId={setSelectedPeerId}
76
79
  />
80
+ {selectedPeerId && (
81
+ <RoleChangeModal
82
+ peerId={selectedPeerId}
83
+ onOpenChange={value => {
84
+ !value && setSelectedPeerId(null);
85
+ }}
86
+ />
87
+ )}
77
88
  </Flex>
78
- {selectedPeerId && (
79
- <RoleChangeModal
80
- peerId={selectedPeerId}
81
- onOpenChange={value => {
82
- !value && setSelectedPeerId(null);
83
- }}
84
- />
85
- )}
86
89
  </Fragment>
87
90
  );
88
91
  };
@@ -123,142 +126,207 @@ export const ParticipantCount = () => {
123
126
  );
124
127
  };
125
128
 
126
- function itemKey(index, data) {
127
- return data.participants[index].id;
128
- }
129
-
130
- const VirtualizedParticipants = ({ participants, isConnected, setSelectedPeerId }) => {
131
- const [ref, { width, height }] = useMeasure();
129
+ const VirtualizedParticipants = ({
130
+ peersOrderedByRoles = {},
131
+ isConnected,
132
+ setSelectedPeerId,
133
+ filter,
134
+ handRaisedList = [],
135
+ }) => {
132
136
  return (
133
- <Box
134
- ref={ref}
137
+ <Flex
138
+ direction="column"
135
139
  css={{
136
- flex: '1 1 0',
137
- mr: '-$10',
140
+ gap: '$8',
141
+ maxHeight: '100%',
142
+ overflowY: 'auto',
143
+ overflowX: 'hidden',
144
+ pr: '$3',
138
145
  }}
139
146
  >
140
- <FixedSizeList
141
- itemSize={68}
142
- itemData={{ participants, isConnected, setSelectedPeerId }}
143
- itemKey={itemKey}
144
- itemCount={participants.length}
145
- width={width}
146
- height={height}
147
- >
148
- {VirtualisedParticipantListItem}
149
- </FixedSizeList>
150
- </Box>
151
- );
152
- };
153
-
154
- const VirtualisedParticipantListItem = React.memo(({ style, index, data }) => {
155
- return (
156
- <div style={style} key={data.participants[index].id}>
157
- <Participant
158
- peer={data.participants[index]}
159
- isConnected={data.isConnected}
160
- setSelectedPeerId={data.setSelectedPeerId}
147
+ <RoleAccordion
148
+ peerList={handRaisedList}
149
+ roleName="Hand Raised"
150
+ filter={filter}
151
+ isConnected={isConnected}
152
+ setSelectedPeerId={setSelectedPeerId}
153
+ isHandRaisedAccordion
161
154
  />
162
- </div>
155
+ {Object.keys(peersOrderedByRoles).map(role => (
156
+ <RoleAccordion
157
+ key={role}
158
+ peerList={peersOrderedByRoles[role]}
159
+ roleName={role}
160
+ isConnected={isConnected}
161
+ setSelectedPeerId={setSelectedPeerId}
162
+ filter={filter}
163
+ />
164
+ ))}
165
+ </Flex>
163
166
  );
164
- });
167
+ };
165
168
 
166
- const Participant = ({ peer, isConnected, setSelectedPeerId }) => {
169
+ export const Participant = ({ peer, isConnected, setSelectedPeerId }) => {
170
+ const localPeerId = useHMSStore(selectLocalPeerID);
167
171
  return (
168
- <Fragment>
169
- <Flex
170
- key={peer.id}
171
- css={{ w: '100%', py: '$4', pr: '$10' }}
172
- align="center"
173
- data-testid={'participant_' + peer.name}
174
- >
175
- <Avatar
176
- name={peer.name}
177
- css={{
178
- position: 'unset',
179
- transform: 'unset',
180
- mr: '$8',
181
- fontSize: '$sm',
182
- size: '$12',
183
- p: '$4',
172
+ <Flex
173
+ key={peer.id}
174
+ css={{
175
+ w: '100%',
176
+ p: '$4 $8',
177
+ pr: '$6',
178
+ h: '$16',
179
+ '&:hover .participant_item': { display: 'flex' },
180
+ }}
181
+ align="center"
182
+ justify="between"
183
+ data-testid={'participant_' + peer.name}
184
+ >
185
+ <Text variant="sm" css={{ ...textEllipsis(150), fontWeight: '$semiBold', color: '$on_surface_high' }}>
186
+ {peer.name} {localPeerId === peer.id ? '(You)' : ''}
187
+ </Text>
188
+ {isConnected ? (
189
+ <ParticipantActions
190
+ peerId={peer.id}
191
+ isLocal={peer.id === localPeerId}
192
+ role={peer.roleName}
193
+ onSettings={() => {
194
+ setSelectedPeerId(peer.id);
184
195
  }}
185
196
  />
186
- <Flex direction="column" css={{ flex: '1 1 0' }}>
187
- <Text variant="md" css={{ ...textEllipsis(150), fontWeight: '$semiBold' }}>
188
- {peer.name}
189
- </Text>
190
- <Text variant="sub2">{peer.roleName}</Text>
191
- </Flex>
192
- {isConnected && (
193
- <ParticipantActions
194
- peerId={peer.id}
195
- role={peer.roleName}
196
- onSettings={() => {
197
- setSelectedPeerId(peer.id);
198
- }}
199
- />
200
- )}
201
- </Flex>
202
- </Fragment>
197
+ ) : null}
198
+ </Flex>
203
199
  );
204
200
  };
205
201
 
206
202
  /**
207
203
  * shows settings to change for a participant like changing their role
208
204
  */
209
- const ParticipantActions = React.memo(({ onSettings, peerId, role }) => {
205
+ const ParticipantActions = React.memo(({ onSettings, peerId, role, isLocal }) => {
210
206
  const isHandRaised = useHMSStore(selectPeerMetadata(peerId))?.isHandRaised;
211
207
  const canChangeRole = useHMSStore(selectPermissions)?.changeRole;
212
- const audioTrack = useHMSStore(selectAudioTrackByPeerID(peerId));
213
- const localPeerId = useHMSStore(selectLocalPeerID);
214
- const canChangeVolume = peerId !== localPeerId && audioTrack;
215
- const shouldShowMoreActions = canChangeRole || canChangeVolume;
208
+ const shouldShowMoreActions = canChangeRole;
209
+ const isAudioMuted = !useHMSStore(selectIsPeerAudioEnabled(peerId));
216
210
 
217
211
  return (
218
- <Flex align="center" css={{ flexShrink: 0 }}>
212
+ <Flex
213
+ align="center"
214
+ css={{
215
+ flexShrink: 0,
216
+ gap: '$8',
217
+ mt: '$2',
218
+ }}
219
+ >
219
220
  <ConnectionIndicator peerId={peerId} />
220
- {isHandRaised && <HandRaiseIcon />}
221
- {shouldShowMoreActions && !isInternalRole(role) && (
222
- <ParticipantMoreActions onRoleChange={onSettings} peerId={peerId} role={role} />
221
+ {isHandRaised && (
222
+ <Flex
223
+ align="center"
224
+ justify="center"
225
+ css={{ p: '$1', c: '$on_surface_high', bg: '$surface_bright', borderRadius: '$round' }}
226
+ >
227
+ <HandIcon height={19} width={19} />
228
+ </Flex>
223
229
  )}
230
+ {isAudioMuted ? (
231
+ <Flex
232
+ align="center"
233
+ justify="center"
234
+ css={{ p: '$2', c: '$on_surface_high', bg: '$surface_bright', borderRadius: '$round' }}
235
+ >
236
+ <MicOffIcon height={19} width={19} />
237
+ </Flex>
238
+ ) : null}
239
+
240
+ {shouldShowMoreActions && !isInternalRole(role) && !isLocal ? (
241
+ <ParticipantMoreActions onRoleChange={onSettings} peerId={peerId} role={role} />
242
+ ) : null}
224
243
  </Flex>
225
244
  );
226
245
  });
227
246
 
228
- const ParticipantMoreActions = ({ onRoleChange, peerId }) => {
247
+ const ParticipantMoreActions = ({ onRoleChange, peerId, role }) => {
248
+ const hmsActions = useHMSActions();
229
249
  const { changeRole: canChangeRole, removeOthers: canRemoveOthers } = useHMSStore(selectPermissions);
250
+ const layout = useRoomLayout();
251
+ const {
252
+ bring_to_stage_label,
253
+ remove_from_stage_label,
254
+ on_stage_role,
255
+ off_stage_roles = [],
256
+ } = layout?.screens?.conferencing?.default?.elements.on_stage_exp || {};
257
+ const canBringToStage = off_stage_roles.includes(role);
258
+ const isInStage = role === on_stage_role;
259
+ const prevRole = useHMSStore(selectPeerMetadata(peerId))?.prevRole;
230
260
  const localPeerId = useHMSStore(selectLocalPeerID);
231
261
  const isLocal = localPeerId === peerId;
232
- const actions = useHMSActions();
233
262
  const [open, setOpen] = useState(false);
263
+
264
+ const handleStageAction = async () => {
265
+ if (isInStage) {
266
+ hmsActions.changeRoleOfPeer(peerId, prevRole || off_stage_roles[0]);
267
+ } else {
268
+ await hmsActions.changeRoleOfPeer(peerId, on_stage_role);
269
+ }
270
+ setOpen(false);
271
+ };
272
+
234
273
  return (
235
274
  <Dropdown.Root open={open} onOpenChange={value => setOpen(value)} modal={false}>
236
- <Dropdown.Trigger asChild data-testid="participant_more_actions" css={{ p: '$2', r: '$0' }} tabIndex={0}>
237
- <Text>
275
+ <Dropdown.Trigger
276
+ asChild
277
+ data-testid="participant_more_actions"
278
+ className="participant_item"
279
+ css={{
280
+ p: '$1',
281
+ r: '$0',
282
+ c: '$on_surface_high',
283
+ display: open ? 'flex' : 'none',
284
+ '&:hover': {
285
+ bg: '$surface_bright',
286
+ },
287
+ '@md': {
288
+ display: 'flex',
289
+ },
290
+ }}
291
+ tabIndex={0}
292
+ >
293
+ <Box css={{ my: 'auto' }}>
238
294
  <VerticalMenuIcon />
239
- </Text>
295
+ </Box>
240
296
  </Dropdown.Trigger>
241
297
  <Dropdown.Portal>
242
- <Dropdown.Content align="end" sideOffset={8} css={{ w: '$64' }}>
243
- {canChangeRole && (
244
- <Dropdown.Item onClick={() => onRoleChange(peerId)}>
298
+ <Dropdown.Content align="end" sideOffset={8} css={{ w: '$64', bg: '$surface_default' }}>
299
+ {canChangeRole && canBringToStage ? (
300
+ <Dropdown.Item css={{ bg: '$surface_default' }} onClick={() => handleStageAction()}>
301
+ <ChangeRoleIcon />
302
+ <Text variant="sm" css={{ ml: '$4', fontWeight: '$semiBold', c: '$on_surface_high' }}>
303
+ {isInStage ? remove_from_stage_label : bring_to_stage_label}
304
+ </Text>
305
+ </Dropdown.Item>
306
+ ) : (
307
+ <Dropdown.Item css={{ bg: '$surface_default' }} onClick={() => onRoleChange(peerId)}>
245
308
  <ChangeRoleIcon />
246
- <Text css={{ ml: '$4' }}>Change Role</Text>
309
+ <Text variant="sm" css={{ ml: '$4', fontWeight: '$semiBold', c: '$on_surface_high' }}>
310
+ Change Role
311
+ </Text>
247
312
  </Dropdown.Item>
248
313
  )}
249
- <ParticipantVolume peerId={peerId} />
314
+
250
315
  {!isLocal && canRemoveOthers && (
251
316
  <Dropdown.Item
317
+ css={{ color: '$alert_error_default', bg: '$surface_default' }}
252
318
  onClick={async () => {
253
319
  try {
254
- await actions.removePeer(peerId, '');
320
+ await hmsActions.removePeer(peerId, '');
255
321
  } catch (error) {
256
- // TODO: Toast here
322
+ ToastManager.addToast({ title: error.message, variant: 'error' });
257
323
  }
258
324
  }}
259
325
  >
260
- <RemoveUserIcon />
261
- <Text css={{ ml: '$4', color: '$alert_error_default' }}>Remove Participant</Text>
326
+ <PeopleRemoveIcon />
327
+ <Text variant="sm" css={{ ml: '$4', color: 'inherit', fontWeight: '$semiBold' }}>
328
+ Remove Participant
329
+ </Text>
262
330
  </Dropdown.Item>
263
331
  )}
264
332
  </Dropdown.Content>
@@ -267,37 +335,10 @@ const ParticipantMoreActions = ({ onRoleChange, peerId }) => {
267
335
  );
268
336
  };
269
337
 
270
- const ParticipantVolume = ({ peerId }) => {
271
- const audioTrack = useHMSStore(selectAudioTrackByPeerID(peerId));
272
- const localPeerId = useHMSStore(selectLocalPeerID);
273
- const hmsActions = useHMSActions();
274
- // No volume control for local peer or non audio publishing role
275
- if (peerId === localPeerId || !audioTrack) {
276
- return null;
277
- }
278
-
279
- return (
280
- <Dropdown.Item css={{ h: 'auto' }}>
281
- <Flex direction="column" css={{ w: '100%' }}>
282
- <Flex align="center">
283
- <SpeakerIcon />
284
- <Text css={{ ml: '$4' }}>Volume{audioTrack.volume ? `(${audioTrack.volume})` : ''}</Text>
285
- </Flex>
286
- <Slider
287
- css={{ my: '0.5rem' }}
288
- step={5}
289
- value={[audioTrack.volume]}
290
- onValueChange={e => {
291
- hmsActions.setVolume(e[0], audioTrack?.id);
292
- }}
293
- />
294
- </Flex>
295
- </Dropdown.Item>
296
- );
297
- };
298
-
299
- export const ParticipantSearch = ({ onSearch, placeholder }) => {
338
+ export const ParticipantSearch = ({ onSearch, placeholder, inSidePane = false }) => {
300
339
  const [value, setValue] = React.useState('');
340
+ const isMobile = useMedia(cssConfig.media.md);
341
+
301
342
  useDebounce(
302
343
  () => {
303
344
  onSearch(value);
@@ -306,22 +347,21 @@ export const ParticipantSearch = ({ onSearch, placeholder }) => {
306
347
  [value, onSearch],
307
348
  );
308
349
  return (
309
- <Box css={{ p: '$4 0', my: '$8', position: 'relative' }}>
310
- <Box
311
- css={{
312
- position: 'absolute',
313
- left: '$4',
314
- top: '$2',
315
- transform: 'translateY(50%)',
316
- color: '$on_surface_medium',
317
- }}
318
- >
319
- <SearchIcon />
320
- </Box>
350
+ <Flex
351
+ align="center"
352
+ css={{
353
+ p: isMobile ? '$0 $6' : '$2 0',
354
+ mb: '$2',
355
+ position: 'relative',
356
+ color: '$on_surface_medium',
357
+ mt: inSidePane ? '$4' : '',
358
+ }}
359
+ >
360
+ <SearchIcon style={{ position: 'absolute', left: isMobile ? '1.25rem' : '0.5rem' }} />
321
361
  <Input
322
362
  type="text"
323
- placeholder={placeholder || 'Search among participants'}
324
- css={{ w: '100%', pl: '$14', bg: '$surface_bright' }}
363
+ placeholder={placeholder || 'Search for participants'}
364
+ css={{ w: '100%', p: '$6', pl: '$14', mr: '$4', bg: inSidePane ? '$surface_default' : '$surface_dim' }}
325
365
  value={value}
326
366
  onKeyDown={event => {
327
367
  event.stopPropagation();
@@ -332,6 +372,6 @@ export const ParticipantSearch = ({ onSearch, placeholder }) => {
332
372
  autoComplete="off"
333
373
  aria-autocomplete="none"
334
374
  />
335
- </Box>
375
+ </Flex>
336
376
  );
337
377
  };
@@ -0,0 +1,78 @@
1
+ import React from 'react';
2
+ import { useMeasure } from 'react-use';
3
+ import { FixedSizeList } from 'react-window';
4
+ import { Accordion } from '../../../Accordion';
5
+ import { Box, Flex } from '../../../Layout';
6
+ import { Participant } from './ParticipantList';
7
+ import { getFormattedCount } from '../../common/utils';
8
+
9
+ const ROW_HEIGHT = 50;
10
+
11
+ function itemKey(index, data) {
12
+ return data.peerList[index].id;
13
+ }
14
+
15
+ const VirtualizedParticipantItem = React.memo(({ index, data }) => {
16
+ return (
17
+ <Participant
18
+ key={data.peerList[index].id}
19
+ peer={data.peerList[index]}
20
+ isConnected={data.isConnected}
21
+ setSelectedPeerId={data.setSelectedPeerId}
22
+ />
23
+ );
24
+ });
25
+
26
+ export const RoleAccordion = ({
27
+ peerList = [],
28
+ roleName,
29
+ setSelectedPeerId,
30
+ isConnected,
31
+ filter,
32
+ isHandRaisedAccordion = false,
33
+ }) => {
34
+ const [ref, { width }] = useMeasure();
35
+ const height = ROW_HEIGHT * peerList.length;
36
+ const showAcordion = filter?.search ? peerList.some(peer => peer.name.toLowerCase().includes(filter.search)) : true;
37
+ if (!showAcordion || (isHandRaisedAccordion && filter?.search) || peerList.length === 0) {
38
+ return null;
39
+ }
40
+
41
+ return (
42
+ <Flex direction="column" css={{ flexGrow: 1 }} ref={ref}>
43
+ <Accordion.Root
44
+ type="single"
45
+ collapsible
46
+ defaultValue={roleName}
47
+ css={{ borderRadius: '$3', border: '1px solid $border_bright' }}
48
+ >
49
+ <Accordion.Item value={roleName}>
50
+ <Accordion.Header
51
+ css={{
52
+ textTransform: 'capitalize',
53
+ p: '$6 $8',
54
+ fontSize: '$sm',
55
+ fontWeight: '$semiBold',
56
+ c: '$on_surface_medium',
57
+ }}
58
+ >
59
+ {roleName} {`(${getFormattedCount(peerList.length)})`}
60
+ </Accordion.Header>
61
+ <Accordion.Content>
62
+ <Box css={{ borderTop: '1px solid $border_default' }} />
63
+ <FixedSizeList
64
+ itemSize={ROW_HEIGHT}
65
+ itemData={{ peerList, isConnected, setSelectedPeerId }}
66
+ itemKey={itemKey}
67
+ itemCount={peerList.length}
68
+ width={width}
69
+ height={height}
70
+ >
71
+ {VirtualizedParticipantItem}
72
+ </FixedSizeList>
73
+ </Accordion.Content>
74
+ </Accordion.Item>
75
+ </Accordion.Root>
76
+ </Flex>
77
+ );
78
+ };
@@ -11,11 +11,11 @@ export const LeftControls = styled(Flex, {
11
11
  justifyContent: 'flex-start',
12
12
  alignItems: 'center',
13
13
  width: '100%',
14
- gap: '$2',
14
+ gap: '$4',
15
15
  });
16
16
  export const RightControls = styled(Flex, {
17
17
  justifyContent: 'flex-end',
18
18
  alignItems: 'center',
19
19
  width: '100%',
20
- gap: '$2',
20
+ gap: '$4',
21
21
  });
@@ -34,7 +34,7 @@ export function HLSQualitySelector({ layers, onQualityChange, selection, isAuto
34
34
  '@sm': 'xs',
35
35
  '@xs': 'tiny',
36
36
  }}
37
- css={{ display: 'flex', alignItems: 'center', ml: '$2' }}
37
+ css={{ display: 'flex', alignItems: 'center', ml: '$2', c: '$on_surface_medium' }}
38
38
  >
39
39
  {isAuto && (
40
40
  <>
@@ -57,26 +57,49 @@ export function HLSQualitySelector({ layers, onQualityChange, selection, isAuto
57
57
  </Flex>
58
58
  </Dropdown.Trigger>
59
59
  {layers.length > 0 && (
60
- <Dropdown.Content sideOffset={5} align="end" css={{ height: 'auto', maxHeight: '$96', w: '$64' }}>
61
- <Dropdown.Item onClick={() => onQualityChange({ height: 'auto' })} key="auto">
62
- <Text css={{ flex: '1 1 0' }}>Automatic</Text>
63
- {isAuto && <CheckCircleIcon />}
64
- </Dropdown.Item>
60
+ <Dropdown.Content
61
+ sideOffset={5}
62
+ align="end"
63
+ css={{ height: 'auto', maxHeight: '$96', w: '$64', bg: '$surface_bright' }}
64
+ >
65
65
  {layers.map(layer => {
66
66
  return (
67
- <Dropdown.Item onClick={() => onQualityChange(layer)} key={layer.width}>
68
- <Text css={{ flex: '1 1 0' }}>{getQualityText(layer)}</Text>
67
+ <Dropdown.Item
68
+ onClick={() => onQualityChange(layer)}
69
+ key={layer.width}
70
+ css={{
71
+ bg: '$surface_bright',
72
+ '&:hover': {
73
+ bg: '$surface_default',
74
+ },
75
+ }}
76
+ >
77
+ <Text>{getQualityText(layer)}</Text>
78
+ <Text css={{ flex: '1 1 0', c: '$on_surface_low', pl: '$2' }}>{getBitrateText(layer)}</Text>
69
79
  {!isAuto && layer.width === selection?.width && layer.height === selection?.height && (
70
80
  <CheckCircleIcon />
71
81
  )}
72
82
  </Dropdown.Item>
73
83
  );
74
84
  })}
85
+ <Dropdown.Item
86
+ onClick={() => onQualityChange({ height: 'auto' })}
87
+ key="auto"
88
+ css={{
89
+ bg: '$surface_bright',
90
+ '&:hover': {
91
+ bg: '$surface_default',
92
+ },
93
+ }}
94
+ >
95
+ <Text css={{ flex: '1 1 0' }}>Auto</Text>
96
+ {isAuto && <CheckCircleIcon />}
97
+ </Dropdown.Item>
75
98
  </Dropdown.Content>
76
99
  )}
77
100
  </Dropdown.Root>
78
101
  );
79
102
  }
80
103
 
81
- const getQualityText = layer =>
82
- `${Math.min(layer.height, layer.width)}p (${(Number(layer.bitrate / 1000) / 1000).toFixed(2)} Mbps)`;
104
+ const getQualityText = layer => `${Math.min(layer.height, layer.width)}p `;
105
+ const getBitrateText = layer => `(${(Number(layer.bitrate / 1000) / 1000).toFixed(2)} Mbps)`;