catchup-library-web 1.20.36 → 1.21.1
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.
- package/dist/index.js +498 -264
- package/dist/index.mjs +498 -264
- package/package.json +2 -6
- package/src/components/activities/material-contents/FillInTheBlanksActivityMaterialContent.tsx +68 -10
- package/src/components/activities/material-contents/GroupingActivityMaterialContent.tsx +74 -2
- package/src/components/activities/material-contents/MatchingActivityMaterialContent.tsx +255 -144
- package/src/components/activities/material-contents/OrderingActivityMaterialContent.tsx +76 -2
- package/src/components/activities/material-contents/FillInTheBlanksActivityMaterialContent2.tsx +0 -306
- package/src/components/activities/material-contents/GroupingActivityMaterialContent2.tsx +0 -362
- package/src/components/activities/material-contents/MatchingActivityMaterialContent2.tsx +0 -350
- package/src/components/activities/material-contents/OrderingActivityMaterialContent2.tsx +0 -231
- package/src/components/dnds/DraggableDroppableItem.tsx +0 -60
- package/src/components/dnds/DraggableItem.tsx +0 -39
- package/src/components/dnds/DroppableItem.tsx +0 -33
|
@@ -2,7 +2,6 @@ import { useEffect, useRef, useState } from "react";
|
|
|
2
2
|
import ShowMaterialMediaByContentType from "./ShowMaterialMediaByContentType";
|
|
3
3
|
import { InlineMath } from "react-katex";
|
|
4
4
|
import { constructInputWithSpecialExpressionList } from "../../../utilization/CatchtivityUtilization";
|
|
5
|
-
import { IMatchingActivityMaterialProps } from "../../../properties/ActivityProperties";
|
|
6
5
|
import DividerLine from "../../dividers/DividerLine";
|
|
7
6
|
|
|
8
7
|
const MatchingActivityMaterialContent = ({
|
|
@@ -14,24 +13,25 @@ const MatchingActivityMaterialContent = ({
|
|
|
14
13
|
onChange,
|
|
15
14
|
isPreview,
|
|
16
15
|
showCorrectAnswer,
|
|
17
|
-
}
|
|
18
|
-
const [selectedValue, setSelectedValue] = useState
|
|
19
|
-
const [draggedValue, setDraggedValue] = useState
|
|
20
|
-
const [dropTargetKey, setDropTargetKey] = useState
|
|
21
|
-
const [draggedElement, setDraggedElement] = useState
|
|
22
|
-
null
|
|
23
|
-
);
|
|
16
|
+
}) => {
|
|
17
|
+
const [selectedValue, setSelectedValue] = useState(null);
|
|
18
|
+
const [draggedValue, setDraggedValue] = useState(null);
|
|
19
|
+
const [dropTargetKey, setDropTargetKey] = useState(null);
|
|
20
|
+
const [draggedElement, setDraggedElement] = useState(null);
|
|
24
21
|
const [isShuffled, setIsShuffled] = useState(false);
|
|
25
|
-
const [shuffledMaterialList, setShuffledMaterialList] = useState
|
|
26
|
-
const dragElementRef = useRef
|
|
27
|
-
const [
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
22
|
+
const [shuffledMaterialList, setShuffledMaterialList] = useState([]);
|
|
23
|
+
const dragElementRef = useRef(null);
|
|
24
|
+
const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });
|
|
25
|
+
const [touchPosition, setTouchPosition] = useState({ x: 0, y: 0 });
|
|
26
|
+
const itemsRef = useRef(null);
|
|
27
|
+
const scrollIntervalRef = useRef(null);
|
|
28
|
+
|
|
29
|
+
// Auto-scroll configuration
|
|
30
|
+
const SCROLL_THRESHOLD = 100; // Distance from edge to trigger scroll
|
|
31
|
+
const SCROLL_SPEED = 10; // Pixels to scroll per interval
|
|
32
32
|
|
|
33
33
|
useEffect(() => {
|
|
34
|
-
const shuffleArray = (array
|
|
34
|
+
const shuffleArray = (array) => {
|
|
35
35
|
if (!isShuffled) {
|
|
36
36
|
const copyArray = JSON.parse(JSON.stringify(array));
|
|
37
37
|
for (let i = copyArray.length - 1; i > 0; i--) {
|
|
@@ -43,7 +43,7 @@ const MatchingActivityMaterialContent = ({
|
|
|
43
43
|
}
|
|
44
44
|
return array;
|
|
45
45
|
};
|
|
46
|
-
const materialList
|
|
46
|
+
const materialList = [];
|
|
47
47
|
Object.keys(materialMap).forEach((materialKey) => {
|
|
48
48
|
materialList.push(materialMap[materialKey]);
|
|
49
49
|
});
|
|
@@ -52,32 +52,84 @@ const MatchingActivityMaterialContent = ({
|
|
|
52
52
|
|
|
53
53
|
useEffect(() => {
|
|
54
54
|
if (!showCorrectAnswer) return;
|
|
55
|
-
answer.data.find(
|
|
56
|
-
|
|
57
|
-
).answerMap = materialMap;
|
|
55
|
+
answer.data.find((answerData) => answerData.type === "MATCHING").answerMap =
|
|
56
|
+
materialMap;
|
|
58
57
|
}, [showCorrectAnswer, answer, materialMap]);
|
|
59
58
|
|
|
59
|
+
// Auto-scroll effect for mouse drag
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
if (!draggedValue || mousePosition.y === 0) return;
|
|
62
|
+
|
|
63
|
+
const handleAutoScroll = () => {
|
|
64
|
+
const viewportHeight = window.innerHeight;
|
|
65
|
+
const scrollY = window.scrollY;
|
|
66
|
+
|
|
67
|
+
// Check if near top edge
|
|
68
|
+
if (mousePosition.y < SCROLL_THRESHOLD + scrollY) {
|
|
69
|
+
window.scrollBy(0, -SCROLL_SPEED);
|
|
70
|
+
}
|
|
71
|
+
// Check if near bottom edge
|
|
72
|
+
else if (mousePosition.y > viewportHeight + scrollY - SCROLL_THRESHOLD) {
|
|
73
|
+
window.scrollBy(0, SCROLL_SPEED);
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
scrollIntervalRef.current = setInterval(handleAutoScroll, 16); // ~60fps
|
|
78
|
+
|
|
79
|
+
return () => {
|
|
80
|
+
if (scrollIntervalRef.current) {
|
|
81
|
+
clearInterval(scrollIntervalRef.current);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
}, [draggedValue, mousePosition.y]);
|
|
85
|
+
|
|
86
|
+
// Auto-scroll effect for touch drag
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
if (!draggedValue || touchPosition.y === 0) return;
|
|
89
|
+
|
|
90
|
+
const handleAutoScroll = () => {
|
|
91
|
+
const viewportHeight = window.innerHeight;
|
|
92
|
+
const scrollY = window.scrollY;
|
|
93
|
+
|
|
94
|
+
// Check if near top edge
|
|
95
|
+
if (touchPosition.y < SCROLL_THRESHOLD + scrollY) {
|
|
96
|
+
window.scrollBy(0, -SCROLL_SPEED);
|
|
97
|
+
}
|
|
98
|
+
// Check if near bottom edge
|
|
99
|
+
else if (touchPosition.y > viewportHeight + scrollY - SCROLL_THRESHOLD) {
|
|
100
|
+
window.scrollBy(0, SCROLL_SPEED);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
scrollIntervalRef.current = setInterval(handleAutoScroll, 16); // ~60fps
|
|
105
|
+
|
|
106
|
+
return () => {
|
|
107
|
+
if (scrollIntervalRef.current) {
|
|
108
|
+
clearInterval(scrollIntervalRef.current);
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
}, [draggedValue, touchPosition.y]);
|
|
112
|
+
|
|
60
113
|
const retrieveAnswerMap = () => {
|
|
61
114
|
const foundIndex = answer.data.findIndex(
|
|
62
|
-
(answerData
|
|
115
|
+
(answerData) => answerData.type === "MATCHING"
|
|
63
116
|
);
|
|
64
117
|
return answer.data[foundIndex].answerMap;
|
|
65
118
|
};
|
|
66
119
|
|
|
67
|
-
const retrieveFilteredMaterialList = (answerMap
|
|
68
|
-
const selectedValueList
|
|
120
|
+
const retrieveFilteredMaterialList = (answerMap) => {
|
|
121
|
+
const selectedValueList = [];
|
|
69
122
|
Object.keys(answerMap).forEach((key) => {
|
|
70
123
|
selectedValueList.push(answerMap[key]);
|
|
71
124
|
});
|
|
72
125
|
|
|
73
126
|
return shuffledMaterialList.filter(
|
|
74
127
|
(material) =>
|
|
75
|
-
selectedValueList.findIndex((value
|
|
76
|
-
-1
|
|
128
|
+
selectedValueList.findIndex((value) => material === value) === -1
|
|
77
129
|
);
|
|
78
130
|
};
|
|
79
131
|
|
|
80
|
-
const checkAnswerState = (correctAnswer
|
|
132
|
+
const checkAnswerState = (correctAnswer, learnerAnswer) => {
|
|
81
133
|
if (!isPreview) return null;
|
|
82
134
|
if (!learnerAnswer) return "EMPTY";
|
|
83
135
|
if (correctAnswer === learnerAnswer) {
|
|
@@ -87,30 +139,42 @@ const MatchingActivityMaterialContent = ({
|
|
|
87
139
|
};
|
|
88
140
|
|
|
89
141
|
// Mouse drag handlers
|
|
90
|
-
const handleMouseDown = (
|
|
91
|
-
e: React.MouseEvent,
|
|
92
|
-
materialValue: string
|
|
93
|
-
): void => {
|
|
142
|
+
const handleMouseDown = (e, materialValue) => {
|
|
94
143
|
if (!checkCanAnswerQuestion()) return;
|
|
95
144
|
e.preventDefault();
|
|
96
145
|
setDraggedValue(materialValue);
|
|
97
146
|
setSelectedValue(null);
|
|
147
|
+
setMousePosition({ x: e.clientX, y: e.clientY });
|
|
98
148
|
};
|
|
99
149
|
|
|
100
|
-
const
|
|
150
|
+
const handleMouseMove = (e) => {
|
|
151
|
+
if (!draggedValue) return;
|
|
152
|
+
|
|
153
|
+
setMousePosition({ x: e.clientX, y: e.clientY });
|
|
154
|
+
|
|
155
|
+
// Find the element under the mouse point
|
|
156
|
+
const elementUnder = document.elementFromPoint(e.clientX, e.clientY);
|
|
157
|
+
const dropZone = elementUnder?.closest("[data-matching-drop-zone]");
|
|
158
|
+
|
|
159
|
+
if (dropZone) {
|
|
160
|
+
const dropKey = dropZone.getAttribute("data-matching-drop-zone");
|
|
161
|
+
setDropTargetKey(dropKey);
|
|
162
|
+
} else {
|
|
163
|
+
setDropTargetKey(null);
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const handleMouseUp = () => {
|
|
101
168
|
if (dropTargetKey !== null && draggedValue !== null) {
|
|
102
169
|
onChange(answer, dropTargetKey, draggedValue);
|
|
103
170
|
}
|
|
104
171
|
setDraggedValue(null);
|
|
105
172
|
setDropTargetKey(null);
|
|
173
|
+
setMousePosition({ x: 0, y: 0 });
|
|
106
174
|
};
|
|
107
175
|
|
|
108
176
|
// Touch drag handlers
|
|
109
|
-
const handleTouchStart = (
|
|
110
|
-
e: React.TouchEvent,
|
|
111
|
-
materialValue: string,
|
|
112
|
-
element: HTMLElement
|
|
113
|
-
): void => {
|
|
177
|
+
const handleTouchStart = (e, materialValue, element) => {
|
|
114
178
|
if (!checkCanAnswerQuestion()) return;
|
|
115
179
|
const touch = e.touches[0];
|
|
116
180
|
setDraggedValue(materialValue);
|
|
@@ -119,7 +183,7 @@ const MatchingActivityMaterialContent = ({
|
|
|
119
183
|
setSelectedValue(null);
|
|
120
184
|
};
|
|
121
185
|
|
|
122
|
-
const handleTouchMove = (e
|
|
186
|
+
const handleTouchMove = (e) => {
|
|
123
187
|
if (!draggedValue) return;
|
|
124
188
|
|
|
125
189
|
const touch = e.touches[0];
|
|
@@ -140,23 +204,24 @@ const MatchingActivityMaterialContent = ({
|
|
|
140
204
|
}
|
|
141
205
|
};
|
|
142
206
|
|
|
143
|
-
const handleTouchEnd = ()
|
|
207
|
+
const handleTouchEnd = () => {
|
|
144
208
|
if (dropTargetKey !== null && draggedValue !== null) {
|
|
145
209
|
onChange(answer, dropTargetKey, draggedValue);
|
|
146
210
|
}
|
|
147
211
|
setDraggedValue(null);
|
|
148
212
|
setDropTargetKey(null);
|
|
149
213
|
setDraggedElement(null);
|
|
214
|
+
setTouchPosition({ x: 0, y: 0 });
|
|
150
215
|
};
|
|
151
216
|
|
|
152
217
|
// Click/tap to select (for easier mobile interaction)
|
|
153
|
-
const handleSelectItem = (materialValue
|
|
218
|
+
const handleSelectItem = (materialValue) => {
|
|
154
219
|
if (!checkCanAnswerQuestion()) return;
|
|
155
220
|
setSelectedValue(materialValue);
|
|
156
221
|
setDraggedValue(null);
|
|
157
222
|
};
|
|
158
223
|
|
|
159
|
-
const handleDropZoneClick = (answerMapKey
|
|
224
|
+
const handleDropZoneClick = (answerMapKey) => {
|
|
160
225
|
if (selectedValue !== null) {
|
|
161
226
|
onChange(answer, answerMapKey, selectedValue);
|
|
162
227
|
setSelectedValue(null);
|
|
@@ -167,7 +232,55 @@ const MatchingActivityMaterialContent = ({
|
|
|
167
232
|
const filteredMaterialList = retrieveFilteredMaterialList(answerMap);
|
|
168
233
|
|
|
169
234
|
return (
|
|
170
|
-
<div onMouseUp={handleMouseUp}>
|
|
235
|
+
<div onMouseMove={handleMouseMove} onMouseUp={handleMouseUp}>
|
|
236
|
+
{/* Floating drag preview for mouse */}
|
|
237
|
+
{draggedValue && mousePosition.x > 0 && (
|
|
238
|
+
<div
|
|
239
|
+
className="fixed pointer-events-none z-50 opacity-80"
|
|
240
|
+
style={{
|
|
241
|
+
left: `${mousePosition.x}px`,
|
|
242
|
+
top: `${mousePosition.y}px`,
|
|
243
|
+
transform: "translate(-50%, -50%)",
|
|
244
|
+
}}
|
|
245
|
+
>
|
|
246
|
+
{contentMap.type === "TEXT" ? (
|
|
247
|
+
<div className="border-catchup-blue border-2 rounded-catchup-xlarge bg-white shadow-lg">
|
|
248
|
+
<div className="flex flex-col items-center justify-center m-2 min-w-[200px] px-4">
|
|
249
|
+
<p className="text-lg whitespace-pre-wrap">
|
|
250
|
+
{constructInputWithSpecialExpressionList(draggedValue).map(
|
|
251
|
+
(inputPart, index) => (
|
|
252
|
+
<span
|
|
253
|
+
key={index}
|
|
254
|
+
className={`${inputPart.isBold ? "font-bold" : ""} ${
|
|
255
|
+
inputPart.isUnderline ? "underline" : ""
|
|
256
|
+
}`}
|
|
257
|
+
>
|
|
258
|
+
{inputPart.isEquation ? (
|
|
259
|
+
<span className="text-xl">
|
|
260
|
+
<InlineMath math={inputPart.value} />
|
|
261
|
+
</span>
|
|
262
|
+
) : (
|
|
263
|
+
inputPart.value
|
|
264
|
+
)}
|
|
265
|
+
</span>
|
|
266
|
+
)
|
|
267
|
+
)}
|
|
268
|
+
</p>
|
|
269
|
+
</div>
|
|
270
|
+
</div>
|
|
271
|
+
) : (
|
|
272
|
+
<div className="border-catchup-blue border-2 rounded-catchup-xlarge bg-white shadow-lg">
|
|
273
|
+
<ShowMaterialMediaByContentType
|
|
274
|
+
key={`${uniqueValue}-drag-mouse`}
|
|
275
|
+
contentType={contentMap.type}
|
|
276
|
+
src={draggedValue}
|
|
277
|
+
canFullScreen={false}
|
|
278
|
+
/>
|
|
279
|
+
</div>
|
|
280
|
+
)}
|
|
281
|
+
</div>
|
|
282
|
+
)}
|
|
283
|
+
|
|
171
284
|
{/* Floating drag preview for touch */}
|
|
172
285
|
{draggedValue && touchPosition.x > 0 && (
|
|
173
286
|
<div
|
|
@@ -206,7 +319,7 @@ const MatchingActivityMaterialContent = ({
|
|
|
206
319
|
) : (
|
|
207
320
|
<div className="border-catchup-blue border-2 rounded-catchup-xlarge bg-white shadow-lg">
|
|
208
321
|
<ShowMaterialMediaByContentType
|
|
209
|
-
key={`${uniqueValue}-drag`}
|
|
322
|
+
key={`${uniqueValue}-drag-touch`}
|
|
210
323
|
contentType={contentMap.type}
|
|
211
324
|
src={draggedValue}
|
|
212
325
|
canFullScreen={false}
|
|
@@ -293,36 +406,34 @@ const MatchingActivityMaterialContent = ({
|
|
|
293
406
|
</>
|
|
294
407
|
) : null}
|
|
295
408
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
);
|
|
409
|
+
{Object.keys(answerMap).map((answerMapKey, index) => {
|
|
410
|
+
const learnerAnswerState = checkAnswerState(
|
|
411
|
+
materialMap[answerMapKey],
|
|
412
|
+
answerMap[answerMapKey]
|
|
413
|
+
);
|
|
302
414
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
).map((inputPart, index) => (
|
|
415
|
+
return (
|
|
416
|
+
<div key={index} className="flex flex-row w-full">
|
|
417
|
+
<div className="w-1/3">
|
|
418
|
+
<div
|
|
419
|
+
className={`${
|
|
420
|
+
contentMap.type === "TEXT"
|
|
421
|
+
? "h-catchup-activity-text-box-item"
|
|
422
|
+
: "h-catchup-activity-media-box-item"
|
|
423
|
+
} flex flex-col items-center justify-center border-2 rounded-catchup-xlarge transition-all duration-300 my-3 ${
|
|
424
|
+
learnerAnswerState === "EMPTY"
|
|
425
|
+
? "border-catchup-blue"
|
|
426
|
+
: learnerAnswerState === "CORRECT"
|
|
427
|
+
? "border-catchup-green"
|
|
428
|
+
: learnerAnswerState === "INCORRECT"
|
|
429
|
+
? "border-catchup-red"
|
|
430
|
+
: "border-catchup-blue"
|
|
431
|
+
}`}
|
|
432
|
+
>
|
|
433
|
+
<div className="flex flex-col items-center justify-center transition-all duration-300 px-4 text-center">
|
|
434
|
+
<p className="text-lg whitespace-pre-wrap">
|
|
435
|
+
{constructInputWithSpecialExpressionList(answerMapKey).map(
|
|
436
|
+
(inputPart, index) => (
|
|
326
437
|
<span
|
|
327
438
|
key={index}
|
|
328
439
|
className={`${inputPart.isBold ? "font-bold" : ""} ${
|
|
@@ -337,86 +448,86 @@ const MatchingActivityMaterialContent = ({
|
|
|
337
448
|
inputPart.value
|
|
338
449
|
)}
|
|
339
450
|
</span>
|
|
340
|
-
)
|
|
341
|
-
|
|
342
|
-
</
|
|
451
|
+
)
|
|
452
|
+
)}
|
|
453
|
+
</p>
|
|
343
454
|
</div>
|
|
344
455
|
</div>
|
|
345
|
-
|
|
346
|
-
|
|
456
|
+
</div>
|
|
457
|
+
<div className="mx-4 w-[2px] bg-catchup-lighter-gray"></div>
|
|
458
|
+
<div className="flex-1">
|
|
459
|
+
<div
|
|
460
|
+
data-matching-drop-zone={answerMapKey}
|
|
461
|
+
onMouseEnter={() =>
|
|
462
|
+
draggedValue && setDropTargetKey(answerMapKey)
|
|
463
|
+
}
|
|
464
|
+
onMouseLeave={() => setDropTargetKey(null)}
|
|
465
|
+
onClick={() => handleDropZoneClick(answerMapKey)}
|
|
466
|
+
className={`${
|
|
467
|
+
dropTargetKey === answerMapKey
|
|
468
|
+
? "bg-catchup-light-blue ring-2 ring-blue-400"
|
|
469
|
+
: ""
|
|
470
|
+
} ${
|
|
471
|
+
contentMap.type === "TEXT"
|
|
472
|
+
? "h-catchup-activity-text-box-item"
|
|
473
|
+
: "h-catchup-activity-media-box-item"
|
|
474
|
+
} flex flex-col items-center justify-center border-2 rounded-catchup-xlarge cursor-pointer transition-all duration-300 my-3 ${
|
|
475
|
+
learnerAnswerState === "EMPTY"
|
|
476
|
+
? "border-catchup-blue"
|
|
477
|
+
: learnerAnswerState === "CORRECT"
|
|
478
|
+
? "border-catchup-green"
|
|
479
|
+
: learnerAnswerState === "INCORRECT"
|
|
480
|
+
? "border-catchup-red"
|
|
481
|
+
: "border-catchup-blue"
|
|
482
|
+
}`}
|
|
483
|
+
>
|
|
347
484
|
<div
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
? "bg-catchup-light-blue ring-2 ring-blue-400"
|
|
357
|
-
: ""
|
|
358
|
-
} ${
|
|
359
|
-
contentMap.type === "TEXT"
|
|
360
|
-
? "h-catchup-activity-text-box-item"
|
|
361
|
-
: "h-catchup-activity-media-box-item"
|
|
362
|
-
} flex flex-col items-center justify-center border-2 rounded-catchup-xlarge cursor-pointer transition-all duration-300 my-3 ${
|
|
363
|
-
learnerAnswerState === "EMPTY"
|
|
364
|
-
? "border-catchup-blue"
|
|
365
|
-
: learnerAnswerState === "CORRECT"
|
|
366
|
-
? "border-catchup-green"
|
|
367
|
-
: learnerAnswerState === "INCORRECT"
|
|
368
|
-
? "border-catchup-red"
|
|
369
|
-
: "border-catchup-blue"
|
|
370
|
-
}`}
|
|
485
|
+
className="h-full flex-1 flex flex-row items-center justify-center px-4"
|
|
486
|
+
onClick={(e) => {
|
|
487
|
+
e.stopPropagation();
|
|
488
|
+
if (checkCanAnswerQuestion() && answerMap[answerMapKey]) {
|
|
489
|
+
onChange(answer, answerMapKey, null);
|
|
490
|
+
setSelectedValue(null);
|
|
491
|
+
}
|
|
492
|
+
}}
|
|
371
493
|
>
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
</p>
|
|
404
|
-
) : (
|
|
405
|
-
<ShowMaterialMediaByContentType
|
|
406
|
-
key={`${uniqueValue}-${index}`}
|
|
407
|
-
contentType={contentMap.type}
|
|
408
|
-
src={answerMap[answerMapKey]}
|
|
409
|
-
canFullScreen={false}
|
|
410
|
-
/>
|
|
411
|
-
)
|
|
412
|
-
) : null}
|
|
413
|
-
</div>
|
|
494
|
+
{answerMap[answerMapKey] ? (
|
|
495
|
+
contentMap.type === "TEXT" ? (
|
|
496
|
+
<p className="text-lg whitespace-pre-wrap">
|
|
497
|
+
{constructInputWithSpecialExpressionList(
|
|
498
|
+
answerMap[answerMapKey]
|
|
499
|
+
).map((inputPart, index) => (
|
|
500
|
+
<span
|
|
501
|
+
key={index}
|
|
502
|
+
className={`${
|
|
503
|
+
inputPart.isBold ? "font-bold" : ""
|
|
504
|
+
} ${inputPart.isUnderline ? "underline" : ""}`}
|
|
505
|
+
>
|
|
506
|
+
{inputPart.isEquation ? (
|
|
507
|
+
<span className="text-xl">
|
|
508
|
+
<InlineMath math={inputPart.value} />
|
|
509
|
+
</span>
|
|
510
|
+
) : (
|
|
511
|
+
inputPart.value
|
|
512
|
+
)}
|
|
513
|
+
</span>
|
|
514
|
+
))}
|
|
515
|
+
</p>
|
|
516
|
+
) : (
|
|
517
|
+
<ShowMaterialMediaByContentType
|
|
518
|
+
key={`${uniqueValue}-${index}`}
|
|
519
|
+
contentType={contentMap.type}
|
|
520
|
+
src={answerMap[answerMapKey]}
|
|
521
|
+
canFullScreen={false}
|
|
522
|
+
/>
|
|
523
|
+
)
|
|
524
|
+
) : null}
|
|
414
525
|
</div>
|
|
415
526
|
</div>
|
|
416
527
|
</div>
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
528
|
+
</div>
|
|
529
|
+
);
|
|
530
|
+
})}
|
|
420
531
|
</div>
|
|
421
532
|
);
|
|
422
533
|
};
|
|
@@ -22,6 +22,10 @@ const OrderingActivityMaterialContent = ({
|
|
|
22
22
|
null
|
|
23
23
|
);
|
|
24
24
|
const dragElementRef = useRef<HTMLDivElement>(null);
|
|
25
|
+
const [mousePosition, setMousePosition] = useState<{ x: number; y: number }>({
|
|
26
|
+
x: 0,
|
|
27
|
+
y: 0,
|
|
28
|
+
});
|
|
25
29
|
const [touchPosition, setTouchPosition] = useState<{ x: number; y: number }>({
|
|
26
30
|
x: 0,
|
|
27
31
|
y: 0,
|
|
@@ -90,6 +94,24 @@ const OrderingActivityMaterialContent = ({
|
|
|
90
94
|
e.preventDefault();
|
|
91
95
|
setDraggedKey(materialKey);
|
|
92
96
|
setSelectedKey(null);
|
|
97
|
+
setMousePosition({ x: e.clientX, y: e.clientY });
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const handleMouseMove = (e: React.MouseEvent): void => {
|
|
101
|
+
if (!draggedKey) return;
|
|
102
|
+
|
|
103
|
+
setMousePosition({ x: e.clientX, y: e.clientY });
|
|
104
|
+
|
|
105
|
+
// Find the element under the mouse point
|
|
106
|
+
const elementUnder = document.elementFromPoint(e.clientX, e.clientY);
|
|
107
|
+
const dropZone = elementUnder?.closest("[data-ordering-drop-zone]");
|
|
108
|
+
|
|
109
|
+
if (dropZone) {
|
|
110
|
+
const dropKey = dropZone.getAttribute("data-ordering-drop-zone");
|
|
111
|
+
setDropTargetKey(dropKey);
|
|
112
|
+
} else {
|
|
113
|
+
setDropTargetKey(null);
|
|
114
|
+
}
|
|
93
115
|
};
|
|
94
116
|
|
|
95
117
|
const handleMouseUp = (): void => {
|
|
@@ -102,6 +124,7 @@ const OrderingActivityMaterialContent = ({
|
|
|
102
124
|
}
|
|
103
125
|
setDraggedKey(null);
|
|
104
126
|
setDropTargetKey(null);
|
|
127
|
+
setMousePosition({ x: 0, y: 0 });
|
|
105
128
|
};
|
|
106
129
|
|
|
107
130
|
// Touch drag handlers
|
|
@@ -150,6 +173,7 @@ const OrderingActivityMaterialContent = ({
|
|
|
150
173
|
setDraggedKey(null);
|
|
151
174
|
setDropTargetKey(null);
|
|
152
175
|
setDraggedElement(null);
|
|
176
|
+
setTouchPosition({ x: 0, y: 0 });
|
|
153
177
|
};
|
|
154
178
|
|
|
155
179
|
// Click/tap to select (for easier mobile interaction)
|
|
@@ -170,7 +194,57 @@ const OrderingActivityMaterialContent = ({
|
|
|
170
194
|
const answerMap = retrieveAnswerMap();
|
|
171
195
|
|
|
172
196
|
return (
|
|
173
|
-
<div
|
|
197
|
+
<div
|
|
198
|
+
className="flex flex-row flex-wrap"
|
|
199
|
+
onMouseMove={handleMouseMove}
|
|
200
|
+
onMouseUp={handleMouseUp}
|
|
201
|
+
>
|
|
202
|
+
{/* Floating drag preview for mouse */}
|
|
203
|
+
{draggedKey && mousePosition.x > 0 && (
|
|
204
|
+
<div
|
|
205
|
+
className="fixed pointer-events-none z-50 opacity-80"
|
|
206
|
+
style={{
|
|
207
|
+
left: `${mousePosition.x}px`,
|
|
208
|
+
top: `${mousePosition.y}px`,
|
|
209
|
+
transform: "translate(-50%, -50%)",
|
|
210
|
+
}}
|
|
211
|
+
>
|
|
212
|
+
{contentMap.type === "TEXT" ? (
|
|
213
|
+
<div className="border-catchup-blue border-2 px-3 py-2 rounded-catchup-xlarge bg-white shadow-lg">
|
|
214
|
+
<p className="text-xl whitespace-pre-wrap">
|
|
215
|
+
{constructInputWithSpecialExpressionList(
|
|
216
|
+
materialMap[answerMap[draggedKey]]
|
|
217
|
+
).map((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-xl">
|
|
226
|
+
<InlineMath math={inputPart.value} />
|
|
227
|
+
</span>
|
|
228
|
+
) : (
|
|
229
|
+
inputPart.value
|
|
230
|
+
)}
|
|
231
|
+
</span>
|
|
232
|
+
))}
|
|
233
|
+
</p>
|
|
234
|
+
</div>
|
|
235
|
+
) : (
|
|
236
|
+
<div className="border-catchup-blue border-2 px-2 py-1 rounded-catchup-xlarge bg-white shadow-lg">
|
|
237
|
+
<ShowMaterialMediaByContentType
|
|
238
|
+
key={`${uniqueValue}-drag-mouse`}
|
|
239
|
+
contentType={contentMap.type}
|
|
240
|
+
src={materialMap[answerMap[draggedKey]]}
|
|
241
|
+
canFullScreen={false}
|
|
242
|
+
/>
|
|
243
|
+
</div>
|
|
244
|
+
)}
|
|
245
|
+
</div>
|
|
246
|
+
)}
|
|
247
|
+
|
|
174
248
|
{/* Floating drag preview for touch */}
|
|
175
249
|
{draggedKey && touchPosition.x > 0 && (
|
|
176
250
|
<div
|
|
@@ -207,7 +281,7 @@ const OrderingActivityMaterialContent = ({
|
|
|
207
281
|
) : (
|
|
208
282
|
<div className="border-catchup-blue border-2 px-2 py-1 rounded-catchup-xlarge bg-white shadow-lg">
|
|
209
283
|
<ShowMaterialMediaByContentType
|
|
210
|
-
key={`${uniqueValue}-drag`}
|
|
284
|
+
key={`${uniqueValue}-drag-touch`}
|
|
211
285
|
contentType={contentMap.type}
|
|
212
286
|
src={materialMap[answerMap[draggedKey]]}
|
|
213
287
|
canFullScreen={false}
|