@mhamz.01/easyflow-whiteboard 2.71.0 → 2.73.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,2CAikBzB"}
|
|
@@ -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
|
|
3
|
+
import { useState, useEffect, useRef } from "react";
|
|
4
4
|
import TaskNode from "./custom-node";
|
|
5
5
|
import DocumentNode from "./document-node";
|
|
6
6
|
// ─── Component ────────────────────────────────────────────────────────────────
|
|
@@ -22,19 +22,9 @@ export default function CanvasOverlayLayer({ tasks, documents, onTasksUpdate, on
|
|
|
22
22
|
offsetX: 0,
|
|
23
23
|
offsetY: 0,
|
|
24
24
|
});
|
|
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());
|
|
31
25
|
const rafIdRef = useRef(null);
|
|
32
26
|
const overlayRef = useRef(null);
|
|
33
|
-
|
|
34
|
-
selectedIdsRef.current = selectedIds;
|
|
35
|
-
// Sync Refs immediately
|
|
36
|
-
localTasksRef.current = localTasks;
|
|
37
|
-
localDocsRef.current = localDocuments;
|
|
27
|
+
const selectedIdsRef = useRef(selectedIds);
|
|
38
28
|
selectedIdsRef.current = selectedIds;
|
|
39
29
|
// ── Sync props → local state ────────────────────────────────────────────────
|
|
40
30
|
useEffect(() => { setLocalTasks(tasks); }, [tasks]);
|
|
@@ -118,17 +108,6 @@ export default function CanvasOverlayLayer({ tasks, documents, onTasksUpdate, on
|
|
|
118
108
|
};
|
|
119
109
|
}, [fabricCanvas, canvasZoom, canvasReady]); // Re-bind when zoom changes to keep closure fresh
|
|
120
110
|
// ── 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
|
-
}, []);
|
|
132
111
|
useEffect(() => {
|
|
133
112
|
const canvas = fabricCanvas?.current;
|
|
134
113
|
if (!canvas)
|
|
@@ -311,38 +290,63 @@ export default function CanvasOverlayLayer({ tasks, documents, onTasksUpdate, on
|
|
|
311
290
|
if (!dragging)
|
|
312
291
|
return;
|
|
313
292
|
// Inside the useEffect that watches [dragging, localTasks, localDocuments, fabricCanvas]
|
|
314
|
-
const handleMove =
|
|
293
|
+
const handleMove = (e) => {
|
|
315
294
|
if (!dragStateRef.current.isDragging)
|
|
316
295
|
return;
|
|
296
|
+
if (e.cancelable)
|
|
297
|
+
e.preventDefault();
|
|
317
298
|
const pointer = getPointerEvent(e);
|
|
318
|
-
requestAnimationFrame
|
|
319
|
-
|
|
299
|
+
// 1. Throttle updates using requestAnimationFrame for 120Hz/144Hz screen support
|
|
300
|
+
if (rafIdRef.current !== null)
|
|
301
|
+
cancelAnimationFrame(rafIdRef.current);
|
|
302
|
+
rafIdRef.current = requestAnimationFrame(() => {
|
|
303
|
+
const { itemIds, startPositions, canvasObjectsStartPos, offsetX, offsetY } = dragStateRef.current;
|
|
320
304
|
const canvas = fabricCanvas?.current;
|
|
321
305
|
if (!canvas)
|
|
322
306
|
return;
|
|
307
|
+
// 2. Read the "Source of Truth" transform from the canvas
|
|
323
308
|
const vpt = canvas.viewportTransform;
|
|
324
|
-
const liveZoom = vpt[0];
|
|
325
|
-
const liveVpX = vpt[4];
|
|
326
|
-
const liveVpY = vpt[5];
|
|
309
|
+
const liveZoom = vpt[0]; // Scale
|
|
310
|
+
const liveVpX = vpt[4]; // Pan X
|
|
311
|
+
const liveVpY = vpt[5]; // Pan Y
|
|
312
|
+
// 3. Convert current Mouse Screen Position → World Position
|
|
327
313
|
const currentWorldX = (pointer.clientX - liveVpX) / liveZoom;
|
|
328
314
|
const currentWorldY = (pointer.clientY - liveVpY) / liveZoom;
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
315
|
+
// 4. Calculate where the "Anchor" node should be in World Units
|
|
316
|
+
// (Current Mouse World - Initial World Offset from Start)
|
|
317
|
+
const newWorldX = currentWorldX - offsetX;
|
|
318
|
+
const newWorldY = currentWorldY - offsetY;
|
|
319
|
+
// 5. Calculate the Movement Delta in World Units
|
|
320
|
+
// We compare where the first item started vs where it is now.
|
|
321
|
+
const firstId = itemIds[0];
|
|
322
|
+
const firstStart = startPositions.get(firstId);
|
|
323
|
+
if (!firstStart)
|
|
324
|
+
return;
|
|
325
|
+
const deltaX = newWorldX - firstStart.x;
|
|
326
|
+
const deltaY = newWorldY - firstStart.y;
|
|
327
|
+
// 6. Update HTML Nodes (Batching these into one state update)
|
|
328
|
+
setLocalTasks((prev) => prev.map((t) => itemIds.includes(t.id) ? {
|
|
329
|
+
...t,
|
|
330
|
+
x: (startPositions.get(t.id)?.x ?? t.x) + deltaX,
|
|
331
|
+
y: (startPositions.get(t.id)?.y ?? t.y) + deltaY,
|
|
332
|
+
} : t));
|
|
333
|
+
setLocalDocuments((prev) => prev.map((d) => itemIds.includes(d.id) ? {
|
|
334
|
+
...d,
|
|
335
|
+
x: (startPositions.get(d.id)?.x ?? d.x) + deltaX,
|
|
336
|
+
y: (startPositions.get(d.id)?.y ?? d.y) + deltaY,
|
|
337
|
+
} : d));
|
|
338
|
+
// 7. Sync Fabric Objects (Imperative update for performance)
|
|
339
|
+
canvasObjectsStartPos.forEach((startPos, obj) => {
|
|
340
|
+
obj.set({
|
|
341
|
+
left: startPos.left + deltaX,
|
|
342
|
+
top: startPos.top + deltaY,
|
|
343
|
+
});
|
|
344
|
+
obj.setCoords(); // Required for selection/intersection accuracy
|
|
342
345
|
});
|
|
346
|
+
// 8. Single render call for all Fabric changes
|
|
343
347
|
canvas.requestRenderAll();
|
|
344
348
|
});
|
|
345
|
-
}
|
|
349
|
+
};
|
|
346
350
|
const handleEnd = () => {
|
|
347
351
|
if (rafIdRef.current !== null)
|
|
348
352
|
cancelAnimationFrame(rafIdRef.current);
|
|
@@ -6,5 +6,5 @@ import { Slider } from "../../../components/ui/slider";
|
|
|
6
6
|
export default function ImageOptions() {
|
|
7
7
|
const toolOptions = useWhiteboardStore((state) => state.toolOptions);
|
|
8
8
|
const setToolOption = useWhiteboardStore((state) => state.setToolOption);
|
|
9
|
-
return (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx(Label, { className: "text-xs text-neutral-400", children: "Opacity" }), _jsxs("span", { className: "text-xs text-neutral-500", children: [Math.round(toolOptions.image.opacity * 100), "%"] })] }), _jsx(Slider, { value: [toolOptions.image.opacity], min: 0, max:
|
|
9
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx(Label, { className: "text-xs text-neutral-400", children: "Opacity" }), _jsxs("span", { className: "text-xs text-neutral-500", children: [Math.round(toolOptions.image.opacity * 100), "%"] })] }), _jsx(Slider, { value: [toolOptions.image.opacity], min: 0, max: 100, step: 1, onValueChange: ([value]) => setToolOption("image", "opacity", value), className: "w-full" })] }), _jsxs("div", { className: "mt-4 p-3 bg-neutral-800/50 rounded-lg border border-neutral-700", children: [_jsxs("p", { className: "text-xs text-neutral-400", children: ["Click the ", _jsx("strong", { className: "text-neutral-200", children: "Image" }), " button to upload an image from your computer."] }), _jsx("p", { className: "text-xs text-neutral-500 mt-2", children: "Supported formats: JPG, PNG, GIF, WebP" })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { className: "text-xs text-neutral-400", children: "Tips" }), _jsxs("ul", { className: "text-xs text-neutral-500 space-y-1 list-disc list-inside", children: [_jsx("li", { children: "Drag corners to resize" }), _jsx("li", { children: "Click and drag to move" }), _jsx("li", { children: "Use Delete key to remove" }), _jsx("li", { children: "Double-click to edit properties" })] })] })] }));
|
|
10
10
|
}
|