catchup-library-web 1.20.33 → 1.20.35

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "catchup-library-web",
3
- "version": "1.20.33",
3
+ "version": "1.20.35",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
@@ -303,11 +303,7 @@ const FillInTheBlanksActivityMaterialContent = ({
303
303
  value={answerMap[materialKey]}
304
304
  showSpecialOnly={false}
305
305
  />
306
- ) : (
307
- <p className="text-gray-400 italic">
308
- {i18n.t("please_drop_here")}
309
- </p>
310
- )}
306
+ ) : null}
311
307
  </div>
312
308
  </div>
313
309
 
@@ -1,11 +1,9 @@
1
- import { useEffect, useState } from "react";
2
- import { useDrop } from "react-dnd";
1
+ import { useEffect, useState, useRef } from "react";
3
2
  import ShowMaterialMediaByContentType from "./ShowMaterialMediaByContentType";
4
3
  import { InlineMath } from "react-katex";
5
4
  import useScreenSize from "../../../hooks/useScreenSize";
6
5
  import { constructInputWithSpecialExpressionList } from "../../../utilization/CatchtivityUtilization";
7
6
  import { IOrderingActivityMaterialProps } from "../../../properties/ActivityProperties";
8
- import DraggableDroppableItem from "../../dnds/DraggableDroppableItem";
9
7
 
10
8
  const OrderingActivityMaterialContent = ({
11
9
  uniqueValue,
@@ -17,18 +15,19 @@ const OrderingActivityMaterialContent = ({
17
15
  isPreview,
18
16
  showCorrectAnswer,
19
17
  }: IOrderingActivityMaterialProps) => {
20
- const [selectedTargetKey, setSelectedTargetKey] = useState(null);
21
18
  const [selectedKey, setSelectedKey] = useState<string | null>(null);
19
+ const [draggedKey, setDraggedKey] = useState<string | null>(null);
20
+ const [dropTargetKey, setDropTargetKey] = useState<string | null>(null);
21
+ const [draggedElement, setDraggedElement] = useState<HTMLElement | null>(
22
+ null
23
+ );
24
+ const dragElementRef = useRef<HTMLDivElement>(null);
25
+ const [touchPosition, setTouchPosition] = useState<{ x: number; y: number }>({
26
+ x: 0,
27
+ y: 0,
28
+ });
22
29
  const { screenSize } = useScreenSize();
23
30
  const [view, setView] = useState("PC");
24
- const [{ isOver, canDrop }, drop] = useDrop({
25
- accept: "ORDERING",
26
- drop: () => {},
27
- collect: (monitor) => ({
28
- isOver: monitor.isOver(),
29
- canDrop: monitor.canDrop(),
30
- }),
31
- });
32
31
 
33
32
  useEffect(() => {
34
33
  if (!screenSize) return;
@@ -64,27 +63,9 @@ const OrderingActivityMaterialContent = ({
64
63
  return "INCORRECT";
65
64
  };
66
65
 
67
- const handleOrderingActivityItemChange = (
68
- selectedKey: string,
69
- materialKey: string
70
- ) => {
71
- if (checkCanAnswerQuestion()) {
72
- if (selectedKey) {
73
- onChange(answer, selectedKey, materialKey);
74
- setSelectedKey(null);
75
- } else {
76
- setSelectedKey(materialKey);
77
- }
78
- }
79
- };
80
-
81
66
  const calculateMarginTop = (index: number) => {
82
67
  if (index === 0) {
83
- if (contentMap.type === "TEXT") {
84
- return 0;
85
- } else {
86
- return 0;
87
- }
68
+ return 0;
88
69
  } else if (index === 1) {
89
70
  if (contentMap.type === "TEXT") {
90
71
  return 65;
@@ -98,19 +79,144 @@ const OrderingActivityMaterialContent = ({
98
79
  return -130;
99
80
  }
100
81
  } else if (index % 2 === 1) {
101
- if (contentMap.type === "TEXT") {
102
- return 0;
103
- } else {
104
- return 0;
105
- }
82
+ return 0;
106
83
  }
107
84
  return 0;
108
85
  };
109
86
 
87
+ // Mouse drag handlers
88
+ const handleMouseDown = (e: React.MouseEvent, materialKey: string): void => {
89
+ if (!checkCanAnswerQuestion()) return;
90
+ e.preventDefault();
91
+ setDraggedKey(materialKey);
92
+ setSelectedKey(null);
93
+ };
94
+
95
+ const handleMouseUp = (): void => {
96
+ if (
97
+ dropTargetKey !== null &&
98
+ draggedKey !== null &&
99
+ dropTargetKey !== draggedKey
100
+ ) {
101
+ onChange(answer, draggedKey, dropTargetKey);
102
+ }
103
+ setDraggedKey(null);
104
+ setDropTargetKey(null);
105
+ };
106
+
107
+ // Touch drag handlers
108
+ const handleTouchStart = (
109
+ e: React.TouchEvent,
110
+ materialKey: string,
111
+ element: HTMLElement
112
+ ): void => {
113
+ if (!checkCanAnswerQuestion()) return;
114
+ const touch = e.touches[0];
115
+ setDraggedKey(materialKey);
116
+ setDraggedElement(element);
117
+ setTouchPosition({ x: touch.clientX, y: touch.clientY });
118
+ setSelectedKey(null);
119
+ };
120
+
121
+ const handleTouchMove = (e: React.TouchEvent): void => {
122
+ if (!draggedKey) return;
123
+
124
+ const touch = e.touches[0];
125
+ setTouchPosition({ x: touch.clientX, y: touch.clientY });
126
+
127
+ // Find the element under the touch point
128
+ const elementUnder = document.elementFromPoint(
129
+ touch.clientX,
130
+ touch.clientY
131
+ );
132
+ const dropZone = elementUnder?.closest("[data-ordering-drop-zone]");
133
+
134
+ if (dropZone) {
135
+ const dropKey = dropZone.getAttribute("data-ordering-drop-zone");
136
+ setDropTargetKey(dropKey);
137
+ } else {
138
+ setDropTargetKey(null);
139
+ }
140
+ };
141
+
142
+ const handleTouchEnd = (): void => {
143
+ if (
144
+ dropTargetKey !== null &&
145
+ draggedKey !== null &&
146
+ dropTargetKey !== draggedKey
147
+ ) {
148
+ onChange(answer, draggedKey, dropTargetKey);
149
+ }
150
+ setDraggedKey(null);
151
+ setDropTargetKey(null);
152
+ setDraggedElement(null);
153
+ };
154
+
155
+ // Click/tap to select (for easier mobile interaction)
156
+ const handleSelectItem = (materialKey: string): void => {
157
+ if (!checkCanAnswerQuestion()) return;
158
+
159
+ if (selectedKey === null) {
160
+ setSelectedKey(materialKey);
161
+ } else if (selectedKey === materialKey) {
162
+ setSelectedKey(null);
163
+ } else {
164
+ onChange(answer, selectedKey, materialKey);
165
+ setSelectedKey(null);
166
+ }
167
+ setDraggedKey(null);
168
+ };
169
+
110
170
  const answerMap = retrieveAnswerMap();
111
171
 
112
172
  return (
113
- <div className="flex flex-row flex-wrap">
173
+ <div className="flex flex-row flex-wrap" onMouseUp={handleMouseUp}>
174
+ {/* Floating drag preview for touch */}
175
+ {draggedKey && touchPosition.x > 0 && (
176
+ <div
177
+ className="fixed pointer-events-none z-50 opacity-80"
178
+ style={{
179
+ left: `${touchPosition.x}px`,
180
+ top: `${touchPosition.y}px`,
181
+ transform: "translate(-50%, -50%)",
182
+ }}
183
+ >
184
+ {contentMap.type === "TEXT" ? (
185
+ <div className="border-catchup-blue border-2 px-3 py-2 rounded-catchup-xlarge bg-white shadow-lg">
186
+ <p className="text-xl whitespace-pre-wrap">
187
+ {constructInputWithSpecialExpressionList(
188
+ materialMap[answerMap[draggedKey]]
189
+ ).map((inputPart, index) => (
190
+ <span
191
+ key={index}
192
+ className={`${inputPart.isBold ? "font-bold" : ""} ${
193
+ inputPart.isUnderline ? "underline" : ""
194
+ }`}
195
+ >
196
+ {inputPart.isEquation ? (
197
+ <span className="text-xl">
198
+ <InlineMath math={inputPart.value} />
199
+ </span>
200
+ ) : (
201
+ inputPart.value
202
+ )}
203
+ </span>
204
+ ))}
205
+ </p>
206
+ </div>
207
+ ) : (
208
+ <div className="border-catchup-blue border-2 px-2 py-1 rounded-catchup-xlarge bg-white shadow-lg">
209
+ <ShowMaterialMediaByContentType
210
+ key={`${uniqueValue}-drag`}
211
+ contentType={contentMap.type}
212
+ src={materialMap[answerMap[draggedKey]]}
213
+ canFullScreen={false}
214
+ />
215
+ </div>
216
+ )}
217
+ </div>
218
+ )}
219
+
114
220
  {Object.keys(answerMap).map((materialKey, index) => {
115
221
  const learnerAnswerState = checkAnswerState(
116
222
  answerMap[materialKey] + "",
@@ -141,85 +247,78 @@ const OrderingActivityMaterialContent = ({
141
247
  </div>
142
248
  </div>
143
249
 
144
- <DraggableDroppableItem
145
- key={index}
146
- item={{ index: materialKey }}
147
- type={"ORDERING"}
148
- dropRef={drop}
149
- component={
150
- <div
151
- className={`${
152
- canDrop
153
- ? selectedKey !== materialKey
154
- ? selectedTargetKey === materialKey
155
- ? "bg-catchup-light-blue rounded-catchup-xlarge"
156
- : "bg-catchup-light-blue rounded-catchup-xlarge opacity-40"
157
- : ""
158
- : ""
159
- } ${
160
- contentMap.type === "TEXT"
161
- ? "h-catchup-activity-text-box-item"
162
- : "h-catchup-activity-media-box-item"
163
- } flex flex-col items-center justify-center border-2 rounded-catchup-xlarge cursor-pointer p-3 ${
164
- learnerAnswerState === "CORRECT"
165
- ? "border-catchup-green"
166
- : learnerAnswerState === "INCORRECT"
167
- ? "border-catchup-red"
168
- : "border-catchup-blue"
169
- }`}
170
- onMouseDown={() => {
171
- if (checkCanAnswerQuestion()) {
172
- setSelectedKey(materialKey);
173
- }
174
- }}
175
- onTouchEnd={() => {
176
- if (checkCanAnswerQuestion()) {
177
- setSelectedKey(materialKey);
178
- }
179
- }}
180
- >
181
- {contentMap.type === "TEXT" ? (
182
- <p className="text-xl whitespace-pre-wrap">
183
- {constructInputWithSpecialExpressionList(
184
- materialMap[answerMap[materialKey]]
185
- ).map((inputPart, index) => (
186
- <span
187
- key={index}
188
- className={`${
189
- inputPart.isBold ? "font-bold" : ""
190
- } ${inputPart.isUnderline ? "underline" : ""}`}
191
- >
192
- {inputPart.isEquation ? (
193
- <span className="text-xl">
194
- <InlineMath math={inputPart.value} />
195
- </span>
196
- ) : (
197
- inputPart.value
198
- )}
199
- </span>
200
- ))}
201
- </p>
202
- ) : (
203
- <ShowMaterialMediaByContentType
204
- key={`${uniqueValue}-${index}`}
205
- contentType={contentMap.type}
206
- src={materialMap[answerMap[materialKey]]}
207
- canFullScreen={true}
208
- />
209
- )}
210
- </div>
250
+ <div
251
+ ref={draggedKey === materialKey ? dragElementRef : null}
252
+ data-ordering-drop-zone={materialKey}
253
+ className={`flex-1 ${
254
+ draggedKey === materialKey
255
+ ? "opacity-40"
256
+ : selectedKey === materialKey
257
+ ? "ring-2 ring-blue-500"
258
+ : "opacity-100"
259
+ } ${
260
+ dropTargetKey === materialKey && draggedKey !== materialKey
261
+ ? "ring-2 ring-blue-400 bg-blue-50"
262
+ : ""
263
+ } transition-all duration-200`}
264
+ onMouseDown={(e) => handleMouseDown(e, materialKey)}
265
+ onMouseEnter={() =>
266
+ draggedKey &&
267
+ draggedKey !== materialKey &&
268
+ setDropTargetKey(materialKey)
211
269
  }
212
- target={selectedTargetKey}
213
- setTarget={setSelectedTargetKey}
214
- moveCardHandler={() => {
215
- if (!selectedKey) return;
216
- if (!selectedTargetKey) return;
217
- handleOrderingActivityItemChange(
218
- selectedKey,
219
- selectedTargetKey
220
- );
221
- }}
222
- />
270
+ onMouseLeave={() => setDropTargetKey(null)}
271
+ onTouchStart={(e) =>
272
+ handleTouchStart(e, materialKey, e.currentTarget)
273
+ }
274
+ onTouchMove={handleTouchMove}
275
+ onTouchEnd={handleTouchEnd}
276
+ >
277
+ <div
278
+ className={`${
279
+ contentMap.type === "TEXT"
280
+ ? "h-catchup-activity-text-box-item"
281
+ : "h-catchup-activity-media-box-item"
282
+ } flex flex-col items-center justify-center border-2 rounded-catchup-xlarge cursor-pointer p-3 ${
283
+ learnerAnswerState === "CORRECT"
284
+ ? "border-catchup-green"
285
+ : learnerAnswerState === "INCORRECT"
286
+ ? "border-catchup-red"
287
+ : "border-catchup-blue"
288
+ }`}
289
+ onClick={() => handleSelectItem(materialKey)}
290
+ >
291
+ {contentMap.type === "TEXT" ? (
292
+ <p className="text-xl whitespace-pre-wrap">
293
+ {constructInputWithSpecialExpressionList(
294
+ materialMap[answerMap[materialKey]]
295
+ ).map((inputPart, index) => (
296
+ <span
297
+ key={index}
298
+ className={`${inputPart.isBold ? "font-bold" : ""} ${
299
+ inputPart.isUnderline ? "underline" : ""
300
+ }`}
301
+ >
302
+ {inputPart.isEquation ? (
303
+ <span className="text-xl">
304
+ <InlineMath math={inputPart.value} />
305
+ </span>
306
+ ) : (
307
+ inputPart.value
308
+ )}
309
+ </span>
310
+ ))}
311
+ </p>
312
+ ) : (
313
+ <ShowMaterialMediaByContentType
314
+ key={`${uniqueValue}-${index}`}
315
+ contentType={contentMap.type}
316
+ src={materialMap[answerMap[materialKey]]}
317
+ canFullScreen={true}
318
+ />
319
+ )}
320
+ </div>
321
+ </div>
223
322
  </div>
224
323
  </div>
225
324
  );
@@ -0,0 +1,231 @@
1
+ import { useEffect, useState } from "react";
2
+ import { useDrop } from "react-dnd";
3
+ import ShowMaterialMediaByContentType from "./ShowMaterialMediaByContentType";
4
+ import { InlineMath } from "react-katex";
5
+ import useScreenSize from "../../../hooks/useScreenSize";
6
+ import { constructInputWithSpecialExpressionList } from "../../../utilization/CatchtivityUtilization";
7
+ import { IOrderingActivityMaterialProps } from "../../../properties/ActivityProperties";
8
+ import DraggableDroppableItem from "../../dnds/DraggableDroppableItem";
9
+
10
+ const OrderingActivityMaterialContent = ({
11
+ uniqueValue,
12
+ answer,
13
+ materialMap,
14
+ contentMap,
15
+ checkCanAnswerQuestion,
16
+ onChange,
17
+ isPreview,
18
+ showCorrectAnswer,
19
+ }: IOrderingActivityMaterialProps) => {
20
+ const [selectedTargetKey, setSelectedTargetKey] = useState(null);
21
+ const [selectedKey, setSelectedKey] = useState<string | null>(null);
22
+ const { screenSize } = useScreenSize();
23
+ const [view, setView] = useState("PC");
24
+ const [{ isOver, canDrop }, drop] = useDrop({
25
+ accept: "ORDERING",
26
+ drop: () => {},
27
+ collect: (monitor) => ({
28
+ isOver: monitor.isOver(),
29
+ canDrop: monitor.canDrop(),
30
+ }),
31
+ });
32
+
33
+ useEffect(() => {
34
+ if (!screenSize) return;
35
+ if (screenSize.width <= 1024) {
36
+ setView("TABLET");
37
+ } else {
38
+ setView("PC");
39
+ }
40
+ }, [screenSize]);
41
+
42
+ useEffect(() => {
43
+ if (!showCorrectAnswer) return;
44
+ const answerMap = answer.data.find(
45
+ (answerData: any) => answerData.type === "ORDERING"
46
+ ).answerMap;
47
+ Object.keys(answerMap).forEach((answerKey, index) => {
48
+ answerMap[answerKey] = index;
49
+ });
50
+ }, [showCorrectAnswer]);
51
+
52
+ const retrieveAnswerMap = () => {
53
+ const foundIndex = answer.data.findIndex(
54
+ (answerData: any) => answerData.type === "ORDERING"
55
+ );
56
+ return answer.data[foundIndex].answerMap;
57
+ };
58
+
59
+ const checkAnswerState = (correctAnswer: string, learnerAnswer: string) => {
60
+ if (!isPreview) return null;
61
+ if (correctAnswer === learnerAnswer) {
62
+ return "CORRECT";
63
+ }
64
+ return "INCORRECT";
65
+ };
66
+
67
+ const handleOrderingActivityItemChange = (
68
+ selectedKey: string,
69
+ materialKey: string
70
+ ) => {
71
+ if (checkCanAnswerQuestion()) {
72
+ if (selectedKey) {
73
+ onChange(answer, selectedKey, materialKey);
74
+ setSelectedKey(null);
75
+ } else {
76
+ setSelectedKey(materialKey);
77
+ }
78
+ }
79
+ };
80
+
81
+ const calculateMarginTop = (index: number) => {
82
+ if (index === 0) {
83
+ if (contentMap.type === "TEXT") {
84
+ return 0;
85
+ } else {
86
+ return 0;
87
+ }
88
+ } else if (index === 1) {
89
+ if (contentMap.type === "TEXT") {
90
+ return 65;
91
+ } else {
92
+ return 130;
93
+ }
94
+ } else if (index % 2 === 0) {
95
+ if (contentMap.type === "TEXT") {
96
+ return -65;
97
+ } else {
98
+ return -130;
99
+ }
100
+ } else if (index % 2 === 1) {
101
+ if (contentMap.type === "TEXT") {
102
+ return 0;
103
+ } else {
104
+ return 0;
105
+ }
106
+ }
107
+ return 0;
108
+ };
109
+
110
+ const answerMap = retrieveAnswerMap();
111
+
112
+ return (
113
+ <div className="flex flex-row flex-wrap">
114
+ {Object.keys(answerMap).map((materialKey, index) => {
115
+ const learnerAnswerState = checkAnswerState(
116
+ answerMap[materialKey] + "",
117
+ index + ""
118
+ );
119
+ return (
120
+ <div className="w-full lg:w-1/2" key={index}>
121
+ <div
122
+ className={`flex flex-row items-center my-4 mx-2`}
123
+ style={{
124
+ marginTop:
125
+ view === "PC"
126
+ ? calculateMarginTop(parseFloat(materialKey))
127
+ : 0,
128
+ }}
129
+ >
130
+ <div className="mr-3">
131
+ <div className="h-catchup-activity-box-item w-catchup-activity-box-item flex flex-col items-center justify-center cursor-pointer transition-all duration-300 overflow-y-auto">
132
+ <div
133
+ className={`${
134
+ selectedKey === materialKey
135
+ ? "border-2 border-catchup-light-gray"
136
+ : "border-2 border-catchup-blue"
137
+ } flex flex-col items-center justify-center transition-all duration-300 rounded-catchup-full w-[50px] h-[50px]`}
138
+ >
139
+ <p className="">{parseFloat(materialKey) + 1}</p>
140
+ </div>
141
+ </div>
142
+ </div>
143
+
144
+ <DraggableDroppableItem
145
+ key={index}
146
+ item={{ index: materialKey }}
147
+ type={"ORDERING"}
148
+ dropRef={drop}
149
+ component={
150
+ <div
151
+ className={`${
152
+ canDrop
153
+ ? selectedKey !== materialKey
154
+ ? selectedTargetKey === materialKey
155
+ ? "bg-catchup-light-blue rounded-catchup-xlarge"
156
+ : "bg-catchup-light-blue rounded-catchup-xlarge opacity-40"
157
+ : ""
158
+ : ""
159
+ } ${
160
+ contentMap.type === "TEXT"
161
+ ? "h-catchup-activity-text-box-item"
162
+ : "h-catchup-activity-media-box-item"
163
+ } flex flex-col items-center justify-center border-2 rounded-catchup-xlarge cursor-pointer p-3 ${
164
+ learnerAnswerState === "CORRECT"
165
+ ? "border-catchup-green"
166
+ : learnerAnswerState === "INCORRECT"
167
+ ? "border-catchup-red"
168
+ : "border-catchup-blue"
169
+ }`}
170
+ onMouseDown={() => {
171
+ if (checkCanAnswerQuestion()) {
172
+ setSelectedKey(materialKey);
173
+ }
174
+ }}
175
+ onTouchEnd={() => {
176
+ if (checkCanAnswerQuestion()) {
177
+ setSelectedKey(materialKey);
178
+ }
179
+ }}
180
+ >
181
+ {contentMap.type === "TEXT" ? (
182
+ <p className="text-xl whitespace-pre-wrap">
183
+ {constructInputWithSpecialExpressionList(
184
+ materialMap[answerMap[materialKey]]
185
+ ).map((inputPart, index) => (
186
+ <span
187
+ key={index}
188
+ className={`${
189
+ inputPart.isBold ? "font-bold" : ""
190
+ } ${inputPart.isUnderline ? "underline" : ""}`}
191
+ >
192
+ {inputPart.isEquation ? (
193
+ <span className="text-xl">
194
+ <InlineMath math={inputPart.value} />
195
+ </span>
196
+ ) : (
197
+ inputPart.value
198
+ )}
199
+ </span>
200
+ ))}
201
+ </p>
202
+ ) : (
203
+ <ShowMaterialMediaByContentType
204
+ key={`${uniqueValue}-${index}`}
205
+ contentType={contentMap.type}
206
+ src={materialMap[answerMap[materialKey]]}
207
+ canFullScreen={true}
208
+ />
209
+ )}
210
+ </div>
211
+ }
212
+ target={selectedTargetKey}
213
+ setTarget={setSelectedTargetKey}
214
+ moveCardHandler={() => {
215
+ if (!selectedKey) return;
216
+ if (!selectedTargetKey) return;
217
+ handleOrderingActivityItemChange(
218
+ selectedKey,
219
+ selectedTargetKey
220
+ );
221
+ }}
222
+ />
223
+ </div>
224
+ </div>
225
+ );
226
+ })}
227
+ </div>
228
+ );
229
+ };
230
+
231
+ export default OrderingActivityMaterialContent;