@100mslive/roomkit-react 0.1.8 → 0.1.9-alpha.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (140) hide show
  1. package/dist/{HLSView-DDGPZHA2.js → HLSView-4JC65BAY.js} +3 -3
  2. package/dist/Modal/Dialog.d.ts +402 -1706
  3. package/dist/Prebuilt/App.d.ts +5 -0
  4. package/dist/Prebuilt/AppContext.d.ts +1 -0
  5. package/dist/Prebuilt/AppStateContext.d.ts +16 -0
  6. package/dist/Prebuilt/components/ConferenceScreen.d.ts +2 -0
  7. package/dist/Prebuilt/components/Footer/PollsToggle.d.ts +2 -0
  8. package/dist/Prebuilt/components/LeaveScreen.d.ts +2 -0
  9. package/dist/Prebuilt/components/MwebLandscapePrompt.d.ts +2 -0
  10. package/dist/Prebuilt/components/Notifications/AutoplayBlockedModal.d.ts +2 -0
  11. package/dist/Prebuilt/components/Notifications/HLSFailureModal.d.ts +2 -0
  12. package/dist/Prebuilt/components/Notifications/InitErrorModal.d.ts +2 -0
  13. package/dist/Prebuilt/components/Notifications/Notifications.d.ts +2 -0
  14. package/dist/Prebuilt/components/Notifications/PeerNotifications.d.ts +1 -0
  15. package/dist/Prebuilt/components/Notifications/PermissionErrorModal.d.ts +2 -0
  16. package/dist/Prebuilt/components/Notifications/ReconnectNotifications.d.ts +2 -0
  17. package/dist/Prebuilt/components/Notifications/TrackBulkUnmuteModal.d.ts +2 -0
  18. package/dist/Prebuilt/components/Notifications/TrackNotifications.d.ts +1 -0
  19. package/dist/Prebuilt/components/Notifications/TrackUnmuteModal.d.ts +2 -0
  20. package/dist/Prebuilt/components/Polls/Polls.d.ts +2 -0
  21. package/dist/Prebuilt/components/Preview/PreviewJoin.d.ts +1 -2
  22. package/dist/Prebuilt/components/Preview/PreviewScreen.d.ts +2 -0
  23. package/dist/Prebuilt/components/hooks/useRedirectToLeave.d.ts +1 -1
  24. package/dist/{VirtualBackground-UVZJVOA2.js → VirtualBackground-MIRXD2HZ.js} +3 -5
  25. package/dist/{VirtualBackground-UVZJVOA2.js.map → VirtualBackground-MIRXD2HZ.js.map} +1 -1
  26. package/dist/chunk-322YFA55.js +14441 -0
  27. package/dist/chunk-322YFA55.js.map +7 -0
  28. package/dist/{chunk-6SQTFOK6.js → chunk-6UGU3UJL.js} +66 -3
  29. package/dist/chunk-6UGU3UJL.js.map +7 -0
  30. package/dist/context/DialogContext.d.ts +6 -0
  31. package/dist/hooks/useDialogContainerSelector.d.ts +1 -0
  32. package/dist/index.cjs.js +10944 -9974
  33. package/dist/index.cjs.js.map +4 -4
  34. package/dist/index.d.ts +1 -0
  35. package/dist/index.js +6 -2
  36. package/dist/meta.cjs.json +3871 -3188
  37. package/dist/meta.esbuild.json +4303 -3728
  38. package/dist/utils/animations.d.ts +11 -0
  39. package/package.json +6 -7
  40. package/src/Modal/Dialog.tsx +31 -3
  41. package/src/Prebuilt/App.tsx +46 -99
  42. package/src/Prebuilt/AppContext.tsx +4 -0
  43. package/src/Prebuilt/AppStateContext.tsx +71 -0
  44. package/src/Prebuilt/common/constants.js +35 -0
  45. package/src/Prebuilt/common/utils.js +47 -0
  46. package/src/Prebuilt/components/AppData/AppData.jsx +5 -0
  47. package/src/Prebuilt/components/AppData/useSidepane.js +23 -1
  48. package/src/Prebuilt/components/AppData/useUISettings.js +48 -4
  49. package/src/Prebuilt/components/{conference.jsx → ConferenceScreen.tsx} +30 -43
  50. package/src/Prebuilt/components/Footer/Footer.tsx +5 -0
  51. package/src/Prebuilt/components/Footer/PaginatedParticipants.tsx +63 -32
  52. package/src/Prebuilt/components/Footer/ParticipantList.jsx +2 -1
  53. package/src/Prebuilt/components/Footer/PollsToggle.tsx +22 -0
  54. package/src/Prebuilt/components/Footer/RoleAccordion.tsx +2 -2
  55. package/src/Prebuilt/components/Header/StreamActions.tsx +5 -3
  56. package/src/Prebuilt/components/Leave/DesktopLeaveRoom.tsx +4 -5
  57. package/src/Prebuilt/components/Leave/LeaveRoom.tsx +0 -4
  58. package/src/Prebuilt/components/{PostLeave.jsx → LeaveScreen.tsx} +6 -13
  59. package/src/Prebuilt/components/MoreSettings/ChangeNameModal.jsx +2 -3
  60. package/src/Prebuilt/components/MoreSettings/EmbedUrl.jsx +2 -3
  61. package/src/Prebuilt/components/MoreSettings/SplitComponents/MwebOptions.tsx +18 -1
  62. package/src/Prebuilt/components/{MwebLandscapePrompt.jsx → MwebLandscapePrompt.tsx} +10 -11
  63. package/src/Prebuilt/components/Notifications/{AutoplayBlockedModal.jsx → AutoplayBlockedModal.tsx} +2 -1
  64. package/src/Prebuilt/components/Notifications/{HLSFailureModal.jsx → HLSFailureModal.tsx} +10 -8
  65. package/src/Prebuilt/components/Notifications/{InitErrorModal.jsx → InitErrorModal.tsx} +5 -2
  66. package/src/Prebuilt/components/Notifications/{Notifications.jsx → Notifications.tsx} +41 -27
  67. package/src/Prebuilt/components/Notifications/{PeerNotifications.jsx → PeerNotifications.tsx} +3 -0
  68. package/src/Prebuilt/components/Notifications/{PermissionErrorModal.jsx → PermissionErrorModal.tsx} +6 -4
  69. package/src/Prebuilt/components/Notifications/{ReconnectNotifications.jsx → ReconnectNotifications.tsx} +11 -6
  70. package/src/Prebuilt/components/Notifications/{TrackBulkUnmuteModal.jsx → TrackBulkUnmuteModal.tsx} +9 -3
  71. package/src/Prebuilt/components/Notifications/{TrackUnmuteModal.jsx → TrackUnmuteModal.tsx} +9 -3
  72. package/src/Prebuilt/components/Notifications/index.tsx +1 -0
  73. package/src/Prebuilt/components/Polls/CreatePollQuiz/PollsQuizMenu.jsx +229 -0
  74. package/src/Prebuilt/components/Polls/CreatePollQuiz/Timer.jsx +71 -0
  75. package/src/Prebuilt/components/Polls/CreateQuestions/CreateQuestions.jsx +132 -0
  76. package/src/Prebuilt/components/Polls/CreateQuestions/DeleteQuestionModal.jsx +66 -0
  77. package/src/Prebuilt/components/Polls/CreateQuestions/QuestionForm.jsx +251 -0
  78. package/src/Prebuilt/components/Polls/CreateQuestions/SavedQuestion.jsx +57 -0
  79. package/src/Prebuilt/components/Polls/Polls.tsx +28 -0
  80. package/src/Prebuilt/components/Polls/Voting/PollResultSummary.jsx +125 -0
  81. package/src/Prebuilt/components/Polls/Voting/QuestionCard.jsx +249 -0
  82. package/src/Prebuilt/components/Polls/Voting/StandardVoting.jsx +40 -0
  83. package/src/Prebuilt/components/Polls/Voting/TimedVoting.jsx +36 -0
  84. package/src/Prebuilt/components/Polls/Voting/Voting.jsx +99 -0
  85. package/src/Prebuilt/components/Polls/common/MultipleChoiceOptions.jsx +101 -0
  86. package/src/Prebuilt/components/Polls/common/OptionInputWithDelete.jsx +25 -0
  87. package/src/Prebuilt/components/Polls/common/SingleChoiceOptions.jsx +125 -0
  88. package/src/Prebuilt/components/Polls/common/StatusIndicator.jsx +47 -0
  89. package/src/Prebuilt/components/Polls/common/VoteCount.jsx +28 -0
  90. package/src/Prebuilt/components/Polls/common/VoteProgress.jsx +17 -0
  91. package/src/Prebuilt/components/Polls/common/VoterList.jsx +22 -0
  92. package/src/Prebuilt/components/Polls/common/Votes.jsx +72 -0
  93. package/src/Prebuilt/components/Preview/PreviewForm.tsx +3 -2
  94. package/src/Prebuilt/components/Preview/PreviewJoin.tsx +32 -27
  95. package/src/Prebuilt/components/Preview/{PreviewContainer.tsx → PreviewScreen.tsx} +2 -19
  96. package/src/Prebuilt/components/RaiseHand.jsx +1 -1
  97. package/src/Prebuilt/components/RoleChangeModal.jsx +2 -3
  98. package/src/Prebuilt/components/RoleChangeRequest/RequestPrompt.tsx +2 -3
  99. package/src/Prebuilt/components/Settings/SettingsModal.jsx +2 -3
  100. package/src/Prebuilt/components/Settings/StartRecording.jsx +15 -4
  101. package/src/Prebuilt/components/SidePaneTabs.tsx +1 -1
  102. package/src/Prebuilt/components/StatsForNerds.jsx +2 -3
  103. package/src/Prebuilt/components/Streaming/Common.jsx +31 -21
  104. package/src/Prebuilt/components/VideoLayouts/ScreenshareLayout.tsx +8 -9
  105. package/src/Prebuilt/components/VideoTile.jsx +37 -33
  106. package/src/Prebuilt/components/hooks/useAutoStartStreaming.tsx +3 -3
  107. package/src/Prebuilt/components/hooks/useRedirectToLeave.tsx +9 -17
  108. package/src/Prebuilt/components/pdfAnnotator/pdfFileOptions.jsx +2 -3
  109. package/src/Prebuilt/components/pdfAnnotator/submitPdf.jsx +1 -1
  110. package/src/Prebuilt/components/pdfAnnotator/uploadedFile.jsx +2 -3
  111. package/src/Prebuilt/layouts/EmbedView.jsx +47 -60
  112. package/src/Prebuilt/layouts/PDFView.jsx +49 -99
  113. package/src/Prebuilt/layouts/SidePane.tsx +8 -4
  114. package/src/Prebuilt/layouts/VideoStreamingSection.tsx +2 -2
  115. package/src/Prebuilt/primitives/DialogContent.jsx +4 -5
  116. package/src/context/DialogContext.tsx +13 -0
  117. package/src/hooks/useDialogContainerSelector.tsx +7 -0
  118. package/src/index.ts +1 -0
  119. package/src/utils/animations.ts +6 -0
  120. package/dist/Prebuilt/components/Notifications/HeadlessEndRoomListener.d.ts +0 -2
  121. package/dist/Prebuilt/components/PrebuiltDialogPortal.d.ts +0 -4
  122. package/dist/Prebuilt/components/PrebuiltTileElements.d.ts +0 -2198
  123. package/dist/Prebuilt/components/Preview/PreviewContainer.d.ts +0 -3
  124. package/dist/chunk-6SQTFOK6.js.map +0 -7
  125. package/dist/chunk-HUMNPIYI.js +0 -70
  126. package/dist/chunk-HUMNPIYI.js.map +0 -7
  127. package/dist/chunk-PRM33R4R.js +0 -7160
  128. package/dist/chunk-PRM33R4R.js.map +0 -7
  129. package/dist/conference-N7S47TDK.js +0 -6602
  130. package/dist/conference-N7S47TDK.js.map +0 -7
  131. package/src/Prebuilt/components/GoLiveButton.jsx +0 -42
  132. package/src/Prebuilt/components/Notifications/HeadlessEndRoomListener.tsx +0 -23
  133. package/src/Prebuilt/components/PrebuiltDialogPortal.tsx +0 -6
  134. package/src/Prebuilt/components/PrebuiltTileElements.tsx +0 -5
  135. package/src/Prebuilt/components/Streaming/HLSStreaming.jsx +0 -220
  136. package/src/Prebuilt/components/Streaming/RTMPStreaming.jsx +0 -334
  137. package/src/Prebuilt/components/Streaming/StreamingLanding.jsx +0 -76
  138. /package/dist/{HLSView-DDGPZHA2.js.map → HLSView-4JC65BAY.js.map} +0 -0
  139. /package/{src/Prebuilt/components/Notifications/index.jsx → dist/Prebuilt/components/Notifications/index.d.ts} +0 -0
  140. /package/src/Prebuilt/components/Notifications/{TrackNotifications.jsx → TrackNotifications.tsx} +0 -0
@@ -0,0 +1,249 @@
1
+ // @ts-check
2
+ import React, { useCallback, useMemo, useState } from 'react';
3
+ import { selectLocalPeerID, selectLocalPeerRoleName, useHMSActions, useHMSStore } from '@100mslive/react-sdk';
4
+ import { ChevronLeftIcon, ChevronRightIcon } from '@100mslive/react-icons';
5
+ import { Box, Button, Flex, IconButton, Input, styled, Text } from '../../../../';
6
+ import { checkCorrectAnswer } from '../../../common/utils';
7
+ import { MultipleChoiceOptions } from '../common/MultipleChoiceOptions';
8
+ import { SingleChoiceOptions } from '../common/SingleChoiceOptions';
9
+ import { QUESTION_TYPE } from '../../../common/constants';
10
+
11
+ const TextArea = styled('textarea', {
12
+ backgroundColor: '$surface_brighter',
13
+ border: '1px solid $border_bright',
14
+ borderRadius: '$1',
15
+ mb: '$md',
16
+ color: '$on_surface_high',
17
+ resize: 'none',
18
+ p: '$2',
19
+ w: '100%',
20
+ });
21
+
22
+ export const QuestionCard = ({
23
+ pollID,
24
+ isQuiz,
25
+ startedBy,
26
+ pollState,
27
+ index,
28
+ totalQuestions,
29
+ result,
30
+ type,
31
+ text,
32
+ options = [],
33
+ answer,
34
+ setCurrentIndex,
35
+ skippable = false,
36
+ responses = [],
37
+ isTimed = false,
38
+ rolesThatCanViewResponses,
39
+ }) => {
40
+ const actions = useHMSActions();
41
+ const localPeerID = useHMSStore(selectLocalPeerID);
42
+ const localPeerResponse = responses?.find(response => response.peer?.peerid === localPeerID);
43
+ const isLocalPeerCreator = localPeerID === startedBy;
44
+ const localPeerRoleName = useHMSStore(selectLocalPeerRoleName);
45
+ const roleCanViewResponse =
46
+ !rolesThatCanViewResponses ||
47
+ rolesThatCanViewResponses.length === 0 ||
48
+ rolesThatCanViewResponses.includes(localPeerRoleName || '');
49
+ const showVoteCount = roleCanViewResponse && (localPeerResponse || (isLocalPeerCreator && pollState === 'stopped'));
50
+
51
+ const isLive = pollState === 'started';
52
+ const canRespond = isLive && !localPeerResponse;
53
+
54
+ const isCorrectAnswer = checkCorrectAnswer(answer, localPeerResponse, type);
55
+
56
+ const prev = index !== 1;
57
+ const next = index !== totalQuestions && (skippable || localPeerResponse);
58
+
59
+ const moveNext = useCallback(() => {
60
+ setCurrentIndex(curr => Math.min(totalQuestions, curr + 1));
61
+ }, [setCurrentIndex, totalQuestions]);
62
+
63
+ const movePrev = () => {
64
+ setCurrentIndex(curr => Math.max(1, curr - 1));
65
+ };
66
+
67
+ const [textAnswer, setTextAnswer] = useState('');
68
+ const [singleOptionAnswer, setSingleOptionAnswer] = useState();
69
+ const [multipleOptionAnswer, setMultipleOptionAnswer] = useState(new Set());
70
+
71
+ const stringAnswerExpected = [QUESTION_TYPE.LONG_ANSWER, QUESTION_TYPE.SHORT_ANSWER].includes(type);
72
+
73
+ const isValidVote = useMemo(() => {
74
+ if (stringAnswerExpected) {
75
+ return textAnswer.length > 0;
76
+ } else if (type === QUESTION_TYPE.SINGLE_CHOICE) {
77
+ return singleOptionAnswer !== undefined;
78
+ } else if (type === QUESTION_TYPE.MULTIPLE_CHOICE) {
79
+ return multipleOptionAnswer.size > 0;
80
+ }
81
+ }, [textAnswer, singleOptionAnswer, multipleOptionAnswer, type, stringAnswerExpected]);
82
+
83
+ const handleVote = useCallback(async () => {
84
+ if (!isValidVote) {
85
+ return;
86
+ }
87
+ await actions.interactivityCenter.addResponsesToPoll(pollID, [
88
+ {
89
+ questionIndex: index,
90
+ text: textAnswer,
91
+ option: singleOptionAnswer,
92
+ options: Array.from(multipleOptionAnswer),
93
+ },
94
+ ]);
95
+ }, [actions, index, pollID, isValidVote, textAnswer, singleOptionAnswer, multipleOptionAnswer]);
96
+
97
+ const handleSkip = useCallback(async () => {
98
+ await actions.interactivityCenter.addResponsesToPoll(pollID, [
99
+ {
100
+ questionIndex: index,
101
+ skipped: true,
102
+ },
103
+ ]);
104
+ moveNext();
105
+ }, [actions, index, pollID, moveNext]);
106
+
107
+ return (
108
+ <Box
109
+ css={{
110
+ backgroundColor: '$surface_bright',
111
+ borderRadius: '$1',
112
+ p: '$md',
113
+ mt: '$md',
114
+ border:
115
+ isQuiz && localPeerResponse && !localPeerResponse.skipped
116
+ ? `1px solid ${isCorrectAnswer ? '$alert_success' : '$alert_error_default'}`
117
+ : 'none',
118
+ }}
119
+ >
120
+ <Flex align="center" justify="between">
121
+ <Text variant="caption" css={{ color: '$on_surface_low', fontWeight: '$semiBold' }}>
122
+ QUESTION {index} OF {totalQuestions}: {type.toUpperCase()}
123
+ </Text>
124
+
125
+ {isTimed ? (
126
+ <Flex align="center" css={{ gap: '$4' }}>
127
+ <IconButton
128
+ disabled={!prev}
129
+ onClick={movePrev}
130
+ css={
131
+ prev
132
+ ? { color: '$on_surface_high', cursor: 'pointer' }
133
+ : {
134
+ color: '$on_surface_low',
135
+ cursor: 'not-allowed',
136
+ }
137
+ }
138
+ >
139
+ <ChevronLeftIcon height={16} width={16} />
140
+ </IconButton>
141
+ <IconButton
142
+ disabled={!next}
143
+ onClick={moveNext}
144
+ css={
145
+ next
146
+ ? { color: '$on_surface_high', cursor: 'pointer' }
147
+ : {
148
+ color: '$on_surface_low',
149
+ cursor: 'not-allowed',
150
+ }
151
+ }
152
+ >
153
+ <ChevronRightIcon height={16} width={16} />
154
+ </IconButton>
155
+ </Flex>
156
+ ) : null}
157
+ </Flex>
158
+
159
+ <Box css={{ my: '$md' }}>
160
+ <Text css={{ color: '$on_surface_high' }}>{text}</Text>
161
+ </Box>
162
+
163
+ {type === QUESTION_TYPE.SHORT_ANSWER ? (
164
+ <Input
165
+ disabled={!canRespond}
166
+ placeholder="Enter your answer"
167
+ onChange={e => setTextAnswer(e.target.value)}
168
+ css={{
169
+ w: '100%',
170
+ backgroundColor: '$surface_brighter',
171
+ mb: '$md',
172
+ border: '1px solid $border_default',
173
+ cursor: localPeerResponse ? 'not-allowed' : 'text',
174
+ }}
175
+ />
176
+ ) : null}
177
+
178
+ {type === QUESTION_TYPE.LONG_ANSWER ? (
179
+ <TextArea
180
+ disabled={!canRespond}
181
+ placeholder="Enter your answer"
182
+ onChange={e => setTextAnswer(e.target.value)}
183
+ />
184
+ ) : null}
185
+
186
+ {type === QUESTION_TYPE.SINGLE_CHOICE ? (
187
+ <SingleChoiceOptions
188
+ questionIndex={index}
189
+ isQuiz={isQuiz}
190
+ canRespond={canRespond}
191
+ response={localPeerResponse}
192
+ correctOptionIndex={answer?.option}
193
+ options={options}
194
+ setAnswer={setSingleOptionAnswer}
195
+ totalResponses={result?.totalResponses}
196
+ showVoteCount={showVoteCount}
197
+ />
198
+ ) : null}
199
+
200
+ {type === QUESTION_TYPE.MULTIPLE_CHOICE ? (
201
+ <MultipleChoiceOptions
202
+ questionIndex={index}
203
+ isQuiz={isQuiz}
204
+ canRespond={canRespond}
205
+ response={localPeerResponse}
206
+ correctOptionIndexes={answer?.options}
207
+ options={options}
208
+ selectedOptions={multipleOptionAnswer}
209
+ setSelectedOptions={setMultipleOptionAnswer}
210
+ totalResponses={result?.totalResponses}
211
+ showVoteCount={showVoteCount}
212
+ />
213
+ ) : null}
214
+
215
+ {isLive && (
216
+ <QuestionActions
217
+ isValidVote={isValidVote}
218
+ skippable={skippable}
219
+ onSkip={handleSkip}
220
+ onVote={handleVote}
221
+ response={localPeerResponse}
222
+ stringAnswerExpected={stringAnswerExpected}
223
+ />
224
+ )}
225
+ </Box>
226
+ );
227
+ };
228
+
229
+ const QuestionActions = ({ isValidVote, skippable, response, stringAnswerExpected, onVote, onSkip }) => {
230
+ return (
231
+ <Flex align="center" justify="end" css={{ gap: '$4', w: '100%' }}>
232
+ {skippable && !response ? (
233
+ <Button variant="standard" onClick={onSkip} css={{ p: '$xs $10', fontWeight: '$semiBold' }}>
234
+ Skip
235
+ </Button>
236
+ ) : null}
237
+
238
+ {response ? (
239
+ <Text css={{ fontWeight: '$semiBold', color: '$on_surface_medium' }}>
240
+ {response.skipped ? 'Skipped' : stringAnswerExpected ? 'Submitted' : 'Voted'}
241
+ </Text>
242
+ ) : (
243
+ <Button css={{ p: '$xs $10', fontWeight: '$semiBold' }} disabled={!isValidVote} onClick={onVote}>
244
+ {stringAnswerExpected ? 'Submit' : 'Vote'}
245
+ </Button>
246
+ )}
247
+ </Flex>
248
+ );
249
+ };
@@ -0,0 +1,40 @@
1
+ // @ts-check
2
+ import React from 'react';
3
+ import { QuestionCard } from './QuestionCard';
4
+
5
+ /**
6
+ *
7
+ * @param {{poll: import("@100mslive/react-sdk").HMSPoll}} param0
8
+ * @returns
9
+ */
10
+ export const StandardView = ({ poll }) => {
11
+ if (!poll?.questions) {
12
+ return null;
13
+ }
14
+ return (
15
+ <>
16
+ {poll.questions?.map((question, index) => (
17
+ <QuestionCard
18
+ pollID={poll.id}
19
+ isQuiz={poll.type === 'quiz'}
20
+ startedBy={poll.startedBy}
21
+ pollState={poll.state}
22
+ key={`${question.text}-${index}`}
23
+ index={question.index}
24
+ text={question.text}
25
+ type={question.type}
26
+ result={question.result}
27
+ totalQuestions={poll.questions?.length || 0}
28
+ options={question.options}
29
+ skippable={question.skippable}
30
+ responses={question.responses}
31
+ answer={question.answer}
32
+ setCurrentIndex={() => {
33
+ return;
34
+ }}
35
+ rolesThatCanViewResponses={poll.rolesThatCanViewResponses}
36
+ />
37
+ ))}
38
+ </>
39
+ );
40
+ };
@@ -0,0 +1,36 @@
1
+ // @ts-check
2
+ import React, { useState } from 'react';
3
+ import { QuestionCard } from './QuestionCard';
4
+
5
+ /**
6
+ *
7
+ * @param {{poll: import("@100mslive/react-sdk").HMSPoll}} param0
8
+ * @returns
9
+ */
10
+ export const TimedView = ({ poll }) => {
11
+ const [currentIndex, setCurrentIndex] = useState(0);
12
+ const activeQuestion = poll.questions?.[currentIndex];
13
+ if (!activeQuestion) {
14
+ return null;
15
+ }
16
+ return (
17
+ <QuestionCard
18
+ pollID={poll.id}
19
+ isQuiz={poll.type === 'quiz'}
20
+ startedBy={poll.startedBy}
21
+ pollState={poll.state}
22
+ index={activeQuestion.index}
23
+ text={activeQuestion.text}
24
+ type={activeQuestion.type}
25
+ result={activeQuestion?.result}
26
+ totalQuestions={poll.questions?.length || 0}
27
+ options={activeQuestion.options}
28
+ skippable={activeQuestion.skippable || false}
29
+ responses={activeQuestion.responses}
30
+ answer={activeQuestion.answer}
31
+ setCurrentIndex={setCurrentIndex}
32
+ rolesThatCanViewResponses={poll.rolesThatCanViewResponses}
33
+ isTimed
34
+ />
35
+ );
36
+ };
@@ -0,0 +1,99 @@
1
+ // @ts-check
2
+ import React from 'react';
3
+ import {
4
+ selectLocalPeerID,
5
+ selectPeerNameByID,
6
+ selectPollByID,
7
+ useHMSActions,
8
+ useHMSStore,
9
+ } from '@100mslive/react-sdk';
10
+ import { ChevronLeftIcon, CrossIcon } from '@100mslive/react-icons';
11
+ import { Box, Button, Flex, Text } from '../../../../';
12
+ import { Container } from '../../Streaming/Common';
13
+ // import { PollResultSummary } from "./PollResultSummary";
14
+ import { StandardView } from './StandardVoting';
15
+ import { TimedView } from './TimedVoting';
16
+ import { usePollViewState } from '../../AppData/useUISettings';
17
+ import { StatusIndicator } from '../common/StatusIndicator';
18
+ import { POLL_VIEWS } from '../../../common/constants';
19
+
20
+ export const Voting = ({ id, toggleVoting }) => {
21
+ const actions = useHMSActions();
22
+ const poll = useHMSStore(selectPollByID(id));
23
+ const pollCreatorName = useHMSStore(selectPeerNameByID(poll?.createdBy));
24
+ const isLocalPeerCreator = useHMSStore(selectLocalPeerID) === poll?.createdBy;
25
+ const { setPollView } = usePollViewState();
26
+
27
+ if (!poll) {
28
+ return null;
29
+ }
30
+
31
+ // Sets view - linear or vertical, toggles timer indicator
32
+ const isTimed = (poll.duration || 0) > 0;
33
+ const isLive = poll.state === 'started';
34
+
35
+ return (
36
+ <Container rounded>
37
+ <Flex
38
+ align="center"
39
+ css={{
40
+ gap: '$6',
41
+ py: '$6',
42
+ px: '$10',
43
+ my: '$4',
44
+ w: '100%',
45
+ color: '$on_surface_high',
46
+ borderBottom: '1px solid $border_default',
47
+ }}
48
+ >
49
+ <Flex
50
+ onClick={() => setPollView(POLL_VIEWS.CREATE_POLL_QUIZ)}
51
+ css={{ cursor: 'pointer', c: '$on_surface_medium', '&:hover': { color: '$on_surface_high' } }}
52
+ >
53
+ <ChevronLeftIcon />
54
+ </Flex>
55
+ <Text variant="h6">{poll?.type?.toUpperCase()}</Text>
56
+ <StatusIndicator isLive={isLive} shouldShowTimer={isLive && isTimed} />
57
+ <Box
58
+ css={{
59
+ marginLeft: 'auto',
60
+ cursor: 'pointer',
61
+ '&:hover': { opacity: '0.8' },
62
+ }}
63
+ >
64
+ <CrossIcon onClick={toggleVoting} />
65
+ </Box>
66
+ </Flex>
67
+
68
+ <Flex direction="column" css={{ p: '$8 $10' }}>
69
+ <Flex align="center">
70
+ <Box css={{ flex: 'auto' }}>
71
+ <Text css={{ color: '$on_surface_medium', fontWeight: '$semiBold' }}>
72
+ {pollCreatorName || 'Participant'} started a {poll.type}
73
+ </Text>
74
+ </Box>
75
+ {poll.state === 'started' && isLocalPeerCreator && (
76
+ <Box css={{ flex: 'initial' }}>
77
+ <Button
78
+ variant="danger"
79
+ css={{ fontSize: '$sm', fontWeight: '$semiBold', p: '$3 $6' }}
80
+ onClick={() => actions.interactivityCenter.stopPoll(id)}
81
+ >
82
+ End {poll.type}
83
+ </Button>
84
+ </Box>
85
+ )}
86
+ </Flex>
87
+ {/* {poll.state === "stopped" && (
88
+ <PollResultSummary
89
+ pollResult={poll.result}
90
+ questions={poll.questions}
91
+ isQuiz={poll.type === "quiz"}
92
+ isAdmin={isLocalPeerCreator}
93
+ />
94
+ )} */}
95
+ {isTimed ? <TimedView poll={poll} /> : <StandardView poll={poll} />}
96
+ </Flex>
97
+ </Container>
98
+ );
99
+ };
@@ -0,0 +1,101 @@
1
+ // @ts-check
2
+ import React from 'react';
3
+ import { CheckIcon } from '@100mslive/react-icons';
4
+ import { Checkbox, Flex, Label, Text } from '../../../../';
5
+ import { OptionInputWithDelete } from './OptionInputWithDelete';
6
+ import { VoteCount } from './VoteCount';
7
+ import { VoteProgress } from './VoteProgress';
8
+
9
+ export const MultipleChoiceOptions = ({
10
+ questionIndex,
11
+ isQuiz,
12
+ options,
13
+ correctOptionIndexes,
14
+ canRespond,
15
+ response,
16
+ totalResponses,
17
+ selectedOptions,
18
+ setSelectedOptions,
19
+ showVoteCount,
20
+ }) => {
21
+ const handleCheckedChange = (checked, index) => {
22
+ const newSelected = new Set(selectedOptions);
23
+ if (checked) {
24
+ newSelected.add(index);
25
+ } else {
26
+ newSelected.delete(index);
27
+ }
28
+ setSelectedOptions(newSelected);
29
+ };
30
+
31
+ return (
32
+ <Flex direction="column" css={{ gap: '$md', w: '100%', mb: '$md' }}>
33
+ {options.map(option => {
34
+ const isCorrectAnswer = isQuiz && correctOptionIndexes?.includes(option.index);
35
+
36
+ return (
37
+ <Flex align="center" key={`${questionIndex}-${option.index}`} css={{ w: '100%', gap: '$9' }}>
38
+ <Checkbox.Root
39
+ id={`${questionIndex}-${option.index}`}
40
+ disabled={!canRespond}
41
+ checked={response?.options?.includes(option.index)}
42
+ onCheckedChange={checked => handleCheckedChange(checked, option.index)}
43
+ css={{
44
+ cursor: canRespond ? 'pointer' : 'not-allowed',
45
+ }}
46
+ >
47
+ <Checkbox.Indicator>
48
+ <CheckIcon width={16} height={16} />
49
+ </Checkbox.Indicator>
50
+ </Checkbox.Root>
51
+
52
+ <Flex direction="column" css={{ flexGrow: '1' }}>
53
+ <Flex css={{ w: '100%' }}>
54
+ <Text css={{ display: 'flex', flexGrow: '1' }}>
55
+ <Label htmlFor={`${questionIndex}-${option.index}`}>{option.text}</Label>
56
+ </Text>
57
+ {showVoteCount && (
58
+ <VoteCount isQuiz={isQuiz} isCorrectAnswer={isCorrectAnswer} voteCount={option.voteCount} />
59
+ )}
60
+ </Flex>
61
+ {showVoteCount && <VoteProgress option={option} totalResponses={totalResponses} />}
62
+ </Flex>
63
+ </Flex>
64
+ );
65
+ })}
66
+ </Flex>
67
+ );
68
+ };
69
+
70
+ export const MultipleChoiceOptionInputs = ({ isQuiz, options, selectAnswer, handleOptionTextChange, removeOption }) => {
71
+ return (
72
+ <Flex direction="column" css={{ gap: '$md', w: '100%', mb: '$md' }}>
73
+ {options.map((option, index) => {
74
+ return (
75
+ <Flex align="center" key={index} css={{ w: '100%', gap: '$5' }}>
76
+ {isQuiz && (
77
+ <Checkbox.Root
78
+ onCheckedChange={checked => selectAnswer(checked, index)}
79
+ checked={option.isCorrectAnswer}
80
+ css={{
81
+ cursor: 'pointer',
82
+ width: '$9',
83
+ }}
84
+ >
85
+ <Checkbox.Indicator>
86
+ <CheckIcon width={16} height={16} />
87
+ </Checkbox.Indicator>
88
+ </Checkbox.Root>
89
+ )}
90
+ <OptionInputWithDelete
91
+ index={index}
92
+ option={option}
93
+ handleOptionTextChange={handleOptionTextChange}
94
+ removeOption={removeOption}
95
+ />
96
+ </Flex>
97
+ );
98
+ })}
99
+ </Flex>
100
+ );
101
+ };
@@ -0,0 +1,25 @@
1
+ import React from 'react';
2
+ import { TrashIcon } from '@100mslive/react-icons';
3
+ import { Input } from '../../../../Input';
4
+ import IconButton from '../../../IconButton';
5
+
6
+ export const OptionInputWithDelete = ({ index, option, handleOptionTextChange, removeOption }) => {
7
+ return (
8
+ <>
9
+ <Input
10
+ placeholder={`Option ${index + 1}`}
11
+ css={{
12
+ w: '100%',
13
+ backgroundColor: '$surface_default',
14
+ border: '1px solid $border_bright',
15
+ }}
16
+ value={option?.text || ''}
17
+ key={index}
18
+ onChange={event => handleOptionTextChange(index, event.target.value)}
19
+ />
20
+ <IconButton onClick={() => removeOption(index)} css={{ bg: '$transparent', border: 'none' }}>
21
+ <TrashIcon />
22
+ </IconButton>
23
+ </>
24
+ );
25
+ };
@@ -0,0 +1,125 @@
1
+ // @ts-check
2
+ import React from 'react';
3
+ import { Flex, Label, RadioGroup, Text } from '../../../../';
4
+ import { OptionInputWithDelete } from './OptionInputWithDelete';
5
+ import { VoteCount } from './VoteCount';
6
+ import { VoteProgress } from './VoteProgress';
7
+
8
+ export const SingleChoiceOptions = ({
9
+ questionIndex,
10
+ isQuiz,
11
+ options,
12
+ response,
13
+ canRespond,
14
+ correctOptionIndex,
15
+ setAnswer,
16
+ totalResponses,
17
+ showVoteCount,
18
+ }) => {
19
+ return (
20
+ <RadioGroup.Root value={response?.option} onValueChange={value => setAnswer(value)}>
21
+ <Flex direction="column" css={{ gap: '$md', w: '100%', mb: '$md' }}>
22
+ {options.map(option => {
23
+ const isCorrectAnswer = isQuiz && option.index === correctOptionIndex;
24
+
25
+ return (
26
+ <Flex align="center" key={`${questionIndex}-${option.index}`} css={{ w: '100%', gap: '$5' }}>
27
+ <RadioGroup.Item
28
+ css={{
29
+ background: 'none',
30
+ h: '$9',
31
+ w: '$9',
32
+ border: '2px solid',
33
+ borderColor: '$on_surface_high',
34
+ display: 'flex',
35
+ justifyContent: 'center',
36
+ alignItems: 'center',
37
+ cursor: canRespond ? 'pointer' : 'not-allowed',
38
+ '&[data-state="checked"]': {
39
+ borderColor: '$primary_bright',
40
+ borderWidth: '2px',
41
+ },
42
+ }}
43
+ disabled={!canRespond}
44
+ value={option.index}
45
+ id={`${questionIndex}-${option.index}`}
46
+ >
47
+ <RadioGroup.Indicator
48
+ css={{
49
+ h: '80%',
50
+ w: '80%',
51
+ background: '$primary_bright',
52
+ borderRadius: '$round',
53
+ }}
54
+ />
55
+ </RadioGroup.Item>
56
+
57
+ <Flex direction="column" css={{ flexGrow: '1' }}>
58
+ <Flex css={{ w: '100%' }}>
59
+ <Text css={{ display: 'flex', flexGrow: '1' }}>
60
+ <Label htmlFor={`${questionIndex}-${option.index}`}>{option.text}</Label>
61
+ </Text>
62
+ {showVoteCount && (
63
+ <VoteCount isQuiz={isQuiz} isCorrectAnswer={isCorrectAnswer} voteCount={option.voteCount} />
64
+ )}
65
+ </Flex>
66
+ {showVoteCount && <VoteProgress option={option} totalResponses={totalResponses} />}
67
+ </Flex>
68
+ </Flex>
69
+ );
70
+ })}
71
+ </Flex>
72
+ </RadioGroup.Root>
73
+ );
74
+ };
75
+
76
+ export const SingleChoiceOptionInputs = ({ isQuiz, options, selectAnswer, handleOptionTextChange, removeOption }) => {
77
+ const correctOptionIndex = options.findIndex(option => option.isCorrectAnswer);
78
+
79
+ return (
80
+ <RadioGroup.Root value={correctOptionIndex} onValueChange={selectAnswer}>
81
+ <Flex direction="column" css={{ gap: '$md', w: '100%', mb: '$md' }}>
82
+ {options.map((option, index) => {
83
+ return (
84
+ <Flex align="center" key={`option-${index}`} css={{ w: '100%', gap: '$5' }}>
85
+ {isQuiz && (
86
+ <RadioGroup.Item
87
+ css={{
88
+ background: 'none',
89
+ w: '$9',
90
+ border: '2px solid',
91
+ borderColor: '$on_surface_high',
92
+ display: 'flex',
93
+ justifyContent: 'center',
94
+ alignItems: 'center',
95
+ cursor: 'pointer',
96
+ '&[data-state="checked"]': {
97
+ borderColor: '$primary_bright',
98
+ borderWidth: '2px',
99
+ },
100
+ }}
101
+ value={index}
102
+ >
103
+ <RadioGroup.Indicator
104
+ css={{
105
+ h: '80%',
106
+ w: '80%',
107
+ background: '$primary_bright',
108
+ borderRadius: '$round',
109
+ }}
110
+ />
111
+ </RadioGroup.Item>
112
+ )}
113
+ <OptionInputWithDelete
114
+ index={index}
115
+ option={option}
116
+ handleOptionTextChange={handleOptionTextChange}
117
+ removeOption={removeOption}
118
+ />
119
+ </Flex>
120
+ );
121
+ })}
122
+ </Flex>
123
+ </RadioGroup.Root>
124
+ );
125
+ };