From edc7669b18707fb6cd33c2ebcb62fe2e74debf69 Mon Sep 17 00:00:00 2001 From: braks <78412429+bcakmakoglu@users.noreply.github.com> Date: Thu, 5 Oct 2023 22:19:27 +0200 Subject: [PATCH] feat(core): add `getIncomers`, `getOutgoers` & `getConnectedEdges` as store actions --- packages/core/src/store/actions.ts | 23 +++++++- packages/core/src/store/state.ts | 2 + packages/core/src/types/store.ts | 6 +++ packages/core/src/utils/graph.ts | 84 +++++++++++++++++++++++------- 4 files changed, 95 insertions(+), 20 deletions(-) diff --git a/packages/core/src/store/actions.ts b/packages/core/src/store/actions.ts index a6928353..a4acc7c8 100644 --- a/packages/core/src/store/actions.ts +++ b/packages/core/src/store/actions.ts @@ -34,9 +34,11 @@ import { createGraphNodes, createNodeRemoveChange, createSelectionChange, - getConnectedEdges, + getConnectedEdges as getConnectedEdgesBase, getDimensions, getHandleBounds, + getIncomers as getIncomersBase, + getOutgoers as getOutgoersBase, getOverlappingArea, getSelectionChanges, isDef, @@ -54,7 +56,9 @@ export function useActions( hooksOn: any, state: State, getters: ComputedGetters, + // todo: change to a Set nodeIds: ComputedRef, + // todo: change to a Set edgeIds: ComputedRef, ): Actions { const viewportHelper = useViewport(state, getters) @@ -65,6 +69,18 @@ export function useActions( state.hooks.updateNodeInternals.trigger(updateIds) } + const getIncomers: Actions['getIncomers'] = (nodeOrId) => { + return getIncomersBase(nodeOrId, state.nodes, state.edges) + } + + const getOutgoers: Actions['getOutgoers'] = (nodeOrId) => { + return getOutgoersBase(nodeOrId, state.nodes, state.edges) + } + + const getConnectedEdges: Actions['getConnectedEdges'] = (nodesOrId) => { + return getConnectedEdgesBase(nodesOrId, state.edges) + } + const findNode: Actions['findNode'] = (id) => { if (!id) { return @@ -519,7 +535,7 @@ export function useActions( const edgeChanges: EdgeRemoveChange[] = [] function createEdgeRemovalChanges(nodes: Node[]) { - const connections = getConnectedEdges(nodes, state.edges).filter((edge) => (isDef(edge.deletable) ? edge.deletable : true)) + const connections = getConnectedEdges(nodes).filter((edge) => (isDef(edge.deletable) ? edge.deletable : true)) edgeChanges.push( ...connections.map((connection) => createEdgeRemoveChange(connection.id, connection.source, connection.target)), @@ -920,6 +936,9 @@ export function useActions( setInteractive, setState, getIntersectingNodes, + getIncomers, + getOutgoers, + getConnectedEdges, isNodeIntersecting, panBy, fitView: (params) => viewportHelper.value.fitView(params), diff --git a/packages/core/src/store/state.ts b/packages/core/src/store/state.ts index 8db87c5a..86077c29 100644 --- a/packages/core/src/store/state.ts +++ b/packages/core/src/store/state.ts @@ -31,7 +31,9 @@ function defaultState(): State { return { vueFlowRef: null, viewportRef: null, + // todo: change this to a Set nodes: [], + // todo: change this to a Set edges: [], nodeTypes: {}, edgeTypes: {}, diff --git a/packages/core/src/types/store.ts b/packages/core/src/types/store.ts index 5be8f548..84cfc91e 100644 --- a/packages/core/src/types/store.ts +++ b/packages/core/src/types/store.ts @@ -275,6 +275,12 @@ export interface Actions extends ViewportFunctions { getIntersectingNodes: GetIntersectingNodes /** check if a node is intersecting with a defined area */ isNodeIntersecting: IsNodeIntersecting + /** get a node's incomers */ + getIncomers: (nodeOrId: Node | string) => GraphNode[] + /** get a node's outgoers */ + getOutgoers: (nodeOrId: Node | string) => GraphNode[] + /** get a node's connected edges */ + getConnectedEdges: (nodesOrId: Node[] | string) => GraphEdge[] /** pan the viewport; return indicates if a transform has happened or not */ panBy: (delta: XYPosition) => boolean diff --git a/packages/core/src/utils/graph.ts b/packages/core/src/utils/graph.ts index 0f7f3f18..4f83aeb0 100644 --- a/packages/core/src/utils/graph.ts +++ b/packages/core/src/utils/graph.ts @@ -9,6 +9,7 @@ import type { Dimensions, Edge, EdgeMarkerType, + Element, ElementData, Elements, FlowElements, @@ -151,24 +152,63 @@ export function parseEdge(edge: Edge, defaults: Partial = {}): GraphE return Object.assign({}, defaults, edge, { id: edge.id.toString() }) as GraphEdge } -function getConnectedElements( +function getConnectedElements( nodeOrId: Node | { id: string } | string, - elements: T, + nodes: T[], + edges: Edge[], dir: 'source' | 'target', -): T extends FlowElements ? GraphNode[] : Node[] { +): T[] { const id = isString(nodeOrId) ? nodeOrId : nodeOrId.id + const connectedIds = new Set() + const origin = dir === 'source' ? 'target' : 'source' - const ids = elements.filter((e) => isEdge(e) && e[origin] === id).map((e) => isEdge(e) && e[dir]) - return elements.filter((e) => ids.includes(e.id)) as T extends FlowElements ? GraphNode[] : Node[] -} -export function getOutgoers(nodeOrId: Node | { id: string } | string, elements: T) { - return getConnectedElements(nodeOrId, elements, 'target') + edges.forEach((edge) => { + if (edge[origin] === id) { + connectedIds.add(edge[dir]) + } + }) + + return nodes.filter((n) => connectedIds.has(n.id)) } -export function getIncomers(nodeOrId: Node | { id: string } | string, elements: T) { - return getConnectedElements(nodeOrId, elements, 'source') +export function getOutgoers(nodeOrId: Node | { id: string } | string, nodes: N[], edges: Edge[]): N[] +export function getOutgoers( + nodeOrId: Node | { id: string } | string, + elements: T, +): T extends FlowElements ? GraphNode[] : Node[] +export function getOutgoers(...args: any[]) { + if (args.length === 3) { + const [nodeOrId, nodes, edges] = args + return getConnectedElements(nodeOrId, nodes, edges, 'target') + } + + const [nodeOrId, elements] = args + const node: Node = isString(nodeOrId) ? { id: nodeOrId } : nodeOrId + + const outgoers = elements.filter((el: Element) => isEdge(el) && el.source === node.id) + + return outgoers.map((edge: Edge) => elements.find((el: Element) => isNode(el) && el.id === edge.target)) +} + +export function getIncomers(nodeOrId: Node | { id: string } | string, nodes: N[], edges: Edge[]): N[] +export function getIncomers( + nodeOrId: Node | { id: string } | string, + elements: T, +): T extends FlowElements ? GraphNode[] : Node[] +export function getIncomers(...args: any[]) { + if (args.length === 3) { + const [nodeOrId, nodes, edges] = args + return getConnectedElements(nodeOrId, nodes, edges, 'source') + } + + const [nodeOrId, elements] = args + const node: Node = isString(nodeOrId) ? { id: nodeOrId } : nodeOrId + + const incomers = elements.filter((el: Element) => isEdge(el) && el.target === node.id) + + return incomers.map((edge: Edge) => elements.find((el: Element) => isNode(el) && el.id === edge.source)) } export function getEdgeId({ source, sourceHandle, target, targetHandle }: Connection) { @@ -364,26 +404,34 @@ export function getNodesInside( }) } -export function getConnectedEdges(nodes: N[], edges: E[]) { - const nodeIds = nodes.map((node) => (isString(node) ? node : node.id)) +export function getConnectedEdges(nodesOrId: Node[] | string, edges: E[]) { + const nodeIds = new Set() - return edges.filter((edge) => nodeIds.includes(edge.source) || nodeIds.includes(edge.target)) + if (isString(nodesOrId)) { + nodeIds.add(nodesOrId) + } else if (nodesOrId.length >= 1) { + nodesOrId.forEach((n) => nodeIds.add(n.id)) + } + + return edges.filter((edge) => nodeIds.has(edge.source) || nodeIds.has(edge.target)) } -export function getConnectedNodes(nodes: N[], edges: E[]) { - const nodeIds = nodes.map((node) => (isString(node) ? node : node.id)) +export function getConnectedNodes(nodes: N[], edges: Edge[]) { + const nodeIds = new Set() + + nodes.forEach((node) => nodeIds.add(isString(node) ? node : node.id)) const connectedNodeIds = edges.reduce((acc, edge) => { - if (nodeIds.includes(edge.source)) { + if (nodeIds.has(edge.source)) { acc.add(edge.target) } - if (nodeIds.includes(edge.target)) { + if (nodeIds.has(edge.target)) { acc.add(edge.source) } return acc - }, new Set()) + }, new Set()) return nodes.filter((node) => connectedNodeIds.has(isString(node) ? node : node.id)) }