@mhamz.01/easyflow-whiteboard 2.67.0 → 2.68.0
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.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"custom-node-overlay-layer.d.ts","sourceRoot":"","sources":["../../../src/components/node/custom-node-overlay-layer.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAS9C,MAAM,WAAW,IAAI;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,GAAG,aAAa,GAAG,MAAM,CAAC;IACxC,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;IACrC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED,UAAU,uBAAuB;IAC/B,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC;IACxC,iBAAiB,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,EAAE,KAAK,IAAI,CAAC;IACpD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1C,YAAY,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACzE,qBAAqB,CAAC,EAAE,YAAY,EAAE,CAAC;IACvC,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC9C,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAaD,MAAM,CAAC,OAAO,UAAU,kBAAkB,CAAC,EACzC,KAAK,EACL,SAAS,EACT,aAAa,EACb,iBAAiB,EACjB,UAAc,EACd,cAA+B,EAC/B,YAAmB,EACnB,qBAA0B,EAC1B,YAAY,GACb,EAAE,uBAAuB,
|
|
1
|
+
{"version":3,"file":"custom-node-overlay-layer.d.ts","sourceRoot":"","sources":["../../../src/components/node/custom-node-overlay-layer.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAS9C,MAAM,WAAW,IAAI;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,GAAG,aAAa,GAAG,MAAM,CAAC;IACxC,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;IACrC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED,UAAU,uBAAuB;IAC/B,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC;IACxC,iBAAiB,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,EAAE,KAAK,IAAI,CAAC;IACpD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1C,YAAY,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACzE,qBAAqB,CAAC,EAAE,YAAY,EAAE,CAAC;IACvC,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC9C,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAaD,MAAM,CAAC,OAAO,UAAU,kBAAkB,CAAC,EACzC,KAAK,EACL,SAAS,EACT,aAAa,EACb,iBAAiB,EACjB,UAAc,EACd,cAA+B,EAC/B,YAAmB,EACnB,qBAA0B,EAC1B,YAAY,GACb,EAAE,uBAAuB,2CAgmBzB"}
|
|
@@ -22,10 +22,15 @@ export default function CanvasOverlayLayer({ tasks, documents, onTasksUpdate, on
|
|
|
22
22
|
offsetX: 0,
|
|
23
23
|
offsetY: 0,
|
|
24
24
|
});
|
|
25
|
+
const finalPositionsRef = useRef(null);
|
|
25
26
|
const rafIdRef = useRef(null);
|
|
26
27
|
const overlayRef = useRef(null);
|
|
28
|
+
const localTasksRef = useRef(localTasks);
|
|
29
|
+
const localDocumentsRef = useRef(localDocuments);
|
|
27
30
|
const selectedIdsRef = useRef(selectedIds);
|
|
28
31
|
selectedIdsRef.current = selectedIds;
|
|
32
|
+
localTasksRef.current = localTasks;
|
|
33
|
+
localDocumentsRef.current = localDocuments;
|
|
29
34
|
// ── Sync props → local state ────────────────────────────────────────────────
|
|
30
35
|
useEffect(() => { setLocalTasks(tasks); }, [tasks]);
|
|
31
36
|
useEffect(() => { setLocalDocuments(documents); }, [documents]);
|
|
@@ -220,53 +225,70 @@ export default function CanvasOverlayLayer({ tasks, documents, onTasksUpdate, on
|
|
|
220
225
|
return e;
|
|
221
226
|
};
|
|
222
227
|
const handleDragStart = (itemId, e) => {
|
|
228
|
+
// 1. Safety check for the Fabric instance
|
|
223
229
|
const canvas = fabricCanvas?.current;
|
|
224
230
|
if (!canvas)
|
|
225
231
|
return;
|
|
232
|
+
// 2. Normalize the event (Touch vs Mouse)
|
|
226
233
|
if (e.cancelable)
|
|
227
234
|
e.preventDefault();
|
|
228
235
|
const pointer = getPointerEvent(e);
|
|
236
|
+
// 3. Determine which items are being dragged
|
|
237
|
+
// selection update DOES NOT trigger before drag snapshot
|
|
229
238
|
let itemsToDrag;
|
|
230
|
-
if (
|
|
231
|
-
itemsToDrag = Array.from(
|
|
239
|
+
if (selectedIdsRef.current.has(itemId)) {
|
|
240
|
+
itemsToDrag = Array.from(selectedIdsRef.current);
|
|
232
241
|
}
|
|
233
242
|
else {
|
|
234
243
|
itemsToDrag = [itemId];
|
|
235
|
-
setSelectedIds(new Set([itemId]));
|
|
236
244
|
}
|
|
245
|
+
// 4. Capture current World Transform (Zoom & Pan)
|
|
246
|
+
// We read directly from the canvas to ensure zero-frame lag
|
|
237
247
|
const vpt = canvas.viewportTransform || [1, 0, 0, 1, 0, 0];
|
|
238
248
|
const liveZoom = vpt[0];
|
|
239
249
|
const liveVpX = vpt[4];
|
|
240
250
|
const liveVpY = vpt[5];
|
|
251
|
+
// 5. Convert the Click Position from Screen Pixels to World Units
|
|
241
252
|
const clickWorldX = (pointer.clientX - liveVpX) / liveZoom;
|
|
242
253
|
const clickWorldY = (pointer.clientY - liveVpY) / liveZoom;
|
|
254
|
+
// 6. Get the clicked item's current World Position
|
|
243
255
|
const clickedPos = getItemPosition(itemId);
|
|
244
256
|
if (!clickedPos)
|
|
245
257
|
return;
|
|
258
|
+
// 7. Calculate the Offset in WORLD UNITS
|
|
259
|
+
// This is the distance from the mouse to the node's top-left in the infinite grid.
|
|
260
|
+
// This value remains constant even if you zoom during the drag.
|
|
246
261
|
const worldOffsetX = clickWorldX - clickedPos.x;
|
|
247
262
|
const worldOffsetY = clickWorldY - clickedPos.y;
|
|
263
|
+
// 8. Snapshot starting positions for all selected HTML nodes
|
|
248
264
|
const startPositions = new Map();
|
|
249
265
|
itemsToDrag.forEach((id) => {
|
|
250
266
|
const pos = getItemPosition(id);
|
|
251
267
|
if (pos)
|
|
252
268
|
startPositions.set(id, pos);
|
|
253
269
|
});
|
|
270
|
+
// 9. Snapshot starting positions for all selected Fabric objects
|
|
254
271
|
const canvasObjectsStartPos = new Map();
|
|
255
272
|
selectedCanvasObjects.forEach((obj) => {
|
|
256
273
|
canvasObjectsStartPos.set(obj, { left: obj.left || 0, top: obj.top || 0 });
|
|
257
274
|
});
|
|
275
|
+
// 10. Commit to the ref for the requestAnimationFrame loop
|
|
258
276
|
dragStateRef.current = {
|
|
259
277
|
isDragging: true,
|
|
260
278
|
itemIds: itemsToDrag,
|
|
261
279
|
startPositions,
|
|
262
280
|
canvasObjectsStartPos,
|
|
263
|
-
offsetX: worldOffsetX,
|
|
264
|
-
offsetY: worldOffsetY,
|
|
281
|
+
offsetX: worldOffsetX, // Now stored as World Units
|
|
282
|
+
offsetY: worldOffsetY, // Now stored as World Units
|
|
265
283
|
};
|
|
284
|
+
if (!selectedIdsRef.current.has(itemId) && dragStateRef.current.itemIds.length === 0) {
|
|
285
|
+
setSelectedIds(new Set([itemId]));
|
|
286
|
+
}
|
|
287
|
+
// 11. Trigger UI states
|
|
266
288
|
setDragging({ itemIds: itemsToDrag });
|
|
267
|
-
// ── ATTACH LISTENERS DIRECTLY ──
|
|
268
289
|
document.body.style.cursor = "grabbing";
|
|
269
290
|
document.body.style.userSelect = "none";
|
|
291
|
+
document.body.style.touchAction = "none";
|
|
270
292
|
};
|
|
271
293
|
// ── Drag move (HTML Node side) ───────────────────────────────────────────────
|
|
272
294
|
useEffect(() => {
|
|
@@ -308,16 +330,22 @@ export default function CanvasOverlayLayer({ tasks, documents, onTasksUpdate, on
|
|
|
308
330
|
const deltaX = newWorldX - firstStart.x;
|
|
309
331
|
const deltaY = newWorldY - firstStart.y;
|
|
310
332
|
// 6. Update HTML Nodes (Batching these into one state update)
|
|
311
|
-
setLocalTasks((prev) =>
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
333
|
+
setLocalTasks((prev) => {
|
|
334
|
+
const next = prev.map((t) => itemIds.includes(t.id)
|
|
335
|
+
? { ...t, x: (startPositions.get(t.id)?.x ?? t.x) + deltaX,
|
|
336
|
+
y: (startPositions.get(t.id)?.y ?? t.y) + deltaY }
|
|
337
|
+
: t);
|
|
338
|
+
localTasksRef.current = next; // ← write-through: ref always has latest
|
|
339
|
+
return next;
|
|
340
|
+
});
|
|
341
|
+
setLocalDocuments((prev) => {
|
|
342
|
+
const next = prev.map((d) => itemIds.includes(d.id)
|
|
343
|
+
? { ...d, x: (startPositions.get(d.id)?.x ?? d.x) + deltaX,
|
|
344
|
+
y: (startPositions.get(d.id)?.y ?? d.y) + deltaY }
|
|
345
|
+
: d);
|
|
346
|
+
localDocumentsRef.current = next; // ← write-through
|
|
347
|
+
return next;
|
|
348
|
+
});
|
|
321
349
|
// 7. Sync Fabric Objects (Imperative update for performance)
|
|
322
350
|
canvasObjectsStartPos.forEach((startPos, obj) => {
|
|
323
351
|
obj.set({
|
|
@@ -326,25 +354,29 @@ export default function CanvasOverlayLayer({ tasks, documents, onTasksUpdate, on
|
|
|
326
354
|
});
|
|
327
355
|
obj.setCoords(); // Required for selection/intersection accuracy
|
|
328
356
|
});
|
|
357
|
+
finalPositionsRef.current = {
|
|
358
|
+
tasks: localTasksRef.current, // will be updated by React after setState flushes
|
|
359
|
+
documents: localDocumentsRef.current,
|
|
360
|
+
};
|
|
329
361
|
// 8. Single render call for all Fabric changes
|
|
330
362
|
canvas.requestRenderAll();
|
|
331
363
|
});
|
|
332
364
|
};
|
|
333
365
|
const handleEnd = () => {
|
|
334
|
-
if (rafIdRef.current !== null)
|
|
366
|
+
if (rafIdRef.current !== null) {
|
|
335
367
|
cancelAnimationFrame(rafIdRef.current);
|
|
336
|
-
|
|
368
|
+
rafIdRef.current = null;
|
|
369
|
+
}
|
|
337
370
|
dragStateRef.current.isDragging = false;
|
|
338
371
|
setDragging(null);
|
|
339
|
-
// 2. Remove the listeners immediately
|
|
340
|
-
window.removeEventListener("mousemove", handleMove);
|
|
341
|
-
window.removeEventListener("mouseup", handleEnd);
|
|
342
|
-
window.removeEventListener("touchmove", handleMove);
|
|
343
|
-
window.removeEventListener("touchend", handleEnd);
|
|
344
372
|
document.body.style.cursor = "";
|
|
345
373
|
document.body.style.userSelect = "";
|
|
346
|
-
|
|
347
|
-
|
|
374
|
+
document.body.style.touchAction = "";
|
|
375
|
+
// ✅ FIX 1+3: Read from live refs — never from the stale closure.
|
|
376
|
+
// localTasksRef is kept in sync on every render, so this is always
|
|
377
|
+
// the position after the last committed setState, not the t=0 snapshot.
|
|
378
|
+
onTasksUpdate?.(localTasksRef.current);
|
|
379
|
+
onDocumentsUpdate?.(localDocumentsRef.current);
|
|
348
380
|
};
|
|
349
381
|
window.addEventListener("mousemove", handleMove, { passive: false });
|
|
350
382
|
window.addEventListener("mouseup", handleEnd);
|