169 lines
3.4 KiB
Vue
169 lines
3.4 KiB
Vue
<script setup>
|
|
import { computed, ref } from 'vue'
|
|
import { TransitionPresets, useDebounceFn, useTransition, watchDebounced } from '@vueuse/core'
|
|
import { getBezierPath, useVueFlow } from '@vue-flow/core'
|
|
|
|
const props = defineProps({
|
|
id: {
|
|
type: String,
|
|
required: true,
|
|
},
|
|
source: {
|
|
type: String,
|
|
required: true,
|
|
},
|
|
target: {
|
|
type: String,
|
|
required: true,
|
|
},
|
|
sourceX: {
|
|
type: Number,
|
|
required: true,
|
|
},
|
|
sourceY: {
|
|
type: Number,
|
|
required: true,
|
|
},
|
|
targetX: {
|
|
type: Number,
|
|
required: true,
|
|
},
|
|
targetY: {
|
|
type: Number,
|
|
required: true,
|
|
},
|
|
sourcePosition: {
|
|
type: String,
|
|
required: true,
|
|
},
|
|
targetPosition: {
|
|
type: String,
|
|
required: true,
|
|
},
|
|
data: {
|
|
type: Object,
|
|
required: false,
|
|
},
|
|
markerEnd: {
|
|
type: String,
|
|
required: false,
|
|
},
|
|
style: {
|
|
type: Object,
|
|
required: false,
|
|
},
|
|
})
|
|
|
|
const curve = ref()
|
|
|
|
const dot = ref()
|
|
|
|
const transform = ref({ x: 0, y: 0 })
|
|
|
|
const showDot = ref(false)
|
|
|
|
const { onNodeDoubleClick, fitBounds, fitView } = useVueFlow()
|
|
|
|
const path = computed(() =>
|
|
getBezierPath({
|
|
sourceX: props.sourceX,
|
|
sourceY: props.sourceY,
|
|
sourcePosition: props.sourcePosition,
|
|
targetX: props.targetX,
|
|
targetY: props.targetY,
|
|
targetPosition: props.targetPosition,
|
|
}),
|
|
)
|
|
|
|
const debouncedFitBounds = useDebounceFn(fitBounds, 1, { maxWait: 1 })
|
|
|
|
onNodeDoubleClick(({ node }) => {
|
|
const isSource = props.source === node.id
|
|
const isTarget = props.target === node.id
|
|
|
|
if (!showDot.value && (isSource || isTarget)) {
|
|
showDot.value = true
|
|
let totalLength = curve.value.getTotalLength()
|
|
const initialPos = ref(isSource ? 0 : totalLength)
|
|
let stopHandle
|
|
|
|
const output = useTransition(initialPos, {
|
|
duration: Math.floor(totalLength / 2 / 100) * 1000,
|
|
transition: TransitionPresets.easeOutCubic,
|
|
onFinished: () => {
|
|
stopHandle?.()
|
|
showDot.value = false
|
|
fitView({
|
|
nodes: [isSource ? props.target : props.source],
|
|
duration: 500,
|
|
})
|
|
},
|
|
})
|
|
|
|
transform.value = curve.value.getPointAtLength(output.value)
|
|
|
|
debouncedFitBounds(
|
|
{
|
|
width: 100,
|
|
height: 200,
|
|
x: transform.value.x - 100,
|
|
y: transform.value.y - 100,
|
|
},
|
|
{ duration: 500 },
|
|
)
|
|
|
|
setTimeout(() => {
|
|
initialPos.value = isSource ? totalLength : 0
|
|
|
|
stopHandle = watchDebounced(
|
|
output,
|
|
(next) => {
|
|
if (!showDot.value) {
|
|
return
|
|
}
|
|
|
|
const nextLength = curve.value.getTotalLength()
|
|
|
|
if (totalLength !== nextLength) {
|
|
totalLength = nextLength
|
|
initialPos.value = isSource ? totalLength : 0
|
|
}
|
|
|
|
transform.value = curve.value.getPointAtLength(next)
|
|
|
|
debouncedFitBounds({
|
|
width: 100,
|
|
height: 200,
|
|
x: transform.value.x - 100,
|
|
y: transform.value.y - 100,
|
|
})
|
|
},
|
|
{ debounce: 1 },
|
|
)
|
|
}, 500)
|
|
}
|
|
})
|
|
</script>
|
|
|
|
<script>
|
|
export default {
|
|
inheritAttrs: false,
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<path :id="id" ref="curve" :style="style" class="vue-flow__edge-path" :d="path[0]" :marker-end="markerEnd" />
|
|
|
|
<Transition name="fade">
|
|
<circle
|
|
v-if="showDot"
|
|
ref="dot"
|
|
r="5"
|
|
cy="0"
|
|
cx="0"
|
|
:transform="`translate(${transform.x}, ${transform.y})`"
|
|
style="fill: #fdd023"
|
|
/>
|
|
</Transition>
|
|
</template>
|