Rift.IO OSM R1 Initial Submission
[osm/UI.git] / skyquake / plugins / composer / src / src / libraries / graph / DescriptorGraphPathBuilder.js
1 /*
2 *
3 * Copyright 2016 RIFT.IO Inc
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *
17 */
18 /**
19 * Draw edges by dragging a VirtualNetworkFunctionConnectionPoint to VNFD, VLD or another VirtualNetworkFunctionConnectionPoint.
20 *
21 * If VLD does not exist between two VNFDs then add one.
22 *
23 * CSS for this class is defined in DescriptorGraph.scss.
24 *
25 */
26
27 import d3 from 'd3'
28 import CatalogItemsActions from '../../actions/CatalogItemsActions'
29 import SelectionManager from '../SelectionManager'
30 import DescriptorModelFactory from '../model/DescriptorModelFactory'
31
32 const line = d3.svg.line()
33 .x(d => {
34 return d.x;
35 })
36 .y(d => {
37 return d.y;
38 });
39
40 function mouseWithinPosition(position, mouseCoordinates, scale = 1) {
41 const x = mouseCoordinates[0] / scale;
42 const y = mouseCoordinates[1] / scale;
43 const withinXBoundary = position.left < x && position.right > x;
44 const withinYBoundary = position.top < y && position.bottom > y;
45 return withinXBoundary && withinYBoundary;
46 }
47
48 function getContainerUnderMouse(comp, element, scale) {
49 const mouseCoordinates = d3.mouse(element);
50 return comp.descriptorsAndConnectionPoints().filter(d => {
51 return DescriptorModelFactory.isConnectionPoint(d) ||
52 DescriptorModelFactory.isInternalConnectionPoint(d) ||
53 DescriptorModelFactory.isVirtualLink(d) ||
54 DescriptorModelFactory.isInternalVirtualLink(d);
55 }).filter(d => mouseWithinPosition(d.position, mouseCoordinates, scale));
56 }
57
58 export default class DescriptorGraphPathBuilder {
59
60 constructor(graph) {
61 this.graph = graph;
62 this.containers = [];
63 }
64
65 descriptorsAndConnectionPoints() {
66 return this.graph.g.selectAll('.descriptor, .connector');
67 }
68
69 descriptors() {
70 return this.graph.containersGroup.selectAll('.descriptor');
71 }
72
73 connectionPoints() {
74 return this.graph.connectorsGroup.selectAll('.connector');
75 }
76
77 paths() {
78 return this.graph.g.selectAll('.connection');
79 }
80
81 addContainers(containers) {
82 this.containers = this.containers.concat(containers);
83 }
84
85 static addConnection(srcConnector, dstConnector) {
86
87 // return true on success; falsy otherwise to allow the caller to clean up accordingly
88
89 if (!(srcConnector || dstConnector)) {
90 return false;
91 }
92
93 if (srcConnector.canConnectTo && srcConnector.canConnectTo(dstConnector)) {
94
95 const root = srcConnector.getRoot();
96
97 if (DescriptorModelFactory.isVirtualLink(dstConnector) || DescriptorModelFactory.isInternalVirtualLink(dstConnector)) {
98 dstConnector.addConnectionPoint(srcConnector);
99 } else {
100 if (root) {
101 const vld = root.createVld();
102 root.removeAnyConnectionsForConnector(srcConnector);
103 root.removeAnyConnectionsForConnector(dstConnector);
104 vld.addConnectionPoint(srcConnector);
105 vld.addConnectionPoint(dstConnector);
106 }
107 }
108
109 // notify catalog items have changed to force a redraw and update state accordingly
110 CatalogItemsActions.catalogItemDescriptorChanged(root);
111
112 return true;
113
114 }
115
116 }
117
118 static addConnectionToVLD(srcSelection, dstSelection) {
119
120 if (srcSelection[0].length === 0 || dstSelection[0].length === 0) {
121 return false;
122 }
123
124 const dstVirtualLink = dstSelection.datum();
125 const srcConnectionPoint = srcSelection.datum();
126
127 dstVirtualLink.addConnectionPoint(srcConnectionPoint);
128
129 // notify catalog items have changed to force a redraw and update state accordingly
130 CatalogItemsActions.catalogItemDescriptorChanged(dstVirtualLink.getRoot());
131
132 return true;
133
134 }
135
136 render() {
137
138 /*
139 Strategy: compare mouse position with the position of all the selectable elements.
140 On mouse down:
141 determine if there is a connection point under the mouse (the source)
142 determine if there is already a path connected on this source
143 On mouse move:
144 draw a tracer line from the source to the mouse
145 On mouse up:
146 determine if there is a connection point or VLD under the mouse (the destination)
147 take the respective action and notify
148 */
149
150 const comp = this;
151 const drawLine = line.interpolate('basis');
152
153 comp.boundMouseDownHandler = function (d) {
154
155 let hasInitializedMouseMoveHandler = false;
156
157 const srcConnectionPoint = comp.connectionPoints().filter(d => {
158 return DescriptorModelFactory.isConnectionPoint(d) || DescriptorModelFactory.isInternalConnectionPoint(d);
159 }).filter(d => mouseWithinPosition(d.position, d3.mouse(this), comp.graph.scale));
160
161 if (srcConnectionPoint[0].length) {
162
163 const src = srcConnectionPoint.datum() || {};
164
165 // determine if there is already a path on this connection point
166 // if there is then hide it; the mouseup handler will clean up
167 const existingPath = comp.paths().filter(d => {
168 return d && d.parent && d.parent.type === 'vld' && d.key === src.key;
169 });
170
171 // create a new path to follow the mouse
172 const path = comp.graph.paths.selectAll('.new-connection').data([srcConnectionPoint.datum()], DescriptorModelFactory.containerIdentity);
173 path.enter().append('path').classed('new-connection', true);
174
175 comp.boundMouseMoveHandler = function () {
176
177 const mouseCoordinates = d3.mouse(this);
178
179 path.attr({
180 fill: 'red',
181 stroke: 'red',
182 'stroke-width': '4px',
183 d: d => {
184 const srcPosition = d.position.centerPoint();
185 const dstPosition = {
186 x: mouseCoordinates[0] / comp.graph.scale,
187 y: mouseCoordinates[1] / comp.graph.scale
188 };
189 return drawLine([srcPosition, dstPosition]);
190 }
191 });
192
193 if (!hasInitializedMouseMoveHandler) {
194
195 hasInitializedMouseMoveHandler = true;
196
197 SelectionManager.removeOutline();
198
199 existingPath.style({
200 opacity: 0
201 });
202
203 // allow for visual treatment of this drag operation
204 srcConnectionPoint.classed('-selected', true);
205
206 // allow for visual treatment of this drag operation
207 comp.graph.svg.classed('-is-dragging-connection-point', true);
208
209 // identify which descriptors are a valid drop target
210 comp.descriptors().filter(d => {
211 const validTarget = src.canConnectTo && src.canConnectTo(d);
212 return validTarget;
213 }).classed('-is-valid-drop-target', true);
214
215 // identify which connection points are a valid drop target
216 comp.connectionPoints().filter(d => {
217 const validTarget = src.canConnectTo && src.canConnectTo(d);
218 return validTarget;
219 }).classed('-is-valid-drop-target', true);
220
221 }
222
223 const validDropTarget = getContainerUnderMouse(comp, this, comp.graph.scale);
224 comp.graph.g.selectAll('.-is-drag-over').classed('-is-drag-over', false);
225 SelectionManager.removeOutline();
226 if (validDropTarget) {
227 validDropTarget
228 .filter(d => src.canConnectTo && src.canConnectTo(d))
229 .classed('-is-drag-over', true)
230 .each(function () {
231 // warn must be a function so 'this' will = the path element
232 SelectionManager.outlineSvg(this);
233 });
234 }
235
236 };
237
238 // determine what the interaction is and do it
239 comp.boundMouseUpHandler = function () {
240
241 // remove these handlers so they start fresh on the next drag operation
242 comp.graph.svg
243 .on('mouseup.edgeBuilder', null)
244 .on('mousemove.edgeBuilder', null);
245
246 // remove visual treatments
247 comp.graph.svg.classed('-is-dragging-connection-point', false);
248 comp.descriptors().classed('-is-valid-drop-target', false);
249 comp.connectionPoints().classed('-is-valid-drop-target', false);
250 comp.graph.g.selectAll('.-is-drag-over').classed('-is-drag-over', false);
251
252 const dstSelection = getContainerUnderMouse(comp, this, comp.graph.scale);
253
254 // determine if there is a connection point
255 if (dstSelection[0].length) {
256 if (DescriptorGraphPathBuilder.addConnection(src, dstSelection.datum())) {
257 existingPath.remove();
258 }
259 }
260
261 // if we hid an existing path restore it
262 existingPath.style({
263 opacity: null
264 });
265
266 // remove the tracer path
267 path.remove();
268
269 SelectionManager.refreshOutline();
270
271 };
272
273 // init drag handlers
274 comp.graph.svg
275 .on('mouseup.edgeBuilder', comp.boundMouseUpHandler)
276 .on('mousemove.edgeBuilder', comp.boundMouseMoveHandler);
277 }
278
279 };
280
281 // enable dragging features
282 comp.graph.svg
283 .on('mousedown.edgeBuilder', comp.boundMouseDownHandler);
284
285 }
286
287 }