Skip to content

Commit 42e81c7

Browse files
committed
ENG-1290: Port discourse node config panel
1 parent 6cfb5dc commit 42e81c7

File tree

2 files changed

+226
-99
lines changed

2 files changed

+226
-99
lines changed

apps/roam/src/components/settings/DiscourseRelationConfigPanel.tsx

Lines changed: 137 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ import { formatHexColor } from "./DiscourseNodeCanvasSettings";
5151
import posthog from "posthog-js";
5252
import { getSetting, setSetting } from "~/utils/extensionSettings";
5353
import { USE_REIFIED_RELATIONS } from "~/data/userSettings";
54+
import { setGlobalSetting } from "~/components/settings/utils/accessors";
5455

5556
const DEFAULT_SELECTED_RELATION = {
5657
display: "none",
@@ -568,108 +569,147 @@ export const RelationEditPanel = ({
568569
className="select-none"
569570
onClick={() => {
570571
setLoading(true);
571-
setTimeout(async () => {
572-
const rootUid = editingRelationInfo.uid;
573-
setInputSetting({
574-
blockUid: rootUid,
575-
key: "source",
576-
value: source,
577-
});
578-
setInputSetting({
579-
blockUid: rootUid,
580-
key: "destination",
581-
value: destination,
582-
index: 1,
583-
});
584-
setInputSetting({
585-
blockUid: rootUid,
586-
key: "complement",
587-
value: complement,
588-
index: 2,
589-
});
590-
updateBlock({
591-
uid: rootUid,
592-
text: label,
593-
});
594-
const ifUid =
595-
editingRelationInfo.children.find((t) =>
596-
toFlexRegex("if").test(t.text),
597-
)?.uid ||
598-
(await createBlock({
599-
node: { text: "If" },
600-
parentUid: rootUid,
601-
order: 3,
602-
}));
603-
saveCyToElementRef(tab);
604-
const blocks = tabs
605-
.map((t) => elementsRef.current[t])
606-
.map((elements) => ({
607-
text: "And",
608-
children: elements
609-
.filter((e) => e.data.id.includes("-"))
610-
.map((e) => {
611-
const { source, target, relation } = e.data as {
612-
source: string;
613-
target: string;
614-
relation: string;
615-
};
616-
return {
617-
text: (
618-
elements.find((e) => e.data.id === source)?.data as {
619-
node: string;
620-
}
621-
)?.node,
622-
children: [
623-
{
624-
text: relation,
572+
setTimeout(
573+
() =>
574+
void (async () => {
575+
const rootUid = editingRelationInfo.uid;
576+
setInputSetting({
577+
blockUid: rootUid,
578+
key: "source",
579+
value: source,
580+
});
581+
setInputSetting({
582+
blockUid: rootUid,
583+
key: "destination",
584+
value: destination,
585+
index: 1,
586+
});
587+
setInputSetting({
588+
blockUid: rootUid,
589+
key: "complement",
590+
value: complement,
591+
index: 2,
592+
});
593+
updateBlock({
594+
uid: rootUid,
595+
text: label,
596+
});
597+
const ifUid =
598+
editingRelationInfo.children.find((t) =>
599+
toFlexRegex("if").test(t.text),
600+
)?.uid ||
601+
(await createBlock({
602+
node: { text: "If" },
603+
parentUid: rootUid,
604+
order: 3,
605+
}));
606+
saveCyToElementRef(tab);
607+
const blocks = tabs
608+
.map((t) => elementsRef.current[t])
609+
.map((elements) => ({
610+
text: "And",
611+
children: elements
612+
.filter((e) => e.data.id.includes("-"))
613+
.map((e) => {
614+
const { source, target, relation } = e.data as {
615+
source: string;
616+
target: string;
617+
relation: string;
618+
};
619+
return {
620+
text: (
621+
elements.find((e) => e.data.id === source)
622+
?.data as {
623+
node: string;
624+
}
625+
)?.node,
625626
children: [
626627
{
627-
text: ["source", "destination"].includes(target)
628-
? target
629-
: (
630-
elements.find((e) => e.data.id === target)
631-
?.data as { node: string }
632-
)?.node,
628+
text: relation,
629+
children: [
630+
{
631+
text: ["source", "destination"].includes(
632+
target,
633+
)
634+
? target
635+
: (
636+
elements.find(
637+
(e) => e.data.id === target,
638+
)?.data as { node: string }
639+
)?.node,
640+
},
641+
],
633642
},
634643
],
644+
};
645+
})
646+
.concat([
647+
{
648+
text: "node positions",
649+
children: elements
650+
.filter(
651+
(
652+
e,
653+
): e is {
654+
data: { id: string; node: unknown };
655+
position: { x: number; y: number };
656+
} => Object.keys(e).includes("position"),
657+
)
658+
.map((e) => ({
659+
text: e.data.id,
660+
children: [
661+
{ text: `${e.position.x} ${e.position.y}` },
662+
],
663+
})),
635664
},
636-
],
637-
};
638-
})
639-
.concat([
640-
{
641-
text: "node positions",
642-
children: elements
643-
.filter(
644-
(
645-
e,
646-
): e is {
647-
data: { id: string; node: unknown };
648-
position: { x: number; y: number };
649-
} => Object.keys(e).includes("position"),
650-
)
651-
.map((e) => ({
652-
text: e.data.id,
653-
children: [
654-
{ text: `${e.position.x} ${e.position.y}` },
655-
],
656-
})),
657-
},
658-
]),
659-
}));
660-
await Promise.all(
661-
getShallowTreeByParentUid(ifUid).map(({ uid }) =>
662-
deleteBlock(uid),
663-
),
664-
);
665-
await Promise.all(
666-
blocks.map((block, order) =>
667-
createBlock({ parentUid: ifUid, node: block, order }),
668-
),
669-
);
670-
refreshConfigTree();
671-
back();
672-
}, 1);
665+
]),
666+
}));
667+
await Promise.all(
668+
getShallowTreeByParentUid(ifUid).map(({ uid }) =>
669+
deleteBlock(uid),
670+
),
671+
);
672+
await Promise.all(
673+
blocks.map((block, order) =>
674+
createBlock({ parentUid: ifUid, node: block, order }),
675+
),
676+
);
677+
refreshConfigTree();
678+
679+
const ifConditions = blocks.map((block) => {
680+
const positionsChild = block.children.find(
681+
(c) => c.text === "node positions",
682+
);
683+
const triples = block.children
684+
.filter((c) => c.text !== "node positions")
685+
.map(
686+
(c) =>
687+
[
688+
c.text,
689+
c.children[0].text,
690+
c.children[0].children[0].text,
691+
] as [string, string, string],
692+
);
693+
const nodePositions = Object.fromEntries(
694+
(positionsChild?.children ?? []).map((c) => [
695+
c.text,
696+
c.children[0].text,
697+
]),
698+
);
699+
return { triples, nodePositions };
700+
});
701+
setGlobalSetting(["Relations", rootUid], {
702+
label,
703+
source,
704+
destination,
705+
complement,
706+
ifConditions,
707+
});
708+
709+
back();
710+
})(),
711+
1,
712+
);
673713
}}
674714
/>
675715
</div>

apps/roam/src/components/settings/utils/init.ts

Lines changed: 89 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ import getShallowTreeByParentUid from "roamjs-components/queries/getShallowTreeB
33
import { createPage, createBlock } from "roamjs-components/writes";
44
import setBlockProps from "~/utils/setBlockProps";
55
import getBlockProps from "~/utils/getBlockProps";
6+
import type { json } from "~/utils/getBlockProps";
67
import INITIAL_NODE_VALUES from "~/data/defaultDiscourseNodes";
8+
import DEFAULT_RELATIONS_BLOCK_PROPS from "~/components/settings/data/defaultRelationsBlockProps";
79
import { getAllDiscourseNodes } from "./accessors";
810
import {
911
DiscourseNodeSchema,
@@ -13,6 +15,7 @@ import {
1315
DG_BLOCK_PROP_SETTINGS_PAGE_TITLE,
1416
DISCOURSE_NODE_PAGE_PREFIX,
1517
} from "./zodSchema";
18+
import toFlexRegex from "roamjs-components/util/toFlexRegex";
1619

1720
const ensurePageExists = async (pageTitle: string): Promise<string> => {
1821
let pageUid = getPageUidByPageTitle(pageTitle);
@@ -66,6 +69,7 @@ const buildBlockMap = (pageUid: string): Record<string, string> => {
6669
};
6770

6871
const initializeSettingsBlockProps = (
72+
pageUid: string,
6973
blockMap: Record<string, string>,
7074
): void => {
7175
const configs = getTopLevelBlockPropsConfig();
@@ -74,10 +78,25 @@ const initializeSettingsBlockProps = (
7478
const uid = blockMap[key];
7579
if (uid) {
7680
const existingProps = getBlockProps(uid);
81+
const defaults = schema.parse({});
82+
7783
if (!existingProps || Object.keys(existingProps).length === 0) {
78-
const defaults = schema.parse({});
7984
setBlockProps(uid, defaults, false);
8085
}
86+
87+
// Reconcile placeholder relation keys with real block UIDs.
88+
// TODO: remove this when fully migrated to blockprops, as the keys won't need to match block UIDs anymore and the defaults can use any stable IDs.
89+
if (key === "Global") {
90+
const relations = ((existingProps as Record<string, json> | null)?.[
91+
"Relations"
92+
] ?? (defaults as Record<string, json>)["Relations"]) as Record<
93+
string,
94+
json
95+
>;
96+
if (relations) {
97+
reconcileRelationKeys(pageUid, uid, relations);
98+
}
99+
}
81100
}
82101
}
83102
};
@@ -89,7 +108,7 @@ const initSettingsPageBlocks = async (): Promise<Record<string, string>> => {
89108
const topLevelBlocks = getTopLevelBlockPropsConfig().map(({ key }) => key);
90109
await ensureBlocksExist(pageUid, topLevelBlocks, blockMap);
91110

92-
initializeSettingsBlockProps(blockMap);
111+
initializeSettingsBlockProps(pageUid, blockMap);
93112

94113
return blockMap;
95114
};
@@ -150,6 +169,74 @@ const initDiscourseNodePages = async (): Promise<Record<string, string>> => {
150169
return nodePageUids;
151170
};
152171

172+
/**
173+
* Replace placeholder relation keys (_INFO-rel, etc.) in the Global blockprops
174+
* with the actual block UIDs from the grammar > relations block tree.
175+
*
176+
* TODO: Remove this when fully migrated to blockprops. Once relations are read
177+
* exclusively from blockprops, the keys won't need to match block UIDs anymore
178+
* and the defaults can use any stable IDs.
179+
*/
180+
const reconcileRelationKeys = (
181+
pageUid: string,
182+
globalBlockUid: string,
183+
relations: Record<string, json>,
184+
): void => {
185+
const placeholderKeys = Object.keys(DEFAULT_RELATIONS_BLOCK_PROPS);
186+
const hasPlaceholders = placeholderKeys.some((k) => k in relations);
187+
if (!hasPlaceholders) {
188+
return;
189+
}
190+
191+
const pageChildren = getShallowTreeByParentUid(pageUid);
192+
const grammarBlock = pageChildren.find((c) =>
193+
toFlexRegex("grammar").test(c.text),
194+
);
195+
if (!grammarBlock) {
196+
return;
197+
}
198+
199+
const grammarChildren = getShallowTreeByParentUid(grammarBlock.uid);
200+
const relationsBlock = grammarChildren.find((c) =>
201+
toFlexRegex("relations").test(c.text),
202+
);
203+
if (!relationsBlock) {
204+
return;
205+
}
206+
207+
const relationBlocks = getShallowTreeByParentUid(relationsBlock.uid);
208+
209+
const labelToUid: Record<string, string> = {};
210+
for (const block of relationBlocks) {
211+
labelToUid[block.text] = block.uid;
212+
}
213+
214+
const placeholderToLabel: Record<string, string> = {};
215+
for (const [key, value] of Object.entries(DEFAULT_RELATIONS_BLOCK_PROPS)) {
216+
placeholderToLabel[key] = value.label;
217+
}
218+
219+
const reconciledRelations: Record<string, json> = {};
220+
let changed = false;
221+
222+
for (const [key, value] of Object.entries(relations)) {
223+
if (placeholderKeys.includes(key)) {
224+
const label = placeholderToLabel[key];
225+
const realUid = label ? labelToUid[label] : undefined;
226+
if (realUid) {
227+
reconciledRelations[realUid] = value;
228+
changed = true;
229+
continue;
230+
}
231+
}
232+
reconciledRelations[key] = value;
233+
}
234+
235+
if (changed) {
236+
setBlockProps(globalBlockUid, { Relations: reconciledRelations }, false);
237+
}
238+
};
239+
153240
export type InitSchemaResult = {
154241
blockUids: Record<string, string>;
155242
nodePageUids: Record<string, string>;

0 commit comments

Comments
 (0)