fix(edges): edges not reacting to hidden nodes

* Change getEdges to parse edges with source & target nodes and filter if one of them is missing
* add new type for Edges w sourceTargetNode called GraphEdge which is the return type of getEdges
*

Signed-off-by: Braks <78412429+bcakmakoglu@users.noreply.github.com>
This commit is contained in:
Braks
2021-11-23 18:25:08 +01:00
parent cddf875b6d
commit d22e21041d
9 changed files with 76 additions and 107 deletions
+22 -66
View File
@@ -1,12 +1,11 @@
<script lang="ts" setup>
import { useHandle, useStore } from '../../composables'
import { ConnectionMode, Edge, SourceTargetNode, Position, FlowEvents, EdgePositions, Dimensions, Transform } from '../../types'
import { ConnectionMode, Position, EdgePositions, Dimensions, Transform, GraphEdge } from '../../types'
import { isEdge, getEdgePositions, getHandle, isEdgeVisible } from '../../utils'
import EdgeAnchor from './EdgeAnchor.vue'
interface EdgeWrapper {
edge: Edge
sourceTargetNodes: SourceTargetNode
edge: GraphEdge
component?: any
markerEndId?: string
edgeUpdaterRadius?: number
@@ -15,20 +14,7 @@ interface EdgeWrapper {
transform: Transform
}
interface EdgeEvents {
(event: 'mouseEnter', data: FlowEvents['edgeMouseEnter']): void
(event: 'mouseMove', data: FlowEvents['edgeMouseMove']): void
(event: 'mouseLeave', data: FlowEvents['edgeMouseLeave']): void
(event: 'contextMenu', data: FlowEvents['edgeContextMenu']): void
(event: 'click', data: FlowEvents['edgeClick']): void
(event: 'dblClick', data: FlowEvents['edgeDoubleClick']): void
(event: 'updateStart', data: FlowEvents['edgeUpdateStart']): void
(event: 'update', data: FlowEvents['edgeUpdate']): void
(event: 'updateEnd', data: FlowEvents['edgeUpdateEnd']): void
}
const props = withDefaults(defineProps<EdgeWrapper>(), {})
const emit = defineEmits<EdgeEvents>()
const store = useStore()
@@ -41,43 +27,18 @@ const onEdgeClick = (event: MouseEvent) => {
store.unsetNodesSelection()
store.addSelectedElements([props.edge])
}
emit('click', data)
store.hooks.edgeClick.trigger(data)
store.hooks.elementClick.trigger({ event, element: props.edge })
}
const onEdgeContextMenu = (event: MouseEvent) => {
const data = { event, edge: props.edge }
emit('contextMenu', data)
store.hooks.edgeContextMenu.trigger(data)
}
const onDoubleClick = (event: MouseEvent) => {
const data = { event, edge: props.edge }
emit('dblClick', data)
store.hooks.edgeDoubleClick.trigger(data)
}
const onEdgeMouseEnter = (event: MouseEvent) => {
const data = { event, edge: props.edge }
emit('mouseEnter', data)
store.hooks.edgeMouseEnter.trigger({ event, edge: props.edge })
}
const onEdgeMouseMove = (event: MouseEvent) => {
const data = { event, edge: props.edge }
emit('mouseMove', data)
store.hooks.edgeMouseMove.trigger(data)
}
const onEdgeMouseLeave = (event: MouseEvent) => {
const data = { event, edge: props.edge }
emit('mouseLeave', data)
store.hooks.edgeMouseLeave.trigger(data)
}
const onEdgeContextMenu = (event: MouseEvent) => store.hooks.edgeContextMenu.trigger({ event, edge: props.edge })
const onDoubleClick = (event: MouseEvent) => store.hooks.edgeDoubleClick.trigger({ event, edge: props.edge })
const onEdgeMouseEnter = (event: MouseEvent) => store.hooks.edgeMouseEnter.trigger({ event, edge: props.edge })
const onEdgeMouseMove = (event: MouseEvent) => store.hooks.edgeMouseMove.trigger({ event, edge: props.edge })
const onEdgeMouseLeave = (event: MouseEvent) => store.hooks.edgeMouseLeave.trigger({ event, edge: props.edge })
const onEdgeUpdaterMouseEnter = () => (updating.value = true)
const onEdgeUpdaterMouseOut = () => (updating.value = false)
const onEdgeUpdaterSourceMouseDown = (event: MouseEvent) => {
handleEdgeUpdater(event, true)
}
const onEdgeUpdaterTargetMouseDown = (event: MouseEvent) => {
handleEdgeUpdater(event, false)
}
const onEdgeUpdaterSourceMouseDown = (event: MouseEvent) => handleEdgeUpdater(event, true)
const onEdgeUpdaterTargetMouseDown = (event: MouseEvent) => handleEdgeUpdater(event, false)
const handleEdgeUpdater = (event: MouseEvent, isSourceHandle: boolean) => {
const nodeId = isSourceHandle ? props.edge.target : props.edge.source
@@ -85,7 +46,6 @@ const handleEdgeUpdater = (event: MouseEvent, isSourceHandle: boolean) => {
const isValidConnection = () => true
const isTarget = isSourceHandle
emit('updateStart', { event, edge: props.edge })
store.hooks.edgeUpdateStart.trigger({ event, edge: props.edge })
handler(
event,
@@ -94,32 +54,26 @@ const handleEdgeUpdater = (event: MouseEvent, isSourceHandle: boolean) => {
isTarget,
isValidConnection,
isSourceHandle ? 'target' : 'source',
(connection) => {
emit('update', { edge: props.edge, connection })
store.hooks.edgeUpdate.trigger({ edge: props.edge, connection })
},
() => {
emit('updateEnd', event)
store.hooks.edgeUpdateEnd.trigger(event)
},
(connection) => store.hooks.edgeUpdate.trigger({ edge: props.edge, connection }),
() => store.hooks.edgeUpdateEnd.trigger(event),
)
}
// when connection type is loose we can define all handles as sources
const targetNodeHandles = controlledComputed(
() => props.sourceTargetNodes,
() => props.edge.sourceTargetNodes,
() =>
store.connectionMode === ConnectionMode.Strict
? props.sourceTargetNodes.targetNode.__vf.handleBounds.target
: props.sourceTargetNodes.targetNode.__vf.handleBounds.target ??
props.sourceTargetNodes.targetNode.__vf.handleBounds.source,
? props.edge.sourceTargetNodes.targetNode.__vf.handleBounds.target
: props.edge.sourceTargetNodes.targetNode.__vf.handleBounds.target ??
props.edge.sourceTargetNodes.targetNode.__vf.handleBounds.source,
)
const sourceHandle = controlledComputed(
() => props.sourceTargetNodes,
() => props.edge.sourceTargetNodes,
() => {
if (props.sourceTargetNodes.sourceNode && props.sourceTargetNodes.sourceNode.__vf.handleBounds.source)
return getHandle(props.sourceTargetNodes.sourceNode.__vf.handleBounds.source, props.edge.sourceHandle)
if (props.edge.sourceTargetNodes.sourceNode && props.edge.sourceTargetNodes.sourceNode.__vf.handleBounds.source)
return getHandle(props.edge.sourceTargetNodes.sourceNode.__vf.handleBounds.source, props.edge.sourceHandle)
else return null
},
)
@@ -136,10 +90,10 @@ const isSelected = controlledComputed(
)
const edgePos = computed(() =>
getEdgePositions(
props.sourceTargetNodes.sourceNode,
props.edge.sourceTargetNodes.sourceNode,
sourceHandle.value,
sourcePosition.value,
props.sourceTargetNodes.targetNode,
props.edge.sourceTargetNodes.targetNode,
targetHandle.value,
targetPosition.value,
),
@@ -186,6 +140,7 @@ export default {
v-if="edgePos.sourceX && edgePos.sourceY && edgePos.targetX && edgePos.targetY"
v-bind="{
id: props.edge.id,
sourceTargetNodes: props.edge.sourceTargetNodes,
source: props.edge.source,
target: props.edge.target,
selected: isSelected,
@@ -214,6 +169,7 @@ export default {
:is="props.component ?? props.edge.type"
v-bind="{
id: props.edge.id,
sourceTargetNodes: props.edge.sourceTargetNodes,
source: props.edge.source,
target: props.edge.target,
selected: isSelected,
+2 -11
View File
@@ -4,21 +4,20 @@ import {
ConnectionLineType,
ConnectionMode,
Dimensions,
Edge,
EdgeComponent,
ElementId,
GraphEdge,
GraphNode,
HandleType,
Transform,
XYPosition,
} from '../../types'
import { getSourceTargetNodes } from '../../utils'
import EdgeWrapper from '../../components/Edges/EdgeWrapper.vue'
import ConnectionLine from '../../components/ConnectionLine/ConnectionLine.vue'
import MarkerDefinitions from './MarkerDefinitions.vue'
interface EdgeRendererProps {
edges: Edge[]
edges: GraphEdge[]
nodes: GraphNode[]
edgeTypes: Record<string, EdgeComponent>
transform: Transform
@@ -70,13 +69,6 @@ const connectionLineVisible = controlledComputed(
),
)
const transform = computed(() => `translate(${props.transform[0]},${props.transform[1]}) scale(${props.transform[2]})`)
const sourceTargetNodes = (edge: Edge) => {
const { sourceNode, targetNode } = getSourceTargetNodes(edge, props.nodes)
if (!sourceNode) console.warn(`couldn't create edge for source id: ${edge.source}; edge id: ${edge.id}`)
if (!targetNode) console.warn(`couldn't create edge for target id: ${edge.target}; edge id: ${edge.id}`)
return { sourceNode, targetNode }
}
</script>
<script lang="ts">
export default {
@@ -91,7 +83,6 @@ export default {
v-for="(edge, i) of props.edges"
:key="`${edge.id}-${i}`"
:edge="edge"
:source-target-nodes="sourceTargetNodes(edge)"
:component="getType(edge.type)"
:selectable="props.elementsSelectable"
:edge-updater-radius="props.edgeUpdaterRadius"
+19 -3
View File
@@ -1,6 +1,6 @@
import microDiff from 'microdiff'
import { setActivePinia, createPinia, defineStore, StoreDefinition, acceptHMRUpdate } from 'pinia'
import { FlowState, Node, FlowActions, Elements, FlowGetters, Edge, GraphNode, NextElements } from '~/types'
import { FlowState, Node, FlowActions, Elements, FlowGetters, GraphNode, NextElements, GraphEdge } from '~/types'
import {
clampPosition,
getDimensions,
@@ -13,6 +13,7 @@ import {
deepUnref,
getHandleBounds,
isGraphNode,
getSourceTargetNodes,
} from '~/utils'
import parseElementsWorker from '~/workers/parseElements'
@@ -59,8 +60,23 @@ export default (id: string, preloadedState: FlowState) => {
return n.filter((node) => !node.isHidden)
},
getEdges(): Edge[] {
return this.edges.filter((edge) => !edge.isHidden)
getEdges(): GraphEdge[] {
return this.edges
.filter((edge) => !edge.isHidden)
.map((edge) => {
const { sourceNode, targetNode } = getSourceTargetNodes(edge, this.getNodes)
if (!sourceNode) console.warn(`couldn't create edge for source id: ${edge.source}; edge id: ${edge.id}`)
if (!targetNode) console.warn(`couldn't create edge for target id: ${edge.target}; edge id: ${edge.id}`)
return {
...edge,
sourceTargetNodes: {
sourceNode,
targetNode,
},
}
})
.filter(({ sourceTargetNodes: { sourceNode, targetNode } }) => !!(sourceNode && targetNode))
},
},
actions: {
+5 -4
View File
@@ -5,15 +5,16 @@ import {
ElementId,
Elements,
FitViewParams,
FlowOptions, Loading,
FlowOptions,
Loading,
Position,
XYPosition,
} from './flow'
import { Connection, ConnectionLineType, ConnectionMode } from './connection'
import { GraphNode, Node, NodeExtent, NodeTypes, TranslateExtent } from './node'
import { FlowStore } from '~/types/store'
import { EdgeTypes } from '~/types/edge'
import { KeyCode, PanOnScrollMode } from '~/types/zoom'
import { FlowStore } from './store'
import { EdgeTypes } from './edge'
import { KeyCode, PanOnScrollMode } from './zoom'
export type HandleType = 'source' | 'target'
+3 -3
View File
@@ -1,17 +1,17 @@
import { CSSProperties } from 'vue'
import { Edge, EdgeTypes, GraphEdge } from './edge'
import { Edge, EdgeTypes } from './edge'
import { NodeExtent, GraphNode, NodeTypes, TranslateExtent, Node } from './node'
import { ConnectionLineType, ConnectionMode } from './connection'
import { KeyCode, PanOnScrollMode } from './zoom'
import { FlowStore } from './store'
export type ElementId = string
export type FlowElement<T = any> = GraphNode<T> | GraphEdge<T>
export type FlowElement<T = any> = GraphNode<T> | Edge<T>
export type FlowElements<T = any> = FlowElement<T>[]
export type Elements<T = any> = (Node<T> | Edge<T>)[]
export type NextElements = {
nextNodes: GraphNode[]
nextEdges: GraphEdge[]
nextEdges: Edge[]
}
export type Transform = [number, number, number]
+2 -2
View File
@@ -21,7 +21,7 @@ import {
OnConnectStopFunc,
SetConnectionId,
} from './connection'
import { EdgeComponent, GraphEdge } from './edge'
import { Edge, EdgeComponent, GraphEdge } from './edge'
import { NodeComponent, NodeDiffUpdate, NodeDimensionUpdate, NodeExtent, GraphNode, NodePosUpdate, TranslateExtent } from './node'
import { D3Selection, D3Zoom, D3ZoomHandler, InitD3ZoomPayload } from './zoom'
import { FlowHooks } from './hooks'
@@ -29,7 +29,7 @@ import { FlowHooks } from './hooks'
export interface FlowState extends FlowOptions {
elements: FlowElements
nodes: GraphNode[]
edges: GraphEdge[]
edges: Edge[]
selectedElements?: FlowElements
selectedNodesBbox: Rect
+6 -4
View File
@@ -9,6 +9,8 @@ import {
SourceTargetNode,
Transform,
XYPosition,
Elements,
FlowElements,
} from '~/types'
export function getHandlePosition(position: Position, node: GraphNode, handle: any | null = null): XYPosition {
@@ -113,15 +115,15 @@ export function isEdgeVisible({ sourcePos, targetPos, width, height, transform }
return overlappingArea > 0
}
export const getSourceTargetNodes = (edge: Edge, nodes: GraphNode[]): SourceTargetNode => {
export const getSourceTargetNodes = (edge: Edge, elements: FlowElements | Elements): SourceTargetNode => {
let { sourceNode, targetNode }: any = {
sourceNode: null,
targetNode: null,
}
for (const node of nodes) {
for (const el of elements) {
if (!sourceNode || !targetNode) {
if (node.id === edge.source) sourceNode = node
if (node.id === edge.target) targetNode = node
if (el.id === edge.source) sourceNode = el
if (el.id === edge.target) targetNode = el
} else {
break
}
+6 -4
View File
@@ -15,6 +15,7 @@ import {
GraphNode,
FlowElements,
FlowElement,
GraphEdge,
} from '~/types'
import { useWindow } from '~/composables'
@@ -48,14 +49,15 @@ export const getHostForElement = (element: HTMLElement): Document => {
else return window.document
}
export const isEdge = (element: Node | FlowElement | Connection): element is Edge =>
export const isEdge = (element: Node | Edge | Connection): element is Edge =>
'id' in element && 'source' in element && 'target' in element
export const isNode = (element: Node | FlowElement | Connection): element is Node =>
export const isNode = (element: Node | Edge | Connection): element is Node =>
'id' in element && !('source' in element) && !('target' in element)
export const isGraphNode = (element: Node | FlowElement | Connection): element is GraphNode =>
isNode(element) && '__vf' in element
export const isGraphNode = (element: FlowElement | Connection): element is GraphNode => isNode(element) && '__vf' in element
export const isGraphEdge = (element: FlowElement | Connection): element is GraphEdge =>
isEdge(element) && 'sourceTargetNodes' in element
const getConnectedElements = (node: GraphNode, elements: Elements, dir: 'source' | 'target') => {
if (!isNode(node)) return []
+11 -10
View File
@@ -2,7 +2,6 @@ import { Component } from 'vue'
import { isEdge, isNode, parseEdge, parseNode } from './graph'
import {
ConnectionMode,
Edge,
EdgeProps,
Elements,
FlowState,
@@ -11,6 +10,8 @@ import {
GraphNode,
NodeProps,
PanOnScrollMode,
Node,
Edge,
} from '~/types'
import { DefaultNode, InputNode, OutputNode, BezierEdge, SmoothStepEdge, StepEdge, StraightEdge } from '~/components'
import { createHooks } from '~/composables'
@@ -100,9 +101,9 @@ export const initialState = (): FlowState => ({
vueFlowVersion: typeof __VUE_FLOW_VERSION__ !== 'undefined' ? __VUE_FLOW_VERSION__ : '-',
})
export const parseElements = async (elements: Elements, nodes: GraphNode[], edges: Edge[], nodeExtent: NodeExtent) =>
export const parseElements = async (elements: Elements, nodes: Node[], edges: Edge[], nodeExtent: NodeExtent) =>
new Promise<NextElements>((resolve) => {
const nextElements: NextElements = {
const { nextEdges, nextNodes }: NextElements = {
nextNodes: [],
nextEdges: [],
}
@@ -111,10 +112,10 @@ export const parseElements = async (elements: Elements, nodes: GraphNode[], edge
const storeNode = nodes[nodes.map((x) => x.id).indexOf(element.id)]
if (storeNode) {
const updatedNode: GraphNode = {
const updatedNode = {
...storeNode,
...element,
}
} as GraphNode
updatedNode.__vf!.position = element.position
if (typeof element.type !== 'undefined' && element.type !== storeNode.type) {
@@ -123,24 +124,24 @@ export const parseElements = async (elements: Elements, nodes: GraphNode[], edge
updatedNode.__vf!.width = 0
}
nextElements.nextNodes.push(updatedNode)
nextNodes.push(updatedNode)
} else {
nextElements.nextNodes.push(parseNode(element, nodeExtent))
nextNodes.push(parseNode(element, nodeExtent))
}
} else if (isEdge(element)) {
const storeEdge = edges[edges.map((x) => x.id).indexOf(element.id)]
if (storeEdge) {
nextElements.nextEdges.push({
nextEdges.push({
...storeEdge,
...element,
})
} else {
nextElements.nextEdges.push(parseEdge(element))
nextEdges.push(parseEdge(element))
}
}
}
resolve(nextElements)
resolve({ nextEdges, nextNodes })
})
const isObject = (val: any) => val !== null && typeof val === 'object'