- Introduced new components: ArrowEdge, CustomEdge, InteractiveArrowEdge, and EdgeConfiguration for enhanced edge management in the process flow. - Implemented dynamic edge paths with customizable styles, labels, and animations, improving visual representation and user interaction. - Enhanced ProcessFlowCanvas to support new edge types and configurations, allowing for more flexible process designs. - Updated ProcessFlowNodes to include new edge components, ensuring seamless integration with existing node functionalities. - Improved user experience by providing configuration options for edges, including animation and style settings, directly within the process builder.
285 lines
6.9 KiB
Vue
285 lines
6.9 KiB
Vue
<template>
|
|
<g>
|
|
|
|
<!-- Main edge path -->
|
|
<BaseEdge
|
|
:id="id"
|
|
:path="edgePath"
|
|
:style="{
|
|
stroke: selected ? '#ff6b6b' : edgeStyle.stroke || '#555',
|
|
strokeWidth: selected ? 3 : edgeStyle.strokeWidth || 2,
|
|
...edgeStyle
|
|
}"
|
|
:marker-end="selected ? 'url(#arrow-selected)' : 'url(#arrow)'"
|
|
:class="{
|
|
'vue-flow__edge-path': true,
|
|
'custom-edge': true,
|
|
'selected': selected,
|
|
'animated': animated
|
|
}"
|
|
/>
|
|
|
|
<!-- Interactive control points for path adjustment -->
|
|
<g v-if="selected && showControlPoints" class="edge-control-points">
|
|
<!-- Mid-point control for path bending -->
|
|
<circle
|
|
:cx="labelX"
|
|
:cy="labelY"
|
|
r="4"
|
|
fill="#3b82f6"
|
|
stroke="white"
|
|
stroke-width="2"
|
|
class="edge-control-point"
|
|
style="cursor: move;"
|
|
@mousedown="onControlPointMouseDown"
|
|
@touchstart="onControlPointMouseDown"
|
|
/>
|
|
|
|
<!-- Visual indicator for draggable control -->
|
|
<circle
|
|
:cx="labelX"
|
|
:cy="labelY"
|
|
r="8"
|
|
fill="rgba(59, 130, 246, 0.2)"
|
|
class="edge-control-indicator"
|
|
style="pointer-events: none;"
|
|
/>
|
|
</g>
|
|
|
|
<!-- Edge label using EdgeLabelRenderer for HTML content -->
|
|
<EdgeLabelRenderer v-if="label">
|
|
<div
|
|
:style="{
|
|
position: 'absolute',
|
|
transform: `translate(-50%, -50%) translate(${labelX}px, ${labelY}px)`,
|
|
pointerEvents: 'all',
|
|
fontSize: '12px',
|
|
fontWeight: '500'
|
|
}"
|
|
class="edge-label-container nodrag nopan"
|
|
>
|
|
<div class="edge-label">
|
|
{{ label }}
|
|
</div>
|
|
</div>
|
|
</EdgeLabelRenderer>
|
|
</g>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { computed, ref, inject } from 'vue'
|
|
import { BaseEdge, EdgeLabelRenderer, getBezierPath, getSmoothStepPath, getStraightPath } from '@vue-flow/core'
|
|
import { useEdge } from '@vue-flow/core'
|
|
|
|
const props = defineProps({
|
|
id: String,
|
|
sourceX: Number,
|
|
sourceY: Number,
|
|
targetX: Number,
|
|
targetY: Number,
|
|
sourcePosition: String,
|
|
targetPosition: String,
|
|
selected: Boolean,
|
|
animated: Boolean,
|
|
label: String,
|
|
style: Object,
|
|
type: {
|
|
type: String,
|
|
default: 'smoothstep'
|
|
},
|
|
data: Object,
|
|
markerEnd: String,
|
|
markerStart: String
|
|
})
|
|
|
|
// Get edge instance for updating
|
|
const { edge } = useEdge()
|
|
|
|
// Control state
|
|
const showControlPoints = ref(true)
|
|
const isDragging = ref(false)
|
|
const pathOffset = ref({ x: 0, y: 0 })
|
|
|
|
// Computed edge style
|
|
const edgeStyle = computed(() => ({
|
|
stroke: '#555',
|
|
strokeWidth: 2,
|
|
...props.style
|
|
}))
|
|
|
|
// Calculate edge path based on type with optional offset for custom positioning
|
|
const edgePath = computed(() => {
|
|
try {
|
|
const offsetX = pathOffset.value.x || 0
|
|
const offsetY = pathOffset.value.y || 0
|
|
|
|
// Apply offset to create custom path curvature
|
|
const centerX = (props.sourceX + props.targetX) / 2 + offsetX
|
|
const centerY = (props.sourceY + props.targetY) / 2 + offsetY
|
|
|
|
switch (props.type) {
|
|
case 'straight':
|
|
return getStraightPath(props.sourceX, props.sourceY, props.targetX, props.targetY)[0]
|
|
case 'smoothstep':
|
|
case 'custom':
|
|
return getSmoothStepPath(
|
|
props.sourceX,
|
|
props.sourceY,
|
|
props.sourcePosition,
|
|
props.targetX,
|
|
props.targetY,
|
|
props.targetPosition,
|
|
centerX,
|
|
centerY,
|
|
20, // offset
|
|
8 // border radius
|
|
)[0]
|
|
case 'bezier':
|
|
default:
|
|
return getBezierPath({
|
|
sourceX: props.sourceX,
|
|
sourceY: props.sourceY,
|
|
sourcePosition: props.sourcePosition,
|
|
targetX: props.targetX,
|
|
targetY: props.targetY,
|
|
targetPosition: props.targetPosition,
|
|
curvature: 0.25
|
|
})[0]
|
|
}
|
|
} catch (error) {
|
|
console.error('Error calculating edge path:', error)
|
|
// Fallback to a simple straight line
|
|
return `M ${props.sourceX} ${props.sourceY} L ${props.targetX} ${props.targetY}`
|
|
}
|
|
})
|
|
|
|
// Calculate label position
|
|
const labelX = computed(() => {
|
|
return (props.sourceX + props.targetX) / 2 + pathOffset.value.x
|
|
})
|
|
|
|
const labelY = computed(() => {
|
|
return (props.sourceY + props.targetY) / 2 + pathOffset.value.y
|
|
})
|
|
|
|
// Control point drag handling
|
|
const onControlPointMouseDown = (event) => {
|
|
event.preventDefault()
|
|
event.stopPropagation()
|
|
|
|
isDragging.value = true
|
|
const startX = event.clientX || event.touches[0].clientX
|
|
const startY = event.clientY || event.touches[0].clientY
|
|
const startOffsetX = pathOffset.value.x
|
|
const startOffsetY = pathOffset.value.y
|
|
|
|
const onMouseMove = (moveEvent) => {
|
|
if (!isDragging.value) return
|
|
|
|
const currentX = moveEvent.clientX || moveEvent.touches[0].clientX
|
|
const currentY = moveEvent.clientY || moveEvent.touches[0].clientY
|
|
|
|
const deltaX = currentX - startX
|
|
const deltaY = currentY - startY
|
|
|
|
// Update path offset for custom positioning
|
|
pathOffset.value = {
|
|
x: startOffsetX + deltaX,
|
|
y: startOffsetY + deltaY
|
|
}
|
|
|
|
// Store the custom path in edge data for persistence
|
|
if (edge.value) {
|
|
edge.value.data = {
|
|
...edge.value.data,
|
|
customPath: pathOffset.value
|
|
}
|
|
}
|
|
}
|
|
|
|
const onMouseUp = () => {
|
|
isDragging.value = false
|
|
document.removeEventListener('mousemove', onMouseMove)
|
|
document.removeEventListener('mouseup', onMouseUp)
|
|
document.removeEventListener('touchmove', onMouseMove)
|
|
document.removeEventListener('touchend', onMouseUp)
|
|
}
|
|
|
|
document.addEventListener('mousemove', onMouseMove)
|
|
document.addEventListener('mouseup', onMouseUp)
|
|
document.addEventListener('touchmove', onMouseMove)
|
|
document.addEventListener('touchend', onMouseUp)
|
|
}
|
|
|
|
// Initialize path offset from saved data
|
|
try {
|
|
if (edge.value && edge.value.data && edge.value.data.customPath) {
|
|
pathOffset.value = edge.value.data.customPath
|
|
}
|
|
} catch (error) {
|
|
console.log('Edge data not available yet, using default path')
|
|
}
|
|
</script>
|
|
|
|
<script>
|
|
export default {
|
|
name: 'CustomEdge',
|
|
inheritAttrs: false
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.edge-label-container {
|
|
z-index: 1000;
|
|
}
|
|
|
|
.edge-label {
|
|
background: white;
|
|
padding: 4px 8px;
|
|
border-radius: 4px;
|
|
font-size: 11px;
|
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
border: 1px solid #e2e8f0;
|
|
white-space: nowrap;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.edge-control-point {
|
|
cursor: move;
|
|
transition: r 0.2s ease;
|
|
}
|
|
|
|
.edge-control-point:hover {
|
|
r: 5;
|
|
fill: #2563eb;
|
|
}
|
|
|
|
.custom-edge {
|
|
transition: stroke-width 0.2s ease, stroke 0.2s ease;
|
|
}
|
|
|
|
.custom-edge.animated {
|
|
stroke-dasharray: 5;
|
|
animation: dash 1s linear infinite;
|
|
}
|
|
|
|
.custom-edge.selected {
|
|
filter: drop-shadow(0 0 4px rgba(255, 107, 107, 0.5));
|
|
}
|
|
|
|
@keyframes dash {
|
|
to {
|
|
stroke-dashoffset: -10;
|
|
}
|
|
}
|
|
|
|
/* Hide control points when not selected */
|
|
.edge-control-points {
|
|
opacity: 0;
|
|
transition: opacity 0.2s ease;
|
|
}
|
|
|
|
.selected .edge-control-points {
|
|
opacity: 1;
|
|
}
|
|
</style> |