| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /* global define, module, require, exports */ |
| |
| (function (root, factory) { |
| if (typeof define === 'function' && define.amd) { |
| define(['d3', |
| 'nf.Connection', |
| 'nf.ConnectionConfiguration', |
| 'nf.CanvasUtils'], |
| function (d3, nfConnection, nfConnectionConfiguration, nfCanvasUtils) { |
| return (nf.Connectable = factory(d3, nfConnection, nfConnectionConfiguration, nfCanvasUtils)); |
| }); |
| } else if (typeof exports === 'object' && typeof module === 'object') { |
| module.exports = (nf.Connectable = |
| factory(require('d3'), |
| require('nf.Connection'), |
| require('nf.ConnectionConfiguration'), |
| require('nf.CanvasUtils'))); |
| } else { |
| nf.Connectable = factory(root.d3, |
| root.nf.Connection, |
| root.nf.ConnectionConfiguration, |
| root.nf.CanvasUtils); |
| } |
| }(this, function (d3, nfConnection, nfConnectionConfiguration, nfCanvasUtils) { |
| 'use strict'; |
| |
| var connect; |
| var canvas; |
| var origin; |
| |
| var config = { |
| urls: { |
| api: '../nifi-api', |
| } |
| }; |
| |
| /** |
| * Determines if we want to allow adding connections in the current state: |
| * |
| * 1) When shift is down, we could be adding components to the current selection. |
| * 2) When the selection box is visible, we are in the process of moving all the |
| * components currently selected. |
| * 3) When the drag selection box is visible, we are in the process or selecting components |
| * using the selection box. |
| * |
| * @returns {boolean} |
| */ |
| var allowConnection = function () { |
| return !d3.event.shiftKey && d3.select('rect.drag-selection').empty() && d3.select('rect.component-selection').empty(); |
| }; |
| |
| return { |
| init: function () { |
| canvas = d3.select('#canvas'); |
| |
| // dragging behavior for the connector |
| connect = d3.drag() |
| .subject(function (d) { |
| origin = d3.mouse(canvas.node()); |
| return { |
| x: origin[0], |
| y: origin[1] |
| }; |
| }) |
| .on('start', function (d) { |
| // stop further propagation |
| d3.event.sourceEvent.stopPropagation(); |
| |
| // unselect the previous components |
| nfCanvasUtils.getSelection().classed('selected', false); |
| |
| // mark the source component has selected |
| var source = d3.select(this.parentNode).classed('selected', true); |
| |
| // mark this component as dragging and selected |
| d3.select(this).classed('dragging', true); |
| |
| // mark the source of the drag |
| var sourceData = source.datum(); |
| |
| // start the drag line and insert it first to keep it on the bottom |
| var position = d3.mouse(canvas.node()); |
| canvas.insert('path', ':first-child') |
| .datum({ |
| 'sourceId': sourceData.id, |
| 'sourceWidth': sourceData.dimensions.width, |
| 'x': position[0], |
| 'y': position[1] |
| }) |
| .attrs({ |
| 'class': 'connector', |
| 'd': function (pathDatum) { |
| return 'M' + pathDatum.x + ' ' + pathDatum.y + 'L' + pathDatum.x + ' ' + pathDatum.y; |
| } |
| }); |
| |
| // updates the location of the connection img |
| d3.select(this).attr('transform', function () { |
| return 'translate(' + position[0] + ', ' + (position[1] + 20) + ')'; |
| }); |
| |
| // re-append the image to keep it on top |
| canvas.node().appendChild(this); |
| }) |
| .on('drag', function (d) { |
| // updates the location of the connection img |
| d3.select(this).attr('transform', function () { |
| return 'translate(' + d3.event.x + ', ' + (d3.event.y + 50) + ')'; |
| }); |
| |
| // mark node's connectable if supported |
| var destination = d3.select('g.hover').classed('connectable-destination', function () { |
| // ensure the mouse has moved at least 10px in any direction, it seems that |
| // when the drag event is trigger is not consistent between browsers. as a result |
| // some browser would trigger when the mouse hadn't moved yet which caused |
| // click and contextmenu events to appear like an attempt to connection the |
| // component to itself. requiring the mouse to have actually moved before |
| // checking the eligiblity of the destination addresses the issue |
| return (Math.abs(origin[0] - d3.event.x) > 10 || Math.abs(origin[1] - d3.event.y) > 10) && |
| nfCanvasUtils.isValidConnectionDestination(d3.select(this)); |
| }); |
| |
| // update the drag line |
| d3.select('path.connector').classed('connectable', function () { |
| if (destination.empty()) { |
| return false; |
| } |
| |
| // if there is a potential destination, see if its connectable |
| return destination.classed('connectable-destination'); |
| }).attr('d', function (pathDatum) { |
| if (!destination.empty() && destination.classed('connectable-destination')) { |
| var destinationData = destination.datum(); |
| |
| // show the line preview as appropriate |
| if (pathDatum.sourceId === destinationData.id) { |
| var x = pathDatum.x; |
| var y = pathDatum.y; |
| var componentOffset = pathDatum.sourceWidth / 2; |
| var xOffset = nfConnection.config.selfLoopXOffset; |
| var yOffset = nfConnection.config.selfLoopYOffset; |
| return 'M' + x + ' ' + y + 'L' + (x + componentOffset + xOffset) + ' ' + (y - yOffset) + 'L' + (x + componentOffset + xOffset) + ' ' + (y + yOffset) + 'Z'; |
| } else { |
| // get the position on the destination perimeter |
| var end = nfCanvasUtils.getPerimeterPoint(pathDatum, { |
| 'x': destinationData.position.x, |
| 'y': destinationData.position.y, |
| 'width': destinationData.dimensions.width, |
| 'height': destinationData.dimensions.height |
| }); |
| |
| // direct line between components to provide a 'snap feel' |
| return 'M' + pathDatum.x + ' ' + pathDatum.y + 'L' + end.x + ' ' + end.y; |
| } |
| } else { |
| return 'M' + pathDatum.x + ' ' + pathDatum.y + 'L' + d3.event.x + ' ' + d3.event.y; |
| } |
| }); |
| }) |
| .on('end', function (d) { |
| // stop further propagation |
| d3.event.sourceEvent.stopPropagation(); |
| |
| // get the add connect img |
| var addConnect = d3.select(this); |
| |
| // get the connector, if it the current point is not over a new destination |
| // the connector will be removed. otherwise it will be removed after the |
| // connection has been configured/cancelled |
| var connector = d3.select('path.connector'); |
| var connectorData = connector.datum(); |
| |
| // get the destination |
| var destination = d3.select('g.connectable-destination'); |
| |
| // we are not over a new destination |
| if (destination.empty()) { |
| // get the source to determine if we are still over it |
| var source = d3.select('#id-' + connectorData.sourceId); |
| var sourceData = source.datum(); |
| |
| // get the mouse position relative to the source |
| var position = d3.mouse(source.node()); |
| |
| // if the position is outside the component, remove the add connect img |
| if (position[0] < 0 || position[0] > sourceData.dimensions.width || position[1] < 0 || position[1] > sourceData.dimensions.height) { |
| addConnect.remove(); |
| } else { |
| // reset the add connect img by restoring the position and place in the DOM |
| addConnect.classed('dragging', false).attr('transform', function () { |
| return 'translate(' + d.origX + ', ' + d.origY + ')'; |
| }); |
| source.node().appendChild(this); |
| } |
| |
| // remove the connector |
| connector.remove(); |
| } else { |
| // remove the add connect img |
| addConnect.remove(); |
| |
| // create the connection |
| var destinationData = destination.datum(); |
| |
| $.ajax({ |
| type: 'GET', |
| url: config.urls.api + '/process-groups/' + encodeURIComponent(destinationData.component.parentGroupId), |
| dataType: 'json' |
| }).done(function (response) { |
| var defaultSettings = { |
| flowfileExpiration: response.component.defaultFlowFileExpiration, |
| objectThreshold: response.component.defaultBackPressureObjectThreshold, |
| dataSizeThreshold: response.component.defaultBackPressureDataSizeThreshold, |
| }; |
| nfConnectionConfiguration.createConnection(connectorData.sourceId, destinationData.id, defaultSettings); |
| }); |
| } |
| }); |
| }, |
| |
| /** |
| * Activates the connect behavior for the components in the specified selection. |
| * |
| * @param {selection} components |
| */ |
| activate: function (components) { |
| components |
| .classed('connectable', true) |
| .on('mouseenter.connectable', function (d) { |
| if (allowConnection()) { |
| var selection = d3.select(this); |
| |
| // ensure the current component supports connection source |
| if (nfCanvasUtils.isValidConnectionSource(selection)) { |
| // see if theres already a connector rendered |
| var addConnect = d3.select('text.add-connect'); |
| if (addConnect.empty()) { |
| var x = (d.dimensions.width / 2) - 14; |
| var y = (d.dimensions.height / 2) + 14; |
| |
| selection.append('text') |
| .datum({ |
| origX: x, |
| origY: y |
| }) |
| .call(connect) |
| .attrs({ |
| 'class': 'add-connect', |
| 'transform': 'translate(' + x + ', ' + y + ')' |
| }) |
| .text('\ue834'); |
| } |
| } |
| } |
| }) |
| .on('mouseleave.connectable', function () { |
| // conditionally remove the connector |
| var addConnect = d3.select(this).select('text.add-connect'); |
| if (!addConnect.empty() && !addConnect.classed('dragging')) { |
| addConnect.remove(); |
| } |
| }) |
| // Using mouseover/out to workaround chrome issue #122746 |
| .on('mouseover.connectable', function () { |
| // mark that we are hovering when appropriate |
| d3.select(this).classed('hover', function () { |
| return allowConnection(); |
| }); |
| }) |
| .on('mouseout.connection', function () { |
| // remove all hover related classes |
| d3.select(this).classed('hover connectable-destination', false); |
| }); |
| }, |
| |
| /** |
| * Deactivates the connect behavior for the components in the specified selection. |
| * |
| * @param {selection} components |
| */ |
| deactivate: function (components) { |
| components |
| .classed('connectable', false) |
| .on('mouseenter.connectable', null) |
| .on('mouseleave.connectable', null) |
| .on('mouseover.connectable', null) |
| .on('mouseout.connectable', null); |
| } |
| }; |
| })); |