Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
234 changes: 137 additions & 97 deletions apps/roam/src/components/settings/DiscourseRelationConfigPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import { formatHexColor } from "./DiscourseNodeCanvasSettings";
import posthog from "posthog-js";
import { getSetting, setSetting } from "~/utils/extensionSettings";
import { USE_REIFIED_RELATIONS } from "~/data/userSettings";
import { setGlobalSetting } from "~/components/settings/utils/accessors";

const DEFAULT_SELECTED_RELATION = {
display: "none",
Expand Down Expand Up @@ -568,108 +569,147 @@ export const RelationEditPanel = ({
className="select-none"
onClick={() => {
setLoading(true);
setTimeout(async () => {
const rootUid = editingRelationInfo.uid;
setInputSetting({
blockUid: rootUid,
key: "source",
value: source,
});
setInputSetting({
blockUid: rootUid,
key: "destination",
value: destination,
index: 1,
});
setInputSetting({
blockUid: rootUid,
key: "complement",
value: complement,
index: 2,
});
updateBlock({
uid: rootUid,
text: label,
});
const ifUid =
editingRelationInfo.children.find((t) =>
toFlexRegex("if").test(t.text),
)?.uid ||
(await createBlock({
node: { text: "If" },
parentUid: rootUid,
order: 3,
}));
saveCyToElementRef(tab);
const blocks = tabs
.map((t) => elementsRef.current[t])
.map((elements) => ({
text: "And",
children: elements
.filter((e) => e.data.id.includes("-"))
.map((e) => {
const { source, target, relation } = e.data as {
source: string;
target: string;
relation: string;
};
return {
text: (
elements.find((e) => e.data.id === source)?.data as {
node: string;
}
)?.node,
children: [
{
text: relation,
setTimeout(
() =>
void (async () => {
const rootUid = editingRelationInfo.uid;
setInputSetting({
blockUid: rootUid,
key: "source",
value: source,
});
setInputSetting({
blockUid: rootUid,
key: "destination",
value: destination,
index: 1,
});
setInputSetting({
blockUid: rootUid,
key: "complement",
value: complement,
index: 2,
});
updateBlock({
uid: rootUid,
text: label,
});
const ifUid =
editingRelationInfo.children.find((t) =>
toFlexRegex("if").test(t.text),
)?.uid ||
(await createBlock({
node: { text: "If" },
parentUid: rootUid,
order: 3,
}));
saveCyToElementRef(tab);
const blocks = tabs
.map((t) => elementsRef.current[t])
.map((elements) => ({
text: "And",
children: elements
.filter((e) => e.data.id.includes("-"))
.map((e) => {
const { source, target, relation } = e.data as {
source: string;
target: string;
relation: string;
};
return {
text: (
elements.find((e) => e.data.id === source)
?.data as {
node: string;
}
)?.node,
children: [
{
text: ["source", "destination"].includes(target)
? target
: (
elements.find((e) => e.data.id === target)
?.data as { node: string }
)?.node,
text: relation,
children: [
{
text: ["source", "destination"].includes(
target,
)
? target
: (
elements.find(
(e) => e.data.id === target,
)?.data as { node: string }
)?.node,
},
],
},
],
};
})
.concat([
{
text: "node positions",
children: elements
.filter(
(
e,
): e is {
data: { id: string; node: unknown };
position: { x: number; y: number };
} => Object.keys(e).includes("position"),
)
.map((e) => ({
text: e.data.id,
children: [
{ text: `${e.position.x} ${e.position.y}` },
],
})),
},
],
};
})
.concat([
{
text: "node positions",
children: elements
.filter(
(
e,
): e is {
data: { id: string; node: unknown };
position: { x: number; y: number };
} => Object.keys(e).includes("position"),
)
.map((e) => ({
text: e.data.id,
children: [
{ text: `${e.position.x} ${e.position.y}` },
],
})),
},
]),
}));
await Promise.all(
getShallowTreeByParentUid(ifUid).map(({ uid }) =>
deleteBlock(uid),
),
);
await Promise.all(
blocks.map((block, order) =>
createBlock({ parentUid: ifUid, node: block, order }),
),
);
refreshConfigTree();
back();
}, 1);
]),
}));
await Promise.all(
getShallowTreeByParentUid(ifUid).map(({ uid }) =>
deleteBlock(uid),
),
);
await Promise.all(
blocks.map((block, order) =>
createBlock({ parentUid: ifUid, node: block, order }),
),
);
refreshConfigTree();

const ifConditions = blocks.map((block) => {
const positionsChild = block.children.find(
(c) => c.text === "node positions",
);
const triples = block.children
.filter((c) => c.text !== "node positions")
.map(
(c) =>
[
c.text,
c.children[0].text,
c.children[0].children[0].text,
] as [string, string, string],
);
const nodePositions = Object.fromEntries(
(positionsChild?.children ?? []).map((c) => [
c.text,
c.children[0].text,
]),
);
return { triples, nodePositions };
});
setGlobalSetting(["Relations", rootUid], {
label,
source,
destination,
complement,
ifConditions,
});

back();
})(),
1,
);
}}
/>
</div>
Expand Down
91 changes: 89 additions & 2 deletions apps/roam/src/components/settings/utils/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import getShallowTreeByParentUid from "roamjs-components/queries/getShallowTreeB
import { createPage, createBlock } from "roamjs-components/writes";
import setBlockProps from "~/utils/setBlockProps";
import getBlockProps from "~/utils/getBlockProps";
import type { json } from "~/utils/getBlockProps";
import INITIAL_NODE_VALUES from "~/data/defaultDiscourseNodes";
import DEFAULT_RELATIONS_BLOCK_PROPS from "~/components/settings/data/defaultRelationsBlockProps";
import { getAllDiscourseNodes } from "./accessors";
import {
DiscourseNodeSchema,
Expand All @@ -13,6 +15,7 @@ import {
DG_BLOCK_PROP_SETTINGS_PAGE_TITLE,
DISCOURSE_NODE_PAGE_PREFIX,
} from "./zodSchema";
import toFlexRegex from "roamjs-components/util/toFlexRegex";

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

const initializeSettingsBlockProps = (
pageUid: string,
blockMap: Record<string, string>,
): void => {
const configs = getTopLevelBlockPropsConfig();
Expand All @@ -74,10 +78,25 @@ const initializeSettingsBlockProps = (
const uid = blockMap[key];
if (uid) {
const existingProps = getBlockProps(uid);
const defaults = schema.parse({});

if (!existingProps || Object.keys(existingProps).length === 0) {
const defaults = schema.parse({});
setBlockProps(uid, defaults, false);
}

// Reconcile placeholder relation keys with real block UIDs.
// 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.
if (key === "Global") {
const relations = ((existingProps as Record<string, json> | null)?.[
"Relations"
] ?? (defaults as Record<string, json>)["Relations"]) as Record<
string,
json
>;
if (relations) {
reconcileRelationKeys(pageUid, uid, relations);
}
}
}
}
};
Expand All @@ -89,7 +108,7 @@ const initSettingsPageBlocks = async (): Promise<Record<string, string>> => {
const topLevelBlocks = getTopLevelBlockPropsConfig().map(({ key }) => key);
await ensureBlocksExist(pageUid, topLevelBlocks, blockMap);

initializeSettingsBlockProps(blockMap);
initializeSettingsBlockProps(pageUid, blockMap);

return blockMap;
};
Expand Down Expand Up @@ -150,6 +169,74 @@ const initDiscourseNodePages = async (): Promise<Record<string, string>> => {
return nodePageUids;
};

/**
* Replace placeholder relation keys (_INFO-rel, etc.) in the Global blockprops
* with the actual block UIDs from the grammar > relations block tree.
*
* TODO: Remove this when fully migrated to blockprops. Once relations are read
* exclusively from blockprops, the keys won't need to match block UIDs anymore
* and the defaults can use any stable IDs.
*/
const reconcileRelationKeys = (
pageUid: string,
globalBlockUid: string,
relations: Record<string, json>,
): void => {
const placeholderKeys = Object.keys(DEFAULT_RELATIONS_BLOCK_PROPS);
const hasPlaceholders = placeholderKeys.some((k) => k in relations);
if (!hasPlaceholders) {
return;
}

const pageChildren = getShallowTreeByParentUid(pageUid);
const grammarBlock = pageChildren.find((c) =>
toFlexRegex("grammar").test(c.text),
);
if (!grammarBlock) {
return;
}

const grammarChildren = getShallowTreeByParentUid(grammarBlock.uid);
const relationsBlock = grammarChildren.find((c) =>
toFlexRegex("relations").test(c.text),
);
if (!relationsBlock) {
return;
}

const relationBlocks = getShallowTreeByParentUid(relationsBlock.uid);

const labelToUid: Record<string, string> = {};
for (const block of relationBlocks) {
labelToUid[block.text] = block.uid;
}

const placeholderToLabel: Record<string, string> = {};
for (const [key, value] of Object.entries(DEFAULT_RELATIONS_BLOCK_PROPS)) {
placeholderToLabel[key] = value.label;
}

const reconciledRelations: Record<string, json> = {};
let changed = false;

for (const [key, value] of Object.entries(relations)) {
if (placeholderKeys.includes(key)) {
const label = placeholderToLabel[key];
const realUid = label ? labelToUid[label] : undefined;
if (realUid) {
reconciledRelations[realUid] = value;
changed = true;
continue;
}
}
reconciledRelations[key] = value;
}

if (changed) {
setBlockProps(globalBlockUid, { Relations: reconciledRelations }, false);
}
};

export type InitSchemaResult = {
blockUids: Record<string, string>;
nodePageUids: Record<string, string>;
Expand Down