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