Skip to content
112 changes: 111 additions & 1 deletion apps/obsidian/src/components/ModifyNodeModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,15 @@ type ModifyNodeFormProps = {
title: string;
initialFile?: TFile; // for edit mode
selectedExistingNode?: TFile;
relationshipTypeId?: string;
relationshipTargetFile?: TFile;
isCurrentFileSource?: boolean;
}) => Promise<void>;
onCancel: () => void;
initialTitle?: string;
initialNodeType?: DiscourseNode;
initialFile?: TFile; // for edit mode
currentFile?: TFile; // the file where the node is being created from
plugin: DiscourseGraphPlugin;
};

Expand All @@ -34,6 +38,7 @@ export const ModifyNodeForm = ({
initialTitle = "",
initialNodeType,
initialFile,
currentFile,
plugin,
}: ModifyNodeFormProps) => {
const isEditMode = !!initialFile;
Expand All @@ -48,6 +53,9 @@ export const ModifyNodeForm = ({
const [isFocused, setIsFocused] = useState(false);
const [searchResults, setSearchResults] = useState<TFile[]>([]);
const [isSearching, setIsSearching] = useState(false);
const [selectedRelationshipKey, setSelectedRelationshipKey] = useState<
string | undefined
>(undefined);
const queryEngine = useRef(new QueryEngine(plugin.app));
const titleInputRef = useRef<HTMLInputElement>(null);
const popoverRef = useRef<HTMLDivElement>(null);
Expand Down Expand Up @@ -136,6 +144,60 @@ export const ModifyNodeForm = ({
}
}, [activeIndex, isOpen]);

// Determine available relationships based on current file and selected node type
const availableRelationships = useMemo(() => {
if (!currentFile || !selectedNodeType || isEditMode) {
return [];
}

const currentFileCache = plugin.app.metadataCache.getFileCache(currentFile);
const currentNodeTypeId = currentFileCache?.frontmatter?.nodeTypeId;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unsafe assignment of an any value.eslint@typescript-eslint/no-unsafe-assignment


if (!currentNodeTypeId) {
return [];
}

// Find all relations that connect the current node type to the selected node type
const relevantRelations = plugin.settings.discourseRelations.filter(
(relation) =>
(relation.sourceId === currentNodeTypeId &&
relation.destinationId === selectedNodeType.id) ||
(relation.sourceId === selectedNodeType.id &&
relation.destinationId === currentNodeTypeId),
);

return relevantRelations
.map((relation) => {
const relationType = plugin.settings.relationTypes.find(
(rt) => rt.id === relation.relationshipTypeId,
);
if (!relationType) return null;

const isCurrentFileSource = relation.sourceId === currentNodeTypeId;
return {
relationTypeId: relation.relationshipTypeId,
label: isCurrentFileSource
? relationType.label
: relationType.complement,
isCurrentFileSource,
uniqueKey: relation.id,
};
})
.filter(Boolean) as Array<{
relationTypeId: string;
label: string;
isCurrentFileSource: boolean;
uniqueKey: string;
}>;
}, [currentFile, selectedNodeType, isEditMode, plugin]);

useEffect(() => {
if (selectedRelationshipKey === undefined && availableRelationships[0]) {
setSelectedRelationshipKey(availableRelationships[0].uniqueKey);
}
// eslint-disable-next-line react-hooks/exhaustive-deps -- only run when availableRelationships changes
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We generally want to avoid this. Are there any changes we can make so that we can include all the deps?

}, [availableRelationships]);

const isFormValid = title.trim() && selectedNodeType;

const handleSelect = useCallback((file: TFile) => {
Expand Down Expand Up @@ -194,6 +256,7 @@ export const ModifyNodeForm = ({
setQuery("");
setTitle("");
}
setSelectedRelationshipKey(undefined);
};

const handleQueryChange = (e: React.ChangeEvent<HTMLInputElement>) => {
Expand Down Expand Up @@ -223,11 +286,19 @@ export const ModifyNodeForm = ({

try {
setIsSubmitting(true);
const selectedRel = selectedRelationshipKey
? availableRelationships.find(
(r) => r.uniqueKey === selectedRelationshipKey,
)
: undefined;
await onSubmit({
nodeType: selectedNodeType,
title: trimmedTitle,
initialFile,
selectedExistingNode: selectedExistingNode || undefined,
relationshipTypeId: selectedRel?.relationTypeId,
relationshipTargetFile: currentFile || undefined,
isCurrentFileSource: selectedRel?.isCurrentFileSource,
});
onCancel();
} catch (error) {
Expand All @@ -252,11 +323,16 @@ export const ModifyNodeForm = ({
isEditMode,
initialFile,
selectedExistingNode,
selectedRelationshipKey,
currentFile,
availableRelationships,
]);

return (
<div>
<h2>{isEditMode ? "Modify discourse node" : "Create discourse node"}</h2>
<h2>
{isEditMode ? "Modify discourse node" : "Create discourse node"}
</h2>
<div className="setting-item">
<div className="setting-item-name">Type</div>
<div className="setting-item-control">
Expand Down Expand Up @@ -371,6 +447,30 @@ export const ModifyNodeForm = ({
</div>
</div>

{availableRelationships.length > 0 && !isEditMode && (
<div className="setting-item">
<div className="setting-item-name">Relationship</div>
<div className="setting-item-control">
<select
value={selectedRelationshipKey ?? ""}
onChange={(e) =>
setSelectedRelationshipKey(e.target.value || undefined)
}
disabled={isSubmitting}
className="w-full"
>
<option value="">No relation</option>
{availableRelationships.map((rel) => (
<option key={rel.uniqueKey} value={rel.uniqueKey}>
{rel.label} &quot;{currentFile?.basename}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's move {currentFile?.basename} outside of the dropdown. It is duplicate information that becomes quite noisy with multiple relations.

&quot;
</option>
))}
</select>
</div>
</div>
)}

<div className="modal-button-container mt-5 flex justify-end gap-2">
<button
type="button"
Expand Down Expand Up @@ -407,10 +507,14 @@ type ModifyNodeModalProps = {
title: string;
initialFile?: TFile;
selectedExistingNode?: TFile;
relationshipTypeId?: string;
relationshipTargetFile?: TFile;
isCurrentFileSource?: boolean;
}) => Promise<void>;
initialTitle?: string;
initialNodeType?: DiscourseNode;
initialFile?: TFile;
currentFile?: TFile;
};

class ModifyNodeModal extends Modal {
Expand All @@ -420,11 +524,15 @@ class ModifyNodeModal extends Modal {
title: string;
initialFile?: TFile;
selectedExistingNode?: TFile;
relationshipTypeId?: string;
relationshipTargetFile?: TFile;
isCurrentFileSource?: boolean;
}) => Promise<void>;
private root: Root | null = null;
private initialTitle?: string;
private initialNodeType?: DiscourseNode;
private initialFile?: TFile;
private currentFile?: TFile;
private plugin: DiscourseGraphPlugin;

constructor(app: App, props: ModifyNodeModalProps) {
Expand All @@ -434,6 +542,7 @@ class ModifyNodeModal extends Modal {
this.initialTitle = props.initialTitle;
this.initialNodeType = props.initialNodeType;
this.initialFile = props.initialFile;
this.currentFile = props.currentFile;
this.plugin = props.plugin;
}

Expand All @@ -451,6 +560,7 @@ class ModifyNodeModal extends Modal {
initialTitle={this.initialTitle}
initialNodeType={this.initialNodeType}
initialFile={this.initialFile}
currentFile={this.currentFile}
plugin={this.plugin}
/>
</StrictMode>,
Expand Down
38 changes: 13 additions & 25 deletions apps/obsidian/src/components/RelationshipSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -367,11 +367,6 @@ const CurrentRelationships = ({
const fileCache = plugin.app.metadataCache.getFileCache(activeFile);
if (!fileCache?.frontmatter) return;

const activeNodeTypeId = fileCache.frontmatter.nodeTypeId as
| string
| undefined;
if (!activeNodeTypeId) return;

const nodeInstanceId = await getNodeInstanceIdForFile(plugin, activeFile);
if (!nodeInstanceId) return;

Expand All @@ -381,21 +376,17 @@ const CurrentRelationships = ({
);
const tempRelationships = new Map<string, GroupedRelation>();

for (const relationType of plugin.settings.relationTypes) {
const typeLevelRelation = plugin.settings.discourseRelations.find(
(rel) =>
(rel.sourceId === activeNodeTypeId ||
rel.destinationId === activeNodeTypeId) &&
rel.relationshipTypeId === relationType.id,
for (const r of relations) {
const relationType = plugin.settings.relationTypes.find(
(rt) => rt.id === r.type,
);
if (!typeLevelRelation) continue;
if (!relationType) continue;

const instanceRels = relations.filter((r) => r.type === relationType.id);
const isSource = typeLevelRelation.sourceId === activeNodeTypeId;
const isSource = r.source === nodeInstanceId;
const relationLabel = isSource
? relationType.label
: relationType.complement;
const relationKey = `${relationType.id}-${isSource}`;
const relationKey = `${r.type}-${isSource ? "source" : "target"}`;

if (!tempRelationships.has(relationKey)) {
tempRelationships.set(relationKey, {
Expand All @@ -409,16 +400,13 @@ const CurrentRelationships = ({
}

const group = tempRelationships.get(relationKey)!;
for (const r of instanceRels) {
const otherId = r.source === nodeInstanceId ? r.destination : r.source;
const linkedFile = await getFileForNodeInstanceId(plugin, otherId);
if (!linkedFile) continue;
if (
linkedFile &&
!group.linkedFiles.some((f) => f.path === linkedFile.path)
) {
group.linkedFiles.push(linkedFile);
}
const otherId = isSource ? r.destination : r.source;
const linkedFile = await getFileForNodeInstanceId(plugin, otherId);
if (
linkedFile &&
!group.linkedFiles.some((f) => f.path === linkedFile.path)
) {
group.linkedFiles.push(linkedFile);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1212,7 +1212,6 @@ export class DiscourseRelationUtil extends ShapeUtil<DiscourseRelationShape> {

const { alreadyExisted, relationInstanceId } =
await addRelationToRelationsJson({
app: this.options.app,
plugin: this.options.plugin,
sourceFile,
targetFile,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,10 @@ const convertImageShapeToNode = async ({
plugin,
initialNodeType: nodeType,
initialTitle: "",
onSubmit: async ({ nodeType: selectedNodeType, title }) => {
onSubmit: async ({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this changed from prettier format on save? If so, let me know because mine changes it back to the way it was before the PR.

nodeType: selectedNodeType,
title,
}) => {
try {
const createdFile = await createDiscourseNodeFile({
plugin,
Expand Down
14 changes: 14 additions & 0 deletions apps/obsidian/src/components/canvas/utils/nodeCreationFlow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { addWikilinkBlockrefForFile } from "~/components/canvas/stores/assetStor
import { showToast } from "./toastUtils";
import { calcDiscourseNodeSize } from "~/utils/calcDiscourseNodeSize";
import { getFirstImageSrcForFile } from "~/components/canvas/shapes/discourseNodeShapeUtils";
import { addRelationIfRequested } from "./relationJsonUtils";

export type CreateNodeAtArgs = {
plugin: DiscourseGraphPlugin;
Expand All @@ -24,10 +25,14 @@ export const openCreateDiscourseNodeAt = (args: CreateNodeAtArgs): void => {
nodeTypes: plugin.settings.nodeTypes,
plugin,
initialNodeType,
currentFile: canvasFile,
onSubmit: async ({
nodeType: selectedNodeType,
title,
selectedExistingNode,
relationshipTypeId,
relationshipTargetFile,
isCurrentFileSource,
}) => {
try {
// If user selected an existing node, use it instead of creating a new one
Expand All @@ -43,6 +48,15 @@ export const openCreateDiscourseNodeAt = (args: CreateNodeAtArgs): void => {
throw new Error("Failed to get discourse node file");
}

// Add relationship to frontmatter if specified
if (relationshipTypeId && relationshipTargetFile) {
await addRelationIfRequested(plugin, fileToUse, {
relationshipTypeId,
relationshipTargetFile,
isCurrentFileSource,
});
}

const src = await addWikilinkBlockrefForFile({
app: plugin.app,
canvasFile,
Expand Down
Loading