@100mslive/roomkit-react 0.1.7 → 0.1.8

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 (72) hide show
  1. package/dist/AudioLevel/AudioLevel.d.ts +5 -8
  2. package/dist/AudioLevel/index.d.ts +2 -1
  3. package/dist/AudioLevel/useBorderAudioLevel.d.ts +8 -0
  4. package/dist/{HLSView-3S74KF3A.js → HLSView-DDGPZHA2.js} +5 -4
  5. package/dist/{HLSView-3S74KF3A.js.map → HLSView-DDGPZHA2.js.map} +2 -2
  6. package/dist/Prebuilt/App.d.ts +1 -0
  7. package/dist/Prebuilt/AppContext.d.ts +1 -0
  8. package/dist/Prebuilt/components/Chip.d.ts +12 -0
  9. package/dist/Prebuilt/components/Footer/PaginatedParticipants.d.ts +5 -0
  10. package/dist/Prebuilt/components/Footer/RoleAccordion.d.ts +10 -3
  11. package/dist/Prebuilt/components/Notifications/HeadlessEndRoomListener.d.ts +2 -0
  12. package/dist/Prebuilt/components/PrebuiltDialogPortal.d.ts +4 -0
  13. package/dist/Prebuilt/components/PrebuiltTileElements.d.ts +2198 -0
  14. package/dist/{VirtualBackground-3TI5NA4V.js → VirtualBackground-UVZJVOA2.js} +3 -3
  15. package/dist/{chunk-5DQ3WTED.js → chunk-6SQTFOK6.js} +2 -2
  16. package/dist/{chunk-5DQ3WTED.js.map → chunk-6SQTFOK6.js.map} +1 -1
  17. package/dist/{chunk-36X4ZCLC.js → chunk-HUMNPIYI.js} +2 -2
  18. package/dist/{chunk-Z7P5WITU.js → chunk-PRM33R4R.js} +591 -1186
  19. package/dist/chunk-PRM33R4R.js.map +7 -0
  20. package/dist/{conference-JNABIZBG.js → conference-N7S47TDK.js} +1505 -717
  21. package/dist/conference-N7S47TDK.js.map +7 -0
  22. package/dist/index.cjs.js +3083 -2825
  23. package/dist/index.cjs.js.map +4 -4
  24. package/dist/index.js +4 -2
  25. package/dist/meta.cjs.json +1107 -740
  26. package/dist/meta.esbuild.json +1160 -791
  27. package/package.json +6 -6
  28. package/src/AudioLevel/AudioLevel.tsx +79 -30
  29. package/src/AudioLevel/audio-level.png +0 -0
  30. package/src/AudioLevel/index.ts +2 -1
  31. package/src/AudioLevel/useBorderAudioLevel.tsx +34 -0
  32. package/src/Prebuilt/App.tsx +6 -0
  33. package/src/Prebuilt/AppContext.tsx +2 -0
  34. package/src/Prebuilt/common/constants.js +1 -1
  35. package/src/Prebuilt/common/utils.js +0 -7
  36. package/src/Prebuilt/components/AppData/AppData.jsx +1 -1
  37. package/src/Prebuilt/components/AppData/useUISettings.js +1 -1
  38. package/src/Prebuilt/components/{Chip.jsx → Chip.tsx} +18 -3
  39. package/src/Prebuilt/components/Footer/PaginatedParticipants.tsx +94 -0
  40. package/src/Prebuilt/components/Footer/ParticipantList.jsx +67 -26
  41. package/src/Prebuilt/components/Footer/RoleAccordion.tsx +91 -49
  42. package/src/Prebuilt/components/Footer/RoleOptions.tsx +1 -1
  43. package/src/Prebuilt/components/Leave/DesktopLeaveRoom.tsx +7 -4
  44. package/src/Prebuilt/components/MoreSettings/ChangeNameModal.jsx +3 -2
  45. package/src/Prebuilt/components/MoreSettings/EmbedUrl.jsx +3 -2
  46. package/src/Prebuilt/components/MwebLandscapePrompt.jsx +58 -0
  47. package/src/Prebuilt/components/Notifications/HLSFailureModal.jsx +3 -2
  48. package/src/Prebuilt/components/Notifications/HeadlessEndRoomListener.tsx +23 -0
  49. package/src/Prebuilt/components/Notifications/Notifications.jsx +1 -1
  50. package/src/Prebuilt/components/Notifications/PermissionErrorModal.jsx +3 -2
  51. package/src/Prebuilt/components/PrebuiltDialogPortal.tsx +6 -0
  52. package/src/Prebuilt/components/PrebuiltTileElements.tsx +5 -0
  53. package/src/Prebuilt/components/Preview/PreviewJoin.tsx +11 -7
  54. package/src/Prebuilt/components/RoleChangeModal.jsx +3 -2
  55. package/src/Prebuilt/components/RoleChangeRequest/RequestPrompt.tsx +3 -2
  56. package/src/Prebuilt/components/Settings/SettingsModal.jsx +3 -2
  57. package/src/Prebuilt/components/Settings/StartRecording.jsx +3 -2
  58. package/src/Prebuilt/components/SidePaneTabs.tsx +31 -5
  59. package/src/Prebuilt/components/StatsForNerds.jsx +3 -2
  60. package/src/Prebuilt/components/VideoTile.jsx +21 -83
  61. package/src/Prebuilt/components/conference.jsx +4 -3
  62. package/src/Prebuilt/components/hooks/useDropdownSelection.jsx +1 -1
  63. package/src/Prebuilt/components/pdfAnnotator/pdfFileOptions.jsx +3 -2
  64. package/src/Prebuilt/components/pdfAnnotator/shareScreenOptions.jsx +4 -29
  65. package/src/Prebuilt/components/pdfAnnotator/uploadedFile.jsx +3 -2
  66. package/src/Prebuilt/layouts/HLSView.jsx +1 -0
  67. package/src/Prebuilt/layouts/SidePane.tsx +1 -0
  68. package/src/Prebuilt/primitives/DialogContent.jsx +5 -4
  69. package/dist/chunk-Z7P5WITU.js.map +0 -7
  70. package/dist/conference-JNABIZBG.js.map +0 -7
  71. /package/dist/{VirtualBackground-3TI5NA4V.js.map → VirtualBackground-UVZJVOA2.js.map} +0 -0
  72. /package/dist/{chunk-36X4ZCLC.js.map → chunk-HUMNPIYI.js.map} +0 -0
package/package.json CHANGED
@@ -10,7 +10,7 @@
10
10
  "prebuilt",
11
11
  "roomkit"
12
12
  ],
13
- "version": "0.1.7",
13
+ "version": "0.1.8",
14
14
  "author": "100ms",
15
15
  "license": "MIT",
16
16
  "files": [
@@ -76,10 +76,10 @@
76
76
  "react": ">=17.0.2 <19.0.0"
77
77
  },
78
78
  "dependencies": {
79
- "@100mslive/hls-player": "0.1.16",
80
- "@100mslive/hms-virtual-background": "1.11.16",
81
- "@100mslive/react-icons": "0.8.16",
82
- "@100mslive/react-sdk": "0.8.16",
79
+ "@100mslive/hls-player": "0.1.17",
80
+ "@100mslive/hms-virtual-background": "1.11.17",
81
+ "@100mslive/react-icons": "0.8.17",
82
+ "@100mslive/react-sdk": "0.8.17",
83
83
  "@100mslive/types-prebuilt": "0.12.0",
84
84
  "@emoji-mart/data": "^1.0.6",
85
85
  "@emoji-mart/react": "^1.0.1",
@@ -115,5 +115,5 @@
115
115
  "uuid": "^8.3.2",
116
116
  "worker-timers": "^7.0.40"
117
117
  },
118
- "gitHead": "c06edac3e71481de08e948ffe0affaf705564ad1"
118
+ "gitHead": "966ed9ffac3d55586196ad0b738eb1977dfc2ff7"
119
119
  }
@@ -1,34 +1,83 @@
1
- import { useCallback, useRef } from 'react';
2
- import { HMSTrackID } from '@100mslive/hms-video-store';
3
- import { useAudioLevelStyles } from '@100mslive/react-sdk';
4
- import { useTheme } from '../Theme';
1
+ import React, { useEffect, useRef } from 'react';
2
+ import { selectTrackAudioByID, useHMSVanillaStore } from '@100mslive/react-sdk';
3
+ import { Box, Flex } from '../Layout';
4
+ import { keyframes } from '../Theme';
5
+ //@ts-ignore
6
+ import bg from './audio-level.png';
5
7
 
6
- /**
7
- * pass in a track id and get a ref. That ref can be attached to an element which will have border
8
- * as per audio level post that.
9
- */
10
- export function useBorderAudioLevel(audioTrackId?: HMSTrackID) {
11
- const { theme } = useTheme();
12
- const color = theme.colors.primary_default.value;
13
- const getStyle = useCallback(
14
- (level: number) => {
15
- const style: Record<string, string> = {
16
- transition: 'outline 0.4s ease-in-out',
17
- };
18
- style['outline'] = level ? `${sigmoid(level) * 4}px solid ${color}` : '0px solid transparent';
19
- return style;
20
- },
21
- [color],
8
+ // keep the calculated values before hand to avoid recalcuation everytime
9
+ const positionValues = new Array(101).fill(0).reduce((acc, _, index) => {
10
+ acc[index] = Math.round((index / 100) * 4) / 4; // convert to 0.25 multiples
11
+ return acc;
12
+ }, {});
13
+
14
+ const barAnimation = keyframes({
15
+ from: {
16
+ maskSize: '4em .8em',
17
+ '-webkit-mask-position-y': '.1em',
18
+ maskPosition: 'initial .1em',
19
+ },
20
+
21
+ '50%': {
22
+ maskSize: '4em 1em',
23
+ '-webkit-mask-position-y': 0,
24
+ maskPosition: 'initial 0',
25
+ },
26
+
27
+ to: {
28
+ maskSize: '4em .8em',
29
+ '-webkit-mask-position-y': '.1em',
30
+ maskPosition: 'initial 0.1em',
31
+ },
32
+ });
33
+
34
+ const AudioBar = () => {
35
+ return (
36
+ <Box
37
+ css={{
38
+ width: '.25em',
39
+ height: '1em',
40
+ maskImage: `url(${bg})`,
41
+ '-webkit-mask-repeat': 'no-repeat',
42
+ backgroundColor: '$on_primary_high',
43
+ maskSize: '4em 1em',
44
+ }}
45
+ />
22
46
  );
23
- const ref = useRef(null);
24
- useAudioLevelStyles({
25
- trackId: audioTrackId,
26
- getStyle,
27
- ref,
28
- });
29
- return ref;
30
- }
47
+ };
48
+
49
+ export const AudioLevel = ({ trackId, size }: { trackId?: string; size?: 'small' | 'medium' }) => {
50
+ const ref = useRef<HTMLDivElement | null>(null);
51
+ const vanillaStore = useHMSVanillaStore();
31
52
 
32
- export const sigmoid = (z: number) => {
33
- return 1 / (1 + Math.exp(-z));
53
+ useEffect(() => {
54
+ const unsubscribe = vanillaStore.subscribe(audioLevel => {
55
+ if (ref.current) {
56
+ let index = 0;
57
+ //@ts-ignore
58
+ for (const child of ref.current.children) {
59
+ const positionX = `-${positionValues[audioLevel] * (index === 1 ? 2.5 : 1.25)}em`;
60
+ child.style['-webkit-mask-position-x'] = positionX;
61
+ child.style['mask-position'] = `${positionX} 0`;
62
+ child.style['animation'] =
63
+ positionValues[audioLevel] > 0 ? `${barAnimation} 0.6s steps(3,jump-none) 0s infinite` : 'none';
64
+ index++;
65
+ }
66
+ }
67
+ }, selectTrackAudioByID(trackId));
68
+ return unsubscribe;
69
+ }, [vanillaStore, trackId]);
70
+ return (
71
+ <Flex
72
+ ref={ref}
73
+ css={{
74
+ fontSize: size === 'small' ? '0.75rem' : '1rem',
75
+ gap: size === 'small' ? '$1' : '$2',
76
+ }}
77
+ >
78
+ <AudioBar />
79
+ <AudioBar />
80
+ <AudioBar />
81
+ </Flex>
82
+ );
34
83
  };
Binary file
@@ -1 +1,2 @@
1
- export { useBorderAudioLevel } from './AudioLevel';
1
+ export { useBorderAudioLevel } from './useBorderAudioLevel';
2
+ export { AudioLevel } from './AudioLevel';
@@ -0,0 +1,34 @@
1
+ import { useCallback, useRef } from 'react';
2
+ import { HMSTrackID } from '@100mslive/hms-video-store';
3
+ import { useAudioLevelStyles } from '@100mslive/react-sdk';
4
+ import { useTheme } from '../Theme';
5
+
6
+ /**
7
+ * pass in a track id and get a ref. That ref can be attached to an element which will have border
8
+ * as per audio level post that.
9
+ */
10
+ export function useBorderAudioLevel(audioTrackId?: HMSTrackID) {
11
+ const { theme } = useTheme();
12
+ const color = theme.colors.primary_default.value;
13
+ const getStyle = useCallback(
14
+ (level: number) => {
15
+ const style: Record<string, string> = {
16
+ transition: 'outline 0.4s ease-in-out',
17
+ };
18
+ style['outline'] = level ? `${sigmoid(level) * 4}px solid ${color}` : '0px solid transparent';
19
+ return style;
20
+ },
21
+ [color],
22
+ );
23
+ const ref = useRef(null);
24
+ useAudioLevelStyles({
25
+ trackId: audioTrackId,
26
+ getStyle,
27
+ ref,
28
+ });
29
+ return ref;
30
+ }
31
+
32
+ export const sigmoid = (z: number) => {
33
+ return 1 / (1 + Math.exp(-z));
34
+ };
@@ -24,6 +24,7 @@ import { Init } from './components/init/Init';
24
24
  import { KeyboardHandler } from './components/Input/KeyboardInputManager';
25
25
  // @ts-ignore: No implicit Any
26
26
  import { Notifications } from './components/Notifications';
27
+ import { HeadlessEndRoomListener } from './components/Notifications/HeadlessEndRoomListener';
27
28
  // @ts-ignore: No implicit Any
28
29
  import PostLeave from './components/PostLeave';
29
30
  // @ts-ignore: No implicit Any
@@ -68,6 +69,7 @@ export type HMSPrebuiltProps = {
68
69
  roomId?: string;
69
70
  role?: string;
70
71
  onLeave?: () => void;
72
+ onJoin?: () => void;
71
73
  };
72
74
 
73
75
  export type HMSPrebuiltRefType = {
@@ -90,6 +92,7 @@ export const HMSPrebuilt = React.forwardRef<HMSPrebuiltRefType, HMSPrebuiltProps
90
92
  options: { userName = '', userId = '', endpoints } = {},
91
93
  screens,
92
94
  onLeave,
95
+ onJoin,
93
96
  },
94
97
  ref,
95
98
  ) => {
@@ -172,6 +175,7 @@ export const HMSPrebuilt = React.forwardRef<HMSPrebuiltRefType, HMSPrebuiltProps
172
175
  roomId,
173
176
  role,
174
177
  onLeave,
178
+ onJoin,
175
179
  userName,
176
180
  userId,
177
181
  endpoints: {
@@ -218,6 +222,7 @@ export const HMSPrebuilt = React.forwardRef<HMSPrebuiltRefType, HMSPrebuiltProps
218
222
  <AppData appDetails={metadata} tokenEndpoint={tokenByRoomIdRoleEndpoint} />
219
223
  <Init />
220
224
  <Box
225
+ id="prebuilt-container"
221
226
  css={{
222
227
  bg: '$background_dim',
223
228
  size: '100%',
@@ -348,6 +353,7 @@ function AppRoutes({
348
353
  <BackSwipe />
349
354
  {!isNotificationsDisabled && <FlyingEmoji />}
350
355
  <RemoteStopScreenshare />
356
+ <HeadlessEndRoomListener />
351
357
  <KeyboardHandler />
352
358
  <AuthToken authTokenByRoomCodeEndpoint={authTokenByRoomCodeEndpoint} defaultAuthToken={defaultAuthToken} />
353
359
  {roomLayout && (
@@ -8,6 +8,7 @@ type HMSPrebuiltContextType = {
8
8
  userId?: string;
9
9
  endpoints?: Record<string, string>;
10
10
  onLeave?: () => void;
11
+ onJoin?: () => void;
11
12
  };
12
13
 
13
14
  export const HMSPrebuiltContext = React.createContext<HMSPrebuiltContextType>({
@@ -16,6 +17,7 @@ export const HMSPrebuiltContext = React.createContext<HMSPrebuiltContextType>({
16
17
  userId: '',
17
18
  endpoints: {},
18
19
  onLeave: undefined,
20
+ onJoin: undefined,
19
21
  });
20
22
 
21
23
  HMSPrebuiltContext.displayName = 'HMSPrebuiltContext';
@@ -44,7 +44,7 @@ export const APP_DATA = {
44
44
  pdfConfig: 'pdfConfig',
45
45
  minimiseInset: 'minimiseInset',
46
46
  activeScreensharePeerId: 'activeScreensharePeerId',
47
- disableNotificiations: 'disableNotificiations',
47
+ disableNotifications: 'disableNotifications',
48
48
  };
49
49
  export const UI_SETTINGS = {
50
50
  isAudioOnly: 'isAudioOnly',
@@ -88,10 +88,3 @@ export const formatTime = timeInSeconds => {
88
88
  const hour = hours !== 0 ? `${hours < 10 ? '0' : ''}${hours}:` : '';
89
89
  return `${hour}${minutes < 10 ? '0' : ''}${minutes}:${seconds < 10 ? '0' : ''}${seconds}`;
90
90
  };
91
-
92
- export const getAttributeBoxSize = (width, height) => {
93
- if (!width || !height) {
94
- return '';
95
- }
96
- return width < 180 || height < 180 ? 'small' : 'medium';
97
- };
@@ -65,7 +65,7 @@ const initialAppData = {
65
65
  [APP_DATA.authToken]: '',
66
66
  [APP_DATA.minimiseInset]: false,
67
67
  [APP_DATA.activeScreensharePeerId]: '',
68
- [APP_DATA.disableNotificiations]: false,
68
+ [APP_DATA.disableNotifications]: false,
69
69
  };
70
70
 
71
71
  export const AppData = React.memo(({ appDetails, tokenEndpoint }) => {
@@ -86,7 +86,7 @@ export const useSubscribedNotifications = notificationKey => {
86
86
  };
87
87
 
88
88
  export const useIsNotificationDisabled = () => {
89
- const notificationPreference = useHMSStore(selectAppDataByPath(APP_DATA.disableNotificiations));
89
+ const notificationPreference = useHMSStore(selectAppDataByPath(APP_DATA.disableNotifications));
90
90
  return notificationPreference;
91
91
  };
92
92
 
@@ -1,6 +1,7 @@
1
1
  import React from 'react';
2
2
  import { Flex } from '../../Layout';
3
3
  import { Text } from '../../Text';
4
+ import { CSS } from '../../Theme';
4
5
 
5
6
  const Chip = ({
6
7
  icon = <></>,
@@ -8,14 +9,28 @@ const Chip = ({
8
9
  backgroundColor = '$surface_default',
9
10
  textColor = '$on_surface_high',
10
11
  hideIfNoContent = false,
12
+ onClick,
13
+ css = {},
14
+ }: {
15
+ icon?: React.JSX.Element;
16
+ content: string;
17
+ backgroundColor?: string;
18
+ textColor?: string;
19
+ hideIfNoContent?: boolean;
20
+ onClick?: () => void | Promise<void>;
21
+ css?: CSS;
11
22
  }) => {
12
23
  if (hideIfNoContent && !content) {
13
- return;
24
+ return null;
14
25
  }
15
26
  return (
16
- <Flex align="center" css={{ backgroundColor, p: '$4 $6', borderRadius: '$4' }}>
27
+ <Flex
28
+ align="center"
29
+ css={{ backgroundColor, p: '$4 $6', gap: '$2', borderRadius: '$4', ...css }}
30
+ onClick={() => onClick?.()}
31
+ >
17
32
  {icon}
18
- <Text variant="sm" css={{ fontWeight: '$semiBold', color: textColor, ml: '$2' }}>
33
+ <Text variant="sm" css={{ fontWeight: '$semiBold', color: textColor }}>
19
34
  {content}
20
35
  </Text>
21
36
  </Flex>
@@ -0,0 +1,94 @@
1
+ import React, { useEffect, useRef, useState } from 'react';
2
+ import { useMeasure } from 'react-use';
3
+ import { FixedSizeList } from 'react-window';
4
+ import { selectIsConnectedToRoom, useHMSStore, usePaginatedParticipants } from '@100mslive/react-sdk';
5
+ import { ChevronLeftIcon, CrossIcon } from '@100mslive/react-icons';
6
+ import { Button } from '../../../Button';
7
+ import { IconButton } from '../../../IconButton';
8
+ import { Box, Flex } from '../../../Layout';
9
+ import { Loading } from '../../../Loading';
10
+ import { Text } from '../../../Text';
11
+ // @ts-ignore: No implicit Any
12
+ import { ParticipantSearch } from './ParticipantList';
13
+ import { itemKey, ROW_HEIGHT, VirtualizedParticipantItem } from './RoleAccordion';
14
+ // @ts-ignore: No implicit Any
15
+ import { useSidepaneReset } from '../AppData/useSidepane';
16
+ // @ts-ignore: No implicit Any
17
+ import { getFormattedCount } from '../../common/utils';
18
+
19
+ export const PaginatedParticipants = ({ roleName, onBack }: { roleName: string; onBack: () => void }) => {
20
+ const { peers, total, loadPeers, loadMorePeers } = usePaginatedParticipants({ role: roleName, limit: 20 });
21
+ const [search, setSearch] = useState<string>('');
22
+ const [isLoading, setIsLoading] = useState(false);
23
+ const filteredPeers = peers.filter(p => p.name?.toLowerCase().includes(search));
24
+ const isConnected = useHMSStore(selectIsConnectedToRoom);
25
+ const [ref, { width }] = useMeasure<HTMLDivElement>();
26
+ const containerRef = useRef<HTMLDivElement | null>(null);
27
+ const height = ROW_HEIGHT * peers.length;
28
+ const resetSidePane = useSidepaneReset();
29
+ const hasNext = total > peers.length;
30
+
31
+ useEffect(() => {
32
+ loadPeers();
33
+ // eslint-disable-next-line react-hooks/exhaustive-deps
34
+ }, []);
35
+
36
+ return (
37
+ <Flex ref={ref} direction="column" css={{ size: '100%', gap: '$4' }}>
38
+ <Flex align="center">
39
+ <Flex align="center" css={{ flex: '1 1 0', cursor: 'pointer' }} onClick={onBack}>
40
+ <ChevronLeftIcon />
41
+ <Text variant="lg" css={{ flex: '1 1 0' }}>
42
+ Participants
43
+ </Text>
44
+ </Flex>
45
+ <IconButton
46
+ onClick={e => {
47
+ e.stopPropagation();
48
+ resetSidePane();
49
+ }}
50
+ data-testid="close_sidepane"
51
+ >
52
+ <CrossIcon />
53
+ </IconButton>
54
+ </Flex>
55
+ <ParticipantSearch onSearch={(search: string) => setSearch(search)} placeholder={`Search for ${roleName}`} />
56
+ <Flex direction="column" css={{ border: '1px solid $border_default', borderRadius: '$1', flex: '1 1 0' }}>
57
+ <Flex align="center" css={{ height: ROW_HEIGHT, borderBottom: '1px solid $border_default', px: '$8' }}>
58
+ <Text css={{ fontSize: '$space$7' }}>
59
+ {roleName}({getFormattedCount(peers.length)}/{getFormattedCount(total)})
60
+ </Text>
61
+ </Flex>
62
+ <Box css={{ flex: '1 1 0', overflowY: 'auto', overflowX: 'hidden', mr: '-$10' }}>
63
+ <FixedSizeList
64
+ itemSize={ROW_HEIGHT}
65
+ itemData={{ peerList: filteredPeers, isConnected: isConnected === true }}
66
+ itemKey={itemKey}
67
+ itemCount={filteredPeers.length}
68
+ width={width}
69
+ height={height}
70
+ outerRef={containerRef}
71
+ >
72
+ {VirtualizedParticipantItem}
73
+ </FixedSizeList>
74
+ {hasNext ? (
75
+ <Flex justify="center" css={{ w: '100%' }}>
76
+ <Button
77
+ css={{ w: 'max-content', p: '$4' }}
78
+ onClick={() => {
79
+ setIsLoading(true);
80
+ loadMorePeers()
81
+ .catch(console.error)
82
+ .finally(() => setIsLoading(false));
83
+ }}
84
+ disabled={isLoading}
85
+ >
86
+ {isLoading ? <Loading size={16} /> : 'Load More'}
87
+ </Button>
88
+ </Flex>
89
+ ) : null}
90
+ </Box>
91
+ </Flex>
92
+ </Flex>
93
+ );
94
+ };
@@ -3,6 +3,7 @@ import { useDebounce, useMedia } from 'react-use';
3
3
  import {
4
4
  selectHandRaisedPeers,
5
5
  selectHasPeerHandRaised,
6
+ selectIsLargeRoom,
6
7
  selectIsPeerAudioEnabled,
7
8
  selectLocalPeerID,
8
9
  selectPeerCount,
@@ -20,7 +21,7 @@ import {
20
21
  SearchIcon,
21
22
  VerticalMenuIcon,
22
23
  } from '@100mslive/react-icons';
23
- import { Box, config as cssConfig, Dropdown, Flex, Input, Text, textEllipsis } from '../../..';
24
+ import { Accordion, Box, config as cssConfig, Dropdown, Flex, Input, Text, textEllipsis } from '../../..';
24
25
  import IconButton from '../../IconButton';
25
26
  import { ConnectionIndicator } from '../Connection/ConnectionIndicator';
26
27
  import { ToastManager } from '../Toast/ToastManager';
@@ -31,9 +32,10 @@ import { useParticipants } from '../../common/hooks';
31
32
  import { getFormattedCount } from '../../common/utils';
32
33
  import { SIDE_PANE_OPTIONS } from '../../common/constants';
33
34
 
34
- export const ParticipantList = () => {
35
+ export const ParticipantList = ({ offStageRoles = [], onActive }) => {
35
36
  const [filter, setFilter] = useState();
36
37
  const { participants, isConnected, peerCount } = useParticipants(filter);
38
+ const isLargeRoom = useHMSStore(selectIsLargeRoom);
37
39
  const peersOrderedByRoles = {};
38
40
 
39
41
  const handRaisedPeers = useHMSStore(selectHandRaisedPeers);
@@ -45,6 +47,15 @@ export const ParticipantList = () => {
45
47
  peersOrderedByRoles[participant.roleName].push(participant);
46
48
  });
47
49
 
50
+ // prefill off_stage roles of large rooms to load more peers
51
+ if (isLargeRoom) {
52
+ offStageRoles.forEach(role => {
53
+ if (!peersOrderedByRoles[role]) {
54
+ peersOrderedByRoles[role] = [];
55
+ }
56
+ });
57
+ }
58
+
48
59
  const onSearch = useCallback(value => {
49
60
  setFilter(filterValue => {
50
61
  if (!filterValue) {
@@ -72,6 +83,9 @@ export const ParticipantList = () => {
72
83
  handRaisedList={handRaisedPeers}
73
84
  isConnected={isConnected}
74
85
  filter={filter}
86
+ offStageRoles={offStageRoles}
87
+ isLargeRoom={isLargeRoom}
88
+ onActive={onActive}
75
89
  />
76
90
  </Flex>
77
91
  </Fragment>
@@ -114,7 +128,15 @@ export const ParticipantCount = () => {
114
128
  );
115
129
  };
116
130
 
117
- const VirtualizedParticipants = ({ peersOrderedByRoles = {}, isConnected, filter, handRaisedList = [] }) => {
131
+ const VirtualizedParticipants = ({
132
+ peersOrderedByRoles = {},
133
+ isConnected,
134
+ filter,
135
+ handRaisedList = [],
136
+ offStageRoles,
137
+ isLargeRoom,
138
+ onActive,
139
+ }) => {
118
140
  return (
119
141
  <Flex
120
142
  direction="column"
@@ -127,22 +149,29 @@ const VirtualizedParticipants = ({ peersOrderedByRoles = {}, isConnected, filter
127
149
  flex: '1 1 0',
128
150
  }}
129
151
  >
130
- <RoleAccordion
131
- peerList={handRaisedList}
132
- roleName="Hand Raised"
133
- filter={filter}
134
- isConnected={isConnected}
135
- isHandRaisedAccordion
136
- />
137
- {Object.keys(peersOrderedByRoles).map(role => (
138
- <RoleAccordion
139
- key={role}
140
- peerList={peersOrderedByRoles[role]}
141
- roleName={role}
142
- isConnected={isConnected}
143
- filter={filter}
144
- />
145
- ))}
152
+ <Accordion.Root type={isLargeRoom ? 'single' : 'multiple'} collapsible>
153
+ {handRaisedList.length > 0 ? (
154
+ <RoleAccordion
155
+ peerList={handRaisedList}
156
+ roleName="Hand Raised"
157
+ filter={filter}
158
+ isConnected={isConnected}
159
+ isHandRaisedAccordion
160
+ offStageRoles={offStageRoles}
161
+ />
162
+ ) : null}
163
+ {Object.keys(peersOrderedByRoles).map(role => (
164
+ <RoleAccordion
165
+ key={role}
166
+ peerList={peersOrderedByRoles[role]}
167
+ roleName={role}
168
+ isConnected={isConnected}
169
+ filter={filter}
170
+ offStageRoles={offStageRoles}
171
+ onActive={onActive}
172
+ />
173
+ ))}
174
+ </Accordion.Root>
146
175
  </Flex>
147
176
  );
148
177
  };
@@ -163,7 +192,10 @@ export const Participant = ({ peer, isConnected }) => {
163
192
  justify="between"
164
193
  data-testid={'participant_' + peer.name}
165
194
  >
166
- <Text variant="sm" css={{ ...textEllipsis(150), fontWeight: '$semiBold', color: '$on_surface_high' }}>
195
+ <Text
196
+ variant="sm"
197
+ css={{ ...textEllipsis('100%'), flex: '1 1 0', mr: '$8', fontWeight: '$semiBold', color: '$on_surface_high' }}
198
+ >
167
199
  {peer.name} {localPeerId === peer.id ? '(You)' : ''}
168
200
  </Text>
169
201
  {isConnected ? (
@@ -179,7 +211,10 @@ export const Participant = ({ peer, isConnected }) => {
179
211
  const ParticipantActions = React.memo(({ peerId, role, isLocal }) => {
180
212
  const isHandRaised = useHMSStore(selectHasPeerHandRaised(peerId));
181
213
  const canChangeRole = useHMSStore(selectPermissions)?.changeRole;
182
- const shouldShowMoreActions = canChangeRole;
214
+ const canRemoveOthers = useHMSStore(selectPermissions)?.removeOthers;
215
+ const { elements } = useRoomLayoutConferencingScreen();
216
+ const { on_stage_exp } = elements || {};
217
+ const shouldShowMoreActions = (on_stage_exp && canChangeRole) || canRemoveOthers;
183
218
  const isAudioMuted = !useHMSStore(selectIsPeerAudioEnabled(peerId));
184
219
 
185
220
  return (
@@ -210,15 +245,21 @@ const ParticipantActions = React.memo(({ peerId, role, isLocal }) => {
210
245
  </Flex>
211
246
  ) : null}
212
247
 
213
- {shouldShowMoreActions && !isLocal ? <ParticipantMoreActions peerId={peerId} role={role} /> : null}
248
+ {shouldShowMoreActions && !isLocal ? (
249
+ <ParticipantMoreActions
250
+ peerId={peerId}
251
+ role={role}
252
+ elements={elements}
253
+ canChangeRole={canChangeRole}
254
+ canRemoveOthers={canRemoveOthers}
255
+ />
256
+ ) : null}
214
257
  </Flex>
215
258
  );
216
259
  });
217
260
 
218
- const ParticipantMoreActions = ({ peerId, role }) => {
261
+ const ParticipantMoreActions = ({ peerId, role, elements, canChangeRole, canRemoveOthers }) => {
219
262
  const hmsActions = useHMSActions();
220
- const { changeRole: canChangeRole, removeOthers: canRemoveOthers } = useHMSStore(selectPermissions);
221
- const { elements } = useRoomLayoutConferencingScreen();
222
263
  const {
223
264
  bring_to_stage_label,
224
265
  remove_from_stage_label,
@@ -327,7 +368,7 @@ export const ParticipantSearch = ({ onSearch, placeholder, inSidePane = false })
327
368
  <Input
328
369
  type="text"
329
370
  placeholder={placeholder || 'Search for participants'}
330
- css={{ w: '100%', p: '$6', pl: '$16', mr: '$4', bg: inSidePane ? '$surface_default' : '$surface_dim' }}
371
+ css={{ w: '100%', p: '$6', pl: '$14', bg: inSidePane ? '$surface_default' : '$surface_dim' }}
331
372
  value={value}
332
373
  onKeyDown={event => {
333
374
  event.stopPropagation();