feature: add Create Node action for orphaned node creation
Right-clicking on empty graph area (outside any node or edge) now shows a context menu with a single 'Create Node' option. Clicking it adds a new node with a random label and no edges, allowing the user to wire it up manually afterwards. - NodeContext gains isEmptyArea flag to distinguish empty-area from node right-clicks - Container div contextmenu handler covers the white space below the SVG; SVG-level handler covers empty space within the rendered graph - node contextmenu handler calls stopPropagation so the SVG handler never fires for node clicks; SVG handler also guards via target.closest check - NodeContextMenu renders emptyAreaItems (Create Node only) vs nodeItems (Rename / Subgraph / Remove) based on the flag - randomWordList extracted as a named export so NodeContextMenu can reuse the same word bank - Three new Playwright e2e tests cover: empty-area menu shows only Create Node, created node is orphaned (no new edges), node right-click does not show Create Node
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
import { Dropdown, type MenuProps } from "antd";
|
||||
import { defaultGraph, graphContext, GraphModel, type EdgeModel, type NodeContext } from "./Graph";
|
||||
import { defaultGraph, graphContext, GraphModel, randomWordList, type EdgeModel, type NodeContext } from "./Graph";
|
||||
import { useContext } from "react";
|
||||
import { cloneDeep } from "lodash";
|
||||
import { useGraphsStore } from "../stores/GraphsStore";
|
||||
import { useGraphLayersTreeStore } from "../stores/TreeStore";
|
||||
|
||||
const items: MenuProps['items'] = [
|
||||
const nodeItems: MenuProps['items'] = [
|
||||
{
|
||||
key: 'rename',
|
||||
label: 'Rename',
|
||||
@@ -23,6 +23,13 @@ const items: MenuProps['items'] = [
|
||||
}
|
||||
]
|
||||
|
||||
const emptyAreaItems: MenuProps['items'] = [
|
||||
{
|
||||
key: 'create',
|
||||
label: 'Create Node',
|
||||
}
|
||||
]
|
||||
|
||||
export default function NodeContextMenu({
|
||||
nodeContext,
|
||||
contextMenuOpened,
|
||||
@@ -38,6 +45,7 @@ export default function NodeContextMenu({
|
||||
return;
|
||||
}
|
||||
|
||||
const items = nodeContext.isEmptyArea ? emptyAreaItems : nodeItems;
|
||||
const graphContextValue = useContext(graphContext)!;
|
||||
const graphsById = useGraphsStore((s) => (s as { graphsById: Map<string, GraphModel> }).graphsById);
|
||||
const addTreeNode = useGraphLayersTreeStore(store => store.add);
|
||||
@@ -60,6 +68,11 @@ export default function NodeContextMenu({
|
||||
removeTreeNode(nodeContext.nodeId);
|
||||
break;
|
||||
}
|
||||
case 'create': {
|
||||
const id = crypto.randomUUID();
|
||||
graphContextValue.setGraph(prev => ({ ...prev, nodes: [...prev.nodes, { id, label: randomWordList(1)[0] }] }));
|
||||
break;
|
||||
}
|
||||
case 'subgraph': {
|
||||
graphsById.set(graphContextValue.graphId, cloneDeep(graphContextValue.graph));
|
||||
graphContextValue.selectGraphId(nodeContext.nodeId);
|
||||
|
||||
Reference in New Issue
Block a user