diff --git a/code/components/actions/fit-to-page-tool/fit-to-page-tool.ts b/code/components/actions/fit-to-page-tool/fit-to-page-tool.ts index e1707cc8..cbc731fd 100644 --- a/code/components/actions/fit-to-page-tool/fit-to-page-tool.ts +++ b/code/components/actions/fit-to-page-tool/fit-to-page-tool.ts @@ -69,6 +69,10 @@ export class FitToPageToolAction extends WeaveAction { const renderRect = exportAreaReferencePlugin.getRenderRect(); + if (!renderRect) { + this.cancelAction(); + } + stageZoomPlugin.fitToArea(renderRect, { overrideZoom: params?.overrideZoom ?? false, }); diff --git a/code/components/nodes/color-token/color-token.ts b/code/components/nodes/color-token/color-token.ts index 638e8994..1f6f33dd 100644 --- a/code/components/nodes/color-token/color-token.ts +++ b/code/components/nodes/color-token/color-token.ts @@ -91,11 +91,11 @@ export class ColorTokenNode extends WeaveNode { groupId: id, nodeId: id, id: `${id}-colorToken-border`, - x: 0, - y: 0, + x: 0.5, + y: 0.5, fill: "transparent", - width: colorTokenParams.width, - height: colorTokenParams.height, + width: colorTokenParams.width - 1, + height: colorTokenParams.height - 1, strokeScaleEnabled: true, stroke: "black", strokeWidth: 1, diff --git a/code/components/plugins/export-area-reference/export-area-reference.ts b/code/components/plugins/export-area-reference/export-area-reference.ts index eb20bc84..e74c827d 100644 --- a/code/components/plugins/export-area-reference/export-area-reference.ts +++ b/code/components/plugins/export-area-reference/export-area-reference.ts @@ -2,7 +2,13 @@ // // SPDX-License-Identifier: Apache-2.0 -import { WeavePlugin } from "@inditextech/weave-sdk"; +import { BoundingBox } from "@inditextech/weave-types"; +import { + WeavePlugin, + Guide, + GUIDE_KIND, + GUIDE_ORIENTATION, +} from "@inditextech/weave-sdk"; import { ExportAreaReferencePluginConfig, ExportAreaReferencePluginParams, @@ -49,12 +55,7 @@ export class ExportAreaReferencePlugin extends WeavePlugin { this.layer?.show(); } - getExportRect(params?: { relativeTo?: Konva.Container }): { - x: number; - y: number; - width: number; - height: number; - } { + getExportRect(params?: { relativeTo?: Konva.Container }): BoundingBox | null { const stage = this.instance.getStage(); const renderAreaNode = stage.findOne("#export-area-reference-rect") as @@ -62,7 +63,7 @@ export class ExportAreaReferencePlugin extends WeavePlugin { | undefined; if (!renderAreaNode) { - throw new Error("Render area node not found"); + return null; } const box = renderAreaNode.getClientRect({ @@ -112,6 +113,11 @@ export class ExportAreaReferencePlugin extends WeavePlugin { }); this.renderReference(); + + const hooks = this.instance.getHooks(); + hooks.callHook("snappingManagerPlugin:onAddCustomGuides", { + guides: this.getExportAreaGuides(stage), + }); } changeSize(newSize: string) { @@ -154,7 +160,7 @@ export class ExportAreaReferencePlugin extends WeavePlugin { fontSize: 12 / scale, fontFamily: "Roboto Mono, monospace", opacity: 0.7, - fill: "#ff2c2cff", + fill: "#000000", draggable: false, listening: false, }); @@ -174,7 +180,7 @@ export class ExportAreaReferencePlugin extends WeavePlugin { fontSize: 12 / scale, fontFamily: "Roboto Mono, monospace", opacity: 0.7, - fill: "#ff2c2cff", + fill: "#000000", draggable: false, listening: false, }); @@ -186,23 +192,23 @@ export class ExportAreaReferencePlugin extends WeavePlugin { referenceAreaLayer.add(referenceTextDetails); - const exportRect = new Konva.Rect({ - id: "export-area-reference-export-rect", - name: "export-area-reference", - x: actualSize.width / 2, - y: actualSize.height / 2, - width: actualSize.width, - height: actualSize.height, - stroke: "#ff2c2cff", - strokeScaleEnabled: false, - strokeWidth: 0, - fill: "transparent", - opacity: 0.7, - draggable: false, - listening: false, - }); - - referenceAreaLayer.add(exportRect); + // const exportRect = new Konva.Rect({ + // id: "export-area-reference-export-rect", + // name: "export-area-reference", + // x: actualSize.width / 2, + // y: actualSize.height / 2, + // width: actualSize.width, + // height: actualSize.height, + // stroke: "#000000", + // strokeScaleEnabled: false, + // strokeWidth: 0, + // fill: "transparent", + // opacity: 0.7, + // draggable: false, + // listening: false, + // }); + + // referenceAreaLayer.add(exportRect); const referenceRect = new Konva.Rect({ id: "export-area-reference-rect", @@ -212,10 +218,11 @@ export class ExportAreaReferencePlugin extends WeavePlugin { width: actualSize.width, height: actualSize.height, fillAfterStrokeEnabled: true, - stroke: "#ff2c2cff", + stroke: "#000000", + dash: [6, 6], strokeScaleEnabled: false, strokeWidth: 1, - opacity: 1, + opacity: 0.7, draggable: false, listening: false, }); @@ -248,4 +255,79 @@ export class ExportAreaReferencePlugin extends WeavePlugin { this.layer?.hide(); this.enabled = false; } + + getExportAreaGuides(relativeTo?: Konva.Node | null): Guide[] { + const stage = this.instance.getStage(); + const renderAreaNode = stage.findOne("#export-area-reference-rect") as + | Konva.Rect + | undefined; + + if (!renderAreaNode) { + return []; + } + + if (!relativeTo) { + return []; + } + + const rect = renderAreaNode.getClientRect({ + ...(relativeTo && { + relativeTo: relativeTo as unknown as Konva.Container, + }), + skipStroke: true, + }); + return this.rectToGuides("exportArea", rect); + } + + private rectToGuides(sourceId: string, rect: BoundingBox): Guide[] { + const mainLayer = this.instance.getMainLayer(); + if (!mainLayer) { + return []; + } + + return [ + { + orientation: GUIDE_ORIENTATION.VERTICAL, + value: rect.x, + kind: GUIDE_KIND.STATIC, + guideId: `${sourceId}-${mainLayer.id()}-vertical-start`, + containerId: mainLayer.id(), + }, + { + orientation: GUIDE_ORIENTATION.VERTICAL, + value: rect.x + rect.width / 2, + kind: GUIDE_KIND.STATIC, + guideId: `${sourceId}-${mainLayer.id()}-vertical-middle`, + containerId: mainLayer.id(), + }, + { + orientation: GUIDE_ORIENTATION.VERTICAL, + value: rect.x + rect.width, + kind: GUIDE_KIND.STATIC, + guideId: `${sourceId}-${mainLayer.id()}-vertical-end`, + containerId: mainLayer.id(), + }, + { + orientation: GUIDE_ORIENTATION.HORIZONTAL, + value: rect.y, + kind: GUIDE_KIND.STATIC, + guideId: `${sourceId}-${mainLayer.id()}-horizontal-start`, + containerId: mainLayer.id(), + }, + { + orientation: GUIDE_ORIENTATION.HORIZONTAL, + value: rect.y + rect.height / 2, + kind: GUIDE_KIND.STATIC, + guideId: `${sourceId}-${mainLayer.id()}-horizontal-middle`, + containerId: mainLayer.id(), + }, + { + orientation: GUIDE_ORIENTATION.HORIZONTAL, + value: rect.y + rect.height, + kind: GUIDE_KIND.STATIC, + guideId: `${sourceId}-${mainLayer.id()}-horizontal-end`, + containerId: mainLayer.id(), + }, + ]; + } } diff --git a/code/components/room-components/connected-users.tsx b/code/components/room-components/connected-users.tsx index 9aede7c3..288d9f84 100644 --- a/code/components/room-components/connected-users.tsx +++ b/code/components/room-components/connected-users.tsx @@ -138,8 +138,8 @@ export const ConnectedUsers = () => { {/* Connected users @@ -150,32 +150,34 @@ export const ConnectedUsers = () => { const userInfo = connectedUsers[userKey]; return ( - +
- - - - {getUserShort(userInfo?.name ?? "")} - - -
-
{userInfo?.name}
-
- {userInfo?.email} -
- {/*
+
+ + + + {getUserShort(userInfo?.name ?? "")} + + +
+
{userInfo?.name}
+
+ {userInfo?.email} +
+ {/*
{typeof usersLocks[userInfo.id] !== "undefined" ? OPERATIONS_MAP[ ( @@ -186,19 +188,20 @@ export const ConnectedUsers = () => { ] : "idle"}
*/} +
+ + + {typeof usersLocks[userInfo.id] !== "undefined" + ? OPERATIONS_MAP[ + ( + usersLocks[ + userInfo.id + ] as WeaveUserMutexLock + ).operation + ] + : "idle"} +
- - - {typeof usersLocks[userInfo.id] !== "undefined" - ? OPERATIONS_MAP[ - ( - usersLocks[ - userInfo.id - ] as WeaveUserMutexLock - ).operation - ] - : "idle"} - ); diff --git a/code/components/room-components/context-menu.tsx b/code/components/room-components/context-menu.tsx index cb00221c..8143646d 100644 --- a/code/components/room-components/context-menu.tsx +++ b/code/components/room-components/context-menu.tsx @@ -47,7 +47,7 @@ function ContextMenuButton({ return (
diff --git a/code/components/room-components/help/help-drawer.tsx b/code/components/room-components/help/help-drawer.tsx index 06829d80..40d762b2 100644 --- a/code/components/room-components/help/help-drawer.tsx +++ b/code/components/room-components/help/help-drawer.tsx @@ -15,10 +15,8 @@ import { DrawerTitle, } from "@/components/ui/drawer"; import { XIcon } from "lucide-react"; -import { SYSTEM_OS } from "@/lib/utils"; import { HelpTools } from "./help-tools"; import React from "react"; -import { useGetOs } from "../hooks/use-get-os"; import { HelpZoom } from "./help-zoom"; import { HelpView } from "./help-view"; import { HelpSelection } from "./help-selection"; @@ -26,6 +24,7 @@ import { HelpEdit } from "./help-edit"; import { HelpArrange } from "./help-arrange"; import { useCollaborationRoom } from "@/store/store"; import { DRAWER_ELEMENTS } from "@/lib/constants"; +import { formatForDisplay } from "@tanstack/react-hotkeys"; type HelpDrawerTriggerProps = { onClick?: () => void; @@ -34,10 +33,8 @@ type HelpDrawerTriggerProps = { export const HelpDrawerTrigger = ({ onClick = () => {}, }: Readonly) => { - const os = useGetOs(); - const keyboardShortcutsVisible = useCollaborationRoom( - (state) => state.drawer.keyboardShortcuts.visible + (state) => state.drawer.keyboardShortcuts.visible, ); const setShowDrawer = useCollaborationRoom((state) => state.setShowDrawer); @@ -46,23 +43,22 @@ export const HelpDrawerTrigger = ({ onPointerDown={() => { setShowDrawer( DRAWER_ELEMENTS.keyboardShortcuts, - !keyboardShortcutsVisible + !keyboardShortcutsVisible, ); onClick?.(); }} onClick={() => { setShowDrawer( DRAWER_ELEMENTS.keyboardShortcuts, - !keyboardShortcutsVisible + !keyboardShortcutsVisible, ); onClick?.(); }} - className="w-full text-foreground cursor-pointer hover:rounded-none" + className="w-full text-xs cursor-pointer hover:rounded-none" >
Keyboard shortcuts - {[SYSTEM_OS.MAC as string].includes(os) && "⌥ ⌘ C"} - {[SYSTEM_OS.WINDOWS as string].includes(os) && "Alt Ctrl C"} + {formatForDisplay("Alt+Mod+C")} ); @@ -70,7 +66,7 @@ export const HelpDrawerTrigger = ({ export const HelpDrawer = () => { const keyboardShortcutsVisible = useCollaborationRoom( - (state) => state.drawer.keyboardShortcuts.visible + (state) => state.drawer.keyboardShortcuts.visible, ); const setShowDrawer = useCollaborationRoom((state) => state.setShowDrawer); @@ -86,7 +82,7 @@ export const HelpDrawer = () => { > - + Keyboard shortcuts diff --git a/code/components/room-components/help/help-edit.tsx b/code/components/room-components/help/help-edit.tsx index fabf8906..e0ba61c9 100644 --- a/code/components/room-components/help/help-edit.tsx +++ b/code/components/room-components/help/help-edit.tsx @@ -11,8 +11,8 @@ import { Undo, Redo, } from "lucide-react"; -import { SYSTEM_OS } from "@/lib/utils"; import { HelpShortcutElement } from "./help-shortcut-element"; +import { formatForDisplay } from "@tanstack/react-hotkeys"; export const HelpEdit = () => { return ( @@ -21,62 +21,41 @@ export const HelpEdit = () => { } label="Copy" - shortcuts={{ - [SYSTEM_OS.MAC]: "⌘ C", - [SYSTEM_OS.OTHER]: "Ctrl C", - }} + shortcuts={formatForDisplay("Mod+C")} /> } label="Paste" - shortcuts={{ - [SYSTEM_OS.MAC]: "⌘ P", - [SYSTEM_OS.OTHER]: "Ctrl P", - }} + shortcuts={formatForDisplay("Mod+P")} />
} label="Duplicate" - shortcuts={{ - [SYSTEM_OS.MAC]: "⌘ D", - [SYSTEM_OS.OTHER]: "Ctrl D", - }} + shortcuts={formatForDisplay("Mod+D")} /> } label="Undo" - shortcuts={{ - [SYSTEM_OS.MAC]: "⇧ ⌘ ,", - [SYSTEM_OS.OTHER]: "⇧ Ctrl ,", - }} + shortcuts={formatForDisplay("Shift+Mod+,")} /> } label="Redo" - shortcuts={{ - [SYSTEM_OS.MAC]: "⇧ ⌘ .", - [SYSTEM_OS.OTHER]: "⇧ Ctrl .", - }} + shortcuts={formatForDisplay("Shift+Mod+.")} />
} label="Export selected as image" - shortcuts={{ - [SYSTEM_OS.MAC]: "⇧ ⌘ E", - [SYSTEM_OS.OTHER]: "⇧ Ctrl E", - }} + shortcuts={formatForDisplay("Shift+Mod+E")} /> } label="Export viewport as image" - shortcuts={{ - [SYSTEM_OS.MAC]: "⇧ ⌘ V", - [SYSTEM_OS.OTHER]: "⇧ Ctrl V", - }} + shortcuts={formatForDisplay("Shift+Mod+V")} />
diff --git a/code/components/room-components/help/help-selection.tsx b/code/components/room-components/help/help-selection.tsx index df40ffd7..b4cfbf69 100644 --- a/code/components/room-components/help/help-selection.tsx +++ b/code/components/room-components/help/help-selection.tsx @@ -3,8 +3,8 @@ // SPDX-License-Identifier: Apache-2.0 import { SquareDashed, TextSelect } from "lucide-react"; -import { SYSTEM_OS } from "@/lib/utils"; import { HelpShortcutElement } from "./help-shortcut-element"; +import { formatForDisplay } from "@tanstack/react-hotkeys"; export const HelpSelection = () => { return ( @@ -13,20 +13,14 @@ export const HelpSelection = () => { } label="Select all" - shortcuts={{ - [SYSTEM_OS.MAC]: "⇧ ⌘ A", - [SYSTEM_OS.OTHER]: "⇧ Ctrl A", - }} + shortcuts={formatForDisplay("Shift+Mod+A")} />
} label="Select none" - shortcuts={{ - [SYSTEM_OS.MAC]: "⇧ Esc", - [SYSTEM_OS.OTHER]: "⇧ Esc", - }} + shortcuts={formatForDisplay("Shift+Esc")} />
diff --git a/code/components/room-components/help/help-shortcut-element.tsx b/code/components/room-components/help/help-shortcut-element.tsx index 8b1a1486..8de435c6 100644 --- a/code/components/room-components/help/help-shortcut-element.tsx +++ b/code/components/room-components/help/help-shortcut-element.tsx @@ -3,12 +3,11 @@ // SPDX-License-Identifier: Apache-2.0 import React from "react"; -import { ShortcutElement } from "./shortcut-element"; type HelpShortcutElement = { icon: React.ReactElement; label: string; - shortcuts: Record; + shortcuts: string; }; export const HelpShortcutElement = ({ @@ -22,7 +21,7 @@ export const HelpShortcutElement = ({ {React.cloneElement(icon, { size: 20 })}
{label}
- + {shortcuts} ); }; diff --git a/code/components/room-components/help/help-tools.tsx b/code/components/room-components/help/help-tools.tsx index 87358c10..4d63e6b4 100644 --- a/code/components/room-components/help/help-tools.tsx +++ b/code/components/room-components/help/help-tools.tsx @@ -14,9 +14,9 @@ import { Type, Frame, } from "lucide-react"; -import { SYSTEM_OS } from "@/lib/utils"; import { HelpShortcutElement } from "./help-shortcut-element"; import { useCollaborationRoom } from "@/store/store"; +import { formatForDisplay } from "@tanstack/react-hotkeys"; export const HelpTools = () => { const threadsEnabled = useCollaborationRoom( @@ -29,88 +29,58 @@ export const HelpTools = () => { } label="Select tool" - shortcuts={{ - [SYSTEM_OS.MAC]: "S", - [SYSTEM_OS.OTHER]: "S", - }} + shortcuts={formatForDisplay("S")} /> } label="Frame tool" - shortcuts={{ - [SYSTEM_OS.MAC]: "F", - [SYSTEM_OS.OTHER]: "F", - }} + shortcuts={formatForDisplay("F")} />
} label="Rectangle tool" - shortcuts={{ - [SYSTEM_OS.MAC]: "R", - [SYSTEM_OS.OTHER]: "R", - }} + shortcuts={formatForDisplay("R")} /> } label="Line tool" - shortcuts={{ - [SYSTEM_OS.MAC]: "L", - [SYSTEM_OS.OTHER]: "L", - }} + shortcuts={formatForDisplay("L")} /> } label="Brush tool" - shortcuts={{ - [SYSTEM_OS.MAC]: "B", - [SYSTEM_OS.OTHER]: "B", - }} + shortcuts={formatForDisplay("B")} /> } label="Text tool" - shortcuts={{ - [SYSTEM_OS.MAC]: "T", - [SYSTEM_OS.OTHER]: "T", - }} + shortcuts={formatForDisplay("T")} />
} label="Image tool" - shortcuts={{ - [SYSTEM_OS.MAC]: "I", - [SYSTEM_OS.OTHER]: "I", - }} + shortcuts={formatForDisplay("I")} /> } label="Images tool" - shortcuts={{ - [SYSTEM_OS.MAC]: "O", - [SYSTEM_OS.OTHER]: "O", - }} + shortcuts={formatForDisplay("O")} /> {threadsEnabled && ( } label="Comment tool" - shortcuts={{ - [SYSTEM_OS.MAC]: "H", - [SYSTEM_OS.OTHER]: "H", - }} + shortcuts={formatForDisplay("H")} /> )} } label="Color Token tool" - shortcuts={{ - [SYSTEM_OS.MAC]: "P", - [SYSTEM_OS.OTHER]: "P", - }} + shortcuts={formatForDisplay("P")} />
diff --git a/code/components/room-components/help/help-view.tsx b/code/components/room-components/help/help-view.tsx index 1e9583a5..ceca0fbb 100644 --- a/code/components/room-components/help/help-view.tsx +++ b/code/components/room-components/help/help-view.tsx @@ -3,8 +3,8 @@ // SPDX-License-Identifier: Apache-2.0 import { Eye, MapPinned, MousePointer } from "lucide-react"; -import { SYSTEM_OS } from "@/lib/utils"; import { HelpShortcutElement } from "./help-shortcut-element"; +import { formatForDisplay } from "@tanstack/react-hotkeys"; export const HelpView = () => { return ( @@ -13,28 +13,19 @@ export const HelpView = () => { } label="Show/Hide UI / Zen mode" - shortcuts={{ - [SYSTEM_OS.MAC]: "⌘ \\", - [SYSTEM_OS.OTHER]: "Ctrl \\", - }} + shortcuts={formatForDisplay("Mod+\\")} /> } label="Show/Hide Minimap" - shortcuts={{ - [SYSTEM_OS.MAC]: "N", - [SYSTEM_OS.OTHER]: "N", - }} + shortcuts={formatForDisplay("N")} />
} label="Users cursors" - shortcuts={{ - [SYSTEM_OS.MAC]: "⌥ ⌘ U", - [SYSTEM_OS.OTHER]: "Alt Ctrl U", - }} + shortcuts={formatForDisplay("Alt+Mod+U")} />
diff --git a/code/components/room-components/help/help-zoom.tsx b/code/components/room-components/help/help-zoom.tsx index ee7dc5ca..cf10f133 100644 --- a/code/components/room-components/help/help-zoom.tsx +++ b/code/components/room-components/help/help-zoom.tsx @@ -2,9 +2,9 @@ // // SPDX-License-Identifier: Apache-2.0 -import { Fullscreen, Maximize, ZoomIn, ZoomOut } from "lucide-react"; -import { SYSTEM_OS } from "@/lib/utils"; +import { Fullscreen, Maximize, ScanEye, ZoomIn, ZoomOut } from "lucide-react"; import { HelpShortcutElement } from "./help-shortcut-element"; +import { formatForDisplay } from "@tanstack/react-hotkeys"; export const HelpZoom = () => { return ( @@ -13,36 +13,29 @@ export const HelpZoom = () => { } label="Zoom in" - shortcuts={{ - [SYSTEM_OS.MAC]: "⌘ +", - [SYSTEM_OS.OTHER]: "Ctrl +", - }} + shortcuts={formatForDisplay("Mod++")} /> } label="Zoom out" - shortcuts={{ - [SYSTEM_OS.MAC]: "⌘ -", - [SYSTEM_OS.OTHER]: "Ctrl -", - }} + shortcuts={formatForDisplay("Mod+-")} />
} label="Fit to screen" - shortcuts={{ - [SYSTEM_OS.MAC]: "⇧ 1", - [SYSTEM_OS.OTHER]: "⇧ 1", - }} + shortcuts={formatForDisplay("Shift+1")} /> } label="Fit to selection" - shortcuts={{ - [SYSTEM_OS.MAC]: "⇧ 2", - [SYSTEM_OS.OTHER]: "⇧ 2", - }} + shortcuts={formatForDisplay("Shift+2")} + /> + } + label="Fit to page" + shortcuts={formatForDisplay("Shift+3")} />
diff --git a/code/components/room-components/help/shortcut-element.tsx b/code/components/room-components/help/shortcut-element.tsx deleted file mode 100644 index 149b9191..00000000 --- a/code/components/room-components/help/shortcut-element.tsx +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-FileCopyrightText: 2025 2025 INDUSTRIA DE DISEÑO TEXTIL S.A. (INDITEX S.A.) -// -// SPDX-License-Identifier: Apache-2.0 - -import React from "react"; -import { SYSTEM_OS } from "@/lib/utils"; -import { useGetOs } from "../hooks/use-get-os"; - -type ShortcutElement = { - shortcuts: Record; -}; - -export const ShortcutElement = ({ shortcuts }: Readonly) => { - const os = useGetOs(); - - const isMac = React.useMemo(() => os === SYSTEM_OS.MAC, [os]); - - const keys = React.useMemo(() => { - return isMac ? shortcuts[SYSTEM_OS.MAC] : shortcuts[SYSTEM_OS.OTHER]; - }, [isMac, shortcuts]); - - return ( -
{keys}
- ); -}; diff --git a/code/components/room-components/hooks/use-context-menu.tsx b/code/components/room-components/hooks/use-context-menu.tsx index ba9e7ef4..583e389e 100644 --- a/code/components/room-components/hooks/use-context-menu.tsx +++ b/code/components/room-components/hooks/use-context-menu.tsx @@ -4,6 +4,7 @@ import { toast } from "sonner"; import { + containerOverCursor, WeaveContextMenuPlugin, WeaveCopyPasteNodesPlugin, WeaveStageContextMenuPluginOnNodeContextMenuEvent, @@ -18,8 +19,6 @@ import { useCollaborationRoom } from "@/store/store"; import React from "react"; import { useWeave } from "@inditextech/weave-react"; import { ContextMenuOption } from "../context-menu"; -import { ShortcutElement } from "../help/shortcut-element"; -import { SYSTEM_OS } from "@/lib/utils"; import { ClipboardCopy, ClipboardPaste, @@ -34,9 +33,10 @@ import { Lock, EyeOff, Link, - PackagePlus, + // PackagePlus, PackageOpen, Paperclip, + PanelLeftRightDashed, } from "lucide-react"; import { useExportPageToImageServerSide } from "./use-export-page-to-image-server-side"; import { ImageTemplateNode } from "@/components/nodes/image-template/image-template"; @@ -45,6 +45,9 @@ import { useTemplates } from "@/store/templates"; import { usePromptInputAttachments } from "@/components/ai-elements/prompt-input"; import { useIAChat } from "@/store/ia-chat"; import Konva from "konva"; +import { useHandleGuides } from "./use-handle-guides"; +import { formatForDisplay } from "@tanstack/react-hotkeys"; +// import { useJsonTemplate } from "./use-json-template"; function useContextMenu() { const instance = useWeave((state) => state.instance); @@ -77,9 +80,12 @@ function useContextMenu() { ); const { isExporting } = useExportPageToImageServerSide(); + // const { generateJsonTemplate } = useJsonTemplate(); const promptInputAttachmentsController = usePromptInputAttachments(); + const { copyGuides, pasteGuides, toggleContainerGuides } = useHandleGuides(); + React.useEffect(() => { if (!instance) return; @@ -228,12 +234,7 @@ function useContextMenu() { label: (
Export as image
- + {formatForDisplay("Shift+Mod+E")}
), icon: , @@ -287,12 +288,7 @@ function useContextMenu() { label: (
Copy
- + {formatForDisplay("Mod+C")}
), icon: , @@ -334,12 +330,7 @@ function useContextMenu() { label: (
Paste here
- + {formatForDisplay("Mod+P")}
), icon: , @@ -354,29 +345,168 @@ function useContextMenu() { }, }); - if (!singleLocked && nodes.length > 0) { + if ( + nodes.length === 0 || + (nodes.length === 1 && + nodes[0].instance.getAttrs().nodeType === "frame") + ) { options.push({ - id: "div-templates-1", + id: "div-guides-1", type: "divider", }); - // SAVE AS TEMPLATE + options.push({ - id: "save-as-template", + id: "toggle-guides", type: "button", label: (
-
Save as template
+
Toggle container guides
+ {formatForDisplay("G")} {formatForDisplay("T")}
), - icon: , + icon: , disabled: !["selectionTool"].includes(actActionActive ?? ""), - onClick: () => { - setSaveDialogVisible(true); + onClick: async () => { + setContextMenuShow(false); + + if (!instance) return; + + const node = containerOverCursor(instance, [], stageClickPoint); + + let containerId: string = instance.getMainLayer()?.id() ?? ""; + if (node) { + containerId = node.id(); + } + + toggleContainerGuides(containerId); + }, + }); + + options.push({ + id: "copy-guides", + type: "button", + label: ( +
+
Copy guides to clipboard
+ {formatForDisplay("G")} {formatForDisplay("Mod+C")} +
+ ), + icon: , + disabled: !["selectionTool"].includes(actActionActive ?? ""), + onClick: async () => { + setContextMenuShow(false); + + if (!instance) return; + + let selectedContainerId: string = + instance.getMainLayer()?.id() ?? ""; + + if ( + nodes.length === 1 && + nodes[0].instance.getAttrs().nodeType === "frame" + ) { + selectedContainerId = nodes[0].instance.id(); + } + if (nodes.length === 0) { + const node = containerOverCursor(instance, [], stageClickPoint); + if (node) { + selectedContainerId = node.id(); + } + } + + await copyGuides(selectedContainerId); + }, + }); + + options.push({ + id: "paste-guides", + type: "button", + label: ( +
+
Paste guides from clipboard
+ {formatForDisplay("G")} {formatForDisplay("Mod+P")} +
+ ), + icon: , + disabled: !["selectionTool"].includes(actActionActive ?? ""), + onClick: async () => { setContextMenuShow(false); + + if (!instance) return; + + let selectedContainerId: string = + instance.getMainLayer()?.id() ?? ""; + + if ( + nodes.length === 1 && + nodes[0].instance.getAttrs().nodeType === "frame" + ) { + selectedContainerId = nodes[0].instance.id(); + } + if (nodes.length === 0) { + const node = containerOverCursor(instance, [], stageClickPoint); + if (node) { + selectedContainerId = node.id(); + } + } + + await pasteGuides(selectedContainerId); }, }); } + // if (!singleLocked && nodes.length > 0) { + // options.push({ + // id: "div-templates-1", + // type: "divider", + // }); + // // SAVE AS TEMPLATE + // options.push({ + // id: "save-as-template", + // type: "button", + // label: ( + //
+ //
Save as template
+ //
+ // ), + // icon: , + // disabled: !["selectionTool"].includes(actActionActive ?? ""), + // onClick: () => { + // setSaveDialogVisible(true); + // setContextMenuShow(false); + // }, + // }); + // // SAVE AS TEMPLATE + // options.push({ + // id: "save-as-json-template", + // type: "button", + // label: ( + //
+ //
Save as JSON template
+ //
+ // ), + // icon: , + // disabled: !["selectionTool"].includes(actActionActive ?? ""), + // onClick: async () => { + // try { + // const template = generateJsonTemplate(nodes); + // await navigator.clipboard.writeText(JSON.stringify(template)); + // toast.success("JSON template copied to clipboard."); + // } catch (error) { + // console.error(error); + // if (error instanceof Error && error.cause === "NoInstance") { + // toast.error("Weave instance is not available."); + // } + // if (error instanceof Error && error.cause === "NoNodesSelected") { + // toast.error("No nodes selected to generate JSON template."); + // } + // } + + // setContextMenuShow(false); + // }, + // }); + // } + if (!singleLocked && nodes.length > 0) { // SEPARATOR options.push({ @@ -392,12 +522,7 @@ function useContextMenu() { label: (
Bring to front
- + {formatForDisplay("]")}
), icon: , @@ -413,12 +538,7 @@ function useContextMenu() { label: (
Move up
- + {formatForDisplay("Mod+]")}
), icon: , @@ -435,12 +555,7 @@ function useContextMenu() { label: (
Move down
- + {formatForDisplay("Mod+[")}
), icon: , @@ -457,12 +572,7 @@ function useContextMenu() { label: (
Send to back
- + {formatForDisplay("[")}
), icon: , @@ -486,12 +596,7 @@ function useContextMenu() { label: (
Group
- + {formatForDisplay("Shift+Mod+G")}
), icon: , @@ -512,12 +617,7 @@ function useContextMenu() { label: (
Un-group
- + {formatForDisplay("Shift+Mod+U")}
), icon: , @@ -606,12 +706,7 @@ function useContextMenu() { label: (
Delete
- + {formatForDisplay("Del")}
), icon: , diff --git a/code/components/room-components/hooks/use-get-os.ts b/code/components/room-components/hooks/use-get-os.ts deleted file mode 100644 index aaa75510..00000000 --- a/code/components/room-components/hooks/use-get-os.ts +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-FileCopyrightText: 2025 2025 INDUSTRIA DE DISEÑO TEXTIL S.A. (INDITEX S.A.) -// -// SPDX-License-Identifier: Apache-2.0 - -import { detectOS, SYSTEM_OS, SystemOs } from "@/lib/utils"; -import React from "react"; - -export const useGetOs = () => { - const [os, setOs] = React.useState(SYSTEM_OS.OTHER); - - React.useEffect(() => { - setOs(detectOS()); - }, []); - - return os; -}; diff --git a/code/components/room-components/hooks/use-get-page-thumbnail.tsx b/code/components/room-components/hooks/use-get-page-thumbnail.tsx index ecfda2bd..17aa4bb9 100644 --- a/code/components/room-components/hooks/use-get-page-thumbnail.tsx +++ b/code/components/room-components/hooks/use-get-page-thumbnail.tsx @@ -112,6 +112,11 @@ export const useGetPageThumbnail = () => { const exportRect = exportAreaReferencePlugin.getExportRect(); + if (!exportRect) { + setGenerationState("generated"); + return; + } + clonedStage.toBlob({ x: exportRect.x, y: exportRect.y, diff --git a/code/components/room-components/hooks/use-handle-guides.tsx b/code/components/room-components/hooks/use-handle-guides.tsx new file mode 100644 index 00000000..9280d1b4 --- /dev/null +++ b/code/components/room-components/hooks/use-handle-guides.tsx @@ -0,0 +1,273 @@ +// SPDX-FileCopyrightText: 2025 2025 INDUSTRIA DE DISEÑO TEXTIL S.A. (INDITEX S.A.) +// +// SPDX-License-Identifier: Apache-2.0 + +import { toast } from "sonner"; +import { useWeave } from "@inditextech/weave-react"; +import React from "react"; +import { + WEAVE_NODES_SELECTION_KEY, + WeaveNodesSelectionPlugin, + WEAVE_NODES_SNAPPING_PLUGIN_KEY, + WeaveNodesSnappingPlugin, +} from "@inditextech/weave-sdk"; +import { SidebarActive, useCollaborationRoom } from "@/store/store"; +import { SIDEBAR_ELEMENTS } from "@/lib/constants"; + +export const useHandleGuides = () => { + const instance = useWeave((state) => state.instance); + + const viewType = useCollaborationRoom((state) => state.viewType); + const setShowRightSidebarFloating = useCollaborationRoom( + (state) => state.setShowRightSidebarFloating, + ); + const setSidebarActive = useCollaborationRoom( + (state) => state.setSidebarActive, + ); + + const sidebarToggle = React.useCallback( + (element: SidebarActive) => { + if (viewType === "floating") { + setShowRightSidebarFloating(true); + } + setSidebarActive(element); + }, + [setSidebarActive, viewType, setShowRightSidebarFloating], + ); + + const copyGuides = React.useCallback( + async (containerId?: string) => { + if (!instance) { + return; + } + + const nodesSelectionPlugin = + instance.getPlugin( + WEAVE_NODES_SELECTION_KEY, + ); + + if (!nodesSelectionPlugin) { + return; + } + + const snappingManagerPlugin = + instance.getPlugin( + WEAVE_NODES_SNAPPING_PLUGIN_KEY, + ); + + if (!snappingManagerPlugin) { + return; + } + + let selectedContainerId: string = instance.getMainLayer()?.id() ?? ""; + if (!containerId) { + const selectedNodes = nodesSelectionPlugin.getSelectedNodes(); + + if (selectedNodes.length > 1) { + toast.error("Please select a single container to copy guides from"); + return; + } + + if ( + selectedNodes.length === 1 && + selectedNodes[0].getAttrs().nodeType !== "frame" + ) { + toast.error("Please select a container to copy guides from"); + return; + } + + if (selectedNodes.length === 1) { + selectedContainerId = selectedNodes[0].id(); + } + } else { + const container = instance.getStage().findOne(`#${containerId}`); + + if (!container) { + toast.error("Container not found, please select another container"); + return; + } + + if ( + container.id() !== instance.getMainLayer()?.id() && + container.getAttrs().nodeType !== "frame" + ) { + toast.error("Please select a container to copy guides from"); + return; + } + + selectedContainerId = container.id(); + } + + try { + await snappingManagerPlugin.copyContainerGuidesToClipboard( + selectedContainerId, + ); + toast.success("Guides copied to clipboard"); + } catch (err) { + const e = err as Error; + if (e.message === "No guides to copy") { + toast.error("No guides to copy, please select another container"); + return; + } + if (e.message === "Container not found") { + toast.error("Container not found, please select another container"); + return; + } + toast.error("Failed to copy guides to clipboard, please try again"); + return; + } + }, + [instance], + ); + + const pasteGuides = React.useCallback( + async (containerId?: string) => { + if (!instance) { + return; + } + + const nodesSelectionPlugin = + instance.getPlugin( + WEAVE_NODES_SELECTION_KEY, + ); + + if (!nodesSelectionPlugin) { + return; + } + + const snappingManagerPlugin = + instance.getPlugin( + WEAVE_NODES_SNAPPING_PLUGIN_KEY, + ); + + if (!snappingManagerPlugin) { + return; + } + + let selectedContainerId: string = instance.getMainLayer()?.id() ?? ""; + if (!containerId) { + const selectedNodes = nodesSelectionPlugin.getSelectedNodes(); + + if (selectedNodes.length > 1) { + toast.error("Please select a single container to copy guides from"); + return; + } + + if ( + selectedNodes.length === 1 && + selectedNodes[0].getAttrs().nodeType !== "frame" + ) { + toast.error("Please select a container to copy guides from"); + return; + } + + if (selectedNodes.length === 1) { + selectedContainerId = selectedNodes[0].id(); + } + } else { + const container = instance.getStage().findOne(`#${containerId}`); + + if (!container) { + toast.error("Container not found, please select another container"); + return; + } + + if ( + container.id() !== instance.getMainLayer()?.id() && + container.getAttrs().nodeType !== "frame" + ) { + toast.error("Please select a container to copy guides from"); + return; + } + + selectedContainerId = container.id(); + } + + try { + await snappingManagerPlugin.pasteGuidesFromClipboard( + selectedContainerId, + ); + + toast.success("Guides pasted from clipboard"); + + if ( + !snappingManagerPlugin + .getGuidesManager() + .isCustomGuidesVisible(selectedContainerId) + ) { + snappingManagerPlugin + .getGuidesManager() + .toggleCustomGuides(selectedContainerId); + } + + sidebarToggle(SIDEBAR_ELEMENTS.guides); + } catch (err) { + const e = err as Error; + if (e.message === "Clipboard does not contain valid guides data") { + toast.error( + "No guides data found in clipboard, please copy guides from a container first", + ); + return; + } + if (e.message === "Cannot parse clipboard data as guides") { + toast.error( + "Failed to read guides data from clipboard, please try again", + ); + return; + } + toast.error("Failed to paste guides from clipboard, please try again"); + } + }, + [sidebarToggle, instance], + ); + const toggleContainerGuides = React.useCallback( + (containerId: string) => { + if (!instance) { + return; + } + + const nodesSelectionPlugin = + instance.getPlugin( + WEAVE_NODES_SELECTION_KEY, + ); + + if (!nodesSelectionPlugin) { + return; + } + + const snappingManagerPlugin = + instance.getPlugin( + WEAVE_NODES_SNAPPING_PLUGIN_KEY, + ); + + if (!snappingManagerPlugin) { + return; + } + + const container = instance.getStage().findOne(`#${containerId}`); + + if (!container) { + toast.error("Container not found, please select another container"); + return; + } + + if ( + container.id() !== instance.getMainLayer()?.id() && + container.getAttrs().nodeType !== "frame" + ) { + toast.error("Please select a container to toggle guides from"); + return; + } + + snappingManagerPlugin.getGuidesManager().toggleCustomGuides(containerId); + sidebarToggle(SIDEBAR_ELEMENTS.guides); + }, + [instance], + ); + + return { + copyGuides, + pasteGuides, + toggleContainerGuides, + }; +}; diff --git a/code/components/room-components/hooks/use-handle-room-events.tsx b/code/components/room-components/hooks/use-handle-room-events.tsx index 5f2d6688..1ad824a0 100644 --- a/code/components/room-components/hooks/use-handle-room-events.tsx +++ b/code/components/room-components/hooks/use-handle-room-events.tsx @@ -108,7 +108,6 @@ export const useHandleRoomEvents = () => { React.useEffect(() => { const handleRoomDeleted = async (payload: { roomId: string }) => { refreshRoomData(payload.roomId); - sessionStorage.removeItem(`weave.js_${payload.roomId}`); await instance?.getStore().disconnect(); navigate({ to: "/" }); diff --git a/code/components/room-components/hooks/use-json-template.tsx b/code/components/room-components/hooks/use-json-template.tsx new file mode 100644 index 00000000..76a5f6f3 --- /dev/null +++ b/code/components/room-components/hooks/use-json-template.tsx @@ -0,0 +1,131 @@ +// SPDX-FileCopyrightText: 2025 2025 INDUSTRIA DE DISEÑO TEXTIL S.A. (INDITEX S.A.) +// +// SPDX-License-Identifier: Apache-2.0 + +import React from "react"; +import { Weave } from "@inditextech/weave-sdk"; +import { BoundingBox, WeaveSelection } from "@inditextech/weave-types"; +import { useWeave } from "@inditextech/weave-react"; +import Konva from "konva"; + +function getGroupTopLeft(instance: Weave, nodes: WeaveSelection[]) { + if (!nodes.length) return null; + + const stage = instance.getStage(); + + let minX = Infinity; + let minY = Infinity; + + nodes.forEach((node) => { + const rect = node.instance.getClientRect({ relativeTo: stage }); + minX = Math.min(minX, rect.x); + minY = Math.min(minY, rect.y); + }); + + return { x: minX, y: minY }; +} + +export const useJsonTemplate = () => { + const instance = useWeave((state) => state.instance); + + const generateJsonTemplate = React.useCallback( + (nodes: WeaveSelection[]) => { + if (!instance) + throw new Error("Instance is required to generate JSON template", { + cause: "NoInstance", + }); + + if (nodes.length === 0) { + throw new Error("No nodes selected to generate JSON template", { + cause: "NoNodesSelected", + }); + } + + const topLeft = getGroupTopLeft(instance, nodes); + + const mappedNodes = []; + for (const node of nodes) { + mappedNodes.push( + mapNode( + instance, + node.instance, + topLeft ?? { x: 0, y: 0 }, + instance.getStage() as Konva.Container, + ), + ); + } + + return { + version: "1.0", + name: "test", + nodes: mappedNodes, + }; + }, + [instance], + ); + + return { + generateJsonTemplate, + }; +}; + +const mapNode = ( + instance: Weave, + node: Konva.Node, + topLeft: Konva.Vector2d, + relativeTo: Konva.Container, + // eslint-disable-next-line @typescript-eslint/no-explicit-any +): any => { + const nodeType = node.getAttrs().nodeType; + + const boundigBox: BoundingBox = node.getClientRect({ + skipStroke: true, + relativeTo, + }); + + switch (nodeType) { + case "frame": { + const containerId = node.getAttrs().containerId; + const frameContainer = (node as Konva.Group).findOne(`#${containerId}`); + + if (!frameContainer) { + throw new Error( + `Container with id ${containerId} not found for frame ${node.id()}`, + ); + } + + const nodes = (frameContainer as Konva.Group).find(".node"); + + return { + id: node.id(), + x: Number((boundigBox.x - topLeft.x).toFixed(6)), + y: Number((boundigBox.y - topLeft.y).toFixed(6)), + width: + Number(boundigBox.width.toFixed(6)) - node.getAttrs().strokeWidth * 2, + height: + Number(boundigBox.height.toFixed(6)) - + node.getAttrs().strokeWidth * 2, + kind: "frame", + children: nodes.map((childNode) => + // eslint-disable-next-line @typescript-eslint/no-explicit-any + mapNode(instance, childNode, { x: 0, y: 0 }, frameContainer as any), + ), + editable: true, + optional: true, + }; + } + + default: { + return { + id: node.id(), + x: Number((boundigBox.x - topLeft.x).toFixed(6)), + y: Number((boundigBox.y - topLeft.y).toFixed(6)), + width: Number(boundigBox.width.toFixed(6)), + height: Number(boundigBox.height.toFixed(6)), + kind: nodeType === "rectangle" ? "image" : nodeType, + editable: true, + optional: true, + }; + } + } +}; diff --git a/code/components/room-components/hooks/use-keyboard-handler.tsx b/code/components/room-components/hooks/use-keyboard-handler.tsx index 360345ae..df2df3b6 100644 --- a/code/components/room-components/hooks/use-keyboard-handler.tsx +++ b/code/components/room-components/hooks/use-keyboard-handler.tsx @@ -10,19 +10,28 @@ import { SidebarActive, useCollaborationRoom } from "@/store/store"; import { WEAVE_IMAGE_TOOL_ACTION_NAME, WEAVE_IMAGES_TOOL_ACTION_NAME, + WEAVE_NODES_SELECTION_KEY, WeaveNodesSelectionPlugin, WeaveUsersPointersPlugin, - // WeaveNodesSnappingPlugin, - // GUIDE_ORIENTATION, + GUIDE_ORIENTATION, WEAVE_IMAGE_NODE_TYPE, + WeaveImageNode, } from "@inditextech/weave-sdk"; import { SIDEBAR_ELEMENTS } from "@/lib/constants"; +import { useHandleGuides } from "./use-handle-guides"; +import Konva from "konva"; export function useKeyboardHandler() { const instance = useWeave((state) => state.instance); const selectedNodes = useWeave((state) => state.selection.nodes); const actualAction = useWeave((state) => state.actions.actual); + const [guidesSequence, setGuidesSequence] = React.useState(false); + + const viewType = useCollaborationRoom((state) => state.viewType); + const setShowRightSidebarFloating = useCollaborationRoom( + (state) => state.setShowRightSidebarFloating, + ); const setSidebarActive = useCollaborationRoom( (state) => state.setSidebarActive, ); @@ -34,9 +43,9 @@ export function useKeyboardHandler() { ); const triggerTool = React.useCallback( - (toolName: string, params?: unknown) => { + (toolName: string, params?: unknown, forceExecution?: boolean) => { if (instance && actualAction !== toolName) { - instance.triggerAction(toolName, params); + instance.triggerAction(toolName, params, forceExecution); } if (instance && actualAction === toolName) { instance.cancelAction(toolName); @@ -51,9 +60,12 @@ export function useKeyboardHandler() { const sidebarToggle = React.useCallback( (element: SidebarActive) => { + if (viewType === "floating") { + setShowRightSidebarFloating(true); + } setSidebarActive(element); }, - [setSidebarActive], + [setSidebarActive, viewType, setShowRightSidebarFloating], ); const handleTriggerAction = React.useCallback( @@ -89,322 +101,700 @@ export function useKeyboardHandler() { return false; }, [actualAction]); - // TOOLBAR HOTKEYS + const { copyGuides, pasteGuides, toggleContainerGuides } = useHandleGuides(); - // MOVE, SELECTION AND DELETE TOOLS + // GUIDES HOTKEYS - useHotkey({ key: "M", mod: false, shift: false }, () => { - triggerTool("moveTool"); + useHotkey({ key: "G" }, () => { + setGuidesSequence(true); }); - useHotkey({ key: "S", mod: false, shift: false }, () => { - triggerTool("selectionTool"); - }); + useHotkey( + { key: "C", mod: true }, + async (e) => { + if (!guidesSequence) { + return; + } + e.preventDefault(); + e.stopPropagation(); + setGuidesSequence(false); - useHotkey({ key: "D", mod: false, shift: false }, () => { - triggerTool("eraserTool"); - }); + await copyGuides(); + }, + { + preventDefault: false, + stopPropagation: false, + }, + ); - // SHAPES TOOLS + useHotkey( + { key: "V", mod: true }, + async (e) => { + if (!guidesSequence) { + return; + } + e.preventDefault(); + e.stopPropagation(); + setGuidesSequence(false); - useHotkey({ key: "R", mod: false, shift: false }, () => { - triggerTool("rectangleTool"); - }); + await pasteGuides(); + }, + { + preventDefault: false, + stopPropagation: false, + }, + ); - useHotkey({ key: "E", mod: false, shift: false }, () => { - triggerTool("ellipseTool"); + useHotkey({ key: "H" }, () => { + if (!guidesSequence) { + return; + } + setGuidesSequence(false); + triggerTool("guideTool", { orientation: GUIDE_ORIENTATION.HORIZONTAL }); }); - useHotkey({ key: "P", mod: false, shift: false }, () => { - triggerTool("regularPolygonTool"); + useHotkey({ key: "V" }, () => { + if (!guidesSequence) { + return; + } + setGuidesSequence(false); + triggerTool("guideTool", { orientation: GUIDE_ORIENTATION.VERTICAL }); }); - useHotkey({ key: "J", mod: false, shift: false }, () => { - triggerTool("starTool"); - }); + useHotkey({ key: "T" }, () => { + if (!guidesSequence) { + return; + } - useHotkey({ key: "L", mod: false, shift: false }, () => { - triggerTool("strokeTool"); - }); + if (!instance) { + return; + } - useHotkey({ key: "B", mod: false, shift: false }, () => { - triggerTool("brushTool"); - }); + const nodesSelectionPlugin = instance?.getPlugin( + WEAVE_NODES_SELECTION_KEY, + ); + + if (nodesSelectionPlugin) { + const mainLayer = instance.getMainLayer(); + let containerId = mainLayer?.id() ?? ""; + let performToggle = true; + if ( + nodesSelectionPlugin.getSelectedNodes().length === 1 && + nodesSelectionPlugin.getSelectedNodes()[0].getAttrs().nodeType === + "frame" + ) { + containerId = nodesSelectionPlugin.getSelectedNodes()[0].id(); + } + if ( + nodesSelectionPlugin.getSelectedNodes().length === 1 && + nodesSelectionPlugin.getSelectedNodes()[0].getAttrs().nodeType !== + "frame" + ) { + performToggle = false; + } + + if (performToggle) { + toggleContainerGuides(containerId); + } + } - useHotkey({ key: "Q", mod: false, shift: false }, () => { - triggerTool("arrowTool"); + setGuidesSequence(false); }); + // TOOLBAR HOTKEYS + + // MOVE, SELECTION AND DELETE TOOLS + + useHotkey( + { key: "M", mod: false, shift: false }, + () => { + triggerTool("moveTool"); + }, + { + enabled: keysEnabled, + }, + ); + + useHotkey( + { key: "S", mod: false, shift: false }, + () => { + triggerTool("selectionTool"); + }, + { + enabled: keysEnabled, + }, + ); + + useHotkey( + { key: "D", mod: false, shift: false }, + () => { + if (guidesSequence) { + return; + } + triggerTool("eraserTool"); + }, + { + enabled: keysEnabled, + }, + ); + + // SHAPES TOOLS + + useHotkey( + { key: "R", mod: false, shift: false }, + () => { + triggerTool("rectangleTool"); + }, + { + enabled: keysEnabled, + }, + ); + + useHotkey( + { key: "E", mod: false, shift: false }, + () => { + if (guidesSequence) { + return; + } + triggerTool("ellipseTool"); + }, + { + enabled: keysEnabled, + conflictBehavior: "allow", + }, + ); + + useHotkey( + { key: "P", mod: false, shift: false }, + () => { + triggerTool("regularPolygonTool"); + }, + { + enabled: keysEnabled, + }, + ); + + useHotkey( + { key: "J", mod: false, shift: false }, + () => { + triggerTool("starTool"); + }, + { + enabled: keysEnabled, + }, + ); + + useHotkey( + { key: "L", mod: false, shift: false }, + () => { + triggerTool("strokeTool"); + }, + { + enabled: keysEnabled, + }, + ); + + useHotkey( + { key: "B", mod: false, shift: false }, + () => { + triggerTool("brushTool"); + }, + { + enabled: keysEnabled, + }, + ); + + useHotkey( + { key: "Q", mod: false, shift: false }, + () => { + triggerTool("arrowTool"); + }, + { + enabled: keysEnabled, + }, + ); + // MEDIA TOOLS - useHotkey({ key: "I", mod: false, shift: false }, () => { - triggerTool(WEAVE_IMAGE_TOOL_ACTION_NAME); - setShowSelectFileImage(true); - }); + useHotkey( + { key: "I", mod: false, shift: false }, + () => { + triggerTool(WEAVE_IMAGE_TOOL_ACTION_NAME); + setShowSelectFileImage(true); + }, + { + enabled: keysEnabled, + }, + ); - useHotkey({ key: "O", mod: false, shift: false }, () => { - triggerTool(WEAVE_IMAGES_TOOL_ACTION_NAME); - setShowSelectFileImage(true); - }); + useHotkey( + { key: "O", mod: false, shift: false }, + () => { + triggerTool(WEAVE_IMAGES_TOOL_ACTION_NAME); + setShowSelectFileImage(true); + }, + { + enabled: keysEnabled, + }, + ); // TEXT TOOLS - useHotkey({ key: "T", mod: false, shift: false }, () => { - triggerTool("textTool"); - }); + useHotkey( + { key: "T", mod: false, shift: false }, + () => { + if (guidesSequence) { + return; + } + triggerTool("textTool"); + }, + { + enabled: keysEnabled, + conflictBehavior: "allow", + }, + ); // OTHER TOOLS - useHotkey({ key: "F", mod: false, shift: false }, () => { - triggerTool("frameTool"); - }); + useHotkey( + { key: "F", mod: false, shift: false }, + () => { + triggerTool("frameTool"); + }, + { + enabled: keysEnabled, + }, + ); - useHotkey({ key: "K", mod: false, shift: false }, () => { - triggerTool("colorTokenTool"); - }); + useHotkey( + { key: "K", mod: false, shift: false }, + () => { + triggerTool("colorTokenTool"); + }, + { + enabled: keysEnabled, + }, + ); - useHotkey({ key: "X", mod: false, shift: false }, () => { - triggerTool("connectorTool"); - }); + useHotkey( + { key: "X", mod: false, shift: false }, + () => { + triggerTool("connectorTool"); + }, + { + enabled: keysEnabled, + }, + ); - useHotkey({ key: "H", mod: false, shift: false }, () => { - if (!threadsEnabled) { - return; - } + useHotkey( + { key: "H", mod: false, shift: false }, + () => { + if (guidesSequence) { + return; + } - triggerTool("commentTool"); - sidebarToggle(SIDEBAR_ELEMENTS.comments); - }); + if (!threadsEnabled) { + return; + } + + triggerTool("commentTool"); + sidebarToggle(SIDEBAR_ELEMENTS.comments); + }, + { + enabled: keysEnabled, + conflictBehavior: "allow", + }, + ); // UNDO / REDO - useHotkey({ key: "Z", mod: true, shift: false }, () => { - if (!instance) { - return; - } + useHotkey( + { key: "Z", mod: true, shift: false }, + () => { + if (!instance) { + return; + } - const actualStore = instance.getStore(); - actualStore.undoStateStep(); - }); + const actualStore = instance.getStore(); + actualStore.undoStateStep(); + }, + { + enabled: keysEnabled, + }, + ); - useHotkey({ key: "Y", mod: true, shift: false }, () => { - if (!instance) { - return; - } + useHotkey( + { key: "Y", mod: true, shift: false }, + () => { + if (!instance) { + return; + } - const actualStore = instance.getStore(); - actualStore.redoStateStep(); - }); + const actualStore = instance.getStore(); + actualStore.redoStateStep(); + }, + { + enabled: keysEnabled, + }, + ); // VISIBILITY HOTKEYS - useHotkey({ key: "U", mod: true, shift: true }, () => { - if (!instance) { - return; - } + useHotkey( + { key: "U", mod: true, shift: true }, + () => { + if (!instance) { + return; + } - const usersPointersPlugin = - instance.getPlugin("usersPointers"); + const usersPointersPlugin = + instance.getPlugin("usersPointers"); - if (!usersPointersPlugin) { - return; - } + if (!usersPointersPlugin) { + return; + } - if (usersPointersPlugin.isEnabled()) { - usersPointersPlugin.disable(); - } else { - usersPointersPlugin.enable(); - } - }); + if (usersPointersPlugin.isEnabled()) { + usersPointersPlugin.disable(); + } else { + usersPointersPlugin.enable(); + } + }, + { + enabled: keysEnabled, + }, + ); // SELECTION HOTKEYS - useHotkey({ key: "A", mod: true, shift: true }, () => { - if (!instance) { - return; - } + useHotkey( + { key: "A", mod: true, shift: true }, + () => { + if (!instance) { + return; + } - const selectionPlugin = - instance.getPlugin("nodesSelection"); + const selectionPlugin = + instance.getPlugin("nodesSelection"); - if (!selectionPlugin) { - return; - } + if (!selectionPlugin) { + return; + } - selectionPlugin.selectAll(); - }); + selectionPlugin.selectAll(); + }, + { + enabled: keysEnabled, + }, + ); - useHotkey({ key: "Escape", mod: false, shift: true }, () => { - if (!instance) { - return; - } + useHotkey( + { key: "Escape", mod: false, shift: true }, + () => { + if (!instance) { + return; + } - const selectionPlugin = - instance.getPlugin("nodesSelection"); + const selectionPlugin = + instance.getPlugin("nodesSelection"); - if (!selectionPlugin) { - return; - } + if (!selectionPlugin) { + return; + } - selectionPlugin.selectNone(); - }); + selectionPlugin.selectNone(); + }, + { + enabled: keysEnabled, + preventDefault: false, + stopPropagation: false, + }, + ); // Z-INDEX HOTKEYS - useHotkey({ key: "BracketRight", mod: false, shift: false }, () => { - if (!instance) { - return; - } + useHotkey( + { key: "BracketRight", mod: false, shift: false }, + () => { + if (!instance) { + return; + } - if (selectedNodes.length !== 1) { - return; - } + if (selectedNodes.length !== 1) { + return; + } - instance.bringToFront(selectedNodes[0].instance); - }); + instance.bringToFront(selectedNodes[0].instance); + }, + { + enabled: keysEnabled, + }, + ); - useHotkey({ key: "BracketRight", mod: false, shift: true }, () => { - if (!instance) { - return; - } + useHotkey( + { key: "BracketRight", mod: false, shift: true }, + () => { + if (!instance) { + return; + } - if (selectedNodes.length !== 1) { - return; - } + if (selectedNodes.length !== 1) { + return; + } - instance.moveUp(selectedNodes[0].instance); - }); + instance.moveUp(selectedNodes[0].instance); + }, + { + enabled: keysEnabled, + }, + ); - useHotkey({ key: "BracketLeft", mod: false, shift: true }, () => { - if (!instance) { - return; - } + useHotkey( + { key: "BracketLeft", mod: false, shift: true }, + () => { + if (!instance) { + return; + } - if (selectedNodes.length !== 1) { - return; - } + if (selectedNodes.length !== 1) { + return; + } - instance.moveDown(selectedNodes[0].instance); - }); + instance.moveDown(selectedNodes[0].instance); + }, + { + enabled: keysEnabled, + }, + ); - useHotkey({ key: "BracketLeft", mod: false, shift: false }, () => { - if (!instance) { - return; - } + useHotkey( + { key: "BracketLeft", mod: false, shift: false }, + () => { + if (!instance) { + return; + } - if (selectedNodes.length !== 1) { - return; - } + if (selectedNodes.length !== 1) { + return; + } - instance.sendToBack(selectedNodes[0].instance); - }); + instance.sendToBack(selectedNodes[0].instance); + }, + { + enabled: keysEnabled, + }, + ); // GROUPING HOTKEYS - useHotkey({ key: "G", mod: true, shift: false }, () => { - if (!instance) { - return; - } + useHotkey( + { key: "G", mod: true, shift: false }, + () => { + if (!instance) { + return; + } - if (selectedNodes.length <= 1) { - return; - } + if (selectedNodes.length <= 1) { + return; + } - instance.group( - selectedNodes - .map((n) => n?.node) - .filter((node) => typeof node !== "undefined"), - ); - }); + instance.group( + selectedNodes + .map((n) => n?.node) + .filter((node) => typeof node !== "undefined"), + ); + }, + { + enabled: keysEnabled, + conflictBehavior: "allow", + }, + ); - useHotkey({ key: "U", mod: true, shift: false }, () => { - if (!instance) { - return; - } + useHotkey( + { key: "U", mod: true, shift: false }, + () => { + if (!instance) { + return; + } - if (selectedNodes.length !== 1 || selectedNodes[0].node?.type !== "group") { - return; - } + if ( + selectedNodes.length !== 1 || + selectedNodes[0].node?.type !== "group" + ) { + return; + } - instance.unGroup(selectedNodes[0].node); - }); + instance.unGroup(selectedNodes[0].node); + }, + { + enabled: keysEnabled, + }, + ); // SIDEBARS HOTKEYS - useHotkey({ key: "I", mod: true, alt: true, shift: false }, () => { - sidebarToggle(SIDEBAR_ELEMENTS.images); - }); + useHotkey( + { key: "I", mod: false, alt: false, shift: true }, + () => { + sidebarToggle(SIDEBAR_ELEMENTS.images); + }, + { + enabled: keysEnabled, + }, + ); - useHotkey({ key: "V", mod: true, alt: true, shift: false }, () => { - sidebarToggle(SIDEBAR_ELEMENTS.videos); - }); + useHotkey( + { key: "V", mod: false, alt: false, shift: true }, + () => { + sidebarToggle(SIDEBAR_ELEMENTS.videos); + }, + { + enabled: keysEnabled, + conflictBehavior: "allow", + }, + ); - useHotkey({ key: "C", mod: true, alt: true, shift: false }, () => { - sidebarToggle(SIDEBAR_ELEMENTS.colorTokens); - }); + useHotkey( + { key: "C", mod: false, alt: false, shift: true }, + () => { + sidebarToggle(SIDEBAR_ELEMENTS.colorTokens); + }, + { + enabled: keysEnabled, + }, + ); - useHotkey({ key: "F", mod: true, alt: true, shift: false }, () => { - sidebarToggle(SIDEBAR_ELEMENTS.frames); - }); + useHotkey( + { key: "F", mod: false, alt: false, shift: true }, + () => { + sidebarToggle(SIDEBAR_ELEMENTS.frames); + }, + { + enabled: keysEnabled, + }, + ); - useHotkey({ key: "T", mod: true, alt: true, shift: false }, () => { - sidebarToggle(SIDEBAR_ELEMENTS.templates); - }); + // useHotkey( + // { key: "T", mod: false, alt: false, shift: true }, + // () => { + // sidebarToggle(SIDEBAR_ELEMENTS.templates); + // }, + // { + // enabled: keysEnabled, + // }, + // ); - useHotkey({ key: "E", mod: true, alt: true, shift: false }, () => { - sidebarToggle(SIDEBAR_ELEMENTS.nodesTree); - }); + useHotkey( + { key: "O", mod: false, alt: false, shift: true }, + () => { + sidebarToggle(SIDEBAR_ELEMENTS.comments); + }, + { + enabled: keysEnabled, + }, + ); + + useHotkey( + { key: "G", mod: false, alt: false, shift: true }, + () => { + sidebarToggle(SIDEBAR_ELEMENTS.guides); + }, + { + enabled: keysEnabled, + }, + ); + + useHotkey( + { key: "E", mod: false, alt: false, shift: true }, + () => { + sidebarToggle(SIDEBAR_ELEMENTS.nodesTree); + }, + { + enabled: keysEnabled, + conflictBehavior: "allow", + }, + ); // ZOOM HOTKEYS - useHotkey({ key: "ArrowDown", mod: false, shift: true }, () => { - if (!isZoomingAllowed) { - return; - } + useHotkey( + { key: "ArrowDown", mod: false, alt: true, shift: false }, + () => { + if (!isZoomingAllowed) { + return; + } - handleTriggerAction("zoomInTool", { previousAction: actualAction }); - }); + handleTriggerAction("zoomInTool", { previousAction: actualAction }); + }, + { + enabled: keysEnabled, + }, + ); - useHotkey({ key: "ArrowUp", mod: false, shift: true }, () => { - if (!isZoomingAllowed) { - return; - } + useHotkey( + { key: "ArrowUp", mod: false, alt: true, shift: false }, + () => { + if (!isZoomingAllowed) { + return; + } - handleTriggerAction("zoomOutTool", { previousAction: actualAction }); - }); + handleTriggerAction("zoomOutTool", { previousAction: actualAction }); + }, + { + enabled: keysEnabled, + }, + ); - useHotkey({ key: "1", mod: false, shift: true }, () => { - if (!isZoomingAllowed) { - return; - } + useHotkey( + { key: "1", mod: false, shift: true }, + () => { + if (!isZoomingAllowed) { + return; + } - handleTriggerAction("fitToScreenTool", { - previousAction: actualAction, - overrideZoom: false, - }); - }); + handleTriggerAction("fitToScreenTool", { + previousAction: actualAction, + overrideZoom: false, + }); + }, + { + enabled: keysEnabled, + }, + ); - useHotkey({ key: "2", mod: false, shift: true }, () => { - if (!isZoomingAllowed) { - return; - } + useHotkey( + { key: "2", mod: false, shift: true }, + () => { + if (!isZoomingAllowed) { + return; + } - handleTriggerAction("fitToSelectionTool", { - previousAction: actualAction, - overrideZoom: false, - }); - }); + handleTriggerAction("fitToSelectionTool", { + previousAction: actualAction, + overrideZoom: false, + }); + }, + { + enabled: keysEnabled, + }, + ); - useHotkey({ key: "3", mod: false, shift: true }, () => { - if (!isZoomingAllowed) { - return; - } + useHotkey( + { key: "3", mod: false, shift: true }, + () => { + if (!isZoomingAllowed) { + return; + } - handleTriggerAction("fitToPageTool", { - previousAction: actualAction, - overrideZoom: false, - }); - }); + handleTriggerAction("fitToPageTool", { + previousAction: actualAction, + overrideZoom: false, + }); + }, + { + enabled: keysEnabled, + }, + ); // OTHER HOTKEYS @@ -434,12 +824,14 @@ export function useKeyboardHandler() { ); if (image && reference) { - const imageHandler = instance.getNodeHandler(WEAVE_IMAGE_NODE_TYPE); + const imageHandler = instance.getNodeHandler( + WEAVE_IMAGE_NODE_TYPE, + ); if (imageHandler) { try { imageHandler.cropImageWithReference( - image.instance, + image.instance as Konva.Group, reference.instance, ); } catch (e) { diff --git a/code/components/room-components/hooks/use-on-paste-external-image.ts b/code/components/room-components/hooks/use-on-paste-external-image.ts index afc72e47..c679fcf5 100644 --- a/code/components/room-components/hooks/use-on-paste-external-image.ts +++ b/code/components/room-components/hooks/use-on-paste-external-image.ts @@ -62,8 +62,6 @@ export function useOnPasteExternalImage() { pastingToastIdRef.current = null; } - console.log("onAddedImageHandler"); - toast.success("Paste successful"); const node = instance?.getStage().findOne(`#${nodeId}`); @@ -223,7 +221,6 @@ export function useOnPasteExternalImage() { }, uploadImageFunction, imageId: resourceId, - forceMainContainer: false, ...(position && { position }), }); } diff --git a/code/components/room-components/hooks/use-update-page-thumbnail.tsx b/code/components/room-components/hooks/use-update-page-thumbnail.tsx index f4617628..03cbc888 100644 --- a/code/components/room-components/hooks/use-update-page-thumbnail.tsx +++ b/code/components/room-components/hooks/use-update-page-thumbnail.tsx @@ -134,6 +134,10 @@ export const useUpdatePageThumbnail = () => { const exportRect = exportAreaReferencePlugin.getExportRect(); + if (!exportRect) { + return; + } + clonedStage.toBlob({ x: exportRect.x, y: exportRect.y, diff --git a/code/components/room-components/manage-idle-disconnection.tsx b/code/components/room-components/manage-idle-disconnection.tsx index 730ce0fb..6669562f 100644 --- a/code/components/room-components/manage-idle-disconnection.tsx +++ b/code/components/room-components/manage-idle-disconnection.tsx @@ -50,7 +50,7 @@ export const ManageIdleDisconnection = () => { timeoutRef.current = setTimeout(async () => { setShowIdleWarning(false); sessionStorage.removeItem(`weave.js_${room}`); - console.log("AQUI disconnect?"); + console.log("Idle disconnect"); await instance?.getStore().disconnect(); navigate({ to: "/" }); }, TIME_UNTIL_DISCONNECTION_SECONDS * 1000); diff --git a/code/components/room-components/node-properties/arrow-properties.tsx b/code/components/room-components/node-properties/arrow-properties.tsx index 660198f8..71ae3f77 100644 --- a/code/components/room-components/node-properties/arrow-properties.tsx +++ b/code/components/room-components/node-properties/arrow-properties.tsx @@ -188,6 +188,7 @@ export function ArrowProperties() { hideSearch label="Line width" options={[ + { label: "None", value: "0" }, { label: "Small", value: "1" }, { label: "Medium", value: "3" }, { label: "Large", value: "5" }, @@ -262,7 +263,9 @@ export function ArrowProperties() { ...actualNode, props: { ...actualNode.props, - dash: value.split(",").map((v) => parseInt(v)), + dash: value + ? value.split(",").map((v) => parseInt(v)) + : [], }, }; updateElement(updatedNode); diff --git a/code/components/room-components/node-properties/text-properties.tsx b/code/components/room-components/node-properties/text-properties.tsx index b93f09ac..5ed8010b 100644 --- a/code/components/room-components/node-properties/text-properties.tsx +++ b/code/components/room-components/node-properties/text-properties.tsx @@ -108,7 +108,6 @@ export function TextProperties() { { - console.log("Selected font family:", value); const updatedNode: WeaveStateElement = { ...actualNode, props: { diff --git a/code/components/room-components/overlay/export-page-to-image-config.tsx b/code/components/room-components/overlay/export-page-to-image-config.tsx index 07e01257..4bbeeb30 100644 --- a/code/components/room-components/overlay/export-page-to-image-config.tsx +++ b/code/components/room-components/overlay/export-page-to-image-config.tsx @@ -101,6 +101,10 @@ export function ExportPageToImageConfigDialog({ relativeTo: stage, }); + if (!exportRect) { + return; + } + handleExportPageToImageServerSide({ type: "area", area: exportRect, diff --git a/code/components/room-components/overlay/export-room-to-pdf-config.tsx b/code/components/room-components/overlay/export-room-to-pdf-config.tsx index 2df5e67e..6a232ed8 100644 --- a/code/components/room-components/overlay/export-room-to-pdf-config.tsx +++ b/code/components/room-components/overlay/export-room-to-pdf-config.tsx @@ -110,6 +110,10 @@ export function ExportRoomToPdfConfigDialog({ relativeTo: stage, }); + if (!exportRect) { + return; + } + handleExportRoomToPDFServerSide({ type: "area", area: exportRect, diff --git a/code/components/room-components/overlay/hooks/use-media-tools.tsx b/code/components/room-components/overlay/hooks/use-media-tools.tsx index 7f74329b..167c9b24 100644 --- a/code/components/room-components/overlay/hooks/use-media-tools.tsx +++ b/code/components/room-components/overlay/hooks/use-media-tools.tsx @@ -3,8 +3,6 @@ // SPDX-License-Identifier: Apache-2.0 import React from "react"; -import { ShortcutElement } from "../../help/shortcut-element"; -import { SYSTEM_OS } from "@/lib/utils"; import { useWeave } from "@inditextech/weave-react"; import { Image, Images, Video } from "lucide-react"; import { useCollaborationRoom } from "@/store/store"; @@ -12,6 +10,7 @@ import { WEAVE_IMAGE_TOOL_ACTION_NAME, WEAVE_IMAGES_TOOL_ACTION_NAME, } from "@inditextech/weave-sdk"; +import { formatForDisplay } from "@tanstack/react-hotkeys"; export const useMediaTools = () => { const instance = useWeave((state) => state.instance); @@ -55,12 +54,7 @@ export const useMediaTools = () => { label: (

Image tool

- + {formatForDisplay("I")}
), onClick: () => { @@ -73,12 +67,7 @@ export const useMediaTools = () => { label: (

Images tool

- + {formatForDisplay("O")}
), onClick: () => { diff --git a/code/components/room-components/overlay/hooks/use-node-layering-tool.tsx b/code/components/room-components/overlay/hooks/use-node-layering-tool.tsx index 954b037a..f18cf2fc 100644 --- a/code/components/room-components/overlay/hooks/use-node-layering-tool.tsx +++ b/code/components/room-components/overlay/hooks/use-node-layering-tool.tsx @@ -4,10 +4,9 @@ import { BringToFront, ArrowUp, ArrowDown, SendToBack } from "lucide-react"; import React from "react"; -import { ShortcutElement } from "../../help/shortcut-element"; -import { SYSTEM_OS } from "@/lib/utils"; import { useWeave } from "@inditextech/weave-react"; import { WeaveElementInstance } from "@inditextech/weave-types"; +import { formatForDisplay } from "@tanstack/react-hotkeys"; export const useNodeLayeringTool = () => { const instance = useWeave((state) => state.instance); @@ -28,12 +27,7 @@ export const useNodeLayeringTool = () => { label: (

Bring to front

- + {formatForDisplay("]")}
), onClick: () => { @@ -56,12 +50,7 @@ export const useNodeLayeringTool = () => { label: (

Move up

- + {formatForDisplay("Mod+]")}
), onClick: () => { @@ -84,12 +73,7 @@ export const useNodeLayeringTool = () => { label: (

Move down

- + {formatForDisplay("Mod+[")}
), onClick: () => { @@ -112,12 +96,7 @@ export const useNodeLayeringTool = () => { label: (

Send to back

- + {formatForDisplay("J")}
), onClick: () => { @@ -136,7 +115,7 @@ export const useNodeLayeringTool = () => { active: () => false, }, }), - [instance, node] + [instance, node], ); return NODE_LAYERING_TOOL; diff --git a/code/components/room-components/overlay/hooks/use-other-tools.tsx b/code/components/room-components/overlay/hooks/use-other-tools.tsx index dc20918e..4270584f 100644 --- a/code/components/room-components/overlay/hooks/use-other-tools.tsx +++ b/code/components/room-components/overlay/hooks/use-other-tools.tsx @@ -11,11 +11,11 @@ import { MessageSquare, RulerDimensionLine, Tag, + Video, } from "lucide-react"; import { SidebarActive, useCollaborationRoom } from "@/store/store"; import { SIDEBAR_ELEMENTS } from "@/lib/constants"; -import { ShortcutElement } from "../../help/shortcut-element"; -import { SYSTEM_OS } from "@/lib/utils"; +import { formatForDisplay } from "@tanstack/react-hotkeys"; export const useOtherTools = () => { const instance = useWeave((state) => state.instance); @@ -33,6 +33,9 @@ export const useOtherTools = () => { const measurementReferenceMeasurePixels = useCollaborationRoom( (state) => state.measurement.referenceMeasurePixels, ); + const setShowSelectFileVideo = useCollaborationRoom( + (state) => state.setShowSelectFileVideo, + ); const setSidebarActive = useCollaborationRoom( (state) => state.setSidebarActive, ); @@ -79,6 +82,19 @@ export const useOtherTools = () => { }, active: () => actualAction === "frameTool", }, + videoTool: { + icon: