Skip to content

Commit 8be5e47

Browse files
committed
fix: implement node highlighting feature in Canvas component
- Added a timer to highlight located nodes for a specified duration. - Introduced CSS styles for visual feedback on locating nodes. - Updated the dataflow store to manage the located node state. - Enhanced the locateNode function to reset the highlight after the duration. - Adjusted related components to reflect the new highlighting state.
1 parent 9c96f2f commit 8be5e47

6 files changed

Lines changed: 131 additions & 8 deletions

File tree

packages/dag/src/Canvas.vue

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,10 @@ onMounted(() => {
272272
onUnmounted(() => {
273273
cleanupKeyboardShortcuts()
274274
dataflowStore.unregisterVueFlowUpdateCallback()
275+
if (locatedNodeHighlightTimer) {
276+
clearTimeout(locatedNodeHighlightTimer)
277+
locatedNodeHighlightTimer = null
278+
}
275279
leftPanelResizeObserver?.disconnect()
276280
leftPanelMutationObserver?.disconnect()
277281
rightPanelResizeObserver?.disconnect()
@@ -315,6 +319,8 @@ const ZOOM_STEP = 0.1
315319
const MIN_ZOOM = 0.1
316320
const MAX_ZOOM = 10
317321
const ZOOM_PRESETS = [0.25, 0.5, 0.75, 1, 2]
322+
const LOCATED_NODE_HIGHLIGHT_DURATION = 1800
323+
let locatedNodeHighlightTimer: ReturnType<typeof setTimeout> | null = null
318324
319325
const zoomPercentage = computed(() => Math.round(viewport.value.zoom * 100))
320326
const previousZoom = ref(1)
@@ -806,13 +812,25 @@ provide('clearDragHelperLines', () => {
806812
helperLineVertical.value = undefined
807813
})
808814
809-
function locateNode(nodeId: string) {
815+
async function locateNode(nodeId: string) {
810816
const node = vueFlow.findNode(nodeId)
811817
if (!node) return
818+
dataflowStore.locatedNodeId = ''
819+
await nextTick()
820+
dataflowStore.locatedNodeId = nodeId
821+
822+
if (locatedNodeHighlightTimer) clearTimeout(locatedNodeHighlightTimer)
823+
locatedNodeHighlightTimer = setTimeout(() => {
824+
if (dataflowStore.locatedNodeId === nodeId) {
825+
dataflowStore.locatedNodeId = ''
826+
}
827+
locatedNodeHighlightTimer = null
828+
}, LOCATED_NODE_HIGHLIGHT_DURATION)
829+
812830
fitViewWithOffset({
813831
nodes: [nodeId],
814832
duration: 300,
815-
maxZoom: viewport.value.zoom,
833+
maxZoom: 1,
816834
})
817835
}
818836

packages/dag/src/MonitorView.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ const taskRecord = ref({ total: 0, items: [] as any[] })
4444
const timeFormat = ref('HH:mm:ss')
4545
const nodeDetailDialog = ref(false)
4646
const nodeDetailDialogId = ref('')
47-
const canvasRef = ref<any>(null)
4847
const { t } = useI18n()
4948
5049
const {
@@ -56,6 +55,7 @@ const {
5655
formScope,
5756
isSaving,
5857
syncTypeLabel,
58+
canvasRef,
5959
taskOperationsRef,
6060
consoleRef,
6161
skipErrorRef,

packages/dag/src/components/BaseNode.vue

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,92 @@ $iconSize: 24px;
212212
pointer-events: none;
213213
}*/
214214
}
215+
216+
.df-node-wrap.is-locating {
217+
--canvas-node-locate-stroke: rgba(44, 101, 255, 0.64);
218+
--canvas-node-locate-ring: rgba(44, 101, 255, 0.2);
219+
--canvas-node-locate-glow: rgba(44, 101, 255, 0.16);
220+
221+
position: relative;
222+
z-index: 9;
223+
224+
&::before,
225+
&::after {
226+
content: '';
227+
position: absolute;
228+
inset: -4px;
229+
border-radius: 18px;
230+
pointer-events: none;
231+
}
232+
233+
&::before {
234+
border: 1px solid var(--canvas-node-locate-stroke);
235+
animation: canvas-node-locate-trace 1.8s cubic-bezier(0.16, 1, 0.3, 1);
236+
}
237+
238+
&::after {
239+
box-shadow:
240+
0 0 0 0 var(--canvas-node-locate-ring),
241+
0 10px 24px var(--canvas-node-locate-glow);
242+
animation: canvas-node-locate-halo 1.8s cubic-bezier(0.16, 1, 0.3, 1);
243+
}
244+
}
245+
246+
html.dark .df-node-wrap.is-locating {
247+
--canvas-node-locate-stroke: rgba(92, 140, 255, 0.72);
248+
--canvas-node-locate-ring: rgba(92, 140, 255, 0.28);
249+
--canvas-node-locate-glow: rgba(92, 140, 255, 0.24);
250+
}
251+
252+
@keyframes canvas-node-locate-trace {
253+
0% {
254+
opacity: 0;
255+
transform: scale(0.985);
256+
}
257+
14% {
258+
opacity: 1;
259+
transform: scale(1);
260+
}
261+
100% {
262+
opacity: 0;
263+
transform: scale(1.035);
264+
}
265+
}
266+
267+
@keyframes canvas-node-locate-halo {
268+
0% {
269+
opacity: 0;
270+
transform: scale(0.98);
271+
}
272+
16% {
273+
opacity: 1;
274+
}
275+
68% {
276+
opacity: 0.42;
277+
}
278+
100% {
279+
opacity: 0;
280+
transform: scale(1.055);
281+
box-shadow:
282+
0 0 0 14px rgba(44, 101, 255, 0),
283+
0 10px 24px rgba(44, 101, 255, 0);
284+
}
285+
}
286+
287+
@media (prefers-reduced-motion: reduce) {
288+
.df-node-wrap.is-locating {
289+
&::before {
290+
animation: none;
291+
opacity: 1;
292+
}
293+
294+
&::after {
295+
animation: none;
296+
box-shadow: 0 0 0 4px var(--canvas-node-locate-ring);
297+
}
298+
}
299+
}
300+
215301
.df-menu-list {
216302
margin: -6px;
217303
.df-menu-item {

packages/dag/src/components/elements/CanvasNode.vue

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,14 @@ const hasNodeError = computed(() => store.getters['dataflow/hasNodeError'])
2828
const ins = computed(() => props.data?.__Ctor || {})
2929
3030
const wrapClass = computed(() => {
31-
if (dataflowStore.selectedNode?.id === props.data.id || props.selected)
32-
return 'border-primary'
33-
return ''
31+
const list: string[] = []
32+
if (dataflowStore.selectedNode?.id === props.data.id || props.selected) {
33+
list.push('border-primary')
34+
}
35+
if (dataflowStore.locatedNodeId === props.data.id) {
36+
list.push('is-locating')
37+
}
38+
return list
3439
})
3540
3641
const nodeClass = computed(() => {
@@ -216,6 +221,11 @@ const canBeTarget = computed(() => {
216221
.df-node-wrap {
217222
z-index: 5;
218223
outline: none;
224+
225+
&.is-locating {
226+
z-index: 9;
227+
}
228+
219229
&.can-be-connected {
220230
.node-anchor.input {
221231
display: flex;

packages/dag/src/components/monitor/Node.vue

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -420,8 +420,14 @@ const alarmCls = computed(() =>
420420
)
421421
422422
const wrapClass = computed(() => {
423-
if (dataflowStore.selectedNode?.id === props.data.id) return 'border-primary'
424-
return ''
423+
const list: string[] = []
424+
if (dataflowStore.selectedNode?.id === props.data.id) {
425+
list.push('border-primary')
426+
}
427+
if (dataflowStore.locatedNodeId === props.data.id) {
428+
list.push('is-locating')
429+
}
430+
return list
425431
})
426432
427433
const nodeClass = computed(() => {

packages/dag/src/stores/dataflow.store.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ export const useDataflowStore = defineStore('dataflow', () => {
106106
const pageVersion = ref(Date.now().toString())
107107
const selectedNode = ref(null)
108108
const selectedNodeId = ref(null)
109+
const locatedNodeId = ref('')
109110
const lastClickPosition = ref<[number, number]>([0, 0])
110111
const stateIsReadonly = ref(false)
111112
const showSettings = ref(false)
@@ -886,6 +887,7 @@ export const useDataflowStore = defineStore('dataflow', () => {
886887
// UI / 交互状态归零
887888
selectedNode.value = null
888889
selectedNodeId.value = null
890+
locatedNodeId.value = ''
889891
stateIsReadonly.value = false
890892
showSettings.value = false
891893
showConsole.value = false
@@ -928,6 +930,7 @@ export const useDataflowStore = defineStore('dataflow', () => {
928930
patchDataflowDebounce,
929931
processorNodeTypes,
930932
selectedNode,
933+
locatedNodeId,
931934
lastClickPosition,
932935
stateIsReadonly,
933936
taskSaving,

0 commit comments

Comments
 (0)