11<script setup>
2- import { computed , ref , toRef , watch } from ' vue'
2+ import { computed , nextTick , ref , watch } from ' vue'
33import { BaseEdge , EdgeLabelRenderer , Position , getSmoothStepPath , useNodesData , useVueFlow } from ' @vue-flow/core'
4+ import { ProcessStatus } from ' ./useRunProcess'
45
56const props = defineProps ({
67 id: {
@@ -39,95 +40,124 @@ const props = defineProps({
3940 type: String ,
4041 default: Position .Left ,
4142 },
43+ data: {
44+ type: Object ,
45+ required: false ,
46+ },
4247})
4348
4449const { updateEdgeData } = useVueFlow ()
4550
46- const nodesData = useNodesData ([props .target , props .source ])
51+ /**
52+ * We call `useNodesData` to get the data of the source and target nodes, which
53+ * contain the information about the status of each nodes' process.
54+ */
55+ const nodesData = useNodesData (() => [props .target , props .source ])
4756
4857const labelRef = ref ()
4958
5059const edgeRef = ref ()
5160
61+ /**
62+ * We extract the source and target node data from the nodes data.
63+ * We only need the first element of the array since we are only connecting two nodes.
64+ */
5265const targetNodeData = computed (() => nodesData .value [0 ].data )
5366
5467const sourceNodeData = computed (() => nodesData .value [1 ].data )
5568
56- const isFinished = toRef (() => sourceNodeData .value .isFinished )
57-
58- const isCancelled = toRef (() => targetNodeData .value .isCancelled )
59-
60- const isAnimating = ref (false )
69+ const isAnimating = computed ({
70+ get : () => props .data .isAnimating || false ,
71+ set : (value ) => {
72+ updateEdgeData (props .id , { isAnimating: value })
73+ },
74+ })
6175
6276let animation = null
6377
6478const path = computed (() => getSmoothStepPath (props))
6579
6680const edgeColor = computed (() => {
67- if (targetNodeData .value .hasError ) {
68- return ' #f87171'
69- }
70-
71- if (targetNodeData .value .isFinished ) {
72- return ' #42B983'
73- }
74-
75- if (targetNodeData .value .isCancelled || targetNodeData .value .isSkipped ) {
76- return ' #fbbf24'
77- }
78-
79- if (targetNodeData .value .isRunning || isAnimating .value ) {
80- return ' #2563eb'
81- }
82-
83- return ' #6b7280'
84- })
85-
86- watch (isCancelled, (isCancelled ) => {
87- if (isCancelled) {
88- animation? .cancel ()
81+ switch (targetNodeData .value .status ) {
82+ case ProcessStatus .ERROR :
83+ return ' #f87171'
84+ case ProcessStatus .FINISHED :
85+ return ' #42B983'
86+ case ProcessStatus .CANCELLED :
87+ case ProcessStatus .SKIPPED :
88+ return ' #fbbf24'
89+ case ProcessStatus .RUNNING :
90+ return ' #2563eb'
91+ default :
92+ return ' #6b7280'
8993 }
9094})
9195
92- watch (isAnimating, (isAnimating ) => {
93- updateEdgeData (props .id , { isAnimating })
94- })
95-
96- watch (isFinished, (isFinished ) => {
97- if (isFinished) {
98- runAnimation ()
99- }
100- })
96+ // Cancel the animation if the target nodes' process was cancelled
97+ watch (
98+ () => targetNodeData .value .status === ProcessStatus .CANCELLED ,
99+ (isCancelled ) => {
100+ if (isCancelled) {
101+ animation? .cancel ()
102+ }
103+ },
104+ )
105+
106+ // Run the animation when the source nodes' process is finished
107+ watch (
108+ () => sourceNodeData .value .status === ProcessStatus .FINISHED ,
109+ (isFinished ) => {
110+ if (isFinished) {
111+ runAnimation ()
112+ }
113+ },
114+ )
101115
102116function runAnimation () {
103117 const pathEl = edgeRef .value ? .pathEl
118+ const labelEl = labelRef .value
104119
105- if (! pathEl) {
120+ if (! pathEl || ! labelEl) {
121+ console .warn (' Path or label element not found' )
106122 return
107123 }
108124
109125 const totalLength = pathEl .getTotalLength ()
110126
111127 isAnimating .value = true
112128
113- const keyframes = [{ offsetDistance: ' 0%' }, { offsetDistance: ' 100%' }]
114-
115- // use path length as a possible measure for the animation duration
116- const pathLengthDuration = totalLength * 10
117-
118- animation = labelRef .value .animate (keyframes, {
119- duration: Math .min (Math .max (pathLengthDuration, 1500 ), 3000 ), // clamp duration between 1.5s and 3s
120- direction: ' normal' ,
121- easing: ' ease-in-out' ,
122- iterations: 1 ,
129+ // We need to wait for the next tick to ensure that the label element is rendered
130+ nextTick (() => {
131+ const keyframes = [{ offsetDistance: ' 0%' }, { offsetDistance: ' 100%' }]
132+
133+ // use path length as a possible measure for the animation duration
134+ const pathLengthDuration = totalLength * 10
135+
136+ /**
137+ * We animate the label element along the path of the edge using the `offsetDistance` property and
138+ * the Web Animations API.
139+ *
140+ * The `animate` method returns an `Animation` object that we can use to listen to events like `finish` or `cancel`.
141+ *
142+ * The animation duration is calculated based on the total length of the path and clamped between 1.5s and 3s.
143+ *
144+ * @see https://developer.mozilla.org/en-US/docs/Web/API/Element/animate
145+ */
146+ const labelAnimation = labelEl .animate (keyframes, {
147+ duration: Math .min (Math .max (pathLengthDuration, 1500 ), 3000 ), // clamp duration between 1.5s and 3s
148+ direction: ' normal' ,
149+ easing: ' ease-in-out' ,
150+ iterations: 1 ,
151+ })
152+
153+ const handleAnimationEnd = () => {
154+ isAnimating .value = false
155+ }
156+
157+ labelAnimation .onfinish = handleAnimationEnd
158+ labelAnimation .oncancel = handleAnimationEnd
159+ animation = labelAnimation
123160 })
124-
125- animation .onfinish = handleAnimationEnd
126- animation .oncancel = handleAnimationEnd
127- }
128-
129- function handleAnimationEnd () {
130- isAnimating .value = false
131161}
132162< / script>
133163
@@ -152,7 +182,6 @@ export default {
152182 offsetRotate: '0deg',
153183 offsetAnchor: 'center',
154184 }"
155- class = " animated-edge-label"
156185 >
157186 < span class = " truck" >
158187 < span class = " box" > 📦< / span>
0 commit comments