@mhamz.01/easyflow-whiteboard 2.70.0 → 2.71.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,2CA6jBzB"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useState, useEffect, useRef } from "react";
|
|
3
|
+
import { useState, useEffect, useRef, useCallback } from "react";
|
|
4
4
|
import TaskNode from "./custom-node";
|
|
5
5
|
import DocumentNode from "./document-node";
|
|
6
6
|
// ─── Component ────────────────────────────────────────────────────────────────
|
|
@@ -22,15 +22,20 @@ export default function CanvasOverlayLayer({ tasks, documents, onTasksUpdate, on
|
|
|
22
22
|
offsetX: 0,
|
|
23
23
|
offsetY: 0,
|
|
24
24
|
});
|
|
25
|
-
|
|
25
|
+
// 2. High-Frequency Refs (Bypasses React Render Cycle)
|
|
26
|
+
const taskRefs = useRef(new Map());
|
|
27
|
+
const docRefs = useRef(new Map());
|
|
28
|
+
const localTasksRef = useRef(tasks);
|
|
29
|
+
const localDocsRef = useRef(documents);
|
|
30
|
+
const selectedIdsRef = useRef(new Set());
|
|
26
31
|
const rafIdRef = useRef(null);
|
|
27
32
|
const overlayRef = useRef(null);
|
|
28
|
-
const
|
|
29
|
-
const localDocumentsRef = useRef(localDocuments);
|
|
30
|
-
const selectedIdsRef = useRef(selectedIds);
|
|
33
|
+
// const selectedIdsRef = useRef<Set<string>>(selectedIds);
|
|
31
34
|
selectedIdsRef.current = selectedIds;
|
|
35
|
+
// Sync Refs immediately
|
|
32
36
|
localTasksRef.current = localTasks;
|
|
33
|
-
|
|
37
|
+
localDocsRef.current = localDocuments;
|
|
38
|
+
selectedIdsRef.current = selectedIds;
|
|
34
39
|
// ── Sync props → local state ────────────────────────────────────────────────
|
|
35
40
|
useEffect(() => { setLocalTasks(tasks); }, [tasks]);
|
|
36
41
|
useEffect(() => { setLocalDocuments(documents); }, [documents]);
|
|
@@ -113,6 +118,17 @@ export default function CanvasOverlayLayer({ tasks, documents, onTasksUpdate, on
|
|
|
113
118
|
};
|
|
114
119
|
}, [fabricCanvas, canvasZoom, canvasReady]); // Re-bind when zoom changes to keep closure fresh
|
|
115
120
|
// ── Fabric → Overlay Sync (Fixes Dragging from Fabric area) ──────────────────
|
|
121
|
+
const updateNodeStyles = useCallback((id, x, y, zoom, vp) => {
|
|
122
|
+
const el = taskRefs.current.get(id) || docRefs.current.get(id);
|
|
123
|
+
if (!el)
|
|
124
|
+
return;
|
|
125
|
+
// Calculate final screen position
|
|
126
|
+
const screenX = x * zoom + vp.x;
|
|
127
|
+
const screenY = y * zoom + vp.y;
|
|
128
|
+
// Update via CSS Variables or Direct Transform
|
|
129
|
+
// This is 10x faster than a React State update
|
|
130
|
+
el.style.transform = `translate3d(${screenX}px, ${screenY}px, 0) scale(${zoom})`;
|
|
131
|
+
}, []);
|
|
116
132
|
useEffect(() => {
|
|
117
133
|
const canvas = fabricCanvas?.current;
|
|
118
134
|
if (!canvas)
|
|
@@ -236,8 +252,8 @@ export default function CanvasOverlayLayer({ tasks, documents, onTasksUpdate, on
|
|
|
236
252
|
// 3. Determine which items are being dragged
|
|
237
253
|
// selection update DOES NOT trigger before drag snapshot
|
|
238
254
|
let itemsToDrag;
|
|
239
|
-
if (
|
|
240
|
-
itemsToDrag = Array.from(
|
|
255
|
+
if (selectedIds.has(itemId)) {
|
|
256
|
+
itemsToDrag = Array.from(selectedIds);
|
|
241
257
|
}
|
|
242
258
|
else {
|
|
243
259
|
itemsToDrag = [itemId];
|
|
@@ -281,7 +297,7 @@ export default function CanvasOverlayLayer({ tasks, documents, onTasksUpdate, on
|
|
|
281
297
|
offsetX: worldOffsetX, // Now stored as World Units
|
|
282
298
|
offsetY: worldOffsetY, // Now stored as World Units
|
|
283
299
|
};
|
|
284
|
-
if (!
|
|
300
|
+
if (!selectedIds.has(itemId) && dragStateRef.current.itemIds.length === 0) {
|
|
285
301
|
setSelectedIds(new Set([itemId]));
|
|
286
302
|
}
|
|
287
303
|
// 11. Trigger UI states
|
|
@@ -295,88 +311,48 @@ export default function CanvasOverlayLayer({ tasks, documents, onTasksUpdate, on
|
|
|
295
311
|
if (!dragging)
|
|
296
312
|
return;
|
|
297
313
|
// Inside the useEffect that watches [dragging, localTasks, localDocuments, fabricCanvas]
|
|
298
|
-
const handleMove = (e) => {
|
|
314
|
+
const handleMove = useCallback((e) => {
|
|
299
315
|
if (!dragStateRef.current.isDragging)
|
|
300
316
|
return;
|
|
301
|
-
if (e.cancelable)
|
|
302
|
-
e.preventDefault();
|
|
303
317
|
const pointer = getPointerEvent(e);
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
cancelAnimationFrame(rafIdRef.current);
|
|
307
|
-
rafIdRef.current = requestAnimationFrame(() => {
|
|
308
|
-
const { itemIds, startPositions, canvasObjectsStartPos, offsetX, offsetY } = dragStateRef.current;
|
|
318
|
+
requestAnimationFrame(() => {
|
|
319
|
+
const { itemIds, startPositions, offsetX, offsetY } = dragStateRef.current;
|
|
309
320
|
const canvas = fabricCanvas?.current;
|
|
310
321
|
if (!canvas)
|
|
311
322
|
return;
|
|
312
|
-
// 2. Read the "Source of Truth" transform from the canvas
|
|
313
323
|
const vpt = canvas.viewportTransform;
|
|
314
|
-
const liveZoom = vpt[0];
|
|
315
|
-
const liveVpX = vpt[4];
|
|
316
|
-
const liveVpY = vpt[5];
|
|
317
|
-
// 3. Convert current Mouse Screen Position → World Position
|
|
324
|
+
const liveZoom = vpt[0];
|
|
325
|
+
const liveVpX = vpt[4];
|
|
326
|
+
const liveVpY = vpt[5];
|
|
318
327
|
const currentWorldX = (pointer.clientX - liveVpX) / liveZoom;
|
|
319
328
|
const currentWorldY = (pointer.clientY - liveVpY) / liveZoom;
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
if (!firstStart)
|
|
329
|
-
return;
|
|
330
|
-
const deltaX = newWorldX - firstStart.x;
|
|
331
|
-
const deltaY = newWorldY - firstStart.y;
|
|
332
|
-
// 6. Update HTML Nodes (Batching these into one state update)
|
|
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;
|
|
329
|
+
const deltaX = (currentWorldX - offsetX) - (startPositions.get(itemIds[0])?.x ?? 0);
|
|
330
|
+
const deltaY = (currentWorldY - offsetY) - (startPositions.get(itemIds[0])?.y ?? 0);
|
|
331
|
+
// DOM UPDATE (Instant)
|
|
332
|
+
itemIds.forEach(id => {
|
|
333
|
+
const start = startPositions.get(id);
|
|
334
|
+
if (start) {
|
|
335
|
+
updateNodeStyles(id, start.x + deltaX, start.y + deltaY, liveZoom, { x: liveVpX, y: liveVpY });
|
|
336
|
+
}
|
|
348
337
|
});
|
|
349
|
-
//
|
|
350
|
-
canvasObjectsStartPos.forEach((
|
|
351
|
-
obj.set({
|
|
352
|
-
|
|
353
|
-
top: startPos.top + deltaY,
|
|
354
|
-
});
|
|
355
|
-
obj.setCoords(); // Required for selection/intersection accuracy
|
|
338
|
+
// FABRIC UPDATE (Batched)
|
|
339
|
+
dragStateRef.current.canvasObjectsStartPos.forEach((pos, obj) => {
|
|
340
|
+
obj.set({ left: pos.left + deltaX, top: pos.top + deltaY });
|
|
341
|
+
obj.setCoords();
|
|
356
342
|
});
|
|
357
|
-
finalPositionsRef.current = {
|
|
358
|
-
tasks: localTasksRef.current, // will be updated by React after setState flushes
|
|
359
|
-
documents: localDocumentsRef.current,
|
|
360
|
-
};
|
|
361
|
-
// 8. Single render call for all Fabric changes
|
|
362
343
|
canvas.requestRenderAll();
|
|
363
344
|
});
|
|
364
|
-
};
|
|
345
|
+
}, [fabricCanvas, updateNodeStyles]);
|
|
365
346
|
const handleEnd = () => {
|
|
366
|
-
if (rafIdRef.current !== null)
|
|
347
|
+
if (rafIdRef.current !== null)
|
|
367
348
|
cancelAnimationFrame(rafIdRef.current);
|
|
368
|
-
rafIdRef.current = null;
|
|
369
|
-
}
|
|
370
349
|
dragStateRef.current.isDragging = false;
|
|
371
350
|
setDragging(null);
|
|
372
351
|
document.body.style.cursor = "";
|
|
373
352
|
document.body.style.userSelect = "";
|
|
374
353
|
document.body.style.touchAction = "";
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
// the position after the last committed setState, not the t=0 snapshot.
|
|
378
|
-
onTasksUpdate?.(localTasksRef.current);
|
|
379
|
-
onDocumentsUpdate?.(localDocumentsRef.current);
|
|
354
|
+
onTasksUpdate?.(localTasks);
|
|
355
|
+
onDocumentsUpdate?.(localDocuments);
|
|
380
356
|
};
|
|
381
357
|
window.addEventListener("mousemove", handleMove, { passive: false });
|
|
382
358
|
window.addEventListener("mouseup", handleEnd);
|
|
@@ -438,27 +414,33 @@ export default function CanvasOverlayLayer({ tasks, documents, onTasksUpdate, on
|
|
|
438
414
|
window.addEventListener("keydown", handleKeyDown);
|
|
439
415
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
440
416
|
}, [localTasks, localDocuments, selectedIds, onTasksUpdate, onDocumentsUpdate]);
|
|
441
|
-
const getAbsoluteTransform = (x, y) => {
|
|
442
|
-
// We calculate the screen position in one go, matching Fabric's internal VPT logic
|
|
443
|
-
const screenX = x * canvasZoom + canvasViewport.x;
|
|
444
|
-
const screenY = y * canvasZoom + canvasViewport.y;
|
|
445
|
-
return `translate3d(${screenX}px, ${screenY}px, 0) scale(${canvasZoom})`;
|
|
446
|
-
};
|
|
447
417
|
// ── Render helper ────────────────────────────────────────────────────────────
|
|
448
418
|
const renderItem = (id, x, y, children) => {
|
|
419
|
+
const screenX = x * canvasZoom;
|
|
420
|
+
const screenY = y * canvasZoom;
|
|
421
|
+
// 1. Detect if the user is interacting with the canvas at all
|
|
422
|
+
// 'dragging' is your existing state.
|
|
423
|
+
// You might want to pass 'isZooming' or 'isPanning' from your main canvas component here.
|
|
449
424
|
const isDragging = dragging?.itemIds.includes(id);
|
|
450
425
|
return (_jsx("div", { className: "pointer-events-auto absolute", style: {
|
|
451
426
|
left: 0,
|
|
452
427
|
top: 0,
|
|
453
|
-
//
|
|
454
|
-
transform:
|
|
428
|
+
// 2. Use translate3d for GPU performance
|
|
429
|
+
transform: `translate3d(${screenX}px, ${screenY}px, 0) scale(${canvasZoom})`,
|
|
455
430
|
transformOrigin: "top left",
|
|
456
|
-
|
|
431
|
+
// 3. THE FIX: Remove transition entirely during any viewport change
|
|
432
|
+
// Any 'ease' during zoom causes the "shaking" behavior.
|
|
433
|
+
transition: "none",
|
|
434
|
+
// 4. Optimization
|
|
457
435
|
willChange: "transform",
|
|
458
436
|
zIndex: isDragging ? 1000 : 1,
|
|
459
437
|
}, children: children }, id));
|
|
460
438
|
};
|
|
461
|
-
return (
|
|
462
|
-
|
|
463
|
-
|
|
439
|
+
return (_jsx("div", { ref: overlayRef, className: "absolute inset-0 pointer-events-none", style: { zIndex: 50 }, onWheel: handleOverlayWheel, onClick: (e) => {
|
|
440
|
+
if (e.target === e.currentTarget)
|
|
441
|
+
setSelectedIds(new Set());
|
|
442
|
+
}, children: _jsxs("div", { className: "absolute top-0 left-0 pointer-events-none", style: {
|
|
443
|
+
transform: `translate(${canvasViewport.x}px, ${canvasViewport.y}px)`,
|
|
444
|
+
transformOrigin: "top left",
|
|
445
|
+
}, children: [localTasks.map((task) => renderItem(task.id, task.x, task.y, _jsx(TaskNode, { ...task, isSelected: selectedIds.has(task.id), onSelect: handleSelect, onDragStart: handleDragStart, onStatusChange: handleStatusChange, zoom: 1 }))), localDocuments.map((doc) => renderItem(doc.id, doc.x, doc.y, _jsx(DocumentNode, { ...doc, isSelected: selectedIds.has(doc.id), onSelect: handleSelect, onDragStart: handleDragStart })))] }) }));
|
|
464
446
|
}
|