refactor(edges): Make edge wrapper functional component

# What's changed?

* remove edge-positions from graph edge
* remove source and target nodes from graph edge
* refactor edge wrapper into functional component and handle source/target positions internally
This commit is contained in:
Braks
2022-05-27 23:36:01 +02:00
parent a65d85687d
commit ce13007dbf
2 changed files with 312 additions and 12 deletions
@@ -0,0 +1,200 @@
import { Component, CSSProperties, FunctionalComponent, VNode } from 'vue'
import EdgeAnchor from './EdgeAnchor'
import { ConnectionMode, EdgeComponent, EdgeMarkerType, EdgeTextProps, GraphNode, Position } from '~/types'
import { getEdgePositions, getHandle, getMarkerId } from '~/utils'
interface Props {
id: string
type: EdgeComponent | Function | Object | false
name: string
source: string
target: string
sourceNode: GraphNode
targetNode: GraphNode
targetHandleId: string | null
sourceHandleId: string | null
selectable?: boolean
updatable?: boolean
label?: string | VNode | Component<EdgeTextProps> | Object
data?: any
labelStyle?: CSSProperties
labelShowBg?: boolean
labelBgStyle?: any
labelBgPadding?: [number, number]
labelBgBorderRadius?: number
animated?: boolean
selected?: boolean
style: CSSProperties
markerEnd?: EdgeMarkerType
markerStart?: EdgeMarkerType
connectionMode: ConnectionMode
edgeUpdaterRadius: number
}
const Wrapper: FunctionalComponent<Props> = function (
{
name,
type,
id,
data,
labelBgBorderRadius,
labelBgPadding,
labelBgStyle,
labelStyle,
labelShowBg,
style,
animated,
label,
updatable,
target,
source,
sourceNode,
targetNode,
sourceHandleId,
targetHandleId,
selected,
markerEnd,
markerStart,
connectionMode,
edgeUpdaterRadius,
},
{ emit },
) {
if (!sourceNode || !targetNode) return null
let updating = $ref(false)
const onEdgeUpdaterMouseEnter = () => (updating = true)
const onEdgeUpdaterMouseOut = () => (updating = false)
const onEdgeUpdaterSourceMouseDown = () => emit('source-mousedown')
const onEdgeUpdaterTargetMouseDown = () => emit('target-mousedown')
let sourceNodeHandles
if (connectionMode === ConnectionMode.Strict) {
sourceNodeHandles = sourceNode.handleBounds.source
} else {
sourceNodeHandles = sourceNode.handleBounds.source ?? sourceNode.handleBounds.target
}
const sourceHandle = getHandle(sourceNodeHandles, sourceHandleId)
let targetNodeHandles
if (connectionMode === ConnectionMode.Strict) {
targetNodeHandles = targetNode.handleBounds.target
} else {
targetNodeHandles = targetNode.handleBounds.target ?? targetNode.handleBounds.source
}
const targetHandle = getHandle(targetNodeHandles, targetHandleId)
const sourcePosition = sourceHandle ? sourceHandle.position : Position.Bottom
const targetPosition = targetHandle ? targetHandle.position : Position.Top
const { sourceX, sourceY, targetY, targetX } = getEdgePositions(
sourceNode,
sourceHandle,
sourcePosition,
targetNode,
targetHandle,
targetPosition,
)
return h('g', { class: [`vue-flow__node-${name}`, { updating }] }, [
h(type as any, {
id,
sourceNode,
targetNode,
source,
target,
updatable,
selected,
animated,
label,
labelStyle,
labelShowBg,
labelBgStyle,
labelBgPadding,
labelBgBorderRadius,
data,
style,
markerStart: `url(#${getMarkerId(markerStart)})`,
markerEnd: `url(#${getMarkerId(markerEnd)})`,
sourcePosition,
targetPosition,
sourceX,
sourceY,
targetX,
targetY,
sourceHandleId,
targetHandleId,
}),
updatable
? [
h(
'g',
{
onMouseDown: onEdgeUpdaterSourceMouseDown,
onMouseEnter: onEdgeUpdaterMouseEnter,
onMouseOut: onEdgeUpdaterMouseOut,
},
h(EdgeAnchor, {
position: sourcePosition,
centerX: sourceX,
centerY: sourceY,
radius: edgeUpdaterRadius,
}),
),
h(
'g',
{
onMouseDown: onEdgeUpdaterTargetMouseDown,
onMouseEnter: onEdgeUpdaterMouseEnter,
onMouseOut: onEdgeUpdaterMouseOut,
},
h(EdgeAnchor, {
position: targetPosition,
centerX: targetX,
centerY: targetY,
radius: edgeUpdaterRadius,
}),
),
]
: null,
])
}
Wrapper.props = [
'name',
'type',
'id',
'data',
'labelBgBorderRadius',
'labelBgPadding',
'labelBgStyle',
'labelStyle',
'labelShowBg',
'style',
'animated',
'label',
'updatable',
'selectable',
'target',
'source',
'sourceNode',
'targetNode',
'sourceHandleId',
'targetHandleId',
'selected',
'markerEnd',
'markerStart',
'connectionMode',
'edgeUpdaterRadius',
]
Wrapper.emits = ['source-mousedown', 'target-mousedown']
export default Wrapper
@@ -1,14 +1,19 @@
<script lang="ts" setup>
import { ConnectionLine, EdgeWrapper } from '../../components'
import { useVueFlow } from '../../composables'
import { groupEdgesByZLevel } from '../../utils'
import type { EdgeComponent, GraphEdge } from '../../types'
import { CSSProperties } from 'vue'
import EdgeWrapper from '../../components/Edges/Wrapper'
import ConnectionLine from '../../components/ConnectionLine/ConnectionLine.vue'
import { useHandle, useVueFlow } from '../../composables'
import { connectionExists, groupEdgesByZLevel } from '../../utils'
import { EdgeComponent, GraphEdge } from '../../types'
import { Slots } from '../../context'
import MarkerDefinitions from './MarkerDefinitions.vue'
const slots = inject(Slots)
const {
emits,
connectionMode,
edgeUpdaterRadius,
onPaneReady,
connectionNodeId,
nodesConnectable,
@@ -21,8 +26,14 @@ const {
getNodes,
getEdges,
getEdgeTypes,
setState,
addSelectedEdges,
noPanClassName,
} = $(useVueFlow())
const selectable = (s?: boolean) => (typeof s === 'undefined' ? elementsSelectable : s)
const updatable = (u?: boolean) => (typeof u === 'undefined' ? edgesUpdatable : u)
const sourceNode = $(
controlledComputed($$(connectionNodeId), () => {
if (connectionNodeId) return getNode(connectionNodeId)
@@ -58,9 +69,9 @@ onPaneReady(() => {
)
})
const getType = (edge: GraphEdge) => {
const name = edge.type || 'default'
let edgeType = edge.template ?? getEdgeTypes[name]
const getType = (type?: string, template?: GraphEdge['template']) => {
const name = type || 'default'
let edgeType = template ?? getEdgeTypes[name]
const instance = getCurrentInstance()
if (typeof edgeType === 'string') {
@@ -75,12 +86,76 @@ const getType = (edge: GraphEdge) => {
const slot = slots?.[`edge-${name}`]
if (!slot?.({})) {
console.warn(`[vueflow]: Edge type "${edge.type}" not found and no edge-slot detected. Using fallback type "default".`)
console.warn(`[vueflow]: Edge type "${type}" not found and no edge-slot detected. Using fallback type "default".`)
return false
}
return slot
}
const onEdgeClick = (event: MouseEvent, edge: GraphEdge) => {
const data = { event, edge }
if (selectable(edge.selectable)) {
setState({
nodesSelectionActive: false,
})
addSelectedEdges([edge])
}
emits.edgeClick(data)
}
const onEdgeContextMenu = (event: MouseEvent, edge: GraphEdge) => emits.edgeContextMenu({ event, edge })
const onDoubleClick = (event: MouseEvent, edge: GraphEdge) => emits.edgeDoubleClick({ event, edge })
const onEdgeMouseEnter = (event: MouseEvent, edge: GraphEdge) => emits.edgeMouseEnter({ event, edge })
const onEdgeMouseMove = (event: MouseEvent, edge: GraphEdge) => emits.edgeMouseMove({ event, edge })
const onEdgeMouseLeave = (event: MouseEvent, edge: GraphEdge) => emits.edgeMouseLeave({ event, edge })
const onEdgeUpdaterSourceMouseDown = (event: MouseEvent, edge: GraphEdge) => handleEdgeUpdater(event, edge, true)
const onEdgeUpdaterTargetMouseDown = (event: MouseEvent, edge: GraphEdge) => handleEdgeUpdater(event, edge, false)
const { onMouseDown } = useHandle()
const handleEdgeUpdater = (event: MouseEvent, edge: GraphEdge, isSourceHandle: boolean) => {
const nodeId = isSourceHandle ? edge.target : edge.source
const handleId = (isSourceHandle ? edge.targetHandle : edge.sourceHandle) ?? ''
emits.edgeUpdateStart({ event, edge })
onMouseDown(
event,
handleId,
nodeId,
isSourceHandle,
undefined,
isSourceHandle ? 'target' : 'source',
(connection) => {
if (!connectionExists(edge, getEdges)) emits.edgeUpdate({ edge, connection })
},
() => emits.edgeUpdateEnd({ event, edge }),
)
}
const getClass = (edge: GraphEdge) => {
const extraClass = edge.class instanceof Function ? edge.class(edge) : edge.class
return [
'vue-flow__edge',
noPanClassName,
{
selected: edge.selected,
animated: edge.animated,
inactive: !selectable,
},
extraClass,
]
}
const getStyle = (edge: GraphEdge) => (edge.style instanceof Function ? edge.style(edge) : edge.style) as CSSProperties
</script>
<script lang="ts">
@@ -97,11 +172,36 @@ export default {
v-for="edge of group.edges"
:id="edge.id"
:key="edge.id"
:edge="edge"
:type="getType(edge.type, edge.template)"
:name="edge.type || 'default'"
:type="getType(edge)"
:selectable="typeof edge.selectable === 'undefined' ? elementsSelectable : edge.selectable"
:updatable="typeof edge.updatable === 'undefined' ? edgesUpdatable : edge.updatable"
:source="edge.source"
:target="edge.target"
:target-handle-id="edge.targetHandle"
:source-handle-id="edge.sourceHandle"
:source-node="getNode(edge.source)"
:target-node="getNode(edge.target)"
:label="edge.label"
:data="edge.data"
:animated="edge.animated"
:selectable="selectable(edge.selectable)"
:updatable="updatable(edge.updatable)"
:label-style="edge.labelStyle"
:label-show-bg="edge.labelShowBg"
:label-bg-style="edge.labelBgStyle"
:label-bg-padding="edge.labelBgPadding"
:label-bg-border-radius="edge.labelBgBorderRadius"
:connection-mode="connectionMode"
:edge-updater-radius="edgeUpdaterRadius"
:style="getStyle(edge)"
:class="getClass(edge)"
@click="(e: MouseEvent) => onEdgeClick(e, edge)"
@dblClick="(e: MouseEvent) => onDoubleClick(e, edge)"
@contextmenu="(e: MouseEvent) => onEdgeContextMenu(e, edge)"
@mouseenter="(e: MouseEvent) => onEdgeMouseEnter(e, edge)"
@mousemove="(e: MouseEvent) => onEdgeMouseMove(e, edge)"
@mouseleave="(e: MouseEvent) => onEdgeMouseLeave(e, edge)"
@source-mousedown="(e: MouseEvent) => onEdgeUpdaterSourceMouseDown(e, edge)"
@target-mousedown="(e: MouseEvent) => onEdgeUpdaterTargetMouseDown(e, edge)"
/>
<ConnectionLine v-if="connectionLineVisible && !!sourceNode" :source-node="sourceNode" />
</g>