1- import { useCallback , useRef , useState } from "react" ;
1+ import { useCallback , useEffect , useRef , useState } from "react" ;
22import { TFile } from "obsidian" ;
33import {
44 TLArrowBindingProps ,
@@ -19,6 +19,7 @@ import {
1919} from "~/components/canvas/utils/relationUtils" ;
2020import { DEFAULT_TLDRAW_COLOR } from "~/utils/tldrawColors" ;
2121import { showToast } from "~/components/canvas/utils/toastUtils" ;
22+ import { updateRelationType } from "~/utils/relationsStore" ;
2223import { RelationTypeDropdown } from "./RelationTypeDropdown" ;
2324
2425type DragHandleOverlayProps = {
@@ -73,8 +74,11 @@ const getEdgeMidpoints = (bounds: {
7374export const DragHandleOverlay = ( { plugin, file } : DragHandleOverlayProps ) => {
7475 const editor = useEditor ( ) ;
7576 const [ pendingArrowId , setPendingArrowId ] = useState < TLShapeId | null > ( null ) ;
77+ const [ editingArrowId , setEditingArrowId ] = useState < TLShapeId | null > ( null ) ;
7678 const [ isDragging , setIsDragging ] = useState ( false ) ;
7779 const sourceNodeRef = useRef < DiscourseNodeShape | null > ( null ) ;
80+ // Tracks the arrow id we just finished editing, so the useEffect doesn't re-open the dropdown
81+ const justEditedRef = useRef < TLShapeId | null > ( null ) ;
7882
7983 // Track the single selected discourse node — mirrors RelationsOverlay pattern
8084 const selectedNode = useValue < DiscourseNodeShape | null > (
@@ -89,6 +93,47 @@ export const DragHandleOverlay = ({ plugin, file }: DragHandleOverlayProps) => {
8993 [ editor ] ,
9094 ) ;
9195
96+ // Track when user selects an existing persisted relation arrow
97+ const selectedRelationArrow = useValue < DiscourseRelationShape | null > (
98+ "selectedRelationArrow" ,
99+ ( ) => {
100+ const shape = editor . getOnlySelectedShape ( ) ;
101+ if (
102+ shape ?. type === "discourse-relation" &&
103+ ( shape . meta as Record < string , unknown > ) ?. relationInstanceId
104+ ) {
105+ return shape as DiscourseRelationShape ;
106+ }
107+ return null ;
108+ } ,
109+ [ editor ] ,
110+ ) ;
111+
112+ // Clear justEditedRef when the user selects a different shape (not just transient null)
113+ const currentSelectedId = useValue (
114+ "currentSelectedId" ,
115+ ( ) => editor . getOnlySelectedShape ( ) ?. id ?? null ,
116+ [ editor ] ,
117+ ) ;
118+ useEffect ( ( ) => {
119+ if (
120+ justEditedRef . current &&
121+ currentSelectedId !== justEditedRef . current
122+ ) {
123+ justEditedRef . current = null ;
124+ }
125+ } , [ currentSelectedId ] ) ;
126+
127+ // Open edit dropdown when a persisted relation arrow is selected
128+ useEffect ( ( ) => {
129+ if ( selectedRelationArrow && ! pendingArrowId && ! isDragging ) {
130+ if ( justEditedRef . current === selectedRelationArrow . id ) return ;
131+ setEditingArrowId ( selectedRelationArrow . id ) ;
132+ } else if ( ! selectedRelationArrow ) {
133+ setEditingArrowId ( null ) ;
134+ }
135+ } , [ selectedRelationArrow , pendingArrowId , isDragging ] ) ;
136+
92137 const handlePositions = useValue <
93138 { left : number ; top : number ; anchor : { x : number ; y : number } } [ ] | null
94139 > (
@@ -374,6 +419,9 @@ export const DragHandleOverlay = ({ plugin, file }: DragHandleOverlayProps) => {
374419 }
375420 }
376421
422+ // Prevent the edit-flow useEffect from re-opening the dropdown
423+ // (reifyRelationInFrontmatter will set meta.relationInstanceId on this arrow)
424+ justEditedRef . current = pendingArrowId ;
377425 setPendingArrowId ( null ) ;
378426 sourceNodeRef . current = null ;
379427 } ,
@@ -392,7 +440,72 @@ export const DragHandleOverlay = ({ plugin, file }: DragHandleOverlayProps) => {
392440 sourceNodeRef . current = null ;
393441 } , [ editor , pendingArrowId , cleanupArrow ] ) ;
394442
395- const showHandles = ! ! handlePositions && ! pendingArrowId ;
443+ const handleEditSelect = useCallback (
444+ ( relationTypeId : string ) => {
445+ if ( ! editingArrowId ) return ;
446+
447+ const shape = editor . getShape < DiscourseRelationShape > ( editingArrowId ) ;
448+ if ( ! shape ) {
449+ setEditingArrowId ( null ) ;
450+ return ;
451+ }
452+
453+ // Same type re-selected — just dismiss
454+ if ( shape . props . relationTypeId === relationTypeId ) {
455+ setEditingArrowId ( null ) ;
456+ return ;
457+ }
458+
459+ const relationType = plugin . settings . relationTypes . find (
460+ ( rt ) => rt . id === relationTypeId ,
461+ ) ;
462+ if ( ! relationType ) {
463+ setEditingArrowId ( null ) ;
464+ return ;
465+ }
466+
467+ const relationInstanceId = (
468+ shape . meta as Record < string , unknown >
469+ ) ?. relationInstanceId ;
470+ if ( typeof relationInstanceId === "string" ) {
471+ void updateRelationType ( plugin , relationInstanceId , relationTypeId ) ;
472+ }
473+
474+ // Update arrow visual props
475+ editor . updateShapes ( [
476+ {
477+ id : editingArrowId ,
478+ type : "discourse-relation" ,
479+ props : {
480+ relationTypeId,
481+ color : relationType . color ,
482+ } ,
483+ } ,
484+ ] ) ;
485+
486+ // Update text label for direction
487+ const updatedShape =
488+ editor . getShape < DiscourseRelationShape > ( editingArrowId ) ;
489+ if ( updatedShape ) {
490+ const bindings = getArrowBindings ( editor , updatedShape ) ;
491+ const util = editor . getShapeUtil ( updatedShape ) ;
492+ if ( util instanceof DiscourseRelationUtil ) {
493+ util . updateRelationTextForDirection ( updatedShape , bindings ) ;
494+ }
495+ }
496+
497+ justEditedRef . current = editingArrowId ;
498+ setEditingArrowId ( null ) ;
499+ } ,
500+ [ editor , editingArrowId , plugin ] ,
501+ ) ;
502+
503+ const handleEditDismiss = useCallback ( ( ) => {
504+ justEditedRef . current = editingArrowId ;
505+ setEditingArrowId ( null ) ;
506+ } , [ editingArrowId ] ) ;
507+
508+ const showHandles = ! ! handlePositions && ! pendingArrowId && ! editingArrowId ;
396509
397510 return (
398511 < div style = { { position : "absolute" , inset : 0 , pointerEvents : "none" } } >
@@ -428,7 +541,7 @@ export const DragHandleOverlay = ({ plugin, file }: DragHandleOverlayProps) => {
428541 </ div >
429542 ) ) }
430543
431- { /* Relation type dropdown */ }
544+ { /* Relation type dropdown — new arrow */ }
432545 { pendingArrowId && (
433546 < RelationTypeDropdown
434547 arrowId = { pendingArrowId }
@@ -437,6 +550,16 @@ export const DragHandleOverlay = ({ plugin, file }: DragHandleOverlayProps) => {
437550 onDismiss = { handleDropdownDismiss }
438551 />
439552 ) }
553+
554+ { /* Relation type dropdown — edit existing arrow */ }
555+ { editingArrowId && ! pendingArrowId && (
556+ < RelationTypeDropdown
557+ arrowId = { editingArrowId }
558+ plugin = { plugin }
559+ onSelect = { handleEditSelect }
560+ onDismiss = { handleEditDismiss }
561+ />
562+ ) }
440563 </ div >
441564 ) ;
442565} ;
0 commit comments