@mhamz.01/easyflow-whiteboard 2.58.0 → 2.60.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,2CA4nBzB"}
|
|
@@ -10,6 +10,10 @@ export default function CanvasOverlayLayer({ tasks, documents, onTasksUpdate, on
|
|
|
10
10
|
const [selectedIds, setSelectedIds] = useState(new Set());
|
|
11
11
|
const [dragging, setDragging] = useState(null);
|
|
12
12
|
const [canvasReady, setCanvasReady] = useState(false);
|
|
13
|
+
const nodeClipboardRef = useRef({
|
|
14
|
+
tasks: [],
|
|
15
|
+
documents: [],
|
|
16
|
+
});
|
|
13
17
|
const dragStateRef = useRef({
|
|
14
18
|
isDragging: false,
|
|
15
19
|
itemIds: [],
|
|
@@ -395,6 +399,59 @@ export default function CanvasOverlayLayer({ tasks, documents, onTasksUpdate, on
|
|
|
395
399
|
e.preventDefault();
|
|
396
400
|
setSelectedIds(new Set([...localTasks.map((t) => t.id), ...localDocuments.map((d) => d.id)]));
|
|
397
401
|
}
|
|
402
|
+
// ── Copy HTML nodes ──────────────────────────────────────────────────────
|
|
403
|
+
if ((e.ctrlKey || e.metaKey) && e.key === "c") {
|
|
404
|
+
const ids = selectedIdsRef.current;
|
|
405
|
+
if (ids.size === 0)
|
|
406
|
+
return;
|
|
407
|
+
// Snapshot selected nodes into clipboard — plain data, no refs
|
|
408
|
+
nodeClipboardRef.current = {
|
|
409
|
+
tasks: localTasks.filter((t) => ids.has(t.id)),
|
|
410
|
+
documents: localDocuments.filter((d) => ids.has(d.id)),
|
|
411
|
+
};
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
// ── Paste HTML nodes ─────────────────────────────────────────────────────
|
|
415
|
+
if ((e.ctrlKey || e.metaKey) && e.key === "v") {
|
|
416
|
+
const { tasks: copiedTasks, documents: copiedDocs } = nodeClipboardRef.current;
|
|
417
|
+
if (copiedTasks.length === 0 && copiedDocs.length === 0)
|
|
418
|
+
return;
|
|
419
|
+
const now = Date.now();
|
|
420
|
+
const pastedTasks = copiedTasks.map((t, i) => ({
|
|
421
|
+
...t,
|
|
422
|
+
id: `${t.id}-copy-${now}-${i}`,
|
|
423
|
+
x: t.x + 20,
|
|
424
|
+
y: t.y + 20,
|
|
425
|
+
}));
|
|
426
|
+
const pastedDocs = copiedDocs.map((d, i) => ({
|
|
427
|
+
...d,
|
|
428
|
+
id: `${d.id}-copy-${now}-${i}`,
|
|
429
|
+
x: d.x + 20,
|
|
430
|
+
y: d.y + 20,
|
|
431
|
+
}));
|
|
432
|
+
setLocalTasks((prev) => {
|
|
433
|
+
const updated = [...prev, ...pastedTasks];
|
|
434
|
+
onTasksUpdate?.(updated);
|
|
435
|
+
return updated;
|
|
436
|
+
});
|
|
437
|
+
setLocalDocuments((prev) => {
|
|
438
|
+
const updated = [...prev, ...pastedDocs];
|
|
439
|
+
onDocumentsUpdate?.(updated);
|
|
440
|
+
return updated;
|
|
441
|
+
});
|
|
442
|
+
// Select the newly pasted nodes
|
|
443
|
+
setSelectedIds(new Set([
|
|
444
|
+
...pastedTasks.map((t) => t.id),
|
|
445
|
+
...pastedDocs.map((d) => d.id),
|
|
446
|
+
]));
|
|
447
|
+
// Mirror official Fabric pattern — offset clipboard itself for cascading paste
|
|
448
|
+
// so each Ctrl+V lands 20px further than the last
|
|
449
|
+
nodeClipboardRef.current = {
|
|
450
|
+
tasks: copiedTasks.map((t) => ({ ...t, x: t.x + 20, y: t.y + 20 })),
|
|
451
|
+
documents: copiedDocs.map((d) => ({ ...d, x: d.x + 20, y: d.y + 20 })),
|
|
452
|
+
};
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
398
455
|
// Clear selection
|
|
399
456
|
if (e.key === "Escape") {
|
|
400
457
|
setSelectedIds(new Set());
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useCopyPaste.d.ts","sourceRoot":"","sources":["../../src/hooks/useCopyPaste.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,SAAS,EAAE,MAAM,OAAO,CAAC;AACrD,OAAO,EAAE,MAAM,EAAiC,MAAM,QAAQ,CAAC;AAE/D,UAAU,iBAAiB;IACzB,YAAY,EAAE,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CACxC;AAED,eAAO,MAAM,YAAY,GAAI,kBAAkB,iBAAiB,
|
|
1
|
+
{"version":3,"file":"useCopyPaste.d.ts","sourceRoot":"","sources":["../../src/hooks/useCopyPaste.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,SAAS,EAAE,MAAM,OAAO,CAAC;AACrD,OAAO,EAAE,MAAM,EAAiC,MAAM,QAAQ,CAAC;AAE/D,UAAU,iBAAiB;IACzB,YAAY,EAAE,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CACxC;AAED,eAAO,MAAM,YAAY,GAAI,kBAAkB,iBAAiB,SAmE/D,CAAC"}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { useEffect, useRef } from "react";
|
|
2
2
|
import { ActiveSelection } from "fabric";
|
|
3
3
|
export const useCopyPaste = ({ fabricCanvas }) => {
|
|
4
|
-
|
|
5
|
-
const clipboardRef = useRef([]);
|
|
4
|
+
const clipboardRef = useRef(null);
|
|
6
5
|
useEffect(() => {
|
|
7
6
|
const handleKeyDown = async (e) => {
|
|
8
7
|
if (e.target instanceof HTMLInputElement ||
|
|
@@ -16,67 +15,45 @@ export const useCopyPaste = ({ fabricCanvas }) => {
|
|
|
16
15
|
const active = canvas.getActiveObject();
|
|
17
16
|
if (!active)
|
|
18
17
|
return;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const clones = await Promise.all(active.getObjects().map((obj) => obj.clone()));
|
|
23
|
-
// Snapshot each child's ABSOLUTE canvas position at copy time
|
|
24
|
-
// getObjects() inside activeSelection returns group-relative coords,
|
|
25
|
-
// so we must add the group's position to get true canvas coords
|
|
26
|
-
const groupLeft = active.left ?? 0;
|
|
27
|
-
const groupTop = active.top ?? 0;
|
|
28
|
-
const groupAngle = active.angle ?? 0;
|
|
29
|
-
clones.forEach((clone, i) => {
|
|
30
|
-
const src = active.getObjects()[i];
|
|
31
|
-
clone.set({
|
|
32
|
-
left: (src.left ?? 0) + groupLeft,
|
|
33
|
-
top: (src.top ?? 0) + groupTop,
|
|
34
|
-
angle: (src.angle ?? 0) + groupAngle,
|
|
35
|
-
});
|
|
36
|
-
clone.setCoords();
|
|
37
|
-
});
|
|
38
|
-
clipboardRef.current = clones;
|
|
39
|
-
}
|
|
40
|
-
else {
|
|
41
|
-
// Single object — clone detached
|
|
42
|
-
const clone = await active.clone();
|
|
43
|
-
clipboardRef.current = [clone];
|
|
44
|
-
}
|
|
18
|
+
// Clone once at copy time — store as-is, exactly like official docs
|
|
19
|
+
const cloned = await active.clone();
|
|
20
|
+
clipboardRef.current = cloned;
|
|
45
21
|
return;
|
|
46
22
|
}
|
|
47
23
|
// ── Paste ────────────────────────────────────────────────────────────
|
|
48
24
|
if ((e.ctrlKey || e.metaKey) && e.key === "v") {
|
|
49
|
-
|
|
25
|
+
const copied = clipboardRef.current;
|
|
26
|
+
if (!copied)
|
|
50
27
|
return;
|
|
28
|
+
// Clone again so multiple pastes work independently
|
|
29
|
+
const clonedObj = await copied.clone();
|
|
51
30
|
canvas.discardActiveObject();
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
31
|
+
clonedObj.set({
|
|
32
|
+
left: clonedObj.left + 10,
|
|
33
|
+
top: clonedObj.top + 10,
|
|
34
|
+
evented: true,
|
|
35
|
+
});
|
|
36
|
+
if (clonedObj instanceof ActiveSelection) {
|
|
37
|
+
// Official docs pattern exactly:
|
|
38
|
+
// set canvas reference on the ActiveSelection FIRST,
|
|
39
|
+
// then use forEachObject to add children — not getObjects()
|
|
40
|
+
// forEachObject is the correct API for iterating ActiveSelection
|
|
41
|
+
clonedObj.canvas = canvas;
|
|
42
|
+
clonedObj.forEachObject((obj) => {
|
|
43
|
+
canvas.add(obj);
|
|
63
44
|
});
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
addedObjects.push(clone);
|
|
67
|
-
}
|
|
68
|
-
if (addedObjects.length === 1) {
|
|
69
|
-
canvas.setActiveObject(addedObjects[0]);
|
|
45
|
+
// setCoords on the selection itself — not on individual children
|
|
46
|
+
clonedObj.setCoords();
|
|
70
47
|
}
|
|
71
48
|
else {
|
|
72
|
-
|
|
73
|
-
canvas.setActiveObject(newSelection);
|
|
49
|
+
canvas.add(clonedObj);
|
|
74
50
|
}
|
|
51
|
+
// Official docs pattern: offset clipboard itself for cascading paste
|
|
52
|
+
// This is simpler and safer than storing new clones as clipboard
|
|
53
|
+
copied.top += 10;
|
|
54
|
+
copied.left += 10;
|
|
55
|
+
canvas.setActiveObject(clonedObj);
|
|
75
56
|
canvas.requestRenderAll();
|
|
76
|
-
// Update clipboard with the newly pasted clones (detached copies)
|
|
77
|
-
// so next Ctrl+V offsets from current paste position
|
|
78
|
-
// CRITICAL: clone them immediately so they're detached from canvas
|
|
79
|
-
clipboardRef.current = await Promise.all(addedObjects.map((obj) => obj.clone()));
|
|
80
57
|
}
|
|
81
58
|
};
|
|
82
59
|
window.addEventListener("keydown", handleKeyDown);
|