@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,273 @@
|
|
|
1
|
+
import { FabricObject, Line, Group, IText, PencilBrush, FabricImage, util } from "fabric";
|
|
2
|
+
import { Text } from "fabric";
|
|
3
|
+
// Initialize canvas with default settings
|
|
4
|
+
export const initializeFabricCanvas = (canvas) => {
|
|
5
|
+
// Set default object properties
|
|
6
|
+
FabricObject.prototype.set({
|
|
7
|
+
transparentCorners: false,
|
|
8
|
+
borderColor: "#3b82f6",
|
|
9
|
+
cornerColor: "#3b82f6",
|
|
10
|
+
cornerSize: 8,
|
|
11
|
+
cornerStyle: "circle",
|
|
12
|
+
padding: 4,
|
|
13
|
+
});
|
|
14
|
+
// Enable object caching for better performance
|
|
15
|
+
FabricObject.prototype.objectCaching = true;
|
|
16
|
+
// Initialize drawing brush
|
|
17
|
+
canvas.freeDrawingBrush = new PencilBrush(canvas);
|
|
18
|
+
return canvas;
|
|
19
|
+
};
|
|
20
|
+
// Update shape dimensions during drag-to-draw
|
|
21
|
+
export const updateDrawingObject = (shape, toolType, startPoint, currentPoint) => {
|
|
22
|
+
const width = currentPoint.x - startPoint.x;
|
|
23
|
+
const height = currentPoint.y - startPoint.y;
|
|
24
|
+
switch (toolType) {
|
|
25
|
+
case "rectangle":
|
|
26
|
+
case "frame": {
|
|
27
|
+
const rect = shape; // or Frame
|
|
28
|
+
const dx = currentPoint.x - startPoint.x;
|
|
29
|
+
const dy = currentPoint.y - startPoint.y;
|
|
30
|
+
// 1. Calculate the center point (Midpoint)
|
|
31
|
+
const midX = startPoint.x + dx / 2;
|
|
32
|
+
const midY = startPoint.y + dy / 2;
|
|
33
|
+
// 2. Calculate dimensions
|
|
34
|
+
// We subtract a tiny fraction of the strokeWidth if you want the
|
|
35
|
+
// outer visual edge to be exactly under the pixel of the cursor.
|
|
36
|
+
const strokeW = rect.strokeWidth || 0;
|
|
37
|
+
const width = Math.abs(dx) - strokeW;
|
|
38
|
+
const height = Math.abs(dy) - strokeW;
|
|
39
|
+
rect.set({
|
|
40
|
+
left: midX,
|
|
41
|
+
top: midY,
|
|
42
|
+
width: Math.max(0, width),
|
|
43
|
+
height: Math.max(0, height),
|
|
44
|
+
originX: "center",
|
|
45
|
+
originY: "center",
|
|
46
|
+
});
|
|
47
|
+
rect.setCoords();
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
case "circle": {
|
|
51
|
+
const circle = shape;
|
|
52
|
+
const dx = currentPoint.x - startPoint.x;
|
|
53
|
+
const dy = currentPoint.y - startPoint.y;
|
|
54
|
+
// Radius from diagonal distance
|
|
55
|
+
const radius = Math.sqrt(dx * dx + dy * dy) / 2;
|
|
56
|
+
// Center at midpoint
|
|
57
|
+
circle.set({
|
|
58
|
+
left: startPoint.x + dx / 2,
|
|
59
|
+
top: startPoint.y + dy / 2,
|
|
60
|
+
radius: Math.abs(radius),
|
|
61
|
+
originX: "center",
|
|
62
|
+
originY: "center",
|
|
63
|
+
});
|
|
64
|
+
circle.setCoords();
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
case "line":
|
|
68
|
+
case "arrow":
|
|
69
|
+
if (shape instanceof Line) {
|
|
70
|
+
shape.set({
|
|
71
|
+
x1: startPoint.x,
|
|
72
|
+
y1: startPoint.y,
|
|
73
|
+
x2: currentPoint.x,
|
|
74
|
+
y2: currentPoint.y,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
shape.setCoords();
|
|
80
|
+
};
|
|
81
|
+
// Delete selected objects
|
|
82
|
+
export const deleteSelectedObjects = (canvas) => {
|
|
83
|
+
const activeObjects = canvas.getActiveObjects();
|
|
84
|
+
if (activeObjects.length) {
|
|
85
|
+
activeObjects.forEach((obj) => {
|
|
86
|
+
canvas.remove(obj);
|
|
87
|
+
});
|
|
88
|
+
canvas.discardActiveObject();
|
|
89
|
+
canvas.renderAll();
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
// Export canvas as JSON (save to database)
|
|
93
|
+
export const exportCanvasToJSON = (canvas) => {
|
|
94
|
+
// return JSON.stringify(canvas.toJSON(["data"]));
|
|
95
|
+
};
|
|
96
|
+
// Import canvas from JSON
|
|
97
|
+
export const importCanvasFromJSON = (canvas, json, callback) => {
|
|
98
|
+
canvas.loadFromJSON(json).then(() => {
|
|
99
|
+
canvas.renderAll();
|
|
100
|
+
if (callback)
|
|
101
|
+
callback();
|
|
102
|
+
});
|
|
103
|
+
};
|
|
104
|
+
// Export canvas as image
|
|
105
|
+
export const exportCanvasToImage = (canvas, format = "png") => {
|
|
106
|
+
return canvas.toDataURL({
|
|
107
|
+
format,
|
|
108
|
+
quality: 1,
|
|
109
|
+
multiplier: 2, // 2x resolution for better quality
|
|
110
|
+
});
|
|
111
|
+
};
|
|
112
|
+
// lib/fabric-utils.ts
|
|
113
|
+
export const calculateDashArray = (strokeDashArray, strokeWidth) => {
|
|
114
|
+
if (!strokeDashArray)
|
|
115
|
+
return undefined;
|
|
116
|
+
const multiplier = Math.max(strokeWidth, 1);
|
|
117
|
+
return strokeDashArray.map(value => value * multiplier);
|
|
118
|
+
};
|
|
119
|
+
// Add text at specific position
|
|
120
|
+
export const addText = (canvas, options) => {
|
|
121
|
+
const text = new IText(options.text || "Double click to edit", {
|
|
122
|
+
left: options.x || 100,
|
|
123
|
+
top: options.y || 100,
|
|
124
|
+
fontSize: options.fontSize || 16,
|
|
125
|
+
fontFamily: options.fontFamily || "Arial",
|
|
126
|
+
fontWeight: options.fontWeight || "400",
|
|
127
|
+
fill: options.color || "#000000",
|
|
128
|
+
// textAlign: options.textAlign || "left",
|
|
129
|
+
});
|
|
130
|
+
canvas.add(text);
|
|
131
|
+
canvas.setActiveObject(text);
|
|
132
|
+
canvas.renderAll();
|
|
133
|
+
// Auto-enter edit mode
|
|
134
|
+
text.enterEditing();
|
|
135
|
+
text.selectAll();
|
|
136
|
+
};
|
|
137
|
+
export const addImageToCanvas = async (canvas, url) => {
|
|
138
|
+
if (!canvas)
|
|
139
|
+
return;
|
|
140
|
+
// 1. Get the current Viewport Transform (the Pan and Zoom matrix)
|
|
141
|
+
const vpt = canvas.viewportTransform;
|
|
142
|
+
const zoom = canvas.getZoom();
|
|
143
|
+
// 2. Calculate the exact Scene coordinates for the center
|
|
144
|
+
// Formula: (-PanOffset / Zoom) + (CanvasDimension / 2 / Zoom)
|
|
145
|
+
const centerX = (-vpt[4] + canvas.width / 2) / zoom;
|
|
146
|
+
const centerY = (-vpt[5] + canvas.height / 2) / zoom;
|
|
147
|
+
try {
|
|
148
|
+
const img = await FabricImage.fromURL(url);
|
|
149
|
+
// 3. Dynamic Scaling (Make it fit roughly 40% of the screen height)
|
|
150
|
+
const targetHeight = (canvas.height / zoom) * 0.4;
|
|
151
|
+
img.scaleToHeight(targetHeight);
|
|
152
|
+
img.set({
|
|
153
|
+
left: centerX,
|
|
154
|
+
top: centerY,
|
|
155
|
+
originX: "center",
|
|
156
|
+
originY: "center",
|
|
157
|
+
// cornerStyle: "circle",
|
|
158
|
+
// transparentCorners: false,
|
|
159
|
+
// cornerColor: "#3b82f6", // Nice modern blue
|
|
160
|
+
// cornerStrokeColor: "#ffffff",
|
|
161
|
+
padding: 10
|
|
162
|
+
});
|
|
163
|
+
canvas.add(img);
|
|
164
|
+
canvas.setActiveObject(img);
|
|
165
|
+
canvas.renderAll();
|
|
166
|
+
return img;
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
console.error("Image load failed", error);
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
// Clear entire canvas
|
|
173
|
+
export const clearCanvas = (canvas) => {
|
|
174
|
+
canvas.clear();
|
|
175
|
+
canvas.backgroundColor = "transparent";
|
|
176
|
+
canvas.renderAll();
|
|
177
|
+
};
|
|
178
|
+
// Zoom canvas
|
|
179
|
+
export const zoomCanvas = (canvas, zoomLevel) => {
|
|
180
|
+
canvas.setZoom(zoomLevel);
|
|
181
|
+
canvas.renderAll();
|
|
182
|
+
};
|
|
183
|
+
// Reset view to fit all objects
|
|
184
|
+
export const fitCanvasToObjects = (canvas) => {
|
|
185
|
+
const objects = canvas.getObjects();
|
|
186
|
+
if (objects.length === 0)
|
|
187
|
+
return;
|
|
188
|
+
const group = new Group(objects);
|
|
189
|
+
const groupWidth = group.width || 0;
|
|
190
|
+
const groupHeight = group.height || 0;
|
|
191
|
+
const canvasWidth = canvas.getWidth();
|
|
192
|
+
const canvasHeight = canvas.getHeight();
|
|
193
|
+
const zoom = Math.min(canvasWidth / (groupWidth + 100), canvasHeight / (groupHeight + 100));
|
|
194
|
+
canvas.setZoom(zoom);
|
|
195
|
+
canvas.viewportCenterObject(group);
|
|
196
|
+
// group.destroy();
|
|
197
|
+
canvas.renderAll();
|
|
198
|
+
};
|
|
199
|
+
export const addWelcomeContent = async (canvas, logoUrl) => {
|
|
200
|
+
const centerX = canvas.getWidth() / 2;
|
|
201
|
+
const centerY = canvas.getHeight() / 2;
|
|
202
|
+
// 1. Load the Logo Image
|
|
203
|
+
const logoImg = await FabricImage.fromURL(logoUrl, {
|
|
204
|
+
crossOrigin: 'anonymous'
|
|
205
|
+
});
|
|
206
|
+
// Scale logo to fit (e.g., 80px width)
|
|
207
|
+
const scale = 60 / logoImg.width;
|
|
208
|
+
logoImg.set({
|
|
209
|
+
scaleX: scale,
|
|
210
|
+
scaleY: scale,
|
|
211
|
+
originX: "center",
|
|
212
|
+
originY: "center",
|
|
213
|
+
top: -120, // Relative to group center
|
|
214
|
+
});
|
|
215
|
+
// 2. Creative Background "Aura" (The Glow)
|
|
216
|
+
// const aura = new Circle({
|
|
217
|
+
// radius: 100,
|
|
218
|
+
// fill: "radial-gradient(circle, rgba(99, 102, 241, 0.15) 0%, rgba(255, 255, 255, 0) 70%)",
|
|
219
|
+
// originX: "center",
|
|
220
|
+
// originY: "center",
|
|
221
|
+
// top: -120,
|
|
222
|
+
// selectable: false,
|
|
223
|
+
// evented: false,
|
|
224
|
+
// });
|
|
225
|
+
// 3. High-End Typography
|
|
226
|
+
const mainTitle = new Text("EasyFlow", {
|
|
227
|
+
top: -20,
|
|
228
|
+
fontSize: 84,
|
|
229
|
+
fontFamily: "'cursive', 'Segoe UI', Roboto, sans-serif",
|
|
230
|
+
fontWeight: "700",
|
|
231
|
+
fill: "#029AFF",
|
|
232
|
+
charSpacing: -20, // Tight kerning for a modern look
|
|
233
|
+
originX: "center",
|
|
234
|
+
});
|
|
235
|
+
const subTitle = new Text("Whiteboard", {
|
|
236
|
+
top: 55,
|
|
237
|
+
fontSize: 38,
|
|
238
|
+
fontFamily: "'Inter', 'Segoe UI', Roboto, sans-serif",
|
|
239
|
+
fontWeight: "300", // Thin contrast against the bold "EasyFlow"
|
|
240
|
+
fill: "#fff",
|
|
241
|
+
originX: "center",
|
|
242
|
+
});
|
|
243
|
+
const hint = new Text("SELECT A TOOL TO START CREATING", {
|
|
244
|
+
top: 110,
|
|
245
|
+
fontSize: 12,
|
|
246
|
+
fontFamily: "monospace", // Techy/Creative feel
|
|
247
|
+
fontWeight: "600",
|
|
248
|
+
fill: "#9ca3af",
|
|
249
|
+
charSpacing: 200, // Wide spacing for the hint
|
|
250
|
+
originX: "center",
|
|
251
|
+
});
|
|
252
|
+
// 4. Create Master Hero Group
|
|
253
|
+
const heroGroup = new Group([logoImg, mainTitle, subTitle, hint], {
|
|
254
|
+
left: centerX,
|
|
255
|
+
top: centerY + 20, // Start slightly lower for "float-up" animation
|
|
256
|
+
originX: "center",
|
|
257
|
+
originY: "center",
|
|
258
|
+
selectable: false,
|
|
259
|
+
evented: false,
|
|
260
|
+
opacity: 0,
|
|
261
|
+
});
|
|
262
|
+
heroGroup.id = "welcome-hero";
|
|
263
|
+
canvas.add(heroGroup);
|
|
264
|
+
// 5. "Float & Fade" Animation
|
|
265
|
+
heroGroup.animate({
|
|
266
|
+
opacity: 1,
|
|
267
|
+
top: centerY // Moves up 20px while fading in
|
|
268
|
+
}, {
|
|
269
|
+
duration: 1200,
|
|
270
|
+
easing: util.ease.easeOutQuart,
|
|
271
|
+
onChange: () => canvas.requestRenderAll(),
|
|
272
|
+
});
|
|
273
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/lib/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,UAAU,EAAE,MAAM,MAAM,CAAA;AAG5C,wBAAgB,EAAE,CAAC,GAAG,MAAM,EAAE,UAAU,EAAE,UAEzC"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { FabricObject } from "fabric";
|
|
2
|
+
export type Tool = "select" | "pan" | "pen" | "rectangle" | "circle" | "line" | "arrow" | "frame" | "text" | "image" | "eraser" | "connector" | "undo" | "redo";
|
|
3
|
+
export type ActiveDropdown = "task" | "document" | null;
|
|
4
|
+
export interface ToolOptions {
|
|
5
|
+
pen: {
|
|
6
|
+
color: string;
|
|
7
|
+
strokeWidth: number;
|
|
8
|
+
opacity: number;
|
|
9
|
+
strokeDashArray: number[] | null;
|
|
10
|
+
};
|
|
11
|
+
rectangle: {
|
|
12
|
+
fillColor: string;
|
|
13
|
+
strokeColor: string;
|
|
14
|
+
strokeWidth: number;
|
|
15
|
+
strokeDashArray: number[] | null;
|
|
16
|
+
};
|
|
17
|
+
circle: {
|
|
18
|
+
fillColor: string;
|
|
19
|
+
strokeColor: string;
|
|
20
|
+
strokeWidth: number;
|
|
21
|
+
strokeDashArray: number[] | null;
|
|
22
|
+
};
|
|
23
|
+
frame: {
|
|
24
|
+
fillColor: string;
|
|
25
|
+
strokeColor: string;
|
|
26
|
+
strokeWidth: number;
|
|
27
|
+
strokeDashArray: number[] | null;
|
|
28
|
+
};
|
|
29
|
+
text: {
|
|
30
|
+
fontSize: number;
|
|
31
|
+
fontFamily: string;
|
|
32
|
+
fontWeight: string;
|
|
33
|
+
textAlign: string;
|
|
34
|
+
color: string;
|
|
35
|
+
};
|
|
36
|
+
image: {
|
|
37
|
+
opacity: number;
|
|
38
|
+
filters: string[];
|
|
39
|
+
};
|
|
40
|
+
eraser: {
|
|
41
|
+
size: number;
|
|
42
|
+
};
|
|
43
|
+
line: {
|
|
44
|
+
strokeColor: string;
|
|
45
|
+
strokeWidth: number;
|
|
46
|
+
strokeDashArray: number[] | null;
|
|
47
|
+
};
|
|
48
|
+
arrow: {
|
|
49
|
+
strokeColor: string;
|
|
50
|
+
strokeWidth: number;
|
|
51
|
+
strokeDashArray: number[] | null;
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
interface WhiteboardState {
|
|
55
|
+
activeTool: Tool;
|
|
56
|
+
setActiveTool: (tool: Tool) => void;
|
|
57
|
+
activeDropdown: ActiveDropdown;
|
|
58
|
+
setActiveDropdown: (id: ActiveDropdown) => void;
|
|
59
|
+
selectedObjectType: string | null;
|
|
60
|
+
setSelectedObjectType: (type: string | null) => void;
|
|
61
|
+
canvasObjects: FabricObject[];
|
|
62
|
+
addCanvasObject: (obj: FabricObject) => void;
|
|
63
|
+
removeCanvasObject: (obj: FabricObject) => void;
|
|
64
|
+
clearCanvasObjects: () => void;
|
|
65
|
+
toolOptions: ToolOptions;
|
|
66
|
+
setToolOption: <T extends keyof ToolOptions>(tool: T, option: keyof ToolOptions[T], value: any) => void;
|
|
67
|
+
history: string[];
|
|
68
|
+
historyIndex: number;
|
|
69
|
+
canUndo: boolean;
|
|
70
|
+
canRedo: boolean;
|
|
71
|
+
pushHistory: (state: string) => void;
|
|
72
|
+
undo: () => string | null;
|
|
73
|
+
redo: () => string | null;
|
|
74
|
+
setCanUndo: (canUndo: boolean) => void;
|
|
75
|
+
setCanRedo: (canRedo: boolean) => void;
|
|
76
|
+
zoom: number;
|
|
77
|
+
setZoom: (zoom: number) => void;
|
|
78
|
+
selectedObjects: FabricObject[];
|
|
79
|
+
setSelectedObjects: (objects: FabricObject[]) => void;
|
|
80
|
+
}
|
|
81
|
+
export declare const useWhiteboardStore: import("zustand").UseBoundStore<Omit<import("zustand").StoreApi<WhiteboardState>, "setState" | "devtools"> & {
|
|
82
|
+
setState(partial: WhiteboardState | Partial<WhiteboardState> | ((state: WhiteboardState) => WhiteboardState | Partial<WhiteboardState>), replace?: false | undefined, action?: (string | {
|
|
83
|
+
[x: string]: unknown;
|
|
84
|
+
[x: number]: unknown;
|
|
85
|
+
[x: symbol]: unknown;
|
|
86
|
+
type: string;
|
|
87
|
+
}) | undefined): void;
|
|
88
|
+
setState(state: WhiteboardState | ((state: WhiteboardState) => WhiteboardState), replace: true, action?: (string | {
|
|
89
|
+
[x: string]: unknown;
|
|
90
|
+
[x: number]: unknown;
|
|
91
|
+
[x: symbol]: unknown;
|
|
92
|
+
type: string;
|
|
93
|
+
}) | undefined): void;
|
|
94
|
+
devtools: {
|
|
95
|
+
cleanup: () => void;
|
|
96
|
+
};
|
|
97
|
+
}>;
|
|
98
|
+
export {};
|
|
99
|
+
//# sourceMappingURL=whiteboard-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"whiteboard-store.d.ts","sourceRoot":"","sources":["../../src/store/whiteboard-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { create } from "zustand";
|
|
2
|
+
import { devtools } from "zustand/middleware";
|
|
3
|
+
const defaultToolOptions = {
|
|
4
|
+
pen: {
|
|
5
|
+
color: "#ffffff",
|
|
6
|
+
strokeWidth: 2,
|
|
7
|
+
opacity: 1,
|
|
8
|
+
strokeDashArray: null,
|
|
9
|
+
},
|
|
10
|
+
rectangle: {
|
|
11
|
+
fillColor: "transparent",
|
|
12
|
+
strokeColor: "#ffffff",
|
|
13
|
+
strokeWidth: 2,
|
|
14
|
+
strokeDashArray: null,
|
|
15
|
+
},
|
|
16
|
+
circle: {
|
|
17
|
+
fillColor: "transparent",
|
|
18
|
+
strokeColor: "#ffffff",
|
|
19
|
+
strokeWidth: 2,
|
|
20
|
+
strokeDashArray: null,
|
|
21
|
+
},
|
|
22
|
+
frame: {
|
|
23
|
+
fillColor: "#FFFFFF",
|
|
24
|
+
strokeColor: "#E5E7EB",
|
|
25
|
+
strokeWidth: 1,
|
|
26
|
+
strokeDashArray: null,
|
|
27
|
+
},
|
|
28
|
+
text: {
|
|
29
|
+
fontSize: 24,
|
|
30
|
+
fontFamily: "cursive",
|
|
31
|
+
color: "#ffffff",
|
|
32
|
+
fontWeight: "400",
|
|
33
|
+
textAlign: "left",
|
|
34
|
+
},
|
|
35
|
+
image: {
|
|
36
|
+
opacity: 1,
|
|
37
|
+
filters: [],
|
|
38
|
+
},
|
|
39
|
+
eraser: {
|
|
40
|
+
size: 20,
|
|
41
|
+
},
|
|
42
|
+
line: {
|
|
43
|
+
strokeColor: "#ffffff",
|
|
44
|
+
strokeWidth: 2,
|
|
45
|
+
strokeDashArray: null,
|
|
46
|
+
}, // ← ADD THIS
|
|
47
|
+
arrow: {
|
|
48
|
+
strokeColor: "#ffffff",
|
|
49
|
+
strokeWidth: 2,
|
|
50
|
+
strokeDashArray: null,
|
|
51
|
+
}, // ← ADD THIS
|
|
52
|
+
};
|
|
53
|
+
export const useWhiteboardStore = create()(devtools((set, get) => ({
|
|
54
|
+
// Tool state
|
|
55
|
+
activeTool: "select",
|
|
56
|
+
setActiveTool: (tool) => set({ activeTool: tool }),
|
|
57
|
+
// Selected object type (for editing)
|
|
58
|
+
selectedObjectType: null,
|
|
59
|
+
setSelectedObjectType: (type) => set({ selectedObjectType: type }),
|
|
60
|
+
// Canvas objects
|
|
61
|
+
canvasObjects: [],
|
|
62
|
+
addCanvasObject: (obj) => set((state) => ({
|
|
63
|
+
canvasObjects: [...state.canvasObjects, obj],
|
|
64
|
+
})),
|
|
65
|
+
removeCanvasObject: (obj) => set((state) => ({
|
|
66
|
+
canvasObjects: state.canvasObjects.filter((o) => o !== obj),
|
|
67
|
+
})),
|
|
68
|
+
clearCanvasObjects: () => set({ canvasObjects: [] }),
|
|
69
|
+
// Tool options
|
|
70
|
+
toolOptions: defaultToolOptions,
|
|
71
|
+
setToolOption: (tool, option, value) => set((state) => ({
|
|
72
|
+
toolOptions: {
|
|
73
|
+
...state.toolOptions,
|
|
74
|
+
[tool]: {
|
|
75
|
+
...state.toolOptions[tool],
|
|
76
|
+
[option]: value,
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
})),
|
|
80
|
+
// History
|
|
81
|
+
history: [],
|
|
82
|
+
historyIndex: -1,
|
|
83
|
+
canUndo: false,
|
|
84
|
+
canRedo: false,
|
|
85
|
+
pushHistory: (state) => {
|
|
86
|
+
const { history, historyIndex } = get();
|
|
87
|
+
// Slice off any redo states ahead of current index
|
|
88
|
+
const newHistory = history.slice(0, historyIndex + 1);
|
|
89
|
+
newHistory.push(state);
|
|
90
|
+
set({
|
|
91
|
+
history: newHistory,
|
|
92
|
+
historyIndex: newHistory.length - 1,
|
|
93
|
+
// Can undo if there is at least 1 previous state (index > 0)
|
|
94
|
+
canUndo: newHistory.length > 1,
|
|
95
|
+
canRedo: false,
|
|
96
|
+
});
|
|
97
|
+
},
|
|
98
|
+
// Sets the selected dropdown (task/document) - used to auto-switch back to select tool when closing
|
|
99
|
+
activeDropdown: null,
|
|
100
|
+
setActiveDropdown: (id) => set({
|
|
101
|
+
activeDropdown: id,
|
|
102
|
+
}),
|
|
103
|
+
undo: () => {
|
|
104
|
+
const { history, historyIndex } = get();
|
|
105
|
+
// Must have a previous state to go back to
|
|
106
|
+
if (historyIndex > 0) {
|
|
107
|
+
const newIndex = historyIndex - 1;
|
|
108
|
+
set({
|
|
109
|
+
historyIndex: newIndex,
|
|
110
|
+
canUndo: newIndex > 0,
|
|
111
|
+
canRedo: true,
|
|
112
|
+
});
|
|
113
|
+
return history[newIndex];
|
|
114
|
+
}
|
|
115
|
+
return null;
|
|
116
|
+
},
|
|
117
|
+
redo: () => {
|
|
118
|
+
const { history, historyIndex } = get();
|
|
119
|
+
if (historyIndex < history.length - 1) {
|
|
120
|
+
const newIndex = historyIndex + 1;
|
|
121
|
+
set({
|
|
122
|
+
historyIndex: newIndex,
|
|
123
|
+
canUndo: true,
|
|
124
|
+
canRedo: newIndex < history.length - 1,
|
|
125
|
+
});
|
|
126
|
+
return history[newIndex];
|
|
127
|
+
}
|
|
128
|
+
return null;
|
|
129
|
+
},
|
|
130
|
+
setCanUndo: (v) => set({ canUndo: v }),
|
|
131
|
+
setCanRedo: (v) => set({ canRedo: v }),
|
|
132
|
+
// Zoom
|
|
133
|
+
zoom: 1,
|
|
134
|
+
setZoom: (zoom) => set({ zoom }),
|
|
135
|
+
selectedObjects: [],
|
|
136
|
+
setSelectedObjects: (objects) => set({ selectedObjects: objects }),
|
|
137
|
+
}), { name: "fabric-whiteboard-store" }));
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface BaseNode {
|
|
2
|
+
id: string;
|
|
3
|
+
x: number;
|
|
4
|
+
y: number;
|
|
5
|
+
}
|
|
6
|
+
export interface TaskNodeData extends BaseNode {
|
|
7
|
+
type: "task";
|
|
8
|
+
title: string;
|
|
9
|
+
status: "todo" | "in-progress" | "done";
|
|
10
|
+
assignee?: string;
|
|
11
|
+
project?: string;
|
|
12
|
+
priority?: "low" | "medium" | "high";
|
|
13
|
+
dueDate?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface DocumentNodeData extends BaseNode {
|
|
16
|
+
type: "document";
|
|
17
|
+
title: string;
|
|
18
|
+
project: string;
|
|
19
|
+
breadcrumb?: string[];
|
|
20
|
+
preview: string;
|
|
21
|
+
updatedAt?: string;
|
|
22
|
+
}
|
|
23
|
+
export type CanvasNode = TaskNodeData | DocumentNodeData;
|
|
24
|
+
//# sourceMappingURL=canvas-node.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canvas-node.d.ts","sourceRoot":"","sources":["../../src/types/canvas-node.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,QAAQ;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED,MAAM,WAAW,YAAa,SAAQ,QAAQ;IAC5C,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,GAAG,aAAa,GAAG,MAAM,CAAC;IACxC,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,gBAAiB,SAAQ,QAAQ;IAChD,IAAI,EAAE,UAAU,CAAC;IACjB,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;CACpB;AAED,MAAM,MAAM,UAAU,GAAG,YAAY,GAAG,gBAAgB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mhamz.01/easyflow-whiteboard",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A feature-rich whiteboard component built with Fabric.js and React",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": ["dist"],
|
|
8
|
+
"keywords": ["whiteboard", "canvas", "fabric", "react", "drawing"],
|
|
9
|
+
"author": "Your Name",
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsc",
|
|
13
|
+
"prepublishOnly": "npm run build"
|
|
14
|
+
},
|
|
15
|
+
"peerDependencies": {
|
|
16
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
17
|
+
"react-dom": "^18.0.0 || ^19.0.0",
|
|
18
|
+
"fabric": "^7.0.0",
|
|
19
|
+
"zustand": "^5.0.0"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"lucide-react": "^0.563.0",
|
|
23
|
+
"clsx": "^2.1.1",
|
|
24
|
+
"tailwind-merge": "^3.4.0",
|
|
25
|
+
"class-variance-authority": "^0.7.1"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/react": "^19.0.0",
|
|
29
|
+
"@types/react-dom": "^19.0.0",
|
|
30
|
+
"@types/fabric": "^5.3.11",
|
|
31
|
+
"@types/node": "^20.0.0",
|
|
32
|
+
"typescript": "^5.0.0"
|
|
33
|
+
}
|
|
34
|
+
}
|