From c061487a1621361cb457ba2c519046556b4e02b1 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 10 Jun 2020 00:45:20 +0100 Subject: [PATCH] Massively reduce our dependency on d3 to render the view This is a slightly scary set of changes to be making. It overhauls how the view is rendered. Rather than use d3 for every single part of generating the view, we new use native DOM functions as much as possible. d3 is still used for the basic heavy lifting of working out what nodes/links etc need to be added/removed from the view. But once it comes to rendering them, d3 is side-lined as much as possible. There's room for further improvement. This change focusses on Nodes and Links. It has not touched groups, subflow-ports and link-nodes. --- .../@node-red/editor-client/src/js/history.js | 4 +- .../@node-red/editor-client/src/js/nodes.js | 6 - .../editor-client/src/js/polyfills.js | 8 + .../editor-client/src/js/ui/editor.js | 26 +- .../editor-client/src/js/ui/palette.js | 6 +- .../editor-client/src/js/ui/subflow.js | 3 - .../@node-red/editor-client/src/js/ui/view.js | 1044 +++++++++-------- 7 files changed, 556 insertions(+), 541 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/history.js b/packages/node_modules/@node-red/editor-client/src/js/history.js index 9fcfe30f9..895c659ef 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/history.js +++ b/packages/node_modules/@node-red/editor-client/src/js/history.js @@ -206,9 +206,6 @@ RED.history = (function() { RED.nodes.filterNodes({type:"subflow:"+subflow.id}).forEach(function(n) { n.inputs = subflow.in.length; n.outputs = subflow.out.length; - while (n.outputs > n.ports.length) { - n.ports.push(n.ports.length); - } n.resize = true; n.dirty = true; }); @@ -413,6 +410,7 @@ RED.history = (function() { } } } + ev.node.__outputs = inverseEv.changes.outputs; RED.editor.updateNodeProperties(ev.node,outputMap); RED.editor.validateNode(ev.node); } diff --git a/packages/node_modules/@node-red/editor-client/src/js/nodes.js b/packages/node_modules/@node-red/editor-client/src/js/nodes.js index 5ffb55527..f33d48007 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/nodes.js +++ b/packages/node_modules/@node-red/editor-client/src/js/nodes.js @@ -206,13 +206,7 @@ RED.nodes = (function() { if (n._def.category == "config") { configNodes[n.id] = n; } else { - n.ports = []; if (n.wires && (n.wires.length > n.outputs)) { n.outputs = n.wires.length; } - if (n.outputs) { - for (var i=0;i= node.outputs && removedLinks.indexOf(l) === -1) { removedLinks.push(l); } }); - } else if (node.outputs > node.ports.length) { - while (node.outputs > node.ports.length) { - node.ports.push(node.ports.length); - } } + delete node.__outputs; } node.inputs = Math.min(1,Math.max(0,parseInt(node.inputs))); if (isNaN(node.inputs)) { @@ -1505,6 +1501,10 @@ RED.editor = (function() { } }, open: function(tray, done) { + if (editing_node.hasOwnProperty('outputs')) { + editing_node.__outputs = editing_node.outputs; + } + var trayFooter = tray.find(".red-ui-tray-footer"); var trayBody = tray.find('.red-ui-tray-body'); trayBody.parent().css('overflow','hidden'); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js b/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js index 170ff149a..ecee89d70 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js @@ -105,7 +105,7 @@ RED.palette = (function() { for (var i=0;i= nodeWidth) { // break word if too wide for(var j = word.length; j > 0; j--) { var s = word.substring(0, j); - var width = RED.view.calculateTextWidth(s, "red-ui-palette-label", 0); + var width = RED.view.calculateTextWidth(s, "red-ui-palette-label"); if (width < nodeWidth) { displayLines.push(s); word = word.substring(j); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js b/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js index 8eda5f5b7..683d5b829 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js @@ -284,9 +284,6 @@ RED.subflow = (function() { } n.inputs = activeSubflow.in.length; n.outputs = activeSubflow.out.length; - while (n.outputs < n.ports.length) { - n.ports.pop(); - } n.resize = true; n.dirty = true; RED.editor.updateNodeProperties(n); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js index f40b139bc..5f411ce5b 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -758,7 +758,7 @@ RED.view = (function() { } function canvasMouseDown() { -if (RED.view.DEBUG) { console.warn("canvasMouseDown", mouse_mode); } + if (RED.view.DEBUG) { console.warn("canvasMouseDown", mouse_mode); } var point; if (mouse_mode === RED.state.SELECTING_NODE) { d3.event.stopPropagation(); @@ -1457,7 +1457,7 @@ if (RED.view.DEBUG) { console.warn("canvasMouseDown", mouse_mode); } } function canvasMouseUp() { -if (RED.view.DEBUG) { console.warn("canvasMouseUp", mouse_mode); } + if (RED.view.DEBUG) { console.warn("canvasMouseUp", mouse_mode); } var i; var historyEvent; if (mouse_mode === RED.state.PANNING) { @@ -1751,7 +1751,7 @@ if (RED.view.DEBUG) { console.warn("canvasMouseUp", mouse_mode); } } function clearSelection() { -if (RED.view.DEBUG) { console.warn("clearSelection", mouse_mode); } + if (RED.view.DEBUG) { console.warn("clearSelection", mouse_mode); } for (var i=0;i 0) { + var selectClass; + var portType; + if ((drag_lines[0].virtualLink && drag_lines[0].portType === PORT_TYPE_INPUT) || drag_lines[0].portType === PORT_TYPE_OUTPUT) { + selectClass = ".red-ui-flow-port-input .red-ui-flow-port"; + portType = PORT_TYPE_INPUT; + } else { + selectClass = ".red-ui-flow-port-output .red-ui-flow-port"; + portType = PORT_TYPE_OUTPUT; + } + portMouseOver(d3.select(this.parentNode).selectAll(selectClass),d,portType,0); + } + } + } + function nodeMouseOut(d) { + if (RED.view.DEBUG) { console.warn("nodeMouseOut", mouse_mode,d); } + this.parentNode.classList.remove("red-ui-flow-node-hovered"); + clearTimeout(portLabelHoverTimeout); + if (portLabelHover) { + portLabelHover.remove(); + portLabelHover = null; + } + if (mouse_mode === RED.state.JOINING || mouse_mode === RED.state.QUICK_JOINING) { + if (drag_lines.length > 0) { + var selectClass; + var portType; + if ((drag_lines[0].virtualLink && drag_lines[0].portType === PORT_TYPE_INPUT) || drag_lines[0].portType === PORT_TYPE_OUTPUT) { + selectClass = ".red-ui-flow-port-input .red-ui-flow-port"; + portType = PORT_TYPE_INPUT; + } else { + selectClass = ".red-ui-flow-port-output .red-ui-flow-port"; + portType = PORT_TYPE_OUTPUT; + } + portMouseOut(d3.select(this.parentNode).selectAll(selectClass),d,portType,0); + } + } + } + + function portMouseDownProxy(e) { portMouseDown(this.__data__,this.__portType__,this.__portIndex__, e); } + function portTouchStartProxy(e) { portMouseDown(this.__data__,this.__portType__,this.__portIndex__, e); e.preventDefault() } + function portMouseUpProxy(e) { portMouseUp(this.__data__,this.__portType__,this.__portIndex__, e); } + function portTouchEndProxy(e) { portMouseUp(this.__data__,this.__portType__,this.__portIndex__, e); e.preventDefault() } + function portMouseOverProxy(e) { portMouseOver(d3.select(this), this.__data__,this.__portType__,this.__portIndex__, e); } + function portMouseOutProxy(e) { portMouseOut(d3.select(this), this.__data__,this.__portType__,this.__portIndex__, e); } + + function linkMouseDown(d) { + if (mouse_mode === RED.state.SELECTING_NODE) { + d3.event.stopPropagation(); + return; + } + mousedown_link = d; + clearSelection(); + selected_link = mousedown_link; + updateSelection(); + redraw(); + focusView(); + d3.event.stopPropagation(); + if (d3.event.metaKey || d3.event.ctrlKey) { + d3.select(this).classed("red-ui-flow-link-splice",true); + var point = d3.mouse(this); + var clickedGroup = getGroupAt(point[0],point[1]); + showQuickAddDialog(point, selected_link, clickedGroup); + } + } + function linkTouchStart(d) { + if (mouse_mode === RED.state.SELECTING_NODE) { + d3.event.stopPropagation(); + return; + } + mousedown_link = d; + clearSelection(); + selected_link = mousedown_link; + updateSelection(); + redraw(); + focusView(); + d3.event.stopPropagation(); + + var obj = d3.select(document.body); + var touch0 = d3.event.touches.item(0); + var pos = [touch0.pageX,touch0.pageY]; + touchStartTime = setTimeout(function() { + touchStartTime = null; + showTouchMenu(obj,pos); + },touchLongPressTimeout); + d3.event.preventDefault(); + } function groupMouseUp(g) { if (dblClickPrimed && mousedown_group == g && clickElapsed > 0 && clickElapsed < 750) { @@ -3159,12 +3312,12 @@ if (RED.view.DEBUG) { console.warn("nodeMouseDown", mouse_mode,d); } .text(fontAwesomeUnicode); } else { var icon = icon_group.append("image") + .style("display","none") .attr("xlink:href",iconUrl) .attr("class","red-ui-flow-node-icon") .attr("x",0) .attr("width","30") - .attr("height","30") - .style("display","none"); + .attr("height","30"); var img = new Image(); img.src = iconUrl; @@ -3192,6 +3345,32 @@ if (RED.view.DEBUG) { console.warn("nodeMouseDown", mouse_mode,d); } } } + function errorBadgeMouseEnter(e) { + var d = this.__data__; + if (d.validationErrors && d.validationErrors.length > 0) { + clearTimeout(portLabelHoverTimeout); + var node = this; + portLabelHoverTimeout = setTimeout(function() { + var pos = getElementPosition(node); + portLabelHoverTimeout = null; + portLabelHover = showTooltip( + (pos[0]), + (pos[1]), + RED._("editor.errors.invalidProperties")+"\n - "+d.validationErrors.join("\n - "), + "top" + ); + },500); + } + } + function errorBadgeMouseLeave() { + clearTimeout(portLabelHoverTimeout); + if (portLabelHover) { + portLabelHover.remove(); + portLabelHover = null; + } + } + + function redraw() { requestAnimationFrame(_redraw); } @@ -3217,28 +3396,8 @@ if (RED.view.DEBUG) { console.warn("nodeMouseDown", mouse_mode,d); } // TODO: This is exactly the same set of handlers used for regular nodes - DRY .on("mouseup",nodeMouseUp) .on("mousedown",nodeMouseDown) - .on("touchstart",function(d) { - var obj = d3.select(this); - var touch0 = d3.event.touches.item(0); - var pos = [touch0.pageX,touch0.pageY]; - startTouchCenter = [touch0.pageX,touch0.pageY]; - startTouchDistance = 0; - touchStartTime = setTimeout(function() { - showTouchMenu(obj,pos); - },touchLongPressTimeout); - nodeMouseDown.call(this,d) - d3.event.preventDefault(); - }) - .on("touchend", function(d) { - clearTimeout(touchStartTime); - touchStartTime = null; - if (RED.touch.radialMenu.active()) { - d3.event.stopPropagation(); - return; - } - nodeMouseUp.call(this,d); - d3.event.preventDefault(); - }); + .on("touchstart",nodeTouchStart) + .on("touchend",nodeTouchEnd) outGroup.append("g").attr('transform','translate(-5,15)').append("rect").attr("class","red-ui-flow-port").attr("rx",3).attr("ry",3).attr("width",10).attr("height",10) .on("mousedown", function(d,i){portMouseDown(d,PORT_TYPE_INPUT,0);} ) @@ -3262,28 +3421,8 @@ if (RED.view.DEBUG) { console.warn("nodeMouseDown", mouse_mode,d); } // TODO: This is exactly the same set of handlers used for regular nodes - DRY .on("mouseup",nodeMouseUp) .on("mousedown",nodeMouseDown) - .on("touchstart",function(d) { - var obj = d3.select(this); - var touch0 = d3.event.touches.item(0); - var pos = [touch0.pageX,touch0.pageY]; - startTouchCenter = [touch0.pageX,touch0.pageY]; - startTouchDistance = 0; - touchStartTime = setTimeout(function() { - showTouchMenu(obj,pos); - },touchLongPressTimeout); - nodeMouseDown.call(this,d) - d3.event.preventDefault(); - }) - .on("touchend", function(d) { - clearTimeout(touchStartTime); - touchStartTime = null; - if (RED.touch.radialMenu.active()) { - d3.event.stopPropagation(); - return; - } - nodeMouseUp.call(this,d); - d3.event.preventDefault(); - }); + .on("touchstart",nodeTouchStart) + .on("touchend", nodeTouchEnd); inGroup.append("g").attr('transform','translate(35,15)').append("rect").attr("class","red-ui-flow-port").attr("rx",3).attr("ry",3).attr("width",10).attr("height",10) .on("mousedown", function(d,i){portMouseDown(d,PORT_TYPE_OUTPUT,i);} ) @@ -3307,28 +3446,8 @@ if (RED.view.DEBUG) { console.warn("nodeMouseDown", mouse_mode,d); } // TODO: This is exactly the same set of handlers used for regular nodes - DRY .on("mouseup",nodeMouseUp) .on("mousedown",nodeMouseDown) - .on("touchstart",function(d) { - var obj = d3.select(this); - var touch0 = d3.event.touches.item(0); - var pos = [touch0.pageX,touch0.pageY]; - startTouchCenter = [touch0.pageX,touch0.pageY]; - startTouchDistance = 0; - touchStartTime = setTimeout(function() { - showTouchMenu(obj,pos); - },touchLongPressTimeout); - nodeMouseDown.call(this,d) - d3.event.preventDefault(); - }) - .on("touchend", function(d) { - d3.event.preventDefault(); - clearTimeout(touchStartTime); - touchStartTime = null; - if (RED.touch.radialMenu.active()) { - d3.event.stopPropagation(); - return; - } - nodeMouseUp.call(this,d); - }); + .on("touchstart",nodeTouchStart) + .on("touchend", nodeTouchEnd); statusGroup.append("g").attr('transform','translate(-5,15)').append("rect").attr("class","red-ui-flow-port").attr("rx",3).attr("ry",3).attr("width",10).attr("height",10) .on("mousedown", function(d,i){portMouseDown(d,PORT_TYPE_INPUT,0);} ) @@ -3384,12 +3503,16 @@ if (RED.view.DEBUG) { console.warn("nodeMouseDown", mouse_mode,d); } .attr("class", "red-ui-flow-node red-ui-flow-node-group") .classed("red-ui-flow-subflow", activeSubflow != null); + nodeEnter.each(function(d,i) { + this.__outputs__ = []; + this.__inputs__ = []; var node = d3.select(this); + var nodeContents = document.createDocumentFragment(); var isLink = (d.type === "link in" || d.type === "link out") var hideLabel = d.hasOwnProperty('l')?!d.l : isLink; node.attr("id",d.id); - var labelWidth = calculateTextWidth(RED.utils.getNodeLabel(d), "red-ui-flow-node-label", 50); + var labelWidth = calculateTextWidth(RED.utils.getNodeLabel(d), "red-ui-flow-node-label") + 50; if (d.resize || d.w === undefined) { if (hideLabel) { d.w = node_height; @@ -3411,27 +3534,35 @@ if (RED.view.DEBUG) { console.warn("nodeMouseDown", mouse_mode,d); } // .on("click",function(d) { d._def.onbadgeclick.call(d);d3.event.preventDefault();}); // } // } - if (d._def.button) { - var nodeButtonGroup = node.append("svg:g") - .attr("transform",function(d) { return "translate("+((d._def.align == "right") ? 94 : -25)+",2)"; }) - .attr("class","red-ui-flow-node-button"); - nodeButtonGroup.append("rect") - .attr("class","red-ui-flow-node-button-background") - .attr("rx",5) - .attr("ry",5) - .attr("width",32) - .attr("height",node_height-4); - nodeButtonGroup.append("rect") - .attr("class","red-ui-flow-node-button-button") - .attr("x",function(d) { return d._def.align == "right"? 11:5}) - .attr("y",4) - .attr("rx",4) - .attr("ry",4) - .attr("width",16) - .attr("height",node_height-12) - .attr("fill",function(d) { return RED.utils.getNodeColor(d.type,d._def); /*d._def.color;*/}) - .attr("cursor","pointer") + var buttonGroup = document.createElementNS("http://www.w3.org/2000/svg","g"); + buttonGroup.__data__ = d; + buttonGroup.setAttribute("transform", "translate("+((d._def.align == "right") ? 94 : -25)+",2)"); + buttonGroup.setAttribute("class","red-ui-flow-node-button"); + node[0][0].__buttonGroup__ = buttonGroup; + + var bgBackground = document.createElementNS("http://www.w3.org/2000/svg","rect"); + bgBackground.__data__ = d; + bgBackground.setAttribute("class","red-ui-flow-node-button-background"); + bgBackground.setAttribute("rx",5); + bgBackground.setAttribute("ry",5); + bgBackground.setAttribute("width",32); + bgBackground.setAttribute("height",node_height-4); + buttonGroup.appendChild(bgBackground); + node[0][0].__buttonGroupBackground__ = bgBackground; + + var bgButton = document.createElementNS("http://www.w3.org/2000/svg","rect"); + bgButton.__data__ = d; + bgButton.setAttribute("class","red-ui-flow-node-button-button"); + bgButton.setAttribute("x", d._def.align == "right"? 11:5); + bgButton.setAttribute("y",4); + bgButton.setAttribute("rx",4); + bgButton.setAttribute("ry",4); + bgButton.setAttribute("width",16); + bgButton.setAttribute("height",node_height-12); + bgButton.setAttribute("fill", RED.utils.getNodeColor(d.type,d._def)); + bgButton.setAttribute("cursor","pointer"); + d3.select(bgButton) .on("mousedown",function(d) {if (!lasso && isButtonEnabled(d)) {focusView();d3.select(this).attr("fill-opacity",0.2);d3.event.preventDefault(); d3.event.stopPropagation();}}) .on("mouseup",function(d) {if (!lasso && isButtonEnabled(d)) { d3.select(this).attr("fill-opacity",0.4);d3.event.preventDefault();d3.event.stopPropagation();}}) .on("mouseover",function(d) {if (!lasso && isButtonEnabled(d)) { d3.select(this).attr("fill-opacity",0.4);}}) @@ -3444,203 +3575,124 @@ if (RED.view.DEBUG) { console.warn("nodeMouseDown", mouse_mode,d); } }}) .on("click",nodeButtonClicked) .on("touchstart",function(d) { nodeButtonClicked.call(this,d); d3.event.preventDefault();}) + buttonGroup.appendChild(bgButton); + node[0][0].__buttonGroupButton__ = bgButton; + + nodeContents.appendChild(buttonGroup); + } - var mainRect = node.append("rect") - .attr("class", "red-ui-flow-node") - .classed("red-ui-flow-node-unknown", d.type == "unknown") - .attr("rx", 5) - .attr("ry", 5) - .attr("fill", RED.utils.getNodeColor(d.type,d._def)) + var mainRect = document.createElementNS("http://www.w3.org/2000/svg","rect"); + mainRect.__data__ = d; + mainRect.setAttribute("class", "red-ui-flow-node "+(d.type == "unknown"?"red-ui-flow-node-unknown":"")); + mainRect.setAttribute("rx", 5); + mainRect.setAttribute("ry", 5); + mainRect.setAttribute("fill", RED.utils.getNodeColor(d.type,d._def)); + node[0][0].__mainRect__ = mainRect; + d3.select(mainRect) .on("mouseup",nodeMouseUp) .on("mousedown",nodeMouseDown) - .on("touchstart",function(d) { - var obj = d3.select(this); - var touch0 = d3.event.touches.item(0); - var pos = [touch0.pageX,touch0.pageY]; - startTouchCenter = [touch0.pageX,touch0.pageY]; - startTouchDistance = 0; - touchStartTime = setTimeout(function() { - showTouchMenu(obj,pos); - },touchLongPressTimeout); - nodeMouseDown.call(this,d) - d3.event.preventDefault(); - }) - .on("touchend", function(d) { - d3.event.preventDefault(); - clearTimeout(touchStartTime); - touchStartTime = null; - if (RED.touch.radialMenu.active()) { - d3.event.stopPropagation(); - return; - } - nodeMouseUp.call(this,d); - }) - .on("mouseover",function(d) { - if (mouse_mode === 0 || mouse_mode === RED.state.SELECTING_NODE) { - if (mouse_mode === RED.state.SELECTING_NODE && selectNodesOptions && selectNodesOptions.filter) { - if (selectNodesOptions.filter(d)) { - node.classed("red-ui-flow-node-hovered",true); - } - } else { - node.classed("red-ui-flow-node-hovered",true); - } - clearTimeout(portLabelHoverTimeout); - if (d.hasOwnProperty('l')?!d.l : (d.type === "link in" || d.type === "link out")) { - portLabelHoverTimeout = setTimeout(function() { - var tooltip; - if (d._def.label) { - tooltip = d._def.label; - try { - tooltip = (typeof tooltip === "function" ? tooltip.call(d) : tooltip)||""; - } catch(err) { - console.log("Definition error: "+d.type+".label",err); - tooltip = d.type; - } - } - if (tooltip !== "") { - var pos = getElementPosition(node.node()); - portLabelHoverTimeout = null; - portLabelHover = showTooltip( - (pos[0] + d.w/2), - (pos[1]-1), - tooltip, - "top" - ); - } - },500); - } - } else if (mouse_mode === RED.state.JOINING || mouse_mode === RED.state.QUICK_JOINING) { - if (drag_lines.length > 0) { - var selectClass; - var portType; - if ((drag_lines[0].virtualLink && drag_lines[0].portType === PORT_TYPE_INPUT) || drag_lines[0].portType === PORT_TYPE_OUTPUT) { - selectClass = ".red-ui-flow-port-input .red-ui-flow-port"; - portType = PORT_TYPE_INPUT; - } else { - selectClass = ".red-ui-flow-port-output .red-ui-flow-port"; - portType = PORT_TYPE_OUTPUT; - } - portMouseOver(d3.select(this.parentNode).selectAll(selectClass),d,portType,0); - } - } - }) - .on("mouseout",function(d) { - node.classed("red-ui-flow-node-hovered",false); - clearTimeout(portLabelHoverTimeout); - if (portLabelHover) { - portLabelHover.remove(); - portLabelHover = null; - } - if (mouse_mode === RED.state.JOINING || mouse_mode === RED.state.QUICK_JOINING) { - if (drag_lines.length > 0) { - var selectClass; - var portType; - if ((drag_lines[0].virtualLink && drag_lines[0].portType === PORT_TYPE_INPUT) || drag_lines[0].portType === PORT_TYPE_OUTPUT) { - selectClass = ".red-ui-flow-port-input .red-ui-flow-port"; - portType = PORT_TYPE_INPUT; - } else { - selectClass = ".red-ui-flow-port-output .red-ui-flow-port"; - portType = PORT_TYPE_OUTPUT; - } - portMouseOut(d3.select(this.parentNode).selectAll(selectClass),d,portType,0); - } - } - }); - + .on("touchstart",nodeTouchStart) + .on("touchend",nodeTouchEnd) + .on("mouseover",nodeMouseOver) + .on("mouseout",nodeMouseOut); + nodeContents.appendChild(mainRect); //node.append("rect").attr("class", "node-gradient-top").attr("rx", 6).attr("ry", 6).attr("height",30).attr("stroke","none").attr("fill","url(#gradient-top)").style("pointer-events","none"); //node.append("rect").attr("class", "node-gradient-bottom").attr("rx", 6).attr("ry", 6).attr("height",30).attr("stroke","none").attr("fill","url(#gradient-bottom)").style("pointer-events","none"); if (d._def.icon) { var icon_url = RED.utils.getNodeIcon(d._def,d); - var icon_group = node.append("g") - .attr("class","red-ui-flow-node-icon-group") - .attr("x",0).attr("y",0); - - var icon_shade = icon_group.append("rect") - .attr("x",0).attr("y",0) - .attr("class","red-ui-flow-node-icon-shade") - .attr("width","30") - .attr("height", Math.min(50,d.h-4)); + var icon_groupEl = document.createElementNS("http://www.w3.org/2000/svg","g"); + icon_groupEl.__data__ = d; + icon_groupEl.setAttribute("class","red-ui-flow-node-icon-group"+("right" == d._def.align?" red-ui-flow-node-icon-group-right":"")); + icon_groupEl.setAttribute("x",0); + icon_groupEl.setAttribute("y",0); + icon_groupEl.style["pointer-events"] = "none"; + node[0][0].__iconGroup__ = icon_groupEl; + var icon_shade = document.createElementNS("http://www.w3.org/2000/svg","rect"); + icon_shade.setAttribute("x",0); + icon_shade.setAttribute("y",0); + icon_shade.setAttribute("class","red-ui-flow-node-icon-shade") + icon_shade.setAttribute("width",30); + icon_shade.setAttribute("height",Math.min(50,d.h-4)); + icon_groupEl.appendChild(icon_shade); + node[0][0].__iconShade__ = icon_shade; + var icon_group = d3.select(icon_groupEl) createIconAttributes(icon_url, icon_group, d); - var icon_shade_border = icon_group.append("path") - .attr("d","M 30 1 l 0 "+(d.h-2)) - .attr("class","red-ui-flow-node-icon-shade-border"); + var icon_shade_border = document.createElementNS("http://www.w3.org/2000/svg","path"); + icon_shade_border.setAttribute("d","right" != d._def.align ? "M 30 1 l 0 "+(d.h-2) : "M 0 1 l 0 "+(d.h-2) ) + icon_shade_border.setAttribute("class", "red-ui-flow-node-icon-shade-border") + icon_groupEl.appendChild(icon_shade_border); + node[0][0].__iconShadeBorder__ = icon_shade_border; - if ("right" == d._def.align) { - icon_group.attr("class","red-ui-flow-node-icon-group red-ui-flow-node-icon-group-"+d._def.align); - icon_shade_border.attr("d", "M 0 1 l 0 "+(d.h-2)) - //icon.attr("class","red-ui-flow-node-icon red-ui-flow-node-icon-"+d._def.align); - //icon.attr("class","red-ui-flow-node-icon-shade red-ui-flow-node-icon-shade-"+d._def.align); - //icon.attr("class","red-ui-flow-node-icon-shade-border red-ui-flow-node-icon-shade-border-"+d._def.align); - } - - //if (d.inputs > 0 && d._def.align == null) { - // icon_shade.attr("width",35); - // icon.attr("transform","translate(5,0)"); - // icon_shade_border.attr("transform","translate(5,0)"); - //} - //if (d._def.outputs > 0 && "right" == d._def.align) { - // icon_shade.attr("width",35); //icon.attr("x",5); - //} - - //icon.style("pointer-events","none"); - icon_group.style("pointer-events","none"); + nodeContents.appendChild(icon_groupEl); } var labelLineNumber = (separateTextByLineBreak.length == 0)? 1:separateTextByLineBreak.length; var labelId = d.id.replace(".","-"); for(var i=0;i 0) { - clearTimeout(portLabelHoverTimeout); - portLabelHoverTimeout = setTimeout(function() { - var pos = getElementPosition(nodeErrorButton.node()); - portLabelHoverTimeout = null; - portLabelHover = showTooltip( - (pos[0]), - (pos[1]), - RED._("editor.errors.invalidProperties")+"\n - "+d.validationErrors.join("\n - "), - "top" - ); - },500); - } - }).on("mouseleave", function() { - clearTimeout(portLabelHoverTimeout); - if (portLabelHover) { - portLabelHover.remove(); - portLabelHover = null; - } - }); + var statusRect = document.createElementNS("http://www.w3.org/2000/svg","rect"); + statusRect.setAttribute("class","red-ui-flow-node-status"); + statusRect.setAttribute("x",6); + statusRect.setAttribute("y",1); + statusRect.setAttribute("width",9); + statusRect.setAttribute("height",9); + statusRect.setAttribute("rx",2); + statusRect.setAttribute("ry",2); + statusRect.setAttribute("stroke-width","3"); + statusEl.appendChild(statusRect); + node[0][0].__statusShape__ = statusRect; + + var statusLabel = document.createElementNS("http://www.w3.org/2000/svg","text"); + statusLabel.setAttribute("class","red-ui-flow-node-status-label"); + statusLabel.setAttribute("x",20); + statusLabel.setAttribute("y",10); + statusEl.appendChild(statusLabel); + node[0][0].__statusLabel__ = statusLabel; + + nodeContents.appendChild(statusEl); + + + var changeBadgeG = document.createElementNS("http://www.w3.org/2000/svg","g"); + changeBadgeG.setAttribute("class","red-ui-flow-node-changed hide"); + changeBadgeG.setAttribute("transform","translate(20, -2)"); + node[0][0].__changeBadge__ = changeBadgeG; + var changeBadge = document.createElementNS("http://www.w3.org/2000/svg","circle"); + changeBadge.setAttribute("r",5); + changeBadgeG.appendChild(changeBadge); + nodeContents.appendChild(changeBadgeG); + + + var errorBadgeG = document.createElementNS("http://www.w3.org/2000/svg","g"); + errorBadgeG.setAttribute("class","red-ui-flow-node-error hide"); + errorBadgeG.setAttribute("transform","translate(0, -2)"); + node[0][0].__errorBadge__ = errorBadgeG; + var errorBadge = document.createElementNS("http://www.w3.org/2000/svg","path"); + errorBadge.setAttribute("d","M -5,4 l 10,0 -5,-8 z"); + errorBadgeG.appendChild(errorBadge); + errorBadge.__data__ = d; + errorBadge.addEventListener("mouseenter", errorBadgeMouseEnter); + errorBadge.addEventListener("mouseleave", errorBadgeMouseLeave); + nodeContents.appendChild(errorBadgeG); + + node[0][0].appendChild(nodeContents); }); - node.each(function(d,i) { if (d.dirty) { var isLink = (d.type === "link in" || d.type === "link out") @@ -3648,7 +3700,7 @@ if (RED.view.DEBUG) { console.warn("nodeMouseDown", mouse_mode,d); } dirtyNodes[d.id] = d; //if (d.x < -50) deleteSelection(); // Delete nodes if dragged back to palette var labelLineNumber = (d.h-6)/24; - var labelWidth = calculateTextWidth(RED.utils.getNodeLabel(d), "red-ui-flow-node-label", 50); + var labelWidth = calculateTextWidth(RED.utils.getNodeLabel(d), "red-ui-flow-node-label") + 50; if (d.resize) { var ow = d.w; if (hideLabel) { @@ -3667,18 +3719,15 @@ if (RED.view.DEBUG) { console.warn("nodeMouseDown", mouse_mode,d); } } var thisNode = d3.select(this); - thisNode.classed("red-ui-flow-node-disabled", d.d === true); - thisNode.classed("red-ui-flow-subflow", activeSubflow != null) - //thisNode.selectAll(".centerDot").attr({"cx":function(d) { return d.w/2;},"cy":function(d){return d.h/2}}); - thisNode.attr("transform", "translate(" + (d.x-d.w/2) + "," + (d.y-d.h/2) + ")"); + this.setAttribute("transform", "translate(" + (d.x-d.w/2) + "," + (d.y-d.h/2) + ")"); if (mouse_mode != RED.state.MOVING_ACTIVE) { - thisNode.classed("red-ui-flow-node-selected", d.selected ) - thisNode.selectAll(".red-ui-flow-node") - .attr("width", d.w) - .attr("height", d.h) - .classed("red-ui-flow-node-highlighted",d.highlighted ) - ; + this.classList.toggle("red-ui-flow-node-disabled", d.d === true); + this.classList.toggle("red-ui-flow-subflow", activeSubflow != null) + this.classList.toggle("red-ui-flow-node-selected", !!d.selected ) + this.__mainRect__.setAttribute("width", d.w) + this.__mainRect__.setAttribute("height", d.h) + this.__mainRect__.classList.toggle("red-ui-flow-node-highlighted",!!d.highlighted ); var l = ""; if (d._def.label) { l = d._def.label; @@ -3696,63 +3745,61 @@ if (RED.view.DEBUG) { console.warn("nodeMouseDown", mouse_mode,d); } var yp = d.h / 2 - (sn / 2) * 24 + 16 var labelId = d.id.replace(".","-"); if(labelLineNumber0?5:0);}); //thisNode.selectAll(".red-ui-flow-node-icon-shade-right").attr("x",function(d){return d.w-30;}); //thisNode.selectAll(".red-ui-flow-node-icon-shade-border-right").attr("d",function(d){return "M "+(d.w-30)+" 1 l 0 "+(d.h-2)}); - var inputPorts = thisNode.selectAll(".red-ui-flow-port-input"); if ((!isLink || (showAllLinkPorts === -1 && !activeLinkNodes[d.id])) && d.inputs === 0 && !inputPorts.empty()) { inputPorts.remove(); @@ -3775,52 +3822,60 @@ if (RED.view.DEBUG) { console.warn("nodeMouseDown", mouse_mode,d); } .on("mouseover",function(d){portMouseOver(d3.select(this),d,PORT_TYPE_INPUT,0);}) .on("mouseout",function(d) {portMouseOut(d3.select(this),d,PORT_TYPE_INPUT,0);}); } - var numOutputs = d.outputs; if (isLink && d.type === "link out") { if (showAllLinkPorts===PORT_TYPE_OUTPUT || activeLinkNodes[d.id]) { - d.ports = [0]; numOutputs = 1; } else { - d.ports = []; numOutputs = 0; } } var y = (d.h/2)-((numOutputs-1)/2)*13; - d.ports = d.ports || d3.range(numOutputs); - d._ports = thisNode.selectAll(".red-ui-flow-port-output").data(d.ports); - var output_group = d._ports.enter().append("g").attr("class","red-ui-flow-port-output"); - var output_group_ports; - if (d.type === "link out") { - output_group_ports = output_group.append("circle") - .attr("cx",11).attr("cy",5) - .attr("r",5) - .attr("class","red-ui-flow-port red-ui-flow-link-port") - } else { - output_group_ports = output_group.append("rect") - .attr("class","red-ui-flow-port") - .attr("rx",3).attr("ry",3) - .attr("width",10) - .attr("height",10) + // Remove extra ports + while (this.__outputs__.length > numOutputs) { + var port = this.__outputs__.pop(); + port.remove(); } + for(var portIndex = 0; portIndex < numOutputs; portIndex++ ) { + var portGroup; + if (portIndex === this.__outputs__.length) { + portGroup = document.createElementNS("http://www.w3.org/2000/svg","g"); + portGroup.setAttribute("class","red-ui-flow-port-output"); + var portPort; + if (d.type === "link out") { + portPort = document.createElementNS("http://www.w3.org/2000/svg","circle"); + portPort.setAttribute("cx",11); + portPort.setAttribute("cy",5); + portPort.setAttribute("r",5); + portPort.setAttribute("class","red-ui-flow-port red-ui-flow-link-port"); + } else { + portPort = document.createElementNS("http://www.w3.org/2000/svg","rect"); + portPort.setAttribute("rx",3); + portPort.setAttribute("ry",3); + portPort.setAttribute("width",10); + portPort.setAttribute("height",10); + portPort.setAttribute("class","red-ui-flow-port"); + } + portGroup.appendChild(portPort); + portPort.__data__ = this.__data__; + portPort.__portType__ = PORT_TYPE_OUTPUT; + portPort.__portIndex__ = portIndex; + portPort.addEventListener("mousedown", portMouseDownProxy); + portPort.addEventListener("touchstart", portTouchStartProxy); + portPort.addEventListener("mouseup", portMouseUpProxy); + portPort.addEventListener("touchstart", portTouchEndProxy); + portPort.addEventListener("mouseover", portMouseOverProxy); + portPort.addEventListener("mouseout", portMouseOutProxy); - output_group_ports.on("mousedown",(function(){var node = d; return function(d,i){portMouseDown(node,PORT_TYPE_OUTPUT,i);}})() ) - .on("touchstart",(function(){var node = d; return function(d,i){portMouseDown(node,PORT_TYPE_OUTPUT,i);d3.event.preventDefault();}})() ) - .on("mouseup",(function(){var node = d; return function(d,i){portMouseUp(node,PORT_TYPE_OUTPUT,i);}})() ) - .on("touchend",(function(){var node = d; return function(d,i){portMouseUp(node,PORT_TYPE_OUTPUT,i);d3.event.preventDefault();}})() ) - .on("mouseover",(function(){var node = d; return function(d,i){portMouseOver(d3.select(this),node,PORT_TYPE_OUTPUT,i);}})()) - .on("mouseout",(function(){var node = d; return function(d,i) {portMouseOut(d3.select(this),node,PORT_TYPE_OUTPUT,i);}})()); - - d._ports.exit().remove(); - if (d._ports) { - numOutputs = d.outputs || 1; - y = (d.h/2)-((numOutputs-1)/2)*13; + this.appendChild(portGroup); + this.__outputs__.push(portGroup); + } else { + portGroup = this.__outputs__[portIndex]; + } var x = d.w - 5; - d._ports.each(function(d,i) { - var port = d3.select(this); - port.attr("transform", function(d) { return "translate("+x+","+((y+13*i)-5)+")";}); - }); + var y = (d.h/2)-((numOutputs-1)/2)*13; + portGroup.setAttribute("transform","translate("+x+","+((y+13*portIndex)-5)+")") } if (d._def.icon) { var icon = thisNode.select(".red-ui-flow-node-icon"); @@ -3840,54 +3895,49 @@ if (RED.view.DEBUG) { console.warn("nodeMouseDown", mouse_mode,d); } } var iconGroup = thisNode.select(".red-ui-flow-node-icon-group"); createIconAttributes(new_url, iconGroup, d); + icon = thisNode.select(".red-ui-flow-node-icon"); + faIcon = thisNode.select(".fa-lg"); } + + icon.attr("y",function(){return (d.h-d3.select(this).attr("height"))/2;}); + this.__iconShade__.setAttribute("height", d.h ); + this.__iconShadeBorder__.setAttribute("d", + "M " + (((!d._def.align && d.inputs !== 0 && d.outputs === 0) || "right" === d._def.align) ? 0 : 30) + " 1 l 0 " + (d.h - 2) + ); + faIcon.attr("y",(d.h+13)/2); } - - thisNode.selectAll(".red-ui-flow-node-changed") - .attr("transform", "translate("+(d.w-10)+", -2)") - .classed("hide", !(d.changed||d.moved)); - - thisNode.selectAll(".red-ui-flow-node-error") - .attr("transform", "translate("+(d.w-10-((d.changed||d.moved)?14:0))+", -2)") - .classed("hide", d.valid); + this.__changeBadge__.setAttribute("transform", "translate("+(d.w-10)+", -2)"); + this.__changeBadge__.classList.toggle("hide", !(d.changed||d.moved)); + this.__errorBadge__.setAttribute("transform", "translate("+(d.w-10-((d.changed||d.moved)?14:0))+", -2)"); + this.__errorBadge__.classList.toggle("hide", d.valid); thisNode.selectAll(".red-ui-flow-port-input").each(function(d,i) { var port = d3.select(this); port.attr("transform",function(d){return "translate(-5,"+((d.h/2)-5)+")";}) }); - thisNode.selectAll(".red-ui-flow-node-icon").attr("y",function(d){return (d.h-d3.select(this).attr("height"))/2;}); - thisNode.selectAll(".red-ui-flow-node-icon-shade").attr("height", d.h ); - thisNode.selectAll(".red-ui-flow-node-icon-shade-border").attr("d", - "M " + (((!d._def.align && d.inputs !== 0 && d.outputs === 0) || "right" === d._def.align) ? 0 : 30) + " 1 l 0 " + (d.h - 2) - ); - thisNode.selectAll(".fa-lg").attr("y",(d.h+13)/2); + if (d._def.button) { + var buttonEnabled = isButtonEnabled(d); + this.__buttonGroup__.setAttribute("opacity", !buttonEnabled?0.4:1 ); + this.__buttonGroupButton__.setAttribute("cursor",buttonEnabled?"":"pointer"); - thisNode.selectAll(".red-ui-flow-node-button").attr("opacity", function(d2) { return !isButtonEnabled(d2)?0.4:1 }); - thisNode.selectAll(".red-ui-flow-node-button-button").attr("cursor",function(d2) { return isButtonEnabled(d2)?"":"pointer"}); - thisNode.selectAll(".red-ui-flow-node-button").attr("transform",function(d){ var x = d._def.align == "right"?d.w-6:-25; if (d._def.button.toggle && !d[d._def.button.toggle]) { x = x - (d._def.align == "right"?8:-8); } - return "translate("+x+",2)"; - }); - thisNode.selectAll(".red-ui-flow-node-button rect").attr("fill-opacity",function(d){ - if (d._def.button.toggle) { - return d[d._def.button.toggle]?1:0.2; - } - return 1; - }); + this.__buttonGroup__.setAttribute("transform", "translate("+x+",2)"); - if (d._def.button && (typeof d._def.button.visible === "function")) { // is defined and a function... - if (d._def.button.visible.call(d) === false) { - thisNode.selectAll(".red-ui-flow-node-button").style("display","none"); - } - else { - thisNode.selectAll(".red-ui-flow-node-button").style("display","inherit"); + this.__buttonGroupBackground__.setAttribute("fill-opacity",d._def.button.toggle?(d[d._def.button.toggle]?1:0.2):1) + + if (typeof d._def.button.visible === "function") { // is defined and a function... + if (d._def.button.visible.call(d) === false) { + this.__buttonGroup__.style.display = "none"; + } + else { + this.__buttonGroup__.style.display = "inherit"; + } } } - // thisNode.selectAll(".node_badge_group").attr("transform",function(d){return "translate("+(d.w-40)+","+(d.h+3)+")";}); // thisNode.selectAll("text.node_badge_label").text(function(d,i) { // if (d._def.badge) { @@ -3908,27 +3958,27 @@ if (RED.view.DEBUG) { console.warn("nodeMouseDown", mouse_mode,d); } if (d.dirtyStatus) { if (!showStatus || !d.status) { - thisNode.selectAll(".red-ui-flow-node-status-group").style("display","none"); + this.__statusGroup__.style.display = "none"; } else { - thisNode.selectAll(".red-ui-flow-node-status-group").style("display","inline"); + this.__statusGroup__.style.display = "inline"; var fill = status_colours[d.status.fill]; // Only allow our colours for now if (d.status.shape == null && fill == null) { - thisNode.selectAll(".red-ui-flow-node-status").style("display","none"); - thisNode.selectAll(".red-ui-flow-node-status-group").attr("transform","translate(-14,"+(d.h+3)+")"); + this.__statusShape__.style.display = "none"; + this.__statusGroup__.setAttribute("transform","translate(-14,"+(d.h+3)+")"); } else { - thisNode.selectAll(".red-ui-flow-node-status-group").attr("transform","translate(3,"+(d.h+3)+")"); + this.__statusGroup__.setAttribute("transform","translate(3,"+(d.h+3)+")"); var statusClass = "red-ui-flow-node-status-"+(d.status.shape||"dot")+"-"+d.status.fill; - thisNode.selectAll(".red-ui-flow-node-status").style("display","inline").attr("class","red-ui-flow-node-status "+statusClass); + this.__statusShape__.style.display = "inline"; + this.__statusShape__.setAttribute("class","red-ui-flow-node-status "+statusClass); } if (d.status.hasOwnProperty('text')) { - thisNode.selectAll(".red-ui-flow-node-status-label").text(d.status.text); + this.__statusLabel__.textContent = d.status.text; } else { - thisNode.selectAll(".red-ui-flow-node-status-label").text(""); + this.__statusLabel__.textContent = ""; } } delete d.dirtyStatus; } - d.dirty = false; if (d.g) { if (!dirtyGroups[d.g]) { @@ -3941,7 +3991,6 @@ if (RED.view.DEBUG) { console.warn("nodeMouseDown", mouse_mode,d); } } } }); - var link = linkLayer.selectAll(".red-ui-flow-link").data( activeLinks, function(d) { @@ -3952,92 +4001,66 @@ if (RED.view.DEBUG) { console.warn("nodeMouseDown", mouse_mode,d); } linkEnter.each(function(d,i) { var l = d3.select(this); - d.added = true; - l.append("svg:path").attr("class","red-ui-flow-link-background red-ui-flow-link-path") - .classed("red-ui-flow-link-link", function(d) { return d.link }) - .on("mousedown",function(d) { - if (mouse_mode === RED.state.SELECTING_NODE) { - d3.event.stopPropagation(); - return; - } - mousedown_link = d; - clearSelection(); - selected_link = mousedown_link; - updateSelection(); - redraw(); - focusView(); - d3.event.stopPropagation(); - if (d3.event.metaKey || d3.event.ctrlKey) { - l.classed("red-ui-flow-link-splice",true); - var point = d3.mouse(this); - var clickedGroup = getGroupAt(point[0],point[1]); - showQuickAddDialog(point, selected_link, clickedGroup); - } - }) - .on("touchstart",function(d) { - if (mouse_mode === RED.state.SELECTING_NODE) { - d3.event.stopPropagation(); - return; - } - mousedown_link = d; - clearSelection(); - selected_link = mousedown_link; - updateSelection(); - redraw(); - focusView(); - d3.event.stopPropagation(); + var pathContents = document.createDocumentFragment(); - var obj = d3.select(document.body); - var touch0 = d3.event.touches.item(0); - var pos = [touch0.pageX,touch0.pageY]; - touchStartTime = setTimeout(function() { - touchStartTime = null; - showTouchMenu(obj,pos); - },touchLongPressTimeout); - d3.event.preventDefault(); - }) - l.append("svg:path").attr("class","red-ui-flow-link-outline red-ui-flow-link-path"); - l.append("svg:path").attr("class","red-ui-flow-link-line red-ui-flow-link-path") - .classed("red-ui-flow-link-link", function(d) { return d.link }) - .classed("red-ui-flow-subflow-link", function(d) { return !d.link && activeSubflow }); + d.added = true; + var pathBack = document.createElementNS("http://www.w3.org/2000/svg","path"); + pathBack.__data__ = d; + pathBack.setAttribute("class","red-ui-flow-link-background red-ui-flow-link-path"+(d.link?" red-ui-flow-link-link":"")); + this.__pathBack__ = pathBack; + pathContents.appendChild(pathBack); + d3.select(pathBack) + .on("mousedown",linkMouseDown) + .on("touchstart",linkTouchStart) + + var pathOutline = document.createElementNS("http://www.w3.org/2000/svg","path"); + pathOutline.__data__ = d; + pathOutline.setAttribute("class","red-ui-flow-link-outline red-ui-flow-link-path"); + this.__pathOutline__ = pathOutline; + pathContents.appendChild(pathOutline); + + var pathLine = document.createElementNS("http://www.w3.org/2000/svg","path"); + pathLine.__data__ = d; + pathLine.setAttribute("class","red-ui-flow-link-line red-ui-flow-link-path"+ + (d.link?" red-ui-flow-link-link":(activeSubflow?" red-ui-flow-subflow-link":""))); + this.__pathLine__ = pathLine; + pathContents.appendChild(pathLine); + + l[0][0].appendChild(pathContents); }); link.exit().remove(); - var links = linkLayer.selectAll(".red-ui-flow-link-path"); - links.each(function(d) { + link.each(function(d) { var link = d3.select(this); if (d.added || d===selected_link || d.selected || dirtyNodes[d.source.id] || dirtyNodes[d.target.id]) { - if (/red-ui-flow-link-line/.test(link.attr('class'))) { - link.classed("red-ui-flow-subflow-link", function(d) { return !d.link && activeSubflow }); + var numOutputs = d.source.outputs || 1; + var sourcePort = d.sourcePort || 0; + var y = -((numOutputs-1)/2)*13 +13*sourcePort; + d.x1 = d.source.x+d.source.w/2; + d.y1 = d.source.y+y; + d.x2 = d.target.x-d.target.w/2; + d.y2 = d.target.y; + + // return "M "+d.x1+" "+d.y1+ + // " C "+(d.x1+scale*node_width)+" "+(d.y1+scaleY*node_height)+" "+ + // (d.x2-scale*node_width)+" "+(d.y2-scaleY*node_height)+" "+ + // d.x2+" "+d.y2; + var path = generateLinkPath(d.x1,d.y1,d.x2,d.y2,1); + if (/NaN/.test(path)) { + path = "" } - link.attr("d",function(d){ - var numOutputs = d.source.outputs || 1; - var sourcePort = d.sourcePort || 0; - var y = -((numOutputs-1)/2)*13 +13*sourcePort; - d.x1 = d.source.x+d.source.w/2; - d.y1 = d.source.y+y; - d.x2 = d.target.x-d.target.w/2; - d.y2 = d.target.y; - - // return "M "+d.x1+" "+d.y1+ - // " C "+(d.x1+scale*node_width)+" "+(d.y1+scaleY*node_height)+" "+ - // (d.x2-scale*node_width)+" "+(d.y2-scaleY*node_height)+" "+ - // d.x2+" "+d.y2; - var path = generateLinkPath(d.x1,d.y1,d.x2,d.y2,1); - if (/NaN/.test(path)) { - return "" - } - return path; - }); - link.classed("red-ui-flow-node-disabled", function(d) { return d.source.d || d.target.d; }); + this.__pathBack__.setAttribute("d",path); + this.__pathOutline__.setAttribute("d",path); + this.__pathLine__.setAttribute("d",path); + this.__pathLine__.classList.toggle("red-ui-flow-node-disabled",!!(d.source.d || d.target.d)); + this.__pathLine__.classList.toggle("red-ui-flow-subflow-link", !d.link && activeSubflow); } - }) + this.classList.toggle("red-ui-flow-link-selected", !!(d===selected_link||d.selected)); - link.classed("red-ui-flow-link-selected", function(d) { return d === selected_link || d.selected; }); - link.classed("red-ui-flow-link-unknown",function(d) { + var connectedToUnknown = !!(d.target.type == "unknown" || d.source.type == "unknown"); + this.classList.toggle("red-ui-flow-link-unknown",!!(d.target.type == "unknown" || d.source.type == "unknown")) delete d.added; - return d.target.type == "unknown" || d.source.type == "unknown" - }); + }) var offLinks = linkLayer.selectAll(".red-ui-flow-link-off-flow").data( activeFlowLinks, function(d) { @@ -4157,13 +4180,11 @@ if (RED.view.DEBUG) { console.warn("nodeMouseDown", mouse_mode,d); } }) - var group = groupLayer.selectAll(".red-ui-flow-group").data(activeGroups,function(d) { return d.id }); group.exit().each(function(d,i) { document.getElementById("group_select_"+d.id).remove() }).remove(); var groupEnter = group.enter().insert("svg:g").attr("class", "red-ui-flow-group") - var addedGroups = false; groupEnter.each(function(d,i) { addedGroups = true; @@ -4208,7 +4229,6 @@ if (RED.view.DEBUG) { console.warn("nodeMouseDown", mouse_mode,d); } }) } group[0].reverse(); - group.each(function(d,i) { if (d.resize) { d.minWidth = 0; @@ -4245,7 +4265,7 @@ if (RED.view.DEBUG) { console.warn("nodeMouseDown", mouse_mode,d); } } if (!d.minWidth) { if (d.style.label && d.name) { - d.minWidth = calculateTextWidth(d.name||"","red-ui-flow-group-label",8); + d.minWidth = calculateTextWidth(d.name||"","red-ui-flow-group-label") + 8; d.labels = separateTextByLineBreak; } else { d.minWidth = 40; @@ -4344,7 +4364,6 @@ if (RED.view.DEBUG) { console.warn("nodeMouseDown", mouse_mode,d); } delete d.dirty; } }) - } else { // JOINING - unselect any selected links linkLayer.selectAll(".red-ui-flow-link-selected").data( @@ -4560,7 +4579,6 @@ if (RED.view.DEBUG) { console.warn("nodeMouseDown", mouse_mode,d); } } } function toggleSnapGrid(state) { - snapGrid = state; redraw(); } function toggleStatus(s) {