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

Sign up to get free protection for your applications and to get access to all the features.
Files changed (142) hide show
  1. package/dist/{HLSView-IQRPLYNH.js → HLSView-U53QN3AC.js} +3 -3
  2. package/dist/Modal/Dialog.d.ts +402 -1706
  3. package/dist/Prebuilt/App.d.ts +6 -0
  4. package/dist/Prebuilt/AppContext.d.ts +2 -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/PaginatedParticipants.d.ts +5 -0
  8. package/dist/Prebuilt/components/Footer/PollsToggle.d.ts +2 -0
  9. package/dist/Prebuilt/components/Footer/RoleAccordion.d.ts +10 -3
  10. package/dist/Prebuilt/components/LeaveScreen.d.ts +2 -0
  11. package/dist/Prebuilt/components/MwebLandscapePrompt.d.ts +2 -0
  12. package/dist/Prebuilt/components/Notifications/AutoplayBlockedModal.d.ts +2 -0
  13. package/dist/Prebuilt/components/Notifications/HLSFailureModal.d.ts +2 -0
  14. package/dist/Prebuilt/components/Notifications/InitErrorModal.d.ts +2 -0
  15. package/dist/Prebuilt/components/Notifications/Notifications.d.ts +2 -0
  16. package/dist/Prebuilt/components/Notifications/PeerNotifications.d.ts +1 -0
  17. package/dist/Prebuilt/components/Notifications/PermissionErrorModal.d.ts +2 -0
  18. package/dist/Prebuilt/components/Notifications/ReconnectNotifications.d.ts +2 -0
  19. package/dist/Prebuilt/components/Notifications/TrackBulkUnmuteModal.d.ts +2 -0
  20. package/dist/Prebuilt/components/Notifications/TrackNotifications.d.ts +1 -0
  21. package/dist/Prebuilt/components/Notifications/TrackUnmuteModal.d.ts +2 -0
  22. package/dist/Prebuilt/components/Polls/Polls.d.ts +2 -0
  23. package/dist/Prebuilt/components/Preview/PreviewJoin.d.ts +1 -2
  24. package/dist/Prebuilt/components/Preview/PreviewScreen.d.ts +2 -0
  25. package/dist/Prebuilt/components/hooks/useRedirectToLeave.d.ts +1 -1
  26. package/dist/{VirtualBackground-GP4ATXD3.js → VirtualBackground-PMLQPJB6.js} +3 -5
  27. package/dist/{VirtualBackground-GP4ATXD3.js.map → VirtualBackground-PMLQPJB6.js.map} +1 -1
  28. package/dist/chunk-ANQRGVIX.js +14441 -0
  29. package/dist/chunk-ANQRGVIX.js.map +7 -0
  30. package/dist/{chunk-Z3O2WGWV.js → chunk-XQ2NRKIW.js} +66 -3
  31. package/dist/chunk-XQ2NRKIW.js.map +7 -0
  32. package/dist/context/DialogContext.d.ts +6 -0
  33. package/dist/hooks/useDialogContainerSelector.d.ts +1 -0
  34. package/dist/index.cjs.js +10956 -9818
  35. package/dist/index.cjs.js.map +4 -4
  36. package/dist/index.d.ts +1 -0
  37. package/dist/index.js +6 -2
  38. package/dist/meta.cjs.json +4076 -3201
  39. package/dist/meta.esbuild.json +4391 -3623
  40. package/dist/utils/animations.d.ts +11 -0
  41. package/package.json +6 -7
  42. package/src/AudioLevel/AudioLevel.tsx +1 -1
  43. package/src/Modal/Dialog.tsx +31 -3
  44. package/src/Prebuilt/App.tsx +49 -97
  45. package/src/Prebuilt/AppContext.tsx +6 -0
  46. package/src/Prebuilt/AppStateContext.tsx +71 -0
  47. package/src/Prebuilt/common/constants.js +36 -1
  48. package/src/Prebuilt/common/utils.js +47 -0
  49. package/src/Prebuilt/components/AppData/AppData.jsx +6 -1
  50. package/src/Prebuilt/components/AppData/useSidepane.js +23 -1
  51. package/src/Prebuilt/components/AppData/useUISettings.js +49 -5
  52. package/src/Prebuilt/components/Chip.tsx +6 -2
  53. package/src/Prebuilt/components/{conference.jsx → ConferenceScreen.tsx} +34 -46
  54. package/src/Prebuilt/components/Footer/Footer.tsx +5 -0
  55. package/src/Prebuilt/components/Footer/PaginatedParticipants.tsx +125 -0
  56. package/src/Prebuilt/components/Footer/ParticipantList.jsx +55 -24
  57. package/src/Prebuilt/components/Footer/PollsToggle.tsx +22 -0
  58. package/src/Prebuilt/components/Footer/RoleAccordion.tsx +87 -85
  59. package/src/Prebuilt/components/Footer/RoleOptions.tsx +1 -1
  60. package/src/Prebuilt/components/Header/StreamActions.tsx +5 -3
  61. package/src/Prebuilt/components/Leave/DesktopLeaveRoom.tsx +4 -5
  62. package/src/Prebuilt/components/Leave/LeaveRoom.tsx +0 -4
  63. package/src/Prebuilt/components/{PostLeave.jsx → LeaveScreen.tsx} +6 -13
  64. package/src/Prebuilt/components/MoreSettings/ChangeNameModal.jsx +2 -3
  65. package/src/Prebuilt/components/MoreSettings/EmbedUrl.jsx +2 -3
  66. package/src/Prebuilt/components/MoreSettings/SplitComponents/MwebOptions.tsx +18 -1
  67. package/src/Prebuilt/components/{MwebLandscapePrompt.jsx → MwebLandscapePrompt.tsx} +10 -11
  68. package/src/Prebuilt/components/Notifications/{AutoplayBlockedModal.jsx → AutoplayBlockedModal.tsx} +2 -1
  69. package/src/Prebuilt/components/Notifications/{HLSFailureModal.jsx → HLSFailureModal.tsx} +10 -8
  70. package/src/Prebuilt/components/Notifications/{InitErrorModal.jsx → InitErrorModal.tsx} +5 -2
  71. package/src/Prebuilt/components/Notifications/{Notifications.jsx → Notifications.tsx} +41 -27
  72. package/src/Prebuilt/components/Notifications/{PeerNotifications.jsx → PeerNotifications.tsx} +3 -0
  73. package/src/Prebuilt/components/Notifications/{PermissionErrorModal.jsx → PermissionErrorModal.tsx} +6 -4
  74. package/src/Prebuilt/components/Notifications/{ReconnectNotifications.jsx → ReconnectNotifications.tsx} +11 -6
  75. package/src/Prebuilt/components/Notifications/{TrackBulkUnmuteModal.jsx → TrackBulkUnmuteModal.tsx} +9 -3
  76. package/src/Prebuilt/components/Notifications/{TrackUnmuteModal.jsx → TrackUnmuteModal.tsx} +9 -3
  77. package/src/Prebuilt/components/Notifications/index.tsx +1 -0
  78. package/src/Prebuilt/components/Polls/CreatePollQuiz/PollsQuizMenu.jsx +229 -0
  79. package/src/Prebuilt/components/Polls/CreatePollQuiz/Timer.jsx +71 -0
  80. package/src/Prebuilt/components/Polls/CreateQuestions/CreateQuestions.jsx +132 -0
  81. package/src/Prebuilt/components/Polls/CreateQuestions/DeleteQuestionModal.jsx +66 -0
  82. package/src/Prebuilt/components/Polls/CreateQuestions/QuestionForm.jsx +251 -0
  83. package/src/Prebuilt/components/Polls/CreateQuestions/SavedQuestion.jsx +57 -0
  84. package/src/Prebuilt/components/Polls/Polls.tsx +28 -0
  85. package/src/Prebuilt/components/Polls/Voting/PollResultSummary.jsx +125 -0
  86. package/src/Prebuilt/components/Polls/Voting/QuestionCard.jsx +249 -0
  87. package/src/Prebuilt/components/Polls/Voting/StandardVoting.jsx +40 -0
  88. package/src/Prebuilt/components/Polls/Voting/TimedVoting.jsx +36 -0
  89. package/src/Prebuilt/components/Polls/Voting/Voting.jsx +99 -0
  90. package/src/Prebuilt/components/Polls/common/MultipleChoiceOptions.jsx +101 -0
  91. package/src/Prebuilt/components/Polls/common/OptionInputWithDelete.jsx +25 -0
  92. package/src/Prebuilt/components/Polls/common/SingleChoiceOptions.jsx +125 -0
  93. package/src/Prebuilt/components/Polls/common/StatusIndicator.jsx +47 -0
  94. package/src/Prebuilt/components/Polls/common/VoteCount.jsx +28 -0
  95. package/src/Prebuilt/components/Polls/common/VoteProgress.jsx +17 -0
  96. package/src/Prebuilt/components/Polls/common/VoterList.jsx +22 -0
  97. package/src/Prebuilt/components/Polls/common/Votes.jsx +72 -0
  98. package/src/Prebuilt/components/Preview/PreviewForm.tsx +3 -2
  99. package/src/Prebuilt/components/Preview/PreviewJoin.tsx +29 -21
  100. package/src/Prebuilt/components/Preview/{PreviewContainer.tsx → PreviewScreen.tsx} +2 -19
  101. package/src/Prebuilt/components/RaiseHand.jsx +1 -1
  102. package/src/Prebuilt/components/RoleChangeModal.jsx +2 -3
  103. package/src/Prebuilt/components/RoleChangeRequest/RequestPrompt.tsx +2 -3
  104. package/src/Prebuilt/components/Settings/SettingsModal.jsx +2 -3
  105. package/src/Prebuilt/components/Settings/StartRecording.jsx +15 -4
  106. package/src/Prebuilt/components/SidePaneTabs.tsx +32 -6
  107. package/src/Prebuilt/components/StatsForNerds.jsx +2 -3
  108. package/src/Prebuilt/components/Streaming/Common.jsx +31 -21
  109. package/src/Prebuilt/components/VideoLayouts/ScreenshareLayout.tsx +8 -9
  110. package/src/Prebuilt/components/VideoTile.jsx +28 -39
  111. package/src/Prebuilt/components/hooks/useAutoStartStreaming.tsx +3 -3
  112. package/src/Prebuilt/components/hooks/useDropdownSelection.jsx +1 -1
  113. package/src/Prebuilt/components/hooks/useRedirectToLeave.tsx +9 -17
  114. package/src/Prebuilt/components/pdfAnnotator/pdfFileOptions.jsx +2 -3
  115. package/src/Prebuilt/components/pdfAnnotator/submitPdf.jsx +1 -1
  116. package/src/Prebuilt/components/pdfAnnotator/uploadedFile.jsx +2 -3
  117. package/src/Prebuilt/layouts/EmbedView.jsx +47 -60
  118. package/src/Prebuilt/layouts/PDFView.jsx +49 -99
  119. package/src/Prebuilt/layouts/SidePane.tsx +9 -4
  120. package/src/Prebuilt/layouts/VideoStreamingSection.tsx +2 -2
  121. package/src/Prebuilt/primitives/DialogContent.jsx +4 -5
  122. package/src/context/DialogContext.tsx +13 -0
  123. package/src/hooks/useDialogContainerSelector.tsx +7 -0
  124. package/src/index.ts +1 -0
  125. package/src/utils/animations.ts +6 -0
  126. package/dist/Prebuilt/components/PrebuiltDialogPortal.d.ts +0 -4
  127. package/dist/Prebuilt/components/Preview/PreviewContainer.d.ts +0 -3
  128. package/dist/chunk-2H5NIZB7.js +0 -70
  129. package/dist/chunk-2H5NIZB7.js.map +0 -7
  130. package/dist/chunk-GLYGPYNS.js +0 -7125
  131. package/dist/chunk-GLYGPYNS.js.map +0 -7
  132. package/dist/chunk-Z3O2WGWV.js.map +0 -7
  133. package/dist/conference-JD35TNH4.js +0 -6503
  134. package/dist/conference-JD35TNH4.js.map +0 -7
  135. package/src/Prebuilt/components/GoLiveButton.jsx +0 -42
  136. package/src/Prebuilt/components/PrebuiltDialogPortal.tsx +0 -6
  137. package/src/Prebuilt/components/Streaming/HLSStreaming.jsx +0 -220
  138. package/src/Prebuilt/components/Streaming/RTMPStreaming.jsx +0 -334
  139. package/src/Prebuilt/components/Streaming/StreamingLanding.jsx +0 -76
  140. /package/dist/{HLSView-IQRPLYNH.js.map → HLSView-U53QN3AC.js.map} +0 -0
  141. /package/{src/Prebuilt/components/Notifications/index.jsx → dist/Prebuilt/components/Notifications/index.d.ts} +0 -0
  142. /package/src/Prebuilt/components/Notifications/{TrackNotifications.jsx → TrackNotifications.tsx} +0 -0
@@ -0,0 +1,132 @@
1
+ // @ts-check
2
+ import React, { useMemo, useState } from 'react';
3
+ import { v4 as uuid } from 'uuid';
4
+ import { selectPollByID, useHMSActions, useHMSStore } from '@100mslive/react-sdk';
5
+ import { AddCircleIcon } from '@100mslive/react-icons';
6
+ import { Button, Flex, Text } from '../../../../';
7
+ import { Container, ContentHeader } from '../../Streaming/Common';
8
+ import { isValidQuestion, QuestionForm } from './QuestionForm';
9
+ import { SavedQuestion } from './SavedQuestion';
10
+ import { usePollViewToggle } from '../../AppData/useSidepane';
11
+ import { usePollViewState } from '../../AppData/useUISettings';
12
+ import { POLL_VIEWS } from '../../../common/constants';
13
+
14
+ export function CreateQuestions() {
15
+ const [questions, setQuestions] = useState([{ draftID: uuid() }]);
16
+ const actions = useHMSActions();
17
+ const togglePollView = usePollViewToggle();
18
+ const { pollInView: id, setPollView } = usePollViewState();
19
+ const interaction = useHMSStore(selectPollByID(id));
20
+
21
+ const isValidPoll = useMemo(
22
+ () => questions.length > 0 && questions.every(question => isValidQuestion(question)),
23
+ [questions],
24
+ );
25
+
26
+ const launchPoll = async () => {
27
+ const validQuestions = questions
28
+ .filter(question => isValidQuestion(question))
29
+ .map(question => ({
30
+ text: question.text,
31
+ type: question.type,
32
+ options: question.options,
33
+ skippable: question.skippable,
34
+ }));
35
+ await actions.interactivityCenter.addQuestionsToPoll(id, validQuestions);
36
+ await actions.interactivityCenter.startPoll(id);
37
+ setPollView(POLL_VIEWS.VOTE);
38
+ };
39
+ const headingTitle = interaction?.type
40
+ ? interaction?.type?.[0]?.toUpperCase() + interaction?.type?.slice(1)
41
+ : 'Polls and Quizzes';
42
+ const isQuiz = interaction?.type === 'quiz';
43
+ return (
44
+ <Container rounded>
45
+ <ContentHeader
46
+ content={headingTitle}
47
+ onClose={togglePollView}
48
+ onBack={() => setPollView(POLL_VIEWS.CREATE_POLL_QUIZ)}
49
+ />
50
+ <Flex direction="column" css={{ p: '$10', overflowY: 'auto' }}>
51
+ <Flex direction="column">
52
+ {questions.map((question, index) => (
53
+ <QuestionCard
54
+ key={question.draftID}
55
+ question={question}
56
+ index={index}
57
+ length={questions.length}
58
+ onSave={questionParams => {
59
+ setQuestions(questions => [
60
+ ...questions.slice(0, index),
61
+ questionParams,
62
+ ...questions.slice(index + 1),
63
+ ]);
64
+ }}
65
+ isQuiz={isQuiz}
66
+ removeQuestion={questionID =>
67
+ setQuestions(prev => {
68
+ return prev.filter(questionFromSet => questionID !== questionFromSet.draftID);
69
+ })
70
+ }
71
+ convertToDraft={questionID =>
72
+ setQuestions(prev => {
73
+ const copyOfQuestions = [...prev];
74
+ copyOfQuestions.forEach(question => {
75
+ if (questionID && question.draftID === questionID) {
76
+ question.saved = false;
77
+ }
78
+ });
79
+ return copyOfQuestions;
80
+ })
81
+ }
82
+ />
83
+ ))}
84
+ </Flex>
85
+ <Flex
86
+ css={{
87
+ c: '$on_surface_low',
88
+ my: '$sm',
89
+ cursor: 'pointer',
90
+ '&:hover': { c: '$on_surface_medium' },
91
+ }}
92
+ onClick={() => setQuestions([...questions, { draftID: uuid() }])}
93
+ >
94
+ <AddCircleIcon />
95
+ <Text variant="body1" css={{ ml: '$md', c: '$inherit' }}>
96
+ Add another question
97
+ </Text>
98
+ </Flex>
99
+ <Flex css={{ w: '100%' }} justify="end">
100
+ <Button disabled={!isValidPoll} onClick={launchPoll}>
101
+ Launch {interaction.type}
102
+ </Button>
103
+ </Flex>
104
+ </Flex>
105
+ </Container>
106
+ );
107
+ }
108
+
109
+ const QuestionCard = ({ question, onSave, index, length, removeQuestion, isQuiz, convertToDraft }) => {
110
+ return (
111
+ <Flex direction="column" css={{ p: '$md', bg: '$surface_default', r: '$1', mb: '$sm' }}>
112
+ {question.saved ? (
113
+ <SavedQuestion
114
+ question={question}
115
+ index={index}
116
+ length={length}
117
+ convertToDraft={convertToDraft}
118
+ removeQuestion={removeQuestion}
119
+ />
120
+ ) : (
121
+ <QuestionForm
122
+ question={question}
123
+ removeQuestion={() => removeQuestion(question.draftID)}
124
+ onSave={params => onSave(params)}
125
+ index={index}
126
+ length={length}
127
+ isQuiz={isQuiz}
128
+ />
129
+ )}
130
+ </Flex>
131
+ );
132
+ };
@@ -0,0 +1,66 @@
1
+ import React from 'react';
2
+ import { AlertTriangleIcon, CrossIcon } from '@100mslive/react-icons';
3
+ import { Button } from '../../../../Button';
4
+ import { Box, Flex } from '../../../../Layout';
5
+ import { Dialog } from '../../../../Modal';
6
+ import { Text } from '../../../../Text';
7
+
8
+ export const DeleteQuestionModal = ({ open, setOpen, removeQuestion }) => {
9
+ return (
10
+ <Dialog.Root open={open}>
11
+ <Dialog.Overlay />
12
+ <Dialog.Portal>
13
+ <Dialog.Content css={{ p: '$10' }}>
14
+ <Box>
15
+ <Flex
16
+ css={{
17
+ color: '$alert_error_default',
18
+ display: 'flex',
19
+ alignItems: 'center',
20
+ }}
21
+ >
22
+ <AlertTriangleIcon style={{ marginRight: '0.5rem' }} />
23
+ <Text variant="lg" css={{ color: 'inherit', fontWeight: '$semiBold' }}>
24
+ Delete Question?
25
+ </Text>
26
+
27
+ <Box
28
+ css={{
29
+ ml: 'auto',
30
+ color: '$on_surface_medium',
31
+ '&:hover': { color: '$on_surface_high', cursor: 'pointer' },
32
+ }}
33
+ onClick={() => setOpen(false)}
34
+ >
35
+ <CrossIcon />
36
+ </Box>
37
+ </Flex>
38
+ <Text variant="sm" css={{ color: '$on_surface_medium', mb: '$8', mt: '$4' }}>
39
+ The question will be deleted. You can't undo this action.
40
+ </Text>
41
+ <Flex css={{ w: '100%', mt: '$12', gap: '$md' }}>
42
+ <Button
43
+ variant="standard"
44
+ outlined
45
+ onClick={() => setOpen(false)}
46
+ css={{ w: '100%', fontSize: '$md', fontWeight: '$semiBold' }}
47
+ >
48
+ Cancel
49
+ </Button>
50
+ <Button
51
+ css={{ w: '100%', fontSize: '$md', fontWeight: '$semiBold' }}
52
+ variant="danger"
53
+ onClick={() => {
54
+ removeQuestion();
55
+ setOpen(false);
56
+ }}
57
+ >
58
+ Delete
59
+ </Button>
60
+ </Flex>
61
+ </Box>
62
+ </Dialog.Content>
63
+ </Dialog.Portal>
64
+ </Dialog.Root>
65
+ );
66
+ };
@@ -0,0 +1,251 @@
1
+ // @ts-check
2
+ import React, { useCallback, useRef, useState } from 'react';
3
+ import { AddCircleIcon, TrashIcon } from '@100mslive/react-icons';
4
+ import { Box, Button, Dropdown, Flex, Input, Switch, Text, Tooltip } from '../../../../';
5
+ import { DialogDropdownTrigger } from '../../../primitives/DropdownTrigger';
6
+ import { DeleteQuestionModal } from './DeleteQuestionModal';
7
+ import { useDropdownSelection } from '../../hooks/useDropdownSelection';
8
+ import { isValidTextInput } from '../../../common/utils';
9
+ import { MultipleChoiceOptionInputs } from '../common/MultipleChoiceOptions';
10
+ import { SingleChoiceOptionInputs } from '../common/SingleChoiceOptions';
11
+ import { QUESTION_TYPE, QUESTION_TYPE_TITLE } from '../../../common/constants';
12
+
13
+ export const QuestionForm = ({ question, index, length, onSave, removeQuestion, isQuiz }) => {
14
+ const ref = useRef(null);
15
+ const selectionBg = useDropdownSelection();
16
+ const [openDelete, setOpenDelete] = useState(false);
17
+ const [open, setOpen] = useState(false);
18
+ const [type, setType] = useState(question.type || QUESTION_TYPE.SINGLE_CHOICE);
19
+ const [text, setText] = useState(question.text);
20
+ const [options, setOptions] = useState(
21
+ question?.options || [
22
+ { text: '', isCorrectAnswer: false },
23
+ { text: '', isCorrectAnswer: false },
24
+ ],
25
+ );
26
+ const [skippable, setSkippable] = useState(false);
27
+ const isValid = isValidQuestion({
28
+ text,
29
+ type,
30
+ options,
31
+ isQuiz,
32
+ });
33
+
34
+ const handleOptionTextChange = useCallback(
35
+ (index, text) => {
36
+ setOptions(options => [...options.slice(0, index), { ...options[index], text }, ...options.slice(index + 1)]);
37
+ },
38
+ [setOptions],
39
+ );
40
+
41
+ const removeOption = useCallback(
42
+ index =>
43
+ setOptions(options => {
44
+ const newOptions = [...options];
45
+ newOptions.splice(index, 1);
46
+ return newOptions;
47
+ }),
48
+ [setOptions],
49
+ );
50
+
51
+ const selectSingleChoiceAnswer = useCallback(
52
+ answerIndex => {
53
+ if (!isQuiz) {
54
+ return;
55
+ }
56
+ setOptions(options =>
57
+ options.map((option, index) => ({
58
+ ...option,
59
+ isCorrectAnswer: index === answerIndex,
60
+ })),
61
+ );
62
+ },
63
+ [setOptions, isQuiz],
64
+ );
65
+
66
+ const selectMultipleChoiceAnswer = useCallback(
67
+ (checked, index) => {
68
+ if (!isQuiz) {
69
+ return;
70
+ }
71
+ setOptions(options => [
72
+ ...options.slice(0, index),
73
+ { ...options[index], isCorrectAnswer: checked },
74
+ ...options.slice(index + 1),
75
+ ]);
76
+ },
77
+ [setOptions, isQuiz],
78
+ );
79
+
80
+ return (
81
+ <>
82
+ <Text variant="overline" css={{ c: '$on_surface_low', textTransform: 'uppercase' }}>
83
+ Question {index + 1} of {length}
84
+ </Text>
85
+ <Text variant="body2" css={{ mt: '$4', mb: '$md' }}>
86
+ Question Type
87
+ </Text>
88
+ <Dropdown.Root open={open} onOpenChange={setOpen}>
89
+ <DialogDropdownTrigger
90
+ ref={ref}
91
+ title={QUESTION_TYPE_TITLE[type]}
92
+ css={{
93
+ backgroundColor: '$surface_bright',
94
+ border: '1px solid $border_bright',
95
+ }}
96
+ open={open}
97
+ />
98
+ <Dropdown.Portal>
99
+ <Dropdown.Content align="start" sideOffset={8} css={{ w: ref.current?.clientWidth, zIndex: 1000 }}>
100
+ {Object.keys(QUESTION_TYPE_TITLE).map(value => {
101
+ return (
102
+ <Dropdown.Item
103
+ key={value}
104
+ onSelect={() => setType(value)}
105
+ css={{
106
+ px: '$9',
107
+ bg: type === value ? selectionBg : undefined,
108
+ }}
109
+ >
110
+ {QUESTION_TYPE_TITLE[value]}
111
+ </Dropdown.Item>
112
+ );
113
+ })}
114
+ </Dropdown.Content>
115
+ </Dropdown.Portal>
116
+ </Dropdown.Root>
117
+ <Input
118
+ placeholder="Ask a question"
119
+ css={{
120
+ mt: '$md',
121
+ backgroundColor: '$surface_bright',
122
+ border: '1px solid $border_bright',
123
+ }}
124
+ type="text"
125
+ value={text}
126
+ onChange={event => setText(event.target.value)}
127
+ />
128
+ {type === QUESTION_TYPE.SINGLE_CHOICE || type === QUESTION_TYPE.MULTIPLE_CHOICE ? (
129
+ <>
130
+ <Text variant="body2" css={{ my: '$6', c: '$on_surface_medium' }}>
131
+ Options
132
+ </Text>
133
+
134
+ {isQuiz && (
135
+ <Text variant="xs" css={{ c: '$on_surface_medium', mb: '$md' }}>
136
+ {type === QUESTION_TYPE.SINGLE_CHOICE
137
+ ? 'Use the radio buttons to indicate the correct answer'
138
+ : 'Use the checkboxes to indicate the correct answer(s)'}
139
+ </Text>
140
+ )}
141
+
142
+ {type === QUESTION_TYPE.SINGLE_CHOICE && (
143
+ <SingleChoiceOptionInputs
144
+ isQuiz={isQuiz}
145
+ options={options}
146
+ selectAnswer={selectSingleChoiceAnswer}
147
+ handleOptionTextChange={handleOptionTextChange}
148
+ removeOption={removeOption}
149
+ />
150
+ )}
151
+
152
+ {type === QUESTION_TYPE.MULTIPLE_CHOICE && (
153
+ <MultipleChoiceOptionInputs
154
+ isQuiz={isQuiz}
155
+ options={options}
156
+ selectAnswer={selectMultipleChoiceAnswer}
157
+ handleOptionTextChange={handleOptionTextChange}
158
+ removeOption={removeOption}
159
+ />
160
+ )}
161
+
162
+ {options?.length < 20 && (
163
+ <Flex
164
+ css={{
165
+ c: '$on_surface_medium',
166
+ cursor: 'pointer',
167
+ '&:hover': { c: '$on_surface_high' },
168
+ }}
169
+ onClick={() => setOptions([...options, { text: '', isCorrectAnswer: false }])}
170
+ >
171
+ <AddCircleIcon style={{ position: 'relative', left: '-2px' }} />
172
+
173
+ <Text
174
+ variant="body1"
175
+ css={{
176
+ ml: '$4',
177
+ c: 'inherit',
178
+ }}
179
+ >
180
+ Add an option
181
+ </Text>
182
+ </Flex>
183
+ )}
184
+ {isQuiz ? (
185
+ <Flex css={{ mt: '$md', gap: '$6' }}>
186
+ <Switch defaultChecked={skippable} onCheckedChange={checked => setSkippable(checked)} />
187
+ <Text variant="sm" css={{ color: '$on_surface_medium' }}>
188
+ Not required to answer
189
+ </Text>
190
+ </Flex>
191
+ ) : null}
192
+ </>
193
+ ) : null}
194
+
195
+ <Flex justify="between" align="center" css={{ mt: '$12' }}>
196
+ <Box
197
+ css={{
198
+ color: '$on_surface_medium',
199
+ cursor: 'pointer',
200
+ '&:hover': { color: '$on_surface_high' },
201
+ }}
202
+ >
203
+ <TrashIcon onClick={() => setOpenDelete(!open)} />
204
+ </Box>
205
+ <Tooltip
206
+ disabled={isValid}
207
+ title={`Please fill all the fields ${isQuiz ? 'and mark the correct answer(s)' : ''} to continue`}
208
+ boxCss={{ maxWidth: '$40' }}
209
+ >
210
+ <Button
211
+ variant="standard"
212
+ disabled={!isValid}
213
+ onClick={() => {
214
+ onSave({
215
+ saved: true,
216
+ text,
217
+ type,
218
+ options,
219
+ skippable,
220
+ draftID: question.draftID,
221
+ });
222
+ }}
223
+ >
224
+ Save
225
+ </Button>
226
+ </Tooltip>
227
+ </Flex>
228
+
229
+ <DeleteQuestionModal open={openDelete} setOpen={setOpenDelete} removeQuestion={removeQuestion} />
230
+ </>
231
+ );
232
+ };
233
+
234
+ export const isValidQuestion = ({ text, type, options, isQuiz = false }) => {
235
+ if (!isValidTextInput(text) || !type) {
236
+ return false;
237
+ }
238
+
239
+ if (![QUESTION_TYPE.SINGLE_CHOICE, QUESTION_TYPE.MULTIPLE_CHOICE].includes(type)) {
240
+ return true;
241
+ }
242
+
243
+ const everyOptionHasText = options.every(option => option && isValidTextInput(option.text, 1));
244
+ const hasCorrectAnswer = options.some(option => option.isCorrectAnswer);
245
+
246
+ if (!isQuiz) {
247
+ return everyOptionHasText;
248
+ }
249
+
250
+ return everyOptionHasText && hasCorrectAnswer;
251
+ };
@@ -0,0 +1,57 @@
1
+ // @ts-check
2
+ import React, { useState } from 'react';
3
+ import { CheckCircleIcon, TrashIcon } from '@100mslive/react-icons';
4
+ import { Box, Button, Flex, Text } from '../../../../';
5
+ import { DeleteQuestionModal } from './DeleteQuestionModal';
6
+ import { QUESTION_TYPE_TITLE } from '../../../common/constants';
7
+
8
+ export const SavedQuestion = ({ question, index, length, convertToDraft, removeQuestion }) => {
9
+ const [openDeleteModal, setOpenDeleteModal] = useState(false);
10
+ return (
11
+ <>
12
+ <Text variant="overline" css={{ c: '$on_surface_low', textTransform: 'uppercase' }}>
13
+ Question {index + 1} of {length}: {QUESTION_TYPE_TITLE[question.type]}
14
+ </Text>
15
+ <Text variant="body2" css={{ mt: '$4', mb: '$md' }}>
16
+ {question.text}
17
+ </Text>
18
+ {question.options.map(option => (
19
+ <Flex css={{ alignItems: 'center', my: '$xs' }}>
20
+ <Text variant="body2" css={{ c: '$on_surface_medium' }}>
21
+ {option.text}
22
+ </Text>
23
+ {option.isCorrectAnswer && (
24
+ <Flex css={{ color: '$alert_success', mx: '$xs' }}>
25
+ <CheckCircleIcon height={24} width={24} />
26
+ </Flex>
27
+ )}
28
+ </Flex>
29
+ ))}
30
+ {question.skippable ? (
31
+ <Text variant="sm" css={{ color: '$on_surface_low', my: '$md' }}>
32
+ Not required to answer
33
+ </Text>
34
+ ) : null}
35
+ <Flex justify="between" css={{ w: '100%', alignItems: 'center' }}>
36
+ <Box
37
+ onClick={() => setOpenDeleteModal(true)}
38
+ css={{ color: '$on_surface_low', '&:hover': { color: '$on_surface_medium', cursor: 'pointer' } }}
39
+ >
40
+ <TrashIcon />
41
+ </Box>
42
+ <Button
43
+ variant="standard"
44
+ css={{ fontWeight: '$semiBold', p: '$4 $8' }}
45
+ onClick={() => convertToDraft(question.draftID)}
46
+ >
47
+ Edit
48
+ </Button>
49
+ </Flex>
50
+ <DeleteQuestionModal
51
+ removeQuestion={() => removeQuestion(question.draftID)}
52
+ open={openDeleteModal}
53
+ setOpen={setOpenDeleteModal}
54
+ />
55
+ </>
56
+ );
57
+ };
@@ -0,0 +1,28 @@
1
+ import React from 'react';
2
+ // @ts-ignore: No implicit Any
3
+ import { PollsQuizMenu } from './CreatePollQuiz/PollsQuizMenu';
4
+ // @ts-ignore: No implicit Any
5
+ import { CreateQuestions } from './CreateQuestions/CreateQuestions';
6
+ // @ts-ignore: No implicit Any
7
+ import { Voting } from './Voting/Voting';
8
+ // @ts-ignore: No implicit Any
9
+ import { usePollViewToggle } from '../AppData/useSidepane';
10
+ // @ts-ignore: No implicit Any
11
+ import { usePollViewState } from '../AppData/useUISettings';
12
+ // @ts-ignore: No implicit Any
13
+ import { POLL_VIEWS } from '../../common/constants';
14
+
15
+ export const Polls = () => {
16
+ const togglePollView = usePollViewToggle();
17
+ const { pollInView: pollID, view } = usePollViewState();
18
+
19
+ if (view === POLL_VIEWS.CREATE_POLL_QUIZ) {
20
+ return <PollsQuizMenu />;
21
+ } else if (view === POLL_VIEWS.CREATE_QUESTIONS) {
22
+ return <CreateQuestions />;
23
+ } else if (view === POLL_VIEWS.VOTE) {
24
+ return <Voting toggleVoting={togglePollView} id={pollID} />;
25
+ } else {
26
+ return null;
27
+ }
28
+ };
@@ -0,0 +1,125 @@
1
+ // @ts-check
2
+ import React, { useMemo } from 'react';
3
+ import { selectLocalPeerID, useHMSStore } from '@100mslive/react-sdk';
4
+ import { Box, Text } from '../../../../';
5
+ import { checkCorrectAnswer } from '../../../common/utils';
6
+
7
+ /**
8
+ * @param {{ isQuiz: boolean;
9
+ * isAdmin: boolean;
10
+ * pollResult: import("@100mslive/react-sdk").HMSPoll['result'];
11
+ * questions: import("@100mslive/react-sdk").HMSPoll['questions'] }} param0
12
+ */
13
+ export const PollResultSummary = ({ isQuiz, isAdmin, pollResult, questions }) => {
14
+ const localPeerID = useHMSStore(selectLocalPeerID);
15
+ const noAnswers = pollResult?.maxUsers || 0 - (pollResult?.totalUsers || 0);
16
+ const participationPercentage =
17
+ pollResult?.maxUsers && pollResult?.maxUsers > 0 ? ((pollResult?.totalUsers || 0) * 100) / pollResult.maxUsers : 0;
18
+
19
+ const totalCorrectAnswers = useMemo(() => {
20
+ let correctAnswers = 0;
21
+ questions?.forEach(question => {
22
+ correctAnswers += question.result?.correctResponses || 0;
23
+ });
24
+ return correctAnswers;
25
+ }, [questions]);
26
+
27
+ const totalIncorrectAnswers = useMemo(() => {
28
+ let incorrectAnswers = 0;
29
+ questions?.forEach(question => {
30
+ incorrectAnswers +=
31
+ (question.result?.totalResponses || 0) -
32
+ (question.result?.correctResponses || 0) -
33
+ (question.result?.skippedCount || 0);
34
+ });
35
+ return incorrectAnswers;
36
+ }, [questions]);
37
+
38
+ const localCorrectAnswers = useMemo(() => {
39
+ let correctAnswers = 0;
40
+ questions?.forEach(question => {
41
+ const localResponse = question.responses?.find(response => response.peer?.peerid === localPeerID);
42
+ if (checkCorrectAnswer(question.answer, localResponse, question.type)) {
43
+ correctAnswers++;
44
+ }
45
+ });
46
+ return correctAnswers;
47
+ }, [localPeerID, questions]);
48
+
49
+ const localIncorrectAnswers = useMemo(() => {
50
+ let incorrectAnswers = 0;
51
+ questions?.forEach(question => {
52
+ const localResponse = question.responses?.find(response => response.peer?.peerid === localPeerID);
53
+ if (!checkCorrectAnswer(question.answer, localResponse, question.type) && localResponse) {
54
+ incorrectAnswers++;
55
+ }
56
+ });
57
+ return incorrectAnswers;
58
+ }, [localPeerID, questions]);
59
+
60
+ if (!pollResult) {
61
+ return null;
62
+ }
63
+
64
+ let StatsComponents;
65
+
66
+ if (isQuiz && isAdmin) {
67
+ StatsComponents = (
68
+ <>
69
+ <PollStat label="No. of correct answers" value={totalCorrectAnswers} />
70
+ <PollStat label="No. of wrong answerss" value={totalIncorrectAnswers} />
71
+ <PollStat label="Peers who didn't answer" value={noAnswers} />
72
+ <PollStat label="Participation Percentage" value={participationPercentage.toFixed(2) + '%'} />
73
+ </>
74
+ );
75
+ } else if (isQuiz && !isAdmin) {
76
+ StatsComponents = (
77
+ <>
78
+ <PollStat label="No. of correct answers" value={localCorrectAnswers} />
79
+ <PollStat label="No. of wrong answers" value={localIncorrectAnswers} />
80
+ </>
81
+ );
82
+ } else if (!isQuiz && isAdmin) {
83
+ StatsComponents = (
84
+ <>
85
+ <PollStat label="Peers who didn't answer" value={noAnswers} />
86
+ <PollStat label="Participation Percentage" value={participationPercentage.toFixed(2) + '%'} />
87
+ </>
88
+ );
89
+ } else {
90
+ return null;
91
+ }
92
+
93
+ return (
94
+ <Box
95
+ css={{
96
+ display: 'grid',
97
+ 'grid-template-columns': 'repeat(2, 2fr)',
98
+ gap: '$4',
99
+ mt: '$3',
100
+ }}
101
+ >
102
+ {StatsComponents}
103
+ </Box>
104
+ );
105
+ };
106
+
107
+ const PollStat = ({ label, value }) => {
108
+ return (
109
+ <Box css={{ bg: '$surface_bright', p: '$8', r: '$1' }}>
110
+ <Text
111
+ variant="overline"
112
+ css={{
113
+ fontWeight: '$semiBold',
114
+ color: '$on_surface_medium',
115
+ textTransform: 'uppercase',
116
+ }}
117
+ >
118
+ {label}
119
+ </Text>
120
+ <Text variant="sub1" css={{ fontWeight: '$semiBold', color: '$on_surface_medium' }}>
121
+ {value}
122
+ </Text>
123
+ </Box>
124
+ );
125
+ };