@100mslive/roomkit-react 0.1.19-alpha.1 → 0.1.19

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. package/dist/{HLSView-UBVLOPNY.js → HLSView-4L4OAX2K.js} +2 -2
  2. package/dist/Prebuilt/App.d.ts +1 -0
  3. package/dist/Prebuilt/common/constants.d.ts +1 -1
  4. package/dist/Prebuilt/components/Chat/EmptyChat.d.ts +2 -0
  5. package/dist/Prebuilt/components/Polls/Voting/{Leaderboard.d.ts → LeaderboardSummary.d.ts} +1 -1
  6. package/dist/Prebuilt/components/Polls/Voting/StatisticBox.d.ts +5 -0
  7. package/dist/Prebuilt/components/VirtualBackground/VBCollection.d.ts +3 -4
  8. package/dist/Prebuilt/components/VirtualBackground/VBHandler.d.ts +14 -0
  9. package/dist/Prebuilt/components/VirtualBackground/VBPicker.d.ts +1 -1
  10. package/dist/Prebuilt/components/VirtualBackground/constants.d.ts +0 -2
  11. package/dist/{chunk-VU2CQFCB.js → chunk-NHPPOGUF.js} +1403 -1293
  12. package/dist/chunk-NHPPOGUF.js.map +7 -0
  13. package/dist/index.cjs.js +1749 -1609
  14. package/dist/index.cjs.js.map +4 -4
  15. package/dist/index.js +1 -1
  16. package/dist/meta.cjs.json +236 -94
  17. package/dist/meta.esbuild.json +243 -101
  18. package/package.json +6 -6
  19. package/src/Prebuilt/App.tsx +2 -1
  20. package/src/Prebuilt/AppStateContext.tsx +2 -0
  21. package/src/Prebuilt/common/constants.ts +1 -1
  22. package/src/Prebuilt/components/Chat/Chat.tsx +2 -2
  23. package/src/Prebuilt/components/Chat/ChatActions.tsx +3 -3
  24. package/src/Prebuilt/components/Chat/ChatBody.tsx +28 -46
  25. package/src/Prebuilt/components/Chat/EmptyChat.tsx +51 -0
  26. package/src/Prebuilt/components/Footer/PaginatedParticipants.tsx +1 -1
  27. package/src/Prebuilt/components/Footer/PollsToggle.tsx +7 -1
  28. package/src/Prebuilt/components/Notifications/Notifications.tsx +0 -29
  29. package/src/Prebuilt/components/Polls/Polls.tsx +2 -2
  30. package/src/Prebuilt/components/Polls/Voting/LeaderboardEntry.tsx +2 -2
  31. package/src/Prebuilt/components/Polls/Voting/LeaderboardSummary.tsx +162 -0
  32. package/src/Prebuilt/components/Polls/Voting/PeerParticipationSummary.tsx +2 -9
  33. package/src/Prebuilt/components/Polls/Voting/StatisticBox.tsx +15 -0
  34. package/src/Prebuilt/components/Polls/Voting/Voting.jsx +1 -17
  35. package/src/Prebuilt/components/TileMenu/TileMenuContent.tsx +1 -1
  36. package/src/Prebuilt/components/VirtualBackground/VBCollection.tsx +5 -11
  37. package/src/Prebuilt/components/VirtualBackground/VBHandler.tsx +77 -0
  38. package/src/Prebuilt/components/VirtualBackground/VBPicker.tsx +45 -79
  39. package/src/Prebuilt/components/VirtualBackground/constants.ts +0 -4
  40. package/src/Prebuilt/layouts/SidePane.tsx +6 -1
  41. package/src/Theme/stitches.config.ts +2 -10
  42. package/dist/chunk-VU2CQFCB.js.map +0 -7
  43. package/src/Prebuilt/components/Polls/Voting/Leaderboard.tsx +0 -123
  44. package/src/Prebuilt/components/Polls/Voting/PollResultSummary.jsx +0 -125
  45. /package/dist/{HLSView-UBVLOPNY.js.map → HLSView-4L4OAX2K.js.map} +0 -0
package/package.json CHANGED
@@ -10,7 +10,7 @@
10
10
  "prebuilt",
11
11
  "roomkit"
12
12
  ],
13
- "version": "0.1.19-alpha.1",
13
+ "version": "0.1.19",
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.28-alpha.1",
80
- "@100mslive/hms-virtual-background": "1.11.28-alpha.1",
81
- "@100mslive/react-icons": "0.8.28-alpha.1",
82
- "@100mslive/react-sdk": "0.8.28-alpha.1",
79
+ "@100mslive/hls-player": "0.1.28",
80
+ "@100mslive/hms-virtual-background": "1.11.28",
81
+ "@100mslive/react-icons": "0.8.28",
82
+ "@100mslive/react-sdk": "0.8.28",
83
83
  "@100mslive/types-prebuilt": "0.12.5",
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": "5f85c1840b3852eee50dbd94689eac2aa7a1f327"
118
+ "gitHead": "2616c7f5408ccbe73ba402514ca7b87b310d377c"
119
119
  }
@@ -51,6 +51,7 @@ export type HMSPrebuiltOptions = {
51
51
  userName?: string;
52
52
  userId?: string;
53
53
  endpoints?: object;
54
+ effectsSDKKey?: string;
54
55
  };
55
56
 
56
57
  export type HMSPrebuiltProps = {
@@ -215,7 +216,7 @@ export const HMSPrebuilt = React.forwardRef<HMSPrebuiltRefType, HMSPrebuiltProps
215
216
  <Init />
216
217
  <DialogContainerProvider dialogContainerSelector={containerSelector}>
217
218
  <Box
218
- id={DEFAULT_PORTAL_CONTAINER.slice(1)} //Skips the #
219
+ className={DEFAULT_PORTAL_CONTAINER.slice(1)} // Skips the '.' in the selector
219
220
  css={{
220
221
  bg: '$background_dim',
221
222
  size: '100%',
@@ -1,6 +1,7 @@
1
1
  import React, { useContext, useEffect } from 'react';
2
2
  import { usePreviousDistinct } from 'react-use';
3
3
  import { HMSRoomState, selectRoomState, useHMSStore } from '@100mslive/react-sdk';
4
+ import { VBHandler } from './components/VirtualBackground/VBHandler';
4
5
  import { useRoomLayout } from './provider/roomLayoutProvider';
5
6
  import { useRedirectToLeave } from './components/hooks/useRedirectToLeave';
6
7
  import {
@@ -60,6 +61,7 @@ export const useAppStateManager = () => {
60
61
  ) {
61
62
  const goTo = isPreviewScreenEnabled ? PrebuiltStates.PREVIEW : PrebuiltStates.MEETING;
62
63
  setActiveState(isLeaveScreenEnabled ? PrebuiltStates.LEAVE : goTo);
64
+ VBHandler.reset();
63
65
  redirectToLeave(1000); // to clear toasts after 1 second
64
66
  } else if (!prevRoomState && roomState === HMSRoomState.Disconnected) {
65
67
  setActiveState(isPreviewScreenEnabled ? PrebuiltStates.PREVIEW : PrebuiltStates.MEETING);
@@ -134,4 +134,4 @@ export enum QUESTION_TYPE {
134
134
 
135
135
  export const ROLE_CHANGE_DECLINED = 'role_change_declined';
136
136
 
137
- export const DEFAULT_PORTAL_CONTAINER = '#prebuilt-container';
137
+ export const DEFAULT_PORTAL_CONTAINER = '.prebuilt-container';
@@ -70,10 +70,10 @@ const NewMessageIndicator = ({
70
70
  }
71
71
  // @ts-ignore
72
72
  const outerElement = listRef.current._outerRef;
73
- // @ts-ignore
74
- if (outerElement.scrollHeight === listRef.current.state.scrollOffset + outerElement.offsetHeight) {
73
+ if (outerElement.clientHeight + outerElement.scrollTop + outerElement.offsetTop >= outerElement.scrollHeight) {
75
74
  return null;
76
75
  }
76
+
77
77
  return (
78
78
  <Flex
79
79
  justify="center"
@@ -104,8 +104,8 @@ export const ChatActions = ({
104
104
  }
105
105
  > = {
106
106
  reply: {
107
- text: message.recipientRoles?.length ? 'Reply to Group' : 'Reply Privately',
108
- tooltipText: message.recipientRoles?.length ? 'Reply to Group' : 'Reply Privately',
107
+ text: message.recipientRoles?.length ? 'Reply to group' : 'Reply privately',
108
+ tooltipText: message.recipientRoles?.length ? 'Reply to group' : 'Reply privately',
109
109
  icon: <ReplyIcon style={iconStyle} />,
110
110
  onClick: onReply,
111
111
  show: showReply,
@@ -145,7 +145,7 @@ export const ChatActions = ({
145
145
  show: !!can_block_user && !sentByLocalPeer && !isSenderBlocked,
146
146
  },
147
147
  remove: {
148
- text: 'Remove Partipant',
148
+ text: 'Remove participant',
149
149
  icon: <PeopleRemoveIcon style={iconStyle} />,
150
150
  color: '$alert_error_default',
151
151
  show: !!canRemoveOthers && !sentByLocalPeer,
@@ -1,4 +1,4 @@
1
- import React, { Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react';
1
+ import React, { Fragment, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
2
2
  import { useMedia } from 'react-use';
3
3
  import AutoSizer from 'react-virtualized-auto-sizer';
4
4
  import { VariableSizeList } from 'react-window';
@@ -7,11 +7,11 @@ import {
7
7
  HMSPeerID,
8
8
  HMSRoleName,
9
9
  selectHMSMessages,
10
- selectHMSMessagesCount,
11
10
  selectLocalPeerID,
12
11
  selectLocalPeerRoleName,
13
12
  selectPeerNameByID,
14
13
  selectSessionStore,
14
+ selectUnreadHMSMessagesCount,
15
15
  useHMSActions,
16
16
  useHMSStore,
17
17
  useHMSVanillaStore,
@@ -20,9 +20,8 @@ import { Box, Flex } from '../../../Layout';
20
20
  import { Text } from '../../../Text';
21
21
  import { config as cssConfig, styled } from '../../../Theme';
22
22
  import { Tooltip } from '../../../Tooltip';
23
- // @ts-ignore
24
- import emptyChat from '../../images/empty-chat.svg';
25
23
  import { ChatActions } from './ChatActions';
24
+ import { EmptyChat } from './EmptyChat';
26
25
  import { useRoomLayoutConferencingScreen } from '../../provider/roomLayoutProvider/hooks/useRoomLayoutScreen';
27
26
  // @ts-ignore: No implicit Any
28
27
  import { useSetSubscribedChatSelector } from '../AppData/useUISettings';
@@ -38,15 +37,19 @@ const formatTime = (date: Date) => {
38
37
  return `${hours < 10 ? '0' : ''}${hours}:${minutes < 10 ? '0' : ''}${minutes} ${suffix}`;
39
38
  };
40
39
 
41
- const rowHeights: Record<number, number> = {};
40
+ const rowHeights: Record<number, { size: number; id: string }> = {};
41
+ let listInstance: VariableSizeList | null = null; //eslint-disable-line
42
42
  function getRowHeight(index: number) {
43
43
  // 72 will be default row height for any message length
44
- // 16 will add margin value as clientHeight don't include margin
45
- return rowHeights[index] + 16 || 72;
44
+ return rowHeights[index]?.size || 72;
46
45
  }
47
46
 
48
- const setRowHeight = (index: number, size: number) => {
49
- Object.assign(rowHeights, { [index]: size });
47
+ const setRowHeight = (index: number, id: string, size: number) => {
48
+ if (rowHeights[index]?.id === id && rowHeights[index]?.size) {
49
+ return;
50
+ }
51
+ listInstance?.resetAfterIndex(Math.max(index - 1, 0));
52
+ Object.assign(rowHeights, { [index]: { size, id } });
50
53
  };
51
54
 
52
55
  const MessageTypeContainer = ({ left, right }: { left?: string; right?: string }) => {
@@ -181,11 +184,11 @@ const ChatMessage = React.memo(
181
184
  showReply = true;
182
185
  }
183
186
 
184
- useEffect(() => {
187
+ useLayoutEffect(() => {
185
188
  if (rowRef.current) {
186
- setRowHeight(index, rowRef.current.clientHeight);
189
+ setRowHeight(index, message.id, rowRef.current.clientHeight);
187
190
  }
188
- }, [index]);
191
+ }, [index, message.id]);
189
192
 
190
193
  return (
191
194
  <Box
@@ -356,7 +359,13 @@ const VirtualizedChatMessages = React.forwardRef<
356
359
  >
357
360
  {({ height, width }: { height: number; width: number }) => (
358
361
  <VariableSizeList
359
- ref={listRef}
362
+ ref={node => {
363
+ if (node) {
364
+ // @ts-ignore
365
+ listRef.current = node;
366
+ listInstance = node;
367
+ }
368
+ }}
360
369
  itemCount={messages.length}
361
370
  itemSize={getRowHeight}
362
371
  itemData={messages}
@@ -391,52 +400,25 @@ export const ChatBody = React.forwardRef<VariableSizeList, { scrollToBottom: (co
391
400
  return messages?.filter(message => message.type === 'chat' && !blacklistedMessageIDSet.has(message.id)) || [];
392
401
  }, [blacklistedMessageIDs, messages]);
393
402
 
394
- const isMobile = useMedia(cssConfig.media.md);
395
- const { elements } = useRoomLayoutConferencingScreen();
396
403
  const vanillaStore = useHMSVanillaStore();
397
404
 
398
405
  useEffect(() => {
399
406
  const unsubscribe = vanillaStore.subscribe(() => {
400
407
  // @ts-ignore
401
- if (!listRef?.current) {
408
+ if (!listRef.current) {
402
409
  return;
403
410
  }
404
411
  // @ts-ignore
405
412
  const outerElement = listRef.current._outerRef;
406
- // @ts-ignore
407
- if (outerElement.scrollHeight - (listRef.current.state.scrollOffset + outerElement.offsetHeight) <= 10) {
408
- scrollToBottom(1);
413
+ if (outerElement.clientHeight + outerElement.scrollTop + outerElement.offsetTop >= outerElement.scrollHeight) {
414
+ requestAnimationFrame(() => scrollToBottom(1));
409
415
  }
410
- }, selectHMSMessagesCount);
416
+ }, selectUnreadHMSMessagesCount);
411
417
  return unsubscribe;
412
418
  }, [vanillaStore, listRef, scrollToBottom]);
413
419
 
414
- if (filteredMessages.length === 0 && !(isMobile && elements?.chat?.is_overlay)) {
415
- return (
416
- <Flex
417
- css={{
418
- width: '100%',
419
- flex: '1 1 0',
420
- textAlign: 'center',
421
- px: '$4',
422
- }}
423
- align="center"
424
- justify="center"
425
- >
426
- <Box>
427
- <img src={emptyChat} alt="Empty Chat" height={132} width={185} style={{ margin: '0 auto' }} />
428
- <Text variant="h5" css={{ mt: '$8', c: '$on_surface_high' }}>
429
- Start a conversation
430
- </Text>
431
- <Text
432
- variant="sm"
433
- css={{ mt: '$4', maxWidth: '80%', textAlign: 'center', mx: 'auto', c: '$on_surface_medium' }}
434
- >
435
- There are no messages here yet. Start a conversation by sending a message.
436
- </Text>
437
- </Box>
438
- </Flex>
439
- );
420
+ if (filteredMessages.length === 0) {
421
+ return <EmptyChat />;
440
422
  }
441
423
 
442
424
  return <VirtualizedChatMessages messages={filteredMessages} ref={listRef} scrollToBottom={scrollToBottom} />;
@@ -0,0 +1,51 @@
1
+ import React from 'react';
2
+ import { useMedia } from 'react-use';
3
+ import { Box, Flex } from '../../../Layout';
4
+ import { Text } from '../../../Text';
5
+ import { config as cssConfig } from '../../../Theme';
6
+ // @ts-ignore
7
+ import emptyChat from '../../images/empty-chat.svg';
8
+ import { useRoomLayoutConferencingScreen } from '../../provider/roomLayoutProvider/hooks/useRoomLayoutScreen';
9
+ import { useIsPeerBlacklisted } from '../hooks/useChatBlacklist';
10
+
11
+ export const EmptyChat = () => {
12
+ const { elements } = useRoomLayoutConferencingScreen();
13
+ const isLocalPeerBlacklisted = useIsPeerBlacklisted({ local: true });
14
+ const isMobile = useMedia(cssConfig.media.md);
15
+ const canSendMessages =
16
+ elements.chat &&
17
+ (elements.chat.public_chat_enabled ||
18
+ elements.chat.private_chat_enabled ||
19
+ (elements.chat.roles_whitelist && elements.chat.roles_whitelist.length)) &&
20
+ !isLocalPeerBlacklisted;
21
+
22
+ if (isMobile && elements?.chat?.is_overlay) return <></>;
23
+
24
+ return (
25
+ <Flex
26
+ css={{
27
+ width: '100%',
28
+ flex: '1 1 0',
29
+ textAlign: 'center',
30
+ px: '$4',
31
+ }}
32
+ align="center"
33
+ justify="center"
34
+ >
35
+ <Box>
36
+ <img src={emptyChat} alt="Empty Chat" height={132} width={185} style={{ margin: '0 auto' }} />
37
+ <Text variant="h5" css={{ mt: '$8', c: '$on_surface_high' }}>
38
+ {canSendMessages ? 'Start a conversation' : 'No messages yet'}
39
+ </Text>
40
+ {canSendMessages ? (
41
+ <Text
42
+ variant="sm"
43
+ css={{ mt: '$4', maxWidth: '80%', textAlign: 'center', mx: 'auto', c: '$on_surface_medium' }}
44
+ >
45
+ There are no messages here yet. Start a conversation by sending a message.
46
+ </Text>
47
+ ) : null}
48
+ </Box>
49
+ </Flex>
50
+ );
51
+ };
@@ -69,7 +69,7 @@ const VirtualizedParticipantItem = React.memo(
69
69
  export const PaginatedParticipants = ({ roleName, onBack }: { roleName: string; onBack: () => void }) => {
70
70
  const { peers, total, hasNext, loadPeers, loadMorePeers } = usePaginatedParticipants({ role: roleName, limit: 20 });
71
71
  const [search, setSearch] = useState<string>('');
72
- const filteredPeers = peers.filter(p => p.name?.toLowerCase().includes(search));
72
+ const filteredPeers = peers.filter(p => p.name?.toLowerCase().includes(search?.toLowerCase()));
73
73
  const isConnected = useHMSStore(selectIsConnectedToRoom);
74
74
  const [ref, { width }] = useMeasure<HTMLDivElement>();
75
75
  const height = ROW_HEIGHT * (filteredPeers.length + 1);
@@ -1,4 +1,4 @@
1
- import React from 'react';
1
+ import React, { useEffect } from 'react';
2
2
  import { QuizActiveIcon, QuizIcon } from '@100mslive/react-icons';
3
3
  import { Tooltip } from '../../..';
4
4
  // @ts-ignore: No implicit Any
@@ -14,6 +14,12 @@ export const PollsToggle = () => {
14
14
  const togglePollView = usePollViewToggle();
15
15
  const { unreadPollQuiz, setUnreadPollQuiz } = useUnreadPollQuizPresent();
16
16
 
17
+ useEffect(() => {
18
+ if (unreadPollQuiz && isPollsOpen) {
19
+ setUnreadPollQuiz(false);
20
+ }
21
+ }, [isPollsOpen, unreadPollQuiz, setUnreadPollQuiz]);
22
+
17
23
  return (
18
24
  <Tooltip key="polls" title={`${isPollsOpen ? 'Close' : 'Open'} polls and quizzes`}>
19
25
  <IconButton
@@ -56,36 +56,7 @@ export function Notifications() {
56
56
  });
57
57
  }, []);
58
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
-
87
59
  useCustomEvent({ type: ROLE_CHANGE_DECLINED, onEvent: handleRoleChangeDenied });
88
- // useCustomEvent({ type: 'POLL_LEADERBOARD_SHARED', onEvent: leaderboardResultsShared });
89
60
 
90
61
  useEffect(() => {
91
62
  if (!notification || isNotificationDisabled) {
@@ -3,7 +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
+ import { LeaderboardSummary } from './Voting/LeaderboardSummary';
7
7
  // @ts-ignore: No implicit Any
8
8
  import { Voting } from './Voting/Voting';
9
9
  // @ts-ignore: No implicit Any
@@ -24,7 +24,7 @@ export const Polls = () => {
24
24
  } else if (view === POLL_VIEWS.VOTE) {
25
25
  return <Voting toggleVoting={togglePollView} id={pollID} />;
26
26
  } else if (view === POLL_VIEWS.RESULTS) {
27
- return <Leaderboard pollID={pollID} />;
27
+ return <LeaderboardSummary pollID={pollID} />;
28
28
  } else {
29
29
  return null;
30
30
  }
@@ -44,13 +44,13 @@ export const LeaderboardEntry = ({
44
44
  {userName}
45
45
  </Text>
46
46
 
47
- <Text variant="sm" css={{ mt: '$2' }}>
47
+ <Text variant="sm" css={{ mt: '$1' }}>
48
48
  {score} / {maxPossibleScore} points
49
49
  </Text>
50
50
  </Box>
51
51
  </Flex>
52
52
  <Flex align="center" css={{ gap: '$4', color: '$on_surface_medium' }}>
53
- {position === 1 ? <TrophyFilledIcon height={18} width={18} /> : null}
53
+ {position === 1 ? <TrophyFilledIcon height={16} width={16} /> : null}
54
54
  <CheckCircleIcon height={16} width={16} />
55
55
  {questionCount ? (
56
56
  <Text variant="xs">
@@ -0,0 +1,162 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import {
3
+ HMSQuizLeaderboardResponse,
4
+ HMSQuizLeaderboardSummary,
5
+ selectPollByID,
6
+ useHMSActions,
7
+ useHMSStore,
8
+ } from '@100mslive/react-sdk';
9
+ import { ChevronLeftIcon, ChevronRightIcon, CrossIcon } from '@100mslive/react-icons';
10
+ import { Box, Flex } from '../../../../Layout';
11
+ import { Loading } from '../../../../Loading';
12
+ import { Text } from '../../../../Text';
13
+ import { LeaderboardEntry } from './LeaderboardEntry';
14
+ import { StatisticBox } from './StatisticBox';
15
+ // @ts-ignore
16
+ import { useSidepaneToggle } from '../../AppData/useSidepane';
17
+ // @ts-ignore
18
+ import { usePollViewState } from '../../AppData/useUISettings';
19
+ // @ts-ignore
20
+ import { StatusIndicator } from '../common/StatusIndicator';
21
+ import { POLL_VIEWS } from '../../../common/constants';
22
+
23
+ export const LeaderboardSummary = ({ pollID }: { pollID: string }) => {
24
+ const hmsActions = useHMSActions();
25
+ const quiz = useHMSStore(selectPollByID(pollID));
26
+ const [quizLeaderboard, setQuizLeaderboard] = useState<HMSQuizLeaderboardResponse | undefined>();
27
+ const [viewAllEntries, setViewAllEntries] = useState(false);
28
+ const summary: HMSQuizLeaderboardSummary = quizLeaderboard?.summary || {
29
+ totalUsers: 0,
30
+ votedUsers: 0,
31
+ avgScore: 0,
32
+ avgTime: 0,
33
+ correctAnswers: 0,
34
+ };
35
+
36
+ const { setPollView } = usePollViewState();
37
+ const toggleSidepane = useSidepaneToggle();
38
+
39
+ useEffect(() => {
40
+ const fetchLeaderboardData = async () => {
41
+ if (!quizLeaderboard && quiz) {
42
+ const leaderboardData = await hmsActions.interactivityCenter.fetchLeaderboard(quiz, 0, 50);
43
+ setQuizLeaderboard(leaderboardData);
44
+ }
45
+ };
46
+ fetchLeaderboardData();
47
+ }, [quiz, hmsActions.interactivityCenter, quizLeaderboard]);
48
+
49
+ if (!quiz || !quizLeaderboard)
50
+ return (
51
+ <Flex align="center" justify="center" css={{ size: '100%' }}>
52
+ <Loading />
53
+ </Flex>
54
+ );
55
+
56
+ const defaultCalculations = { maxPossibleScore: 0, totalResponses: 0 };
57
+ const { maxPossibleScore, totalResponses } =
58
+ quiz.questions?.reduce((accumulator, question) => {
59
+ accumulator.maxPossibleScore += question.weight || 0;
60
+ accumulator.totalResponses += question?.responses?.length || 0;
61
+ return accumulator;
62
+ }, defaultCalculations) || defaultCalculations;
63
+
64
+ const questionCount = quiz.questions?.length || 0;
65
+
66
+ return (
67
+ <Flex direction="column" css={{ size: '100%' }}>
68
+ <Flex justify="between" align="center" css={{ pb: '$6', borderBottom: '1px solid $border_bright', mb: '$8' }}>
69
+ <Flex align="center" css={{ gap: '$4' }}>
70
+ <Flex
71
+ css={{ color: '$on_surface_medium', '&:hover': { color: '$on_surface_high', cursor: 'pointer' } }}
72
+ onClick={() => setPollView(POLL_VIEWS.VOTE)}
73
+ >
74
+ <ChevronLeftIcon />
75
+ </Flex>
76
+ <Text variant="lg" css={{ fontWeight: '$semiBold' }}>
77
+ {quiz.title}
78
+ </Text>
79
+ <StatusIndicator isLive={false} />
80
+ </Flex>
81
+ <Flex
82
+ css={{ color: '$on_surface_medium', '&:hover': { color: '$on_surface_high', cursor: 'pointer' } }}
83
+ onClick={toggleSidepane}
84
+ >
85
+ <CrossIcon />
86
+ </Flex>
87
+ </Flex>
88
+
89
+ {!viewAllEntries ? (
90
+ <Box css={{ py: '$4' }}>
91
+ <Text variant="sm" css={{ fontWeight: '$semiBold' }}>
92
+ Participation Summary
93
+ </Text>
94
+
95
+ <Box css={{ my: '$4' }}>
96
+ <Flex css={{ w: '100%', gap: '$4' }}>
97
+ <StatisticBox
98
+ title="Voted"
99
+ value={`${summary?.totalUsers ? (100 * summary?.votedUsers) / summary?.totalUsers : 0}%`}
100
+ />
101
+ <StatisticBox title="Correct Answers" value={`${summary?.correctAnswers}/${totalResponses}`} />
102
+ </Flex>
103
+ <Flex css={{ w: '100%', gap: '$4', mt: '$4' }}>
104
+ {summary?.avgTime > 0 ? <StatisticBox title="Avg. Time" value={summary?.avgTime} /> : null}
105
+ <StatisticBox title="Avg. Score" value={summary?.avgScore} />
106
+ </Flex>
107
+ </Box>
108
+ </Box>
109
+ ) : null}
110
+
111
+ <Text variant="sm" css={{ fontWeight: '$semiBold' }}>
112
+ Leaderboard
113
+ </Text>
114
+ <Text variant="xs" css={{ color: '$on_surface_medium' }}>
115
+ Based on score and time taken to cast the correct answer
116
+ </Text>
117
+ <Box
118
+ css={{
119
+ mt: '$8',
120
+ overflowY: 'auto',
121
+ flex: viewAllEntries ? '1 1 0' : 'unset',
122
+ mr: viewAllEntries ? '-$6' : 'unset',
123
+ px: viewAllEntries ? '0' : '$4',
124
+ pr: viewAllEntries ? '$6' : '$4',
125
+ backgroundColor: viewAllEntries ? '' : '$surface_default',
126
+ borderRadius: '$1',
127
+ }}
128
+ >
129
+ {quizLeaderboard?.entries &&
130
+ quizLeaderboard.entries
131
+ .slice(0, viewAllEntries ? undefined : 5)
132
+ .map(question => (
133
+ <LeaderboardEntry
134
+ key={question.position}
135
+ position={question.position}
136
+ score={question.score}
137
+ questionCount={questionCount}
138
+ correctResponses={question.correctResponses}
139
+ userName={question.peer.username || ''}
140
+ maxPossibleScore={maxPossibleScore}
141
+ />
142
+ ))}
143
+ {quizLeaderboard?.entries?.length > 5 && !viewAllEntries ? (
144
+ <Flex
145
+ align="center"
146
+ justify="end"
147
+ css={{
148
+ w: '100%',
149
+ borderTop: '1px solid $border_bright',
150
+ cursor: 'pointer',
151
+ color: '$on_surface_high',
152
+ p: '$6 $2',
153
+ }}
154
+ onClick={() => setViewAllEntries(true)}
155
+ >
156
+ <Text variant="sm">View All</Text> <ChevronRightIcon />
157
+ </Flex>
158
+ ) : null}
159
+ </Box>
160
+ </Flex>
161
+ );
162
+ };
@@ -2,6 +2,7 @@ import React from 'react';
2
2
  import { HMSPoll, selectLocalPeer, useHMSStore } from '@100mslive/react-sdk';
3
3
  import { Box } from '../../../../Layout';
4
4
  import { Text } from '../../../../Text';
5
+ import { StatisticBox } from './StatisticBox';
5
6
  // @ts-ignore
6
7
  import { getPeerParticipationSummary } from '../../../common/utils';
7
8
 
@@ -22,15 +23,7 @@ export const PeerParticipationSummary = ({ poll }: { poll: HMSPoll }) => {
22
23
  <Text css={{ fontWeight: '$semiBold', my: '$8' }}>Participation Summary</Text>
23
24
  <Box css={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '$4' }}>
24
25
  {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>
26
+ <StatisticBox key={box.title} title={box.title} value={box.value} />
34
27
  ))}
35
28
  </Box>
36
29
  </Box>
@@ -0,0 +1,15 @@
1
+ import React from 'react';
2
+ import { Box } from '../../../../Layout';
3
+ import { Text } from '../../../../Text';
4
+
5
+ export const StatisticBox = ({ title, value = 0 }: { title: string; value: string | number }) => (
6
+ <Box css={{ p: '$8', background: '$surface_default', borderRadius: '$1', w: '100%' }}>
7
+ <Text
8
+ variant="tiny"
9
+ css={{ textTransform: 'uppercase', color: '$on_surface_medium', fontWeight: '$semiBold', my: '$4' }}
10
+ >
11
+ {title}
12
+ </Text>
13
+ <Text css={{ fontWeight: '$semiBold' }}>{value}</Text>
14
+ </Box>
15
+ );
@@ -3,7 +3,6 @@ import React from 'react';
3
3
  import {
4
4
  selectLocalPeerID,
5
5
  selectPeerNameByID,
6
- selectPermissions,
7
6
  selectPollByID,
8
7
  useHMSActions,
9
8
  useHMSStore,
@@ -11,7 +10,6 @@ import {
11
10
  import { ChevronLeftIcon, CrossIcon } from '@100mslive/react-icons';
12
11
  import { Box, Button, Flex, Text } from '../../../../';
13
12
  import { Container } from '../../Streaming/Common';
14
- // import { PollResultSummary } from "./PollResultSummary";
15
13
  import { StandardView } from './StandardVoting';
16
14
  import { TimedView } from './TimedVoting';
17
15
  import { usePollViewState } from '../../AppData/useUISettings';
@@ -24,17 +22,12 @@ export const Voting = ({ id, toggleVoting }) => {
24
22
  const pollCreatorName = useHMSStore(selectPeerNameByID(poll?.createdBy));
25
23
  const isLocalPeerCreator = useHMSStore(selectLocalPeerID) === poll?.createdBy;
26
24
  const { setPollView } = usePollViewState();
27
- const permissions = useHMSStore(selectPermissions);
28
-
29
- // const sharedLeaderboards = useHMSStore(selectSessionStore(SESSION_STORE_KEY.SHARED_LEADERBOARDS));
30
25
 
31
26
  if (!poll) {
32
27
  return null;
33
28
  }
34
29
 
35
- // const isLeaderboardShared = (sharedLeaderboards || []).includes(id);
36
- const canViewLeaderboard =
37
- poll.type === 'quiz' && poll.state === 'stopped' && !poll.anonymous && permissions?.pollWrite;
30
+ const canViewLeaderboard = poll.type === 'quiz' && poll.state === 'stopped' && !poll.anonymous;
38
31
 
39
32
  // Sets view - linear or vertical, toggles timer indicator
40
33
  const isTimed = (poll.duration || 0) > 0;
@@ -82,15 +75,6 @@ export const Voting = ({ id, toggleVoting }) => {
82
75
  </Box>
83
76
  </Flex>
84
77
 
85
- {/* {poll.state === "stopped" && (
86
- <PollResultSummary
87
- pollResult={poll.result}
88
- questions={poll.questions}
89
- isQuiz={poll.type === "quiz"}
90
- isAdmin={isLocalPeerCreator}
91
- />
92
- )} */}
93
-
94
78
  {isTimed ? <TimedView poll={poll} /> : <StandardView poll={poll} />}
95
79
 
96
80
  {poll.state === 'started' && isLocalPeerCreator && (