Nodes selection store, linking and unlinking nodes

This commit is contained in:
2025-11-11 13:01:32 +01:00
parent cd4963a4bd
commit 4092b12aef
6 changed files with 126 additions and 17 deletions

View File

@@ -6,7 +6,8 @@ import { graphToDot } from "../Graphviz";
import type { BreadcrumbItemType } from "antd/es/breadcrumb/Breadcrumb";
import NodeContextMenu from "./NodeContextMenu";
import NodeRenameModal from "./NodeRenameModal";
import { useGraphsStore } from "./GraphsCollection";
import { useGraphsStore } from "../stores/GraphsStore";
import { useKeysdownStore, useSelectedNodesStore } from "../stores/ArrayStore";
export class GraphModel {
@@ -17,10 +18,12 @@ export class GraphModel {
export class EdgeModel {
from: string;
to: string;
id: string;
constructor(from: string, to: string) {
constructor(from: string, to: string, id: string) {
this.from = from;
this.to = to;
this.id = id;
}
}
@@ -63,7 +66,7 @@ export default function Graph({ setGraphPath }: { setGraphPath: React.Dispatch<R
const containerRef = useRef(null);
const [graph, setGraph] = useState(defaultGraph());
const [contextMenuOpened, openContextMenu] = useState(false);
const [renameModalOpened, openRenameModal] = useState(false);
const [renameModalOpened, openRenameModal] = useState(false);
const [graphId, selectGraphId] = useState('main');
const [graphsPath, setGraphsPath] = useState([createPathSegment('main', 'Main')])
const [nodeContext, openNodeContext] = useState<null | NodeContext>(null);
@@ -73,6 +76,12 @@ export default function Graph({ setGraphPath }: { setGraphPath: React.Dispatch<R
graph: graph,
setGraph: setGraph
};
const isKeyPressed = useKeysdownStore(store => store.has);
const selectNode = useSelectedNodesStore(store => store.add);
const deselectNode = useSelectedNodesStore(store => store.remove);
const isNodeSelected = useSelectedNodesStore(store => store.has);
const anyNodeSelected = useSelectedNodesStore(store => store.hasAny);
const deselectAllNodes = useSelectedNodesStore(store => store.clear);
useEffect(() => {
setGraphPath(graphsPath);
@@ -119,12 +128,39 @@ export default function Graph({ setGraphPath }: { setGraphPath: React.Dispatch<R
function attachInteractions(svgElement: SVGSVGElement) {
const svg = d3.select(svgElement);
svg.selectAll('g.edge')
.style('cursor', 'not-allowed')
.on('click', function () {
const d3Node = d3.select(this);
const id = d3Node.attr('id');
setGraph(prev => ({ ...prev, edges: [...prev.edges.filter(e => e.id !== id)] }));
})
svg.selectAll('g.node')
.style('cursor', 'pointer')
.on('click', function (event) {
const id = d3.select(this).attr('id');
createChildNode(id);
renderGraph();
.on('click', function () {
const d3Node = d3.select(this);
const d3Rect = d3Node.select('g.node polygon');
const id = d3Node.attr('id');
if (isKeyPressed('Control')) {
if (isNodeSelected(id)) {
deselectNode(id);
d3Rect
.attr("stroke", "#000000");
} else {
selectNode(id);
d3Rect
.attr("stroke", "#1677ff")
}
} else {
if (anyNodeSelected()) {
linkSelectedNodesAsParents(id);
deselectAllNodes();
d3.selectAll('g.node polygon')
.attr("stroke", "#000000");
} else {
createChildNode(id);
}
}
})
.on('contextmenu', function (event) {
const id = d3.select(this).attr('id');
@@ -143,9 +179,27 @@ export default function Graph({ setGraphPath }: { setGraphPath: React.Dispatch<R
});
}
function linkSelectedNodesAsParents(childNodeId: string) {
const selectedNodesIds = useSelectedNodesStore.getState().items;
setGraph(prev => ({ ...prev, edges: [...prev.edges, ...selectedNodesIds.map(parentId => ({ from: parentId, to: childNodeId, id: crypto.randomUUID() }))] }))
}
function createChildNode(parentId: string) {
const id = crypto.randomUUID();
setGraph(prev => ({ ...prev, nodes: [...prev.nodes, { id, label: 'New node' }], edges: [...prev.edges, { from: parentId, to: id }] }));
setGraph(prev => ({ ...prev, nodes: [...prev.nodes, { id, label: getRandomWords(1)[0] }], edges: [...prev.edges, { from: parentId, to: id, id: crypto.randomUUID() }] }));
}
function getRandomWords(count: number) {
const wordList = [
"apple", "river", "mountain", "sky", "storm", "ocean", "forest", "dream",
"stone", "flame", "shadow", "cloud", "leaf", "wind", "fire", "earth",
"flower", "bird", "light", "night", "sun", "rain", "snow", "tree",
"wolf", "star", "sand", "wave", "heart", "path"
];
return wordList
.sort(() => Math.random() - 0.5)
.slice(0, count);
}
function createPathSegment(
@@ -199,11 +253,7 @@ export function defaultGraph(): GraphModel {
{ id: end, label: 'End' }
],
edges: [
{ from: start, to: end },
{ from: start, to: end, id: crypto.randomUUID() },
]
};
}