Skip to content

Commit e29c8fa

Browse files
ENG-977 Create icon button for canvas drawer (#500)
* feat: Add canvas drawer button to ToastListener Co-authored-by: mclicks <mclicks@gmail.com> * Refactor: Move CanvasDrawerButton to its own component Co-authored-by: mclicks <mclicks@gmail.com> * Refactor: Move CanvasDrawerButton outside TldrawCanvas Co-authored-by: mclicks <mclicks@gmail.com> * feat: Add toggle behavior to canvas drawer Co-authored-by: mclicks <mclicks@gmail.com> * feat: Implement CanvasDrawerProvider for improved drawer management - Added CanvasDrawerProvider to manage the canvas drawer's lifecycle and allow it to be called from non-React contexts. - Updated openCanvasDrawer function to utilize the new provider for better state handling. - Refactored CanvasDrawer component to accept unmountRef as a prop. - Enhanced CanvasDrawerButton styling for improved UI consistency. * . --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com>
1 parent 58b3729 commit e29c8fa

3 files changed

Lines changed: 103 additions & 10 deletions

File tree

apps/roam/src/components/canvas/CanvasDrawer.tsx

Lines changed: 71 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useEffect, useMemo, useState } from "react";
1+
import React, { useEffect, useMemo, useRef, useState } from "react";
22
import ResizableDrawer from "~/components/ResizableDrawer";
33
import renderOverlay from "roamjs-components/util/renderOverlay";
44
import { Button, Collapse, Checkbox } from "@blueprintjs/core";
@@ -9,9 +9,37 @@ import getCurrentPageUid from "roamjs-components/dom/getCurrentPageUid";
99
import getBlockProps from "~/utils/getBlockProps";
1010
import { TLBaseShape } from "tldraw";
1111
import { DiscourseNodeShape } from "./DiscourseNodeUtil";
12+
import { render as renderToast } from "roamjs-components/components/Toast";
1213

1314
export type GroupedShapes = Record<string, DiscourseNodeShape[]>;
1415

16+
// Module-level ref holder set by the provider
17+
// This allows openCanvasDrawer to be called from non-React contexts
18+
// (command palette, context menus, etc.)
19+
let drawerUnmountRef: React.MutableRefObject<(() => void) | null> | null = null;
20+
21+
export const CanvasDrawerProvider = ({
22+
children,
23+
}: {
24+
children: React.ReactNode;
25+
}) => {
26+
const unmountRef = useRef<(() => void) | null>(null);
27+
28+
useEffect(() => {
29+
drawerUnmountRef = unmountRef;
30+
31+
return () => {
32+
if (unmountRef.current) {
33+
unmountRef.current();
34+
unmountRef.current = null;
35+
}
36+
drawerUnmountRef = null;
37+
};
38+
}, []);
39+
40+
return <>{children}</>;
41+
};
42+
1543
type Props = { groupedShapes: GroupedShapes; pageUid: string };
1644

1745
const CanvasDrawerContent = ({ groupedShapes, pageUid }: Props) => {
@@ -20,7 +48,7 @@ const CanvasDrawerContent = ({ groupedShapes, pageUid }: Props) => {
2048
const [filterType, setFilterType] = useState("All");
2149
const [filteredShapes, setFilteredShapes] = useState<GroupedShapes>({});
2250

23-
const pageTitle = useMemo(() => getPageTitleByPageUid(pageUid), []);
51+
const pageTitle = useMemo(() => getPageTitleByPageUid(pageUid), [pageUid]);
2452
const noResults = Object.keys(groupedShapes).length === 0;
2553
const typeToTitleMap = useMemo(() => {
2654
const nodes = getDiscourseNodes();
@@ -143,14 +171,44 @@ const CanvasDrawerContent = ({ groupedShapes, pageUid }: Props) => {
143171

144172
const CanvasDrawer = ({
145173
onClose,
174+
unmountRef,
146175
...props
147-
}: { onClose: () => void } & Props) => (
148-
<ResizableDrawer onClose={onClose} title={"Canvas Drawer"}>
149-
<CanvasDrawerContent {...props} />
150-
</ResizableDrawer>
151-
);
176+
}: {
177+
onClose: () => void;
178+
unmountRef: React.MutableRefObject<(() => void) | null>;
179+
} & Props) => {
180+
const handleClose = () => {
181+
unmountRef.current = null;
182+
onClose();
183+
};
184+
185+
return (
186+
<ResizableDrawer onClose={handleClose} title={"Canvas Drawer"}>
187+
<CanvasDrawerContent {...props} />
188+
</ResizableDrawer>
189+
);
190+
};
191+
192+
export const openCanvasDrawer = (): void => {
193+
if (!drawerUnmountRef) {
194+
renderToast({
195+
id: "canvas-drawer-not-found",
196+
content:
197+
"Unable to open Canvas Drawer. Please load canvas in main window first.",
198+
intent: "warning",
199+
});
200+
console.error(
201+
"CanvasDrawer: Cannot open drawer - CanvasDrawerProvider not found",
202+
);
203+
return;
204+
}
205+
206+
if (drawerUnmountRef.current) {
207+
drawerUnmountRef.current();
208+
drawerUnmountRef.current = null;
209+
return;
210+
}
152211

153-
export const openCanvasDrawer = () => {
154212
const pageUid = getCurrentPageUid();
155213
const props = getBlockProps(pageUid) as Record<string, unknown>;
156214
const rjsqb = props["roamjs-query-builder"] as Record<string, unknown>;
@@ -174,7 +232,11 @@ export const openCanvasDrawer = () => {
174232
};
175233

176234
const groupedShapes = groupShapesByUid(shapes);
177-
renderOverlay({ Overlay: CanvasDrawer, props: { groupedShapes, pageUid } });
235+
drawerUnmountRef.current =
236+
renderOverlay({
237+
Overlay: CanvasDrawer,
238+
props: { groupedShapes, pageUid, unmountRef: drawerUnmountRef },
239+
}) || null;
178240
};
179241

180242
export default CanvasDrawer;
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import React from "react";
2+
import { Button, Icon } from "@blueprintjs/core";
3+
import { openCanvasDrawer } from "./CanvasDrawer";
4+
5+
const CanvasDrawerButton = () => {
6+
return (
7+
<div
8+
className="pointer-events-auto absolute top-11 m-2 rounded-lg"
9+
style={{
10+
zIndex: 250,
11+
// copying tldraw var(--shadow-2)
12+
boxShadow:
13+
"0px 0px 2px hsl(0, 0%, 0%, 16%), 0px 2px 3px hsl(0, 0%, 0%, 24%), 0px 2px 6px hsl(0, 0%, 0%, 0.1), inset 0px 0px 0px 1px hsl(0, 0%, 100%)",
14+
}}
15+
>
16+
<Button
17+
icon={<Icon icon="add-column-left" />}
18+
onClick={openCanvasDrawer}
19+
minimal
20+
title="Toggle Canvas Drawer"
21+
/>
22+
</div>
23+
);
24+
};
25+
26+
export default CanvasDrawerButton;

apps/roam/src/components/canvas/Tldraw.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ import {
8484
import ConvertToDialog from "./ConvertToDialog";
8585
import { createMigrations } from "./DiscourseRelationShape/discourseRelationMigrations";
8686
import ToastListener, { dispatchToastEvent } from "./ToastListener";
87+
import CanvasDrawerButton from "./CanvasDrawerButton";
88+
import { CanvasDrawerProvider } from "./CanvasDrawer";
8789
import sendErrorEmail from "~/utils/sendErrorEmail";
8890
import { AUTO_CANVAS_RELATIONS_KEY } from "~/data/userSettings";
8991
import { getSetting } from "~/utils/extensionSettings";
@@ -707,6 +709,7 @@ const TldrawCanvas = ({ title }: { title: string }) => {
707709
/>
708710
</TldrawUi>
709711
</TldrawEditor>
712+
<CanvasDrawerButton />
710713
</>
711714
)}
712715
</div>
@@ -990,7 +993,9 @@ const renderTldrawCanvasHelper = ({
990993

991994
const unmount = renderWithUnmount(
992995
<ExtensionApiContextProvider {...onloadArgs}>
993-
<TldrawCanvas title={title} />
996+
<CanvasDrawerProvider>
997+
<TldrawCanvas title={title} />
998+
</CanvasDrawerProvider>
994999
</ExtensionApiContextProvider>,
9951000
canvasWrapperEl,
9961001
);

0 commit comments

Comments
 (0)