@mhamz.01/easyflow-whiteboard 2.178.0 → 2.181.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/README.md +429 -0
- package/dist/components/node/custom-node-overlay-layer.d.ts.map +1 -1
- package/dist/components/node/custom-node-overlay-layer.js +1 -0
- package/dist/components/node/custom-node.d.ts.map +1 -1
- package/dist/components/node/custom-node.js +1 -0
- package/dist/components/node/document-node.d.ts.map +1 -1
- package/dist/components/node/document-node.js +1 -0
- package/dist/components/node/hooks/useKeyboardShortcuts.d.ts.map +1 -1
- package/dist/components/node/hooks/useKeyboardShortcuts.js +5 -1
- package/dist/components/node/hooks/useNodeDrag.d.ts.map +1 -1
- package/dist/components/node/hooks/useNodeDrag.js +5 -1
- package/dist/components/node/hooks/useNodeState.d.ts.map +1 -1
- package/dist/components/node/hooks/useNodeState.js +1 -0
- package/dist/components/node/types/overlay-types.d.ts.map +1 -1
- package/dist/components/node/types/overlay-types.js +1 -0
- package/dist/components/toolbar/document-dropdown.d.ts.map +1 -1
- package/dist/components/toolbar/document-dropdown.js +1 -0
- package/dist/components/toolbar/layers-control.d.ts.map +1 -1
- package/dist/components/toolbar/layers-control.js +1 -0
- package/dist/components/toolbar/options/arrow-options.d.ts.map +1 -1
- package/dist/components/toolbar/options/arrow-options.js +1 -0
- package/dist/components/toolbar/options/erase-option.d.ts.map +1 -1
- package/dist/components/toolbar/options/erase-option.js +1 -0
- package/dist/components/toolbar/options/image-options.d.ts.map +1 -1
- package/dist/components/toolbar/options/image-options.js +1 -0
- package/dist/components/toolbar/options/line-options.d.ts.map +1 -1
- package/dist/components/toolbar/options/line-options.js +1 -0
- package/dist/components/toolbar/options/pen-option.d.ts.map +1 -1
- package/dist/components/toolbar/options/pen-option.js +1 -0
- package/dist/components/toolbar/options/shape-option.d.ts.map +1 -1
- package/dist/components/toolbar/options/shape-option.js +1 -0
- package/dist/components/toolbar/options/text-option.d.ts.map +1 -1
- package/dist/components/toolbar/options/text-option.js +1 -0
- package/dist/components/toolbar/task-dropdown.d.ts.map +1 -1
- package/dist/components/toolbar/task-dropdown.js +1 -0
- package/dist/components/toolbar/toolbar-button.d.ts.map +1 -1
- package/dist/components/toolbar/toolbar-seperator.d.ts.map +1 -1
- package/dist/components/toolbar/toolbar-skeleton/toolbar-skeleton.d.ts.map +1 -1
- package/dist/components/toolbar/toolbar-skeleton/toolbar-skeleton.js +1 -0
- package/dist/components/toolbar/tooloptions-panel.d.ts.map +1 -1
- package/dist/components/toolbar/tooloptions-panel.js +1 -0
- package/dist/components/toolbar/whiteboard-toolbar.d.ts.map +1 -1
- package/dist/components/toolbar/whiteboard-toolbar.js +13 -5
- package/dist/components/ui/dropdown-menu.d.ts.map +1 -1
- package/dist/components/ui/label.d.ts.map +1 -1
- package/dist/components/ui/slider.d.ts.map +1 -1
- package/dist/components/ui/slider.js +1 -0
- package/dist/components/whiteboard/whiteboard-test.d.ts +5 -1
- package/dist/components/whiteboard/whiteboard-test.d.ts.map +1 -1
- package/dist/components/whiteboard/whiteboard-test.js +25 -4
- package/dist/components/zoomcontrol/zoom-control.d.ts.map +1 -1
- package/dist/components/zoomcontrol/zoom-control.js +1 -0
- package/dist/hooks/useCanvasInit.d.ts.map +1 -1
- package/dist/hooks/useCanvasInit.js +7 -1
- package/dist/hooks/useCopyPaste.d.ts.map +1 -1
- package/dist/hooks/useCopyPaste.js +6 -1
- package/dist/hooks/useDrawing.d.ts.map +1 -1
- package/dist/hooks/useDrawing.js +1 -0
- package/dist/hooks/useEraser.d.ts.map +1 -1
- package/dist/hooks/useEraser.js +1 -0
- package/dist/hooks/useLiveUpdate.d.ts.map +1 -1
- package/dist/hooks/useLiveUpdate.js +1 -0
- package/dist/hooks/useMouseHandlers.d.ts.map +1 -1
- package/dist/hooks/useMouseHandlers.js +1 -0
- package/dist/hooks/usePan.d.ts.map +1 -1
- package/dist/hooks/usePan.js +1 -0
- package/dist/hooks/usePersistance.d.ts.map +1 -1
- package/dist/hooks/usePersistance.js +1 -0
- package/dist/hooks/useSelection.d.ts.map +1 -1
- package/dist/hooks/useSelection.js +1 -1
- package/dist/hooks/useTextStyle.d.ts.map +1 -1
- package/dist/hooks/useTextStyle.js +1 -0
- package/dist/hooks/useToolManager.d.ts.map +1 -1
- package/dist/hooks/useToolManager.js +19 -0
- package/dist/hooks/useZoom.d.ts.map +1 -1
- package/dist/hooks/useZoom.js +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/lib/fabric-arrow.d.ts.map +1 -1
- package/dist/lib/fabric-arrow.js +1 -0
- package/dist/lib/fabric-bidirectional-arrow.d.ts.map +1 -1
- package/dist/lib/fabric-bidirectional-arrow.js +1 -0
- package/dist/lib/fabric-frame.d.ts.map +1 -1
- package/dist/lib/fabric-frame.js +1 -1
- package/dist/lib/fabric-utils.d.ts.map +1 -1
- package/dist/lib/fabric-utils.js +1 -0
- package/dist/lib/utils.d.ts.map +1 -1
- package/dist/lib/utils.js +1 -0
- package/dist/store/whiteboard-store.d.ts +2 -0
- package/dist/store/whiteboard-store.d.ts.map +1 -1
- package/dist/store/whiteboard-store.js +4 -0
- package/dist/styles.css +6 -0
- package/dist/types/canvas-node.d.ts.map +1 -1
- package/dist/types/canvas-node.js +1 -0
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
# @mhamz.01/easyflow-whiteboard
|
|
2
|
+
|
|
3
|
+
A feature-rich, embeddable React whiteboard built on Fabric.js, Zustand, and TailwindCSS. Drop it into any React 18+ app and get a fully functional infinite canvas with drawing tools, HTML overlay nodes, and automatic persistence.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Table of Contents
|
|
8
|
+
|
|
9
|
+
- [Installation](#installation)
|
|
10
|
+
- [Quick Start](#quick-start)
|
|
11
|
+
- [Component API](#component-api)
|
|
12
|
+
- [Types](#types)
|
|
13
|
+
- [Store API](#store-api)
|
|
14
|
+
- [Available Tools](#available-tools)
|
|
15
|
+
- [Keyboard Shortcuts](#keyboard-shortcuts)
|
|
16
|
+
- [Persistence Pattern](#persistence-pattern)
|
|
17
|
+
- [Architecture Overview](#architecture-overview)
|
|
18
|
+
- [Directory Structure](#directory-structure)
|
|
19
|
+
- [Performance Design](#performance-design)
|
|
20
|
+
- [Built With](#built-with)
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install @mhamz.01/easyflow-whiteboard
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Peer Dependencies
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm install fabric@^7 react@^18 react-dom@^18 zustand@^5 \
|
|
34
|
+
@radix-ui/react-dropdown-menu@^2 \
|
|
35
|
+
@radix-ui/react-label@^2 \
|
|
36
|
+
@radix-ui/react-slider@^1
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Quick Start
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
import { EasyflowWhiteboard } from '@mhamz.01/easyflow-whiteboard';
|
|
45
|
+
import '@mhamz.01/easyflow-whiteboard/dist/styles.css';
|
|
46
|
+
|
|
47
|
+
export default function Page() {
|
|
48
|
+
return (
|
|
49
|
+
<div style={{ width: '100%', height: '100vh' }}>
|
|
50
|
+
<EasyflowWhiteboard />
|
|
51
|
+
</div>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
> **Next.js note:** The component uses `"use client"` internally. Wrap it in a dynamic import or a client boundary if you use the App Router.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Component API
|
|
61
|
+
|
|
62
|
+
### `<EasyflowWhiteboard />`
|
|
63
|
+
|
|
64
|
+
| Prop | Type | Default | Description |
|
|
65
|
+
|------|------|---------|-------------|
|
|
66
|
+
| `initialData` | `WhiteboardInitialData` | — | Pre-load saved canvas JSON and HTML nodes |
|
|
67
|
+
| `onSave` | `(payload: WhiteboardSavePayload) => void` | — | Debounced callback fired whenever the canvas changes |
|
|
68
|
+
| `saveDebounceMs` | `number` | `2000` | Debounce delay (ms) for `onSave` |
|
|
69
|
+
| `availableTasks` | `TaskTemplate[]` | `[]` | Tasks shown in the task picker dropdown |
|
|
70
|
+
| `availableDocuments` | `DocumentTemplate[]` | `[]` | Documents shown in the document picker dropdown |
|
|
71
|
+
| `isLoadingData` | `boolean` | `false` | Show toolbar skeleton while async data loads |
|
|
72
|
+
| `createNewTaskHref` | `string` | — | URL for the "Create new task" footer link |
|
|
73
|
+
| `createNewDocumentHref` | `string` | — | URL for the "Create new document" footer link |
|
|
74
|
+
| `editable` | `boolean` | `true` | Set `false` to make the canvas fully read-only. See [EDITABLE_MODE.md](./EDITABLE_MODE.md) |
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Types
|
|
79
|
+
|
|
80
|
+
### `WhiteboardInitialData`
|
|
81
|
+
|
|
82
|
+
Shape of the data you pass in to restore a saved board.
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
interface WhiteboardInitialData {
|
|
86
|
+
canvas?: string; // canvas.toJSON() string from a previous save
|
|
87
|
+
tasks?: TaskNodeData[];
|
|
88
|
+
documents?: DocumentNodeData[];
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### `WhiteboardSavePayload`
|
|
93
|
+
|
|
94
|
+
Shape of the data the `onSave` callback receives.
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
interface WhiteboardSavePayload {
|
|
98
|
+
canvas: string; // Serialized Fabric.js canvas state
|
|
99
|
+
tasks: TaskNodeData[];
|
|
100
|
+
documents: DocumentNodeData[];
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### `TaskTemplate` / `TaskNodeData`
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
interface TaskNodeData {
|
|
108
|
+
id: string;
|
|
109
|
+
type: "task";
|
|
110
|
+
title: string;
|
|
111
|
+
status: "todo" | "in-progress" | "done";
|
|
112
|
+
x: number; // World-space X position on the canvas
|
|
113
|
+
y: number; // World-space Y position on the canvas
|
|
114
|
+
assignee?: string;
|
|
115
|
+
project?: string;
|
|
116
|
+
priority?: "low" | "medium" | "high";
|
|
117
|
+
dueDate?: string;
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### `DocumentTemplate` / `DocumentNodeData`
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
interface DocumentNodeData {
|
|
125
|
+
id: string;
|
|
126
|
+
type: "document";
|
|
127
|
+
title: string;
|
|
128
|
+
project: string;
|
|
129
|
+
breadcrumb?: string[];
|
|
130
|
+
preview: string;
|
|
131
|
+
updatedAt?: string;
|
|
132
|
+
x: number;
|
|
133
|
+
y: number;
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Store API
|
|
140
|
+
|
|
141
|
+
Access and mutate whiteboard state from anywhere in your app:
|
|
142
|
+
|
|
143
|
+
```tsx
|
|
144
|
+
import { useWhiteboardStore } from '@mhamz.01/easyflow-whiteboard';
|
|
145
|
+
|
|
146
|
+
function MyComponent() {
|
|
147
|
+
const activeTool = useWhiteboardStore((s) => s.activeTool);
|
|
148
|
+
const setActiveTool = useWhiteboardStore((s) => s.setActiveTool);
|
|
149
|
+
|
|
150
|
+
return <button onClick={() => setActiveTool("pen")}>Pen</button>;
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### State Slices
|
|
155
|
+
|
|
156
|
+
| Field | Type | Description |
|
|
157
|
+
|-------|------|-------------|
|
|
158
|
+
| `activeTool` | `Tool` | Currently selected drawing tool |
|
|
159
|
+
| `setActiveTool` | `(tool: Tool) => void` | Switch the active tool programmatically |
|
|
160
|
+
| `toolOptions` | `ToolOptions` | Per-tool styling options (color, strokeWidth, etc.) |
|
|
161
|
+
| `setToolOption` | `(tool, option, value) => void` | Update a single tool option |
|
|
162
|
+
| `selectedObjects` | `FabricObject[]` | Fabric objects currently selected on canvas |
|
|
163
|
+
| `canUndo` / `canRedo` | `boolean` | Whether undo/redo is available |
|
|
164
|
+
| `zoom` | `number` | Current canvas zoom level (1 = 100%) |
|
|
165
|
+
| `bringToFront` | `(canvas) => void` | Move selected object to the top layer |
|
|
166
|
+
| `sendToBack` | `(canvas) => void` | Move selected object to the bottom layer |
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## Available Tools
|
|
171
|
+
|
|
172
|
+
| Tool ID | Shortcut | Description |
|
|
173
|
+
|---------|----------|-------------|
|
|
174
|
+
| `select` | `V` | Select and move Fabric objects |
|
|
175
|
+
| `pan` | `H` | Click-drag to pan the viewport; two-finger pinch to zoom |
|
|
176
|
+
| `pen` | `P` | Freehand drawing with configurable stroke |
|
|
177
|
+
| `rectangle` | `R` | Draw rectangles (drag to size) |
|
|
178
|
+
| `circle` | `C` | Draw circles (drag to size) |
|
|
179
|
+
| `frame` | `F` | Draw white frames — always rendered behind other objects |
|
|
180
|
+
| `line` | `L` | Draw straight lines |
|
|
181
|
+
| `arrow` | `A` | Draw lines with a filled arrowhead |
|
|
182
|
+
| `text` | `T` | Place editable inline text |
|
|
183
|
+
| `image` | `I` | Upload and place an image from disk |
|
|
184
|
+
| `eraser` | `E` | Brush-erase objects by painting over them |
|
|
185
|
+
| `undo` | `Ctrl+Z` | Undo the last canvas change |
|
|
186
|
+
| `redo` | `Ctrl+Shift+Z` | Redo the last undone change |
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## Keyboard Shortcuts
|
|
191
|
+
|
|
192
|
+
| Key | Action |
|
|
193
|
+
|-----|--------|
|
|
194
|
+
| `V H P R C F L A T I E` | Activate corresponding tool |
|
|
195
|
+
| `Delete` / `Backspace` | Delete selected objects or HTML nodes |
|
|
196
|
+
| `Ctrl+Z` | Undo |
|
|
197
|
+
| `Ctrl+Shift+Z` or `Ctrl+Y` | Redo |
|
|
198
|
+
| `Ctrl+C` | Copy selected Fabric objects |
|
|
199
|
+
| `Ctrl+V` | Paste with cascading offset |
|
|
200
|
+
| `Ctrl+A` | Select all HTML nodes (tasks + documents) |
|
|
201
|
+
| `Escape` | Clear HTML node selection |
|
|
202
|
+
| `Ctrl++` / `Ctrl+-` | Zoom in / out |
|
|
203
|
+
| `Ctrl+0` | Reset zoom to 100% |
|
|
204
|
+
| `Ctrl+Scroll` | Zoom to cursor |
|
|
205
|
+
| `Shift+Scroll` | Horizontal pan |
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## Persistence Pattern
|
|
210
|
+
|
|
211
|
+
The library owns the UI and interaction. You own the data storage:
|
|
212
|
+
|
|
213
|
+
```tsx
|
|
214
|
+
import { EasyflowWhiteboard, WhiteboardInitialData, WhiteboardSavePayload } from '@mhamz.01/easyflow-whiteboard';
|
|
215
|
+
import '@mhamz.01/easyflow-whiteboard/dist/styles.css';
|
|
216
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
217
|
+
|
|
218
|
+
export default function BoardPage({ boardId }: { boardId: string }) {
|
|
219
|
+
const [initialData, setInitialData] = useState<WhiteboardInitialData>();
|
|
220
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
221
|
+
|
|
222
|
+
// 1. Load saved state from your database on mount
|
|
223
|
+
useEffect(() => {
|
|
224
|
+
fetch(`/api/boards/${boardId}`)
|
|
225
|
+
.then(r => r.json())
|
|
226
|
+
.then(data => {
|
|
227
|
+
setInitialData({
|
|
228
|
+
canvas: data.canvasJson,
|
|
229
|
+
tasks: data.tasks,
|
|
230
|
+
documents: data.documents,
|
|
231
|
+
});
|
|
232
|
+
setIsLoading(false);
|
|
233
|
+
});
|
|
234
|
+
}, [boardId]);
|
|
235
|
+
|
|
236
|
+
// 2. Save on every debounced change
|
|
237
|
+
const handleSave = useCallback(async (payload: WhiteboardSavePayload) => {
|
|
238
|
+
await fetch(`/api/boards/${boardId}`, {
|
|
239
|
+
method: 'PUT',
|
|
240
|
+
body: JSON.stringify({
|
|
241
|
+
canvasJson: payload.canvas,
|
|
242
|
+
tasks: payload.tasks,
|
|
243
|
+
documents: payload.documents,
|
|
244
|
+
}),
|
|
245
|
+
});
|
|
246
|
+
}, [boardId]);
|
|
247
|
+
|
|
248
|
+
return (
|
|
249
|
+
<div style={{ width: '100%', height: '100vh' }}>
|
|
250
|
+
<EasyflowWhiteboard
|
|
251
|
+
initialData={initialData}
|
|
252
|
+
onSave={handleSave}
|
|
253
|
+
saveDebounceMs={2000}
|
|
254
|
+
isLoadingData={isLoading}
|
|
255
|
+
availableTasks={myProjectTasks}
|
|
256
|
+
availableDocuments={myProjectDocs}
|
|
257
|
+
createNewTaskHref="/tasks/new"
|
|
258
|
+
createNewDocumentHref="/docs/new"
|
|
259
|
+
/>
|
|
260
|
+
</div>
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
## Architecture Overview
|
|
268
|
+
|
|
269
|
+
```
|
|
270
|
+
EasyflowWhiteboard (whiteboard-test.tsx)
|
|
271
|
+
│
|
|
272
|
+
├── <canvas> Fabric.js v7 rendering surface
|
|
273
|
+
│ Handles: shapes, paths, images, arrows, frames, text, selection
|
|
274
|
+
│
|
|
275
|
+
├── CanvasOverlayLayer Absolutely-positioned HTML div
|
|
276
|
+
│ ├── TaskNode Rich task card (status, assignee, priority)
|
|
277
|
+
│ └── DocumentNode Rich document card (preview, breadcrumb)
|
|
278
|
+
│ Reads canvas viewport transform on every `after:render` to stay aligned
|
|
279
|
+
│
|
|
280
|
+
├── WhiteboardToolbar Fixed bottom bar
|
|
281
|
+
│ ├── Tool buttons (V H P R C F L A T I E)
|
|
282
|
+
│ ├── TaskDropdown Template picker → add task to canvas
|
|
283
|
+
│ └── DocumentDropdown Template picker → add doc to canvas
|
|
284
|
+
│
|
|
285
|
+
├── ToolOptionsPanel Left sidebar (appears for active tools)
|
|
286
|
+
│ └── PenOptions / ShapeOptions / TextOptions / ImageOptions / ...
|
|
287
|
+
│
|
|
288
|
+
└── ZoomControls Top-right zoom percentage pill
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### Two-layer rendering
|
|
292
|
+
|
|
293
|
+
Fabric.js renders vector objects (shapes, paths, images) on a `<canvas>` element. An HTML `<div>` overlay, transformed with the same `viewportTransform` matrix, renders interactive React cards (tasks, documents). The two layers share a single world coordinate system — placing a node at `(x: 100, y: 200)` positions it exactly over canvas coordinates `(100, 200)`.
|
|
294
|
+
|
|
295
|
+
### Hook composition
|
|
296
|
+
|
|
297
|
+
The root `FabricWhiteboard` component delegates every behaviour to a dedicated hook:
|
|
298
|
+
|
|
299
|
+
| Hook | Responsibility |
|
|
300
|
+
|------|---------------|
|
|
301
|
+
| `useCanvasInit` | Mount canvas, hydrate saved state, handle resize |
|
|
302
|
+
| `useToolManager` | Switch drawing mode and cursors on tool change |
|
|
303
|
+
| `useDrawing` | Mouse-drag shape creation |
|
|
304
|
+
| `useEraser` | Brush cursor, trail render, object deletion |
|
|
305
|
+
| `useMouseHandlers` | Single event router with rAF throttle |
|
|
306
|
+
| `usePan` | Viewport drag and pinch zoom |
|
|
307
|
+
| `useZoom` | Wheel zoom, keyboard shortcuts |
|
|
308
|
+
| `useSelection` | Fabric selection → store, rubber-band box |
|
|
309
|
+
| `useTextStyle` | Live font/color updates on selected text |
|
|
310
|
+
| `useLiveUpdate` | Live style updates on selected objects |
|
|
311
|
+
| `usePersistence` | Debounced auto-save + history push |
|
|
312
|
+
| `useCopyPaste` | Ctrl+C / Ctrl+V clipboard |
|
|
313
|
+
|
|
314
|
+
---
|
|
315
|
+
|
|
316
|
+
## Directory Structure
|
|
317
|
+
|
|
318
|
+
```
|
|
319
|
+
src/
|
|
320
|
+
├── index.ts Public API — component, store, and types
|
|
321
|
+
├── styles.css Component stylesheet (TailwindCSS, scoped)
|
|
322
|
+
│
|
|
323
|
+
├── store/
|
|
324
|
+
│ └── whiteboard-store.ts Zustand store (tools, history, zoom, layer order)
|
|
325
|
+
│
|
|
326
|
+
├── types/
|
|
327
|
+
│ └── canvas-node.ts TaskNodeData, DocumentNodeData, CanvasNode
|
|
328
|
+
│
|
|
329
|
+
├── lib/
|
|
330
|
+
│ ├── fabric-utils.ts Canvas setup helpers, shape updates, welcome animation
|
|
331
|
+
│ ├── fabric-arrow.ts Custom Arrow shape (Line + triangle arrowhead)
|
|
332
|
+
│ ├── fabric-bidirectional-arrow.ts Custom BidirectionalArrow shape (arrowheads both ends)
|
|
333
|
+
│ ├── fabric-frame.ts Custom Frame shape (white Rect, always behind)
|
|
334
|
+
│ └── utils.ts cn() — clsx + tailwind-merge
|
|
335
|
+
│
|
|
336
|
+
├── hooks/ Canvas behaviour hooks (consumed by whiteboard-test.tsx)
|
|
337
|
+
│ ├── useCanvasInit.ts Fabric canvas mount, hydration, resize
|
|
338
|
+
│ ├── useToolManager.ts Drawing mode + cursor switch on tool change
|
|
339
|
+
│ ├── useDrawing.ts Mouse-drag shape creation (rect, circle, etc.)
|
|
340
|
+
│ ├── useEraser.ts Eraser brush, trail, object deletion
|
|
341
|
+
│ ├── useMouseHandlers.ts Single event router with rAF throttle on mouse:move
|
|
342
|
+
│ ├── usePan.ts Viewport drag and two-finger pinch zoom
|
|
343
|
+
│ ├── useZoom.ts Wheel zoom, toolbar buttons, keyboard shortcuts
|
|
344
|
+
│ ├── useSelection.ts Fabric selection events → store + rubber-band box
|
|
345
|
+
│ ├── useTextStyle.ts Live font/size/color sync to selected IText
|
|
346
|
+
│ ├── useLiveUpdate.ts Live fill/stroke sync to selected objects
|
|
347
|
+
│ ├── usePersistance.ts Debounced onSave callback + undo history push
|
|
348
|
+
│ └── useCopyPaste.ts Ctrl+C / Ctrl+V with cascading paste offset
|
|
349
|
+
│
|
|
350
|
+
└── components/
|
|
351
|
+
├── whiteboard/
|
|
352
|
+
│ └── whiteboard-test.tsx Root component — composes all hooks and layers
|
|
353
|
+
│
|
|
354
|
+
├── node/ HTML overlay: draggable cards synced to canvas
|
|
355
|
+
│ ├── custom-node-overlay-layer.tsx Overlay orchestrator (renders all nodes)
|
|
356
|
+
│ ├── custom-node.tsx TaskNode — task card with status toggle
|
|
357
|
+
│ ├── document-node.tsx DocumentNode — document card with preview
|
|
358
|
+
│ ├── types/
|
|
359
|
+
│ │ └── overlay-types.ts Shared interfaces (Task, Document, DragState, etc.)
|
|
360
|
+
│ └── hooks/
|
|
361
|
+
│ ├── useNodeState.ts Local state + parent prop sync
|
|
362
|
+
│ ├── useNodeSelection.ts selectedIds set + selection guard refs
|
|
363
|
+
│ ├── useNodeDrag.ts Full drag lifecycle with rAF throttle
|
|
364
|
+
│ ├── useFabricSync.ts Fabric object:moving → HTML position delta
|
|
365
|
+
│ ├── useWheelZoom.ts Forwards wheel events from overlay to Fabric
|
|
366
|
+
│ ├── useSelectionBox.ts Rubber-band hit-test for HTML nodes
|
|
367
|
+
│ └── useKeyboardShortcuts.ts Ctrl+A, Escape, Delete for overlay nodes
|
|
368
|
+
│
|
|
369
|
+
├── toolbar/
|
|
370
|
+
│ ├── whiteboard-toolbar.tsx Bottom tool bar — buttons, shortcuts, dropdowns
|
|
371
|
+
│ ├── tooloptions-panel.tsx Left sidebar — properties for active tool or selection
|
|
372
|
+
│ ├── toolbar-button.tsx Reusable icon button with tooltip
|
|
373
|
+
│ ├── toolbar-seperator.tsx Vertical divider between toolbar groups
|
|
374
|
+
│ ├── task-dropdown.tsx Task template picker (opens above toolbar)
|
|
375
|
+
│ ├── document-dropdown.tsx Document template picker (opens above toolbar)
|
|
376
|
+
│ ├── layers-control.tsx Bring/send layer order controls
|
|
377
|
+
│ ├── options/ Per-tool options panels
|
|
378
|
+
│ │ ├── pen-option.tsx Stroke color, width, dash style
|
|
379
|
+
│ │ ├── shape-option.tsx Fill, stroke, dash style for rect/circle/frame
|
|
380
|
+
│ │ ├── text-option.tsx Font family, size, weight, alignment, color
|
|
381
|
+
│ │ ├── image-options.tsx Opacity and filter controls for images
|
|
382
|
+
│ │ ├── line-options.tsx Stroke color, width, dash style for lines
|
|
383
|
+
│ │ └── arrow-options.tsx Stroke color, width for arrows
|
|
384
|
+
│ └── toolbar-skeleton/
|
|
385
|
+
│ └── toolbar-skeleton.tsx Animated loading skeleton matching toolbar shape
|
|
386
|
+
│
|
|
387
|
+
├── ui/ Low-level Radix UI wrappers (no business logic)
|
|
388
|
+
│ ├── dropdown-menu.tsx Radix DropdownMenu
|
|
389
|
+
│ ├── label.tsx Radix Label
|
|
390
|
+
│ └── slider.tsx Radix Slider
|
|
391
|
+
│
|
|
392
|
+
└── zoomcontrol/
|
|
393
|
+
└── zoom-control.tsx Zoom percentage pill with +/− buttons
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
---
|
|
397
|
+
|
|
398
|
+
## Performance Design
|
|
399
|
+
|
|
400
|
+
The library is built to stay smooth at 60fps even during fast draws and large canvases.
|
|
401
|
+
|
|
402
|
+
| Pattern | Where | Why |
|
|
403
|
+
|---------|-------|-----|
|
|
404
|
+
| `requestRenderAll()` everywhere | All hooks | Defers paint to Fabric's rAF loop; never blocks the main thread |
|
|
405
|
+
| rAF throttle on `mouse:move` | `useMouseHandlers` | Raw pointer rate can be 200Hz; throttle to one frame |
|
|
406
|
+
| Props in refs, effects register once | All canvas hooks | Avoids tearing down/reattaching listeners on every render |
|
|
407
|
+
| `React.memo` + custom comparators | Toolbar, Overlay wrappers | Pan/zoom state changes don't re-render toolbar or node cards |
|
|
408
|
+
| Delta accumulator in `useFabricSync` | Node drag | Never drops position updates even when RAF guard is active |
|
|
409
|
+
| `requestIdleCallback` for JSON serialization | `usePersistence` | Expensive `canvas.toJSON()` runs during browser idle time |
|
|
410
|
+
| History capped at 50 snapshots | `whiteboard-store` | Each snapshot can be 100 KB+; cap prevents memory growth |
|
|
411
|
+
|
|
412
|
+
---
|
|
413
|
+
|
|
414
|
+
## Built With
|
|
415
|
+
|
|
416
|
+
| Dependency | Role |
|
|
417
|
+
|-----------|------|
|
|
418
|
+
| [Fabric.js v7](https://fabricjs.com/) | Canvas rendering engine |
|
|
419
|
+
| [Zustand v5](https://zustand-demo.pmnd.rs/) | Lightweight state management |
|
|
420
|
+
| [Radix UI](https://www.radix-ui.com/) | Accessible headless UI primitives |
|
|
421
|
+
| [Lucide React](https://lucide.dev/) | Icon set |
|
|
422
|
+
| [TailwindCSS v4](https://tailwindcss.com/) | Utility-first styling |
|
|
423
|
+
| [clsx](https://github.com/lukeed/clsx) + [tailwind-merge](https://github.com/dcastil/tailwind-merge) | Safe class name composition |
|
|
424
|
+
|
|
425
|
+
---
|
|
426
|
+
|
|
427
|
+
## License
|
|
428
|
+
|
|
429
|
+
MIT © Muhammad Hamza
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"custom-node-overlay-layer.d.ts","sourceRoot":"","sources":["../../../src/components/node/custom-node-overlay-layer.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"custom-node-overlay-layer.d.ts","sourceRoot":"","sources":["../../../src/components/node/custom-node-overlay-layer.tsx"],"names":[],"mappings":"AAIA,OAAO,KAA8B,MAAM,OAAO,CAAC;AAGnD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAUrE,YAAY,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;;AAE5D,wBA6KE"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
// HTML overlay orchestrator — renders task and document node cards positioned in canvas world-space coordinates.
|
|
3
4
|
import React, { useRef, useCallback } from "react";
|
|
4
5
|
import TaskNode from "./custom-node";
|
|
5
6
|
import DocumentNode from "./document-node";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"custom-node.d.ts","sourceRoot":"","sources":["../../../src/components/node/custom-node.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"custom-node.d.ts","sourceRoot":"","sources":["../../../src/components/node/custom-node.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D,UAAU,aAAc,SAAQ,aAAa;IAC3C,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;IACjB,cAAc,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,aAAa,GAAG,MAAM,KAAK,IAAI,CAAC;IAClF,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,CAAC,OAAO,UAAU,QAAQ,CAAC,EAC/B,EAAE,EACF,KAAK,EACL,MAAM,EACN,QAAQ,EACR,OAAO,EACP,QAAQ,EACR,OAAO,EACP,UAAkB,EAClB,QAAQ,EACR,WAAW,EACX,cAAc,EACd,IAAQ,GACT,EAAE,aAAa,2CAoIf"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
// TaskNode card — draggable HTML card showing task title, status, priority, assignee, and a one-click done toggle.
|
|
3
4
|
import { useState, useEffect } from "react";
|
|
4
5
|
import { Check, ListCheck, Calendar, User2, AlertCircle } from "lucide-react";
|
|
5
6
|
export default function TaskNode({ id, title, status, assignee, project, priority, dueDate, isSelected = false, onSelect, onDragStart, onStatusChange, zoom = 1, // Default to 1:1 scale
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"document-node.d.ts","sourceRoot":"","sources":["../../../src/components/node/document-node.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"document-node.d.ts","sourceRoot":"","sources":["../../../src/components/node/document-node.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D,UAAU,iBAAkB,SAAQ,aAAa;IAC/C,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,CAAC,OAAO,UAAU,YAAY,CAAC,EACnC,EAAE,EACF,KAAK,EACL,OAAO,EACP,UAAe,EACf,OAAO,EACP,SAAS,EACT,UAAkB,EAClB,QAAQ,EACR,WAAW,GACZ,EAAE,iBAAiB,2CAwHnB"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
// DocumentNode card — draggable HTML card showing document title, breadcrumb path, and an expandable content preview.
|
|
3
4
|
import { useState, useEffect } from "react";
|
|
4
5
|
import { ChevronDown, ChevronUp, Clock, ExternalLink, MoreHorizontal, Layers } from "lucide-react";
|
|
5
6
|
export default function DocumentNode({ id, title, project, breadcrumb = [], preview, updatedAt, isSelected = false, onSelect, onDragStart, }) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useKeyboardShortcuts.d.ts","sourceRoot":"","sources":["../../../../src/components/node/hooks/useKeyboardShortcuts.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,OAAO,CAAC;AACxE,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"useKeyboardShortcuts.d.ts","sourceRoot":"","sources":["../../../../src/components/node/hooks/useKeyboardShortcuts.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,OAAO,CAAC;AACxE,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAG7D,UAAU,yBAAyB;IACjC,aAAa,EAAE,gBAAgB,CAAC,IAAI,EAAE,CAAC,CAAC;IACxC,iBAAiB,EAAE,gBAAgB,CAAC,QAAQ,EAAE,CAAC,CAAC;IAChD,cAAc,EAAE,gBAAgB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;IAC9C,cAAc,EAAE,QAAQ,CAAC,cAAc,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IACtD,aAAa,EAAE,QAAQ,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IAChD,iBAAiB,EAAE,QAAQ,CAAC,cAAc,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;IACxD,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC;IACxC,iBAAiB,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,EAAE,KAAK,IAAI,CAAC;CACrD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,EACnC,aAAa,EACb,iBAAiB,EACjB,cAAc,EACd,cAAc,EACd,aAAa,EACb,iBAAiB,EACjB,aAAa,EACb,iBAAiB,GAClB,EAAE,yBAAyB,QA2C3B"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useEffect } from "react";
|
|
2
|
+
import { useWhiteboardStore } from "../../../store/whiteboard-store";
|
|
2
3
|
/**
|
|
3
4
|
* SRP: handles global keyboard shortcuts for the overlay.
|
|
4
5
|
*
|
|
@@ -11,6 +12,7 @@ import { useEffect } from "react";
|
|
|
11
12
|
* Delete/Backspace — delete selected nodes
|
|
12
13
|
*/
|
|
13
14
|
export function useKeyboardShortcuts({ localTasksRef, localDocumentsRef, selectedIdsRef, setSelectedIds, setLocalTasks, setLocalDocuments, onTasksUpdate, onDocumentsUpdate, }) {
|
|
15
|
+
const editable = useWhiteboardStore((s) => s.editable);
|
|
14
16
|
useEffect(() => {
|
|
15
17
|
const handleKeyDown = (e) => {
|
|
16
18
|
if (e.target instanceof HTMLInputElement ||
|
|
@@ -29,6 +31,8 @@ export function useKeyboardShortcuts({ localTasksRef, localDocumentsRef, selecte
|
|
|
29
31
|
return;
|
|
30
32
|
}
|
|
31
33
|
if ((e.key === "Delete" || e.key === "Backspace") && selectedIdsRef.current.size > 0) {
|
|
34
|
+
if (!editable)
|
|
35
|
+
return;
|
|
32
36
|
e.preventDefault();
|
|
33
37
|
const sel = selectedIdsRef.current;
|
|
34
38
|
const updatedTasks = localTasksRef.current.filter((t) => !sel.has(t.id));
|
|
@@ -42,5 +46,5 @@ export function useKeyboardShortcuts({ localTasksRef, localDocumentsRef, selecte
|
|
|
42
46
|
};
|
|
43
47
|
window.addEventListener("keydown", handleKeyDown);
|
|
44
48
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
45
|
-
}, [onTasksUpdate, onDocumentsUpdate]);
|
|
49
|
+
}, [onTasksUpdate, onDocumentsUpdate, editable]);
|
|
46
50
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useNodeDrag.d.ts","sourceRoot":"","sources":["../../../../src/components/node/hooks/useNodeDrag.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,SAAS,EACT,gBAAgB,EAChB,QAAQ,EACR,cAAc,EACd,UAAU,IAAI,eAAe,EAC7B,UAAU,IAAI,eAAe,EAC9B,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACrC,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAA4B,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"useNodeDrag.d.ts","sourceRoot":"","sources":["../../../../src/components/node/hooks/useNodeDrag.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,SAAS,EACT,gBAAgB,EAChB,QAAQ,EACR,cAAc,EACd,UAAU,IAAI,eAAe,EAC7B,UAAU,IAAI,eAAe,EAC9B,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACrC,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAA4B,MAAM,wBAAwB,CAAC;AAGvF,UAAU,gBAAgB;IACxB,cAAc,EAAE,gBAAgB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;IAC9C,kBAAkB,EAAE,gBAAgB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;IAClD,yBAAyB,EAAE,gBAAgB,CAAC,OAAO,CAAC,CAAC;IACrD,aAAa,EAAE,gBAAgB,CAAC,IAAI,EAAE,CAAC,CAAC;IACxC,iBAAiB,EAAE,gBAAgB,CAAC,QAAQ,EAAE,CAAC,CAAC;IAChD,YAAY,CAAC,EAAE,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACxC,aAAa,EAAE,QAAQ,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IAChD,iBAAiB,EAAE,QAAQ,CAAC,cAAc,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;IACxD,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC;IACxC,iBAAiB,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,EAAE,KAAK,IAAI,CAAC;CACrD;AAcD;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,EAC1B,cAAc,EACd,kBAAkB,EAClB,yBAAyB,EACzB,aAAa,EACb,iBAAiB,EACjB,YAAY,EACZ,aAAa,EACb,iBAAiB,EACjB,aAAa,EACb,iBAAiB,GAClB,EAAE,gBAAgB;;iBAEmC,MAAM,EAAE;;0BAcrD,MAAM,KAAG;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS;8BAWzC,MAAM,KAAK,eAAe,GAAG,eAAe;EAsLxD"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useState, useRef, useCallback, useEffect } from "react";
|
|
2
|
+
import { useWhiteboardStore } from "../../../store/whiteboard-store";
|
|
2
3
|
function readPointer(e) {
|
|
3
4
|
if ("touches" in e && e.touches.length > 0) {
|
|
4
5
|
return { clientX: e.touches[0].clientX, clientY: e.touches[0].clientY };
|
|
@@ -22,6 +23,7 @@ function readPointer(e) {
|
|
|
22
23
|
* matrix-decomposition vs matrix-multiplication divergence.
|
|
23
24
|
*/
|
|
24
25
|
export function useNodeDrag({ selectedIdsRef, dragSelectedIdsRef, htmlNodesSelectedByBoxRef, localTasksRef, localDocumentsRef, fabricCanvas, setLocalTasks, setLocalDocuments, onTasksUpdate, onDocumentsUpdate, }) {
|
|
26
|
+
const editable = useWhiteboardStore((s) => s.editable);
|
|
25
27
|
const [dragging, setDragging] = useState(null);
|
|
26
28
|
const dragStateRef = useRef({
|
|
27
29
|
isDragging: false,
|
|
@@ -42,6 +44,8 @@ export function useNodeDrag({ selectedIdsRef, dragSelectedIdsRef, htmlNodesSelec
|
|
|
42
44
|
return undefined;
|
|
43
45
|
}, [localTasksRef, localDocumentsRef]);
|
|
44
46
|
const handleDragStart = useCallback((itemId, e) => {
|
|
47
|
+
if (!editable)
|
|
48
|
+
return;
|
|
45
49
|
const canvas = fabricCanvas?.current;
|
|
46
50
|
if (!canvas)
|
|
47
51
|
return;
|
|
@@ -119,7 +123,7 @@ export function useNodeDrag({ selectedIdsRef, dragSelectedIdsRef, htmlNodesSelec
|
|
|
119
123
|
document.body.style.cursor = "grabbing";
|
|
120
124
|
document.body.style.userSelect = "none";
|
|
121
125
|
document.body.style.touchAction = "none";
|
|
122
|
-
}, [fabricCanvas, selectedIdsRef, dragSelectedIdsRef, getItemPosition]);
|
|
126
|
+
}, [fabricCanvas, selectedIdsRef, dragSelectedIdsRef, getItemPosition, editable]);
|
|
123
127
|
// Attach move/end listeners only while a drag is active (SRP for side effects).
|
|
124
128
|
useEffect(() => {
|
|
125
129
|
if (!dragging)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useNodeState.d.ts","sourceRoot":"","sources":["../../../../src/components/node/hooks/useNodeState.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useNodeState.d.ts","sourceRoot":"","sources":["../../../../src/components/node/hooks/useNodeState.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAE7D;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE;;;;;;;EAoBhE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"overlay-types.d.ts","sourceRoot":"","sources":["../../../../src/components/node/types/overlay-types.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"overlay-types.d.ts","sourceRoot":"","sources":["../../../../src/components/node/types/overlay-types.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"document-dropdown.d.ts","sourceRoot":"","sources":["../../../src/components/toolbar/document-dropdown.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"document-dropdown.d.ts","sourceRoot":"","sources":["../../../src/components/toolbar/document-dropdown.tsx"],"names":[],"mappings":"AAQA,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,UAAU,qBAAqB;IAC7B,aAAa,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAC/C,kBAAkB,CAAC,EAAE,gBAAgB,EAAE,CAAC;IACxC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAkDD,MAAM,CAAC,OAAO,UAAU,gBAAgB,CAAC,EACvC,aAAa,EACb,kBAAuB,EACvB,aAAa,GACd,EAAE,qBAAqB,2CAoGvB"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
// Document picker dropdown — lists document templates passed by the consumer; clicking one places it on the canvas as a DocumentNode.
|
|
3
4
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger } from "../ui/dropdown-menu";
|
|
4
5
|
import { FileText, Plus, Clock, Layers } from "lucide-react";
|
|
5
6
|
import { useWhiteboardStore } from "../../store/whiteboard-store";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"layers-control.d.ts","sourceRoot":"","sources":["../../../src/components/toolbar/layers-control.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"layers-control.d.ts","sourceRoot":"","sources":["../../../src/components/toolbar/layers-control.tsx"],"names":[],"mappings":"AAKA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAGhC,UAAU,kBAAkB;IAC1B,YAAY,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CAC9C;AAED,MAAM,CAAC,OAAO,UAAU,aAAa,CAAC,EAAE,YAAY,EAAE,EAAE,kBAAkB,2CAyEzE"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
// Layer ordering controls — bring to front/back and step forward/backward for the currently selected Fabric object.
|
|
3
4
|
import { ArrowUp, ArrowDown, ChevronsUp, ChevronsDown } from "lucide-react";
|
|
4
5
|
import { useWhiteboardStore } from "../../store/whiteboard-store";
|
|
5
6
|
export default function LayerControls({ fabricCanvas }) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"arrow-options.d.ts","sourceRoot":"","sources":["../../../../src/components/toolbar/options/arrow-options.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"arrow-options.d.ts","sourceRoot":"","sources":["../../../../src/components/toolbar/options/arrow-options.tsx"],"names":[],"mappings":"AAMA,OAAO,EAAuB,SAAS,EAAE,MAAM,OAAO,CAAC;AACvD,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAehC,UAAU,iBAAiB;IACzB,YAAY,CAAC,EAAE,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CACzC;AAED,MAAM,CAAC,OAAO,UAAU,YAAY,CAAC,EAAE,YAAY,EAAE,EAAE,iBAAiB,2CAiNvE"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
// Arrow tool options panel — stroke color, stroke width, and dash style for the arrow drawing tool.
|
|
3
4
|
import { useWhiteboardStore } from "../../../store/whiteboard-store";
|
|
4
5
|
import { cn } from "../../../lib/utils";
|
|
5
6
|
import { useEffect, useState } from "react";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"erase-option.d.ts","sourceRoot":"","sources":["../../../../src/components/toolbar/options/erase-option.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"erase-option.d.ts","sourceRoot":"","sources":["../../../../src/components/toolbar/options/erase-option.tsx"],"names":[],"mappings":"AAQA,MAAM,CAAC,OAAO,UAAU,aAAa,4CAgEpC"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
// Eraser tool options panel — brush size control for the eraser tool.
|
|
3
4
|
import { useWhiteboardStore } from "../../../store/whiteboard-store";
|
|
4
5
|
const PRESET_SIZES = [10, 20, 30, 50];
|
|
5
6
|
export default function EraserOptions() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"image-options.d.ts","sourceRoot":"","sources":["../../../../src/components/toolbar/options/image-options.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"image-options.d.ts","sourceRoot":"","sources":["../../../../src/components/toolbar/options/image-options.tsx"],"names":[],"mappings":"AAQA,MAAM,CAAC,OAAO,UAAU,YAAY,4CA8CnC"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
// Image tool options panel — opacity slider and filter controls for placed FabricImage objects.
|
|
3
4
|
import { useWhiteboardStore } from "../../../store/whiteboard-store";
|
|
4
5
|
import { Label } from "../../../components/ui/label";
|
|
5
6
|
import { Slider } from "../../../components/ui/slider";
|