@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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"line-options.d.ts","sourceRoot":"","sources":["../../../../src/components/toolbar/options/line-options.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"line-options.d.ts","sourceRoot":"","sources":["../../../../src/components/toolbar/options/line-options.tsx"],"names":[],"mappings":"AAyBA,MAAM,CAAC,OAAO,UAAU,WAAW,4CA0JlC"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
// Line tool options panel — stroke color, stroke width, and dash style for the line drawing tool.
|
|
3
4
|
import { useWhiteboardStore } from "../../../store/whiteboard-store";
|
|
4
5
|
import { cn } from "../../../lib/utils";
|
|
5
6
|
const PRESET_COLORS = [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pen-option.d.ts","sourceRoot":"","sources":["../../../../src/components/toolbar/options/pen-option.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"pen-option.d.ts","sourceRoot":"","sources":["../../../../src/components/toolbar/options/pen-option.tsx"],"names":[],"mappings":"AA4BA,MAAM,CAAC,OAAO,UAAU,UAAU,4CAgMjC"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
// Pen tool options panel — controls stroke color presets, stroke width presets + slider, and dash style (solid/dashed/dotted).
|
|
3
4
|
import { useWhiteboardStore } from "../../../store/whiteboard-store";
|
|
4
5
|
import { cn } from "../../../lib/utils";
|
|
5
6
|
const PRESET_COLORS = [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shape-option.d.ts","sourceRoot":"","sources":["../../../../src/components/toolbar/options/shape-option.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"shape-option.d.ts","sourceRoot":"","sources":["../../../../src/components/toolbar/options/shape-option.tsx"],"names":[],"mappings":"AAyBA,UAAU,iBAAiB;IACzB,SAAS,EAAE,WAAW,GAAG,QAAQ,GAAG,OAAO,CAAC;CAC7C;AAED,MAAM,CAAC,OAAO,UAAU,YAAY,CAAC,EAAE,SAAS,EAAE,EAAE,iBAAiB,2CA2MpE"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
// Shape tool options panel — fill color, stroke color, stroke width, and dash style for rect, circle, and frame tools.
|
|
3
4
|
import { useWhiteboardStore } from "../../../store/whiteboard-store";
|
|
4
5
|
import { cn } from "../../../lib/utils";
|
|
5
6
|
const PRESET_COLORS = [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"text-option.d.ts","sourceRoot":"","sources":["../../../../src/components/toolbar/options/text-option.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"text-option.d.ts","sourceRoot":"","sources":["../../../../src/components/toolbar/options/text-option.tsx"],"names":[],"mappings":"AA8CA,MAAM,CAAC,OAAO,UAAU,WAAW,4CA2NlC"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
// Text tool options panel — font family, font size, font weight, text alignment, and color controls.
|
|
3
4
|
import { useWhiteboardStore } from "../../../store/whiteboard-store";
|
|
4
5
|
import { cn } from "../../../lib/utils";
|
|
5
6
|
import { AlignLeft, AlignCenter, AlignRight, AlignJustify, ChevronDown } from "lucide-react";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"task-dropdown.d.ts","sourceRoot":"","sources":["../../../src/components/toolbar/task-dropdown.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"task-dropdown.d.ts","sourceRoot":"","sources":["../../../src/components/toolbar/task-dropdown.tsx"],"names":[],"mappings":"AAYA,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,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,UAAU,iBAAiB;IACzB,SAAS,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,IAAI,CAAC;IACxC,cAAc,CAAC,EAAE,YAAY,EAAE,CAAC;IAChC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AA2DD,MAAM,CAAC,OAAO,UAAU,YAAY,CAAC,EAAE,SAAS,EAAE,cAAmB,EAAE,aAAa,EAAE,EAAE,iBAAiB,2CA2GxG"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
// Task picker dropdown — lists task templates passed by the consumer; clicking one places it on the canvas as a TaskNode.
|
|
3
4
|
import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, } from "../ui/dropdown-menu";
|
|
4
5
|
import { CheckSquare, Plus, Calendar, Hash } from "lucide-react";
|
|
5
6
|
import { useWhiteboardStore } from "../../store/whiteboard-store";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toolbar-button.d.ts","sourceRoot":"","sources":["../../../src/components/toolbar/toolbar-button.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"toolbar-button.d.ts","sourceRoot":"","sources":["../../../src/components/toolbar/toolbar-button.tsx"],"names":[],"mappings":"AAKA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAG1C,UAAU,eAAe;IACvB,IAAI,EAAE,UAAU,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,CAAC,OAAO,UAAU,UAAU,CAAC,EACjC,IAAI,EAAE,IAAI,EACV,KAAK,EACL,QAAQ,EACR,OAAO,EACP,QAAgB,EAChB,QAAQ,GACT,EAAE,eAAe,2CAkDjB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toolbar-seperator.d.ts","sourceRoot":"","sources":["../../../src/components/toolbar/toolbar-seperator.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"toolbar-seperator.d.ts","sourceRoot":"","sources":["../../../src/components/toolbar/toolbar-seperator.tsx"],"names":[],"mappings":"AAKA,UAAU,qBAAqB;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,CAAC,OAAO,UAAU,gBAAgB,CAAC,EACvC,SAAc,GACf,EAAE,qBAAqB,2CAQvB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toolbar-skeleton.d.ts","sourceRoot":"","sources":["../../../../src/components/toolbar/toolbar-skeleton/toolbar-skeleton.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"toolbar-skeleton.d.ts","sourceRoot":"","sources":["../../../../src/components/toolbar/toolbar-skeleton/toolbar-skeleton.tsx"],"names":[],"mappings":"AAIA,wBAAgB,eAAe,4CA+D9B"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
// Animated loading skeleton matching the toolbar's pill-and-button layout — shown while the Fabric canvas initialises.
|
|
3
4
|
export function ToolbarSkeleton() {
|
|
4
5
|
return (_jsxs("div", { className: "fixed bottom-4 md:bottom-6 left-0 right-0 z-50 flex flex-col items-center gap-2.5 px-4 pointer-events-none", children: [_jsx("div", { className: "w-full md:w-auto max-w-full", children: _jsxs("div", { className: "flex items-center gap-[3px] px-2 py-1.5 bg-[rgba(5,5,5,0.97)] border border-[rgba(161,161,161,0.8)] rounded-[18px] w-fit overflow-hidden", children: [_jsx(SkBtn, { delay: 0 }), _jsx(SkBtn, { delay: 0.1 }), _jsx(Sep, {}), _jsx(SkBtn, { delay: 0.15 }), _jsx(SkBtn, { delay: 0.2 }), _jsx(SkBtn, { delay: 0.25 }), _jsx(SkBtn, { delay: 0.3 }), _jsx(SkBtn, { delay: 0.35 }), _jsx(SkBtn, { delay: 0.4 }), _jsx(SkBtn, { delay: 0.45 }), _jsx(SkBtn, { delay: 0.5 }), _jsx(Sep, {}), _jsx(SkPill, { width: 92, delay: 0.55, labelWidth: 44 }), _jsx("div", { className: "w-[3px]" }), _jsx(SkPill, { width: 106, delay: 0.65, labelWidth: 54 })] }) }), _jsxs("div", { className: "flex gap-[5px] items-center md:hidden", children: [_jsx("div", { className: "h-1 rounded-sm bg-white/55", style: { animation: "sk-dot 2s ease-in-out infinite" } }), _jsx("div", { className: "w-1 h-1 rounded-sm bg-white/15" })] }), _jsx("style", { children: `
|
|
5
6
|
.sk-sweep {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tooloptions-panel.d.ts","sourceRoot":"","sources":["../../../src/components/toolbar/tooloptions-panel.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"tooloptions-panel.d.ts","sourceRoot":"","sources":["../../../src/components/toolbar/tooloptions-panel.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,EAAE,SAAS,EAA6C,MAAM,OAAO,CAAC;AAUpF,OAAO,MAA4C,MAAM,QAAQ,CAAC;AAIlE,UAAU,qBAAqB;IAC7B,YAAY,EAAE,SAAS,CAAC,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CAC/C;;AAWD,wBA2PG"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
// Tool properties sidebar — slides in with the relevant options panel for the active tool or the type of the selected object.
|
|
3
4
|
import React, { useEffect, useMemo, useState, useCallback } from "react";
|
|
4
5
|
import { cn } from "../../lib/utils";
|
|
5
6
|
import { useWhiteboardStore } from "../../store/whiteboard-store";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"whiteboard-toolbar.d.ts","sourceRoot":"","sources":["../../../src/components/toolbar/whiteboard-toolbar.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"whiteboard-toolbar.d.ts","sourceRoot":"","sources":["../../../src/components/toolbar/whiteboard-toolbar.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,EAAqB,gBAAgB,EAAkC,MAAM,OAAO,CAAC;AAKnG,OAAO,EAAE,MAAM,EAAQ,MAAM,QAAQ,CAAC;AAQtC,UAAU,YAAY;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,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,UAAU,gBAAgB;IACxB,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;AAqBD,UAAU,sBAAsB;IAC9B,YAAY,EAAE,gBAAgB,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC9C,cAAc,EAAE,gBAAgB,CAAC,OAAO,CAAC,CAAC;IAC1C,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,IAAI,CAAC;IACzC,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAChD,cAAc,CAAC,EAAE,YAAY,EAAE,CAAC;IAChC,kBAAkB,CAAC,EAAE,gBAAgB,EAAE,CAAC;IACxC,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAChC;;AAUD,wBAoTG"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
// Bottom toolbar — renders all tool buttons, keyboard shortcuts, undo/redo, image upload, and node picker dropdowns.
|
|
3
4
|
import React, { useEffect, useRef, useState, useCallback, useMemo } from "react";
|
|
4
5
|
import { MousePointer2, Pencil, Square, Circle, Minus, MoveRight, Type, Eraser, Hand, Frame, Undo2, Redo2, Image as ImageIcon, } from "lucide-react";
|
|
5
6
|
import { util } from "fabric";
|
|
@@ -24,6 +25,8 @@ const TOOLS = [
|
|
|
24
25
|
{ id: "undo", icon: Undo2, label: "Undo", shortcut: "⌘Z", category: "history" },
|
|
25
26
|
{ id: "redo", icon: Redo2, label: "Redo", shortcut: "⌘⇧Z", category: "history" },
|
|
26
27
|
];
|
|
28
|
+
// Tools that remain usable in read-only mode — everything else mutates the canvas.
|
|
29
|
+
const READ_ONLY_ALLOWED_TOOLS = new Set(["select", "pan"]);
|
|
27
30
|
// ── Memoized ToolButton wrapper ───────────────────────────────────────────────
|
|
28
31
|
const MemoizedToolButton = React.memo(ToolButton);
|
|
29
32
|
const MemoizedToolbarSeparator = React.memo(ToolbarSeparator);
|
|
@@ -37,6 +40,7 @@ export default React.memo(function WhiteboardToolbar({ fabricCanvas, isRestoring
|
|
|
37
40
|
const canRedo = useWhiteboardStore((s) => s.canRedo);
|
|
38
41
|
const undo = useWhiteboardStore((s) => s.undo);
|
|
39
42
|
const redo = useWhiteboardStore((s) => s.redo);
|
|
43
|
+
const editable = useWhiteboardStore((s) => s.editable);
|
|
40
44
|
const scrollRef = useRef(null);
|
|
41
45
|
const fileInputRef = useRef(null);
|
|
42
46
|
const [scrollPosition, setScrollPosition] = useState(0);
|
|
@@ -65,6 +69,8 @@ export default React.memo(function WhiteboardToolbar({ fabricCanvas, isRestoring
|
|
|
65
69
|
}, [setActiveTool, isRestoringRef]);
|
|
66
70
|
// ── Handle tool click (memoized) ──────────────────────────────────────────────
|
|
67
71
|
const handleToolClick = useCallback((toolId) => {
|
|
72
|
+
if (!editable && !READ_ONLY_ALLOWED_TOOLS.has(toolId))
|
|
73
|
+
return;
|
|
68
74
|
const canvas = fabricCanvas.current;
|
|
69
75
|
if (!canvas)
|
|
70
76
|
return;
|
|
@@ -87,7 +93,7 @@ export default React.memo(function WhiteboardToolbar({ fabricCanvas, isRestoring
|
|
|
87
93
|
return;
|
|
88
94
|
}
|
|
89
95
|
setActiveTool(toolId);
|
|
90
|
-
}, [fabricCanvas, undo, redo, restoreCanvas, setActiveTool]);
|
|
96
|
+
}, [fabricCanvas, undo, redo, restoreCanvas, setActiveTool, editable]);
|
|
91
97
|
// ── Handle image upload (memoized) ────────────────────────────────────────────
|
|
92
98
|
const handleImageUpload = useCallback((e) => {
|
|
93
99
|
const file = e.target.files?.[0];
|
|
@@ -153,7 +159,7 @@ export default React.memo(function WhiteboardToolbar({ fabricCanvas, isRestoring
|
|
|
153
159
|
return;
|
|
154
160
|
}
|
|
155
161
|
if (key === "delete" || key === "backspace") {
|
|
156
|
-
if (fabricCanvas.current && activeTool === "select") {
|
|
162
|
+
if (editable && fabricCanvas.current && activeTool === "select") {
|
|
157
163
|
e.preventDefault();
|
|
158
164
|
deleteSelectedObjects(fabricCanvas.current);
|
|
159
165
|
}
|
|
@@ -172,11 +178,13 @@ export default React.memo(function WhiteboardToolbar({ fabricCanvas, isRestoring
|
|
|
172
178
|
};
|
|
173
179
|
window.addEventListener("keydown", onKey);
|
|
174
180
|
return () => window.removeEventListener("keydown", onKey);
|
|
175
|
-
}, [fabricCanvas, activeTool, handleToolClick]);
|
|
181
|
+
}, [fabricCanvas, activeTool, handleToolClick, editable]);
|
|
176
182
|
// ── Memoized tool buttons ─────────────────────────────────────────────────────
|
|
177
|
-
const toolButtons = useMemo(() => TOOLS.map((tool, index) => (_jsxs("div", { className: "flex items-center snap-center", children: [_jsx(MemoizedToolButton, { icon: tool.icon, label: tool.label, isActive: activeTool === tool.id, onClick: () => handleToolClick(tool.id), disabled: (tool.id === "undo" && !canUndo) ||
|
|
183
|
+
const toolButtons = useMemo(() => TOOLS.map((tool, index) => (_jsxs("div", { className: "flex items-center snap-center", children: [_jsx(MemoizedToolButton, { icon: tool.icon, label: tool.label, isActive: activeTool === tool.id, onClick: () => handleToolClick(tool.id), disabled: (tool.id === "undo" && !canUndo) ||
|
|
184
|
+
(tool.id === "redo" && !canRedo) ||
|
|
185
|
+
(!editable && !READ_ONLY_ALLOWED_TOOLS.has(tool.id)), shortcut: tool.shortcut }), (index === 1 || index === 9) && (_jsx("div", { className: "hidden md:block mx-1", children: _jsx(MemoizedToolbarSeparator, {}) }))] }, tool.id))), [activeTool, canUndo, canRedo, handleToolClick, editable]);
|
|
178
186
|
// ── Memoized dropdown section ─────────────────────────────────────────────────
|
|
179
|
-
const dropdownSection = useMemo(() => (onAddTask || onAddDocument) && (_jsxs("div", { className: "flex items-center gap-1 snap-center pr-2", children: [_jsx("div", { className: "mx-1 flex-shrink-0", children: _jsx(MemoizedToolbarSeparator, {}) }), onAddTask && (_jsx(MemoizedTaskDropdown, { onAddTask: onAddTask, availableTasks: availableTasks, createNewHref: createNewTaskHref })), onAddDocument && (_jsx(MemoizedDocumentDropdown, { onAddDocument: onAddDocument, availableDocuments: availableDocuments, createNewHref: createNewDocumentHref }))] })), [onAddTask, onAddDocument, availableTasks, availableDocuments, createNewTaskHref, createNewDocumentHref]);
|
|
187
|
+
const dropdownSection = useMemo(() => editable && (onAddTask || onAddDocument) && (_jsxs("div", { className: "flex items-center gap-1 snap-center pr-2", children: [_jsx("div", { className: "mx-1 flex-shrink-0", children: _jsx(MemoizedToolbarSeparator, {}) }), onAddTask && (_jsx(MemoizedTaskDropdown, { onAddTask: onAddTask, availableTasks: availableTasks, createNewHref: createNewTaskHref })), onAddDocument && (_jsx(MemoizedDocumentDropdown, { onAddDocument: onAddDocument, availableDocuments: availableDocuments, createNewHref: createNewDocumentHref }))] })), [onAddTask, onAddDocument, availableTasks, availableDocuments, createNewTaskHref, createNewDocumentHref, editable]);
|
|
180
188
|
// ── Scroll indicator visibility ───────────────────────────────────────────────
|
|
181
189
|
const scrollIndicators = useMemo(() => (_jsxs("div", { className: "flex gap-1.5 md:hidden", children: [_jsx("div", { className: `w-1.5 h-1.5 rounded-full transition-all duration-300 ${scrollPosition < 0.5 ? "bg-white w-6" : "bg-white/30"}` }), _jsx("div", { className: `w-1.5 h-1.5 rounded-full transition-all duration-300 ${scrollPosition >= 0.5 ? "bg-white w-6" : "bg-white/30"}` })] })), [scrollPosition]);
|
|
182
190
|
return (_jsxs(_Fragment, { children: [_jsx("input", { ref: fileInputRef, type: "file", accept: "image/*", onChange: handleImageUpload, className: "hidden" }), _jsxs("div", { className: "fixed bottom-4 md:bottom-6 left-0 right-0 z-50 flex flex-col items-center gap-2 px-4 pointer-events-none", children: [_jsxs("div", { className: "relative w-full md:w-auto max-w-full pointer-events-auto", children: [_jsx("div", { className: "absolute left-0 top-0 bottom-0 w-8 bg-gradient-to-r from-[#0b0b0b] to-transparent pointer-events-none z-10 md:hidden rounded-l-2xl" }), _jsx("div", { className: "absolute right-0 top-0 bottom-0 w-8 bg-gradient-to-l from-[#0b0b0b] to-transparent pointer-events-none z-10 md:hidden rounded-r-2xl" }), _jsxs("div", { ref: scrollRef, onScroll: handleScroll, className: "flex items-center gap-1 px-2 py-2 bg-black/95 backdrop-blur-md border border-[#A1A1A1] rounded-2xl shadow-2xl overflow-x-auto md:overflow-x-visible snap-x snap-mandatory md:snap-none scrollbar-hide scroll-smooth", children: [toolButtons, dropdownSection] })] }), scrollIndicators] }), _jsx("style", { children: `
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dropdown-menu.d.ts","sourceRoot":"","sources":["../../../src/components/ui/dropdown-menu.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"dropdown-menu.d.ts","sourceRoot":"","sources":["../../../src/components/ui/dropdown-menu.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,KAAK,qBAAqB,MAAM,+BAA+B,CAAC;AAGvE,iBAAS,YAAY,CAAC,EACpB,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,qBAAqB,CAAC,IAAI,CAAC,2CAEzD;AAED,iBAAS,kBAAkB,CAAC,EAC1B,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,qBAAqB,CAAC,MAAM,CAAC,2CAI3D;AAED,iBAAS,mBAAmB,CAAC,EAC3B,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,qBAAqB,CAAC,OAAO,CAAC,2CAO5D;AAED,iBAAS,mBAAmB,CAAC,EAC3B,SAAS,EACT,UAAc,EACd,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,qBAAqB,CAAC,OAAO,CAAC,2CAc5D;AAED,iBAAS,iBAAiB,CAAC,EACzB,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,qBAAqB,CAAC,KAAK,CAAC,2CAI1D;AAED,iBAAS,gBAAgB,CAAC,EACxB,SAAS,EACT,KAAK,EACL,OAAmB,EACnB,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,qBAAqB,CAAC,IAAI,CAAC,GAAG;IAC3D,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,OAAO,CAAC,EAAE,SAAS,GAAG,aAAa,CAAA;CACpC,2CAaA;AAED,iBAAS,wBAAwB,CAAC,EAChC,SAAS,EACT,QAAQ,EACR,OAAO,EACP,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,qBAAqB,CAAC,YAAY,CAAC,2CAmBjE;AAED,iBAAS,sBAAsB,CAAC,EAC9B,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,qBAAqB,CAAC,UAAU,CAAC,2CAO/D;AAED,iBAAS,qBAAqB,CAAC,EAC7B,SAAS,EACT,QAAQ,EACR,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,qBAAqB,CAAC,SAAS,CAAC,2CAkB9D;AAED,iBAAS,iBAAiB,CAAC,EACzB,SAAS,EACT,KAAK,EACL,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,qBAAqB,CAAC,KAAK,CAAC,GAAG;IAC5D,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB,2CAYA;AAED,iBAAS,qBAAqB,CAAC,EAC7B,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,qBAAqB,CAAC,SAAS,CAAC,2CAQ9D;AAED,iBAAS,oBAAoB,CAAC,EAC5B,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,2CAW9B;AAED,iBAAS,eAAe,CAAC,EACvB,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,qBAAqB,CAAC,GAAG,CAAC,2CAExD;AAED,iBAAS,sBAAsB,CAAC,EAC9B,SAAS,EACT,KAAK,EACL,QAAQ,EACR,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,qBAAqB,CAAC,UAAU,CAAC,GAAG;IACjE,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB,2CAeA;AAED,iBAAS,sBAAsB,CAAC,EAC9B,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,qBAAqB,CAAC,UAAU,CAAC,2CAW/D;AAED,OAAO,EACL,YAAY,EACZ,kBAAkB,EAClB,mBAAmB,EACnB,mBAAmB,EACnB,iBAAiB,EACjB,iBAAiB,EACjB,gBAAgB,EAChB,wBAAwB,EACxB,sBAAsB,EACtB,qBAAqB,EACrB,qBAAqB,EACrB,oBAAoB,EACpB,eAAe,EACf,sBAAsB,EACtB,sBAAsB,GACvB,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"label.d.ts","sourceRoot":"","sources":["../../../src/components/ui/label.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"label.d.ts","sourceRoot":"","sources":["../../../src/components/ui/label.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,KAAK,cAAc,MAAM,uBAAuB,CAAC;AAIxD,iBAAS,KAAK,CAAC,EACb,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,cAAc,CAAC,IAAI,CAAC,2CAWlD;AAED,OAAO,EAAE,KAAK,EAAE,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slider.d.ts","sourceRoot":"","sources":["../../../src/components/ui/slider.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"slider.d.ts","sourceRoot":"","sources":["../../../src/components/ui/slider.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,KAAK,eAAe,MAAM,wBAAwB,CAAC;AAI1D,iBAAS,MAAM,CAAC,EACd,SAAS,EACT,YAAY,EACZ,KAAK,EACL,GAAO,EACP,GAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,eAAe,CAAC,IAAI,CAAC,2CA8CnD;AAED,OAAO,EAAE,MAAM,EAAE,CAAA"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
// Radix Slider wrapper — accessible range slider component styled to match the library's dark theme.
|
|
3
4
|
import * as React from "react";
|
|
4
5
|
import * as SliderPrimitive from "@radix-ui/react-slider";
|
|
5
6
|
import { cn } from "../../lib/utils";
|
|
@@ -29,6 +29,10 @@ interface FabricWhiteboardProps {
|
|
|
29
29
|
documents: any[];
|
|
30
30
|
}) => void;
|
|
31
31
|
saveDebounceMs?: number;
|
|
32
|
+
createNewTaskHref?: string;
|
|
33
|
+
createNewDocumentHref?: string;
|
|
34
|
+
/** When false, the canvas becomes fully read-only: no drawing, selecting/moving/deleting objects, pasting, or adding nodes. Defaults to true. */
|
|
35
|
+
editable?: boolean;
|
|
32
36
|
}
|
|
33
|
-
export default function FabricWhiteboard({ initialData, onSave, saveDebounceMs, availableTasks, availableDocuments, isLoadingData, }: FabricWhiteboardProps): import("react/jsx-runtime").JSX.Element;
|
|
37
|
+
export default function FabricWhiteboard({ initialData, onSave, saveDebounceMs, availableTasks, availableDocuments, isLoadingData, createNewTaskHref, createNewDocumentHref, editable: editableProp, }: FabricWhiteboardProps): import("react/jsx-runtime").JSX.Element;
|
|
34
38
|
//# sourceMappingURL=whiteboard-test.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"whiteboard-test.d.ts","sourceRoot":"","sources":["../../../src/components/whiteboard/whiteboard-test.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"whiteboard-test.d.ts","sourceRoot":"","sources":["../../../src/components/whiteboard/whiteboard-test.tsx"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,6BAA6B,CAAC;AAkB5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAChE,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAGxD,YAAY,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AACvE,YAAY,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAIxE,UAAU,IAAI;IACZ,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,GAAG,aAAa,GAAG,MAAM,CAAC;IACxC,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;IACrC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,UAAU,qBAAqB;IAC7B,WAAW,CAAC,EAAE;QACZ,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,KAAK,CAAC,EAAE,IAAI,EAAE,CAAC;QACf,SAAS,CAAC,EAAE,QAAQ,EAAE,CAAC;KACxB,CAAC;IACF,cAAc,CAAC,EAAE,YAAY,EAAE,CAAC;IAChC,kBAAkB,CAAC,EAAE,gBAAgB,EAAE,CAAC;IACxC,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE;QACjB,MAAM,EAAE,MAAM,CAAC;QACf,KAAK,EAAE,GAAG,EAAE,CAAC;QACb,SAAS,EAAE,GAAG,EAAE,CAAC;KAClB,KAAK,IAAI,CAAC;IACX,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,iJAAiJ;IACjJ,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AA0HD,MAAM,CAAC,OAAO,UAAU,gBAAgB,CAAC,EACvC,WAAW,EACX,MAAM,EACN,cAAc,EACd,cAAmB,EACnB,kBAAuB,EACvB,aAAqB,EACrB,iBAAiB,EACjB,qBAAqB,EACrB,QAAQ,EAAE,YAAmB,GAC9B,EAAE,qBAAqB,2CAsUvB"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
// Root whiteboard component — composes all canvas hooks, the memoized toolbar, the HTML overlay, and zoom controls into a full editor.
|
|
3
4
|
import React, { useCallback, useRef, useState } from "react";
|
|
4
5
|
import { classRegistry } from "fabric";
|
|
5
6
|
import { useWhiteboardStore } from "../../store/whiteboard-store";
|
|
@@ -24,8 +25,8 @@ import { ToolbarSkeleton } from "../toolbar/toolbar-skeleton/toolbar-skeleton";
|
|
|
24
25
|
import { useCopyPaste } from "../../hooks/useCopyPaste";
|
|
25
26
|
classRegistry.setClass(Frame, "frame");
|
|
26
27
|
// ── Memoized Toolbar wrapper (Issue 10) ───────────────────────────────────────
|
|
27
|
-
const MemoizedToolbarWrapper = React.memo(function ToolbarWrapper({ canvasReady, fabricCanvasRef, isRestoringRef, onAddTask, onAddDocument, availableDocuments, availableTasks, isLoadingData, }) {
|
|
28
|
-
return canvasReady ? (_jsx(WhiteboardToolbar, { fabricCanvas: fabricCanvasRef, isRestoringRef: isRestoringRef, onAddTask: onAddTask, onAddDocument: onAddDocument, availableDocuments: availableDocuments, availableTasks: availableTasks, isLoadingData: isLoadingData })) : (_jsx(ToolbarSkeleton, {}));
|
|
28
|
+
const MemoizedToolbarWrapper = React.memo(function ToolbarWrapper({ canvasReady, fabricCanvasRef, isRestoringRef, onAddTask, onAddDocument, availableDocuments, availableTasks, isLoadingData, createNewTaskHref, createNewDocumentHref, }) {
|
|
29
|
+
return canvasReady ? (_jsx(WhiteboardToolbar, { fabricCanvas: fabricCanvasRef, isRestoringRef: isRestoringRef, onAddTask: onAddTask, onAddDocument: onAddDocument, availableDocuments: availableDocuments, availableTasks: availableTasks, isLoadingData: isLoadingData, createNewTaskHref: createNewTaskHref, createNewDocumentHref: createNewDocumentHref })) : (_jsx(ToolbarSkeleton, {}));
|
|
29
30
|
});
|
|
30
31
|
// ── Memoized Overlay wrapper (Issue 11) ───────────────────────────────────────
|
|
31
32
|
// This wrapper only updates when tasks/documents/canvasReady change
|
|
@@ -65,7 +66,7 @@ selectedCanvasObjects, clearHtmlSelectionRef, // ← destructure from props
|
|
|
65
66
|
prev.selectionBox === next.selectionBox &&
|
|
66
67
|
prev.selectedCanvasObjects === next.selectedCanvasObjects);
|
|
67
68
|
});
|
|
68
|
-
export default function FabricWhiteboard({ initialData, onSave, saveDebounceMs, availableTasks = [], availableDocuments = [], isLoadingData = false, }) {
|
|
69
|
+
export default function FabricWhiteboard({ initialData, onSave, saveDebounceMs, availableTasks = [], availableDocuments = [], isLoadingData = false, createNewTaskHref, createNewDocumentHref, editable: editableProp = true, }) {
|
|
69
70
|
// Refs
|
|
70
71
|
const canvasRef = useRef(null);
|
|
71
72
|
const fabricCanvasRef = useRef(null);
|
|
@@ -76,6 +77,9 @@ export default function FabricWhiteboard({ initialData, onSave, saveDebounceMs,
|
|
|
76
77
|
const suppressHistoryRef = useRef(false);
|
|
77
78
|
const isRestoringRef = useRef(false);
|
|
78
79
|
const clearHtmlSelectionRef = useRef(() => { });
|
|
80
|
+
// Read by the (stable, []-deps) add-task/add-document callbacks below so they
|
|
81
|
+
// stay no-ops in read-only mode without needing to be recreated on toggle.
|
|
82
|
+
const editableRef = useRef(editableProp);
|
|
79
83
|
// Eraser refs
|
|
80
84
|
const eraserTraceRef = useRef(null);
|
|
81
85
|
const eraserTargetsRef = useRef(new Set());
|
|
@@ -87,6 +91,19 @@ export default function FabricWhiteboard({ initialData, onSave, saveDebounceMs,
|
|
|
87
91
|
const toolOptions = useWhiteboardStore((state) => state.toolOptions);
|
|
88
92
|
const addCanvasObject = useWhiteboardStore((state) => state.addCanvasObject);
|
|
89
93
|
const pushHistory = useWhiteboardStore((state) => state.pushHistory);
|
|
94
|
+
const setEditable = useWhiteboardStore((state) => state.setEditable);
|
|
95
|
+
const setActiveTool = useWhiteboardStore((state) => state.setActiveTool);
|
|
96
|
+
// Sync the editable prop into the shared store so every hook/component that
|
|
97
|
+
// needs to gate mutations (tool manager, copy-paste, toolbar, node drag,
|
|
98
|
+
// keyboard shortcuts) can read a single source of truth. Forcing the active
|
|
99
|
+
// tool back to "select" when locking down also neutralizes the drawing/
|
|
100
|
+
// eraser mouse handlers, which key strictly off activeTool.
|
|
101
|
+
React.useEffect(() => {
|
|
102
|
+
editableRef.current = editableProp;
|
|
103
|
+
setEditable(editableProp);
|
|
104
|
+
if (!editableProp)
|
|
105
|
+
setActiveTool("select");
|
|
106
|
+
}, [editableProp, setEditable, setActiveTool]);
|
|
90
107
|
// State
|
|
91
108
|
const [tasks, setTasks] = useState([]);
|
|
92
109
|
const [canvasReady, setCanvasReady] = useState(false);
|
|
@@ -224,6 +241,8 @@ export default function FabricWhiteboard({ initialData, onSave, saveDebounceMs,
|
|
|
224
241
|
});
|
|
225
242
|
// ── Memoized static handlers ──────────────────────────────────────────────────
|
|
226
243
|
const handleAddTaskFromDropdown = useCallback((taskTemplate) => {
|
|
244
|
+
if (!editableRef.current)
|
|
245
|
+
return;
|
|
227
246
|
const canvas = fabricCanvasRef.current;
|
|
228
247
|
if (!canvas)
|
|
229
248
|
return;
|
|
@@ -244,6 +263,8 @@ export default function FabricWhiteboard({ initialData, onSave, saveDebounceMs,
|
|
|
244
263
|
]);
|
|
245
264
|
}, []);
|
|
246
265
|
const handleAddDocumentFromDropdown = useCallback((docTemplate) => {
|
|
266
|
+
if (!editableRef.current)
|
|
267
|
+
return;
|
|
247
268
|
const canvas = fabricCanvasRef.current;
|
|
248
269
|
if (!canvas)
|
|
249
270
|
return;
|
|
@@ -291,5 +312,5 @@ export default function FabricWhiteboard({ initialData, onSave, saveDebounceMs,
|
|
|
291
312
|
backgroundImage: `radial-gradient(circle, rgba(255,255,255,0.2) 1.2px, transparent 1.2px)`,
|
|
292
313
|
backgroundSize: "40px 40px",
|
|
293
314
|
zIndex: 0,
|
|
294
|
-
} }), _jsx("canvas", { ref: canvasRef, className: "absolute inset-0", style: { zIndex: 1 } }), _jsx(MemoizedOverlayWrapper, { tasks: tasks, documents: documents, canvasReady: canvasReady, fabricCanvasRef: fabricCanvasRef, onTasksUpdate: setTasks, onDocumentsUpdate: setDocuments, selectionBox: selectionBox, selectedCanvasObjects: selectedCanvasObjects, clearHtmlSelectionRef: clearHtmlSelectionRef }), _jsxs("div", { className: "absolute inset-0 pointer-events-none", style: { zIndex: 100 }, children: [_jsx("div", { className: "pointer-events-auto", children: _jsx(MemoizedToolbarWrapper, { canvasReady: canvasReady, fabricCanvasRef: fabricCanvasRef, isRestoringRef: isRestoringRef, onAddTask: handleAddTaskFromDropdown, onAddDocument: handleAddDocumentFromDropdown, availableDocuments: availableDocuments, availableTasks: availableTasks, isLoadingData: isLoadingData }) }), _jsx("div", { className: "pointer-events-auto", children: _jsx(ToolOptionsPanel, { fabricCanvas: fabricCanvasRef }) }), _jsx("div", { className: "pointer-events-auto", children: _jsx(ZoomControls, { zoom: canvasZoom, onZoomIn: handleZoomIn, onZoomOut: handleZoomOut, onResetZoom: handleResetZoom }) })] })] }) }));
|
|
315
|
+
} }), _jsx("canvas", { ref: canvasRef, className: "absolute inset-0", style: { zIndex: 1 } }), _jsx(MemoizedOverlayWrapper, { tasks: tasks, documents: documents, canvasReady: canvasReady, fabricCanvasRef: fabricCanvasRef, onTasksUpdate: setTasks, onDocumentsUpdate: setDocuments, selectionBox: selectionBox, selectedCanvasObjects: selectedCanvasObjects, clearHtmlSelectionRef: clearHtmlSelectionRef }), _jsxs("div", { className: "absolute inset-0 pointer-events-none", style: { zIndex: 100 }, children: [_jsx("div", { className: "pointer-events-auto", children: _jsx(MemoizedToolbarWrapper, { canvasReady: canvasReady, fabricCanvasRef: fabricCanvasRef, isRestoringRef: isRestoringRef, onAddTask: handleAddTaskFromDropdown, onAddDocument: handleAddDocumentFromDropdown, availableDocuments: availableDocuments, availableTasks: availableTasks, isLoadingData: isLoadingData, createNewTaskHref: createNewTaskHref, createNewDocumentHref: createNewDocumentHref }) }), _jsx("div", { className: "pointer-events-auto", children: _jsx(ToolOptionsPanel, { fabricCanvas: fabricCanvasRef }) }), _jsx("div", { className: "pointer-events-auto", children: _jsx(ZoomControls, { zoom: canvasZoom, onZoomIn: handleZoomIn, onZoomOut: handleZoomOut, onResetZoom: handleResetZoom }) })] })] }) }));
|
|
295
316
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"zoom-control.d.ts","sourceRoot":"","sources":["../../../src/components/zoomcontrol/zoom-control.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"zoom-control.d.ts","sourceRoot":"","sources":["../../../src/components/zoomcontrol/zoom-control.tsx"],"names":[],"mappings":"AAMA,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,2CA+DnB"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
// Zoom controls pill — displays current zoom percentage; + zooms in, − zooms out, clicking the number resets to 100%.
|
|
3
4
|
import { Minus, Plus } from "lucide-react";
|
|
4
5
|
export default function ZoomControls({ zoom, onZoomIn, onZoomOut, onResetZoom, }) {
|
|
5
6
|
const zoomPercentage = Math.round(zoom * 100);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useCanvasInit.d.ts","sourceRoot":"","sources":["../../src/hooks/useCanvasInit.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useCanvasInit.d.ts","sourceRoot":"","sources":["../../src/hooks/useCanvasInit.ts"],"names":[],"mappings":"AAEA,OAAO,EAAa,SAAS,EAAY,MAAM,OAAO,CAAC;AACvD,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAMhC,MAAM,WAAW,qBAAqB;IACpC,MAAM,CAAC,EAAK,MAAM,CAAC;IACnB,KAAK,CAAC,EAAM,GAAG,EAAE,CAAC;IAClB,SAAS,CAAC,EAAE,GAAG,EAAE,CAAC;CACnB;AAED,UAAU,kBAAkB;IAC1B,SAAS,EAAW,SAAS,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAAC;IACxD,eAAe,EAAK,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC7C,UAAU,EAAU,MAAM,CAAC;IAC3B,kBAAkB,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC;IACvC,cAAc,EAAM,SAAS,CAAC,OAAO,CAAC,CAAC;IACvC,WAAW,EAAS,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,QAAQ,EAAY,CAAC,KAAK,EAAE,GAAG,EAAE,KAAK,IAAI,CAAC;IAC3C,YAAY,EAAQ,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAC;IAC1C,WAAW,CAAC,EAAQ,qBAAqB,CAAC;IAC1C,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAID,eAAO,MAAM,aAAa,GAAI,4IAW3B,kBAAkB,SA+HpB,CAAC"}
|
|
@@ -1,8 +1,14 @@
|
|
|
1
|
+
// Initialises the Fabric canvas on mount, hydrates saved state (canvas JSON + HTML nodes), and wires resize/touch handlers.
|
|
1
2
|
import { useEffect } from "react";
|
|
2
3
|
import { Canvas } from "fabric";
|
|
3
4
|
import { initializeFabricCanvas, addWelcomeContent } from "../lib/fabric-utils";
|
|
5
|
+
import { useWhiteboardStore } from "../store/whiteboard-store";
|
|
4
6
|
// ─── Hook ─────────────────────────────────────────────────────────────────────
|
|
5
7
|
export const useCanvasInit = ({ canvasRef, fabricCanvasRef, activeTool, suppressHistoryRef, isRestoringRef, pushHistory, setTasks, setDocuments, initialData, onReady, }) => {
|
|
8
|
+
// Seeds the canvas's initial interactivity. useToolManager takes over from
|
|
9
|
+
// here and reacts to later editable toggles — this only avoids a flash of
|
|
10
|
+
// selectable objects on a read-only mount.
|
|
11
|
+
const editable = useWhiteboardStore((s) => s.editable);
|
|
6
12
|
useEffect(() => {
|
|
7
13
|
if (!canvasRef.current || fabricCanvasRef.current)
|
|
8
14
|
return;
|
|
@@ -10,7 +16,7 @@ export const useCanvasInit = ({ canvasRef, fabricCanvasRef, activeTool, suppress
|
|
|
10
16
|
width: window.innerWidth,
|
|
11
17
|
height: window.innerHeight,
|
|
12
18
|
backgroundColor: "transparent",
|
|
13
|
-
selection:
|
|
19
|
+
selection: editable,
|
|
14
20
|
allowTouchScrolling: false,
|
|
15
21
|
stopContextMenu: true,
|
|
16
22
|
objectCaching: true,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useCopyPaste.d.ts","sourceRoot":"","sources":["../../src/hooks/useCopyPaste.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useCopyPaste.d.ts","sourceRoot":"","sources":["../../src/hooks/useCopyPaste.ts"],"names":[],"mappings":"AAEA,OAAO,EAAqB,SAAS,EAAE,MAAM,OAAO,CAAC;AACrD,OAAO,EAAE,MAAM,EAAiC,MAAM,QAAQ,CAAC;AAG/D,UAAU,iBAAiB;IACzB,YAAY,EAAE,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CACxC;AAED,eAAO,MAAM,YAAY,GAAI,kBAAkB,iBAAiB,SAqE/D,CAAC"}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
// Ctrl+C / Ctrl+V clipboard for Fabric objects — clones on copy, re-clones on paste with a cascading 10px offset.
|
|
1
2
|
import { useEffect, useRef } from "react";
|
|
2
3
|
import { ActiveSelection } from "fabric";
|
|
4
|
+
import { useWhiteboardStore } from "../store/whiteboard-store";
|
|
3
5
|
export const useCopyPaste = ({ fabricCanvas }) => {
|
|
4
6
|
const clipboardRef = useRef(null);
|
|
7
|
+
const editable = useWhiteboardStore((s) => s.editable);
|
|
5
8
|
useEffect(() => {
|
|
6
9
|
const handleKeyDown = async (e) => {
|
|
7
10
|
if (e.target instanceof HTMLInputElement ||
|
|
@@ -22,6 +25,8 @@ export const useCopyPaste = ({ fabricCanvas }) => {
|
|
|
22
25
|
}
|
|
23
26
|
// ── Paste ────────────────────────────────────────────────────────────
|
|
24
27
|
if ((e.ctrlKey || e.metaKey) && e.key === "v") {
|
|
28
|
+
if (!editable)
|
|
29
|
+
return;
|
|
25
30
|
const copied = clipboardRef.current;
|
|
26
31
|
if (!copied)
|
|
27
32
|
return;
|
|
@@ -58,5 +63,5 @@ export const useCopyPaste = ({ fabricCanvas }) => {
|
|
|
58
63
|
};
|
|
59
64
|
window.addEventListener("keydown", handleKeyDown);
|
|
60
65
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
61
|
-
}, [fabricCanvas]);
|
|
66
|
+
}, [fabricCanvas, editable]);
|
|
62
67
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useDrawing.d.ts","sourceRoot":"","sources":["../../src/hooks/useDrawing.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useDrawing.d.ts","sourceRoot":"","sources":["../../src/hooks/useDrawing.ts"],"names":[],"mappings":"AAEA,OAAO,EAAU,SAAS,EAAE,MAAM,OAAO,CAAC;AAC1C,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,iBAAiB,EAAsB,MAAM,QAAQ,CAAC;AAMrF,UAAU,eAAe;IACvB,YAAY,EAAQ,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC7C,UAAU,EAAU,MAAM,CAAC;IAC3B,WAAW,EAAS,GAAG,CAAC;IACxB,YAAY,EAAQ,SAAS,CAAC,OAAO,CAAC,CAAC;IACvC,aAAa,EAAO,SAAS,CAAC;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC,CAAC;IAC/D,eAAe,EAAK,SAAS,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;IACnD,kBAAkB,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC;IACvC,WAAW,EAAS,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,eAAe,EAAK,CAAC,GAAG,EAAE,YAAY,KAAK,IAAI,CAAC;CACjD;AAED,eAAO,MAAM,UAAU,GAAI,4IAUxB,eAAe;2BAmBS,iBAAiB;2BAsHjB,iBAAiB;;CA0C3C,CAAC"}
|
package/dist/hooks/useDrawing.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useEraser.d.ts","sourceRoot":"","sources":["../../src/hooks/useEraser.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useEraser.d.ts","sourceRoot":"","sources":["../../src/hooks/useEraser.ts"],"names":[],"mappings":"AAEA,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"}
|
package/dist/hooks/useEraser.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// Eraser tool — renders a brush cursor and trail path, then deletes any Fabric objects intersected on mouse-up.
|
|
1
2
|
import { Circle } from "fabric";
|
|
2
3
|
import * as fabric from "fabric";
|
|
3
4
|
export const useEraser = ({ fabricCanvas, activeTool, toolOptions, eraserTraceRef, eraserTargetsRef, eraserActiveRef, eraserPathRef, eraserPathPointsRef, suppressHistoryRef, pushHistory, }) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useLiveUpdate.d.ts","sourceRoot":"","sources":["../../src/hooks/useLiveUpdate.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useLiveUpdate.d.ts","sourceRoot":"","sources":["../../src/hooks/useLiveUpdate.ts"],"names":[],"mappings":"AAEA,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,SAuD9E,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useMouseHandlers.d.ts","sourceRoot":"","sources":["../../src/hooks/useMouseHandlers.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useMouseHandlers.d.ts","sourceRoot":"","sources":["../../src/hooks/useMouseHandlers.ts"],"names":[],"mappings":"AAEA,OAAO,EAAqB,SAAS,EAAE,MAAM,OAAO,CAAC;AACrD,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,UAAU,qBAAqB;IAC7B,YAAY,EAAE,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACvC,UAAU,EAAI,MAAM,CAAC;IACrB,WAAW,EAAG,GAAG,CAAC;IAClB,eAAe,EAAE;QACf,eAAe,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,IAAI,CAAC;QACpC,eAAe,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,IAAI,CAAC;QACpC,aAAa,EAAI,MAAM,IAAI,CAAC;KAC7B,CAAC;IACF,cAAc,EAAE;QACd,eAAe,EAAG,MAAM,IAAI,CAAC;QAC7B,cAAc,EAAI,MAAM,IAAI,CAAC;QAC7B,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,EAAI,MAAM,OAAO,CAAC;KACjC,CAAC;CACH;AAED,eAAO,MAAM,gBAAgB,GAAI,gEAK9B,qBAAqB,SAqFvB,CAAC"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// Canvas event router — registers Fabric mouse events once and dispatches to drawing/eraser handlers with rAF throttle on mouse:move.
|
|
1
2
|
import { useEffect, useRef } from "react";
|
|
2
3
|
export const useMouseHandlers = ({ fabricCanvas, activeTool, drawingHandlers, eraserHandlers, }) => {
|
|
3
4
|
// ── PERF FIX 1: All props in refs — effect registers ONCE ────────────────
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usePan.d.ts","sourceRoot":"","sources":["../../src/hooks/usePan.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"usePan.d.ts","sourceRoot":"","sources":["../../src/hooks/usePan.ts"],"names":[],"mappings":"AAEA,OAAO,EAAqB,SAAS,EAAE,MAAM,OAAO,CAAC;AACrD,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,SA8Gb,CAAC"}
|
package/dist/hooks/usePan.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// Pan tool — click-drag to scroll the canvas viewport; two-finger pinch to zoom on touch devices.
|
|
1
2
|
import { useEffect, useRef } from "react";
|
|
2
3
|
export const usePan = ({ fabricCanvas, activeTool, handleZoom, setCanvasViewport, }) => {
|
|
3
4
|
// Refs so the effect registers ONCE but always reads latest values.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usePersistance.d.ts","sourceRoot":"","sources":["../../src/hooks/usePersistance.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"usePersistance.d.ts","sourceRoot":"","sources":["../../src/hooks/usePersistance.ts"],"names":[],"mappings":"AAEA,OAAO,KAA4B,MAAM,OAAO,CAAC;AACjD,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAIhC,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,GAAG,EAAE,CAAC;IACb,SAAS,EAAE,GAAG,EAAE,CAAC;CAClB;AAED,UAAU,mBAAmB;IAC3B,YAAY,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC7C,KAAK,EAAE,GAAG,EAAE,CAAC;IACb,SAAS,EAAE,GAAG,EAAE,CAAC;IACjB,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,cAAc,EAAE,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IACzC,kBAAkB,EAAE,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IAG7C,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,qBAAqB,KAAK,IAAI,CAAC;IAElD,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAID,eAAO,MAAM,cAAc,GAAI,8GAS5B,mBAAmB,SA+GrB,CAAC"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// Debounced auto-save — pushes canvas snapshots to the undo/redo history and calls the consumer's onSave callback.
|
|
1
2
|
import { useEffect, useRef } from "react";
|
|
2
3
|
// ─── Hook ─────────────────────────────────────────────────────────────────────
|
|
3
4
|
export const usePersistence = ({ fabricCanvas, tasks, documents, pushHistory, isRestoringRef, suppressHistoryRef, onSave, saveDebounceMs = 2000, }) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useSelection.d.ts","sourceRoot":"","sources":["../../src/hooks/useSelection.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useSelection.d.ts","sourceRoot":"","sources":["../../src/hooks/useSelection.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,MAAM,EAAQ,YAAY,EAAe,MAAM,QAAQ,CAAC;AAMjE,UAAU,iBAAiB;IACzB,YAAY,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC7C,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACzC,eAAe,EAAE,CAAC,GAAG,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,KAAK,IAAI,CAAC;IAC1F,wBAAwB,EAAE,CAAC,OAAO,EAAE,YAAY,EAAE,KAAK,IAAI,CAAC;IAC5D,YAAY,EAAE,KAAK,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC;IAC9C,oBAAoB,CAAC,EAAE,MAAM,IAAI,CAAC;CACnC;AAGD,eAAO,MAAM,YAAY,GAAI,8GAO1B,iBAAiB,SAwKnB,CAAC"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//
|
|
1
|
+
// Tracks Fabric canvas selection events, syncs object type to the store, and drives the rubber-band selection box for HTML nodes.
|
|
2
2
|
import { useEffect, useRef } from "react";
|
|
3
3
|
import { Rect, FabricImage } from "fabric";
|
|
4
4
|
import { Frame } from "../lib/fabric-frame";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useTextStyle.d.ts","sourceRoot":"","sources":["../../src/hooks/useTextStyle.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useTextStyle.d.ts","sourceRoot":"","sources":["../../src/hooks/useTextStyle.ts"],"names":[],"mappings":"AAEA,OAAO,EAAa,SAAS,EAAE,MAAM,OAAO,CAAC;AAC7C,OAAO,EAAE,MAAM,EAAgB,MAAM,QAAQ,CAAC;AAE9C,UAAU,iBAAiB;IACzB,YAAY,EAAE,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACvC,WAAW,EAAE,GAAG,CAAC;CAClB;AAED,eAAO,MAAM,YAAY,GAAI,+BAA+B,iBAAiB,SAiC5E,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useToolManager.d.ts","sourceRoot":"","sources":["../../src/hooks/useToolManager.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useToolManager.d.ts","sourceRoot":"","sources":["../../src/hooks/useToolManager.ts"],"names":[],"mappings":"AAEA,OAAO,EAAa,SAAS,EAAE,MAAM,OAAO,CAAC;AAC7C,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAExC,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AAGjC,UAAU,mBAAmB;IAC3B,YAAY,EAAE,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACvC,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,GAAG,CAAC;IACjB,cAAc,EAAE,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACzC,aAAa,EAAE,SAAS,CAAC,MAAM,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;IAC7C,mBAAmB,EAAE,SAAS,CAAC,MAAM,EAAE,CAAC,CAAC;IACzC,gBAAgB,EAAE,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;CACvC;AAED,eAAO,MAAM,cAAc,GAAI,kHAQ5B,mBAAmB,SAiJrB,CAAC"}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
// Switches the Fabric canvas drawing mode, cursor style, and per-object interactivity when the active tool changes.
|
|
1
2
|
import { useEffect } from "react";
|
|
2
3
|
import { calculateDashArray } from "../lib/fabric-utils";
|
|
4
|
+
import { useWhiteboardStore } from "../store/whiteboard-store";
|
|
3
5
|
export const useToolManager = ({ fabricCanvas, activeTool, toolOptions, eraserTraceRef, eraserPathRef, eraserPathPointsRef, eraserTargetsRef, }) => {
|
|
6
|
+
const editable = useWhiteboardStore((s) => s.editable);
|
|
4
7
|
useEffect(() => {
|
|
5
8
|
const canvas = fabricCanvas.current;
|
|
6
9
|
if (!canvas)
|
|
@@ -16,6 +19,21 @@ export const useToolManager = ({ fabricCanvas, activeTool, toolOptions, eraserTr
|
|
|
16
19
|
eraserPathPointsRef.current = [];
|
|
17
20
|
}
|
|
18
21
|
}
|
|
22
|
+
// Read-only lockdown — regardless of activeTool, no object may be
|
|
23
|
+
// selected, moved, resized, deleted, or drawn into while not editable.
|
|
24
|
+
if (!editable) {
|
|
25
|
+
canvas.isDrawingMode = false;
|
|
26
|
+
canvas.selection = false;
|
|
27
|
+
canvas.defaultCursor = "default";
|
|
28
|
+
canvas.hoverCursor = "default";
|
|
29
|
+
canvas.discardActiveObject();
|
|
30
|
+
canvas.forEachObject((obj) => {
|
|
31
|
+
obj.selectable = false;
|
|
32
|
+
obj.evented = false;
|
|
33
|
+
});
|
|
34
|
+
canvas.renderAll();
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
19
37
|
switch (activeTool) {
|
|
20
38
|
case "select":
|
|
21
39
|
canvas.isDrawingMode = false;
|
|
@@ -111,5 +129,6 @@ export const useToolManager = ({ fabricCanvas, activeTool, toolOptions, eraserTr
|
|
|
111
129
|
eraserPathRef,
|
|
112
130
|
eraserPathPointsRef,
|
|
113
131
|
eraserTargetsRef,
|
|
132
|
+
editable,
|
|
114
133
|
]);
|
|
115
134
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useZoom.d.ts","sourceRoot":"","sources":["../../src/hooks/useZoom.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useZoom.d.ts","sourceRoot":"","sources":["../../src/hooks/useZoom.ts"],"names":[],"mappings":"AAEA,OAAO,EAAkC,SAAS,EAAE,MAAM,OAAO,CAAC;AAClE,OAAO,EAAE,MAAM,EAAuB,MAAM,QAAQ,CAAC;AAErD,UAAU,YAAY;IACpB,YAAY,EAAE,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACzC,aAAa,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,iBAAiB,EAAE,CAAC,QAAQ,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CACjE;AAED,eAAO,MAAM,OAAO,GAAI,yEAMrB,YAAY;0BAiB4B,MAAM,UAAU;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE;CA2IlF,CAAC"}
|
package/dist/hooks/useZoom.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// Handles canvas zoom via mouse wheel, toolbar buttons, and Ctrl+/− keyboard shortcuts; prevents browser native zoom.
|
|
1
2
|
import { useCallback, useEffect, useRef } from "react";
|
|
2
3
|
import { Point } from "fabric";
|
|
3
4
|
export const useZoom = ({ fabricCanvas, MIN_ZOOM, MAX_ZOOM, setCanvasZoom, setCanvasViewport, }) => {
|