catchup-library-web 1.20.30 → 1.20.32

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.
@@ -0,0 +1,306 @@
1
+ import { InlineMath } from "react-katex";
2
+ import InputGroup from "../../groups/InputGroup";
3
+ import { constructInputWithSpecialExpressionList } from "../../../utilization/CatchtivityUtilization";
4
+ import i18n from "../../../language/i18n";
5
+ import { useState } from "react";
6
+ import { useEffect } from "react";
7
+ import { useDrop } from "react-dnd";
8
+ import BaseImage from "../../images/BaseImage";
9
+ import { shuffleArray } from "../../../utilization/AppUtilization";
10
+ import DraggableItem from "../../dnds/DraggableItem";
11
+ import DroppableItem from "../../dnds/DroppableItem";
12
+ import ShowMaterialMediaByContentType from "./ShowMaterialMediaByContentType";
13
+ import DividerLine from "../../dividers/DividerLine";
14
+ import { IFillInTheBlanksActivityMaterialProps } from "../../../properties/ActivityProperties";
15
+ import InputWithSpecialExpression from "../../texts/InputWithSpecialExpression";
16
+
17
+ const FillInTheBlanksActivityMaterialContent = ({
18
+ uniqueValue,
19
+ answer,
20
+ optionList,
21
+ materialMap,
22
+ contentMap,
23
+ checkCanAnswerQuestion,
24
+ onChange,
25
+ isPreview,
26
+ showCorrectAnswer,
27
+ }: IFillInTheBlanksActivityMaterialProps) => {
28
+ const [shuffleOptionList, setShuffleOptionList] = useState([]);
29
+ const [selectedOption, setSelectedOption] = useState(null);
30
+ const [pasteOptionIndex, setPasteOptionIndex] = useState(null);
31
+ const [{ isOver, canDrop }, drop] = useDrop({
32
+ accept: "FILL_IN_THE_BLANKS",
33
+ drop: () => {},
34
+ collect: (monitor) => ({
35
+ isOver: monitor.isOver(),
36
+ canDrop: monitor.canDrop(),
37
+ }),
38
+ });
39
+
40
+ useEffect(() => {
41
+ setShuffleOptionList(shuffleArray(optionList));
42
+ }, []);
43
+
44
+ useEffect(() => {
45
+ if (!showCorrectAnswer) return;
46
+ const foundAnswer = answer.data.find(
47
+ (answerData: any) => answerData.type === "FILL_IN_THE_BLANKS"
48
+ );
49
+ if (foundAnswer.answerMap.length === 0) return;
50
+ if (Object.keys(materialMap).length === 0) return;
51
+ foundAnswer.answerMap = Object.keys(materialMap).map(
52
+ (materialMapKey) => JSON.parse(materialMap[materialMapKey])[0]
53
+ );
54
+
55
+ onChange(answer, 0, JSON.parse(materialMap[0])[0]);
56
+ }, [showCorrectAnswer]);
57
+
58
+ const retrieveAnswerMap = () => {
59
+ const foundIndex = answer.data.findIndex(
60
+ (answerData: any) => answerData.type === "FILL_IN_THE_BLANKS"
61
+ );
62
+ return answer.data[foundIndex].answerMap;
63
+ };
64
+
65
+ const checkAnswerState = (correctAnswerList: any, learnerAnswer: string) => {
66
+ if (!isPreview) return null;
67
+ const foundIndex = correctAnswerList.findIndex(
68
+ (correctAnswer: string) => correctAnswer === learnerAnswer
69
+ );
70
+ if (foundIndex !== -1) {
71
+ return "CORRECT";
72
+ }
73
+ return "INCORRECT";
74
+ };
75
+
76
+ const checkAnswerProvided = (answerMap: any, option: string) => {
77
+ return (
78
+ Object.keys(answerMap).findIndex((key) => answerMap[key] === option) !==
79
+ -1
80
+ );
81
+ };
82
+
83
+ const handleSelectOption = (option: any) => {
84
+ setSelectedOption(option);
85
+ setPasteOptionIndex(null);
86
+ };
87
+
88
+ const answerMap = retrieveAnswerMap();
89
+
90
+ return (
91
+ <div
92
+ className="flex flex-row flex-wrap items-center"
93
+ // onMouseUp={() => {}}
94
+ // onTouchStart={() => {}}
95
+ >
96
+ <div className="hidden md:block">
97
+ <span className="font-semibold text-xl opacity-60">
98
+ {i18n.t("please_select_fill_in_the_blanks_text")}
99
+ </span>
100
+ </div>
101
+ <div className="hidden md:contents">
102
+ <DividerLine />
103
+ </div>
104
+
105
+ <div className="w-full flex flex-row flex-wrap gap-x-2 gap-y-2 my-2">
106
+ {shuffleOptionList.map((option, index) =>
107
+ checkAnswerProvided(answerMap, option) ? (
108
+ <div className="opacity-30" key={index}>
109
+ <ShowMaterialMediaByContentType
110
+ key={`${uniqueValue}-${index}`}
111
+ contentType={contentMap.type}
112
+ src={option}
113
+ canFullScreen={true}
114
+ />
115
+ </div>
116
+ ) : (
117
+ <DraggableItem
118
+ key={index}
119
+ item={{ index: option }}
120
+ type={"FILL_IN_THE_BLANKS"}
121
+ component={
122
+ contentMap.type === "TEXT" ? (
123
+ <div
124
+ className="border-catchup-blue border-2 px-2 rounded-catchup-xlarge cursor-pointer select-none"
125
+ onClick={() => handleSelectOption(option)}
126
+ onMouseDown={() => {
127
+ setSelectedOption(option);
128
+ setPasteOptionIndex(null);
129
+ }}
130
+ onTouchEnd={() => {
131
+ setSelectedOption(option);
132
+ setPasteOptionIndex(null);
133
+ }}
134
+ >
135
+ <p className="italic whitespace-pre-wrap">
136
+ <InputWithSpecialExpression
137
+ value={option}
138
+ showSpecialOnly={false}
139
+ />
140
+ </p>
141
+ </div>
142
+ ) : (
143
+ <div
144
+ className="border-catchup-blue border-2 px-2 py-1 rounded-catchup-xlarge cursor-pointer select-none"
145
+ onClick={() => handleSelectOption(option)}
146
+ onMouseDown={() => {
147
+ setSelectedOption(option);
148
+ setPasteOptionIndex(null);
149
+ }}
150
+ onTouchEnd={() => {
151
+ setSelectedOption(option);
152
+ setPasteOptionIndex(null);
153
+ }}
154
+ >
155
+ <ShowMaterialMediaByContentType
156
+ key={`${uniqueValue}-${index}`}
157
+ contentType={contentMap.type}
158
+ src={option}
159
+ canFullScreen={true}
160
+ />
161
+ </div>
162
+ )
163
+ }
164
+ moveCardHandler={() => {
165
+ onChange(answer, pasteOptionIndex, selectedOption);
166
+ }}
167
+ />
168
+ )
169
+ )}
170
+ </div>
171
+ <div className="w-full flex flex-row flex-wrap">
172
+ {Object.keys(answerMap).map((materialKey, index) => {
173
+ const learnerAnswerState = checkAnswerState(
174
+ JSON.parse(materialMap[materialKey]),
175
+ answerMap[materialKey]
176
+ );
177
+ return (
178
+ <div key={index} className="w-full md:w-1/2">
179
+ <div className="mx-2">
180
+ <DroppableItem
181
+ key={index}
182
+ item={{ index }}
183
+ type={"FILL_IN_THE_BLANKS"}
184
+ target={pasteOptionIndex}
185
+ setTarget={setPasteOptionIndex}
186
+ dropRef={drop}
187
+ component={
188
+ <div className="w-full flex flex-row my-2 gap-x-2">
189
+ <div className="my-auto">
190
+ <p className="text-xl">
191
+ {parseFloat(materialKey) + 1}.
192
+ </p>
193
+ </div>
194
+ <div className="flex-1">
195
+ {checkCanAnswerQuestion() ? (
196
+ contentMap.type === "TEXT" ? (
197
+ <div className="relative">
198
+ <div className="flex-1">
199
+ <div
200
+ className={`w-full min-h-[44px] border rounded-lg ${
201
+ answerMap[materialKey]
202
+ ? "border-catchup-blue-400 px-2"
203
+ : "bg-catchup-gray-50 border-catchup-gray-200 border-dashed py-2 px-4"
204
+ }`}
205
+ onClick={() => {
206
+ if (answerMap[materialKey]) {
207
+ onChange(answer, materialKey, "");
208
+ }
209
+ }}
210
+ >
211
+ {answerMap[materialKey] ? (
212
+ <InputWithSpecialExpression
213
+ value={answerMap[materialKey]}
214
+ showSpecialOnly={false}
215
+ />
216
+ ) : (
217
+ <p className="text-gray-400 italic"></p>
218
+ )}
219
+ </div>
220
+ </div>
221
+
222
+ {learnerAnswerState === "CORRECT" ? (
223
+ <div className="absolute -top-[10px] right-4 bg-catchup-white">
224
+ <BaseImage
225
+ src="/icons/checkbox.webp"
226
+ alt="checkbox"
227
+ size="small"
228
+ />
229
+ </div>
230
+ ) : learnerAnswerState === "INCORRECT" ? (
231
+ <div className="absolute -top-[10px] right-4 bg-catchup-white">
232
+ <BaseImage
233
+ src="/icons/cross-red.webp"
234
+ alt="cross-red"
235
+ size="small"
236
+ />
237
+ </div>
238
+ ) : null}
239
+ </div>
240
+ ) : answerMap[materialKey] === "" ? (
241
+ <div
242
+ className={`w-catchup-activity-media-box-item h-catchup-activity-media-box-item border rounded-catchup-xlarge border-dashed ${
243
+ learnerAnswerState === "CORRECT"
244
+ ? "border-catchup-green"
245
+ : learnerAnswerState === "INCORRECT"
246
+ ? "border-catchup-red"
247
+ : "border-catchup-blue"
248
+ }`}
249
+ >
250
+ <div className="h-full flex flex-col items-center justify-center px-4 py-2">
251
+ <span className="italic">
252
+ {i18n.t("please_drop_here")}
253
+ </span>
254
+ </div>
255
+ </div>
256
+ ) : (
257
+ <div
258
+ className="flex-1 cursor-pointer"
259
+ onClick={() => {
260
+ onChange(answer, materialKey, "");
261
+ }}
262
+ >
263
+ <ShowMaterialMediaByContentType
264
+ key={`${uniqueValue}-${index}`}
265
+ contentType={contentMap.type}
266
+ src={answerMap[materialKey]}
267
+ canFullScreen={true}
268
+ />
269
+ </div>
270
+ )
271
+ ) : (
272
+ <p key={materialKey} className="text-xl">
273
+ {constructInputWithSpecialExpressionList(
274
+ answerMap[materialKey]
275
+ ).map((inputPart, index) => (
276
+ <span
277
+ key={index}
278
+ className={`${
279
+ inputPart.isBold ? "font-bold" : ""
280
+ } ${inputPart.isUnderline ? "underline" : ""}`}
281
+ >
282
+ {inputPart.isEquation ? (
283
+ <span className="text-xl">
284
+ <InlineMath math={inputPart.value} />
285
+ </span>
286
+ ) : (
287
+ inputPart.value
288
+ )}
289
+ </span>
290
+ ))}
291
+ </p>
292
+ )}
293
+ </div>
294
+ </div>
295
+ }
296
+ />
297
+ </div>
298
+ </div>
299
+ );
300
+ })}
301
+ </div>
302
+ </div>
303
+ );
304
+ };
305
+
306
+ export default FillInTheBlanksActivityMaterialContent;