2. drag and drop
ROS2에 대해 알아보자
2. drag and drop
Drag & Drop
- Drag & Drop으로 노드를 vue-flow에 추가하도록 하는 기능이며,
- Eaxmple Drag and Drop을 참고 하여 구현 ( useDnD.js, FlowSidebar.vue )
Drag&Drop 관련 구현
- Drop 영역을 표시할 DropzoneBackground.vue
- Drag&drop 이벤트를 처리할 useDnD.js
- 추가할 노드를 선택할 수 있는 FlowSidebar.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
./
├── App.vue
├── components
│ ├── customnodes ##Drag로 추가할 커스텀 타입의 노드 정의
│ │ ├── CustomNodeTypes.ts ##=> 추가할 노드의 타입들 정의
│ │ ├── GroupNode.vue ##=> 그룹 노드
│ │ ├── StartNode.vue ##=> 초기 시작 노드
│ │ ├── TextInputNode.vue ##=> action 노드(텍스트 인풋을 받아 실행)
│ ├── draganddrop
│ │ └── useDnD.js ## Drag & Drop 이벤트 처리
│ ├── DropzoneBackground.vue ## vue-flow 내 drop 시 BackGround 영역 컴포넌트
│ ├── FlowCanvas.vue
│ ├── FlowSidebar.vue ## 추가할 노드를 선택할수 있는 FlowSidebar
사용 방법
FlowSidebar.vue
- 사이드바에 노드 타입 (input/default/output)을 정의하며, drop시 해당 노드 타입에 따른 emit 이벤트를 날림
useDnD.js
-
vue-flow/core를 사용하여 drag&drop 관련 기능을 구현한 Custom Hook
- state : draggedType/isDragOver/isDragging를 state로 관리
-
Action : useVueFlow() 내 아래 Action(메서드)을 사용해 Drag&Drop Event 구현
- addNodes = 노드 상태 추가
- screenToFlowCoordinate = viewport 내 X/Y 그래프 좌표 (상속 : state Omit.screenToFlowCoordinate)
- onNodesInitialized = updateNode 를 감싼 함수(노드 id에 해당하는 X/Y position 업데이트)
- updateNode = updates a node
-
제공하는 객체 및 함수
- draggedType = 드래그 중인 아이템의 타입
- isDragOver = 드래그 중인 아이템이 특정 영역 위에 있는지 여부
- isDragging = 드래그가 진행 중인지 여부
- onDragStart = 드래그가 시작될 때 호출되는 이벤트 핸들러
- onDragLeave = 드래그 중인 아이템이 특정 영역을 떠날 때 호출되는 이벤트 핸들러
- onDragOver = 드래그 중인 아이템이 특정 영역 위에 있을 때 호출되는 이벤트 핸들러
-
onDrop = 드래그 중인 아이템을 특정 영역에 떨어뜨릴 때 호출되는 이벤트 핸들러
- useVueFlow의 경우 Store 처럼 state/mutation/action/getter들을 제공하며, 추가 필요 기능들이 있으면 vue flow 공식 홈페이지에서 찾아보면 나옴
DropzoneBackground.vue
- vue-flow/background를 활용해 기본 백그라운드 속성 정의
Nested Node Drag & Drop을 위한 Node Position/ComputedPosition Update
- Nested Node로 ParentNode를 갖는 ChildNode를 Drag&Drop으로 생성 시 Position x/y는 부모노드 영역 내에서의 x/y 상대 포지션을 기준으로 잡음
- 따라서 Drop 시 node 좌표를 부모 노드가 있는 경우, 없는 경우에 따라 다르게 업데이트 해야함
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
- Find the group node that contains the drop position when drop NODE
- \*/
function findParentNode(dropX, dropY) {
const nodes = toObject().nodes
if (!nodes.length) {
return null
}
for (const node of nodes.reverse()) {
if (node.type !== CustomTypesDefine.GROUP_NODE) continue
const candidateNode = findNode(node.id)
console.log(candidateNode)
if (
dropX >= candidateNode.computedPosition.x &&
dropX <= candidateNode.computedPosition.x + candidateNode.dimensions.width &&
dropY >= candidateNode.computedPosition.y &&
dropY <= candidateNode.computedPosition.y + candidateNode.dimensions.height
) {
return candidateNode
}
}
return null
}
/\*\*
- Handles the drop event.
-
- @param {DragEvent} event
\*/
function onDrop(event) {
const position = screenToFlowCoordinate({
x: Math.floor(event.clientX),
y: Math.floor(event.clientY),
})
const nodeId = getId(draggedType.value)
let newNode = null
switch (draggedType.value) {
case CustomTypesDefine.START_NODE:
newNode = {
id: nodeId,
type: draggedType.value,
position,
sourcePosition: Position.Bottom,
data: { label: nodeId, status: '', isRunning: false, isStartNode: true },
initialized: true,
}
break
case CustomTypesDefine.INPUT_NODE:
newNode = {
id: nodeId,
type: draggedType.value,
position,
sourcePosition: Position.Bottom,
data: {
label: nodeId,
status: '',
isRunning: false,
isStartNode: false,
actionType: actionType.value,
initialized: true,
},
}
break
case CustomTypesDefine.GROUP_NODE:
newNode = {
id: nodeId,
type: draggedType.value,
position,
data: { label: nodeId },
style: {
backgroundColor: 'rgba(246, 92, 92, 0.49)',
height: '200px',
width: '400px',
},
initialized: true,
}
break
default:
newNode = {
id: nodeId,
type: draggedType.value,
position,
data: { label: nodeId },
}
break
}
console.log('[Drop Position]', position)
const parentNode = findParentNode(position.x, position.y)
console.log('parentNode ', parentNode)
if (parentNode) {
switch (draggedType.value) {
case CustomTypesDefine.START_NODE:
case CustomTypesDefine.INPUT_NODE:
newNode.parentNode = parentNode.id
newNode.extent = `parent`
// newNode.expandParent = true
break
case CustomTypesDefine.GROUP_NODE:
newNode.parentNode = parentNode.id
newNode.extent = `parent`
break
default:
break
}
const { off } = onNodesInitialized(() => {
updateNode(nodeId, (node) => ({
computedPosition: {
x: position.x,
y: position.y,
z: 0,
},
position: {
x: position.x - parentNode.computedPosition.x - node.dimensions.width / 2,
y: position.y - parentNode.computedPosition.y - node.dimensions.height / 2,
},
}))
off()
})
} else {
/\*\*
_ Align node position after drop, so it's centered to the mouse
_
_ We can hook into events even in a callback, and we can remove the event listener after it's been called.
_/
const { off } = onNodesInitialized(() => {
updateNode(nodeId, (node) => ({
position: {
x: node.position.x - node.dimensions.width / 2,
y: node.position.y - node.dimensions.height / 2,
},
}))
off()
})
}
addNodes(newNode)
}
- Ref. https://dev.to/monsterpi13/vue-flow-quickstart-and-best-practices-482k#:~:text=Therefore%2C%20for%20this%20demonstration%2C%20I%20opted%20for,which%20boasts%20exceptional%20support%20for%20Vue3%20.
This post is licensed under
CC BY 4.0
by the author.