UNPKG

@awearsolutions/node-red

Version:

A visual tool for wiring the Internet of Things

1,258 lines (1,170 loc) 124 kB
/** * Copyright JS Foundation and other contributors, http://js.foundation * * Licensed 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. **/ RED.view = (function() { var space_width = 5000, space_height = 5000, lineCurveScale = 0.75, scaleFactor = 1, node_width = 100, node_height = 30; var touchLongPressTimeout = 1000, startTouchDistance = 0, startTouchCenter = [], moveTouchCenter = [], touchStartTime = 0; var workspaceScrollPositions = {}; var gridSize = 20; var snapGrid = false; var activeSpliceLink; var spliceActive = false; var spliceTimer; var activeSubflow = null; var activeNodes = []; var activeLinks = []; var activeFlowLinks = []; var selected_link = null, mousedown_link = null, mousedown_node = null, mousedown_port_type = null, mousedown_port_index = 0, mouseup_node = null, mouse_offset = [0,0], mouse_position = null, mouse_mode = 0, moving_set = [], lasso = null, showStatus = false, lastClickNode = null, dblClickPrimed = null, clickTime = 0, clickElapsed = 0; var clipboard = ""; var status_colours = { "red": "#c00", "green": "#5a8", "yellow": "#F9DF31", "blue": "#53A3F3", "grey": "#d3d3d3" } var PORT_TYPE_INPUT = 1; var PORT_TYPE_OUTPUT = 0; var outer = d3.select("#chart") .append("svg:svg") .attr("width", space_width) .attr("height", space_height) .attr("pointer-events", "all") .style("cursor","crosshair") .on("mousedown", function() { focusView(); }); var vis = outer .append("svg:g") .on("dblclick.zoom", null) .append("svg:g") .attr('class','innerCanvas') .on("mousemove", canvasMouseMove) .on("mousedown", canvasMouseDown) .on("mouseup", canvasMouseUp) .on("touchend", function() { clearTimeout(touchStartTime); touchStartTime = null; if (RED.touch.radialMenu.active()) { return; } if (lasso) { outer_background.attr("fill","#fff"); } canvasMouseUp.call(this); }) .on("touchcancel", canvasMouseUp) .on("touchstart", function() { var touch0; if (d3.event.touches.length>1) { clearTimeout(touchStartTime); touchStartTime = null; d3.event.preventDefault(); touch0 = d3.event.touches.item(0); var touch1 = d3.event.touches.item(1); var a = touch0["pageY"]-touch1["pageY"]; var b = touch0["pageX"]-touch1["pageX"]; var offset = $("#chart").offset(); var scrollPos = [$("#chart").scrollLeft(),$("#chart").scrollTop()]; startTouchCenter = [ (touch1["pageX"]+(b/2)-offset.left+scrollPos[0])/scaleFactor, (touch1["pageY"]+(a/2)-offset.top+scrollPos[1])/scaleFactor ]; moveTouchCenter = [ touch1["pageX"]+(b/2), touch1["pageY"]+(a/2) ] startTouchDistance = Math.sqrt((a*a)+(b*b)); } else { var obj = d3.select(document.body); touch0 = d3.event.touches.item(0); var pos = [touch0.pageX,touch0.pageY]; startTouchCenter = [touch0.pageX,touch0.pageY]; startTouchDistance = 0; var point = d3.touches(this)[0]; touchStartTime = setTimeout(function() { touchStartTime = null; showTouchMenu(obj,pos); //lasso = vis.append("rect") // .attr("ox",point[0]) // .attr("oy",point[1]) // .attr("rx",2) // .attr("ry",2) // .attr("x",point[0]) // .attr("y",point[1]) // .attr("width",0) // .attr("height",0) // .attr("class","lasso"); //outer_background.attr("fill","#e3e3f3"); },touchLongPressTimeout); } }) .on("touchmove", function(){ if (RED.touch.radialMenu.active()) { d3.event.preventDefault(); return; } var touch0; if (d3.event.touches.length<2) { if (touchStartTime) { touch0 = d3.event.touches.item(0); var dx = (touch0.pageX-startTouchCenter[0]); var dy = (touch0.pageY-startTouchCenter[1]); var d = Math.abs(dx*dx+dy*dy); if (d > 64) { clearTimeout(touchStartTime); touchStartTime = null; } } else if (lasso) { d3.event.preventDefault(); } canvasMouseMove.call(this); } else { touch0 = d3.event.touches.item(0); var touch1 = d3.event.touches.item(1); var a = touch0["pageY"]-touch1["pageY"]; var b = touch0["pageX"]-touch1["pageX"]; var offset = $("#chart").offset(); var scrollPos = [$("#chart").scrollLeft(),$("#chart").scrollTop()]; var moveTouchDistance = Math.sqrt((a*a)+(b*b)); var touchCenter = [ touch1["pageX"]+(b/2), touch1["pageY"]+(a/2) ]; if (!isNaN(moveTouchDistance)) { oldScaleFactor = scaleFactor; scaleFactor = Math.min(2,Math.max(0.3, scaleFactor + (Math.floor(((moveTouchDistance*100)-(startTouchDistance*100)))/10000))); var deltaTouchCenter = [ // Try to pan whilst zooming - not 100% startTouchCenter[0]*(scaleFactor-oldScaleFactor),//-(touchCenter[0]-moveTouchCenter[0]), startTouchCenter[1]*(scaleFactor-oldScaleFactor) //-(touchCenter[1]-moveTouchCenter[1]) ]; startTouchDistance = moveTouchDistance; moveTouchCenter = touchCenter; $("#chart").scrollLeft(scrollPos[0]+deltaTouchCenter[0]); $("#chart").scrollTop(scrollPos[1]+deltaTouchCenter[1]); redraw(); } } }); var outer_background = vis.append("svg:rect") .attr("width", space_width) .attr("height", space_height) .attr("fill","#fff"); var grid = vis.append("g"); updateGrid(); function updateGrid() { var gridTicks = []; for (var i=0;i<space_width;i+=+gridSize) { gridTicks.push(i); } grid.selectAll("line.horizontal").remove(); grid.selectAll("line.horizontal").data(gridTicks).enter() .append("line") .attr( { "class":"horizontal", "x1" : 0, "x2" : space_width, "y1" : function(d){ return d;}, "y2" : function(d){ return d;}, "fill" : "none", "shape-rendering" : "crispEdges", "stroke" : "#eee", "stroke-width" : "1px" }); grid.selectAll("line.vertical").remove(); grid.selectAll("line.vertical").data(gridTicks).enter() .append("line") .attr( { "class":"vertical", "y1" : 0, "y2" : space_width, "x1" : function(d){ return d;}, "x2" : function(d){ return d;}, "fill" : "none", "shape-rendering" : "crispEdges", "stroke" : "#eee", "stroke-width" : "1px" }); } var dragGroup = vis.append("g"); var drag_lines = []; function showDragLines(nodes) { for (var i=0;i<nodes.length;i++) { var node = nodes[i]; node.el = dragGroup.append("svg:path").attr("class", "drag_line"); drag_lines.push(node); } } function hideDragLines() { while(drag_lines.length) { var line = drag_lines.pop(); if (line.el) { line.el.remove(); } } } function updateActiveNodes() { var activeWorkspace = RED.workspaces.active(); activeNodes = RED.nodes.filterNodes({z:activeWorkspace}); activeLinks = RED.nodes.filterLinks({ source:{z:activeWorkspace}, target:{z:activeWorkspace} }); } function init() { RED.events.on("workspace:change",function(event) { var chart = $("#chart"); if (event.old !== 0) { workspaceScrollPositions[event.old] = { left:chart.scrollLeft(), top:chart.scrollTop() }; } var scrollStartLeft = chart.scrollLeft(); var scrollStartTop = chart.scrollTop(); activeSubflow = RED.nodes.subflow(event.workspace); RED.menu.setDisabled("menu-item-workspace-edit", activeSubflow); RED.menu.setDisabled("menu-item-workspace-delete",RED.workspaces.count() == 1 || activeSubflow); if (workspaceScrollPositions[event.workspace]) { chart.scrollLeft(workspaceScrollPositions[event.workspace].left); chart.scrollTop(workspaceScrollPositions[event.workspace].top); } else { chart.scrollLeft(0); chart.scrollTop(0); } var scrollDeltaLeft = chart.scrollLeft() - scrollStartLeft; var scrollDeltaTop = chart.scrollTop() - scrollStartTop; if (mouse_position != null) { mouse_position[0] += scrollDeltaLeft; mouse_position[1] += scrollDeltaTop; } clearSelection(); RED.nodes.eachNode(function(n) { n.dirty = true; }); updateSelection(); updateActiveNodes(); redraw(); }); $("#btn-zoom-out").click(function() {zoomOut();}); $("#btn-zoom-zero").click(function() {zoomZero();}); $("#btn-zoom-in").click(function() {zoomIn();}); $("#chart").on("DOMMouseScroll mousewheel", function (evt) { if ( evt.altKey ) { evt.preventDefault(); evt.stopPropagation(); var move = -(evt.originalEvent.detail) || evt.originalEvent.wheelDelta; if (move <= 0) { zoomOut(); } else { zoomIn(); } } }); // Handle nodes dragged from the palette $("#chart").droppable({ accept:".palette_node", drop: function( event, ui ) { d3.event = event; var selected_tool = ui.draggable[0].type; var result = addNode(selected_tool); if (!result) { return; } var historyEvent = result.historyEvent; var nn = result.node; var helperOffset = d3.touches(ui.helper.get(0))[0]||d3.mouse(ui.helper.get(0)); var mousePos = d3.touches(this)[0]||d3.mouse(this); mousePos[1] += this.scrollTop + ((nn.h/2)-helperOffset[1]); mousePos[0] += this.scrollLeft + ((nn.w/2)-helperOffset[0]); mousePos[1] /= scaleFactor; mousePos[0] /= scaleFactor; if (snapGrid) { mousePos[0] = gridSize*(Math.ceil(mousePos[0]/gridSize)); mousePos[1] = gridSize*(Math.ceil(mousePos[1]/gridSize)); } nn.x = mousePos[0]; nn.y = mousePos[1]; var spliceLink = $(ui.helper).data("splice"); if (spliceLink) { // TODO: DRY - droppable/nodeMouseDown/canvasMouseUp RED.nodes.removeLink(spliceLink); var link1 = { source:spliceLink.source, sourcePort:spliceLink.sourcePort, target: nn }; var link2 = { source:nn, sourcePort:0, target: spliceLink.target }; RED.nodes.addLink(link1); RED.nodes.addLink(link2); historyEvent.links = [link1,link2]; historyEvent.removedLinks = [spliceLink]; } RED.history.push(historyEvent); RED.nodes.add(nn); RED.editor.validateNode(nn); RED.nodes.dirty(true); // auto select dropped node - so info shows (if visible) clearSelection(); nn.selected = true; moving_set.push({n:nn}); updateActiveNodes(); updateSelection(); redraw(); if (nn._def.autoedit) { RED.editor.edit(nn); } } }); $("#chart").focus(function() { $("#workspace-tabs").addClass("workspace-focussed") }); $("#chart").blur(function() { $("#workspace-tabs").removeClass("workspace-focussed") }); RED.actions.add("core:copy-selection-to-internal-clipboard",copySelection); RED.actions.add("core:cut-selection-to-internal-clipboard",function(){copySelection();deleteSelection();}); RED.actions.add("core:paste-from-internal-clipboard",function(){importNodes(clipboard);}); RED.actions.add("core:delete-selection",deleteSelection); RED.actions.add("core:edit-selected-node",editSelection); RED.actions.add("core:undo",RED.history.pop); RED.actions.add("core:select-all-nodes",selectAll); RED.actions.add("core:zoom-in",zoomIn); RED.actions.add("core:zoom-out",zoomOut); RED.actions.add("core:zoom-reset",zoomZero); RED.actions.add("core:toggle-show-grid",function(state) { if (state === undefined) { RED.userSettings.toggle("view-show-grid"); } else { toggleShowGrid(state); } }); RED.actions.add("core:toggle-snap-grid",function(state) { if (state === undefined) { RED.userSettings.toggle("view-snap-grid"); } else { toggleSnapGrid(state); } }); RED.actions.add("core:toggle-status",function(state) { if (state === undefined) { RED.userSettings.toggle("view-node-status"); } else { toggleStatus(state); } }); RED.actions.add("core:move-selection-up", function() { moveSelection(0,-1);}); RED.actions.add("core:step-selection-up", function() { moveSelection(0,-20);}); RED.actions.add("core:move-selection-right", function() { moveSelection(1,0);}); RED.actions.add("core:step-selection-right", function() { moveSelection(20,0);}); RED.actions.add("core:move-selection-down", function() { moveSelection(0,1);}); RED.actions.add("core:step-selection-down", function() { moveSelection(0,20);}); RED.actions.add("core:move-selection-left", function() { moveSelection(-1,0);}); RED.actions.add("core:step-selection-left", function() { moveSelection(-20,0);}); } function addNode(type,x,y) { var m = /^subflow:(.+)$/.exec(type); if (activeSubflow && m) { var subflowId = m[1]; if (subflowId === activeSubflow.id) { RED.notify(RED._("notification.error",{message: RED._("notification.errors.cannotAddSubflowToItself")}),"error"); return; } if (RED.nodes.subflowContains(m[1],activeSubflow.id)) { RED.notify(RED._("notification.error",{message: RED._("notification.errors.cannotAddCircularReference")}),"error"); return; } } var nn = { id:RED.nodes.id(),z:RED.workspaces.active()}; nn.type = type; nn._def = RED.nodes.getType(nn.type); if (!m) { nn.inputs = nn._def.inputs || 0; nn.outputs = nn._def.outputs; for (var d in nn._def.defaults) { if (nn._def.defaults.hasOwnProperty(d)) { if (nn._def.defaults[d].value !== undefined) { nn[d] = JSON.parse(JSON.stringify(nn._def.defaults[d].value)); } } } if (nn._def.onadd) { try { nn._def.onadd.call(nn); } catch(err) { console.log("Definition error: "+nn.type+".onadd:",err); } } } else { var subflow = RED.nodes.subflow(m[1]); nn.inputs = subflow.in.length; nn.outputs = subflow.out.length; } nn.changed = true; nn.moved = true; nn.w = node_width; nn.h = Math.max(node_height,(nn.outputs||0) * 15); var historyEvent = { t:"add", nodes:[nn.id], dirty:RED.nodes.dirty() } if (activeSubflow) { var subflowRefresh = RED.subflow.refresh(true); if (subflowRefresh) { historyEvent.subflow = { id:activeSubflow.id, changed: activeSubflow.changed, instances: subflowRefresh.instances } } } return { node: nn, historyEvent: historyEvent } } function canvasMouseDown() { var point; if (!mousedown_node && !mousedown_link) { selected_link = null; updateSelection(); } if (mouse_mode === 0) { if (lasso) { lasso.remove(); lasso = null; } } if (mouse_mode === 0 || mouse_mode === RED.state.QUICK_JOINING) { if (d3.event.metaKey || d3.event.ctrlKey) { point = d3.mouse(this); d3.event.stopPropagation(); var mainPos = $("#main-container").position(); if (mouse_mode !== RED.state.QUICK_JOINING) { mouse_mode = RED.state.QUICK_JOINING; $(window).on('keyup',disableQuickJoinEventHandler); } RED.typeSearch.show({ x:d3.event.clientX-mainPos.left-node_width/2, y:d3.event.clientY-mainPos.top-node_height/2, add: function(type) { var result = addNode(type); if (!result) { return; } var nn = result.node; var historyEvent = result.historyEvent; nn.x = point[0]; nn.y = point[1]; if (mouse_mode === RED.state.QUICK_JOINING) { if (drag_lines.length > 0) { var drag_line = drag_lines[0]; var src = null,dst,src_port; if (drag_line.portType === PORT_TYPE_OUTPUT && nn.inputs > 0) { src = drag_line.node; src_port = drag_line.port; dst = nn; } else if (drag_line.portType === PORT_TYPE_INPUT && nn.outputs > 0) { src = nn; dst = drag_line.node; src_port = 0; } if (src !== null) { var link = {source: src, sourcePort:src_port, target: dst}; RED.nodes.addLink(link); historyEvent.links = [link]; hideDragLines(); if (drag_line.portType === PORT_TYPE_OUTPUT && nn.outputs > 0) { showDragLines([{node:nn,port:0,portType:PORT_TYPE_OUTPUT}]); } else if (drag_line.portType === PORT_TYPE_INPUT && nn.inputs > 0) { showDragLines([{node:nn,port:0,portType:PORT_TYPE_INPUT}]); } else { resetMouseVars(); } } else { hideDragLines(); resetMouseVars(); } } else { if (nn.outputs > 0) { showDragLines([{node:nn,port:0,portType:PORT_TYPE_OUTPUT}]); } else if (nn.inputs > 0) { showDragLines([{node:nn,port:0,portType:PORT_TYPE_INPUT}]); } else { resetMouseVars(); } } } RED.history.push(historyEvent); RED.nodes.add(nn); RED.editor.validateNode(nn); RED.nodes.dirty(true); // auto select dropped node - so info shows (if visible) clearSelection(); nn.selected = true; moving_set.push({n:nn}); updateActiveNodes(); updateSelection(); redraw(); } }); updateActiveNodes(); updateSelection(); redraw(); } } if (mouse_mode === 0 && !(d3.event.metaKey || d3.event.ctrlKey)) { if (!touchStartTime) { point = d3.mouse(this); lasso = vis.append("rect") .attr("ox",point[0]) .attr("oy",point[1]) .attr("rx",1) .attr("ry",1) .attr("x",point[0]) .attr("y",point[1]) .attr("width",0) .attr("height",0) .attr("class","lasso"); d3.event.preventDefault(); } } } function canvasMouseMove() { var i; var node; mouse_position = d3.touches(this)[0]||d3.mouse(this); // Prevent touch scrolling... //if (d3.touches(this)[0]) { // d3.event.preventDefault(); //} // TODO: auto scroll the container //var point = d3.mouse(this); //if (point[0]-container.scrollLeft < 30 && container.scrollLeft > 0) { container.scrollLeft -= 15; } //console.log(d3.mouse(this),container.offsetWidth,container.offsetHeight,container.scrollLeft,container.scrollTop); if (lasso) { var ox = parseInt(lasso.attr("ox")); var oy = parseInt(lasso.attr("oy")); var x = parseInt(lasso.attr("x")); var y = parseInt(lasso.attr("y")); var w; var h; if (mouse_position[0] < ox) { x = mouse_position[0]; w = ox-x; } else { w = mouse_position[0]-x; } if (mouse_position[1] < oy) { y = mouse_position[1]; h = oy-y; } else { h = mouse_position[1]-y; } lasso .attr("x",x) .attr("y",y) .attr("width",w) .attr("height",h) ; return; } if (mouse_mode != RED.state.QUICK_JOINING && mouse_mode != RED.state.IMPORT_DRAGGING && !mousedown_node && selected_link == null) { return; } var mousePos; if (mouse_mode == RED.state.JOINING || mouse_mode === RED.state.QUICK_JOINING) { // update drag line if (drag_lines.length === 0) { if (d3.event.shiftKey) { // Get all the wires we need to detach. var links = []; var existingLinks = []; if (selected_link && ((mousedown_port_type === PORT_TYPE_OUTPUT && selected_link.source === mousedown_node && selected_link.sourcePort === mousedown_port_index ) || (mousedown_port_type === PORT_TYPE_INPUT && selected_link.target === mousedown_node )) ) { existingLinks = [selected_link]; } else { var filter; if (mousedown_port_type === PORT_TYPE_OUTPUT) { filter = { source:mousedown_node, sourcePort: mousedown_port_index } } else { filter = { target: mousedown_node } } existingLinks = RED.nodes.filterLinks(filter); } for (i=0;i<existingLinks.length;i++) { var link = existingLinks[i]; RED.nodes.removeLink(link); links.push({ link:link, node: (mousedown_port_type===PORT_TYPE_OUTPUT)?link.target:link.source, port: (mousedown_port_type===PORT_TYPE_OUTPUT)?0:link.sourcePort, portType: (mousedown_port_type===PORT_TYPE_OUTPUT)?PORT_TYPE_INPUT:PORT_TYPE_OUTPUT }) } if (links.length === 0) { resetMouseVars(); redraw(); } else { showDragLines(links); mouse_mode = 0; updateActiveNodes(); redraw(); mouse_mode = RED.state.JOINING; } } else if (mousedown_node) { showDragLines([{node:mousedown_node,port:mousedown_port_index,portType:mousedown_port_type}]); } selected_link = null; } mousePos = mouse_position; for (i=0;i<drag_lines.length;i++) { var drag_line = drag_lines[i]; var numOutputs = (drag_line.portType === PORT_TYPE_OUTPUT)?(drag_line.node.outputs || 1):1; var sourcePort = drag_line.port; var portY = -((numOutputs-1)/2)*13 +13*sourcePort; var sc = (drag_line.portType === PORT_TYPE_OUTPUT)?1:-1; var dy = mousePos[1]-(drag_line.node.y+portY); var dx = mousePos[0]-(drag_line.node.x+sc*drag_line.node.w/2); var delta = Math.sqrt(dy*dy+dx*dx); var scale = lineCurveScale; var scaleY = 0; if (delta < node_width) { scale = 0.75-0.75*((node_width-delta)/node_width); } if (dx*sc < 0) { scale += 2*(Math.min(5*node_width,Math.abs(dx))/(5*node_width)); if (Math.abs(dy) < 3*node_height) { scaleY = ((dy>0)?0.5:-0.5)*(((3*node_height)-Math.abs(dy))/(3*node_height))*(Math.min(node_width,Math.abs(dx))/(node_width)) ; } } drag_line.el.attr("d", "M "+(drag_line.node.x+sc*drag_line.node.w/2)+" "+(drag_line.node.y+portY)+ " C "+(drag_line.node.x+sc*(drag_line.node.w/2+node_width*scale))+" "+(drag_line.node.y+portY+scaleY*node_height)+" "+ (mousePos[0]-sc*(scale)*node_width)+" "+(mousePos[1]-scaleY*node_height)+" "+ mousePos[0]+" "+mousePos[1] ); } d3.event.preventDefault(); } else if (mouse_mode == RED.state.MOVING) { mousePos = d3.mouse(document.body); if (isNaN(mousePos[0])) { mousePos = d3.touches(document.body)[0]; } var d = (mouse_offset[0]-mousePos[0])*(mouse_offset[0]-mousePos[0]) + (mouse_offset[1]-mousePos[1])*(mouse_offset[1]-mousePos[1]); if (d > 3) { mouse_mode = RED.state.MOVING_ACTIVE; clickElapsed = 0; spliceActive = false; if (moving_set.length === 1) { node = moving_set[0]; spliceActive = node.n.hasOwnProperty("_def") && node.n._def.inputs > 0 && node.n._def.outputs > 0 && RED.nodes.filterLinks({ source: node.n }).length === 0 && RED.nodes.filterLinks({ target: node.n }).length === 0; } } } else if (mouse_mode == RED.state.MOVING_ACTIVE || mouse_mode == RED.state.IMPORT_DRAGGING) { mousePos = mouse_position; var minX = 0; var minY = 0; var maxX = space_width; var maxY = space_height; for (var n = 0; n<moving_set.length; n++) { node = moving_set[n]; if (d3.event.shiftKey) { node.n.ox = node.n.x; node.n.oy = node.n.y; } node.n.x = mousePos[0]+node.dx; node.n.y = mousePos[1]+node.dy; node.n.dirty = true; minX = Math.min(node.n.x-node.n.w/2-5,minX); minY = Math.min(node.n.y-node.n.h/2-5,minY); maxX = Math.max(node.n.x+node.n.w/2+5,maxX); maxY = Math.max(node.n.y+node.n.h/2+5,maxY); } if (minX !== 0 || minY !== 0) { for (i = 0; i<moving_set.length; i++) { node = moving_set[i]; node.n.x -= minX; node.n.y -= minY; } } if (maxX !== space_width || maxY !== space_height) { for (i = 0; i<moving_set.length; i++) { node = moving_set[i]; node.n.x -= (maxX - space_width); node.n.y -= (maxY - space_height); } } if (snapGrid != d3.event.shiftKey && moving_set.length > 0) { var gridOffset = [0,0]; node = moving_set[0]; gridOffset[0] = node.n.x-(gridSize*Math.floor((node.n.x-node.n.w/2)/gridSize)+node.n.w/2); gridOffset[1] = node.n.y-(gridSize*Math.floor(node.n.y/gridSize)); if (gridOffset[0] !== 0 || gridOffset[1] !== 0) { for (i = 0; i<moving_set.length; i++) { node = moving_set[i]; node.n.x -= gridOffset[0]; node.n.y -= gridOffset[1]; if (node.n.x == node.n.ox && node.n.y == node.n.oy) { node.dirty = false; } } } } if ((mouse_mode == RED.state.MOVING_ACTIVE || mouse_mode == RED.state.IMPORT_DRAGGING) && moving_set.length === 1) { node = moving_set[0]; if (spliceActive) { if (!spliceTimer) { spliceTimer = setTimeout(function() { var nodes = []; var bestDistance = Infinity; var bestLink = null; var mouseX = node.n.x; var mouseY = node.n.y; if (outer[0][0].getIntersectionList) { var svgRect = outer[0][0].createSVGRect(); svgRect.x = mouseX; svgRect.y = mouseY; svgRect.width = 1; svgRect.height = 1; nodes = outer[0][0].getIntersectionList(svgRect, outer[0][0]); } else { // Firefox doesn"t do getIntersectionList and that // makes us sad nodes = RED.view.getLinksAtPoint(mouseX,mouseY); } for (var i=0;i<nodes.length;i++) { if (d3.select(nodes[i]).classed("link_background")) { var length = nodes[i].getTotalLength(); for (var j=0;j<length;j+=10) { var p = nodes[i].getPointAtLength(j); var d2 = ((p.x-mouseX)*(p.x-mouseX))+((p.y-mouseY)*(p.y-mouseY)); if (d2 < 200 && d2 < bestDistance) { bestDistance = d2; bestLink = nodes[i]; } } } } if (activeSpliceLink && activeSpliceLink !== bestLink) { d3.select(activeSpliceLink.parentNode).classed("link_splice",false); } if (bestLink) { d3.select(bestLink.parentNode).classed("link_splice",true) } else { d3.select(".link_splice").classed("link_splice",false); } activeSpliceLink = bestLink; spliceTimer = null; },100); } } } } if (mouse_mode !== 0) { redraw(); } } function canvasMouseUp() { var i; var historyEvent; if (mouse_mode === RED.state.QUICK_JOINING) { return; } if (mousedown_node && mouse_mode == RED.state.JOINING) { var removedLinks = []; for (i=0;i<drag_lines.length;i++) { if (drag_lines[i].link) { removedLinks.push(drag_lines[i].link) } } historyEvent = { t:"delete", links: removedLinks, dirty:RED.nodes.dirty() }; RED.history.push(historyEvent); hideDragLines(); } if (lasso) { var x = parseInt(lasso.attr("x")); var y = parseInt(lasso.attr("y")); var x2 = x+parseInt(lasso.attr("width")); var y2 = y+parseInt(lasso.attr("height")); if (!d3.event.ctrlKey) { clearSelection(); } RED.nodes.eachNode(function(n) { if (n.z == RED.workspaces.active() && !n.selected) { n.selected = (n.x > x && n.x < x2 && n.y > y && n.y < y2); if (n.selected) { n.dirty = true; moving_set.push({n:n}); } } }); if (activeSubflow) { activeSubflow.in.forEach(function(n) { n.selected = (n.x > x && n.x < x2 && n.y > y && n.y < y2); if (n.selected) { n.dirty = true; moving_set.push({n:n}); } }); activeSubflow.out.forEach(function(n) { n.selected = (n.x > x && n.x < x2 && n.y > y && n.y < y2); if (n.selected) { n.dirty = true; moving_set.push({n:n}); } }); } updateSelection(); lasso.remove(); lasso = null; } else if (mouse_mode == RED.state.DEFAULT && mousedown_link == null && !d3.event.ctrlKey&& !d3.event.metaKey ) { clearSelection(); updateSelection(); } if (mouse_mode == RED.state.MOVING_ACTIVE) { if (moving_set.length > 0) { var ns = []; for (var j=0;j<moving_set.length;j++) { var n = moving_set[j]; if (n.ox !== n.n.x || n.oy !== n.n.y) { ns.push({n:n.n,ox:n.ox,oy:n.oy,moved:n.n.moved}); n.n.dirty = true; n.n.moved = true; } } if (ns.length > 0) { historyEvent = {t:"move",nodes:ns,dirty:RED.nodes.dirty()}; if (activeSpliceLink) { // TODO: DRY - droppable/nodeMouseDown/canvasMouseUp var spliceLink = d3.select(activeSpliceLink).data()[0]; RED.nodes.removeLink(spliceLink); var link1 = { source:spliceLink.source, sourcePort:spliceLink.sourcePort, target: moving_set[0].n }; var link2 = { source:moving_set[0].n, sourcePort:0, target: spliceLink.target }; RED.nodes.addLink(link1); RED.nodes.addLink(link2); historyEvent.links = [link1,link2]; historyEvent.removedLinks = [spliceLink]; updateActiveNodes(); } RED.nodes.dirty(true); RED.history.push(historyEvent); } } } if (mouse_mode == RED.state.MOVING || mouse_mode == RED.state.MOVING_ACTIVE) { for (i=0;i<moving_set.length;i++) { delete moving_set[i].ox; delete moving_set[i].oy; } } if (mouse_mode == RED.state.IMPORT_DRAGGING) { RED.keyboard.remove("escape"); updateActiveNodes(); RED.nodes.dirty(true); } resetMouseVars(); redraw(); } function zoomIn() { if (scaleFactor < 2) { scaleFactor += 0.1; redraw(); } } function zoomOut() { if (scaleFactor > 0.3) { scaleFactor -= 0.1; redraw(); } } function zoomZero() { scaleFactor = 1; redraw(); } function selectAll() { RED.nodes.eachNode(function(n) { if (n.z == RED.workspaces.active()) { if (!n.selected) { n.selected = true; n.dirty = true; moving_set.push({n:n}); } } }); if (activeSubflow) { activeSubflow.in.forEach(function(n) { if (!n.selected) { n.selected = true; n.dirty = true; moving_set.push({n:n}); } }); activeSubflow.out.forEach(function(n) { if (!n.selected) { n.selected = true; n.dirty = true; moving_set.push({n:n}); } }); } selected_link = null; updateSelection(); redraw(); } function clearSelection() { for (var i=0;i<moving_set.length;i++) { var n = moving_set[i]; n.n.dirty = true; n.n.selected = false; } moving_set = []; selected_link = null; } var lastSelection = null; function updateSelection() { var selection = {}; if (moving_set.length > 0) { selection.nodes = moving_set.map(function(n) { return n.n;}); } if (selected_link != null) { selection.link = selected_link; } var activeWorkspace = RED.workspaces.active(); activeLinks = RED.nodes.filterLinks({ source:{z:activeWorkspace}, target:{z:activeWorkspace} }); var tabOrder = RED.nodes.getWorkspaceOrder(); var currentLinks = activeLinks; var addedLinkLinks = {}; activeFlowLinks = []; for (var i=0;i<moving_set.length;i++) { if (moving_set[i].n.type === "link out" || moving_set[i].n.type === "link in") { var linkNode = moving_set[i].n; var offFlowLinks = {}; linkNode.links.forEach(function(id) { var target = RED.nodes.node(id); if (target) { if (linkNode.type === "link out") { if (target.z === linkNode.z) { if (!addedLinkLinks[linkNode.id+":"+target.id]) { activeLinks.push({ source:linkNode, sourcePort:0, target: target, link: true }); addedLinkLinks[linkNode.id+":"+target.id] = true; } } else { offFlowLinks[target.z] = offFlowLinks[target.z]||[]; offFlowLinks[target.z].push(target); } } else { if (target.z === linkNode.z) { if (!addedLinkLinks[target.id+":"+linkNode.id]) { activeLinks.push({ source:target, sourcePort:0, target: linkNode, link: true }); addedLinkLinks[target.id+":"+linkNode.id] = true; } } else { offFlowLinks[target.z] = offFlowLinks[target.z]||[]; offFlowLinks[target.z].push(target); } } } }); var offFlows = Object.keys(offFlowLinks); // offFlows.sort(function(A,B) { // return tabOrder.indexOf(A) - tabOrder.indexOf(B); // }); if (offFlows.length > 0) { activeFlowLinks.push({ refresh: Math.floor(Math.random()*10000), node: linkNode, links: offFlowLinks//offFlows.map(function(i) { return {id:i,links:offFlowLinks[i]};}) }); } } } var selectionJSON = activeWorkspace+":"+JSON.stringify(selection,function(key,value) { if (key === 'nodes') { return value.map(function(n) { return n.id }) } else if (key === 'link') { return value.source.id+":"+value.sourcePort+":"+value.target.id; } return value; }); if (selectionJSON !== lastSelection) { lastSelection = selectionJSON; RED.events.emit("view:selection-changed",selection); } } function endKeyboardMove() { endMoveSet = false; if (moving_set.length > 0) { var ns = []; for (var i=0;i<moving_set.length;i++) { ns.push({n:moving_set[i].n,ox:moving_set[i].ox,oy:moving_set[i].oy,moved:moving_set[i].n.moved}); moving_set[i].n.moved = true; moving_set[i].n.dirty = true; delete moving_set[i].ox; delete moving_set[i].oy; } redraw(); RED.history.push({t:"move",nodes:ns,dirty:RED.nodes.dirty()}); RED.nodes.dirty(true); } } var endMoveSet = false; function moveSelection(dx,dy) { if (moving_set.length > 0) { if (!endMoveSet) { $(document).one('keyup',endKeyboardMove); endMoveSet = true; } var minX = 0; var minY = 0; var node; for (var i=0;i<moving_set.length;i++) { node = moving_set[i]; node.n.moved = true; node.n.dirty = true; if (node.ox == null && node.oy == null) { node.ox = node.n.x; node.oy = node.n.y; } node.n.x += dx; node.n.y += dy; node.n.dirty = true; minX = Math.min(node.n.x-node.n.w/2-5,minX); minY = Math.min(node.n.y-node.n.h/2-5,minY); } if (minX !== 0 || minY !== 0) { for (var n = 0; n<moving_set.length; n++) { node = moving_set[n]; node.n.x -= minX; node.n.y -= minY; } } redraw(); } } function editSelection() { if (moving_set.length > 0) { var node = moving_set[0].n; if (node.type === "subflow") { RED.editor.editSubflow(activeSubflow); } else { RED.editor.edit(node); } } } function deleteSelection() { if (moving_set.length > 0 || selected_link != null) { var result; var removedNodes = []; var removedLinks = []; var removedSubflowOutputs = []; var removedSubflowInputs = []; var subflowInstances = []; var startDirty = RED.nodes.dirty(); var startChanged = false; if (moving_set.length > 0) { for (var i=0;i<moving_set.length;i++) { var node = moving_set[i].n; node.selected = false; if (node.type != "subflow") { if (node.x < 0) { node.x = 25 } var removedEntities = RED.nodes.remove(node.id); removedNodes.push(node); removedNodes = removedNodes.concat(removedEntities.nodes); removedLinks = removedLinks.concat(removedEntities.links); } else { if (node.direction === "out") { removedSubflowOutputs.push(node); } else if (node.direction === "in") { removedSubflowInputs.push(node); } node.dirty = true; } } if (removedSubflowOutputs.length > 0) { result = RED.subflow.removeOutput(removedSubflowOutputs); if (result) { removedLinks = removedLinks.concat(result.links);