Skip to content

Commit d70a335

Browse files
authored
ENG-797: Handle multiple canvases loaded at the same time (#928)
* [ENG-797] Handle multiple canvases loaded at the same time Scope active canvas focus so clipboard, query builder actions, and shared context don't leak across simultaneously open canvases. * [ENG-797] Add pageUid to action listener deps * [ENG-797] Fix cleanup guard and narrow closest selector - Check activeCanvasEditor identity in cleanup to avoid clearing a canvas that was already replaced by another instance - Guard onPointerDownCapture against null editor ref - Narrow closest() selector to .roamjs-tldraw-canvas-container so nested data-page-uid attributes don't match incorrectly * [ENG-797] Restore discourseContext.relations in Export and lastActions/lastAppEvent in Tldraw Reverts two removals flagged in review: the discourseContext.relations population in Export.tsx (needed when exporting to canvas) and the lastAppEvent/lastActions fields on discourseContext (used to log recent user actions on error).
1 parent 059380f commit d70a335

2 files changed

Lines changed: 58 additions & 2 deletions

File tree

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

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,33 @@ export const discourseContext: DiscourseContextType = {
140140
lastActions: [],
141141
};
142142

143+
let activeCanvasPageUid: string | null = null;
144+
let activeCanvasEditor: Editor | null = null;
145+
146+
const setActiveCanvas = ({
147+
pageUid,
148+
editor,
149+
}: {
150+
pageUid: string;
151+
editor: Editor | null;
152+
}) => {
153+
if (activeCanvasPageUid === pageUid && activeCanvasEditor === editor) {
154+
if (editor && !editor.getInstanceState().isFocused) editor.focus();
155+
return;
156+
}
157+
158+
if (activeCanvasEditor && activeCanvasEditor !== editor) {
159+
activeCanvasEditor.blur();
160+
}
161+
162+
activeCanvasPageUid = pageUid;
163+
activeCanvasEditor = editor;
164+
165+
if (editor && !editor.getInstanceState().isFocused) {
166+
editor.focus();
167+
}
168+
};
169+
143170
export const DEFAULT_WIDTH = 160;
144171
export const DEFAULT_HEIGHT = 64;
145172
export const MAX_WIDTH = "400px";
@@ -726,6 +753,17 @@ const TldrawCanvasShared = ({
726753
inSidebar: !!containerRef.current?.closest(".rm-sidebar-outline"),
727754
});
728755
}, [pageUid]);
756+
757+
useEffect(() => {
758+
return () => {
759+
const editor = appRef.current;
760+
if (activeCanvasPageUid === pageUid && activeCanvasEditor === editor) {
761+
activeCanvasEditor?.blur();
762+
activeCanvasPageUid = null;
763+
activeCanvasEditor = null;
764+
}
765+
};
766+
}, [pageUid]);
729767
const { store, needsUpgrade, performUpgrade, error, isLoading } =
730768
useStoreAdapter(storeAdapterArgs);
731769
const migratedCloudStoreRef = useRef<string | null>(null);
@@ -789,10 +827,14 @@ const TldrawCanvasShared = ({
789827
uid?: string;
790828
val?: string;
791829
shapeId?: TLShapeId;
830+
targetCanvasPageUid?: string;
792831
onRefresh: () => void;
793832
}>,
794833
) => {
795834
if (!/canvas/i.test(e.detail.action)) return;
835+
const targetCanvasPageUid =
836+
e.detail.targetCanvasPageUid ?? activeCanvasPageUid;
837+
if (targetCanvasPageUid !== pageUid) return;
796838
const app = appRef.current;
797839
if (!app) return;
798840
const { x, y } = app.getViewportScreenCenter();
@@ -830,7 +872,7 @@ const TldrawCanvasShared = ({
830872
actionListener,
831873
);
832874
};
833-
}, [appRef, allNodes]);
875+
}, [appRef, allNodes, pageUid]);
834876

835877
// Catch a custom event we used patch-package to add
836878
useEffect(() => {
@@ -918,6 +960,10 @@ const TldrawCanvasShared = ({
918960
tabIndex={-1}
919961
onDragOver={handleDragOver}
920962
onDrop={handleDrop}
963+
onPointerDownCapture={() => {
964+
if (!appRef.current) return;
965+
setActiveCanvas({ pageUid, editor: appRef.current });
966+
}}
921967
>
922968
{isCloudflareSync && (
923969
<div
@@ -988,6 +1034,7 @@ const TldrawCanvasShared = ({
9881034
<TldrawEditor
9891035
// baseUrl="https://samepage.network/assets/tldraw/"
9901036
// instanceId={initialState.instanceId}
1037+
autoFocus={false}
9911038
initialState="select"
9921039
shapeUtils={[...defaultShapeUtils, ...customShapeUtils]}
9931040
tools={[...defaultTools, ...defaultShapeTools, ...customTools]}
@@ -1003,6 +1050,10 @@ const TldrawCanvasShared = ({
10031050

10041051
appRef.current = app;
10051052

1053+
if (!activeCanvasPageUid || activeCanvasPageUid === pageUid) {
1054+
setActiveCanvas({ pageUid, editor: app });
1055+
}
1056+
10061057
void syncCanvasNodeTitlesOnLoad(
10071058
app,
10081059
allNodes.map((n) => n.type),

apps/roam/src/components/results-view/ResultsTable.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,11 @@ const ResultRow = ({
229229
return (
230230
<Button
231231
{...buttonProps}
232-
onClick={() => {
232+
onClick={(event) => {
233+
const targetCanvasPageUid =
234+
event.currentTarget.closest<HTMLElement>(
235+
".roamjs-tldraw-canvas-container[data-page-uid]",
236+
)?.dataset.pageUid || undefined;
233237
document.dispatchEvent(
234238
new CustomEvent("roamjs:query-builder:action", {
235239
detail: {
@@ -238,6 +242,7 @@ const ResultRow = ({
238242
val: r["text"],
239243
onRefresh,
240244
queryUid: parentUid,
245+
targetCanvasPageUid,
241246
},
242247
}),
243248
);

0 commit comments

Comments
 (0)