@mhamz.01/easyflow-whiteboard 1.0.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.
- package/dist/components/node/custom-node-overlay-layer.d.ts +44 -0
- package/dist/components/node/custom-node-overlay-layer.d.ts.map +1 -0
- package/dist/components/node/custom-node-overlay-layer.js +353 -0
- package/dist/components/node/custom-node.d.ts +17 -0
- package/dist/components/node/custom-node.d.ts.map +1 -0
- package/dist/components/node/custom-node.js +63 -0
- package/dist/components/node/document-node.d.ts +14 -0
- package/dist/components/node/document-node.d.ts.map +1 -0
- package/dist/components/node/document-node.js +58 -0
- package/dist/components/toolbar/document-dropdown.d.ts +14 -0
- package/dist/components/toolbar/document-dropdown.d.ts.map +1 -0
- package/dist/components/toolbar/document-dropdown.js +66 -0
- package/dist/components/toolbar/options/arrow-options.d.ts +8 -0
- package/dist/components/toolbar/options/arrow-options.d.ts.map +1 -0
- package/dist/components/toolbar/options/arrow-options.js +109 -0
- package/dist/components/toolbar/options/erase-option.d.ts +2 -0
- package/dist/components/toolbar/options/erase-option.d.ts.map +1 -0
- package/dist/components/toolbar/options/erase-option.js +20 -0
- package/dist/components/toolbar/options/image-options.d.ts +2 -0
- package/dist/components/toolbar/options/image-options.d.ts.map +1 -0
- package/dist/components/toolbar/options/image-options.js +10 -0
- package/dist/components/toolbar/options/line-options.d.ts +2 -0
- package/dist/components/toolbar/options/line-options.d.ts.map +1 -0
- package/dist/components/toolbar/options/line-options.js +46 -0
- package/dist/components/toolbar/options/pen-option.d.ts +2 -0
- package/dist/components/toolbar/options/pen-option.d.ts.map +1 -0
- package/dist/components/toolbar/options/pen-option.js +53 -0
- package/dist/components/toolbar/options/shape-option.d.ts +6 -0
- package/dist/components/toolbar/options/shape-option.d.ts.map +1 -0
- package/dist/components/toolbar/options/shape-option.js +58 -0
- package/dist/components/toolbar/options/text-option.d.ts +2 -0
- package/dist/components/toolbar/options/text-option.d.ts.map +1 -0
- package/dist/components/toolbar/options/text-option.js +73 -0
- package/dist/components/toolbar/task-dropdown.d.ts +15 -0
- package/dist/components/toolbar/task-dropdown.d.ts.map +1 -0
- package/dist/components/toolbar/task-dropdown.js +85 -0
- package/dist/components/toolbar/toolbar-button.d.ts +12 -0
- package/dist/components/toolbar/toolbar-button.d.ts.map +1 -0
- package/dist/components/toolbar/toolbar-button.js +8 -0
- package/dist/components/toolbar/toolbar-seperator.d.ts +6 -0
- package/dist/components/toolbar/toolbar-seperator.d.ts.map +1 -0
- package/dist/components/toolbar/toolbar-seperator.js +5 -0
- package/dist/components/toolbar/tooloptions-panel.d.ts +8 -0
- package/dist/components/toolbar/tooloptions-panel.d.ts.map +1 -0
- package/dist/components/toolbar/tooloptions-panel.js +88 -0
- package/dist/components/toolbar/whiteboard-toolbar.d.ts +28 -0
- package/dist/components/toolbar/whiteboard-toolbar.d.ts.map +1 -0
- package/dist/components/toolbar/whiteboard-toolbar.js +160 -0
- package/dist/components/ui/dropdown-menu.d.ts +26 -0
- package/dist/components/ui/dropdown-menu.d.ts.map +1 -0
- package/dist/components/ui/dropdown-menu.js +51 -0
- package/dist/components/ui/label.d.ts +5 -0
- package/dist/components/ui/label.d.ts.map +1 -0
- package/dist/components/ui/label.js +8 -0
- package/dist/components/ui/slider.d.ts +5 -0
- package/dist/components/ui/slider.d.ts.map +1 -0
- package/dist/components/ui/slider.js +14 -0
- package/dist/components/whiteboard/whiteboard-test.d.ts +2 -0
- package/dist/components/whiteboard/whiteboard-test.d.ts.map +1 -0
- package/dist/components/whiteboard/whiteboard-test.js +207 -0
- package/dist/components/whiteboard/whiteboard.d.ts +1 -0
- package/dist/components/whiteboard/whiteboard.d.ts.map +1 -0
- package/dist/components/whiteboard/whiteboard.js +911 -0
- package/dist/components/zoomcontrol/zoom-control.d.ts +9 -0
- package/dist/components/zoomcontrol/zoom-control.d.ts.map +1 -0
- package/dist/components/zoomcontrol/zoom-control.js +7 -0
- package/dist/hooks/useCanvasInit.d.ts +15 -0
- package/dist/hooks/useCanvasInit.d.ts.map +1 -0
- package/dist/hooks/useCanvasInit.js +89 -0
- package/dist/hooks/useDrawing.d.ts +23 -0
- package/dist/hooks/useDrawing.d.ts.map +1 -0
- package/dist/hooks/useDrawing.js +142 -0
- package/dist/hooks/useEraser.d.ts +27 -0
- package/dist/hooks/useEraser.d.ts.map +1 -0
- package/dist/hooks/useEraser.js +143 -0
- package/dist/hooks/useLiveUpdate.d.ts +9 -0
- package/dist/hooks/useLiveUpdate.d.ts.map +1 -0
- package/dist/hooks/useLiveUpdate.js +63 -0
- package/dist/hooks/useMouseHandlers.d.ts +25 -0
- package/dist/hooks/useMouseHandlers.d.ts.map +1 -0
- package/dist/hooks/useMouseHandlers.js +44 -0
- package/dist/hooks/usePan.d.ts +17 -0
- package/dist/hooks/usePan.d.ts.map +1 -0
- package/dist/hooks/usePan.js +80 -0
- package/dist/hooks/usePersistance.d.ts +13 -0
- package/dist/hooks/usePersistance.d.ts.map +1 -0
- package/dist/hooks/usePersistance.js +79 -0
- package/dist/hooks/useSelection.d.ts +21 -0
- package/dist/hooks/useSelection.d.ts.map +1 -0
- package/dist/hooks/useSelection.js +142 -0
- package/dist/hooks/useTextStyle.d.ts +9 -0
- package/dist/hooks/useTextStyle.d.ts.map +1 -0
- package/dist/hooks/useTextStyle.js +32 -0
- package/dist/hooks/useToolManager.d.ts +15 -0
- package/dist/hooks/useToolManager.d.ts.map +1 -0
- package/dist/hooks/useToolManager.js +115 -0
- package/dist/hooks/useZoom.d.ts +25 -0
- package/dist/hooks/useZoom.d.ts.map +1 -0
- package/dist/hooks/useZoom.js +133 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -0
- package/dist/lib/eraser-brush.d.ts +1 -0
- package/dist/lib/eraser-brush.d.ts.map +1 -0
- package/dist/lib/eraser-brush.js +21 -0
- package/dist/lib/fabric-arrow.d.ts +16 -0
- package/dist/lib/fabric-arrow.d.ts.map +1 -0
- package/dist/lib/fabric-arrow.js +50 -0
- package/dist/lib/fabric-bidirectional-arrow.d.ts +20 -0
- package/dist/lib/fabric-bidirectional-arrow.d.ts.map +1 -0
- package/dist/lib/fabric-bidirectional-arrow.js +65 -0
- package/dist/lib/fabric-frame.d.ts +7 -0
- package/dist/lib/fabric-frame.d.ts.map +1 -0
- package/dist/lib/fabric-frame.js +25 -0
- package/dist/lib/fabric-utils.d.ts +30 -0
- package/dist/lib/fabric-utils.d.ts.map +1 -0
- package/dist/lib/fabric-utils.js +273 -0
- package/dist/lib/utils.d.ts +3 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +5 -0
- package/dist/store/whiteboard-store.d.ts +99 -0
- package/dist/store/whiteboard-store.d.ts.map +1 -0
- package/dist/store/whiteboard-store.js +137 -0
- package/dist/types/canvas-node.d.ts +24 -0
- package/dist/types/canvas-node.d.ts.map +1 -0
- package/dist/types/canvas-node.js +1 -0
- package/package.json +34 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
export const usePan = ({ fabricCanvas, activeTool, handleZoom, setCanvasViewport, }) => {
|
|
3
|
+
useEffect(() => {
|
|
4
|
+
const canvas = fabricCanvas.current;
|
|
5
|
+
if (!canvas)
|
|
6
|
+
return;
|
|
7
|
+
const canvasEl = canvas.getElement();
|
|
8
|
+
canvasEl.style.touchAction = "none";
|
|
9
|
+
let isPanning = false;
|
|
10
|
+
let lastX = 0;
|
|
11
|
+
let lastY = 0;
|
|
12
|
+
let lastTouchDistance = 0;
|
|
13
|
+
const onDown = (opt) => {
|
|
14
|
+
if (activeTool !== "pan")
|
|
15
|
+
return;
|
|
16
|
+
const e = opt.e;
|
|
17
|
+
// Pinch initialization
|
|
18
|
+
if (e.touches && e.touches.length === 2) {
|
|
19
|
+
isPanning = false;
|
|
20
|
+
lastTouchDistance = Math.hypot(e.touches[0].clientX - e.touches[1].clientX, e.touches[0].clientY - e.touches[1].clientY);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
// Pan initialization
|
|
24
|
+
const pointer = e.touches ? e.touches[0] : e;
|
|
25
|
+
isPanning = true;
|
|
26
|
+
lastX = pointer.clientX;
|
|
27
|
+
lastY = pointer.clientY;
|
|
28
|
+
canvas.setCursor("grabbing");
|
|
29
|
+
};
|
|
30
|
+
const onMove = (opt) => {
|
|
31
|
+
if (activeTool !== "pan")
|
|
32
|
+
return;
|
|
33
|
+
const e = opt.e;
|
|
34
|
+
// Handle pinch zoom (two fingers)
|
|
35
|
+
if (e.touches && e.touches.length === 2) {
|
|
36
|
+
const currentDistance = Math.hypot(e.touches[0].clientX - e.touches[1].clientX, e.touches[0].clientY - e.touches[1].clientY);
|
|
37
|
+
if (lastTouchDistance > 0) {
|
|
38
|
+
const zoom = canvas.getZoom();
|
|
39
|
+
const delta = (currentDistance - lastTouchDistance) * 0.01;
|
|
40
|
+
const newZoom = zoom + delta;
|
|
41
|
+
const midX = (e.touches[0].clientX + e.touches[1].clientX) / 2;
|
|
42
|
+
const midY = (e.touches[0].clientY + e.touches[1].clientY) / 2;
|
|
43
|
+
const rect = canvasEl.getBoundingClientRect();
|
|
44
|
+
handleZoom(newZoom, { x: midX - rect.left, y: midY - rect.top });
|
|
45
|
+
}
|
|
46
|
+
lastTouchDistance = currentDistance;
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
// Handle panning (one finger or mouse)
|
|
50
|
+
if (isPanning) {
|
|
51
|
+
const pointer = e.touches ? e.touches[0] : e;
|
|
52
|
+
const vpt = canvas.viewportTransform;
|
|
53
|
+
if (vpt) {
|
|
54
|
+
vpt[4] += pointer.clientX - lastX;
|
|
55
|
+
vpt[5] += pointer.clientY - lastY;
|
|
56
|
+
canvas.requestRenderAll();
|
|
57
|
+
}
|
|
58
|
+
lastX = pointer.clientX;
|
|
59
|
+
lastY = pointer.clientY;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
const onUp = () => {
|
|
63
|
+
const vpt = canvas.viewportTransform;
|
|
64
|
+
if (vpt) {
|
|
65
|
+
setCanvasViewport({ x: vpt[4], y: vpt[5] });
|
|
66
|
+
}
|
|
67
|
+
isPanning = false;
|
|
68
|
+
lastTouchDistance = 0;
|
|
69
|
+
canvas.setCursor(activeTool === "pan" ? "grab" : "default");
|
|
70
|
+
};
|
|
71
|
+
canvas.on("mouse:down", onDown);
|
|
72
|
+
canvas.on("mouse:move", onMove);
|
|
73
|
+
canvas.on("mouse:up", onUp);
|
|
74
|
+
return () => {
|
|
75
|
+
canvas.off("mouse:down", onDown);
|
|
76
|
+
canvas.off("mouse:move", onMove);
|
|
77
|
+
canvas.off("mouse:up", onUp);
|
|
78
|
+
};
|
|
79
|
+
}, [activeTool, handleZoom, setCanvasViewport, fabricCanvas]);
|
|
80
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Canvas } from "fabric";
|
|
3
|
+
interface UsePersistenceProps {
|
|
4
|
+
fabricCanvas: React.RefObject<Canvas | null>;
|
|
5
|
+
tasks: any[];
|
|
6
|
+
documents: any[];
|
|
7
|
+
pushHistory: (state: string) => void;
|
|
8
|
+
isRestoringRef: React.RefObject<boolean>;
|
|
9
|
+
suppressHistoryRef: React.RefObject<boolean>;
|
|
10
|
+
}
|
|
11
|
+
export declare const usePersistence: ({ fabricCanvas, tasks, documents, pushHistory, isRestoringRef, suppressHistoryRef, }: UsePersistenceProps) => void;
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=usePersistance.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usePersistance.d.ts","sourceRoot":"","sources":["../../src/hooks/usePersistance.ts"],"names":[],"mappings":"AAAA,OAAO,KAAgC,MAAM,OAAO,CAAC;AACrD,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,UAAU,mBAAmB;IAC3B,YAAY,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC7C,KAAK,EAAE,GAAG,EAAE,CAAC;IACb,SAAS,EAAE,GAAG,EAAE,CAAC;IACjB,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,cAAc,EAAE,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IACzC,kBAAkB,EAAE,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;CAC9C;AAKD,eAAO,MAAM,cAAc,GAAI,sFAO5B,mBAAmB,SAgFrB,CAAC"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { useEffect, useCallback } from "react";
|
|
2
|
+
const CANVAS_KEY = "easyflow_whiteboard_canvas";
|
|
3
|
+
const NODES_KEY = "easyflow_whiteboard_nodes";
|
|
4
|
+
export const usePersistence = ({ fabricCanvas, tasks, documents, pushHistory, isRestoringRef, suppressHistoryRef, }) => {
|
|
5
|
+
// ── SAVE CANVAS LOGIC ────────────────────────────────────────────────
|
|
6
|
+
const saveCanvas = useCallback(() => {
|
|
7
|
+
const canvas = fabricCanvas.current;
|
|
8
|
+
if (!canvas || isRestoringRef.current || suppressHistoryRef.current)
|
|
9
|
+
return;
|
|
10
|
+
const performSave = () => {
|
|
11
|
+
try {
|
|
12
|
+
const json = JSON.stringify(canvas.toJSON());
|
|
13
|
+
localStorage.setItem(CANVAS_KEY, json);
|
|
14
|
+
pushHistory(json);
|
|
15
|
+
console.log("💾 Canvas saved via requestIdleCallback");
|
|
16
|
+
}
|
|
17
|
+
catch (err) {
|
|
18
|
+
console.error("Canvas save failed:", err);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
// Use requestIdleCallback for zero-lag background processing
|
|
22
|
+
if (typeof window.requestIdleCallback === "function") {
|
|
23
|
+
window.requestIdleCallback(performSave);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
performSave();
|
|
27
|
+
}
|
|
28
|
+
}, [pushHistory, fabricCanvas, isRestoringRef, suppressHistoryRef]);
|
|
29
|
+
// ── SAVE NODES LOGIC ─────────────────────────────────────────────────
|
|
30
|
+
const saveNodes = useCallback(() => {
|
|
31
|
+
if (isRestoringRef.current)
|
|
32
|
+
return;
|
|
33
|
+
const performSave = () => {
|
|
34
|
+
try {
|
|
35
|
+
const nodesData = { tasks, documents };
|
|
36
|
+
localStorage.setItem(NODES_KEY, JSON.stringify(nodesData));
|
|
37
|
+
console.log("💾 Custom nodes saved");
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
console.error("Nodes save failed:", err);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
if (typeof window.requestIdleCallback === "function") {
|
|
44
|
+
window.requestIdleCallback(performSave);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
performSave();
|
|
48
|
+
}
|
|
49
|
+
}, [tasks, documents, isRestoringRef]);
|
|
50
|
+
// ── EFFECT: CANVAS EVENT LISTENERS ──────────────────────────────────
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
const canvas = fabricCanvas.current;
|
|
53
|
+
if (!canvas)
|
|
54
|
+
return;
|
|
55
|
+
let saveTimeout; // ← Changed
|
|
56
|
+
const debouncedSave = (opt) => {
|
|
57
|
+
if (opt?.target?.excludeFromExport)
|
|
58
|
+
return;
|
|
59
|
+
clearTimeout(saveTimeout);
|
|
60
|
+
saveTimeout = window.setTimeout(saveCanvas, 2000); // ← Use window.setTimeout
|
|
61
|
+
};
|
|
62
|
+
canvas.on("object:modified", debouncedSave);
|
|
63
|
+
canvas.on("object:added", debouncedSave);
|
|
64
|
+
canvas.on("object:removed", debouncedSave);
|
|
65
|
+
canvas.on("path:created", debouncedSave);
|
|
66
|
+
return () => {
|
|
67
|
+
clearTimeout(saveTimeout);
|
|
68
|
+
canvas.off("object:modified", debouncedSave);
|
|
69
|
+
canvas.off("object:added", debouncedSave);
|
|
70
|
+
canvas.off("object:removed", debouncedSave);
|
|
71
|
+
canvas.off("path:created", debouncedSave);
|
|
72
|
+
};
|
|
73
|
+
}, [saveCanvas, fabricCanvas]);
|
|
74
|
+
// ── EFFECT: REACT STATE (NODES) WATCHER ─────────────────────────────
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
const timeout = setTimeout(saveNodes, 2000);
|
|
77
|
+
return () => clearTimeout(timeout);
|
|
78
|
+
}, [saveNodes]);
|
|
79
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Canvas, FabricObject } from "fabric";
|
|
2
|
+
interface UseSelectionProps {
|
|
3
|
+
fabricCanvas: React.RefObject<Canvas | null>;
|
|
4
|
+
activeTool: string;
|
|
5
|
+
canvasZoom: number;
|
|
6
|
+
canvasViewport: {
|
|
7
|
+
x: number;
|
|
8
|
+
y: number;
|
|
9
|
+
};
|
|
10
|
+
setSelectionBox: (box: {
|
|
11
|
+
x1: number;
|
|
12
|
+
y1: number;
|
|
13
|
+
x2: number;
|
|
14
|
+
y2: number;
|
|
15
|
+
} | null) => void;
|
|
16
|
+
setSelectedCanvasObjects: (objects: FabricObject[]) => void;
|
|
17
|
+
isDrawingRef: React.MutableRefObject<boolean>;
|
|
18
|
+
}
|
|
19
|
+
export declare const useSelection: ({ fabricCanvas, activeTool, canvasZoom, canvasViewport, setSelectionBox, setSelectedCanvasObjects, isDrawingRef, }: UseSelectionProps) => void;
|
|
20
|
+
export {};
|
|
21
|
+
//# sourceMappingURL=useSelection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useSelection.d.ts","sourceRoot":"","sources":["../../src/hooks/useSelection.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAQ,YAAY,EAAe,MAAM,QAAQ,CAAC;AAMjE,UAAU,iBAAiB;IACzB,YAAY,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC7C,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACzC,eAAe,EAAE,CAAC,GAAG,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,KAAK,IAAI,CAAC;IAC1F,wBAAwB,EAAE,CAAC,OAAO,EAAE,YAAY,EAAE,KAAK,IAAI,CAAC;IAC5D,YAAY,EAAE,KAAK,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC;CAC/C;AAED,eAAO,MAAM,YAAY,GAAI,oHAQ1B,iBAAiB,SA0JnB,CAAC"}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
// hooks/useSelection.ts
|
|
2
|
+
import { useEffect } from "react";
|
|
3
|
+
import { Rect, FabricImage } from "fabric";
|
|
4
|
+
import { Frame } from "../lib/fabric-frame";
|
|
5
|
+
import { Arrow } from "../lib/fabric-arrow";
|
|
6
|
+
import { BidirectionalArrow } from "../lib/fabric-bidirectional-arrow";
|
|
7
|
+
import { useWhiteboardStore } from "../store/whiteboard-store";
|
|
8
|
+
export const useSelection = ({ fabricCanvas, activeTool, canvasZoom, canvasViewport, setSelectionBox, setSelectedCanvasObjects, isDrawingRef, }) => {
|
|
9
|
+
const setSelectedObjectType = useWhiteboardStore((state) => state.setSelectedObjectType);
|
|
10
|
+
const setActiveTool = useWhiteboardStore((state) => state.setActiveTool);
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
const canvas = fabricCanvas.current;
|
|
13
|
+
if (!canvas)
|
|
14
|
+
return;
|
|
15
|
+
let isSelecting = false;
|
|
16
|
+
let selStart = { x: 0, y: 0 };
|
|
17
|
+
let selRect = null;
|
|
18
|
+
let rafId = null;
|
|
19
|
+
const onDown = (e) => {
|
|
20
|
+
if (activeTool !== "select" || e.target)
|
|
21
|
+
return;
|
|
22
|
+
isSelecting = true;
|
|
23
|
+
const p = canvas.getScenePoint(e.e);
|
|
24
|
+
selStart = { x: p.x, y: p.y };
|
|
25
|
+
selRect = new Rect({
|
|
26
|
+
left: p.x,
|
|
27
|
+
top: p.y,
|
|
28
|
+
width: 0,
|
|
29
|
+
height: 0,
|
|
30
|
+
fill: "transparent",
|
|
31
|
+
stroke: "transparent",
|
|
32
|
+
strokeWidth: 0,
|
|
33
|
+
selectable: false,
|
|
34
|
+
evented: false,
|
|
35
|
+
visible: false,
|
|
36
|
+
});
|
|
37
|
+
canvas.add(selRect);
|
|
38
|
+
canvas.renderAll();
|
|
39
|
+
};
|
|
40
|
+
const onMove = (e) => {
|
|
41
|
+
if (!isSelecting || !selRect)
|
|
42
|
+
return;
|
|
43
|
+
if (rafId !== null)
|
|
44
|
+
cancelAnimationFrame(rafId);
|
|
45
|
+
rafId = requestAnimationFrame(() => {
|
|
46
|
+
const p = canvas.getScenePoint(e.e);
|
|
47
|
+
const w = p.x - selStart.x;
|
|
48
|
+
const h = p.y - selStart.y;
|
|
49
|
+
selRect.set({
|
|
50
|
+
left: w < 0 ? p.x : selStart.x,
|
|
51
|
+
top: h < 0 ? p.y : selStart.y,
|
|
52
|
+
width: Math.abs(w),
|
|
53
|
+
height: Math.abs(h),
|
|
54
|
+
});
|
|
55
|
+
selRect.setCoords();
|
|
56
|
+
canvas.renderAll();
|
|
57
|
+
setSelectionBox({
|
|
58
|
+
x1: Math.min(selStart.x, p.x) * canvasZoom + canvasViewport.x,
|
|
59
|
+
y1: Math.min(selStart.y, p.y) * canvasZoom + canvasViewport.y,
|
|
60
|
+
x2: Math.max(selStart.x, p.x) * canvasZoom + canvasViewport.x,
|
|
61
|
+
y2: Math.max(selStart.y, p.y) * canvasZoom + canvasViewport.y,
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
};
|
|
65
|
+
const onUp = () => {
|
|
66
|
+
if (!isSelecting || !selRect)
|
|
67
|
+
return;
|
|
68
|
+
if (rafId !== null) {
|
|
69
|
+
cancelAnimationFrame(rafId);
|
|
70
|
+
rafId = null;
|
|
71
|
+
}
|
|
72
|
+
canvas.remove(selRect);
|
|
73
|
+
canvas.renderAll();
|
|
74
|
+
isSelecting = false;
|
|
75
|
+
selRect = null;
|
|
76
|
+
setTimeout(() => setSelectionBox(null), 100);
|
|
77
|
+
};
|
|
78
|
+
const onSelected = () => {
|
|
79
|
+
const sel = canvas.getActiveObject();
|
|
80
|
+
if (!sel || isDrawingRef.current)
|
|
81
|
+
return;
|
|
82
|
+
setSelectedCanvasObjects(sel.type === "activeSelection" ? sel.getObjects() : [sel]);
|
|
83
|
+
const typeMap = {
|
|
84
|
+
rect: "rectangle",
|
|
85
|
+
circle: "circle",
|
|
86
|
+
line: "line",
|
|
87
|
+
arrow: "arrow",
|
|
88
|
+
"bidirectional-arrow": "arrow",
|
|
89
|
+
"i-text": "text",
|
|
90
|
+
text: "text",
|
|
91
|
+
path: "pen",
|
|
92
|
+
image: "image",
|
|
93
|
+
};
|
|
94
|
+
const t = sel instanceof Frame
|
|
95
|
+
? "frame"
|
|
96
|
+
: sel instanceof FabricImage
|
|
97
|
+
? "image"
|
|
98
|
+
: sel instanceof Arrow
|
|
99
|
+
? "arrow"
|
|
100
|
+
: sel instanceof BidirectionalArrow
|
|
101
|
+
? "arrow"
|
|
102
|
+
: typeMap[sel.type] ?? null;
|
|
103
|
+
setSelectedObjectType(t);
|
|
104
|
+
};
|
|
105
|
+
const onDeselected = () => {
|
|
106
|
+
setSelectedObjectType(null);
|
|
107
|
+
setSelectionBox(null);
|
|
108
|
+
setSelectedCanvasObjects([]);
|
|
109
|
+
if (!isDrawingRef.current && activeTool !== "select" && activeTool !== "pan") {
|
|
110
|
+
setActiveTool("select");
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
canvas.on("selection:created", onSelected);
|
|
114
|
+
canvas.on("selection:updated", onSelected);
|
|
115
|
+
canvas.on("selection:cleared", onDeselected);
|
|
116
|
+
canvas.on("mouse:down", onDown);
|
|
117
|
+
canvas.on("mouse:move", onMove);
|
|
118
|
+
canvas.on("mouse:up", onUp);
|
|
119
|
+
return () => {
|
|
120
|
+
canvas.off("selection:created", onSelected);
|
|
121
|
+
canvas.off("selection:updated", onSelected);
|
|
122
|
+
canvas.off("selection:cleared", onDeselected);
|
|
123
|
+
canvas.off("mouse:down", onDown);
|
|
124
|
+
canvas.off("mouse:move", onMove);
|
|
125
|
+
canvas.off("mouse:up", onUp);
|
|
126
|
+
if (rafId !== null)
|
|
127
|
+
cancelAnimationFrame(rafId);
|
|
128
|
+
if (selRect)
|
|
129
|
+
canvas.remove(selRect);
|
|
130
|
+
};
|
|
131
|
+
}, [
|
|
132
|
+
activeTool,
|
|
133
|
+
canvasZoom,
|
|
134
|
+
canvasViewport,
|
|
135
|
+
fabricCanvas,
|
|
136
|
+
setSelectionBox,
|
|
137
|
+
setSelectedCanvasObjects,
|
|
138
|
+
setSelectedObjectType,
|
|
139
|
+
setActiveTool,
|
|
140
|
+
isDrawingRef
|
|
141
|
+
]);
|
|
142
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { RefObject } from "react";
|
|
2
|
+
import { Canvas } from "fabric";
|
|
3
|
+
interface UseTextStyleProps {
|
|
4
|
+
fabricCanvas: RefObject<Canvas | null>;
|
|
5
|
+
toolOptions: any;
|
|
6
|
+
}
|
|
7
|
+
export declare const useTextStyle: ({ fabricCanvas, toolOptions }: UseTextStyleProps) => void;
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=useTextStyle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useTextStyle.d.ts","sourceRoot":"","sources":["../../src/hooks/useTextStyle.ts"],"names":[],"mappings":"AACA,OAAO,EAAa,SAAS,EAAE,MAAM,OAAO,CAAC;AAC7C,OAAO,EAAE,MAAM,EAAgB,MAAM,QAAQ,CAAC;AAE9C,UAAU,iBAAiB;IACzB,YAAY,EAAE,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACvC,WAAW,EAAE,GAAG,CAAC;CAClB;AAED,eAAO,MAAM,YAAY,GAAI,+BAA+B,iBAAiB,SAiC5E,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
export const useTextStyle = ({ fabricCanvas, toolOptions }) => {
|
|
3
|
+
useEffect(() => {
|
|
4
|
+
const canvas = fabricCanvas.current;
|
|
5
|
+
if (!canvas)
|
|
6
|
+
return;
|
|
7
|
+
const activeObject = canvas.getActiveObject();
|
|
8
|
+
if (!activeObject)
|
|
9
|
+
return;
|
|
10
|
+
const updateTextStyle = (obj) => {
|
|
11
|
+
if (obj.type !== "i-text" && obj.type !== "text")
|
|
12
|
+
return;
|
|
13
|
+
const options = toolOptions.text;
|
|
14
|
+
obj.set({
|
|
15
|
+
fontSize: options.fontSize,
|
|
16
|
+
fontFamily: options.fontFamily,
|
|
17
|
+
fontWeight: options.fontWeight,
|
|
18
|
+
fill: options.color,
|
|
19
|
+
textAlign: options.textAlign,
|
|
20
|
+
});
|
|
21
|
+
obj.dirty = true;
|
|
22
|
+
};
|
|
23
|
+
if (activeObject.type === "activeSelection") {
|
|
24
|
+
const objects = activeObject.getObjects();
|
|
25
|
+
objects.forEach((obj) => updateTextStyle(obj));
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
updateTextStyle(activeObject);
|
|
29
|
+
}
|
|
30
|
+
canvas.requestRenderAll();
|
|
31
|
+
}, [toolOptions.text, fabricCanvas]);
|
|
32
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { RefObject } from "react";
|
|
2
|
+
import { Canvas, Circle } from "fabric";
|
|
3
|
+
import * as fabric from "fabric";
|
|
4
|
+
interface UseToolManagerProps {
|
|
5
|
+
fabricCanvas: RefObject<Canvas | null>;
|
|
6
|
+
activeTool: string;
|
|
7
|
+
toolOptions: any;
|
|
8
|
+
eraserTraceRef: RefObject<Circle | null>;
|
|
9
|
+
eraserPathRef: RefObject<fabric.Path | null>;
|
|
10
|
+
eraserPathPointsRef: RefObject<string[]>;
|
|
11
|
+
eraserTargetsRef: RefObject<Set<any>>;
|
|
12
|
+
}
|
|
13
|
+
export declare const useToolManager: ({ fabricCanvas, activeTool, toolOptions, eraserTraceRef, eraserPathRef, eraserPathPointsRef, eraserTargetsRef, }: UseToolManagerProps) => void;
|
|
14
|
+
export {};
|
|
15
|
+
//# sourceMappingURL=useToolManager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useToolManager.d.ts","sourceRoot":"","sources":["../../src/hooks/useToolManager.ts"],"names":[],"mappings":"AACA,OAAO,EAAa,SAAS,EAAE,MAAM,OAAO,CAAC;AAC7C,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAExC,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AAEjC,UAAU,mBAAmB;IAC3B,YAAY,EAAE,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACvC,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,GAAG,CAAC;IACjB,cAAc,EAAE,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACzC,aAAa,EAAE,SAAS,CAAC,MAAM,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;IAC7C,mBAAmB,EAAE,SAAS,CAAC,MAAM,EAAE,CAAC,CAAC;IACzC,gBAAgB,EAAE,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;CACvC;AAED,eAAO,MAAM,cAAc,GAAI,kHAQ5B,mBAAmB,SA8HrB,CAAC"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
import { calculateDashArray } from "../lib/fabric-utils";
|
|
3
|
+
export const useToolManager = ({ fabricCanvas, activeTool, toolOptions, eraserTraceRef, eraserPathRef, eraserPathPointsRef, eraserTargetsRef, }) => {
|
|
4
|
+
useEffect(() => {
|
|
5
|
+
const canvas = fabricCanvas.current;
|
|
6
|
+
if (!canvas)
|
|
7
|
+
return;
|
|
8
|
+
// Clean up eraser trace when switching tools
|
|
9
|
+
if (activeTool !== "eraser" && eraserTraceRef.current) {
|
|
10
|
+
canvas.remove(eraserTraceRef.current);
|
|
11
|
+
eraserTraceRef.current = null;
|
|
12
|
+
eraserTargetsRef.current.clear();
|
|
13
|
+
if (eraserPathRef.current) {
|
|
14
|
+
canvas.remove(eraserPathRef.current);
|
|
15
|
+
eraserPathRef.current = null;
|
|
16
|
+
eraserPathPointsRef.current = [];
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
switch (activeTool) {
|
|
20
|
+
case "select":
|
|
21
|
+
canvas.isDrawingMode = false;
|
|
22
|
+
canvas.selection = true;
|
|
23
|
+
canvas.defaultCursor = "default";
|
|
24
|
+
canvas.hoverCursor = "move";
|
|
25
|
+
canvas.forEachObject((obj) => {
|
|
26
|
+
obj.selectable = true;
|
|
27
|
+
obj.evented = true;
|
|
28
|
+
obj.hoverCursor = "move";
|
|
29
|
+
});
|
|
30
|
+
break;
|
|
31
|
+
case "pan":
|
|
32
|
+
canvas.isDrawingMode = false;
|
|
33
|
+
canvas.selection = false;
|
|
34
|
+
canvas.defaultCursor = "grab";
|
|
35
|
+
canvas.hoverCursor = "grab";
|
|
36
|
+
canvas.forEachObject((obj) => {
|
|
37
|
+
obj.selectable = false;
|
|
38
|
+
obj.evented = false;
|
|
39
|
+
});
|
|
40
|
+
break;
|
|
41
|
+
case "pen":
|
|
42
|
+
canvas.isDrawingMode = true;
|
|
43
|
+
canvas.selection = false;
|
|
44
|
+
canvas.defaultCursor = "crosshair";
|
|
45
|
+
canvas.hoverCursor = "crosshair";
|
|
46
|
+
if (canvas.freeDrawingBrush) {
|
|
47
|
+
canvas.freeDrawingBrush.color = toolOptions.pen.color;
|
|
48
|
+
canvas.freeDrawingBrush.width = toolOptions.pen.strokeWidth;
|
|
49
|
+
if (toolOptions.pen.strokeDashArray) {
|
|
50
|
+
const dynamicDashArray = calculateDashArray(toolOptions.pen.strokeDashArray, toolOptions.pen.strokeWidth);
|
|
51
|
+
canvas.freeDrawingBrush.strokeDashArray = dynamicDashArray || null;
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
canvas.freeDrawingBrush.strokeDashArray = null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
canvas.forEachObject((obj) => {
|
|
58
|
+
obj.selectable = false;
|
|
59
|
+
obj.evented = false;
|
|
60
|
+
});
|
|
61
|
+
break;
|
|
62
|
+
case "eraser":
|
|
63
|
+
canvas.isDrawingMode = false;
|
|
64
|
+
canvas.selection = false;
|
|
65
|
+
canvas.defaultCursor = "none";
|
|
66
|
+
canvas.hoverCursor = "none";
|
|
67
|
+
if (eraserTraceRef.current) {
|
|
68
|
+
eraserTraceRef.current.set({ visible: false });
|
|
69
|
+
}
|
|
70
|
+
canvas.forEachObject((obj) => {
|
|
71
|
+
obj.selectable = false;
|
|
72
|
+
obj.evented = true;
|
|
73
|
+
});
|
|
74
|
+
break;
|
|
75
|
+
case "text":
|
|
76
|
+
canvas.isDrawingMode = false;
|
|
77
|
+
canvas.selection = false;
|
|
78
|
+
canvas.defaultCursor = "text";
|
|
79
|
+
canvas.hoverCursor = "text";
|
|
80
|
+
canvas.forEachObject((obj) => {
|
|
81
|
+
obj.selectable = false;
|
|
82
|
+
obj.evented = false;
|
|
83
|
+
});
|
|
84
|
+
break;
|
|
85
|
+
case "rectangle":
|
|
86
|
+
case "circle":
|
|
87
|
+
case "line":
|
|
88
|
+
case "frame":
|
|
89
|
+
case "arrow":
|
|
90
|
+
canvas.isDrawingMode = false;
|
|
91
|
+
canvas.selection = false;
|
|
92
|
+
canvas.defaultCursor = "crosshair";
|
|
93
|
+
canvas.hoverCursor = "crosshair";
|
|
94
|
+
canvas.forEachObject((obj) => {
|
|
95
|
+
obj.selectable = false;
|
|
96
|
+
obj.evented = false;
|
|
97
|
+
});
|
|
98
|
+
break;
|
|
99
|
+
default:
|
|
100
|
+
canvas.isDrawingMode = false;
|
|
101
|
+
canvas.selection = true;
|
|
102
|
+
canvas.defaultCursor = "default";
|
|
103
|
+
canvas.hoverCursor = "move";
|
|
104
|
+
}
|
|
105
|
+
canvas.renderAll();
|
|
106
|
+
}, [
|
|
107
|
+
activeTool,
|
|
108
|
+
toolOptions,
|
|
109
|
+
fabricCanvas,
|
|
110
|
+
eraserTraceRef,
|
|
111
|
+
eraserPathRef,
|
|
112
|
+
eraserPathPointsRef,
|
|
113
|
+
eraserTargetsRef,
|
|
114
|
+
]);
|
|
115
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { RefObject } from "react";
|
|
2
|
+
import { Canvas } from "fabric";
|
|
3
|
+
interface UseZoomProps {
|
|
4
|
+
fabricCanvas: RefObject<Canvas | null>;
|
|
5
|
+
MIN_ZOOM: number;
|
|
6
|
+
MAX_ZOOM: number;
|
|
7
|
+
canvasZoom: number;
|
|
8
|
+
canvasViewport: {
|
|
9
|
+
x: number;
|
|
10
|
+
y: number;
|
|
11
|
+
};
|
|
12
|
+
setCanvasZoom: (zoom: number) => void;
|
|
13
|
+
setCanvasViewport: (viewport: {
|
|
14
|
+
x: number;
|
|
15
|
+
y: number;
|
|
16
|
+
}) => void;
|
|
17
|
+
}
|
|
18
|
+
export declare const useZoom: ({ fabricCanvas, MIN_ZOOM, MAX_ZOOM, canvasZoom, canvasViewport, setCanvasZoom, setCanvasViewport, }: UseZoomProps) => {
|
|
19
|
+
handleZoom: (newZoom: number, point?: {
|
|
20
|
+
x: number;
|
|
21
|
+
y: number;
|
|
22
|
+
}) => void;
|
|
23
|
+
};
|
|
24
|
+
export {};
|
|
25
|
+
//# sourceMappingURL=useZoom.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useZoom.d.ts","sourceRoot":"","sources":["../../src/hooks/useZoom.ts"],"names":[],"mappings":"AACA,OAAa,EAA0B,SAAS,EAAE,MAAM,OAAO,CAAC;AAChE,OAAO,EAAE,MAAM,EAAuB,MAAM,QAAQ,CAAC;AAErD,UAAU,YAAY;IACpB,YAAY,EAAE,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACzC,aAAa,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,iBAAiB,EAAE,CAAC,QAAQ,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CACjE;AAED,eAAO,MAAM,OAAO,GAAI,qGAQrB,YAAY;0BAED,MAAM,UAAU;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE;CAoJrD,CAAC"}
|