diff --git a/apps/roam/src/components/canvas/CanvasDrawer.tsx b/apps/roam/src/components/canvas/CanvasDrawer.tsx index f1c4b6d35..e527854d9 100644 --- a/apps/roam/src/components/canvas/CanvasDrawer.tsx +++ b/apps/roam/src/components/canvas/CanvasDrawer.tsx @@ -1,16 +1,8 @@ -import React, { - useCallback, - useEffect, - useMemo, - useRef, - useState, -} from "react"; -import ResizableDrawer from "~/components/ResizableDrawer"; -import renderOverlay from "roamjs-components/util/renderOverlay"; +import React, { useCallback, useEffect, useMemo, useState } from "react"; import { Button, - Card, Collapse, + Icon, Menu, MenuItem, NonIdealState, @@ -22,13 +14,11 @@ import { Tag, Tooltip, } from "@blueprintjs/core"; +import { Editor, useEditor, TLShapeId } from "tldraw"; import getPageTitleByPageUid from "roamjs-components/queries/getPageTitleByPageUid"; -import getDiscourseNodes from "~/utils/getDiscourseNodes"; import getCurrentPageUid from "roamjs-components/dom/getCurrentPageUid"; -import getBlockProps from "~/utils/getBlockProps"; -import { TLBaseShape } from "tldraw"; +import getDiscourseNodes from "~/utils/getDiscourseNodes"; import { DiscourseNodeShape } from "./DiscourseNodeUtil"; -import { render as renderToast } from "roamjs-components/components/Toast"; import { formatHexColor } from "~/components/settings/DiscourseNodeCanvasSettings"; export type GroupedShapes = Record; @@ -42,36 +32,17 @@ type NodeGroup = { isDuplicate: boolean; }; -// Module-level ref holder set by the provider -// This allows openCanvasDrawer to be called from non-React contexts -// (command palette, context menus, etc.) -let drawerUnmountRef: React.MutableRefObject<(() => void) | null> | null = null; - -export const CanvasDrawerProvider = ({ - children, -}: { - children: React.ReactNode; -}) => { - const unmountRef = useRef<(() => void) | null>(null); - - useEffect(() => { - drawerUnmountRef = unmountRef; - - return () => { - if (unmountRef.current) { - unmountRef.current(); - unmountRef.current = null; - } - drawerUnmountRef = null; - }; - }, []); - - return <>{children}; +type Props = { + groupedShapes: GroupedShapes; + pageUid: string; + editor: Editor; }; -type Props = { groupedShapes: GroupedShapes; pageUid: string }; - -const CanvasDrawerContent = ({ groupedShapes, pageUid }: Props) => { +export const CanvasDrawerContent = ({ + groupedShapes, + pageUid, + editor, +}: Props) => { const [openSections, setOpenSections] = useState>({}); const [activeShapeId, setActiveShapeId] = useState(null); const [filterType, setFilterType] = useState("All"); @@ -172,13 +143,19 @@ const CanvasDrawerContent = ({ groupedShapes, pageUid }: Props) => { })); }, []); - const moveCameraToShape = useCallback((shapeId: string) => { - document.dispatchEvent( - new CustomEvent("roamjs:query-builder:action", { - detail: { action: "move-camera-to-shape", shapeId }, - }), - ); - }, []); + const moveCameraToShape = useCallback( + (shapeId: string) => { + const shape = editor.getShape(shapeId as TLShapeId); + if (!shape) { + return; + } + const x = shape.x || 0; + const y = shape.y || 0; + editor.centerOnPoint({ x, y }, { animation: { duration: 200 } }); + editor.select(shapeId as TLShapeId); + }, + [editor], + ); const handleShapeSelection = useCallback( (shape: DiscourseNodeShape) => { @@ -328,8 +305,8 @@ const CanvasDrawerContent = ({ groupedShapes, pageUid }: Props) => { ); return ( -
- +
+
{ )}
- +
{!visibleGroups.length ? ( { } /> ) : ( - +
{visibleGroups.map((group) => renderListView(group))} - +
)}
); }; -const CanvasDrawer = ({ - onClose, - unmountRef, - ...props -}: { - onClose: () => void; - unmountRef: React.MutableRefObject<(() => void) | null>; -} & Props) => { - const handleClose = () => { - unmountRef.current = null; - onClose(); - }; +export const CanvasDrawerPanel = () => { + const editor = useEditor(); + const toggleDrawer = useCallback(() => { + setIsOpen((prev) => !prev); + }, []); + const [isOpen, setIsOpen] = useState(false); + const pageUid = getCurrentPageUid(); + const [groupedShapes, setGroupedShapes] = useState({}); - return ( - - - - ); -}; + useEffect(() => { + const updateGroupedShapes = () => { + const allRecords = editor.store.allRecords(); + const shapes = allRecords.filter((record) => { + if (record.typeName !== "shape") return false; + const shape = record as DiscourseNodeShape; + return !!shape.props?.uid; + }) as DiscourseNodeShape[]; + + const grouped = shapes.reduce((acc: GroupedShapes, shape) => { + const uid = shape.props.uid; + if (!acc[uid]) acc[uid] = []; + acc[uid].push(shape); + return acc; + }, {}); + + setGroupedShapes(grouped); + }; + + updateGroupedShapes(); -export const openCanvasDrawer = (): void => { - if (!drawerUnmountRef) { - renderToast({ - id: "canvas-drawer-not-found", - content: - "Unable to open Canvas Drawer. Please load canvas in main window first.", - intent: "warning", + const unsubscribe = editor.store.listen(() => { + updateGroupedShapes(); }); - console.error( - "CanvasDrawer: Cannot open drawer - CanvasDrawerProvider not found", - ); - return; - } - if (drawerUnmountRef.current) { - drawerUnmountRef.current(); - drawerUnmountRef.current = null; - return; - } + return () => { + unsubscribe(); + }; + }, [editor.store]); - const pageUid = getCurrentPageUid(); - const props = getBlockProps(pageUid) as Record; - const rjsqb = props["roamjs-query-builder"] as Record; - const tldraw = (rjsqb?.tldraw as Record) || {}; - const store = (tldraw?.["store"] as Record) || {}; - const shapes = Object.values(store).filter((s) => { - const shape = s as TLBaseShape; - const uid = shape.props?.uid; - return !!uid; - }) as DiscourseNodeShape[]; - - const groupShapesByUid = (shapes: DiscourseNodeShape[]) => { - const groupedShapes = shapes.reduce((acc: GroupedShapes, shape) => { - const uid = shape.props.uid; - if (!acc[uid]) acc[uid] = []; - acc[uid].push(shape); - return acc; - }, {}); - - return groupedShapes; - }; - - const groupedShapes = groupShapesByUid(shapes); - drawerUnmountRef.current = - renderOverlay({ - // eslint-disable-next-line @typescript-eslint/naming-convention - Overlay: CanvasDrawer, - props: { groupedShapes, pageUid, unmountRef: drawerUnmountRef }, - }) || null; + return ( + <> +
+
+ {isOpen && ( +
+
+
+
+

+ Canvas Drawer +

+
+
+
+
+ +
+
+ )} + + ); }; - -export default CanvasDrawer; diff --git a/apps/roam/src/components/canvas/CanvasDrawerButton.tsx b/apps/roam/src/components/canvas/CanvasDrawerButton.tsx deleted file mode 100644 index d24516387..000000000 --- a/apps/roam/src/components/canvas/CanvasDrawerButton.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import React from "react"; -import { Button, Icon } from "@blueprintjs/core"; -import { openCanvasDrawer } from "./CanvasDrawer"; - -const CanvasDrawerButton = () => { - return ( -
-
- ); -}; - -export default CanvasDrawerButton; diff --git a/apps/roam/src/components/canvas/Tldraw.tsx b/apps/roam/src/components/canvas/Tldraw.tsx index 4dc7d66ac..c7388d0a6 100644 --- a/apps/roam/src/components/canvas/Tldraw.tsx +++ b/apps/roam/src/components/canvas/Tldraw.tsx @@ -84,8 +84,7 @@ import { import ConvertToDialog from "./ConvertToDialog"; import { createMigrations } from "./DiscourseRelationShape/discourseRelationMigrations"; import ToastListener, { dispatchToastEvent } from "./ToastListener"; -import CanvasDrawerButton from "./CanvasDrawerButton"; -import { CanvasDrawerProvider } from "./CanvasDrawer"; +import { CanvasDrawerPanel } from "./CanvasDrawer"; import sendErrorEmail from "~/utils/sendErrorEmail"; import { AUTO_CANVAS_RELATIONS_KEY } from "~/data/userSettings"; import { getSetting } from "~/utils/extensionSettings"; @@ -453,26 +452,6 @@ const TldrawCanvas = ({ title }: { title: string }) => { // Handle actions (roamjs:query-builder:action) useEffect(() => { - const handleMoveCameraToShapeAction = ({ - shapeId, - }: { - shapeId: TLShapeId; - }) => { - const app = appRef.current; - if (!app) return; - const shape = app.getShape(shapeId); - if (!shape) { - return dispatchToastEvent({ - id: "tldraw-warning", - title: `Shape not found.`, - severity: "warning", - }); - } - const x = shape?.x || 0; - const y = shape?.y || 0; - app.centerOnPoint({ x, y }, { animation: { duration: 200 } }); - app.select(shapeId); - }; const actionListener = (( e: CustomEvent<{ action: string; @@ -482,10 +461,6 @@ const TldrawCanvas = ({ title }: { title: string }) => { onRefresh: () => void; }>, ) => { - if (e.detail.action === "move-camera-to-shape") { - if (!e.detail.shapeId) return; - handleMoveCameraToShapeAction({ shapeId: e.detail.shapeId }); - } if (!/canvas/i.test(e.detail.action)) return; const app = appRef.current; if (!app) return; @@ -707,9 +682,9 @@ const TldrawCanvas = ({ title }: { title: string }) => { allRelationIds={allRelationIds} allAddReferencedNodeActions={allAddReferencedNodeActions} /> + - )} @@ -993,9 +968,7 @@ const renderTldrawCanvasHelper = ({ const unmount = renderWithUnmount( - - - + , canvasWrapperEl, ); diff --git a/apps/roam/src/components/canvas/uiOverrides.tsx b/apps/roam/src/components/canvas/uiOverrides.tsx index 2b841b327..dd57c2533 100644 --- a/apps/roam/src/components/canvas/uiOverrides.tsx +++ b/apps/roam/src/components/canvas/uiOverrides.tsx @@ -41,7 +41,6 @@ import { DiscourseContextType } from "./Tldraw"; import { formatHexColor } from "~/components/settings/DiscourseNodeCanvasSettings"; import { COLOR_ARRAY } from "./DiscourseNodeUtil"; import calcCanvasNodeSizeAndImg from "~/utils/calcCanvasNodeSizeAndImg"; -import { openCanvasDrawer } from "./CanvasDrawer"; import { AddReferencedNodeType } from "./DiscourseRelationShape/DiscourseRelationTool"; import { dispatchToastEvent } from "./ToastListener"; import { getRelationColor } from "./DiscourseRelationShape/DiscourseRelationUtil"; @@ -185,16 +184,6 @@ export const CustomContextMenu = ({ return ( - {!selectedShape && ( - - - - )} {(isTextSelected || isImageSelected) && ( diff --git a/apps/roam/src/utils/registerCommandPaletteCommands.ts b/apps/roam/src/utils/registerCommandPaletteCommands.ts index ae61a4ae1..a963e0c3a 100644 --- a/apps/roam/src/utils/registerCommandPaletteCommands.ts +++ b/apps/roam/src/utils/registerCommandPaletteCommands.ts @@ -1,4 +1,3 @@ -import { openCanvasDrawer } from "~/components/canvas/CanvasDrawer"; import { openQueryDrawer } from "~/components/QueryDrawer"; import { render as exportRender } from "~/components/Export"; import { render as renderToast } from "roamjs-components/components/Toast"; @@ -145,7 +144,6 @@ export const registerCommandPaletteCommands = (onloadArgs: OnloadArgs) => { // Roam organizes commands by alphabetically addCommand("DG: Export - Current Page", exportCurrentPage); addCommand("DG: Export - Discourse Graph", exportDiscourseGraph); - addCommand("DG: Open - Canvas Drawer", openCanvasDrawer); addCommand("DG: Open - Discourse Settings", renderSettingsPopup); addCommand("DG: Open - Query Drawer", openQueryDrawerWithArgs); addCommand("DG: Query Block - Create", createQueryBlock);