fix(editor): delete graph edges via right-click context menu

Remove accidental edge deletion on click and document the new graph link workflow in help and docs.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ivan Fontosh
2026-07-01 14:26:14 +08:00
parent 3acb89c80d
commit 374e041321
3 changed files with 72 additions and 12 deletions
+61 -10
View File
@@ -364,15 +364,19 @@ function SceneGraphCanvas({
const ui = graphUi ?? DEFAULT_SCENE_GRAPH_UI;
const { screenToFlowPosition } = useReactFlow();
const [menu, setMenu] = useState<{ x: number; y: number; graphNodeId: GraphNodeId } | null>(null);
const [edgeMenu, setEdgeMenu] = useState<{ x: number; y: number; edgeId: string } | null>(null);
useEffect(() => {
if (!menu) return;
if (!menu && !edgeMenu) return;
const onKey = (e: KeyboardEvent) => {
if (e.key === 'Escape') setMenu(null);
if (e.key === 'Escape') {
setMenu(null);
setEdgeMenu(null);
}
};
window.addEventListener('keydown', onKey);
return () => window.removeEventListener('keydown', onKey);
}, [menu]);
}, [edgeMenu, menu]);
const menuNodeIsStart = useMemo(() => {
if (!menu) return false;
@@ -439,6 +443,7 @@ function SceneGraphCanvas({
target: e.targetGraphNodeId,
type: 'smoothstep',
animated: false,
selectable: false,
}));
}, [currentSceneId, sceneGraphEdges, sceneGraphNodes]);
@@ -491,6 +496,16 @@ function SceneGraphCanvas({
return { x, y };
}, [menu]);
const edgeMenuPosition = useMemo(() => {
if (!edgeMenu) return null;
const pad = 8;
const mw = 200;
const mh = 48;
const x = Math.max(pad, Math.min(edgeMenu.x, window.innerWidth - mw - pad));
const y = Math.max(pad, Math.min(edgeMenu.y, window.innerHeight - mh - pad));
return { x, y };
}, [edgeMenu]);
return (
<GraphUiContext.Provider value={ui}>
<div className={styles.canvasWrap}>
@@ -505,32 +520,33 @@ function SceneGraphCanvas({
onEdgesChange={onEdgesChange}
isValidConnection={isValidConnection}
onConnect={onConnectInternal}
onEdgesDelete={(eds) => {
for (const ed of eds) {
onDisconnect(ed.id);
}
}}
onEdgeClick={(_, edge) => {
onDisconnect(edge.id);
onEdgeContextMenu={(e, edge) => {
e.preventDefault();
setMenu(null);
setEdgeMenu({ x: e.clientX, y: e.clientY, edgeId: edge.id });
}}
onNodesDelete={(nds) => {
onRemoveGraphNodes(nds.map((n) => n.id as GraphNodeId));
}}
onNodeClick={(_, node) => {
setMenu(null);
setEdgeMenu(null);
const d = node.data as SceneCardData;
onCurrentSceneChange(d.sceneId);
}}
onNodeContextMenu={(e, node) => {
e.preventDefault();
setEdgeMenu(null);
setMenu({ x: e.clientX, y: e.clientY, graphNodeId: node.id as GraphNodeId });
}}
onPaneClick={() => {
setMenu(null);
setEdgeMenu(null);
}}
onPaneContextMenu={(e) => {
e.preventDefault();
setMenu(null);
setEdgeMenu(null);
}}
onInit={(instance) => {
instance.fitView({ padding: 0.25 });
@@ -595,6 +611,41 @@ function SceneGraphCanvas({
document.body,
)
: null}
{edgeMenu && edgeMenuPosition
? createPortal(
<>
<button
type="button"
aria-label={ui.closeMenu}
className={styles.menuBackdrop}
onClick={() => setEdgeMenu(null)}
/>
<div
role="menu"
tabIndex={-1}
className={styles.ctxMenu}
style={{ left: edgeMenuPosition.x, top: edgeMenuPosition.y }}
onClick={(e) => e.stopPropagation()}
onKeyDown={(e) => {
if (e.key === 'Escape') setEdgeMenu(null);
}}
>
<button
type="button"
role="menuitem"
className={styles.ctxItemDanger}
onClick={() => {
onDisconnect(edgeMenu.edgeId);
setEdgeMenu(null);
}}
>
{ui.delete}
</button>
</div>
</>,
document.body,
)
: null}
</div>
</GraphUiContext.Provider>
);