@100mslive/roomkit-react 0.1.14 → 0.1.16
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.
- package/dist/{HLSView-662T7R7H.js → HLSView-EMUOLCTM.js} +128 -39
- package/dist/HLSView-EMUOLCTM.js.map +7 -0
- package/dist/Prebuilt/common/PeersSorter.d.ts +1 -0
- package/dist/Prebuilt/common/constants.d.ts +9 -5
- package/dist/Prebuilt/common/hooks.d.ts +1 -0
- package/dist/Prebuilt/components/Footer/ParticipantList.d.ts +17 -0
- package/dist/Prebuilt/components/Footer/RoleAccordion.d.ts +3 -2
- package/dist/Prebuilt/components/Footer/WhiteboardToggle.d.ts +2 -0
- package/dist/Prebuilt/components/HMSVideo/HLSCaptionSelector.d.ts +5 -0
- package/dist/Prebuilt/components/Notifications/HandRaisedNotifications.d.ts +1 -0
- package/dist/Prebuilt/components/Polls/Voting/Leaderboard.d.ts +4 -0
- package/dist/Prebuilt/components/Polls/Voting/LeaderboardEntry.d.ts +9 -0
- package/dist/Prebuilt/components/Polls/Voting/PeerParticipationSummary.d.ts +5 -0
- package/dist/Prebuilt/components/PreviousRoleInMetadata.d.ts +1 -0
- package/dist/Prebuilt/components/RemoveParticipant.d.ts +5 -0
- package/dist/Prebuilt/components/hooks/useCloseScreenshareWhiteboard.d.ts +4 -0
- package/dist/Prebuilt/layouts/WhiteboardView.d.ts +2 -0
- package/dist/{chunk-2B7YYNHQ.js → chunk-ZYR4B4KQ.js} +2240 -1767
- package/dist/chunk-ZYR4B4KQ.js.map +7 -0
- package/dist/index.cjs.js +2805 -2172
- package/dist/index.cjs.js.map +4 -4
- package/dist/index.js +1 -1
- package/dist/meta.cjs.json +739 -177
- package/dist/meta.esbuild.json +749 -186
- package/package.json +7 -7
- package/src/Prebuilt/AppStateContext.tsx +1 -1
- package/src/Prebuilt/common/PeersSorter.ts +24 -8
- package/src/Prebuilt/common/constants.ts +6 -6
- package/src/Prebuilt/common/hooks.ts +16 -0
- package/src/Prebuilt/common/utils.js +33 -0
- package/src/Prebuilt/components/AppData/AppData.tsx +1 -16
- package/src/Prebuilt/components/Chat/Chat.jsx +10 -34
- package/src/Prebuilt/components/Chat/ChatBody.jsx +107 -66
- package/src/Prebuilt/components/Chat/ChatFooter.tsx +21 -12
- package/src/Prebuilt/components/Chat/ChatSelector.tsx +25 -25
- package/src/Prebuilt/components/Chat/ChatSelectorContainer.tsx +15 -16
- package/src/Prebuilt/components/Chat/PinnedMessage.tsx +7 -2
- package/src/Prebuilt/components/ConferenceScreen.tsx +2 -0
- package/src/Prebuilt/components/Footer/ChatToggle.tsx +30 -7
- package/src/Prebuilt/components/Footer/Footer.tsx +2 -1
- package/src/Prebuilt/components/Footer/PaginatedParticipants.tsx +0 -1
- package/src/Prebuilt/components/Footer/{ParticipantList.jsx → ParticipantList.tsx} +169 -127
- package/src/Prebuilt/components/Footer/RoleAccordion.tsx +23 -13
- package/src/Prebuilt/components/Footer/WhiteboardToggle.tsx +34 -0
- package/src/Prebuilt/components/HMSVideo/HLSCaptionSelector.tsx +13 -0
- package/src/Prebuilt/components/HMSVideo/HMSVideo.jsx +34 -2
- package/src/Prebuilt/components/Notifications/HandRaisedNotifications.tsx +35 -0
- package/src/Prebuilt/components/Notifications/Notifications.tsx +47 -14
- package/src/Prebuilt/components/Notifications/PeerNotifications.tsx +7 -2
- package/src/Prebuilt/components/Polls/CreatePollQuiz/PollsQuizMenu.jsx +3 -9
- package/src/Prebuilt/components/Polls/CreateQuestions/CreateQuestions.jsx +21 -1
- package/src/Prebuilt/components/Polls/CreateQuestions/QuestionForm.jsx +34 -7
- package/src/Prebuilt/components/Polls/CreateQuestions/SavedQuestion.jsx +2 -2
- package/src/Prebuilt/components/Polls/Polls.tsx +3 -0
- package/src/Prebuilt/components/Polls/Voting/Leaderboard.tsx +115 -0
- package/src/Prebuilt/components/Polls/Voting/LeaderboardEntry.tsx +63 -0
- package/src/Prebuilt/components/Polls/Voting/PeerParticipationSummary.tsx +38 -0
- package/src/Prebuilt/components/Polls/Voting/QuestionCard.jsx +33 -8
- package/src/Prebuilt/components/Polls/Voting/StandardVoting.jsx +7 -1
- package/src/Prebuilt/components/Polls/Voting/Voting.jsx +31 -13
- package/src/Prebuilt/components/Polls/common/MultipleChoiceOptions.jsx +33 -21
- package/src/Prebuilt/components/Polls/common/SingleChoiceOptions.jsx +47 -35
- package/src/Prebuilt/components/Polls/common/StatusIndicator.jsx +2 -22
- package/src/Prebuilt/components/Polls/common/VoteCount.jsx +1 -15
- package/src/Prebuilt/components/PreviousRoleInMetadata.tsx +21 -0
- package/src/Prebuilt/components/RemoveParticipant.tsx +35 -0
- package/src/Prebuilt/components/RoleChangeModal.jsx +1 -1
- package/src/Prebuilt/components/SidePaneTabs.tsx +0 -1
- package/src/Prebuilt/components/TileMenu/TileMenuContent.tsx +1 -1
- package/src/Prebuilt/components/Toast/ToastConfig.jsx +15 -3
- package/src/Prebuilt/components/VideoLayouts/EqualProminence.tsx +6 -5
- package/src/Prebuilt/components/VideoLayouts/GridLayout.tsx +27 -5
- package/src/Prebuilt/components/VideoLayouts/ScreenshareLayout.tsx +0 -1
- package/src/Prebuilt/components/hooks/useCloseScreenshareWhiteboard.tsx +24 -0
- package/src/Prebuilt/layouts/HLSView.jsx +51 -3
- package/src/Prebuilt/layouts/VideoStreamingSection.tsx +20 -3
- package/src/Prebuilt/layouts/WhiteboardView.tsx +66 -0
- package/dist/HLSView-662T7R7H.js.map +0 -7
- package/dist/chunk-2B7YYNHQ.js.map +0 -7
- package/src/Prebuilt/components/AppData/useAppLayout.js +0 -6
- package/src/Prebuilt/components/init/initUtils.js +0 -67
|
@@ -3,8 +3,40 @@ import { Flex } from '../../../';
|
|
|
3
3
|
|
|
4
4
|
export const HMSVideo = forwardRef(({ children, ...props }, videoRef) => {
|
|
5
5
|
return (
|
|
6
|
-
<Flex
|
|
7
|
-
|
|
6
|
+
<Flex
|
|
7
|
+
data-testid="hms-video"
|
|
8
|
+
css={{
|
|
9
|
+
size: '100%',
|
|
10
|
+
position: 'relative',
|
|
11
|
+
'& video::cue': {
|
|
12
|
+
color: 'white',
|
|
13
|
+
// textShadow: '0px 0px 4px #000',
|
|
14
|
+
whiteSpace: 'pre-line',
|
|
15
|
+
fontSize: '$lg',
|
|
16
|
+
fontStyle: 'normal',
|
|
17
|
+
fontWeight: '$semiBold',
|
|
18
|
+
lineHeight: '$sm',
|
|
19
|
+
letterSpacing: '0.5px',
|
|
20
|
+
},
|
|
21
|
+
'& video::-webkit-media-text-track-display': {
|
|
22
|
+
padding: '0 $4',
|
|
23
|
+
},
|
|
24
|
+
'& video::-webkit-media-text-track-container': {
|
|
25
|
+
fontSize: '$space$10 !important',
|
|
26
|
+
},
|
|
27
|
+
}}
|
|
28
|
+
direction="column"
|
|
29
|
+
{...props}
|
|
30
|
+
>
|
|
31
|
+
<video
|
|
32
|
+
style={{
|
|
33
|
+
flex: '1 1 0',
|
|
34
|
+
margin: '0 auto',
|
|
35
|
+
minHeight: '0',
|
|
36
|
+
}}
|
|
37
|
+
ref={videoRef}
|
|
38
|
+
playsInline
|
|
39
|
+
/>
|
|
8
40
|
{children}
|
|
9
41
|
</Flex>
|
|
10
42
|
);
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
HMSNotificationTypes,
|
|
4
|
+
HMSRoomState,
|
|
5
|
+
selectHasPeerHandRaised,
|
|
6
|
+
selectRoomState,
|
|
7
|
+
useHMSNotifications,
|
|
8
|
+
useHMSStore,
|
|
9
|
+
useHMSVanillaStore,
|
|
10
|
+
} from '@100mslive/react-sdk';
|
|
11
|
+
// @ts-ignore: No implicit Any
|
|
12
|
+
import { ToastBatcher } from '../Toast/ToastBatcher';
|
|
13
|
+
import { useRoomLayoutConferencingScreen } from '../../provider/roomLayoutProvider/hooks/useRoomLayoutScreen';
|
|
14
|
+
|
|
15
|
+
export const HandRaisedNotifications = () => {
|
|
16
|
+
const notification = useHMSNotifications(HMSNotificationTypes.HAND_RAISE_CHANGED);
|
|
17
|
+
const roomState = useHMSStore(selectRoomState);
|
|
18
|
+
const vanillaStore = useHMSVanillaStore();
|
|
19
|
+
const { on_stage_exp } = useRoomLayoutConferencingScreen().elements || {};
|
|
20
|
+
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
if (!notification?.data) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
if (roomState !== HMSRoomState.Connected || notification.data.isLocal || !on_stage_exp) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const hasPeerHandRaised = vanillaStore.getState(selectHasPeerHandRaised(notification.data.id));
|
|
29
|
+
if (hasPeerHandRaised) {
|
|
30
|
+
ToastBatcher.showToast({ notification, type: 'RAISE_HAND' });
|
|
31
|
+
}
|
|
32
|
+
}, [notification, on_stage_exp, roomState, vanillaStore]);
|
|
33
|
+
|
|
34
|
+
return null;
|
|
35
|
+
};
|
|
@@ -4,7 +4,6 @@ import {
|
|
|
4
4
|
HMSNotificationTypes,
|
|
5
5
|
HMSRoleChangeRequest,
|
|
6
6
|
HMSRoomState,
|
|
7
|
-
selectHasPeerHandRaised,
|
|
8
7
|
selectLocalPeerID,
|
|
9
8
|
selectPeerNameByID,
|
|
10
9
|
selectRoomState,
|
|
@@ -13,7 +12,8 @@ import {
|
|
|
13
12
|
useHMSStore,
|
|
14
13
|
useHMSVanillaStore,
|
|
15
14
|
} from '@100mslive/react-sdk';
|
|
16
|
-
import {
|
|
15
|
+
import { GroupIcon } from '@100mslive/react-icons';
|
|
16
|
+
import { Box, Button } from '../../..';
|
|
17
17
|
import { useUpdateRoomLayout } from '../../provider/roomLayoutProvider';
|
|
18
18
|
// @ts-ignore: No implicit Any
|
|
19
19
|
import { ToastBatcher } from '../Toast/ToastBatcher';
|
|
@@ -21,6 +21,7 @@ import { ToastBatcher } from '../Toast/ToastBatcher';
|
|
|
21
21
|
import { ToastManager } from '../Toast/ToastManager';
|
|
22
22
|
import { AutoplayBlockedModal } from './AutoplayBlockedModal';
|
|
23
23
|
import { ChatNotifications } from './ChatNotifications';
|
|
24
|
+
import { HandRaisedNotifications } from './HandRaisedNotifications';
|
|
24
25
|
import { InitErrorModal } from './InitErrorModal';
|
|
25
26
|
import { PeerNotifications } from './PeerNotifications';
|
|
26
27
|
import { PermissionErrorModal } from './PermissionErrorModal';
|
|
@@ -28,6 +29,7 @@ import { ReconnectNotifications } from './ReconnectNotifications';
|
|
|
28
29
|
import { TrackBulkUnmuteModal } from './TrackBulkUnmuteModal';
|
|
29
30
|
import { TrackNotifications } from './TrackNotifications';
|
|
30
31
|
import { TrackUnmuteModal } from './TrackUnmuteModal';
|
|
32
|
+
import { useRoomLayoutConferencingScreen } from '../../provider/roomLayoutProvider/hooks/useRoomLayoutScreen';
|
|
31
33
|
// @ts-ignore: No implicit Any
|
|
32
34
|
import { usePollViewToggle } from '../AppData/useSidepane';
|
|
33
35
|
// @ts-ignore: No implicit Any
|
|
@@ -43,6 +45,7 @@ export function Notifications() {
|
|
|
43
45
|
const roomState = useHMSStore(selectRoomState);
|
|
44
46
|
const updateRoomLayoutForRole = useUpdateRoomLayout();
|
|
45
47
|
const isNotificationDisabled = useIsNotificationDisabled();
|
|
48
|
+
const screenProps = useRoomLayoutConferencingScreen();
|
|
46
49
|
const vanillaStore = useHMSVanillaStore();
|
|
47
50
|
const togglePollView = usePollViewToggle();
|
|
48
51
|
|
|
@@ -53,23 +56,42 @@ export function Notifications() {
|
|
|
53
56
|
});
|
|
54
57
|
}, []);
|
|
55
58
|
|
|
59
|
+
/*
|
|
60
|
+
const leaderboardResultsShared = useCallback(
|
|
61
|
+
(stringifiedPollDetails: string) => {
|
|
62
|
+
const pollDetails = JSON.parse(stringifiedPollDetails);
|
|
63
|
+
if (pollDetails.startedBy !== localPeerID) {
|
|
64
|
+
const pollStartedBy = pollDetails.initiatorName;
|
|
65
|
+
ToastManager.addToast({
|
|
66
|
+
title: `${pollStartedBy} shared leaderboard for the quiz`,
|
|
67
|
+
action: (
|
|
68
|
+
<Button
|
|
69
|
+
onClick={() => togglePollView(pollDetails.id)}
|
|
70
|
+
variant="standard"
|
|
71
|
+
css={{
|
|
72
|
+
backgroundColor: '$surface_bright',
|
|
73
|
+
fontWeight: '$semiBold',
|
|
74
|
+
color: '$on_surface_high',
|
|
75
|
+
p: '$xs $md',
|
|
76
|
+
}}
|
|
77
|
+
>
|
|
78
|
+
View
|
|
79
|
+
</Button>
|
|
80
|
+
),
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
[localPeerID, togglePollView],
|
|
85
|
+
); */
|
|
86
|
+
|
|
56
87
|
useCustomEvent({ type: ROLE_CHANGE_DECLINED, onEvent: handleRoleChangeDenied });
|
|
88
|
+
// useCustomEvent({ type: 'POLL_LEADERBOARD_SHARED', onEvent: leaderboardResultsShared });
|
|
57
89
|
|
|
58
90
|
useEffect(() => {
|
|
59
91
|
if (!notification || isNotificationDisabled) {
|
|
60
92
|
return;
|
|
61
93
|
}
|
|
62
94
|
switch (notification.type) {
|
|
63
|
-
case HMSNotificationTypes.HAND_RAISE_CHANGED: {
|
|
64
|
-
if (roomState !== HMSRoomState.Connected || notification.data.isLocal) {
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
const hasPeerHandRaised = vanillaStore.getState(selectHasPeerHandRaised(notification.data.id));
|
|
68
|
-
if (hasPeerHandRaised) {
|
|
69
|
-
ToastBatcher.showToast({ notification, type: 'RAISE_HAND' });
|
|
70
|
-
}
|
|
71
|
-
break;
|
|
72
|
-
}
|
|
73
95
|
case HMSNotificationTypes.METADATA_UPDATED:
|
|
74
96
|
if (roomState !== HMSRoomState.Connected) {
|
|
75
97
|
return;
|
|
@@ -92,6 +114,16 @@ export function Notifications() {
|
|
|
92
114
|
ToastManager.addToast({
|
|
93
115
|
title: `Error: ${notification.data?.message}`,
|
|
94
116
|
});
|
|
117
|
+
} else if (notification.data?.message === 'role limit reached') {
|
|
118
|
+
ToastManager.addToast({
|
|
119
|
+
title: 'The room is currently full, try joining later',
|
|
120
|
+
close: true,
|
|
121
|
+
icon: (
|
|
122
|
+
<Box css={{ color: '$alert_error_default' }}>
|
|
123
|
+
<GroupIcon />
|
|
124
|
+
</Box>
|
|
125
|
+
),
|
|
126
|
+
});
|
|
95
127
|
} else {
|
|
96
128
|
ToastManager.addToast({
|
|
97
129
|
title:
|
|
@@ -146,7 +178,7 @@ export function Notifications() {
|
|
|
146
178
|
break;
|
|
147
179
|
|
|
148
180
|
case HMSNotificationTypes.POLL_STARTED:
|
|
149
|
-
if (notification.data.startedBy !== localPeerID) {
|
|
181
|
+
if (notification.data.startedBy !== localPeerID && screenProps.screenType !== 'hls_live_streaming') {
|
|
150
182
|
const pollStartedBy = vanillaStore.getState(selectPeerNameByID(notification.data.startedBy)) || 'Participant';
|
|
151
183
|
ToastManager.addToast({
|
|
152
184
|
title: `${pollStartedBy} started a ${notification.data.type}: ${notification.data.title}`,
|
|
@@ -161,7 +193,7 @@ export function Notifications() {
|
|
|
161
193
|
p: '$xs $md',
|
|
162
194
|
}}
|
|
163
195
|
>
|
|
164
|
-
Vote
|
|
196
|
+
{notification.data.type === 'quiz' ? 'Answer' : 'Vote'}
|
|
165
197
|
</Button>
|
|
166
198
|
),
|
|
167
199
|
});
|
|
@@ -188,6 +220,7 @@ export function Notifications() {
|
|
|
188
220
|
<PermissionErrorModal />
|
|
189
221
|
<InitErrorModal />
|
|
190
222
|
<ChatNotifications />
|
|
223
|
+
<HandRaisedNotifications />
|
|
191
224
|
</>
|
|
192
225
|
);
|
|
193
226
|
}
|
|
@@ -3,9 +3,9 @@ import { HMSNotificationTypes, useHMSNotifications } from '@100mslive/react-sdk'
|
|
|
3
3
|
// @ts-ignore: No implicit Any
|
|
4
4
|
import { ToastBatcher } from '../Toast/ToastBatcher';
|
|
5
5
|
// @ts-ignore: No implicit Any
|
|
6
|
-
import { useSubscribedNotifications } from '../AppData/useUISettings';
|
|
6
|
+
import { useSetSubscribedChatSelector, useSubscribedNotifications } from '../AppData/useUISettings';
|
|
7
7
|
// @ts-ignore: No implicit Any
|
|
8
|
-
import { SUBSCRIBED_NOTIFICATIONS } from '../../common/constants';
|
|
8
|
+
import { CHAT_SELECTOR, SUBSCRIBED_NOTIFICATIONS } from '../../common/constants';
|
|
9
9
|
|
|
10
10
|
const notificationTypes = [
|
|
11
11
|
HMSNotificationTypes.PEER_LIST,
|
|
@@ -17,6 +17,8 @@ export const PeerNotifications = () => {
|
|
|
17
17
|
const notification = useHMSNotifications(notificationTypes);
|
|
18
18
|
const isPeerJoinSubscribed = useSubscribedNotifications(SUBSCRIBED_NOTIFICATIONS.PEER_JOINED);
|
|
19
19
|
const isPeerLeftSubscribed = useSubscribedNotifications(SUBSCRIBED_NOTIFICATIONS.PEER_LEFT);
|
|
20
|
+
const [selectedPeer, setPeerSelector] = useSetSubscribedChatSelector(CHAT_SELECTOR.PEER);
|
|
21
|
+
|
|
20
22
|
useEffect(() => {
|
|
21
23
|
if (!notification?.data) {
|
|
22
24
|
return;
|
|
@@ -35,6 +37,9 @@ export const PeerNotifications = () => {
|
|
|
35
37
|
}
|
|
36
38
|
break;
|
|
37
39
|
case HMSNotificationTypes.PEER_LEFT:
|
|
40
|
+
if (selectedPeer.id === notification.data.id) {
|
|
41
|
+
setPeerSelector({});
|
|
42
|
+
}
|
|
38
43
|
if (!isPeerLeftSubscribed) {
|
|
39
44
|
return;
|
|
40
45
|
}
|
|
@@ -191,13 +191,7 @@ const PrevMenu = () => {
|
|
|
191
191
|
</Text>
|
|
192
192
|
<Flex direction="column" css={{ gap: '$10', mt: '$8' }}>
|
|
193
193
|
{polls.map(poll => (
|
|
194
|
-
<InteractionCard
|
|
195
|
-
key={poll.id}
|
|
196
|
-
id={poll.id}
|
|
197
|
-
title={poll.title}
|
|
198
|
-
isLive={poll.state === 'started'}
|
|
199
|
-
isTimed={(poll.duration || 0) > 0}
|
|
200
|
-
/>
|
|
194
|
+
<InteractionCard key={poll.id} id={poll.id} title={poll.title} isLive={poll.state === 'started'} />
|
|
201
195
|
))}
|
|
202
196
|
</Flex>
|
|
203
197
|
</Flex>
|
|
@@ -205,7 +199,7 @@ const PrevMenu = () => {
|
|
|
205
199
|
) : null;
|
|
206
200
|
};
|
|
207
201
|
|
|
208
|
-
const InteractionCard = ({ id, title, isLive
|
|
202
|
+
const InteractionCard = ({ id, title, isLive }) => {
|
|
209
203
|
const { setPollState } = usePollViewState();
|
|
210
204
|
|
|
211
205
|
const goToVote = id => {
|
|
@@ -221,7 +215,7 @@ const InteractionCard = ({ id, title, isLive, isTimed }) => {
|
|
|
221
215
|
<Text variant="sub1" css={{ c: '$on_surface_high', fontWeight: '$semiBold' }}>
|
|
222
216
|
{title}
|
|
223
217
|
</Text>
|
|
224
|
-
<StatusIndicator isLive={isLive}
|
|
218
|
+
<StatusIndicator isLive={isLive} />
|
|
225
219
|
</Flex>
|
|
226
220
|
<Flex css={{ w: '100%', gap: '$4' }} justify="end">
|
|
227
221
|
<Button variant="primary" onClick={() => goToVote(id)}>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
import React, { useMemo, useState } from 'react';
|
|
3
3
|
import { v4 as uuid } from 'uuid';
|
|
4
|
-
import { selectPollByID, useHMSActions, useHMSStore } from '@100mslive/react-sdk';
|
|
4
|
+
import { selectPollByID, useHMSActions, useHMSStore, useRecordingStreaming } from '@100mslive/react-sdk';
|
|
5
5
|
import { AddCircleIcon } from '@100mslive/react-icons';
|
|
6
6
|
import { Button, Flex, Text } from '../../../../';
|
|
7
7
|
import { Container, ContentHeader } from '../../Streaming/Common';
|
|
@@ -14,6 +14,7 @@ import { POLL_VIEWS } from '../../../common/constants';
|
|
|
14
14
|
export function CreateQuestions() {
|
|
15
15
|
const [questions, setQuestions] = useState([{ draftID: uuid() }]);
|
|
16
16
|
const actions = useHMSActions();
|
|
17
|
+
const { isHLSRunning } = useRecordingStreaming();
|
|
17
18
|
const togglePollView = usePollViewToggle();
|
|
18
19
|
const { pollInView: id, setPollView } = usePollViewState();
|
|
19
20
|
const interaction = useHMSStore(selectPollByID(id));
|
|
@@ -31,11 +32,30 @@ export function CreateQuestions() {
|
|
|
31
32
|
type: question.type,
|
|
32
33
|
options: question.options,
|
|
33
34
|
skippable: question.skippable,
|
|
35
|
+
weight: question.weight,
|
|
34
36
|
}));
|
|
35
37
|
await actions.interactivityCenter.addQuestionsToPoll(id, validQuestions);
|
|
36
38
|
await actions.interactivityCenter.startPoll(id);
|
|
39
|
+
await sendTimedMetadata(id);
|
|
37
40
|
setPollView(POLL_VIEWS.VOTE);
|
|
38
41
|
};
|
|
42
|
+
|
|
43
|
+
const sendTimedMetadata = async poll_id => {
|
|
44
|
+
// send hls timedmetadata when it is running
|
|
45
|
+
if (poll_id && isHLSRunning) {
|
|
46
|
+
try {
|
|
47
|
+
await actions.sendHLSTimedMetadata([
|
|
48
|
+
{
|
|
49
|
+
payload: `poll:${poll_id}`,
|
|
50
|
+
duration: 100,
|
|
51
|
+
},
|
|
52
|
+
]);
|
|
53
|
+
} catch (e) {
|
|
54
|
+
console.error(e);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
39
59
|
const headingTitle = interaction?.type
|
|
40
60
|
? interaction?.type?.[0]?.toUpperCase() + interaction?.type?.slice(1)
|
|
41
61
|
: 'Polls and Quizzes';
|
|
@@ -17,6 +17,7 @@ export const QuestionForm = ({ question, index, length, onSave, removeQuestion,
|
|
|
17
17
|
const [open, setOpen] = useState(false);
|
|
18
18
|
const [type, setType] = useState(question.type || QUESTION_TYPE.SINGLE_CHOICE);
|
|
19
19
|
const [text, setText] = useState(question.text);
|
|
20
|
+
const [weight, setWeight] = useState(isQuiz ? 10 : 1);
|
|
20
21
|
const [options, setOptions] = useState(
|
|
21
22
|
question?.options || [
|
|
22
23
|
{ text: '', isCorrectAnswer: false },
|
|
@@ -28,6 +29,7 @@ export const QuestionForm = ({ question, index, length, onSave, removeQuestion,
|
|
|
28
29
|
text,
|
|
29
30
|
type,
|
|
30
31
|
options,
|
|
32
|
+
weight,
|
|
31
33
|
isQuiz,
|
|
32
34
|
});
|
|
33
35
|
|
|
@@ -182,12 +184,31 @@ export const QuestionForm = ({ question, index, length, onSave, removeQuestion,
|
|
|
182
184
|
</Flex>
|
|
183
185
|
)}
|
|
184
186
|
{isQuiz ? (
|
|
185
|
-
|
|
186
|
-
<
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
187
|
+
<>
|
|
188
|
+
<Flex justify="between" align="center" css={{ mt: '$md', gap: '$6', w: '100%' }}>
|
|
189
|
+
<Text variant="sm" css={{ color: '$on_surface_medium' }}>
|
|
190
|
+
Point Weightage
|
|
191
|
+
</Text>
|
|
192
|
+
<Input
|
|
193
|
+
type="number"
|
|
194
|
+
value={weight}
|
|
195
|
+
min={1}
|
|
196
|
+
max={999}
|
|
197
|
+
onChange={e => setWeight(Math.min(e.target.value, 999))}
|
|
198
|
+
css={{
|
|
199
|
+
backgroundColor: '$surface_bright',
|
|
200
|
+
border: '1px solid $border_bright',
|
|
201
|
+
maxWidth: '$20',
|
|
202
|
+
}}
|
|
203
|
+
/>
|
|
204
|
+
</Flex>
|
|
205
|
+
<Flex justify="between" css={{ mt: '$md', gap: '$6', w: '100%' }}>
|
|
206
|
+
<Text variant="sm" css={{ color: '$on_surface_medium' }}>
|
|
207
|
+
Allow to skip
|
|
208
|
+
</Text>
|
|
209
|
+
<Switch defaultChecked={skippable} onCheckedChange={checked => setSkippable(checked)} />
|
|
210
|
+
</Flex>
|
|
211
|
+
</>
|
|
191
212
|
) : null}
|
|
192
213
|
</>
|
|
193
214
|
) : null}
|
|
@@ -222,6 +243,7 @@ export const QuestionForm = ({ question, index, length, onSave, removeQuestion,
|
|
|
222
243
|
options,
|
|
223
244
|
skippable,
|
|
224
245
|
draftID: question.draftID,
|
|
246
|
+
weight,
|
|
225
247
|
});
|
|
226
248
|
}}
|
|
227
249
|
>
|
|
@@ -235,7 +257,7 @@ export const QuestionForm = ({ question, index, length, onSave, removeQuestion,
|
|
|
235
257
|
);
|
|
236
258
|
};
|
|
237
259
|
|
|
238
|
-
export const isValidQuestion = ({ text, type, options, isQuiz = false }) => {
|
|
260
|
+
export const isValidQuestion = ({ text, type, options, weight, isQuiz = false }) => {
|
|
239
261
|
if (!isValidTextInput(text) || !type) {
|
|
240
262
|
return false;
|
|
241
263
|
}
|
|
@@ -251,5 +273,10 @@ export const isValidQuestion = ({ text, type, options, isQuiz = false }) => {
|
|
|
251
273
|
return everyOptionHasText;
|
|
252
274
|
}
|
|
253
275
|
|
|
276
|
+
// The minimum acceptable value of weight is 1
|
|
277
|
+
if (isQuiz && weight < 1) {
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
|
|
254
281
|
return everyOptionHasText && hasCorrectAnswer;
|
|
255
282
|
};
|
|
@@ -15,8 +15,8 @@ export const SavedQuestion = ({ question, index, length, convertToDraft, removeQ
|
|
|
15
15
|
<Text variant="body2" css={{ mt: '$4', mb: '$md' }}>
|
|
16
16
|
{question.text}
|
|
17
17
|
</Text>
|
|
18
|
-
{question.options.map(option => (
|
|
19
|
-
<Flex css={{ alignItems: 'center', my: '$xs' }}>
|
|
18
|
+
{question.options.map((option, index) => (
|
|
19
|
+
<Flex key={`${option.text}-${index}`} css={{ alignItems: 'center', my: '$xs' }}>
|
|
20
20
|
<Text variant="body2" css={{ c: '$on_surface_medium' }}>
|
|
21
21
|
{option.text}
|
|
22
22
|
</Text>
|
|
@@ -3,6 +3,7 @@ import React from 'react';
|
|
|
3
3
|
import { PollsQuizMenu } from './CreatePollQuiz/PollsQuizMenu';
|
|
4
4
|
// @ts-ignore: No implicit Any
|
|
5
5
|
import { CreateQuestions } from './CreateQuestions/CreateQuestions';
|
|
6
|
+
import { Leaderboard } from './Voting/Leaderboard';
|
|
6
7
|
// @ts-ignore: No implicit Any
|
|
7
8
|
import { Voting } from './Voting/Voting';
|
|
8
9
|
// @ts-ignore: No implicit Any
|
|
@@ -22,6 +23,8 @@ export const Polls = () => {
|
|
|
22
23
|
return <CreateQuestions />;
|
|
23
24
|
} else if (view === POLL_VIEWS.VOTE) {
|
|
24
25
|
return <Voting toggleVoting={togglePollView} id={pollID} />;
|
|
26
|
+
} else if (view === POLL_VIEWS.RESULTS) {
|
|
27
|
+
return <Leaderboard pollID={pollID} />;
|
|
25
28
|
} else {
|
|
26
29
|
return null;
|
|
27
30
|
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import { HMSPollLeaderboardResponse, selectPollByID, useHMSActions, useHMSStore } from '@100mslive/react-sdk';
|
|
3
|
+
import { ChevronLeftIcon, CrossIcon } from '@100mslive/react-icons';
|
|
4
|
+
import { Box, Flex } from '../../../../Layout';
|
|
5
|
+
import { Loading } from '../../../../Loading';
|
|
6
|
+
import { Text } from '../../../../Text';
|
|
7
|
+
import { LeaderboardEntry } from './LeaderboardEntry';
|
|
8
|
+
// @ts-ignore
|
|
9
|
+
import { useSidepaneToggle } from '../../AppData/useSidepane';
|
|
10
|
+
// @ts-ignore
|
|
11
|
+
import { usePollViewState } from '../../AppData/useUISettings';
|
|
12
|
+
// @ts-ignore
|
|
13
|
+
import { StatusIndicator } from '../common/StatusIndicator';
|
|
14
|
+
import { POLL_VIEWS } from '../../../common/constants';
|
|
15
|
+
|
|
16
|
+
export const Leaderboard = ({ pollID }: { pollID: string }) => {
|
|
17
|
+
const hmsActions = useHMSActions();
|
|
18
|
+
const poll = useHMSStore(selectPollByID(pollID));
|
|
19
|
+
const [pollLeaderboard, setPollLeaderboard] = useState<HMSPollLeaderboardResponse | undefined>();
|
|
20
|
+
const { setPollView } = usePollViewState();
|
|
21
|
+
const toggleSidepane = useSidepaneToggle();
|
|
22
|
+
|
|
23
|
+
/*
|
|
24
|
+
const sharedLeaderboardRef = useRef(false);
|
|
25
|
+
const sharedLeaderboards = useHMSStore(selectSessionStore(SESSION_STORE_KEY.SHARED_LEADERBOARDS));
|
|
26
|
+
const { sendEvent } = useCustomEvent({
|
|
27
|
+
type: HMSNotificationTypes.POLL_LEADERBOARD_SHARED,
|
|
28
|
+
onEvent: () => {
|
|
29
|
+
return;
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
const fetchLeaderboardData = async () => {
|
|
36
|
+
if (poll) {
|
|
37
|
+
const leaderboardData = await hmsActions.interactivityCenter.fetchLeaderboard(poll, 0, 50);
|
|
38
|
+
setPollLeaderboard(leaderboardData);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
fetchLeaderboardData();
|
|
42
|
+
}, [poll, hmsActions.interactivityCenter]);
|
|
43
|
+
|
|
44
|
+
if (!poll || !pollLeaderboard)
|
|
45
|
+
return (
|
|
46
|
+
<Flex align="center" justify="center" css={{ size: '100%' }}>
|
|
47
|
+
<Loading />
|
|
48
|
+
</Flex>
|
|
49
|
+
);
|
|
50
|
+
const maxPossibleScore = poll.questions?.reduce((total, question) => (total += question.weight || 0), 0) || 0;
|
|
51
|
+
const questionCount = poll.questions?.length || 0;
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<Flex direction="column" css={{ size: '100%' }}>
|
|
55
|
+
<Flex justify="between" align="center" css={{ pb: '$6', borderBottom: '1px solid $border_bright', mb: '$8' }}>
|
|
56
|
+
<Flex align="center" css={{ gap: '$4' }}>
|
|
57
|
+
<Flex
|
|
58
|
+
css={{ color: '$on_surface_medium', '&:hover': { color: '$on_surface_high', cursor: 'pointer' } }}
|
|
59
|
+
onClick={() => setPollView(POLL_VIEWS.VOTE)}
|
|
60
|
+
>
|
|
61
|
+
<ChevronLeftIcon />
|
|
62
|
+
</Flex>
|
|
63
|
+
<Text variant="lg" css={{ fontWeight: '$semiBold' }}>
|
|
64
|
+
{poll.title}
|
|
65
|
+
</Text>
|
|
66
|
+
<StatusIndicator isLive={false} />
|
|
67
|
+
</Flex>
|
|
68
|
+
<Flex
|
|
69
|
+
css={{ color: '$on_surface_medium', '&:hover': { color: '$on_surface_high', cursor: 'pointer' } }}
|
|
70
|
+
onClick={toggleSidepane}
|
|
71
|
+
>
|
|
72
|
+
<CrossIcon />
|
|
73
|
+
</Flex>
|
|
74
|
+
</Flex>
|
|
75
|
+
<Text variant="sm" css={{ fontWeight: '$semiBold' }}>
|
|
76
|
+
Leaderboard
|
|
77
|
+
</Text>
|
|
78
|
+
<Text variant="xs" css={{ color: '$on_surface_medium' }}>
|
|
79
|
+
Based on score and time taken to cast the correct answer
|
|
80
|
+
</Text>
|
|
81
|
+
<Box css={{ mt: '$8', gap: '$4', overflowY: 'auto', flex: '1 1 0', mr: '-$6', pr: '$6' }}>
|
|
82
|
+
{pollLeaderboard?.entries &&
|
|
83
|
+
pollLeaderboard.entries.map(question => (
|
|
84
|
+
<LeaderboardEntry
|
|
85
|
+
key={question.position}
|
|
86
|
+
position={question.position}
|
|
87
|
+
score={question.score}
|
|
88
|
+
questionCount={questionCount}
|
|
89
|
+
correctResponses={question.correctResponses}
|
|
90
|
+
userName={question.peer.username || ''}
|
|
91
|
+
maxPossibleScore={maxPossibleScore}
|
|
92
|
+
/>
|
|
93
|
+
))}
|
|
94
|
+
</Box>
|
|
95
|
+
|
|
96
|
+
{/* {!sharedLeaderboardRef.current ? (
|
|
97
|
+
<Button
|
|
98
|
+
css={{ ml: 'auto', mt: '$8' }}
|
|
99
|
+
onClick={() => {
|
|
100
|
+
const currentlySharedLeaderboards = sharedLeaderboards || [];
|
|
101
|
+
hmsActions.sessionStore.set(SESSION_STORE_KEY.SHARED_LEADERBOARDS, [
|
|
102
|
+
...currentlySharedLeaderboards,
|
|
103
|
+
pollID,
|
|
104
|
+
]);
|
|
105
|
+
const pollDetails = { initiatorName: '', startedBy: poll.startedBy, id: pollID };
|
|
106
|
+
sendEvent();
|
|
107
|
+
sharedLeaderboardRef.current = true;
|
|
108
|
+
}}
|
|
109
|
+
>
|
|
110
|
+
Share Results
|
|
111
|
+
</Button>
|
|
112
|
+
) : null} */}
|
|
113
|
+
</Flex>
|
|
114
|
+
);
|
|
115
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { CheckCircleIcon, TrophyFilledIcon } from '@100mslive/react-icons';
|
|
3
|
+
import { Box, Flex } from '../../../../Layout';
|
|
4
|
+
import { Text } from '../../../../Text';
|
|
5
|
+
|
|
6
|
+
const positionColorMap: Record<number, string> = { 1: '#D69516', 2: '#3E3E3E', 3: '#583B0F' };
|
|
7
|
+
|
|
8
|
+
export const LeaderboardEntry = ({
|
|
9
|
+
position,
|
|
10
|
+
score,
|
|
11
|
+
questionCount,
|
|
12
|
+
correctResponses,
|
|
13
|
+
userName,
|
|
14
|
+
maxPossibleScore,
|
|
15
|
+
}: {
|
|
16
|
+
position: number;
|
|
17
|
+
score: number;
|
|
18
|
+
questionCount: number;
|
|
19
|
+
correctResponses: number;
|
|
20
|
+
userName: string;
|
|
21
|
+
maxPossibleScore: number;
|
|
22
|
+
}) => {
|
|
23
|
+
return (
|
|
24
|
+
<Flex align="center" justify="between">
|
|
25
|
+
<Flex align="center" css={{ gap: '$6' }}>
|
|
26
|
+
<Flex
|
|
27
|
+
align="center"
|
|
28
|
+
justify="center"
|
|
29
|
+
css={{
|
|
30
|
+
backgroundColor: positionColorMap[position] || '',
|
|
31
|
+
h: '$10',
|
|
32
|
+
w: '$10',
|
|
33
|
+
borderRadius: '$round',
|
|
34
|
+
color: position > 3 ? '$on_surface_low' : '#FFF',
|
|
35
|
+
fontSize: '$xs',
|
|
36
|
+
fontWeight: '$semiBold',
|
|
37
|
+
}}
|
|
38
|
+
>
|
|
39
|
+
{position}
|
|
40
|
+
</Flex>
|
|
41
|
+
|
|
42
|
+
<Box>
|
|
43
|
+
<Text variant="sm" css={{ fontWeight: '$semiBold', color: '$on_surface_high' }}>
|
|
44
|
+
{userName}
|
|
45
|
+
</Text>
|
|
46
|
+
|
|
47
|
+
<Text variant="sm">
|
|
48
|
+
{score}/{maxPossibleScore} points
|
|
49
|
+
</Text>
|
|
50
|
+
</Box>
|
|
51
|
+
</Flex>
|
|
52
|
+
<Flex align="center" css={{ gap: '$6', color: '$on_surface_medium' }}>
|
|
53
|
+
{position === 1 ? <TrophyFilledIcon /> : null}
|
|
54
|
+
<CheckCircleIcon height={16} width={16} />
|
|
55
|
+
{questionCount ? (
|
|
56
|
+
<Text variant="xs">
|
|
57
|
+
{correctResponses}/{questionCount}
|
|
58
|
+
</Text>
|
|
59
|
+
) : null}
|
|
60
|
+
</Flex>
|
|
61
|
+
</Flex>
|
|
62
|
+
);
|
|
63
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { HMSPoll, selectLocalPeer, useHMSStore } from '@100mslive/react-sdk';
|
|
3
|
+
import { Box } from '../../../../Layout';
|
|
4
|
+
import { Text } from '../../../../Text';
|
|
5
|
+
// @ts-ignore
|
|
6
|
+
import { getPeerParticipationSummary } from '../../../common/utils';
|
|
7
|
+
|
|
8
|
+
export const PeerParticipationSummary = ({ poll }: { poll: HMSPoll }) => {
|
|
9
|
+
const localPeer = useHMSStore(selectLocalPeer);
|
|
10
|
+
const { totalResponses, correctResponses, score } = getPeerParticipationSummary(
|
|
11
|
+
poll,
|
|
12
|
+
localPeer?.id,
|
|
13
|
+
localPeer?.customerUserId,
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
const boxes = [
|
|
17
|
+
{ title: 'Points', value: score },
|
|
18
|
+
{ title: 'Correct Answers', value: `${correctResponses}/${totalResponses}` },
|
|
19
|
+
];
|
|
20
|
+
return (
|
|
21
|
+
<Box>
|
|
22
|
+
<Text css={{ fontWeight: '$semiBold', my: '$8' }}>Participation Summary</Text>
|
|
23
|
+
<Box css={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '$4' }}>
|
|
24
|
+
{boxes.map(box => (
|
|
25
|
+
<Box key={box.title} css={{ p: '$8', background: '$surface_default', borderRadius: '$1' }}>
|
|
26
|
+
<Text
|
|
27
|
+
variant="tiny"
|
|
28
|
+
css={{ textTransform: 'uppercase', color: '$on_surface_medium', fontWeight: '$semiBold', my: '$4' }}
|
|
29
|
+
>
|
|
30
|
+
{box.title}
|
|
31
|
+
</Text>
|
|
32
|
+
<Text css={{ fontWeight: '$semiBold' }}>{box.value}</Text>
|
|
33
|
+
</Box>
|
|
34
|
+
))}
|
|
35
|
+
</Box>
|
|
36
|
+
</Box>
|
|
37
|
+
);
|
|
38
|
+
};
|