diff --git a/apps/roam/src/components/canvas/Tldraw.tsx b/apps/roam/src/components/canvas/Tldraw.tsx index adfb1b416..d0426ec8c 100644 --- a/apps/roam/src/components/canvas/Tldraw.tsx +++ b/apps/roam/src/components/canvas/Tldraw.tsx @@ -1,5 +1,11 @@ /* eslint-disable @typescript-eslint/naming-convention */ -import React, { useState, useRef, useMemo, useEffect } from "react"; +import React, { + useState, + useRef, + useMemo, + useEffect, + useCallback, +} from "react"; import ExtensionApiContextProvider, { useExtensionAPI, } from "roamjs-components/components/ExtensionApiContext"; @@ -43,6 +49,7 @@ import { StateNode, DefaultSpinner, Box, + useValue, } from "tldraw"; import "tldraw/tldraw.css"; import tldrawStyles from "./tldrawStyles"; @@ -269,6 +276,31 @@ const TldrawCanvas = ({ title }: { title: string }) => { const allAddReferencedNodeActions = useMemo(() => { return Object.keys(allAddReferencedNodeByAction); }, [allAddReferencedNodeByAction]); + const stickyToolIds = useMemo( + () => [ + "discourse-tool", + ...allNodes.map((node) => node.type), + ...allRelationNames, + ...allAddReferencedNodeActions, + ], + [allNodes, allRelationNames, allAddReferencedNodeActions], + ); + const toolSelectionRef = useRef<{ + lastStickyToolId: string | null; + lastExplicitToolId: string | null; + }>({ + lastStickyToolId: null, + lastExplicitToolId: null, + }); + const handleToolSelected = useCallback( + (toolId: string) => { + toolSelectionRef.current.lastExplicitToolId = toolId; + if (stickyToolIds.includes(toolId)) { + toolSelectionRef.current.lastStickyToolId = toolId; + } + }, + [stickyToolIds], + ); const isRelationTool = (toolId: string) => { return ( @@ -433,6 +465,7 @@ const TldrawCanvas = ({ title }: { title: string }) => { toggleMaximized: handleMaximizedChange, setConvertToDialogOpen, discourseContext, + onToolSelected: handleToolSelected, }); // STORE @@ -688,6 +721,8 @@ const TldrawCanvas = ({ title }: { title: string }) => { @@ -707,17 +742,49 @@ const TldrawCanvas = ({ title }: { title: string }) => { const InsideEditorAndUiContext = ({ extensionAPI, allNodes, + stickyToolIds, + toolSelectionRef, // allRelationIds, // allAddReferencedNodeActions, }: { extensionAPI: OnloadArgs["extensionAPI"]; allNodes: DiscourseNode[]; + stickyToolIds: string[]; + toolSelectionRef: React.MutableRefObject<{ + lastStickyToolId: string | null; + lastExplicitToolId: string | null; + }>; // allRelationIds: string[]; // allAddReferencedNodeActions: string[]; }) => { const editor = useEditor(); const toasts = useToasts(); const msg = useTranslation(); + const currentToolId = useValue( + "currentToolId", + () => editor.getCurrentToolId(), + [editor], + ); + + useEffect(() => { + if (stickyToolIds.includes(currentToolId)) { + toolSelectionRef.current.lastStickyToolId = currentToolId; + return; + } + + if (currentToolId !== "select") { + return; + } + + if (toolSelectionRef.current.lastExplicitToolId === "select") return; + + const lastStickyTool = toolSelectionRef.current.lastStickyToolId; + if (!lastStickyTool) return; + if (lastStickyTool === "select") return; + if (editor.getCurrentToolId() !== "select") return; + + editor.setCurrentTool(lastStickyTool); + }, [currentToolId, editor, stickyToolIds, toolSelectionRef]); // const isCustomArrowShape = (shape: TLShape) => { // // TODO: find a better way to identify custom arrow shapes diff --git a/apps/roam/src/components/canvas/uiOverrides.tsx b/apps/roam/src/components/canvas/uiOverrides.tsx index b63cde1d2..a5477850a 100644 --- a/apps/roam/src/components/canvas/uiOverrides.tsx +++ b/apps/roam/src/components/canvas/uiOverrides.tsx @@ -260,6 +260,7 @@ export const createUiOverrides = ({ discourseContext, toggleMaximized, setConvertToDialogOpen, + onToolSelected, }: { allNodes: DiscourseNode[]; allRelationNames: string[]; @@ -267,6 +268,7 @@ export const createUiOverrides = ({ discourseContext: DiscourseContextType; toggleMaximized: () => void; setConvertToDialogOpen: (open: boolean) => void; + onToolSelected?: (toolId: string) => void; }): TLUiOverrides => ({ tools: (editor, tools) => { // Get the custom keyboard shortcut for the discourse tool @@ -285,9 +287,21 @@ export const createUiOverrides = ({ kbd: discourseToolShortcut, readonlyOk: true, onSelect: () => { + onToolSelected?.("discourse-tool"); editor.setCurrentTool("discourse-tool"); }, }; + if (tools["select"]) { + const selectTool = tools["select"]; + tools["select"] = { + ...selectTool, + onSelect: (source) => { + onToolSelected?.("select"); + selectTool.onSelect?.(source); + editor.setCurrentTool("select"); + }, + }; + } allNodes.forEach((node, index) => { const nodeId = node.type; tools[nodeId] = { @@ -296,6 +310,7 @@ export const createUiOverrides = ({ label: `shape.node.${node.type}` as TLUiTranslationKey, kbd: node.shortcut, onSelect: () => { + onToolSelected?.(nodeId); editor.setCurrentTool(nodeId); }, readonlyOk: true, @@ -315,6 +330,7 @@ export const createUiOverrides = ({ kbd: "", readonlyOk: true, onSelect: () => { + onToolSelected?.(name); editor.setCurrentTool(name); }, style: { @@ -338,6 +354,7 @@ export const createUiOverrides = ({ kbd: "", readonlyOk: true, onSelect: () => { + onToolSelected?.(name); editor.setCurrentTool(`${name}`); }, style: {