@100mslive/roomkit-react 0.1.7 → 0.1.8

Sign up to get free protection for your applications and to get access to all the features.
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();