Skip to content

Commit 4a2de51

Browse files
authored
ENG-1494 - Split 3 sync ui (#839)
* fix(roam): harden setHintingShapes against atom reaction cycle errors * feat(sync-worker): add Cloudflare tldraw sync worker foundation * feat(roam): add persisted canvas sync mode toggle plumbing * re-add custom view menu * . * order
1 parent f80038e commit 4a2de51

3 files changed

Lines changed: 144 additions & 1 deletion

File tree

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

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,12 @@ import { TLRecord } from "@tldraw/tlschema";
104104
import { WHITE_LOGO_SVG } from "~/icons";
105105
import { BLOCK_REF_REGEX } from "roamjs-components/dom";
106106
import { defaultHandleExternalTextContent } from "./defaultHandleExternalTextContent";
107+
import {
108+
CanvasSyncMode,
109+
ensureCanvasSyncMode,
110+
getEffectiveCanvasSyncMode,
111+
setCanvasSyncMode,
112+
} from "./canvasSyncMode";
107113
import posthog from "posthog-js";
108114

109115
declare global {
@@ -507,6 +513,22 @@ const TldrawCanvas = ({ title }: { title: string }) => {
507513
});
508514
};
509515

516+
const pageUid = useMemo(() => getPageUidByPageTitle(title), [title]);
517+
const [canvasSyncMode, setCanvasSyncModeState] = useState<CanvasSyncMode>(
518+
() => getEffectiveCanvasSyncMode({ pageUid }),
519+
);
520+
useEffect(() => {
521+
setCanvasSyncModeState(ensureCanvasSyncMode({ pageUid }));
522+
}, [pageUid]);
523+
524+
const onCanvasSyncModeChange = useCallback(
525+
(mode: CanvasSyncMode) => {
526+
setCanvasSyncMode({ pageUid, mode });
527+
setCanvasSyncModeState(mode);
528+
},
529+
[pageUid],
530+
);
531+
510532
// COMPONENTS
511533
const defaultEditorComponents: TLEditorComponents = {
512534
Scribble: TldrawScribble,
@@ -523,6 +545,8 @@ const TldrawCanvas = ({ title }: { title: string }) => {
523545
allNodes,
524546
allRelationNames,
525547
allAddReferencedNodeActions,
548+
canvasSyncMode,
549+
onCanvasSyncModeChange,
526550
});
527551

528552
// UTILS
@@ -572,7 +596,6 @@ const TldrawCanvas = ({ title }: { title: string }) => {
572596
});
573597

574598
// STORE
575-
const pageUid = useMemo(() => getPageUidByPageTitle(title), [title]);
576599
useEffect(() => {
577600
posthog.capture("Canvas: Opened", {
578601
pageUid,
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import getBlockProps, { json } from "~/utils/getBlockProps";
2+
import setBlockProps from "~/utils/setBlockProps";
3+
4+
export type CanvasSyncMode = "local" | "sync";
5+
6+
const QUERY_BUILDER_PROP_KEY = "roamjs-query-builder";
7+
const CANVAS_SYNC_MODE_KEY = "canvasSyncMode";
8+
const DEFAULT_CANVAS_SYNC_MODE: CanvasSyncMode = "local";
9+
10+
const isCanvasSyncMode = (value: unknown): value is CanvasSyncMode =>
11+
value === "local" || value === "sync";
12+
13+
const getRoamJsQueryBuilderProps = (pageUid: string): Record<string, json> => {
14+
const props = getBlockProps(pageUid);
15+
const value = props[QUERY_BUILDER_PROP_KEY];
16+
if (value && typeof value === "object" && !Array.isArray(value)) {
17+
return value;
18+
}
19+
return {};
20+
};
21+
22+
export const getPersistedCanvasSyncMode = ({
23+
pageUid,
24+
}: {
25+
pageUid: string;
26+
}): CanvasSyncMode | null => {
27+
const rjsqb = getRoamJsQueryBuilderProps(pageUid);
28+
const mode = rjsqb[CANVAS_SYNC_MODE_KEY];
29+
return isCanvasSyncMode(mode) ? mode : null;
30+
};
31+
32+
export const getEffectiveCanvasSyncMode = ({
33+
pageUid,
34+
}: {
35+
pageUid: string;
36+
}): CanvasSyncMode => {
37+
return getPersistedCanvasSyncMode({ pageUid }) ?? DEFAULT_CANVAS_SYNC_MODE;
38+
};
39+
40+
export const setCanvasSyncMode = ({
41+
pageUid,
42+
mode,
43+
}: {
44+
pageUid: string;
45+
mode: CanvasSyncMode;
46+
}): void => {
47+
const rjsqb = getRoamJsQueryBuilderProps(pageUid);
48+
setBlockProps(pageUid, {
49+
[QUERY_BUILDER_PROP_KEY]: {
50+
...rjsqb,
51+
[CANVAS_SYNC_MODE_KEY]: mode,
52+
},
53+
});
54+
};
55+
56+
export const ensureCanvasSyncMode = ({
57+
pageUid,
58+
}: {
59+
pageUid: string;
60+
}): CanvasSyncMode => {
61+
const mode = getPersistedCanvasSyncMode({ pageUid });
62+
if (mode) return mode;
63+
setCanvasSyncMode({ pageUid, mode: DEFAULT_CANVAS_SYNC_MODE });
64+
return DEFAULT_CANVAS_SYNC_MODE;
65+
};

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

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ import {
1616
TldrawUiMenuItem,
1717
DefaultMainMenu,
1818
TldrawUiMenuGroup,
19+
TldrawUiDropdownMenuItem,
20+
TldrawUiButton,
21+
TldrawUiButtonLabel,
22+
TldrawUiIcon,
1923
useActions,
2024
DefaultContextMenu,
2125
DefaultContextMenuContent,
@@ -46,6 +50,41 @@ import { DISCOURSE_TOOL_SHORTCUT_KEY } from "~/data/userSettings";
4650
import { getSetting } from "~/utils/extensionSettings";
4751
import { CustomDefaultToolbar } from "./CustomDefaultToolbar";
4852
import { renderModifyNodeDialog } from "~/components/ModifyNodeDialog";
53+
import { CanvasSyncMode } from "./canvasSyncMode";
54+
55+
const SyncModeMenuSwitchItem = ({
56+
checked,
57+
disabled,
58+
label,
59+
onToggle,
60+
}: {
61+
checked: boolean;
62+
disabled?: boolean;
63+
label: string;
64+
onToggle: () => void;
65+
}): React.ReactElement => {
66+
return (
67+
<TldrawUiDropdownMenuItem>
68+
<TldrawUiButton
69+
type="menu"
70+
title={label}
71+
disabled={disabled}
72+
onClick={onToggle}
73+
>
74+
<TldrawUiButtonLabel>{label}</TldrawUiButtonLabel>
75+
<span
76+
style={{
77+
marginLeft: "auto",
78+
display: "inline-flex",
79+
alignItems: "center",
80+
}}
81+
>
82+
<TldrawUiIcon icon={checked ? "toggle-on" : "toggle-off"} small />
83+
</span>
84+
</TldrawUiButton>
85+
</TldrawUiDropdownMenuItem>
86+
);
87+
};
4988

5089
export const getOnSelectForShape = ({
5190
shape,
@@ -185,10 +224,14 @@ export const createUiComponents = ({
185224
allNodes,
186225
allAddReferencedNodeActions,
187226
allRelationNames,
227+
canvasSyncMode,
228+
onCanvasSyncModeChange,
188229
}: {
189230
allNodes: DiscourseNode[];
190231
allRelationNames: string[];
191232
allAddReferencedNodeActions: string[];
233+
canvasSyncMode: CanvasSyncMode;
234+
onCanvasSyncModeChange: (mode: CanvasSyncMode) => void;
192235
}): TLUiComponents => {
193236
return {
194237
Toolbar: (props) => {
@@ -234,9 +277,21 @@ export const createUiComponents = ({
234277
</TldrawUiMenuSubmenu>
235278
);
236279
};
280+
const onToggleSyncMode = (): void => {
281+
const nextMode: CanvasSyncMode =
282+
canvasSyncMode === "sync" ? "local" : "sync";
283+
onCanvasSyncModeChange(nextMode);
284+
};
237285

238286
return (
239287
<DefaultMainMenu>
288+
<TldrawUiMenuGroup id="sync-mode">
289+
<SyncModeMenuSwitchItem
290+
label="Use cloud canvas"
291+
checked={canvasSyncMode === "sync"}
292+
onToggle={onToggleSyncMode}
293+
/>
294+
</TldrawUiMenuGroup>
240295
<EditSubmenu />
241296
<CustomViewMenu /> {/* Replaced <ViewSubmenu /> */}
242297
<ExportFileContentSubMenu />

0 commit comments

Comments
 (0)