catchup-library-web 1.21.1 → 1.21.3
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
CHANGED
|
@@ -5359,6 +5359,7 @@ var GroupingActivityMaterialContent = ({
|
|
|
5359
5359
|
y: 0
|
|
5360
5360
|
});
|
|
5361
5361
|
const ref = (0, import_react20.useRef)(null);
|
|
5362
|
+
const dropZoneRefs = (0, import_react20.useRef)({});
|
|
5362
5363
|
(0, import_react20.useEffect)(() => {
|
|
5363
5364
|
const shuffleArray2 = (array) => {
|
|
5364
5365
|
if (!isShuffled) {
|
|
@@ -5386,6 +5387,17 @@ var GroupingActivityMaterialContent = ({
|
|
|
5386
5387
|
(answerData) => answerData.type === "GROUPING"
|
|
5387
5388
|
).answerMap = materialMap;
|
|
5388
5389
|
}, [showCorrectAnswer, answer, materialMap]);
|
|
5390
|
+
(0, import_react20.useEffect)(() => {
|
|
5391
|
+
if (dropTargetKey && dropZoneRefs.current[dropTargetKey]) {
|
|
5392
|
+
const dropZoneElement = dropZoneRefs.current[dropTargetKey];
|
|
5393
|
+
if (dropZoneElement) {
|
|
5394
|
+
dropZoneElement.scrollIntoView({
|
|
5395
|
+
behavior: "smooth",
|
|
5396
|
+
block: "center"
|
|
5397
|
+
});
|
|
5398
|
+
}
|
|
5399
|
+
}
|
|
5400
|
+
}, [dropTargetKey]);
|
|
5389
5401
|
const retrieveAnswerMap = () => {
|
|
5390
5402
|
const foundIndex = answer.data.findIndex(
|
|
5391
5403
|
(answerData) => answerData.type === "GROUPING"
|
|
@@ -5590,7 +5602,7 @@ var GroupingActivityMaterialContent = ({
|
|
|
5590
5602
|
}) }),
|
|
5591
5603
|
/* @__PURE__ */ (0, import_jsx_runtime30.jsx)(DividerLine_default, {})
|
|
5592
5604
|
] }) : null,
|
|
5593
|
-
|
|
5605
|
+
Object.keys(answerMap).map((answerMapKey, index) => /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { className: "flex flex-row w-full", children: [
|
|
5594
5606
|
/* @__PURE__ */ (0, import_jsx_runtime30.jsx)("div", { className: "w-1/3", children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
|
|
5595
5607
|
"div",
|
|
5596
5608
|
{
|
|
@@ -5611,6 +5623,7 @@ var GroupingActivityMaterialContent = ({
|
|
|
5611
5623
|
/* @__PURE__ */ (0, import_jsx_runtime30.jsx)("div", { className: "flex-1 min-w-0", ref, children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("div", { className: "h-full py-3", children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
|
|
5612
5624
|
"div",
|
|
5613
5625
|
{
|
|
5626
|
+
ref: (el) => dropZoneRefs.current[answerMapKey] = el,
|
|
5614
5627
|
"data-grouping-drop-zone": answerMapKey,
|
|
5615
5628
|
onMouseEnter: () => draggedValue && setDropTargetKey(answerMapKey),
|
|
5616
5629
|
onMouseLeave: () => setDropTargetKey(null),
|
|
@@ -5673,7 +5686,7 @@ var GroupingActivityMaterialContent = ({
|
|
|
5673
5686
|
) }) })
|
|
5674
5687
|
}
|
|
5675
5688
|
) }) })
|
|
5676
|
-
] }, index))
|
|
5689
|
+
] }, index))
|
|
5677
5690
|
] });
|
|
5678
5691
|
};
|
|
5679
5692
|
var GroupingActivityMaterialContent_default = GroupingActivityMaterialContent;
|
|
@@ -5747,16 +5760,22 @@ var MatchingActivityMaterialContent = ({
|
|
|
5747
5760
|
const [selectedValue, setSelectedValue] = (0, import_react21.useState)(null);
|
|
5748
5761
|
const [draggedValue, setDraggedValue] = (0, import_react21.useState)(null);
|
|
5749
5762
|
const [dropTargetKey, setDropTargetKey] = (0, import_react21.useState)(null);
|
|
5750
|
-
const [draggedElement, setDraggedElement] = (0, import_react21.useState)(
|
|
5763
|
+
const [draggedElement, setDraggedElement] = (0, import_react21.useState)(
|
|
5764
|
+
null
|
|
5765
|
+
);
|
|
5751
5766
|
const [isShuffled, setIsShuffled] = (0, import_react21.useState)(false);
|
|
5752
5767
|
const [shuffledMaterialList, setShuffledMaterialList] = (0, import_react21.useState)([]);
|
|
5753
5768
|
const dragElementRef = (0, import_react21.useRef)(null);
|
|
5754
|
-
const [mousePosition, setMousePosition] = (0, import_react21.useState)({
|
|
5755
|
-
|
|
5769
|
+
const [mousePosition, setMousePosition] = (0, import_react21.useState)({
|
|
5770
|
+
x: 0,
|
|
5771
|
+
y: 0
|
|
5772
|
+
});
|
|
5773
|
+
const [touchPosition, setTouchPosition] = (0, import_react21.useState)({
|
|
5774
|
+
x: 0,
|
|
5775
|
+
y: 0
|
|
5776
|
+
});
|
|
5756
5777
|
const itemsRef = (0, import_react21.useRef)(null);
|
|
5757
|
-
const
|
|
5758
|
-
const SCROLL_THRESHOLD = 100;
|
|
5759
|
-
const SCROLL_SPEED = 10;
|
|
5778
|
+
const dropZoneRefs = (0, import_react21.useRef)({});
|
|
5760
5779
|
(0, import_react21.useEffect)(() => {
|
|
5761
5780
|
const shuffleArray2 = (array) => {
|
|
5762
5781
|
if (!isShuffled) {
|
|
@@ -5778,44 +5797,21 @@ var MatchingActivityMaterialContent = ({
|
|
|
5778
5797
|
}, [materialMap, isShuffled]);
|
|
5779
5798
|
(0, import_react21.useEffect)(() => {
|
|
5780
5799
|
if (!showCorrectAnswer) return;
|
|
5781
|
-
answer.data.find(
|
|
5800
|
+
answer.data.find(
|
|
5801
|
+
(answerData) => answerData.type === "MATCHING"
|
|
5802
|
+
).answerMap = materialMap;
|
|
5782
5803
|
}, [showCorrectAnswer, answer, materialMap]);
|
|
5783
5804
|
(0, import_react21.useEffect)(() => {
|
|
5784
|
-
if (
|
|
5785
|
-
|
|
5786
|
-
|
|
5787
|
-
|
|
5788
|
-
|
|
5789
|
-
|
|
5790
|
-
|
|
5791
|
-
window.scrollBy(0, SCROLL_SPEED);
|
|
5792
|
-
}
|
|
5793
|
-
};
|
|
5794
|
-
scrollIntervalRef.current = setInterval(handleAutoScroll, 16);
|
|
5795
|
-
return () => {
|
|
5796
|
-
if (scrollIntervalRef.current) {
|
|
5797
|
-
clearInterval(scrollIntervalRef.current);
|
|
5798
|
-
}
|
|
5799
|
-
};
|
|
5800
|
-
}, [draggedValue, mousePosition.y]);
|
|
5801
|
-
(0, import_react21.useEffect)(() => {
|
|
5802
|
-
if (!draggedValue || touchPosition.y === 0) return;
|
|
5803
|
-
const handleAutoScroll = () => {
|
|
5804
|
-
const viewportHeight = window.innerHeight;
|
|
5805
|
-
const scrollY = window.scrollY;
|
|
5806
|
-
if (touchPosition.y < SCROLL_THRESHOLD + scrollY) {
|
|
5807
|
-
window.scrollBy(0, -SCROLL_SPEED);
|
|
5808
|
-
} else if (touchPosition.y > viewportHeight + scrollY - SCROLL_THRESHOLD) {
|
|
5809
|
-
window.scrollBy(0, SCROLL_SPEED);
|
|
5810
|
-
}
|
|
5811
|
-
};
|
|
5812
|
-
scrollIntervalRef.current = setInterval(handleAutoScroll, 16);
|
|
5813
|
-
return () => {
|
|
5814
|
-
if (scrollIntervalRef.current) {
|
|
5815
|
-
clearInterval(scrollIntervalRef.current);
|
|
5805
|
+
if (dropTargetKey && dropZoneRefs.current[dropTargetKey]) {
|
|
5806
|
+
const dropZoneElement = dropZoneRefs.current[dropTargetKey];
|
|
5807
|
+
if (dropZoneElement) {
|
|
5808
|
+
dropZoneElement.scrollIntoView({
|
|
5809
|
+
behavior: "smooth",
|
|
5810
|
+
block: "center"
|
|
5811
|
+
});
|
|
5816
5812
|
}
|
|
5817
|
-
}
|
|
5818
|
-
}, [
|
|
5813
|
+
}
|
|
5814
|
+
}, [dropTargetKey]);
|
|
5819
5815
|
const retrieveAnswerMap = () => {
|
|
5820
5816
|
const foundIndex = answer.data.findIndex(
|
|
5821
5817
|
(answerData) => answerData.type === "MATCHING"
|
|
@@ -6044,6 +6040,7 @@ var MatchingActivityMaterialContent = ({
|
|
|
6044
6040
|
/* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { className: "flex-1", children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
|
|
6045
6041
|
"div",
|
|
6046
6042
|
{
|
|
6043
|
+
ref: (el) => dropZoneRefs.current[answerMapKey] = el,
|
|
6047
6044
|
"data-matching-drop-zone": answerMapKey,
|
|
6048
6045
|
onMouseEnter: () => draggedValue && setDropTargetKey(answerMapKey),
|
|
6049
6046
|
onMouseLeave: () => setDropTargetKey(null),
|
package/dist/index.mjs
CHANGED
|
@@ -5143,6 +5143,7 @@ var GroupingActivityMaterialContent = ({
|
|
|
5143
5143
|
y: 0
|
|
5144
5144
|
});
|
|
5145
5145
|
const ref = useRef5(null);
|
|
5146
|
+
const dropZoneRefs = useRef5({});
|
|
5146
5147
|
useEffect10(() => {
|
|
5147
5148
|
const shuffleArray2 = (array) => {
|
|
5148
5149
|
if (!isShuffled) {
|
|
@@ -5170,6 +5171,17 @@ var GroupingActivityMaterialContent = ({
|
|
|
5170
5171
|
(answerData) => answerData.type === "GROUPING"
|
|
5171
5172
|
).answerMap = materialMap;
|
|
5172
5173
|
}, [showCorrectAnswer, answer, materialMap]);
|
|
5174
|
+
useEffect10(() => {
|
|
5175
|
+
if (dropTargetKey && dropZoneRefs.current[dropTargetKey]) {
|
|
5176
|
+
const dropZoneElement = dropZoneRefs.current[dropTargetKey];
|
|
5177
|
+
if (dropZoneElement) {
|
|
5178
|
+
dropZoneElement.scrollIntoView({
|
|
5179
|
+
behavior: "smooth",
|
|
5180
|
+
block: "center"
|
|
5181
|
+
});
|
|
5182
|
+
}
|
|
5183
|
+
}
|
|
5184
|
+
}, [dropTargetKey]);
|
|
5173
5185
|
const retrieveAnswerMap = () => {
|
|
5174
5186
|
const foundIndex = answer.data.findIndex(
|
|
5175
5187
|
(answerData) => answerData.type === "GROUPING"
|
|
@@ -5374,7 +5386,7 @@ var GroupingActivityMaterialContent = ({
|
|
|
5374
5386
|
}) }),
|
|
5375
5387
|
/* @__PURE__ */ jsx30(DividerLine_default, {})
|
|
5376
5388
|
] }) : null,
|
|
5377
|
-
|
|
5389
|
+
Object.keys(answerMap).map((answerMapKey, index) => /* @__PURE__ */ jsxs19("div", { className: "flex flex-row w-full", children: [
|
|
5378
5390
|
/* @__PURE__ */ jsx30("div", { className: "w-1/3", children: /* @__PURE__ */ jsx30(
|
|
5379
5391
|
"div",
|
|
5380
5392
|
{
|
|
@@ -5395,6 +5407,7 @@ var GroupingActivityMaterialContent = ({
|
|
|
5395
5407
|
/* @__PURE__ */ jsx30("div", { className: "flex-1 min-w-0", ref, children: /* @__PURE__ */ jsx30("div", { className: "h-full py-3", children: /* @__PURE__ */ jsx30(
|
|
5396
5408
|
"div",
|
|
5397
5409
|
{
|
|
5410
|
+
ref: (el) => dropZoneRefs.current[answerMapKey] = el,
|
|
5398
5411
|
"data-grouping-drop-zone": answerMapKey,
|
|
5399
5412
|
onMouseEnter: () => draggedValue && setDropTargetKey(answerMapKey),
|
|
5400
5413
|
onMouseLeave: () => setDropTargetKey(null),
|
|
@@ -5457,7 +5470,7 @@ var GroupingActivityMaterialContent = ({
|
|
|
5457
5470
|
) }) })
|
|
5458
5471
|
}
|
|
5459
5472
|
) }) })
|
|
5460
|
-
] }, index))
|
|
5473
|
+
] }, index))
|
|
5461
5474
|
] });
|
|
5462
5475
|
};
|
|
5463
5476
|
var GroupingActivityMaterialContent_default = GroupingActivityMaterialContent;
|
|
@@ -5531,16 +5544,22 @@ var MatchingActivityMaterialContent = ({
|
|
|
5531
5544
|
const [selectedValue, setSelectedValue] = useState20(null);
|
|
5532
5545
|
const [draggedValue, setDraggedValue] = useState20(null);
|
|
5533
5546
|
const [dropTargetKey, setDropTargetKey] = useState20(null);
|
|
5534
|
-
const [draggedElement, setDraggedElement] = useState20(
|
|
5547
|
+
const [draggedElement, setDraggedElement] = useState20(
|
|
5548
|
+
null
|
|
5549
|
+
);
|
|
5535
5550
|
const [isShuffled, setIsShuffled] = useState20(false);
|
|
5536
5551
|
const [shuffledMaterialList, setShuffledMaterialList] = useState20([]);
|
|
5537
5552
|
const dragElementRef = useRef6(null);
|
|
5538
|
-
const [mousePosition, setMousePosition] = useState20({
|
|
5539
|
-
|
|
5553
|
+
const [mousePosition, setMousePosition] = useState20({
|
|
5554
|
+
x: 0,
|
|
5555
|
+
y: 0
|
|
5556
|
+
});
|
|
5557
|
+
const [touchPosition, setTouchPosition] = useState20({
|
|
5558
|
+
x: 0,
|
|
5559
|
+
y: 0
|
|
5560
|
+
});
|
|
5540
5561
|
const itemsRef = useRef6(null);
|
|
5541
|
-
const
|
|
5542
|
-
const SCROLL_THRESHOLD = 100;
|
|
5543
|
-
const SCROLL_SPEED = 10;
|
|
5562
|
+
const dropZoneRefs = useRef6({});
|
|
5544
5563
|
useEffect11(() => {
|
|
5545
5564
|
const shuffleArray2 = (array) => {
|
|
5546
5565
|
if (!isShuffled) {
|
|
@@ -5562,44 +5581,21 @@ var MatchingActivityMaterialContent = ({
|
|
|
5562
5581
|
}, [materialMap, isShuffled]);
|
|
5563
5582
|
useEffect11(() => {
|
|
5564
5583
|
if (!showCorrectAnswer) return;
|
|
5565
|
-
answer.data.find(
|
|
5584
|
+
answer.data.find(
|
|
5585
|
+
(answerData) => answerData.type === "MATCHING"
|
|
5586
|
+
).answerMap = materialMap;
|
|
5566
5587
|
}, [showCorrectAnswer, answer, materialMap]);
|
|
5567
5588
|
useEffect11(() => {
|
|
5568
|
-
if (
|
|
5569
|
-
|
|
5570
|
-
|
|
5571
|
-
|
|
5572
|
-
|
|
5573
|
-
|
|
5574
|
-
|
|
5575
|
-
window.scrollBy(0, SCROLL_SPEED);
|
|
5576
|
-
}
|
|
5577
|
-
};
|
|
5578
|
-
scrollIntervalRef.current = setInterval(handleAutoScroll, 16);
|
|
5579
|
-
return () => {
|
|
5580
|
-
if (scrollIntervalRef.current) {
|
|
5581
|
-
clearInterval(scrollIntervalRef.current);
|
|
5582
|
-
}
|
|
5583
|
-
};
|
|
5584
|
-
}, [draggedValue, mousePosition.y]);
|
|
5585
|
-
useEffect11(() => {
|
|
5586
|
-
if (!draggedValue || touchPosition.y === 0) return;
|
|
5587
|
-
const handleAutoScroll = () => {
|
|
5588
|
-
const viewportHeight = window.innerHeight;
|
|
5589
|
-
const scrollY = window.scrollY;
|
|
5590
|
-
if (touchPosition.y < SCROLL_THRESHOLD + scrollY) {
|
|
5591
|
-
window.scrollBy(0, -SCROLL_SPEED);
|
|
5592
|
-
} else if (touchPosition.y > viewportHeight + scrollY - SCROLL_THRESHOLD) {
|
|
5593
|
-
window.scrollBy(0, SCROLL_SPEED);
|
|
5594
|
-
}
|
|
5595
|
-
};
|
|
5596
|
-
scrollIntervalRef.current = setInterval(handleAutoScroll, 16);
|
|
5597
|
-
return () => {
|
|
5598
|
-
if (scrollIntervalRef.current) {
|
|
5599
|
-
clearInterval(scrollIntervalRef.current);
|
|
5589
|
+
if (dropTargetKey && dropZoneRefs.current[dropTargetKey]) {
|
|
5590
|
+
const dropZoneElement = dropZoneRefs.current[dropTargetKey];
|
|
5591
|
+
if (dropZoneElement) {
|
|
5592
|
+
dropZoneElement.scrollIntoView({
|
|
5593
|
+
behavior: "smooth",
|
|
5594
|
+
block: "center"
|
|
5595
|
+
});
|
|
5600
5596
|
}
|
|
5601
|
-
}
|
|
5602
|
-
}, [
|
|
5597
|
+
}
|
|
5598
|
+
}, [dropTargetKey]);
|
|
5603
5599
|
const retrieveAnswerMap = () => {
|
|
5604
5600
|
const foundIndex = answer.data.findIndex(
|
|
5605
5601
|
(answerData) => answerData.type === "MATCHING"
|
|
@@ -5828,6 +5824,7 @@ var MatchingActivityMaterialContent = ({
|
|
|
5828
5824
|
/* @__PURE__ */ jsx32("div", { className: "flex-1", children: /* @__PURE__ */ jsx32(
|
|
5829
5825
|
"div",
|
|
5830
5826
|
{
|
|
5827
|
+
ref: (el) => dropZoneRefs.current[answerMapKey] = el,
|
|
5831
5828
|
"data-matching-drop-zone": answerMapKey,
|
|
5832
5829
|
onMouseEnter: () => draggedValue && setDropTargetKey(answerMapKey),
|
|
5833
5830
|
onMouseLeave: () => setDropTargetKey(null),
|
package/package.json
CHANGED
|
@@ -33,6 +33,7 @@ const GroupingActivityMaterialContent = ({
|
|
|
33
33
|
y: 0,
|
|
34
34
|
});
|
|
35
35
|
const ref = useRef<HTMLDivElement>(null);
|
|
36
|
+
const dropZoneRefs = useRef<{ [key: string]: HTMLDivElement | null }>({});
|
|
36
37
|
|
|
37
38
|
useEffect(() => {
|
|
38
39
|
const shuffleArray = (array: any) => {
|
|
@@ -63,6 +64,19 @@ const GroupingActivityMaterialContent = ({
|
|
|
63
64
|
).answerMap = materialMap;
|
|
64
65
|
}, [showCorrectAnswer, answer, materialMap]);
|
|
65
66
|
|
|
67
|
+
// Auto-scroll to center the drop zone when hovering
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
if (dropTargetKey && dropZoneRefs.current[dropTargetKey]) {
|
|
70
|
+
const dropZoneElement = dropZoneRefs.current[dropTargetKey];
|
|
71
|
+
if (dropZoneElement) {
|
|
72
|
+
dropZoneElement.scrollIntoView({
|
|
73
|
+
behavior: "smooth",
|
|
74
|
+
block: "center",
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}, [dropTargetKey]);
|
|
79
|
+
|
|
66
80
|
const retrieveAnswerMap = () => {
|
|
67
81
|
const foundIndex = answer.data.findIndex(
|
|
68
82
|
(answerData: any) => answerData.type === "GROUPING"
|
|
@@ -369,152 +383,151 @@ const GroupingActivityMaterialContent = ({
|
|
|
369
383
|
<DividerLine />
|
|
370
384
|
</>
|
|
371
385
|
) : null}
|
|
372
|
-
|
|
373
|
-
{
|
|
374
|
-
<div
|
|
375
|
-
<div
|
|
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
|
-
</div>
|
|
386
|
+
{Object.keys(answerMap).map((answerMapKey, index) => (
|
|
387
|
+
<div key={index} className="flex flex-row w-full">
|
|
388
|
+
<div className="w-1/3">
|
|
389
|
+
<div
|
|
390
|
+
className={`border-catchup-blue ${
|
|
391
|
+
contentMap.type === "TEXT"
|
|
392
|
+
? "h-catchup-activity-text-outer-box-item"
|
|
393
|
+
: "h-catchup-activity-media-outer-box-item"
|
|
394
|
+
} flex flex-col items-center justify-center border-2 rounded-catchup-xlarge transition-all duration-300 my-3`}
|
|
395
|
+
>
|
|
396
|
+
<div className="flex flex-col items-center justify-center transition-all duration-300 px-4 text-center">
|
|
397
|
+
<p className="text-lg whitespace-pre-wrap">
|
|
398
|
+
{constructInputWithSpecialExpressionList(answerMapKey).map(
|
|
399
|
+
(inputPart, index) => (
|
|
400
|
+
<span
|
|
401
|
+
key={index}
|
|
402
|
+
className={`${inputPart.isBold ? "font-bold" : ""} ${
|
|
403
|
+
inputPart.isUnderline ? "underline" : ""
|
|
404
|
+
}`}
|
|
405
|
+
>
|
|
406
|
+
{inputPart.isEquation ? (
|
|
407
|
+
<span className="text-2xl">
|
|
408
|
+
<InlineMath math={inputPart.value} />
|
|
409
|
+
</span>
|
|
410
|
+
) : (
|
|
411
|
+
inputPart.value
|
|
412
|
+
)}
|
|
413
|
+
</span>
|
|
414
|
+
)
|
|
415
|
+
)}
|
|
416
|
+
</p>
|
|
405
417
|
</div>
|
|
406
418
|
</div>
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
419
|
+
</div>
|
|
420
|
+
<div className="mx-4 w-[2px] bg-catchup-lighter-gray"></div>
|
|
421
|
+
<div className="flex-1 min-w-0" ref={ref}>
|
|
422
|
+
<div className="h-full py-3">
|
|
423
|
+
<div
|
|
424
|
+
ref={(el) => (dropZoneRefs.current[answerMapKey] = el)}
|
|
425
|
+
data-grouping-drop-zone={answerMapKey}
|
|
426
|
+
onMouseEnter={() =>
|
|
427
|
+
draggedValue && setDropTargetKey(answerMapKey)
|
|
428
|
+
}
|
|
429
|
+
onMouseLeave={() => setDropTargetKey(null)}
|
|
430
|
+
onClick={() => handleDropZoneClick(answerMapKey)}
|
|
431
|
+
className={`${
|
|
432
|
+
dropTargetKey === answerMapKey
|
|
433
|
+
? "bg-catchup-light-blue ring-2 ring-blue-400"
|
|
434
|
+
: ""
|
|
435
|
+
} flex-1 border-catchup-blue rounded-catchup-xlarge border-2 h-full p-1 transition-all duration-200`}
|
|
436
|
+
>
|
|
437
|
+
<div className="h-full w-full overflow-x-auto">
|
|
438
|
+
<div className="flex flex-row items-center gap-2 w-max h-full">
|
|
439
|
+
{answerMap[answerMapKey].map(
|
|
440
|
+
(answerMapValue: string, answerMapIndex: number) => {
|
|
441
|
+
const learnerAnswerState = checkAnswerState(
|
|
442
|
+
materialMap[answerMapKey],
|
|
443
|
+
answerMapValue
|
|
444
|
+
);
|
|
445
|
+
return (
|
|
446
|
+
<div key={answerMapIndex} className="p-1">
|
|
447
|
+
<div
|
|
448
|
+
className={`${
|
|
449
|
+
contentMap.type === "TEXT"
|
|
450
|
+
? "h-catchup-activity-text-box-item"
|
|
451
|
+
: "h-catchup-activity-media-box-item"
|
|
452
|
+
}`}
|
|
453
|
+
>
|
|
433
454
|
<div
|
|
434
455
|
className={`${
|
|
435
|
-
|
|
436
|
-
? "
|
|
437
|
-
: "
|
|
438
|
-
|
|
456
|
+
learnerAnswerState === "EMPTY"
|
|
457
|
+
? "border-catchup-lighter-gray"
|
|
458
|
+
: learnerAnswerState === "CORRECT"
|
|
459
|
+
? "border-catchup-green"
|
|
460
|
+
: learnerAnswerState === "INCORRECT"
|
|
461
|
+
? "border-catchup-red"
|
|
462
|
+
: "border-catchup-blue"
|
|
463
|
+
} border-2 rounded-catchup-xlarge h-full flex flex-col items-center justify-center transition-all duration-300 cursor-pointer`}
|
|
464
|
+
onClick={(e) => {
|
|
465
|
+
e.stopPropagation();
|
|
466
|
+
if (checkCanAnswerQuestion()) {
|
|
467
|
+
onChange(
|
|
468
|
+
answer,
|
|
469
|
+
answerMapKey,
|
|
470
|
+
null,
|
|
471
|
+
answerMapIndex
|
|
472
|
+
);
|
|
473
|
+
setSelectedValue(null);
|
|
474
|
+
}
|
|
475
|
+
}}
|
|
439
476
|
>
|
|
440
|
-
|
|
441
|
-
className=
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
key={index}
|
|
472
|
-
className={`${
|
|
473
|
-
inputPart.isBold
|
|
474
|
-
? "font-bold"
|
|
475
|
-
: ""
|
|
476
|
-
} ${
|
|
477
|
-
inputPart.isUnderline
|
|
478
|
-
? "underline"
|
|
479
|
-
: ""
|
|
480
|
-
}`}
|
|
481
|
-
>
|
|
482
|
-
{inputPart.isEquation ? (
|
|
483
|
-
<span className="text-2xl">
|
|
484
|
-
<InlineMath
|
|
485
|
-
math={inputPart.value}
|
|
486
|
-
/>
|
|
487
|
-
</span>
|
|
488
|
-
) : (
|
|
489
|
-
inputPart.value
|
|
490
|
-
)}
|
|
491
|
-
</span>
|
|
492
|
-
))}
|
|
493
|
-
</p>
|
|
494
|
-
</div>
|
|
477
|
+
{contentMap.type === "TEXT" ? (
|
|
478
|
+
<div className="flex flex-col items-center justify-center transition-all duration-300 min-w-[200px] overflow-y-auto">
|
|
479
|
+
<div className="m-2">
|
|
480
|
+
<p className="text-xl text-center whitespace-pre-wrap">
|
|
481
|
+
{constructInputWithSpecialExpressionList(
|
|
482
|
+
answerMapValue
|
|
483
|
+
).map((inputPart, index) => (
|
|
484
|
+
<span
|
|
485
|
+
key={index}
|
|
486
|
+
className={`${
|
|
487
|
+
inputPart.isBold
|
|
488
|
+
? "font-bold"
|
|
489
|
+
: ""
|
|
490
|
+
} ${
|
|
491
|
+
inputPart.isUnderline
|
|
492
|
+
? "underline"
|
|
493
|
+
: ""
|
|
494
|
+
}`}
|
|
495
|
+
>
|
|
496
|
+
{inputPart.isEquation ? (
|
|
497
|
+
<span className="text-2xl">
|
|
498
|
+
<InlineMath
|
|
499
|
+
math={inputPart.value}
|
|
500
|
+
/>
|
|
501
|
+
</span>
|
|
502
|
+
) : (
|
|
503
|
+
inputPart.value
|
|
504
|
+
)}
|
|
505
|
+
</span>
|
|
506
|
+
))}
|
|
507
|
+
</p>
|
|
495
508
|
</div>
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
509
|
+
</div>
|
|
510
|
+
) : (
|
|
511
|
+
<ShowMaterialMediaByContentType
|
|
512
|
+
key={`${uniqueValue}-${answerMapIndex}`}
|
|
513
|
+
contentType={contentMap.type}
|
|
514
|
+
src={answerMapValue}
|
|
515
|
+
canFullScreen={false}
|
|
516
|
+
/>
|
|
517
|
+
)}
|
|
505
518
|
</div>
|
|
506
519
|
</div>
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
520
|
+
</div>
|
|
521
|
+
);
|
|
522
|
+
}
|
|
523
|
+
)}
|
|
511
524
|
</div>
|
|
512
525
|
</div>
|
|
513
526
|
</div>
|
|
514
527
|
</div>
|
|
515
528
|
</div>
|
|
516
|
-
|
|
517
|
-
|
|
529
|
+
</div>
|
|
530
|
+
))}
|
|
518
531
|
</div>
|
|
519
532
|
);
|
|
520
533
|
};
|
|
@@ -2,6 +2,7 @@ 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";
|
|
5
6
|
import DividerLine from "../../dividers/DividerLine";
|
|
6
7
|
|
|
7
8
|
const MatchingActivityMaterialContent = ({
|
|
@@ -13,25 +14,29 @@ const MatchingActivityMaterialContent = ({
|
|
|
13
14
|
onChange,
|
|
14
15
|
isPreview,
|
|
15
16
|
showCorrectAnswer,
|
|
16
|
-
}) => {
|
|
17
|
-
const [selectedValue, setSelectedValue] = useState(null);
|
|
18
|
-
const [draggedValue, setDraggedValue] = useState(null);
|
|
19
|
-
const [dropTargetKey, setDropTargetKey] = useState(null);
|
|
20
|
-
const [draggedElement, setDraggedElement] = useState(
|
|
17
|
+
}: IMatchingActivityMaterialProps) => {
|
|
18
|
+
const [selectedValue, setSelectedValue] = useState<string | null>(null);
|
|
19
|
+
const [draggedValue, setDraggedValue] = useState<string | null>(null);
|
|
20
|
+
const [dropTargetKey, setDropTargetKey] = useState<string | null>(null);
|
|
21
|
+
const [draggedElement, setDraggedElement] = useState<HTMLElement | null>(
|
|
22
|
+
null
|
|
23
|
+
);
|
|
21
24
|
const [isShuffled, setIsShuffled] = useState(false);
|
|
22
|
-
const [shuffledMaterialList, setShuffledMaterialList] = useState([]);
|
|
23
|
-
const dragElementRef = useRef(null);
|
|
24
|
-
const [mousePosition, setMousePosition] = useState
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
25
|
+
const [shuffledMaterialList, setShuffledMaterialList] = useState<any[]>([]);
|
|
26
|
+
const dragElementRef = useRef<HTMLDivElement>(null);
|
|
27
|
+
const [mousePosition, setMousePosition] = useState<{ x: number; y: number }>({
|
|
28
|
+
x: 0,
|
|
29
|
+
y: 0,
|
|
30
|
+
});
|
|
31
|
+
const [touchPosition, setTouchPosition] = useState<{ x: number; y: number }>({
|
|
32
|
+
x: 0,
|
|
33
|
+
y: 0,
|
|
34
|
+
});
|
|
35
|
+
const itemsRef = useRef<HTMLDivElement>(null);
|
|
36
|
+
const dropZoneRefs = useRef<{ [key: string]: HTMLDivElement | null }>({});
|
|
32
37
|
|
|
33
38
|
useEffect(() => {
|
|
34
|
-
const shuffleArray = (array) => {
|
|
39
|
+
const shuffleArray = (array: any) => {
|
|
35
40
|
if (!isShuffled) {
|
|
36
41
|
const copyArray = JSON.parse(JSON.stringify(array));
|
|
37
42
|
for (let i = copyArray.length - 1; i > 0; i--) {
|
|
@@ -43,7 +48,7 @@ const MatchingActivityMaterialContent = ({
|
|
|
43
48
|
}
|
|
44
49
|
return array;
|
|
45
50
|
};
|
|
46
|
-
const materialList = [];
|
|
51
|
+
const materialList: any = [];
|
|
47
52
|
Object.keys(materialMap).forEach((materialKey) => {
|
|
48
53
|
materialList.push(materialMap[materialKey]);
|
|
49
54
|
});
|
|
@@ -52,84 +57,45 @@ const MatchingActivityMaterialContent = ({
|
|
|
52
57
|
|
|
53
58
|
useEffect(() => {
|
|
54
59
|
if (!showCorrectAnswer) return;
|
|
55
|
-
answer.data.find(
|
|
56
|
-
|
|
60
|
+
answer.data.find(
|
|
61
|
+
(answerData: any) => answerData.type === "MATCHING"
|
|
62
|
+
).answerMap = materialMap;
|
|
57
63
|
}, [showCorrectAnswer, answer, materialMap]);
|
|
58
64
|
|
|
59
|
-
// Auto-scroll
|
|
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
|
|
65
|
+
// Auto-scroll to center the drop zone when hovering
|
|
87
66
|
useEffect(() => {
|
|
88
|
-
if (
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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);
|
|
67
|
+
if (dropTargetKey && dropZoneRefs.current[dropTargetKey]) {
|
|
68
|
+
const dropZoneElement = dropZoneRefs.current[dropTargetKey];
|
|
69
|
+
if (dropZoneElement) {
|
|
70
|
+
dropZoneElement.scrollIntoView({
|
|
71
|
+
behavior: "smooth",
|
|
72
|
+
block: "center",
|
|
73
|
+
});
|
|
101
74
|
}
|
|
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]);
|
|
75
|
+
}
|
|
76
|
+
}, [dropTargetKey]);
|
|
112
77
|
|
|
113
78
|
const retrieveAnswerMap = () => {
|
|
114
79
|
const foundIndex = answer.data.findIndex(
|
|
115
|
-
(answerData) => answerData.type === "MATCHING"
|
|
80
|
+
(answerData: any) => answerData.type === "MATCHING"
|
|
116
81
|
);
|
|
117
82
|
return answer.data[foundIndex].answerMap;
|
|
118
83
|
};
|
|
119
84
|
|
|
120
|
-
const retrieveFilteredMaterialList = (answerMap) => {
|
|
121
|
-
const selectedValueList = [];
|
|
85
|
+
const retrieveFilteredMaterialList = (answerMap: any) => {
|
|
86
|
+
const selectedValueList: any = [];
|
|
122
87
|
Object.keys(answerMap).forEach((key) => {
|
|
123
88
|
selectedValueList.push(answerMap[key]);
|
|
124
89
|
});
|
|
125
90
|
|
|
126
91
|
return shuffledMaterialList.filter(
|
|
127
92
|
(material) =>
|
|
128
|
-
selectedValueList.findIndex((value) => material === value) ===
|
|
93
|
+
selectedValueList.findIndex((value: string) => material === value) ===
|
|
94
|
+
-1
|
|
129
95
|
);
|
|
130
96
|
};
|
|
131
97
|
|
|
132
|
-
const checkAnswerState = (correctAnswer, learnerAnswer) => {
|
|
98
|
+
const checkAnswerState = (correctAnswer: string, learnerAnswer: string) => {
|
|
133
99
|
if (!isPreview) return null;
|
|
134
100
|
if (!learnerAnswer) return "EMPTY";
|
|
135
101
|
if (correctAnswer === learnerAnswer) {
|
|
@@ -139,7 +105,10 @@ const MatchingActivityMaterialContent = ({
|
|
|
139
105
|
};
|
|
140
106
|
|
|
141
107
|
// Mouse drag handlers
|
|
142
|
-
const handleMouseDown = (
|
|
108
|
+
const handleMouseDown = (
|
|
109
|
+
e: React.MouseEvent,
|
|
110
|
+
materialValue: string
|
|
111
|
+
): void => {
|
|
143
112
|
if (!checkCanAnswerQuestion()) return;
|
|
144
113
|
e.preventDefault();
|
|
145
114
|
setDraggedValue(materialValue);
|
|
@@ -147,7 +116,7 @@ const MatchingActivityMaterialContent = ({
|
|
|
147
116
|
setMousePosition({ x: e.clientX, y: e.clientY });
|
|
148
117
|
};
|
|
149
118
|
|
|
150
|
-
const handleMouseMove = (e) => {
|
|
119
|
+
const handleMouseMove = (e: React.MouseEvent): void => {
|
|
151
120
|
if (!draggedValue) return;
|
|
152
121
|
|
|
153
122
|
setMousePosition({ x: e.clientX, y: e.clientY });
|
|
@@ -164,7 +133,7 @@ const MatchingActivityMaterialContent = ({
|
|
|
164
133
|
}
|
|
165
134
|
};
|
|
166
135
|
|
|
167
|
-
const handleMouseUp = () => {
|
|
136
|
+
const handleMouseUp = (): void => {
|
|
168
137
|
if (dropTargetKey !== null && draggedValue !== null) {
|
|
169
138
|
onChange(answer, dropTargetKey, draggedValue);
|
|
170
139
|
}
|
|
@@ -174,7 +143,11 @@ const MatchingActivityMaterialContent = ({
|
|
|
174
143
|
};
|
|
175
144
|
|
|
176
145
|
// Touch drag handlers
|
|
177
|
-
const handleTouchStart = (
|
|
146
|
+
const handleTouchStart = (
|
|
147
|
+
e: React.TouchEvent,
|
|
148
|
+
materialValue: string,
|
|
149
|
+
element: HTMLElement
|
|
150
|
+
): void => {
|
|
178
151
|
if (!checkCanAnswerQuestion()) return;
|
|
179
152
|
const touch = e.touches[0];
|
|
180
153
|
setDraggedValue(materialValue);
|
|
@@ -183,7 +156,7 @@ const MatchingActivityMaterialContent = ({
|
|
|
183
156
|
setSelectedValue(null);
|
|
184
157
|
};
|
|
185
158
|
|
|
186
|
-
const handleTouchMove = (e) => {
|
|
159
|
+
const handleTouchMove = (e: React.TouchEvent): void => {
|
|
187
160
|
if (!draggedValue) return;
|
|
188
161
|
|
|
189
162
|
const touch = e.touches[0];
|
|
@@ -204,7 +177,7 @@ const MatchingActivityMaterialContent = ({
|
|
|
204
177
|
}
|
|
205
178
|
};
|
|
206
179
|
|
|
207
|
-
const handleTouchEnd = () => {
|
|
180
|
+
const handleTouchEnd = (): void => {
|
|
208
181
|
if (dropTargetKey !== null && draggedValue !== null) {
|
|
209
182
|
onChange(answer, dropTargetKey, draggedValue);
|
|
210
183
|
}
|
|
@@ -215,13 +188,13 @@ const MatchingActivityMaterialContent = ({
|
|
|
215
188
|
};
|
|
216
189
|
|
|
217
190
|
// Click/tap to select (for easier mobile interaction)
|
|
218
|
-
const handleSelectItem = (materialValue) => {
|
|
191
|
+
const handleSelectItem = (materialValue: string): void => {
|
|
219
192
|
if (!checkCanAnswerQuestion()) return;
|
|
220
193
|
setSelectedValue(materialValue);
|
|
221
194
|
setDraggedValue(null);
|
|
222
195
|
};
|
|
223
196
|
|
|
224
|
-
const handleDropZoneClick = (answerMapKey) => {
|
|
197
|
+
const handleDropZoneClick = (answerMapKey: string): void => {
|
|
225
198
|
if (selectedValue !== null) {
|
|
226
199
|
onChange(answer, answerMapKey, selectedValue);
|
|
227
200
|
setSelectedValue(null);
|
|
@@ -457,6 +430,7 @@ const MatchingActivityMaterialContent = ({
|
|
|
457
430
|
<div className="mx-4 w-[2px] bg-catchup-lighter-gray"></div>
|
|
458
431
|
<div className="flex-1">
|
|
459
432
|
<div
|
|
433
|
+
ref={(el) => (dropZoneRefs.current[answerMapKey] = el)}
|
|
460
434
|
data-matching-drop-zone={answerMapKey}
|
|
461
435
|
onMouseEnter={() =>
|
|
462
436
|
draggedValue && setDropTargetKey(answerMapKey)
|