@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.
- package/dist/{HLSView-IQRPLYNH.js → HLSView-DDGPZHA2.js} +3 -3
- package/dist/Prebuilt/App.d.ts +1 -0
- package/dist/Prebuilt/AppContext.d.ts +1 -0
- package/dist/Prebuilt/components/Footer/PaginatedParticipants.d.ts +5 -0
- package/dist/Prebuilt/components/Footer/RoleAccordion.d.ts +10 -3
- package/dist/Prebuilt/components/Notifications/HeadlessEndRoomListener.d.ts +2 -0
- package/dist/Prebuilt/components/PrebuiltTileElements.d.ts +2198 -0
- package/dist/{VirtualBackground-GP4ATXD3.js → VirtualBackground-UVZJVOA2.js} +3 -3
- package/dist/{chunk-Z3O2WGWV.js → chunk-6SQTFOK6.js} +2 -2
- package/dist/{chunk-Z3O2WGWV.js.map → chunk-6SQTFOK6.js.map} +1 -1
- package/dist/{chunk-2H5NIZB7.js → chunk-HUMNPIYI.js} +2 -2
- package/dist/{chunk-GLYGPYNS.js → chunk-PRM33R4R.js} +286 -251
- package/dist/chunk-PRM33R4R.js.map +7 -0
- package/dist/{conference-JD35TNH4.js → conference-N7S47TDK.js} +484 -385
- package/dist/conference-N7S47TDK.js.map +7 -0
- package/dist/index.cjs.js +1895 -1727
- package/dist/index.cjs.js.map +4 -4
- package/dist/index.js +2 -2
- package/dist/meta.cjs.json +234 -42
- package/dist/meta.esbuild.json +267 -74
- package/package.json +6 -6
- package/src/AudioLevel/AudioLevel.tsx +1 -1
- package/src/Prebuilt/App.tsx +5 -0
- package/src/Prebuilt/AppContext.tsx +2 -0
- package/src/Prebuilt/common/constants.js +1 -1
- package/src/Prebuilt/components/AppData/AppData.jsx +1 -1
- package/src/Prebuilt/components/AppData/useUISettings.js +1 -1
- package/src/Prebuilt/components/Chip.tsx +6 -2
- package/src/Prebuilt/components/Footer/PaginatedParticipants.tsx +94 -0
- package/src/Prebuilt/components/Footer/ParticipantList.jsx +53 -23
- package/src/Prebuilt/components/Footer/RoleAccordion.tsx +86 -84
- package/src/Prebuilt/components/Footer/RoleOptions.tsx +1 -1
- package/src/Prebuilt/components/Notifications/HeadlessEndRoomListener.tsx +23 -0
- package/src/Prebuilt/components/Notifications/Notifications.jsx +1 -1
- package/src/Prebuilt/components/PrebuiltTileElements.tsx +5 -0
- package/src/Prebuilt/components/Preview/PreviewJoin.tsx +9 -6
- package/src/Prebuilt/components/SidePaneTabs.tsx +31 -5
- package/src/Prebuilt/components/VideoTile.jsx +19 -34
- package/src/Prebuilt/components/conference.jsx +4 -3
- package/src/Prebuilt/components/hooks/useDropdownSelection.jsx +1 -1
- package/src/Prebuilt/layouts/SidePane.tsx +1 -0
- package/dist/chunk-GLYGPYNS.js.map +0 -7
- package/dist/conference-JD35TNH4.js.map +0 -7
- /package/dist/{HLSView-IQRPLYNH.js.map → HLSView-DDGPZHA2.js.map} +0 -0
- /package/dist/{VirtualBackground-GP4ATXD3.js.map → VirtualBackground-UVZJVOA2.js.map} +0 -0
- /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
|
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
|
80
|
-
"@100mslive/hms-virtual-background": "1.11.17
|
81
|
-
"@100mslive/react-icons": "0.8.17
|
82
|
-
"@100mslive/react-sdk": "0.8.17
|
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": "
|
118
|
+
"gitHead": "966ed9ffac3d55586196ad0b738eb1977dfc2ff7"
|
119
119
|
}
|
package/src/Prebuilt/App.tsx
CHANGED
@@ -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
|
-
|
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.
|
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.
|
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
|
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
|
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 = ({
|
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
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
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
|
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: '$
|
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, {
|
1
|
+
import React, { useEffect } from 'react';
|
2
2
|
import { useMeasure } from 'react-use';
|
3
3
|
import { FixedSizeList } from 'react-window';
|
4
|
-
import { HMSPeer,
|
5
|
-
import {
|
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 {
|
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
|
46
|
-
const
|
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
|
-
|
49
|
-
if (!
|
52
|
+
useEffect(() => {
|
53
|
+
if (!isOffStageRole || !isLargeRoom) {
|
50
54
|
return;
|
51
55
|
}
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
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
|
-
<
|
75
|
-
<Accordion.
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
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
|
-
<
|
82
|
-
<
|
83
|
-
|
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
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
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
|
-
<
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
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
|
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' });
|