@100mslive/roomkit-react 0.1.8-alpha.0 → 0.1.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. package/dist/{HLSView-IQRPLYNH.js → HLSView-DDGPZHA2.js} +3 -3
  2. package/dist/Prebuilt/App.d.ts +1 -0
  3. package/dist/Prebuilt/AppContext.d.ts +1 -0
  4. package/dist/Prebuilt/components/Footer/PaginatedParticipants.d.ts +5 -0
  5. package/dist/Prebuilt/components/Footer/RoleAccordion.d.ts +10 -3
  6. package/dist/Prebuilt/components/Notifications/HeadlessEndRoomListener.d.ts +2 -0
  7. package/dist/Prebuilt/components/PrebuiltTileElements.d.ts +2198 -0
  8. package/dist/{VirtualBackground-GP4ATXD3.js → VirtualBackground-UVZJVOA2.js} +3 -3
  9. package/dist/{chunk-Z3O2WGWV.js → chunk-6SQTFOK6.js} +2 -2
  10. package/dist/{chunk-Z3O2WGWV.js.map → chunk-6SQTFOK6.js.map} +1 -1
  11. package/dist/{chunk-2H5NIZB7.js → chunk-HUMNPIYI.js} +2 -2
  12. package/dist/{chunk-GLYGPYNS.js → chunk-PRM33R4R.js} +286 -251
  13. package/dist/chunk-PRM33R4R.js.map +7 -0
  14. package/dist/{conference-JD35TNH4.js → conference-N7S47TDK.js} +484 -385
  15. package/dist/conference-N7S47TDK.js.map +7 -0
  16. package/dist/index.cjs.js +1895 -1727
  17. package/dist/index.cjs.js.map +4 -4
  18. package/dist/index.js +2 -2
  19. package/dist/meta.cjs.json +234 -42
  20. package/dist/meta.esbuild.json +267 -74
  21. package/package.json +6 -6
  22. package/src/AudioLevel/AudioLevel.tsx +1 -1
  23. package/src/Prebuilt/App.tsx +5 -0
  24. package/src/Prebuilt/AppContext.tsx +2 -0
  25. package/src/Prebuilt/common/constants.js +1 -1
  26. package/src/Prebuilt/components/AppData/AppData.jsx +1 -1
  27. package/src/Prebuilt/components/AppData/useUISettings.js +1 -1
  28. package/src/Prebuilt/components/Chip.tsx +6 -2
  29. package/src/Prebuilt/components/Footer/PaginatedParticipants.tsx +94 -0
  30. package/src/Prebuilt/components/Footer/ParticipantList.jsx +53 -23
  31. package/src/Prebuilt/components/Footer/RoleAccordion.tsx +86 -84
  32. package/src/Prebuilt/components/Footer/RoleOptions.tsx +1 -1
  33. package/src/Prebuilt/components/Notifications/HeadlessEndRoomListener.tsx +23 -0
  34. package/src/Prebuilt/components/Notifications/Notifications.jsx +1 -1
  35. package/src/Prebuilt/components/PrebuiltTileElements.tsx +5 -0
  36. package/src/Prebuilt/components/Preview/PreviewJoin.tsx +9 -6
  37. package/src/Prebuilt/components/SidePaneTabs.tsx +31 -5
  38. package/src/Prebuilt/components/VideoTile.jsx +19 -34
  39. package/src/Prebuilt/components/conference.jsx +4 -3
  40. package/src/Prebuilt/components/hooks/useDropdownSelection.jsx +1 -1
  41. package/src/Prebuilt/layouts/SidePane.tsx +1 -0
  42. package/dist/chunk-GLYGPYNS.js.map +0 -7
  43. package/dist/conference-JD35TNH4.js.map +0 -7
  44. /package/dist/{HLSView-IQRPLYNH.js.map → HLSView-DDGPZHA2.js.map} +0 -0
  45. /package/dist/{VirtualBackground-GP4ATXD3.js.map → VirtualBackground-UVZJVOA2.js.map} +0 -0
  46. /package/dist/{chunk-2H5NIZB7.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.8-alpha.0",
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.17-alpha.0",
80
- "@100mslive/hms-virtual-background": "1.11.17-alpha.0",
81
- "@100mslive/react-icons": "0.8.17-alpha.0",
82
- "@100mslive/react-sdk": "0.8.17-alpha.0",
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": "d6e072de081b508a297ebc1684a33a8e9db048ef"
118
+ "gitHead": "966ed9ffac3d55586196ad0b738eb1977dfc2ff7"
119
119
  }
@@ -27,7 +27,7 @@ const barAnimation = keyframes({
27
27
  to: {
28
28
  maskSize: '4em .8em',
29
29
  '-webkit-mask-position-y': '.1em',
30
- maskPosition: 'initial 0',
30
+ maskPosition: 'initial 0.1em',
31
31
  },
32
32
  });
33
33
 
@@ -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: {
@@ -349,6 +353,7 @@ function AppRoutes({
349
353
  <BackSwipe />
350
354
  {!isNotificationsDisabled && <FlyingEmoji />}
351
355
  <RemoteStopScreenshare />
356
+ <HeadlessEndRoomListener />
352
357
  <KeyboardHandler />
353
358
  <AuthToken authTokenByRoomCodeEndpoint={authTokenByRoomCodeEndpoint} defaultAuthToken={defaultAuthToken} />
354
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',
@@ -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
 
@@ -24,9 +24,13 @@ const Chip = ({
24
24
  return null;
25
25
  }
26
26
  return (
27
- <Flex align="center" css={{ backgroundColor, p: '$4 $6', borderRadius: '$4', ...css }} onClick={() => onClick?.()}>
27
+ <Flex
28
+ align="center"
29
+ css={{ backgroundColor, p: '$4 $6', gap: '$2', borderRadius: '$4', ...css }}
30
+ onClick={() => onClick?.()}
31
+ >
28
32
  {icon}
29
- <Text variant="sm" css={{ fontWeight: '$semiBold', color: textColor, ml: '$2' }}>
33
+ <Text variant="sm" css={{ fontWeight: '$semiBold', color: textColor }}>
30
34
  {content}
31
35
  </Text>
32
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,24 +149,29 @@ const VirtualizedParticipants = ({ peersOrderedByRoles = {}, isConnected, filter
127
149
  flex: '1 1 0',
128
150
  }}
129
151
  >
130
- {handRaisedList.length > 0 ? (
131
- <RoleAccordion
132
- peerList={handRaisedList}
133
- roleName="Hand Raised"
134
- filter={filter}
135
- isConnected={isConnected}
136
- isHandRaisedAccordion
137
- />
138
- ) : null}
139
- {Object.keys(peersOrderedByRoles).map(role => (
140
- <RoleAccordion
141
- key={role}
142
- peerList={peersOrderedByRoles[role]}
143
- roleName={role}
144
- isConnected={isConnected}
145
- filter={filter}
146
- />
147
- ))}
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>
148
175
  </Flex>
149
176
  );
150
177
  };
@@ -165,7 +192,10 @@ export const Participant = ({ peer, isConnected }) => {
165
192
  justify="between"
166
193
  data-testid={'participant_' + peer.name}
167
194
  >
168
- <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
+ >
169
199
  {peer.name} {localPeerId === peer.id ? '(You)' : ''}
170
200
  </Text>
171
201
  {isConnected ? (
@@ -338,7 +368,7 @@ export const ParticipantSearch = ({ onSearch, placeholder, inSidePane = false })
338
368
  <Input
339
369
  type="text"
340
370
  placeholder={placeholder || 'Search for participants'}
341
- 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' }}
342
372
  value={value}
343
373
  onKeyDown={event => {
344
374
  event.stopPropagation();
@@ -1,30 +1,30 @@
1
- import React, { useCallback, useEffect, useRef, useState } from 'react';
1
+ import React, { useEffect } from 'react';
2
2
  import { useMeasure } from 'react-use';
3
3
  import { FixedSizeList } from 'react-window';
4
- import { HMSPeer, HMSPeerListIterator, useHMSActions } from '@100mslive/react-sdk';
5
- import { AddCircleIcon } from '@100mslive/react-icons';
4
+ import { HMSPeer, selectIsLargeRoom, useHMSStore, usePaginatedParticipants } from '@100mslive/react-sdk';
5
+ import { ChevronRightIcon } from '@100mslive/react-icons';
6
6
  import { Accordion } from '../../../Accordion';
7
- import { Box, Flex } from '../../../Layout';
7
+ import { Flex } from '../../../Layout';
8
8
  import { Text } from '../../../Text';
9
- import Chip from '../Chip';
10
9
  // @ts-ignore: No implicit Any
11
10
  import { Participant } from './ParticipantList';
12
11
  import { RoleOptions } from './RoleOptions';
13
12
  // @ts-ignore: No implicit Any
14
13
  import { getFormattedCount } from '../../common/utils';
15
14
 
16
- const ROW_HEIGHT = 50;
15
+ export const ROW_HEIGHT = 50;
16
+ const ITER_TIMER = 5000;
17
17
 
18
- interface ItemData {
18
+ export interface ItemData {
19
19
  peerList: HMSPeer[];
20
20
  isConnected: boolean;
21
21
  }
22
22
 
23
- function itemKey(index: number, data: ItemData) {
23
+ export function itemKey(index: number, data: ItemData) {
24
24
  return data.peerList[index].id;
25
25
  }
26
26
 
27
- const VirtualizedParticipantItem = React.memo(({ index, data }: { index: number; data: ItemData }) => {
27
+ export const VirtualizedParticipantItem = React.memo(({ index, data }: { index: number; data: ItemData }) => {
28
28
  return <Participant key={data.peerList[index].id} peer={data.peerList[index]} isConnected={data.isConnected} />;
29
29
  });
30
30
 
@@ -34,101 +34,103 @@ export const RoleAccordion = ({
34
34
  isConnected,
35
35
  filter,
36
36
  isHandRaisedAccordion = false,
37
+ offStageRoles,
38
+ onActive,
37
39
  }: ItemData & {
38
40
  roleName: string;
39
41
  isHandRaisedAccordion?: boolean;
40
42
  filter?: { search: string };
43
+ offStageRoles: string[];
44
+ onActive?: (role: string) => void;
41
45
  }) => {
42
46
  const [ref, { width }] = useMeasure<HTMLDivElement>();
43
- const actions = useHMSActions();
44
47
  const showAcordion = filter?.search ? peerList.some(peer => peer.name.toLowerCase().includes(filter.search)) : true;
45
- const [hasNext, setHasNext] = useState(false);
46
- const iteratorRef = useRef<HMSPeerListIterator | null>(null);
48
+ const isLargeRoom = useHMSStore(selectIsLargeRoom);
49
+ const { peers, total, loadPeers } = usePaginatedParticipants({ role: roleName, limit: 10 });
50
+ const isOffStageRole = roleName && offStageRoles.includes(roleName);
47
51
 
48
- const loadNext = useCallback(() => {
49
- if (!roleName || roleName === 'Hand Raised') {
52
+ useEffect(() => {
53
+ if (!isOffStageRole || !isLargeRoom) {
50
54
  return;
51
55
  }
52
- if (!iteratorRef.current) {
53
- iteratorRef.current = actions.getPeerListIterator({ role: roleName });
54
- }
55
- iteratorRef.current
56
- .next()
57
- .catch(console.error)
58
- .finally(() => {
59
- setHasNext(iteratorRef.current ? iteratorRef.current.hasNext() : false);
60
- });
61
- }, [actions, roleName]);
62
-
63
- useEffect(() => {
64
- loadNext();
65
- }, [loadNext]);
56
+ loadPeers();
57
+ const interval = setInterval(() => {
58
+ loadPeers();
59
+ }, ITER_TIMER);
60
+ return () => clearInterval(interval);
61
+ }, [isOffStageRole, isLargeRoom]); //eslint-disable-line
66
62
 
67
63
  if (!showAcordion || (isHandRaisedAccordion && filter?.search) || (peerList.length === 0 && filter?.search)) {
68
64
  return null;
69
65
  }
70
66
 
71
- const height = ROW_HEIGHT * peerList.length;
67
+ const height = ROW_HEIGHT * (peers.length || peerList.length);
68
+ const peersInAccordion = isOffStageRole && isLargeRoom ? peers : peerList;
69
+ const hasNext = total > peersInAccordion.length;
70
+
71
+ if (peersInAccordion.length === 0) {
72
+ return null;
73
+ }
72
74
 
73
75
  return (
74
- <Flex direction="column" css={{ '&:hover .role_actions': { visibility: 'visible' } }} ref={ref}>
75
- <Accordion.Root
76
- type="single"
77
- collapsible
78
- defaultValue={roleName}
79
- css={{ borderRadius: '$1', border: '1px solid $border_bright' }}
76
+ <Accordion.Item value={roleName} css={{ '&:hover .role_actions': { visibility: 'visible' }, mb: '$8' }} ref={ref}>
77
+ <Accordion.Header
78
+ iconStyles={{ c: '$on_surface_high' }}
79
+ css={{
80
+ textTransform: 'capitalize',
81
+ p: '$6 $8',
82
+ fontSize: '$sm',
83
+ fontWeight: '$semiBold',
84
+ c: '$on_surface_medium',
85
+ borderRadius: '$1',
86
+ border: '1px solid $border_default',
87
+ '&[data-state="open"]': {
88
+ borderBottomLeftRadius: 0,
89
+ borderBottomRightRadius: 0,
90
+ },
91
+ }}
80
92
  >
81
- <Accordion.Item value={roleName}>
82
- <Accordion.Header
83
- iconStyles={{ c: '$on_surface_high' }}
93
+ <Flex justify="between" css={{ flexGrow: 1, pr: '$6' }}>
94
+ <Text
95
+ variant="sm"
96
+ css={{ fontWeight: '$semiBold', textTransform: 'capitalize', color: '$on_surface_medium' }}
97
+ >
98
+ {roleName} {`(${getFormattedCount(isLargeRoom && isOffStageRole ? total : peerList.length)})`}
99
+ </Text>
100
+ <RoleOptions roleName={roleName} peerList={peersInAccordion} />
101
+ </Flex>
102
+ </Accordion.Header>
103
+ <Accordion.Content contentStyles={{ border: '1px solid $border_default', borderTop: 'none' }}>
104
+ <FixedSizeList
105
+ itemSize={ROW_HEIGHT}
106
+ itemData={{ peerList: peersInAccordion, isConnected }}
107
+ itemKey={itemKey}
108
+ itemCount={peersInAccordion.length}
109
+ width={width}
110
+ height={height}
111
+ >
112
+ {VirtualizedParticipantItem}
113
+ </FixedSizeList>
114
+ {offStageRoles?.includes(roleName) && hasNext ? (
115
+ <Flex
116
+ align="center"
117
+ justify="end"
84
118
  css={{
85
- textTransform: 'capitalize',
86
- p: '$6 $8',
87
- fontSize: '$sm',
88
- fontWeight: '$semiBold',
89
- c: '$on_surface_medium',
119
+ gap: '$1',
120
+ cursor: 'pointer',
121
+ color: '$on_surface_high',
122
+ p: '$6',
123
+ borderTop: '1px solid $border_default',
90
124
  }}
125
+ onClick={() => onActive?.(roleName)}
91
126
  >
92
- <Flex justify="between" css={{ flexGrow: 1, pr: '$6' }}>
93
- <Text
94
- variant="sm"
95
- css={{ fontWeight: '$semiBold', textTransform: 'capitalize', color: '$on_surface_medium' }}
96
- >
97
- {roleName} {`(${getFormattedCount(peerList.length)})`}
98
- </Text>
99
- <RoleOptions roleName={roleName} peerList={peerList} />
100
- </Flex>
101
- </Accordion.Header>
102
- <Accordion.Content>
103
- <Box css={{ borderTop: '1px solid $border_default' }} />
104
- <FixedSizeList
105
- itemSize={ROW_HEIGHT}
106
- itemData={{ peerList, isConnected }}
107
- itemKey={itemKey}
108
- itemCount={peerList.length}
109
- width={width}
110
- height={height}
111
- >
112
- {VirtualizedParticipantItem}
113
- </FixedSizeList>
114
- {hasNext ? (
115
- <Chip
116
- icon={<AddCircleIcon />}
117
- content="Load More"
118
- onClick={loadNext}
119
- backgroundColor="$secondary_default"
120
- css={{
121
- w: 'max-content',
122
- borderRadius: '$size$9',
123
- m: '$2 auto',
124
- p: '$4',
125
- cursor: 'pointer',
126
- }}
127
- />
128
- ) : null}
129
- </Accordion.Content>
130
- </Accordion.Item>
131
- </Accordion.Root>
132
- </Flex>
127
+ <Text variant="sm" css={{ color: 'inherit' }}>
128
+ View All
129
+ </Text>
130
+ <ChevronRightIcon />
131
+ </Flex>
132
+ ) : null}
133
+ </Accordion.Content>
134
+ </Accordion.Item>
133
135
  );
134
136
  };
@@ -46,7 +46,7 @@ export const RoleOptions = ({ roleName, peerList }: { roleName: string; peerList
46
46
  const canRemoveRoleFromRoom =
47
47
  permissions?.removeOthers && (on_stage_role === roleName || off_stage_roles?.includes(roleName));
48
48
 
49
- if (!(canMuteRole || canRemoveRoleFromStage || canRemoveRoleFromRoom)) {
49
+ if (!(canMuteRole || canRemoveRoleFromStage || canRemoveRoleFromRoom) || peerList.length === 0) {
50
50
  return null;
51
51
  }
52
52
 
@@ -0,0 +1,23 @@
1
+ import React, { useEffect } from 'react';
2
+ import { HMSNotificationTypes, useHMSNotifications } from '@100mslive/react-sdk';
3
+ // @ts-ignore
4
+ import { useIsNotificationDisabled } from '../AppData/useUISettings';
5
+ import { useRedirectToLeave } from '../hooks/useRedirectToLeave';
6
+
7
+ export function HeadlessEndRoomListener() {
8
+ const notification = useHMSNotifications();
9
+ const isNotificationDisabled = useIsNotificationDisabled();
10
+ const { redirectToLeave } = useRedirectToLeave();
11
+
12
+ useEffect(() => {
13
+ if (!notification || !isNotificationDisabled) {
14
+ return;
15
+ }
16
+ if ([HMSNotificationTypes.ROOM_ENDED, HMSNotificationTypes.REMOVED_FROM_ROOM].includes(notification.type)) {
17
+ redirectToLeave(1000);
18
+ }
19
+ // eslint-disable-next-line react-hooks/exhaustive-deps
20
+ }, [notification]);
21
+
22
+ return <></>;
23
+ }
@@ -104,7 +104,7 @@ export function Notifications() {
104
104
  close: false,
105
105
  });
106
106
  }
107
- // goto leave for terminal if any action is not performed within 2secs
107
+ // goto leave for terminal if any action is not performed within 1s
108
108
  // if network is still unavailable going to preview will throw an error
109
109
  redirectToLeave(1000);
110
110
  return;
@@ -0,0 +1,5 @@
1
+ import { styled } from '../../Theme';
2
+ import { StyledVideoTile } from '../../VideoTile';
3
+
4
+ export const PrebuiltAudioIndicator = styled(StyledVideoTile.AudioIndicator, { height: '$12', width: '$12' });
5
+ export const PrebuiltAttributeBox = styled(StyledVideoTile.AttributeBox, { height: '$12', width: '$12' });