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

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.
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
+ };