catchup-library-web 1.20.35 → 1.20.36

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,362 @@
1
+ import { useEffect, useRef, useState } from "react";
2
+ import { useDrop } from "react-dnd";
3
+ import ShowMaterialMediaByContentType from "./ShowMaterialMediaByContentType";
4
+ import { InlineMath } from "react-katex";
5
+ import { constructInputWithSpecialExpressionList } from "../../../utilization/CatchtivityUtilization";
6
+ import DividerLine from "../../dividers/DividerLine";
7
+ import { IGroupingActivityMaterialProps } from "../../../properties/ActivityProperties";
8
+ import DraggableItem from "../../dnds/DraggableItem";
9
+ import DroppableItem from "../../dnds/DroppableItem";
10
+
11
+ const GroupingActivityMaterialContent = ({
12
+ uniqueValue,
13
+ answer,
14
+ materialMap,
15
+ contentMap,
16
+ checkCanAnswerQuestion,
17
+ onChange,
18
+ isPreview,
19
+ showCorrectAnswer,
20
+ }: IGroupingActivityMaterialProps) => {
21
+ const [selectedValue, setSelectedValue] = useState(null);
22
+ const [selectedTargetKey, setSelectedTargetKey] = useState(null);
23
+ const [isShuffled, setIsShuffled] = useState(false);
24
+ const [shuffledMaterialList, setShuffledMaterialList] = useState([]);
25
+ const [{ isOver, canDrop }, drop] = useDrop({
26
+ accept: "GROUPING",
27
+ drop: () => {},
28
+ collect: (monitor: any) => ({
29
+ isOver: monitor.isOver(),
30
+ canDrop: monitor.canDrop(),
31
+ }),
32
+ });
33
+ const ref = useRef<HTMLDivElement>(null);
34
+
35
+ useEffect(() => {
36
+ const shuffleArray = (array: any) => {
37
+ if (!isShuffled) {
38
+ const copyArray = JSON.parse(JSON.stringify(array));
39
+ for (let i = copyArray.length - 1; i > 0; i--) {
40
+ const j = Math.floor(Math.random() * (i + 1));
41
+ [copyArray[i], copyArray[j]] = [copyArray[j], copyArray[i]];
42
+ }
43
+ setIsShuffled(true);
44
+ return copyArray;
45
+ }
46
+ return array;
47
+ };
48
+ const materialList: any = [];
49
+ Object.keys(materialMap).forEach((materialKey) => {
50
+ for (const materialValue of materialMap[materialKey]) {
51
+ materialList.push(materialValue);
52
+ }
53
+ });
54
+ setShuffledMaterialList(shuffleArray(materialList));
55
+ }, []);
56
+
57
+ useEffect(() => {
58
+ if (!showCorrectAnswer) return;
59
+ answer.data.find(
60
+ (answerData: any) => answerData.type === "GROUPING"
61
+ ).answerMap = materialMap;
62
+ }, [showCorrectAnswer]);
63
+
64
+ const retrieveAnswerMap = () => {
65
+ const foundIndex = answer.data.findIndex(
66
+ (answerData: any) => answerData.type === "GROUPING"
67
+ );
68
+ const answerMap = answer.data[foundIndex].answerMap;
69
+ return answerMap;
70
+ };
71
+
72
+ const retrieveFilteredMaterialList = (answerMap: any) => {
73
+ const selectedValueList: any = [];
74
+ Object.keys(answerMap).forEach((key) => {
75
+ answerMap[key].forEach((value: string) => {
76
+ selectedValueList.push(value);
77
+ });
78
+ });
79
+ return shuffledMaterialList.filter(
80
+ (material) =>
81
+ selectedValueList.findIndex((value: string) => material === value) ===
82
+ -1
83
+ );
84
+ };
85
+
86
+ const checkAnswerState = (correctAnswerList: any, learnerAnswer: string) => {
87
+ if (!isPreview) return null;
88
+ if (!learnerAnswer) return "EMPTY";
89
+ if (!correctAnswerList) return "EMPTY";
90
+ const foundIndex = correctAnswerList.findIndex(
91
+ (correctAnswer: string) => correctAnswer === learnerAnswer
92
+ );
93
+ if (foundIndex !== -1) {
94
+ return "CORRECT";
95
+ }
96
+ return "INCORRECT";
97
+ };
98
+
99
+ const handleGroupingActivityItemOnChange = (
100
+ selectedTargetKey: string,
101
+ selectedValue: string
102
+ ) => {
103
+ if (checkCanAnswerQuestion()) {
104
+ onChange(answer, selectedTargetKey, selectedValue, null);
105
+ setSelectedValue(null);
106
+ }
107
+ };
108
+
109
+ const answerMap = retrieveAnswerMap();
110
+ const filteredMaterialList = retrieveFilteredMaterialList(answerMap);
111
+
112
+ return (
113
+ <>
114
+ {filteredMaterialList.length > 0 ? (
115
+ <>
116
+ <div className="flex-1 flex flex-row gap-x-4 overflow-x-auto py-2">
117
+ {filteredMaterialList.map((materialValue, index) => {
118
+ return (
119
+ <DraggableItem
120
+ key={index}
121
+ item={{ index: materialValue }}
122
+ type={"GROUPING"}
123
+ component={
124
+ <div
125
+ className={`${
126
+ selectedValue === materialValue
127
+ ? "border-catchup-blue"
128
+ : "border-catchup-lighter-gray"
129
+ } ${
130
+ contentMap.type === "TEXT"
131
+ ? "h-catchup-activity-text-box-item"
132
+ : "h-catchup-activity-media-box-item"
133
+ } flex flex-col items-center justify-center border-2 rounded-catchup-xlarge cursor-pointer transition-all duration-300`}
134
+ onMouseDown={() => {
135
+ if (checkCanAnswerQuestion()) {
136
+ setSelectedValue(materialValue);
137
+ }
138
+ }}
139
+ onTouchEnd={() => {
140
+ if (checkCanAnswerQuestion()) {
141
+ setSelectedValue(materialValue);
142
+ }
143
+ }}
144
+ onMouseUp={() => {
145
+ if (checkCanAnswerQuestion()) {
146
+ setSelectedValue(null);
147
+ }
148
+ }}
149
+ onTouchStart={() => {
150
+ if (checkCanAnswerQuestion()) {
151
+ setSelectedValue(null);
152
+ }
153
+ }}
154
+ >
155
+ {contentMap.type === "TEXT" ? (
156
+ <div className="flex flex-col items-center justify-center m-2 min-w-[200px] overflow-y-auto">
157
+ <p className="text-xl text-center whitespace-pre-wrap">
158
+ {constructInputWithSpecialExpressionList(
159
+ materialValue
160
+ ).map((inputPart, index) => (
161
+ <span
162
+ key={index}
163
+ className={`${
164
+ inputPart.isBold ? "font-bold" : ""
165
+ } ${inputPart.isUnderline ? "underline" : ""}`}
166
+ >
167
+ {inputPart.isEquation ? (
168
+ <span className="text-2xl">
169
+ <InlineMath math={inputPart.value} />
170
+ </span>
171
+ ) : (
172
+ inputPart.value
173
+ )}
174
+ </span>
175
+ ))}
176
+ </p>
177
+ </div>
178
+ ) : (
179
+ <ShowMaterialMediaByContentType
180
+ key={`${uniqueValue}-${index}`}
181
+ contentType={contentMap.type}
182
+ src={materialValue}
183
+ canFullScreen={true}
184
+ />
185
+ )}
186
+ </div>
187
+ }
188
+ moveCardHandler={() => {
189
+ if (!selectedTargetKey) return;
190
+ if (!selectedValue) return;
191
+ handleGroupingActivityItemOnChange(
192
+ selectedTargetKey,
193
+ selectedValue
194
+ );
195
+ }}
196
+ />
197
+ );
198
+ })}
199
+ </div>
200
+ <DividerLine />
201
+ </>
202
+ ) : null}
203
+ <div className="overflow-y-auto max-h-[500px]">
204
+ {Object.keys(answerMap).map((answerMapKey, index) => (
205
+ <div key={index} className="flex flex-row w-full">
206
+ <div className="w-1/3">
207
+ <div
208
+ className={`border-catchup-blue ${
209
+ contentMap.type === "TEXT"
210
+ ? "h-catchup-activity-text-outer-box-item"
211
+ : "h-catchup-activity-media-outer-box-item"
212
+ } flex flex-col items-center justify-center border-2 rounded-catchup-xlarge transition-all duration-300 my-3`}
213
+ >
214
+ <div className="flex flex-col items-center justify-center transition-all duration-300 px-4 text-center">
215
+ <p className="text-lg whitespace-pre-wrap">
216
+ {constructInputWithSpecialExpressionList(answerMapKey).map(
217
+ (inputPart, index) => (
218
+ <span
219
+ key={index}
220
+ className={`${inputPart.isBold ? "font-bold" : ""} ${
221
+ inputPart.isUnderline ? "underline" : ""
222
+ }`}
223
+ >
224
+ {inputPart.isEquation ? (
225
+ <span className="text-2xl">
226
+ <InlineMath math={inputPart.value} />
227
+ </span>
228
+ ) : (
229
+ inputPart.value
230
+ )}
231
+ </span>
232
+ )
233
+ )}
234
+ </p>
235
+ </div>
236
+ </div>
237
+ </div>
238
+ <div className="mx-4 w-[2px] bg-catchup-lighter-gray"></div>
239
+ <div className="flex-1 min-w-0" ref={ref}>
240
+ <div className="h-full py-3">
241
+ <div
242
+ className={`${
243
+ canDrop
244
+ ? selectedTargetKey === answerMapKey
245
+ ? "bg-catchup-light-blue"
246
+ : "bg-catchup-light-blue opacity-40"
247
+ : ""
248
+ } flex-1 border-catchup-blue rounded-catchup-xlarge border-2 h-full p-1`}
249
+ >
250
+ <DroppableItem
251
+ key={index}
252
+ item={{ index: answerMapKey }}
253
+ type={"GROUPING"}
254
+ target={selectedTargetKey}
255
+ setTarget={setSelectedTargetKey}
256
+ dropRef={drop}
257
+ component={
258
+ <div className="h-full w-full overflow-x-auto">
259
+ <div className="flex flex-row items-center gap-2 w-max h-full">
260
+ {answerMap[answerMapKey].map(
261
+ (
262
+ answerMapValue: string,
263
+ answerMapIndex: number
264
+ ) => {
265
+ const learnerAnswerState = checkAnswerState(
266
+ materialMap[answerMapKey],
267
+ answerMapValue
268
+ );
269
+ return (
270
+ <div key={answerMapIndex} className="p-1">
271
+ <div
272
+ className={`${
273
+ contentMap.type === "TEXT"
274
+ ? "h-catchup-activity-text-box-item"
275
+ : "h-catchup-activity-media-box-item"
276
+ }`}
277
+ >
278
+ <div
279
+ className={`${
280
+ learnerAnswerState === "EMPTY"
281
+ ? "border-catchup-lighter-gray"
282
+ : learnerAnswerState === "CORRECT"
283
+ ? "border-catchup-green"
284
+ : learnerAnswerState === "INCORRECT"
285
+ ? "border-catchup-red"
286
+ : "border-catchup-blue"
287
+ } border-2 rounded-catchup-xlarge h-full flex flex-col items-center justify-center transition-all duration-300 cursor-pointer`}
288
+ onClick={(e) => {
289
+ e.preventDefault();
290
+ if (checkCanAnswerQuestion()) {
291
+ onChange(
292
+ answer,
293
+ answerMapKey,
294
+ null,
295
+ answerMapIndex
296
+ );
297
+ setSelectedValue(null);
298
+ }
299
+ }}
300
+ >
301
+ {contentMap.type === "TEXT" ? (
302
+ <div className="flex flex-col items-center justify-center transition-all duration-300 min-w-[200px] overflow-y-auto">
303
+ <div className="m-2">
304
+ <p className="text-xl text-center whitespace-pre-wrap">
305
+ {constructInputWithSpecialExpressionList(
306
+ answerMapValue
307
+ ).map((inputPart, index) => (
308
+ <span
309
+ key={index}
310
+ className={`${
311
+ inputPart.isBold
312
+ ? "font-bold"
313
+ : ""
314
+ } ${
315
+ inputPart.isUnderline
316
+ ? "underline"
317
+ : ""
318
+ }`}
319
+ >
320
+ {inputPart.isEquation ? (
321
+ <span className="text-2xl">
322
+ <InlineMath
323
+ math={inputPart.value}
324
+ />
325
+ </span>
326
+ ) : (
327
+ inputPart.value
328
+ )}
329
+ </span>
330
+ ))}
331
+ </p>
332
+ </div>
333
+ </div>
334
+ ) : (
335
+ <ShowMaterialMediaByContentType
336
+ key={`${uniqueValue}-${answerMapIndex}`}
337
+ contentType={contentMap.type}
338
+ src={answerMapValue}
339
+ canFullScreen={false}
340
+ />
341
+ )}
342
+ </div>
343
+ </div>
344
+ </div>
345
+ );
346
+ }
347
+ )}
348
+ </div>
349
+ </div>
350
+ }
351
+ />
352
+ </div>
353
+ </div>
354
+ </div>
355
+ </div>
356
+ ))}
357
+ </div>
358
+ </>
359
+ );
360
+ };
361
+
362
+ export default GroupingActivityMaterialContent;