@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.
Files changed (127) hide show
  1. package/dist/components/node/custom-node-overlay-layer.d.ts +44 -0
  2. package/dist/components/node/custom-node-overlay-layer.d.ts.map +1 -0
  3. package/dist/components/node/custom-node-overlay-layer.js +353 -0
  4. package/dist/components/node/custom-node.d.ts +17 -0
  5. package/dist/components/node/custom-node.d.ts.map +1 -0
  6. package/dist/components/node/custom-node.js +63 -0
  7. package/dist/components/node/document-node.d.ts +14 -0
  8. package/dist/components/node/document-node.d.ts.map +1 -0
  9. package/dist/components/node/document-node.js +58 -0
  10. package/dist/components/toolbar/document-dropdown.d.ts +14 -0
  11. package/dist/components/toolbar/document-dropdown.d.ts.map +1 -0
  12. package/dist/components/toolbar/document-dropdown.js +66 -0
  13. package/dist/components/toolbar/options/arrow-options.d.ts +8 -0
  14. package/dist/components/toolbar/options/arrow-options.d.ts.map +1 -0
  15. package/dist/components/toolbar/options/arrow-options.js +109 -0
  16. package/dist/components/toolbar/options/erase-option.d.ts +2 -0
  17. package/dist/components/toolbar/options/erase-option.d.ts.map +1 -0
  18. package/dist/components/toolbar/options/erase-option.js +20 -0
  19. package/dist/components/toolbar/options/image-options.d.ts +2 -0
  20. package/dist/components/toolbar/options/image-options.d.ts.map +1 -0
  21. package/dist/components/toolbar/options/image-options.js +10 -0
  22. package/dist/components/toolbar/options/line-options.d.ts +2 -0
  23. package/dist/components/toolbar/options/line-options.d.ts.map +1 -0
  24. package/dist/components/toolbar/options/line-options.js +46 -0
  25. package/dist/components/toolbar/options/pen-option.d.ts +2 -0
  26. package/dist/components/toolbar/options/pen-option.d.ts.map +1 -0
  27. package/dist/components/toolbar/options/pen-option.js +53 -0
  28. package/dist/components/toolbar/options/shape-option.d.ts +6 -0
  29. package/dist/components/toolbar/options/shape-option.d.ts.map +1 -0
  30. package/dist/components/toolbar/options/shape-option.js +58 -0
  31. package/dist/components/toolbar/options/text-option.d.ts +2 -0
  32. package/dist/components/toolbar/options/text-option.d.ts.map +1 -0
  33. package/dist/components/toolbar/options/text-option.js +73 -0
  34. package/dist/components/toolbar/task-dropdown.d.ts +15 -0
  35. package/dist/components/toolbar/task-dropdown.d.ts.map +1 -0
  36. package/dist/components/toolbar/task-dropdown.js +85 -0
  37. package/dist/components/toolbar/toolbar-button.d.ts +12 -0
  38. package/dist/components/toolbar/toolbar-button.d.ts.map +1 -0
  39. package/dist/components/toolbar/toolbar-button.js +8 -0
  40. package/dist/components/toolbar/toolbar-seperator.d.ts +6 -0
  41. package/dist/components/toolbar/toolbar-seperator.d.ts.map +1 -0
  42. package/dist/components/toolbar/toolbar-seperator.js +5 -0
  43. package/dist/components/toolbar/tooloptions-panel.d.ts +8 -0
  44. package/dist/components/toolbar/tooloptions-panel.d.ts.map +1 -0
  45. package/dist/components/toolbar/tooloptions-panel.js +88 -0
  46. package/dist/components/toolbar/whiteboard-toolbar.d.ts +28 -0
  47. package/dist/components/toolbar/whiteboard-toolbar.d.ts.map +1 -0
  48. package/dist/components/toolbar/whiteboard-toolbar.js +160 -0
  49. package/dist/components/ui/dropdown-menu.d.ts +26 -0
  50. package/dist/components/ui/dropdown-menu.d.ts.map +1 -0
  51. package/dist/components/ui/dropdown-menu.js +51 -0
  52. package/dist/components/ui/label.d.ts +5 -0
  53. package/dist/components/ui/label.d.ts.map +1 -0
  54. package/dist/components/ui/label.js +8 -0
  55. package/dist/components/ui/slider.d.ts +5 -0
  56. package/dist/components/ui/slider.d.ts.map +1 -0
  57. package/dist/components/ui/slider.js +14 -0
  58. package/dist/components/whiteboard/whiteboard-test.d.ts +2 -0
  59. package/dist/components/whiteboard/whiteboard-test.d.ts.map +1 -0
  60. package/dist/components/whiteboard/whiteboard-test.js +207 -0
  61. package/dist/components/whiteboard/whiteboard.d.ts +1 -0
  62. package/dist/components/whiteboard/whiteboard.d.ts.map +1 -0
  63. package/dist/components/whiteboard/whiteboard.js +911 -0
  64. package/dist/components/zoomcontrol/zoom-control.d.ts +9 -0
  65. package/dist/components/zoomcontrol/zoom-control.d.ts.map +1 -0
  66. package/dist/components/zoomcontrol/zoom-control.js +7 -0
  67. package/dist/hooks/useCanvasInit.d.ts +15 -0
  68. package/dist/hooks/useCanvasInit.d.ts.map +1 -0
  69. package/dist/hooks/useCanvasInit.js +89 -0
  70. package/dist/hooks/useDrawing.d.ts +23 -0
  71. package/dist/hooks/useDrawing.d.ts.map +1 -0
  72. package/dist/hooks/useDrawing.js +142 -0
  73. package/dist/hooks/useEraser.d.ts +27 -0
  74. package/dist/hooks/useEraser.d.ts.map +1 -0
  75. package/dist/hooks/useEraser.js +143 -0
  76. package/dist/hooks/useLiveUpdate.d.ts +9 -0
  77. package/dist/hooks/useLiveUpdate.d.ts.map +1 -0
  78. package/dist/hooks/useLiveUpdate.js +63 -0
  79. package/dist/hooks/useMouseHandlers.d.ts +25 -0
  80. package/dist/hooks/useMouseHandlers.d.ts.map +1 -0
  81. package/dist/hooks/useMouseHandlers.js +44 -0
  82. package/dist/hooks/usePan.d.ts +17 -0
  83. package/dist/hooks/usePan.d.ts.map +1 -0
  84. package/dist/hooks/usePan.js +80 -0
  85. package/dist/hooks/usePersistance.d.ts +13 -0
  86. package/dist/hooks/usePersistance.d.ts.map +1 -0
  87. package/dist/hooks/usePersistance.js +79 -0
  88. package/dist/hooks/useSelection.d.ts +21 -0
  89. package/dist/hooks/useSelection.d.ts.map +1 -0
  90. package/dist/hooks/useSelection.js +142 -0
  91. package/dist/hooks/useTextStyle.d.ts +9 -0
  92. package/dist/hooks/useTextStyle.d.ts.map +1 -0
  93. package/dist/hooks/useTextStyle.js +32 -0
  94. package/dist/hooks/useToolManager.d.ts +15 -0
  95. package/dist/hooks/useToolManager.d.ts.map +1 -0
  96. package/dist/hooks/useToolManager.js +115 -0
  97. package/dist/hooks/useZoom.d.ts +25 -0
  98. package/dist/hooks/useZoom.d.ts.map +1 -0
  99. package/dist/hooks/useZoom.js +133 -0
  100. package/dist/index.d.ts +4 -0
  101. package/dist/index.d.ts.map +1 -0
  102. package/dist/index.js +30 -0
  103. package/dist/lib/eraser-brush.d.ts +1 -0
  104. package/dist/lib/eraser-brush.d.ts.map +1 -0
  105. package/dist/lib/eraser-brush.js +21 -0
  106. package/dist/lib/fabric-arrow.d.ts +16 -0
  107. package/dist/lib/fabric-arrow.d.ts.map +1 -0
  108. package/dist/lib/fabric-arrow.js +50 -0
  109. package/dist/lib/fabric-bidirectional-arrow.d.ts +20 -0
  110. package/dist/lib/fabric-bidirectional-arrow.d.ts.map +1 -0
  111. package/dist/lib/fabric-bidirectional-arrow.js +65 -0
  112. package/dist/lib/fabric-frame.d.ts +7 -0
  113. package/dist/lib/fabric-frame.d.ts.map +1 -0
  114. package/dist/lib/fabric-frame.js +25 -0
  115. package/dist/lib/fabric-utils.d.ts +30 -0
  116. package/dist/lib/fabric-utils.d.ts.map +1 -0
  117. package/dist/lib/fabric-utils.js +273 -0
  118. package/dist/lib/utils.d.ts +3 -0
  119. package/dist/lib/utils.d.ts.map +1 -0
  120. package/dist/lib/utils.js +5 -0
  121. package/dist/store/whiteboard-store.d.ts +99 -0
  122. package/dist/store/whiteboard-store.d.ts.map +1 -0
  123. package/dist/store/whiteboard-store.js +137 -0
  124. package/dist/types/canvas-node.d.ts +24 -0
  125. package/dist/types/canvas-node.d.ts.map +1 -0
  126. package/dist/types/canvas-node.js +1 -0
  127. package/package.json +34 -0
@@ -0,0 +1,9 @@
1
+ interface ZoomControlsProps {
2
+ zoom: number;
3
+ onZoomIn: () => void;
4
+ onZoomOut: () => void;
5
+ onResetZoom: () => void;
6
+ }
7
+ export default function ZoomControls({ zoom, onZoomIn, onZoomOut, onResetZoom, }: ZoomControlsProps): import("react/jsx-runtime").JSX.Element;
8
+ export {};
9
+ //# sourceMappingURL=zoom-control.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"zoom-control.d.ts","sourceRoot":"","sources":["../../../src/components/zoomcontrol/zoom-control.tsx"],"names":[],"mappings":"AAGA,UAAU,iBAAiB;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,WAAW,EAAE,MAAM,IAAI,CAAC;CACzB;AAED,MAAM,CAAC,OAAO,UAAU,YAAY,CAAC,EACnC,IAAI,EACJ,QAAQ,EACR,SAAS,EACT,WAAW,GACZ,EAAE,iBAAiB,2CAmEnB"}
@@ -0,0 +1,7 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Minus, Plus } from "lucide-react";
4
+ export default function ZoomControls({ zoom, onZoomIn, onZoomOut, onResetZoom, }) {
5
+ const zoomPercentage = Math.round(zoom * 100);
6
+ return (_jsx("div", { className: "fixed top-4 right-4 z-50", children: _jsxs("div", { className: "\r\n flex items-center gap-0.5\r\n bg-black/95 backdrop-blur-md\r\n border border-white/10\r\n rounded-full\r\n px-1.5 py-1\r\n shadow-lg shadow-black/20\r\n ", children: [_jsx("button", { onClick: onZoomOut, className: "\r\n w-7 h-7\r\n flex items-center justify-center\r\n hover:bg-white/10\r\n active:bg-white/20\r\n rounded-full\r\n transition-all\r\n ", "aria-label": "Zoom Out", children: _jsx(Minus, { className: "w-3.5 h-3.5 text-white", strokeWidth: 3 }) }), _jsxs("button", { onClick: onResetZoom, className: "\r\n px-1\r\n min-w-[48px]\r\n text-xs font-bold text-white\r\n hover:text-blue-400\r\n transition-colors\r\n tabular-nums\r\n text-center\r\n ", children: [zoomPercentage, "%"] }), _jsx("button", { onClick: onZoomIn, className: "\r\n w-7 h-7\r\n flex items-center justify-center\r\n hover:bg-white/10\r\n active:bg-white/20\r\n rounded-full\r\n transition-all\r\n ", "aria-label": "Zoom In", children: _jsx(Plus, { className: "w-3.5 h-3.5 text-white", strokeWidth: 3 }) })] }) }));
7
+ }
@@ -0,0 +1,15 @@
1
+ import { RefObject } from "react";
2
+ import { Canvas } from "fabric";
3
+ interface UseCanvasInitProps {
4
+ canvasRef: RefObject<HTMLCanvasElement | null>;
5
+ fabricCanvasRef: RefObject<Canvas | null>;
6
+ activeTool: string;
7
+ suppressHistoryRef: RefObject<boolean>;
8
+ isRestoringRef: RefObject<boolean>;
9
+ pushHistory: (state: string) => void;
10
+ setTasks: (tasks: any[]) => void;
11
+ setDocuments: (docs: any[]) => void;
12
+ }
13
+ export declare const useCanvasInit: ({ canvasRef, fabricCanvasRef, activeTool, suppressHistoryRef, isRestoringRef, pushHistory, setTasks, setDocuments, }: UseCanvasInitProps) => void;
14
+ export {};
15
+ //# sourceMappingURL=useCanvasInit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useCanvasInit.d.ts","sourceRoot":"","sources":["../../src/hooks/useCanvasInit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,SAAS,EAAE,MAAM,OAAO,CAAC;AAC7C,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,UAAU,kBAAkB;IAC1B,SAAS,EAAE,SAAS,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAAC;IAC/C,eAAe,EAAE,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC1C,UAAU,EAAE,MAAM,CAAC;IACnB,kBAAkB,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC;IACvC,cAAc,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC;IACnC,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAErC,QAAQ,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,KAAK,IAAI,CAAC;IACjC,YAAY,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAC;CACrC;AAKD,eAAO,MAAM,aAAa,GAAI,sHAS3B,kBAAkB,SAyFpB,CAAC"}
@@ -0,0 +1,89 @@
1
+ import { useEffect } from "react";
2
+ import { Canvas } from "fabric";
3
+ import { initializeFabricCanvas, addWelcomeContent } from "../lib/fabric-utils";
4
+ const CANVAS_KEY = "easyflow_whiteboard_canvas";
5
+ const NODES_KEY = "easyflow_whiteboard_nodes";
6
+ export const useCanvasInit = ({ canvasRef, fabricCanvasRef, activeTool, suppressHistoryRef, isRestoringRef, pushHistory, setTasks, setDocuments, }) => {
7
+ useEffect(() => {
8
+ // 1. GUARD: Prevent re-initialization if canvas already exists
9
+ if (!canvasRef.current || fabricCanvasRef.current)
10
+ return;
11
+ const canvas = new Canvas(canvasRef.current, {
12
+ width: window.innerWidth,
13
+ height: window.innerHeight,
14
+ backgroundColor: "transparent",
15
+ selection: true,
16
+ allowTouchScrolling: false,
17
+ stopContextMenu: true,
18
+ });
19
+ fabricCanvasRef.current = canvas;
20
+ initializeFabricCanvas(canvas);
21
+ const canvasElement = canvas.getElement();
22
+ canvasElement.style.touchAction = "none";
23
+ // 2. LOAD SAVED DATA (Replicating your old logic exactly)
24
+ const savedCanvas = localStorage.getItem(CANVAS_KEY);
25
+ const savedNodes = localStorage.getItem(NODES_KEY);
26
+ const hydrateCanvas = async () => {
27
+ if (savedCanvas || savedNodes) {
28
+ isRestoringRef.current = true;
29
+ if (savedCanvas) {
30
+ try {
31
+ await canvas.loadFromJSON(JSON.parse(savedCanvas));
32
+ canvas.renderAll();
33
+ // Push initial loaded state to history
34
+ pushHistory(JSON.stringify(canvas.toJSON()));
35
+ }
36
+ catch (err) {
37
+ console.error("Canvas load failed:", err);
38
+ addWelcomeContent(canvas, "/images/easyflow-logo.png");
39
+ }
40
+ }
41
+ if (savedNodes) {
42
+ try {
43
+ const { tasks: savedTasks, documents: savedDocs } = JSON.parse(savedNodes);
44
+ if (savedTasks)
45
+ setTasks(savedTasks);
46
+ if (savedDocs)
47
+ setDocuments(savedDocs);
48
+ }
49
+ catch (err) {
50
+ console.error("Nodes load failed:", err);
51
+ }
52
+ }
53
+ isRestoringRef.current = false;
54
+ }
55
+ else {
56
+ // No saved data - show welcome
57
+ addWelcomeContent(canvas, "/images/easyflow-logo.png");
58
+ pushHistory(JSON.stringify(canvas.toJSON()));
59
+ }
60
+ };
61
+ hydrateCanvas();
62
+ // 3. EVENT HANDLERS
63
+ const preventDefaults = (e) => {
64
+ // Note: activeTool inside here might be stale due to closure
65
+ // but canvasElement listener is attached once.
66
+ if (e.touches.length === 1)
67
+ return;
68
+ e.preventDefault();
69
+ };
70
+ const handleResize = () => {
71
+ canvas.setDimensions({
72
+ width: window.innerWidth,
73
+ height: window.innerHeight
74
+ });
75
+ canvas.renderAll();
76
+ };
77
+ window.addEventListener("resize", handleResize);
78
+ canvasElement.addEventListener("touchstart", preventDefaults, { passive: false });
79
+ canvasElement.addEventListener("touchmove", preventDefaults, { passive: false });
80
+ // 4. CLEANUP
81
+ return () => {
82
+ window.removeEventListener("resize", handleResize);
83
+ canvasElement.removeEventListener("touchstart", preventDefaults);
84
+ canvasElement.removeEventListener("touchmove", preventDefaults);
85
+ canvas.dispose();
86
+ fabricCanvasRef.current = null; // Reset ref on unmount
87
+ };
88
+ }, []); // Dependencies MUST be empty to prevent re-init on tool switch
89
+ };
@@ -0,0 +1,23 @@
1
+ import { RefObject } from "react";
2
+ import { Canvas, FabricObject, TPointerEventInfo } from "fabric";
3
+ interface UseDrawingProps {
4
+ fabricCanvas: RefObject<Canvas | null>;
5
+ activeTool: string;
6
+ toolOptions: any;
7
+ isDrawingRef: RefObject<boolean>;
8
+ startPointRef: RefObject<{
9
+ x: number;
10
+ y: number;
11
+ } | null>;
12
+ currentShapeRef: RefObject<FabricObject | null>;
13
+ suppressHistoryRef: RefObject<boolean>;
14
+ pushHistory: (state: string) => void;
15
+ addCanvasObject: (obj: FabricObject) => void;
16
+ }
17
+ export declare const useDrawing: ({ fabricCanvas, activeTool, toolOptions, isDrawingRef, startPointRef, currentShapeRef, suppressHistoryRef, pushHistory, addCanvasObject, }: UseDrawingProps) => {
18
+ handleMouseDown: (opt: TPointerEventInfo) => void;
19
+ handleMouseMove: (opt: TPointerEventInfo) => void;
20
+ handleMouseUp: () => void;
21
+ };
22
+ export {};
23
+ //# sourceMappingURL=useDrawing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useDrawing.d.ts","sourceRoot":"","sources":["../../src/hooks/useDrawing.ts"],"names":[],"mappings":"AACA,OAAc,EAAqB,SAAS,EAAE,MAAM,OAAO,CAAC;AAC5D,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,iBAAiB,EAAsB,MAAM,QAAQ,CAAC;AAKrF,UAAU,eAAe;IACvB,YAAY,EAAE,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACvC,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,GAAG,CAAC;IACjB,YAAY,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC;IACjC,aAAa,EAAE,SAAS,CAAC;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC,CAAC;IAC1D,eAAe,EAAE,SAAS,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;IAChD,kBAAkB,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC;IACvC,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,eAAe,EAAE,CAAC,GAAG,EAAE,YAAY,KAAK,IAAI,CAAC;CAC9C;AAED,eAAO,MAAM,UAAU,GAAI,4IAUxB,eAAe;2BACc,iBAAiB;2BAsHjB,iBAAiB;;CA8ChD,CAAC"}
@@ -0,0 +1,142 @@
1
+ import { Rect, Circle, Line } from "fabric";
2
+ import { Frame } from "../lib/fabric-frame";
3
+ import { Arrow } from "../lib/fabric-arrow";
4
+ import { calculateDashArray, updateDrawingObject, addText } from "../lib/fabric-utils";
5
+ import { useWhiteboardStore } from "../store/whiteboard-store";
6
+ export const useDrawing = ({ fabricCanvas, activeTool, toolOptions, isDrawingRef, startPointRef, currentShapeRef, suppressHistoryRef, pushHistory, addCanvasObject, }) => {
7
+ const handleMouseDown = (opt) => {
8
+ const canvas = fabricCanvas.current;
9
+ if (!canvas)
10
+ return;
11
+ const pointer = canvas.getScenePoint(opt.e);
12
+ if (opt.target || canvas.getActiveObject())
13
+ return;
14
+ // Text tool
15
+ if (activeTool === "text") {
16
+ addText(canvas, {
17
+ x: pointer.x,
18
+ y: pointer.y,
19
+ fontSize: toolOptions.text.fontSize,
20
+ fontFamily: toolOptions.text.fontFamily,
21
+ fontWeight: toolOptions.text.fontWeight,
22
+ color: toolOptions.text.color,
23
+ textAlign: toolOptions.text.textAlign,
24
+ });
25
+ pushHistory(JSON.stringify(canvas.toJSON()));
26
+ useWhiteboardStore.getState().setActiveTool("select");
27
+ return;
28
+ }
29
+ if (!["rectangle", "circle", "frame", "line", "arrow"].includes(activeTool))
30
+ return;
31
+ suppressHistoryRef.current = true;
32
+ isDrawingRef.current = true;
33
+ startPointRef.current = { x: pointer.x, y: pointer.y };
34
+ let shape = null;
35
+ switch (activeTool) {
36
+ case "rectangle":
37
+ shape = new Rect({
38
+ left: pointer.x,
39
+ top: pointer.y,
40
+ width: 1,
41
+ height: 1,
42
+ fill: toolOptions.rectangle.fillColor === "transparent"
43
+ ? "transparent"
44
+ : toolOptions.rectangle.fillColor,
45
+ stroke: toolOptions.rectangle.strokeColor,
46
+ strokeWidth: toolOptions.rectangle.strokeWidth,
47
+ strokeDashArray: calculateDashArray(toolOptions.rectangle.strokeDashArray, toolOptions.rectangle.strokeWidth),
48
+ rx: 5,
49
+ ry: 5,
50
+ strokeLineCap: "round",
51
+ strokeLineJoin: "round",
52
+ selectable: false,
53
+ });
54
+ break;
55
+ case "circle":
56
+ shape = new Circle({
57
+ left: pointer.x,
58
+ top: pointer.y,
59
+ radius: 1,
60
+ fill: toolOptions.circle.fillColor === "transparent"
61
+ ? "transparent"
62
+ : toolOptions.circle.fillColor,
63
+ stroke: toolOptions.circle.strokeColor,
64
+ strokeWidth: toolOptions.circle.strokeWidth,
65
+ strokeDashArray: toolOptions.circle.strokeDashArray
66
+ ? [...toolOptions.circle.strokeDashArray]
67
+ : undefined,
68
+ selectable: false,
69
+ });
70
+ break;
71
+ case "frame":
72
+ shape = new Frame({
73
+ left: pointer.x,
74
+ top: pointer.y,
75
+ width: 1,
76
+ height: 1,
77
+ stroke: toolOptions.frame.strokeColor,
78
+ strokeWidth: toolOptions.frame.strokeWidth,
79
+ strokeDashArray: toolOptions.frame.strokeDashArray
80
+ ? [...toolOptions.frame.strokeDashArray]
81
+ : undefined,
82
+ fill: toolOptions.frame.fillColor,
83
+ selectable: false,
84
+ });
85
+ break;
86
+ case "line":
87
+ shape = new Line([pointer.x, pointer.y, pointer.x, pointer.y], {
88
+ stroke: toolOptions.line.strokeColor,
89
+ strokeWidth: toolOptions.line.strokeWidth,
90
+ strokeDashArray: toolOptions.line.strokeDashArray || undefined,
91
+ selectable: false,
92
+ });
93
+ break;
94
+ case "arrow":
95
+ shape = new Arrow([pointer.x, pointer.y, pointer.x, pointer.y], {
96
+ stroke: toolOptions.arrow.strokeColor,
97
+ strokeWidth: toolOptions.arrow.strokeWidth,
98
+ strokeDashArray: toolOptions.arrow.strokeDashArray || undefined,
99
+ selectable: false,
100
+ });
101
+ break;
102
+ }
103
+ if (shape) {
104
+ canvas.add(shape);
105
+ currentShapeRef.current = shape;
106
+ canvas.renderAll();
107
+ }
108
+ };
109
+ const handleMouseMove = (opt) => {
110
+ const canvas = fabricCanvas.current;
111
+ if (!canvas)
112
+ return;
113
+ if (!isDrawingRef.current ||
114
+ !currentShapeRef.current ||
115
+ !startPointRef.current)
116
+ return;
117
+ const pointer = canvas.getScenePoint(opt.e);
118
+ updateDrawingObject(currentShapeRef.current, activeTool, startPointRef.current, pointer);
119
+ canvas.renderAll();
120
+ };
121
+ const handleMouseUp = () => {
122
+ const canvas = fabricCanvas.current;
123
+ if (!canvas)
124
+ return;
125
+ if (!isDrawingRef.current || !currentShapeRef.current)
126
+ return;
127
+ const shape = currentShapeRef.current;
128
+ shape.set({ selectable: true, evented: true });
129
+ if (shape instanceof Frame && canvas)
130
+ canvas.sendObjectToBack(shape);
131
+ addCanvasObject(shape);
132
+ suppressHistoryRef.current = false;
133
+ pushHistory(JSON.stringify(canvas.toJSON()));
134
+ isDrawingRef.current = false;
135
+ startPointRef.current = null;
136
+ currentShapeRef.current = null;
137
+ useWhiteboardStore.getState().setActiveTool("select");
138
+ canvas.discardActiveObject();
139
+ canvas.renderAll();
140
+ };
141
+ return { handleMouseDown, handleMouseMove, handleMouseUp };
142
+ };
@@ -0,0 +1,27 @@
1
+ import { RefObject } from "react";
2
+ import { Canvas, Circle, FabricObject } from "fabric";
3
+ import * as fabric from "fabric";
4
+ interface UseEraserProps {
5
+ fabricCanvas: RefObject<Canvas | null>;
6
+ activeTool: string;
7
+ toolOptions: any;
8
+ eraserTraceRef: RefObject<Circle | null>;
9
+ eraserTargetsRef: RefObject<Set<FabricObject>>;
10
+ eraserActiveRef: RefObject<boolean>;
11
+ eraserPathRef: RefObject<fabric.Path | null>;
12
+ eraserPathPointsRef: RefObject<string[]>;
13
+ suppressHistoryRef: RefObject<boolean>;
14
+ pushHistory: (state: string) => void;
15
+ }
16
+ export declare const useEraser: ({ fabricCanvas, activeTool, toolOptions, eraserTraceRef, eraserTargetsRef, eraserActiveRef, eraserPathRef, eraserPathPointsRef, suppressHistoryRef, pushHistory, }: UseEraserProps) => {
17
+ handleMouseOver: () => void;
18
+ handleMouseOut: () => void;
19
+ handleEraserDown: () => boolean;
20
+ handleEraserMove: (pointer: {
21
+ x: number;
22
+ y: number;
23
+ }) => void;
24
+ handleEraserUp: () => boolean;
25
+ };
26
+ export {};
27
+ //# sourceMappingURL=useEraser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useEraser.d.ts","sourceRoot":"","sources":["../../src/hooks/useEraser.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAClC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACtD,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AAEjC,UAAU,cAAc;IACtB,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,gBAAgB,EAAE,SAAS,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC;IAC/C,eAAe,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC;IACpC,aAAa,EAAE,SAAS,CAAC,MAAM,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;IAC7C,mBAAmB,EAAE,SAAS,CAAC,MAAM,EAAE,CAAC,CAAC;IACzC,kBAAkB,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC;IACvC,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACtC;AAED,eAAO,MAAM,SAAS,GAAI,oKAWvB,cAAc;;;;gCAsBoB;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE;;CA0I5D,CAAC"}
@@ -0,0 +1,143 @@
1
+ import { Circle } from "fabric";
2
+ import * as fabric from "fabric";
3
+ export const useEraser = ({ fabricCanvas, activeTool, toolOptions, eraserTraceRef, eraserTargetsRef, eraserActiveRef, eraserPathRef, eraserPathPointsRef, suppressHistoryRef, pushHistory, }) => {
4
+ const handleMouseOver = () => {
5
+ if (activeTool === "eraser" && eraserTraceRef.current) {
6
+ eraserTraceRef.current.set({ visible: true });
7
+ fabricCanvas.current?.renderAll();
8
+ }
9
+ };
10
+ const handleMouseOut = () => {
11
+ if (activeTool === "eraser" && eraserTraceRef.current) {
12
+ eraserTraceRef.current.set({ visible: false });
13
+ fabricCanvas.current?.renderAll();
14
+ }
15
+ };
16
+ const handleEraserDown = () => {
17
+ if (activeTool !== "eraser")
18
+ return false;
19
+ eraserActiveRef.current = true;
20
+ suppressHistoryRef.current = true;
21
+ return true;
22
+ };
23
+ const handleEraserMove = (pointer) => {
24
+ const canvas = fabricCanvas.current;
25
+ if (!canvas || activeTool !== "eraser")
26
+ return;
27
+ // Create or update trace circle
28
+ if (!eraserTraceRef.current) {
29
+ eraserTraceRef.current = new Circle({
30
+ radius: toolOptions.eraser.size / 1.5,
31
+ fill: "rgba(195, 195, 195, 0.2)",
32
+ stroke: "#e2e2e0",
33
+ strokeWidth: 2,
34
+ selectable: false,
35
+ evented: false,
36
+ originX: "center",
37
+ originY: "center",
38
+ excludeFromExport: true,
39
+ });
40
+ canvas.add(eraserTraceRef.current);
41
+ }
42
+ if (!eraserTraceRef.current.visible) {
43
+ eraserTraceRef.current.set({ visible: true });
44
+ }
45
+ eraserTraceRef.current.set({ left: pointer.x, top: pointer.y });
46
+ eraserTraceRef.current.setCoords();
47
+ canvas.bringObjectToFront(eraserTraceRef.current);
48
+ // Smooth continuous trail
49
+ if (eraserActiveRef.current) {
50
+ suppressHistoryRef.current = true;
51
+ if (eraserPathPointsRef.current.length === 0) {
52
+ eraserPathPointsRef.current = [`M ${pointer.x} ${pointer.y}`];
53
+ const path = new fabric.Path(eraserPathPointsRef.current.join(" "), {
54
+ stroke: "rgba(220, 220, 220, 0.7)",
55
+ strokeWidth: toolOptions.eraser.size,
56
+ fill: null,
57
+ selectable: false,
58
+ evented: false,
59
+ strokeLineCap: "round",
60
+ strokeLineJoin: "round",
61
+ opacity: 1,
62
+ excludeFromExport: true,
63
+ });
64
+ canvas.add(path);
65
+ eraserPathRef.current = path;
66
+ }
67
+ else {
68
+ eraserPathPointsRef.current.push(`L ${pointer.x} ${pointer.y}`);
69
+ const pathString = eraserPathPointsRef.current.join(" ");
70
+ if (eraserPathRef.current) {
71
+ canvas.remove(eraserPathRef.current);
72
+ }
73
+ const path = new fabric.Path(pathString, {
74
+ stroke: "rgba(53, 53, 53, 0.7)",
75
+ strokeWidth: toolOptions.eraser.size,
76
+ fill: null,
77
+ selectable: false,
78
+ evented: false,
79
+ strokeLineCap: "round",
80
+ strokeLineJoin: "round",
81
+ opacity: 1,
82
+ excludeFromExport: true,
83
+ });
84
+ canvas.add(path);
85
+ eraserPathRef.current = path;
86
+ }
87
+ canvas.bringObjectToFront(eraserTraceRef.current);
88
+ }
89
+ // Find objects under eraser
90
+ const previousTargets = new Set(eraserTargetsRef.current);
91
+ eraserTargetsRef.current.clear();
92
+ canvas.forEachObject((obj) => {
93
+ if (obj === eraserTraceRef.current)
94
+ return;
95
+ if (obj === eraserPathRef.current)
96
+ return;
97
+ if (obj.intersectsWithObject(eraserTraceRef.current)) {
98
+ eraserTargetsRef.current.add(obj);
99
+ if (eraserActiveRef.current && obj.opacity !== 0.3) {
100
+ obj.set({ opacity: 0.3 });
101
+ }
102
+ }
103
+ else if (previousTargets.has(obj)) {
104
+ obj.set({ opacity: 1 });
105
+ }
106
+ });
107
+ canvas.renderAll();
108
+ };
109
+ const handleEraserUp = () => {
110
+ const canvas = fabricCanvas.current;
111
+ if (!canvas || activeTool !== "eraser" || !eraserActiveRef.current)
112
+ return false;
113
+ eraserActiveRef.current = false;
114
+ // Restore opacity
115
+ canvas.forEachObject((obj) => {
116
+ if (obj.opacity === 0.3)
117
+ obj.set({ opacity: 1 });
118
+ });
119
+ // Remove trail
120
+ if (eraserPathRef.current) {
121
+ canvas.remove(eraserPathRef.current);
122
+ eraserPathRef.current = null;
123
+ eraserPathPointsRef.current = [];
124
+ }
125
+ // Delete targeted objects
126
+ eraserTargetsRef.current.forEach((obj) => {
127
+ canvas.remove(obj);
128
+ });
129
+ eraserTargetsRef.current.clear();
130
+ canvas.renderAll();
131
+ // Save history
132
+ suppressHistoryRef.current = false;
133
+ pushHistory(JSON.stringify(canvas.toJSON()));
134
+ return true;
135
+ };
136
+ return {
137
+ handleMouseOver,
138
+ handleMouseOut,
139
+ handleEraserDown,
140
+ handleEraserMove,
141
+ handleEraserUp,
142
+ };
143
+ };
@@ -0,0 +1,9 @@
1
+ import { RefObject } from "react";
2
+ import { Canvas } from "fabric";
3
+ interface UseLiveUpdateProps {
4
+ fabricCanvas: RefObject<Canvas | null>;
5
+ toolOptions: any;
6
+ }
7
+ export declare const useLiveUpdate: ({ fabricCanvas, toolOptions }: UseLiveUpdateProps) => void;
8
+ export {};
9
+ //# sourceMappingURL=useLiveUpdate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useLiveUpdate.d.ts","sourceRoot":"","sources":["../../src/hooks/useLiveUpdate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,SAAS,EAAE,MAAM,OAAO,CAAC;AAC7C,OAAO,EAAE,MAAM,EAA6B,MAAM,QAAQ,CAAC;AAI3D,UAAU,kBAAkB;IAC1B,YAAY,EAAE,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACvC,WAAW,EAAE,GAAG,CAAC;CAClB;AAED,eAAO,MAAM,aAAa,GAAI,+BAA+B,kBAAkB,SA8D9E,CAAC"}
@@ -0,0 +1,63 @@
1
+ import { useEffect } from "react";
2
+ import { FabricImage } from "fabric";
3
+ import { Frame } from "../lib/fabric-frame";
4
+ import { calculateDashArray } from "../lib/fabric-utils";
5
+ export const useLiveUpdate = ({ fabricCanvas, toolOptions }) => {
6
+ useEffect(() => {
7
+ const canvas = fabricCanvas.current;
8
+ if (!canvas)
9
+ return;
10
+ const activeObject = canvas.getActiveObject();
11
+ if (!activeObject)
12
+ return;
13
+ const updateObjectStyle = (obj) => {
14
+ let options = null;
15
+ if (obj.type === "rect")
16
+ options = toolOptions.rectangle;
17
+ else if (obj.type === "circle")
18
+ options = toolOptions.circle;
19
+ else if (obj instanceof Frame)
20
+ options = toolOptions.frame;
21
+ else if (obj.type === "line")
22
+ options = toolOptions.line;
23
+ else if (obj.type === "path")
24
+ options = toolOptions.pen;
25
+ else if (obj instanceof FabricImage || obj.type === "image")
26
+ options = toolOptions.image;
27
+ else if (obj.type === "arrow" || obj.type === "bidirectional-arrow")
28
+ options = toolOptions.arrow;
29
+ if (!options)
30
+ return;
31
+ const update = {};
32
+ if (typeof options.opacity === "number") {
33
+ update.opacity = options.opacity;
34
+ }
35
+ if (obj.type !== "image" && !(obj instanceof FabricImage)) {
36
+ if (obj.type === "path") {
37
+ update.stroke = options.color || options.strokeColor;
38
+ }
39
+ else {
40
+ update.fill =
41
+ options.fillColor === "transparent"
42
+ ? "transparent"
43
+ : options.fillColor;
44
+ update.stroke = options.strokeColor;
45
+ }
46
+ update.strokeWidth = options.strokeWidth;
47
+ update.strokeDashArray = calculateDashArray(options.strokeDashArray, options.strokeWidth);
48
+ }
49
+ obj.set(update);
50
+ obj.setCoords();
51
+ obj.dirty = true;
52
+ };
53
+ if (activeObject.type === "activeSelection") {
54
+ activeObject
55
+ .getObjects()
56
+ .forEach((obj) => updateObjectStyle(obj));
57
+ }
58
+ else {
59
+ updateObjectStyle(activeObject);
60
+ }
61
+ canvas.requestRenderAll();
62
+ }, [toolOptions, fabricCanvas]);
63
+ };
@@ -0,0 +1,25 @@
1
+ import { RefObject } from "react";
2
+ import { Canvas } from "fabric";
3
+ interface UseMouseHandlersProps {
4
+ fabricCanvas: RefObject<Canvas | null>;
5
+ activeTool: string;
6
+ toolOptions: any;
7
+ drawingHandlers: {
8
+ handleMouseDown: (opt: any) => void;
9
+ handleMouseMove: (opt: any) => void;
10
+ handleMouseUp: () => void;
11
+ };
12
+ eraserHandlers: {
13
+ handleMouseOver: () => void;
14
+ handleMouseOut: () => void;
15
+ handleEraserDown: () => boolean;
16
+ handleEraserMove: (pointer: {
17
+ x: number;
18
+ y: number;
19
+ }) => void;
20
+ handleEraserUp: () => boolean;
21
+ };
22
+ }
23
+ export declare const useMouseHandlers: ({ fabricCanvas, activeTool, toolOptions, drawingHandlers, eraserHandlers, }: UseMouseHandlersProps) => void;
24
+ export {};
25
+ //# sourceMappingURL=useMouseHandlers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useMouseHandlers.d.ts","sourceRoot":"","sources":["../../src/hooks/useMouseHandlers.ts"],"names":[],"mappings":"AACA,OAAc,EAAa,SAAS,EAAE,MAAM,OAAO,CAAC;AACpD,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,UAAU,qBAAqB;IAC7B,YAAY,EAAE,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACvC,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,GAAG,CAAC;IACjB,eAAe,EAAE;QACf,eAAe,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,IAAI,CAAC;QACpC,eAAe,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,IAAI,CAAC;QACpC,aAAa,EAAE,MAAM,IAAI,CAAC;KAC3B,CAAC;IACF,cAAc,EAAE;QACd,eAAe,EAAE,MAAM,IAAI,CAAC;QAC5B,cAAc,EAAE,MAAM,IAAI,CAAC;QAC3B,gBAAgB,EAAE,MAAM,OAAO,CAAC;QAChC,gBAAgB,EAAE,CAAC,OAAO,EAAE;YAAE,CAAC,EAAE,MAAM,CAAC;YAAC,CAAC,EAAE,MAAM,CAAA;SAAE,KAAK,IAAI,CAAC;QAC9D,cAAc,EAAE,MAAM,OAAO,CAAC;KAC/B,CAAC;CACH;AAED,eAAO,MAAM,gBAAgB,GAAI,6EAM9B,qBAAqB,SA8CvB,CAAC"}
@@ -0,0 +1,44 @@
1
+ import { useEffect } from "react";
2
+ export const useMouseHandlers = ({ fabricCanvas, activeTool, toolOptions, drawingHandlers, eraserHandlers, }) => {
3
+ useEffect(() => {
4
+ const canvas = fabricCanvas.current;
5
+ if (!canvas)
6
+ return;
7
+ const handleMouseDown = (opt) => {
8
+ // Eraser
9
+ if (eraserHandlers.handleEraserDown())
10
+ return;
11
+ // Drawing
12
+ drawingHandlers.handleMouseDown(opt);
13
+ };
14
+ const handleMouseMove = (opt) => {
15
+ const pointer = canvas.getScenePoint(opt.e);
16
+ // Eraser
17
+ if (activeTool === "eraser") {
18
+ eraserHandlers.handleEraserMove(pointer);
19
+ return;
20
+ }
21
+ // Drawing
22
+ drawingHandlers.handleMouseMove(opt);
23
+ };
24
+ const handleMouseUp = () => {
25
+ // Eraser
26
+ if (eraserHandlers.handleEraserUp())
27
+ return;
28
+ // Drawing
29
+ drawingHandlers.handleMouseUp();
30
+ };
31
+ canvas.on("mouse:over", eraserHandlers.handleMouseOver);
32
+ canvas.on("mouse:out", eraserHandlers.handleMouseOut);
33
+ canvas.on("mouse:down", handleMouseDown);
34
+ canvas.on("mouse:move", handleMouseMove);
35
+ canvas.on("mouse:up", handleMouseUp);
36
+ return () => {
37
+ canvas.off("mouse:over", eraserHandlers.handleMouseOver);
38
+ canvas.off("mouse:out", eraserHandlers.handleMouseOut);
39
+ canvas.off("mouse:down", handleMouseDown);
40
+ canvas.off("mouse:move", handleMouseMove);
41
+ canvas.off("mouse:up", handleMouseUp);
42
+ };
43
+ }, [activeTool, toolOptions, fabricCanvas, drawingHandlers, eraserHandlers]);
44
+ };
@@ -0,0 +1,17 @@
1
+ import { RefObject } from "react";
2
+ import { Canvas } from "fabric";
3
+ interface UsePanProps {
4
+ fabricCanvas: RefObject<Canvas | null>;
5
+ activeTool: string;
6
+ handleZoom: (zoom: number, point?: {
7
+ x: number;
8
+ y: number;
9
+ }) => void;
10
+ setCanvasViewport: (viewport: {
11
+ x: number;
12
+ y: number;
13
+ }) => void;
14
+ }
15
+ export declare const usePan: ({ fabricCanvas, activeTool, handleZoom, setCanvasViewport, }: UsePanProps) => void;
16
+ export {};
17
+ //# sourceMappingURL=usePan.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usePan.d.ts","sourceRoot":"","sources":["../../src/hooks/usePan.ts"],"names":[],"mappings":"AACA,OAAO,EAAa,SAAS,EAAE,MAAM,OAAO,CAAC;AAC7C,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,UAAU,WAAW;IACnB,YAAY,EAAE,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACvC,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IACrE,iBAAiB,EAAE,CAAC,QAAQ,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CACjE;AAED,eAAO,MAAM,MAAM,GAAI,8DAKpB,WAAW,SAkGb,CAAC"}