3 * Copyright 2016 RIFT.IO Inc
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
9 * http://www.apache.org/licenses/LICENSE-2.0
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.
19 * Draw edges by dragging a VirtualNetworkFunctionConnectionPoint to VNFD, VLD or another VirtualNetworkFunctionConnectionPoint.
21 * If VLD does not exist between two VNFDs then add one.
23 * CSS for this class is defined in DescriptorGraph.scss.
28 import CatalogItemsActions
from '../../actions/CatalogItemsActions'
29 import SelectionManager
from '../SelectionManager'
30 import DescriptorModelFactory
from '../model/DescriptorModelFactory'
32 const line
= d3
.svg
.line()
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
;
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
));
58 export default class DescriptorGraphPathBuilder
{
65 descriptorsAndConnectionPoints() {
66 return this.graph
.g
.selectAll('.descriptor, .connector');
70 return this.graph
.containersGroup
.selectAll('.descriptor');
74 return this.graph
.connectorsGroup
.selectAll('.connector');
78 return this.graph
.g
.selectAll('.connection');
81 addContainers(containers
) {
82 this.containers
= this.containers
.concat(containers
);
85 static addConnection(srcConnector
, dstConnector
) {
87 // return true on success; falsy otherwise to allow the caller to clean up accordingly
89 if (!(srcConnector
|| dstConnector
)) {
93 if (srcConnector
.canConnectTo
&& srcConnector
.canConnectTo(dstConnector
)) {
95 const root
= srcConnector
.getRoot();
97 if (DescriptorModelFactory
.isVirtualLink(dstConnector
) || DescriptorModelFactory
.isInternalVirtualLink(dstConnector
)) {
98 dstConnector
.addConnectionPoint(srcConnector
);
101 const vld
= root
.createVld();
102 root
.removeAnyConnectionsForConnector(srcConnector
);
103 root
.removeAnyConnectionsForConnector(dstConnector
);
104 vld
.addConnectionPoint(srcConnector
);
105 vld
.addConnectionPoint(dstConnector
);
109 // notify catalog items have changed to force a redraw and update state accordingly
110 CatalogItemsActions
.catalogItemDescriptorChanged(root
);
118 static addConnectionToVLD(srcSelection
, dstSelection
) {
120 if (srcSelection
[0].length
=== 0 || dstSelection
[0].length
=== 0) {
124 const dstVirtualLink
= dstSelection
.datum();
125 const srcConnectionPoint
= srcSelection
.datum();
127 dstVirtualLink
.addConnectionPoint(srcConnectionPoint
);
129 // notify catalog items have changed to force a redraw and update state accordingly
130 CatalogItemsActions
.catalogItemDescriptorChanged(dstVirtualLink
.getRoot());
139 Strategy: compare mouse position with the position of all the selectable elements.
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
144 draw a tracer line from the source to the mouse
146 determine if there is a connection point or VLD under the mouse (the destination)
147 take the respective action and notify
151 const drawLine
= line
.interpolate('basis');
153 comp
.boundMouseDownHandler = function (d
) {
155 let hasInitializedMouseMoveHandler
= false;
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
));
161 if (srcConnectionPoint
[0].length
) {
163 const src
= srcConnectionPoint
.datum() || {};
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
;
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);
175 comp
.boundMouseMoveHandler = function () {
177 const mouseCoordinates
= d3
.mouse(this);
182 'stroke-width': '4px',
184 const srcPosition
= d
.position
.centerPoint();
185 const dstPosition
= {
186 x
: mouseCoordinates
[0] / comp
.graph
.scale
,
187 y
: mouseCoordinates
[1] / comp
.graph
.scale
189 return drawLine([srcPosition
, dstPosition
]);
193 if (!hasInitializedMouseMoveHandler
) {
195 hasInitializedMouseMoveHandler
= true;
197 SelectionManager
.removeOutline();
203 // allow for visual treatment of this drag operation
204 srcConnectionPoint
.classed('-selected', true);
206 // allow for visual treatment of this drag operation
207 comp
.graph
.svg
.classed('-is-dragging-connection-point', true);
209 // identify which descriptors are a valid drop target
210 comp
.descriptors().filter(d
=> {
211 const validTarget
= src
.canConnectTo
&& src
.canConnectTo(d
);
213 }).classed('-is-valid-drop-target', true);
215 // identify which connection points are a valid drop target
216 comp
.connectionPoints().filter(d
=> {
217 const validTarget
= src
.canConnectTo
&& src
.canConnectTo(d
);
219 }).classed('-is-valid-drop-target', true);
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
) {
228 .filter(d
=> src
.canConnectTo
&& src
.canConnectTo(d
))
229 .classed('-is-drag-over', true)
231 // warn must be a function so 'this' will = the path element
232 SelectionManager
.outlineSvg(this);
238 // determine what the interaction is and do it
239 comp
.boundMouseUpHandler = function () {
241 // remove these handlers so they start fresh on the next drag operation
243 .on('mouseup.edgeBuilder', null)
244 .on('mousemove.edgeBuilder', null);
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);
252 const dstSelection
= getContainerUnderMouse(comp
, this, comp
.graph
.scale
);
254 // determine if there is a connection point
255 if (dstSelection
[0].length
) {
256 if (DescriptorGraphPathBuilder
.addConnection(src
, dstSelection
.datum())) {
257 existingPath
.remove();
261 // if we hid an existing path restore it
266 // remove the tracer path
269 SelectionManager
.refreshOutline();
273 // init drag handlers
275 .on('mouseup.edgeBuilder', comp
.boundMouseUpHandler
)
276 .on('mousemove.edgeBuilder', comp
.boundMouseMoveHandler
);
281 // enable dragging features
283 .on('mousedown.edgeBuilder', comp
.boundMouseDownHandler
);