Skip to content
Draft
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
83 changes: 77 additions & 6 deletions apps/obsidian/src/components/RelationshipSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ import SearchBar from "./SearchBar";
import { DiscourseNode } from "~/types";
import DropdownSelect from "./DropdownSelect";
import { usePlugin } from "./PluginContext";
import { getNodeTypeById } from "~/utils/typeUtils";
import {
findRelationTripletId,
getNodeTypeById,
getRelationById,
} from "~/utils/typeUtils";
import {
getNodeInstanceIdForFile,
getRelationsForNodeInstanceId,
Expand Down Expand Up @@ -203,7 +207,7 @@ const AddRelationship = ({
};

const addRelationship = useCallback(async () => {
if (!selectedRelationType || !selectedNode) return;
if (!selectedRelationType || !selectedNode || !activeNodeTypeId) return;

const relationType = plugin.settings.relationTypes.find(
(r) => r.id === selectedRelationType,
Expand All @@ -220,8 +224,35 @@ const AddRelationship = ({
return;
}

// Get the node type id of the selected node
const selectedNodeTypeId = plugin.app.metadataCache.getFileCache(
selectedNode,
)?.frontmatter?.nodeTypeId as string | undefined;

if (!selectedNodeTypeId) {
new Notice(
"Could not determine node type for the selected file.",
);
return;
}

// Find the relation triplet id
const relationTripletId = findRelationTripletId(
plugin,
activeNodeTypeId,
selectedNodeTypeId,
selectedRelationType,
);

if (!relationTripletId) {
new Notice(
"This relation type is not allowed between these node types.",
);
return;
}

const { alreadyExisted } = await addRelation(plugin, {
type: selectedRelationType,
type: relationTripletId,
source: sourceId,
destination: destId,
});
Expand Down Expand Up @@ -378,8 +409,13 @@ const CurrentRelationships = ({
const tempRelationships = new Map<string, GroupedRelation>();

for (const r of relations) {
// r.type is now the relation triplet id, get the relation triplet first
const relationTriplet = getRelationById(plugin, r.type);
if (!relationTriplet) continue;

// Get the relation type from the triplet
const relationType = plugin.settings.relationTypes.find(
(rt) => rt.id === r.type,
(rt) => rt.id === relationTriplet.relationshipTypeId,
);
if (!relationType) continue;

Expand Down Expand Up @@ -433,17 +469,52 @@ const CurrentRelationships = ({
return;
}

// Get node type ids to find the relation triplet
const activeNodeTypeId = plugin.app.metadataCache.getFileCache(
activeFile,
)?.frontmatter?.nodeTypeId as string | undefined;
const linkedNodeTypeId = plugin.app.metadataCache.getFileCache(
linkedFile,
)?.frontmatter?.nodeTypeId as string | undefined;

if (!activeNodeTypeId || !linkedNodeTypeId) {
new Notice("Could not determine node types for the files.");
return;
}

// Try both directions to find the relation triplet id
let relationTripletId = findRelationTripletId(
plugin,
activeNodeTypeId,
linkedNodeTypeId,
relationTypeId,
);

if (!relationTripletId) {
relationTripletId = findRelationTripletId(
plugin,
linkedNodeTypeId,
activeNodeTypeId,
relationTypeId,
);
}

if (!relationTripletId) {
new Notice("Could not find relation triplet to delete.");
return;
}

await removeRelationBySourceDestinationType(
plugin,
activeId,
linkedId,
relationTypeId,
relationTripletId,
);
await removeRelationBySourceDestinationType(
plugin,
linkedId,
activeId,
relationTypeId,
relationTripletId,
);

new Notice(
Expand Down
46 changes: 43 additions & 3 deletions apps/obsidian/src/components/canvas/overlays/RelationPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,16 @@ import {
getArrowBindings,
} from "~/components/canvas/utils/relationUtils";
import { getFrontmatterForFile } from "~/components/canvas/shapes/discourseNodeShapeUtils";
import { getRelationTypeById } from "~/utils/typeUtils";
import {
findRelationTripletId,
getRelationById,
getRelationTypeById,
} from "~/utils/typeUtils";
import { showToast } from "~/components/canvas/utils/toastUtils";
import { DEFAULT_TLDRAW_COLOR } from "~/utils/tldrawColors";
import {
getNodeInstanceIdForFile,
getNodeTypeIdForFile,
getRelationsForNodeInstanceId,
getFileForNodeInstanceId,
addRelation,
Expand Down Expand Up @@ -377,6 +382,37 @@ export const RelationsPanel = ({
return;
}

// Get node type ids to find the relation triplet
const sourceNodeTypeId = await getNodeTypeIdForFile(plugin, sourceFile);
const destNodeTypeId = await getNodeTypeIdForFile(plugin, destFile);
if (!sourceNodeTypeId || !destNodeTypeId) {
showToast({
severity: "error",
title: "Could Not Resolve Node Types",
description: "Could not determine node types for the files.",
targetCanvasId: canvasFile.path,
});
return;
}

// Find the relation triplet id
const relationTripletId = findRelationTripletId(
plugin,
sourceNodeTypeId,
destNodeTypeId,
relationTypeId,
);
if (!relationTripletId) {
showToast({
severity: "error",
title: "Invalid Relation",
description:
"This relation type is not allowed between these node types.",
targetCanvasId: canvasFile.path,
});
return;
}

const targetNode = await ensureNodeShapeForFile(targetFile);

const id: TLShapeId = createShapeId();
Expand Down Expand Up @@ -448,7 +484,7 @@ export const RelationsPanel = ({
});

const { id: relationInstanceId } = await addRelation(plugin, {
type: relationTypeId,
type: relationTripletId,
source: sourceId,
destination: destId,
});
Expand Down Expand Up @@ -555,7 +591,11 @@ const computeRelations = async (
);
if (!typeLevelRelation) continue;

const instanceRels = relations.filter((r) => r.type === relationType.id);
// r.type is now the relation triplet id, need to filter by matching triplet id
const instanceRels = relations.filter((r) => {
const relationTriplet = getRelationById(plugin, r.type);
return relationTriplet?.relationshipTypeId === relationType.id;
});
const isSource = typeLevelRelation.sourceId === activeNodeTypeId;
const label = isSource ? relationType.label : relationType.complement;
const key = `${relationType.id}-${isSource}`;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ import {
import { RelationBindings } from "./DiscourseRelationBinding";
import { DiscourseNodeShape, DiscourseNodeUtil } from "./DiscourseNodeShape";
import { addRelationToRelationsJson } from "~/components/canvas/utils/relationJsonUtils";
import { getNodeTypeIdForFile } from "~/utils/relationsStore";
import { findRelationTripletId } from "~/utils/typeUtils";
import { showToast } from "~/components/canvas/utils/toastUtils";

export enum ArrowHandles {
Expand Down Expand Up @@ -1210,12 +1212,62 @@ export class DiscourseRelationUtil extends ShapeUtil<DiscourseRelationShape> {
return;
}

// Get node type ids to find the relation triplet
const sourceNodeTypeId = await getNodeTypeIdForFile(
this.options.plugin,
sourceFile,
);
const targetNodeTypeId = await getNodeTypeIdForFile(
this.options.plugin,
targetFile,
);

if (!sourceNodeTypeId || !targetNodeTypeId) {
console.warn(
"Could not resolve node type ids for relation files",
sourceFile.basename,
targetFile.basename,
);
showToast({
severity: "error",
title: "Failed to Save Relation",
description: "Could not determine node types for the connected files",
targetCanvasId: this.options.canvasFile.path,
});
return;
}

// Find the relation triplet id
const relationTripletId = findRelationTripletId(
this.options.plugin,
sourceNodeTypeId,
targetNodeTypeId,
shape.props.relationTypeId,
);

if (!relationTripletId) {
console.warn(
"Could not find relation triplet for",
sourceNodeTypeId,
targetNodeTypeId,
shape.props.relationTypeId,
);
showToast({
severity: "error",
title: "Failed to Save Relation",
description:
"This relation type is not allowed between these node types",
targetCanvasId: this.options.canvasFile.path,
});
return;
}

const { alreadyExisted, relationInstanceId } =
await addRelationToRelationsJson({
plugin: this.options.plugin,
sourceFile,
targetFile,
relationTypeId: shape.props.relationTypeId,
relationTripletId,
});

if (relationInstanceId) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,19 @@ import {
* Persists a relation between two files to the relations store (relations.json).
* Uses addRelation (checks for existing relation by default).
*
* @param relationTripletId - The relation triplet id (DiscourseRelation.id, typically has prefix rel3_)
* @returns Object indicating whether the relation already existed and the relation instance id.
*/
export const addRelationToRelationsJson = async ({
plugin,
sourceFile,
targetFile,
relationTypeId,
relationTripletId,
}: {
plugin: DiscourseGraphPlugin;
sourceFile: TFile;
targetFile: TFile;
relationTypeId: string;
relationTripletId: string;
}): Promise<{ alreadyExisted: boolean; relationInstanceId?: string }> => {
const sourceId = await getNodeInstanceIdForFile(plugin, sourceFile);
const destId = await getNodeInstanceIdForFile(plugin, targetFile);
Expand All @@ -42,7 +43,7 @@ export const addRelationToRelationsJson = async ({
}

const { id, alreadyExisted } = await addRelation(plugin, {
type: relationTypeId,
type: relationTripletId,
source: sourceId,
destination: destId,
});
Expand Down Expand Up @@ -102,6 +103,6 @@ export const addRelationIfRequested = async (
plugin,
sourceFile,
targetFile,
relationTypeId: relation.relationshipTypeId,
relationTripletId: relation.id,
});
};
23 changes: 20 additions & 3 deletions apps/obsidian/src/utils/relationsStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type DiscourseGraphPlugin from "~/index";
import { ensureNodeInstanceId } from "~/utils/nodeInstanceId";
import { checkAndCreateFolder } from "~/utils/file";
import { getVaultId } from "./supabaseContext";
import { findRelationTripletId } from "./typeUtils";

const RELATIONS_FILE_NAME = "relations.json";
const RELATIONS_FILE_VERSION = 1;
Expand Down Expand Up @@ -401,6 +402,7 @@ export const migrateFrontmatterRelationsToRelationsJson = async (
file,
frontmatter as Record<string, unknown>,
);
const sourceNodeTypeId = frontmatter.nodeTypeId as string;

for (const relationType of plugin.settings.relationTypes) {
const raw = frontmatter[relationType.id] as unknown;
Expand All @@ -426,20 +428,35 @@ export const migrateFrontmatterRelationsToRelationsJson = async (
targetFile,
targetFrontmatter as Record<string, unknown>,
);
const destNodeTypeId = targetFrontmatter.nodeTypeId as string;

// Find the relation triplet id
const relationTripletId = findRelationTripletId(
plugin,
sourceNodeTypeId,
destNodeTypeId,
relationType.id,
);
if (!relationTripletId) {
console.warn(
`Could not find relation triplet for migration: ${sourceNodeTypeId} -> ${destNodeTypeId} (${relationType.id})`,
);
continue;
}

const alreadyExists = findRelationBySourceDestinationType(
data,
sourceNodeInstanceId,
destNodeInstanceId,
relationType.id,
relationTripletId,
);
if (alreadyExists) continue;

const reverseExists = findRelationBySourceDestinationType(
data,
destNodeInstanceId,
sourceNodeInstanceId,
relationType.id,
relationTripletId,
);
if (reverseExists) continue;

Expand All @@ -448,7 +465,7 @@ export const migrateFrontmatterRelationsToRelationsJson = async (
const author = plugin.settings.accountLocalId ?? getVaultId(plugin.app);
data.relations[id] = {
id,
type: relationType.id,
type: relationTripletId,
source: sourceNodeInstanceId,
destination: destNodeInstanceId,
created: now,
Expand Down
Loading