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

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 (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)`;