@node-red/editor-client 3.1.0-beta.3 → 3.1.0-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/public/red/red.js CHANGED
@@ -18923,7 +18923,10 @@ RED.keyboard = (function() {
18923
18923
  // One exception is shortcuts that include both Cmd and Ctrl. We don't
18924
18924
  // support them - but we need to make sure we don't block browser-specific
18925
18925
  // shortcuts (such as Cmd-Ctrl-F for fullscreen).
18926
- if ((evt.ctrlKey || evt.metaKey) && (evt.ctrlKey !== evt.metaKey)) {
18926
+ if (evt.ctrlKey && evt.metaKey) {
18927
+ return null; // dont handle both cmd+ctrl - let browser handle this
18928
+ }
18929
+ if (evt.ctrlKey || evt.metaKey) {
18927
18930
  slot = slot.ctrl;
18928
18931
  }
18929
18932
  if (slot && evt.shiftKey) {
@@ -20620,7 +20623,7 @@ RED.view = (function() {
20620
20623
 
20621
20624
  // Note: these are the permitted status colour aliases. The actual RGB values
20622
20625
  // are set in the CSS - flow.scss/colors.scss
20623
- var status_colours = {
20626
+ const status_colours = {
20624
20627
  "red": "#c00",
20625
20628
  "green": "#5a8",
20626
20629
  "yellow": "#F9DF31",
@@ -20629,19 +20632,32 @@ RED.view = (function() {
20629
20632
  "gray": "#d3d3d3"
20630
20633
  }
20631
20634
 
20632
- var PORT_TYPE_INPUT = 1;
20633
- var PORT_TYPE_OUTPUT = 0;
20635
+ const PORT_TYPE_INPUT = 1;
20636
+ const PORT_TYPE_OUTPUT = 0;
20634
20637
 
20635
- var chart;
20636
- var outer;
20638
+ /**
20639
+ * The jQuery object for the workspace chart `#red-ui-workspace-chart` div element
20640
+ * @type {JQuery<HTMLElement>} #red-ui-workspace-chart HTML Element
20641
+ */
20642
+ let chart;
20643
+ /**
20644
+ * The d3 object `#red-ui-workspace-chart` svg element
20645
+ * @type {d3.Selection<HTMLElement, Any, Any, Any>}
20646
+ */
20647
+ let outer;
20648
+ /**
20649
+ * The d3 object `#red-ui-workspace-chart` svg element (specifically for events)
20650
+ * @type {d3.Selection<d3.BaseType, any, any, any>}
20651
+ */
20637
20652
  var eventLayer;
20638
- var gridLayer;
20639
- var linkLayer;
20640
- var junctionLayer;
20641
- var dragGroupLayer;
20642
- var groupSelectLayer;
20643
- var nodeLayer;
20644
- var groupLayer;
20653
+
20654
+ /** @type {SVGGElement} */ let gridLayer;
20655
+ /** @type {SVGGElement} */ let linkLayer;
20656
+ /** @type {SVGGElement} */ let junctionLayer;
20657
+ /** @type {SVGGElement} */ let dragGroupLayer;
20658
+ /** @type {SVGGElement} */ let groupSelectLayer;
20659
+ /** @type {SVGGElement} */ let nodeLayer;
20660
+ /** @type {SVGGElement} */ let groupLayer;
20645
20661
  var drag_lines;
20646
20662
 
20647
20663
  const movingSet = (function() {
@@ -20910,16 +20926,6 @@ RED.view = (function() {
20910
20926
  touchStartTime = setTimeout(function() {
20911
20927
  touchStartTime = null;
20912
20928
  showTouchMenu(obj,pos);
20913
- //lasso = eventLayer.append("rect")
20914
- // .attr("ox",point[0])
20915
- // .attr("oy",point[1])
20916
- // .attr("rx",2)
20917
- // .attr("ry",2)
20918
- // .attr("x",point[0])
20919
- // .attr("y",point[1])
20920
- // .attr("width",0)
20921
- // .attr("height",0)
20922
- // .attr("class","nr-ui-view-lasso");
20923
20929
  },touchLongPressTimeout);
20924
20930
  }
20925
20931
  d3.event.preventDefault();
@@ -21552,7 +21558,7 @@ RED.view = (function() {
21552
21558
  })
21553
21559
  }
21554
21560
 
21555
- function generateLinkPath(origX,origY, destX, destY, sc) {
21561
+ function generateLinkPath(origX,origY, destX, destY, sc, hasStatus = false) {
21556
21562
  var dy = destY-origY;
21557
21563
  var dx = destX-origX;
21558
21564
  var delta = Math.sqrt(dy*dy+dx*dx);
@@ -21569,62 +21575,110 @@ RED.view = (function() {
21569
21575
  } else {
21570
21576
  scale = 0.4-0.2*(Math.max(0,(node_width-Math.min(Math.abs(dx),Math.abs(dy)))/node_width));
21571
21577
  }
21578
+ function genCP(cp) {
21579
+ return ` M ${cp[0]-5} ${cp[1]} h 10 M ${cp[0]} ${cp[1]-5} v 10 `
21580
+ }
21572
21581
  if (dx*sc > 0) {
21573
- return "M "+origX+" "+origY+
21574
- " C "+(origX+sc*(node_width*scale))+" "+(origY+scaleY*node_height)+" "+
21575
- (destX-sc*(scale)*node_width)+" "+(destY-scaleY*node_height)+" "+
21576
- destX+" "+destY
21582
+ let cp = [
21583
+ [(origX+sc*(node_width*scale)), (origY+scaleY*node_height)],
21584
+ [(destX-sc*(scale)*node_width), (destY-scaleY*node_height)]
21585
+ ]
21586
+ return `M ${origX} ${origY} C ${cp[0][0]} ${cp[0][1]} ${cp[1][0]} ${cp[1][1]} ${destX} ${destY}`
21587
+ // + ` ${genCP(cp[0])} ${genCP(cp[1])}`
21577
21588
  } else {
21589
+ let topX, topY, bottomX, bottomY
21590
+ let cp
21591
+ let midX = Math.floor(destX-dx/2);
21592
+ let midY = Math.floor(destY-dy/2);
21593
+ if (Math.abs(dy) < 10) {
21594
+ bottomY = Math.max(origY, destY) + (hasStatus?35:25)
21595
+ let startCurveHeight = bottomY - origY
21596
+ let endCurveHeight = bottomY - destY
21597
+ cp = [
21598
+ [ origX + sc*15 , origY ],
21599
+ [ origX + sc*25 , origY + 5 ],
21600
+ [ origX + sc*25 , origY + startCurveHeight/2 ],
21601
+
21602
+ [ origX + sc*25 , origY + startCurveHeight - 5 ],
21603
+ [ origX + sc*15 , origY + startCurveHeight ],
21604
+ [ origX , origY + startCurveHeight ],
21605
+
21606
+ [ destX - sc*15, origY + startCurveHeight ],
21607
+ [ destX - sc*25, origY + startCurveHeight - 5 ],
21608
+ [ destX - sc*25, destY + endCurveHeight/2 ],
21609
+
21610
+ [ destX - sc*25, destY + 5 ],
21611
+ [ destX - sc*15, destY ],
21612
+ [ destX, destY ],
21613
+ ]
21578
21614
 
21579
- var midX = Math.floor(destX-dx/2);
21580
- var midY = Math.floor(destY-dy/2);
21581
- //
21582
- if (dy === 0) {
21583
- midY = destY + node_height;
21584
- }
21585
- var cp_height = node_height/2;
21586
- var y1 = (destY + midY)/2
21587
- var topX =origX + sc*node_width*scale;
21588
- var topY = dy>0?Math.min(y1 - dy/2 , origY+cp_height):Math.max(y1 - dy/2 , origY-cp_height);
21589
- var bottomX = destX - sc*node_width*scale;
21590
- var bottomY = dy>0?Math.max(y1, destY-cp_height):Math.min(y1, destY+cp_height);
21591
- var x1 = (origX+topX)/2;
21592
- var scy = dy>0?1:-1;
21593
- var cp = [
21594
- // Orig -> Top
21595
- [x1,origY],
21596
- [topX,dy>0?Math.max(origY, topY-cp_height):Math.min(origY, topY+cp_height)],
21597
- // Top -> Mid
21598
- // [Mirror previous cp]
21599
- [x1,dy>0?Math.min(midY, topY+cp_height):Math.max(midY, topY-cp_height)],
21600
- // Mid -> Bottom
21601
- // [Mirror previous cp]
21602
- [bottomX,dy>0?Math.max(midY, bottomY-cp_height):Math.min(midY, bottomY+cp_height)],
21603
- // Bottom -> Dest
21604
- // [Mirror previous cp]
21605
- [(destX+bottomX)/2,destY]
21606
- ];
21607
- if (cp[2][1] === topY+scy*cp_height) {
21608
- if (Math.abs(dy) < cp_height*10) {
21609
- cp[1][1] = topY-scy*cp_height/2;
21610
- cp[3][1] = bottomY-scy*cp_height/2;
21611
- }
21612
- cp[2][0] = topX;
21613
- }
21614
- return "M "+origX+" "+origY+
21615
- " C "+
21616
- cp[0][0]+" "+cp[0][1]+" "+
21617
- cp[1][0]+" "+cp[1][1]+" "+
21618
- topX+" "+topY+
21619
- " S "+
21620
- cp[2][0]+" "+cp[2][1]+" "+
21621
- midX+" "+midY+
21622
- " S "+
21623
- cp[3][0]+" "+cp[3][1]+" "+
21624
- bottomX+" "+bottomY+
21625
- " S "+
21615
+ return "M "+origX+" "+origY+
21616
+ " C "+
21617
+ cp[0][0]+" "+cp[0][1]+" "+
21618
+ cp[1][0]+" "+cp[1][1]+" "+
21619
+ cp[2][0]+" "+cp[2][1]+" "+
21620
+ " C " +
21621
+ cp[3][0]+" "+cp[3][1]+" "+
21626
21622
  cp[4][0]+" "+cp[4][1]+" "+
21627
- destX+" "+destY
21623
+ cp[5][0]+" "+cp[5][1]+" "+
21624
+ " h "+dx+
21625
+ " C "+
21626
+ cp[6][0]+" "+cp[6][1]+" "+
21627
+ cp[7][0]+" "+cp[7][1]+" "+
21628
+ cp[8][0]+" "+cp[8][1]+" "+
21629
+ " C " +
21630
+ cp[9][0]+" "+cp[9][1]+" "+
21631
+ cp[10][0]+" "+cp[10][1]+" "+
21632
+ cp[11][0]+" "+cp[11][1]+" "
21633
+ // +genCP(cp[0])+genCP(cp[1])+genCP(cp[2])+genCP(cp[3])+genCP(cp[4])
21634
+ // +genCP(cp[5])+genCP(cp[6])+genCP(cp[7])+genCP(cp[8])+genCP(cp[9])+genCP(cp[10])
21635
+ } else {
21636
+ var cp_height = node_height/2;
21637
+ var y1 = (destY + midY)/2
21638
+ topX = origX + sc*node_width*scale;
21639
+ topY = dy>0?Math.min(y1 - dy/2 , origY+cp_height):Math.max(y1 - dy/2 , origY-cp_height);
21640
+ bottomX = destX - sc*node_width*scale;
21641
+ bottomY = dy>0?Math.max(y1, destY-cp_height):Math.min(y1, destY+cp_height);
21642
+ var x1 = (origX+topX)/2;
21643
+ var scy = dy>0?1:-1;
21644
+ cp = [
21645
+ // Orig -> Top
21646
+ [x1,origY],
21647
+ [topX,dy>0?Math.max(origY, topY-cp_height):Math.min(origY, topY+cp_height)],
21648
+ // Top -> Mid
21649
+ // [Mirror previous cp]
21650
+ [x1,dy>0?Math.min(midY, topY+cp_height):Math.max(midY, topY-cp_height)],
21651
+ // Mid -> Bottom
21652
+ // [Mirror previous cp]
21653
+ [bottomX,dy>0?Math.max(midY, bottomY-cp_height):Math.min(midY, bottomY+cp_height)],
21654
+ // Bottom -> Dest
21655
+ // [Mirror previous cp]
21656
+ [(destX+bottomX)/2,destY]
21657
+ ];
21658
+ if (cp[2][1] === topY+scy*cp_height) {
21659
+ if (Math.abs(dy) < cp_height*10) {
21660
+ cp[1][1] = topY-scy*cp_height/2;
21661
+ cp[3][1] = bottomY-scy*cp_height/2;
21662
+ }
21663
+ cp[2][0] = topX;
21664
+ }
21665
+ return "M "+origX+" "+origY+
21666
+ " C "+
21667
+ cp[0][0]+" "+cp[0][1]+" "+
21668
+ cp[1][0]+" "+cp[1][1]+" "+
21669
+ topX+" "+topY+
21670
+ " S "+
21671
+ cp[2][0]+" "+cp[2][1]+" "+
21672
+ midX+" "+midY+
21673
+ " S "+
21674
+ cp[3][0]+" "+cp[3][1]+" "+
21675
+ bottomX+" "+bottomY+
21676
+ " S "+
21677
+ cp[4][0]+" "+cp[4][1]+" "+
21678
+ destX+" "+destY
21679
+
21680
+ // +genCP(cp[0])+genCP(cp[1])+genCP(cp[2])+genCP(cp[3])+genCP(cp[4])
21681
+ }
21628
21682
  }
21629
21683
  }
21630
21684
 
@@ -22241,7 +22295,7 @@ RED.view = (function() {
22241
22295
  var portY = -((numOutputs-1)/2)*13 +13*sourcePort;
22242
22296
 
22243
22297
  var sc = (drag_line.portType === PORT_TYPE_OUTPUT)?1:-1;
22244
- drag_line.el.attr("d",generateLinkPath(drag_line.node.x+sc*drag_line.node.w/2,drag_line.node.y+portY,mousePos[0],mousePos[1],sc));
22298
+ drag_line.el.attr("d",generateLinkPath(drag_line.node.x+sc*drag_line.node.w/2,drag_line.node.y+portY,mousePos[0],mousePos[1],sc, !!drag_line.node.status));
22245
22299
  }
22246
22300
  d3.event.preventDefault();
22247
22301
  } else if (mouse_mode == RED.state.MOVING) {
@@ -23565,22 +23619,38 @@ RED.view = (function() {
23565
23619
  }
23566
23620
  }
23567
23621
  document.body.style.cursor = "";
23622
+
23568
23623
  if (mouse_mode == RED.state.JOINING || mouse_mode == RED.state.QUICK_JOINING) {
23569
23624
  if (typeof TouchEvent != "undefined" && evt instanceof TouchEvent) {
23570
- var found = false;
23571
- RED.nodes.eachNode(function(n) {
23572
- if (n.z == RED.workspaces.active()) {
23573
- var hw = n.w/2;
23574
- var hh = n.h/2;
23575
- if (n.x-hw<mouse_position[0] && n.x+hw> mouse_position[0] &&
23576
- n.y-hh<mouse_position[1] && n.y+hh>mouse_position[1]) {
23577
- found = true;
23578
- mouseup_node = n;
23579
- portType = mouseup_node.inputs>0?PORT_TYPE_INPUT:PORT_TYPE_OUTPUT;
23580
- portIndex = 0;
23625
+ if (RED.view.DEBUG) { console.warn("portMouseUp: TouchEvent", mouse_mode,d,portType,portIndex); }
23626
+ const direction = drag_lines[0].portType === PORT_TYPE_INPUT ? PORT_TYPE_OUTPUT : PORT_TYPE_INPUT
23627
+ let found = false;
23628
+ for (let nodeIdx = 0; nodeIdx < activeNodes.length; nodeIdx++) {
23629
+ const n = activeNodes[nodeIdx];
23630
+ if (RED.view.tools.isPointInNode(n, mouse_position)) {
23631
+ found = true;
23632
+ mouseup_node = n;
23633
+ // portType = mouseup_node.inputs > 0 ? PORT_TYPE_INPUT : PORT_TYPE_OUTPUT;
23634
+ portType = direction;
23635
+ portIndex = 0;
23636
+ break
23637
+ }
23638
+ }
23639
+
23640
+ if (!found && drag_lines.length > 0 && !drag_lines[0].virtualLink) {
23641
+ for (let juncIdx = 0; juncIdx < activeJunctions.length; juncIdx++) {
23642
+ // NOTE: a junction is 10px x 10px but the target area is expanded to 30wx20h by adding padding to the bounding box
23643
+ const jNode = activeJunctions[juncIdx];
23644
+ if (RED.view.tools.isPointInNode(jNode, mouse_position, 20, 10)) {
23645
+ found = true;
23646
+ mouseup_node = jNode;
23647
+ portType = direction;
23648
+ portIndex = 0;
23649
+ break
23581
23650
  }
23582
23651
  }
23583
- });
23652
+ }
23653
+
23584
23654
  if (!found && activeSubflow) {
23585
23655
  var subflowPorts = [];
23586
23656
  if (activeSubflow.status) {
@@ -23592,16 +23662,13 @@ RED.view = (function() {
23592
23662
  if (activeSubflow.out) {
23593
23663
  subflowPorts = subflowPorts.concat(activeSubflow.out)
23594
23664
  }
23595
- for (var i=0;i<subflowPorts.length;i++) {
23596
- var n = subflowPorts[i];
23597
- var hw = n.w/2;
23598
- var hh = n.h/2;
23599
- if (n.x-hw<mouse_position[0] && n.x+hw> mouse_position[0] &&
23600
- n.y-hh<mouse_position[1] && n.y+hh>mouse_position[1]) {
23601
- found = true;
23602
- mouseup_node = n;
23603
- portType = mouseup_node.direction === "in"?PORT_TYPE_OUTPUT:PORT_TYPE_INPUT;
23604
- portIndex = 0;
23665
+ for (var i = 0; i < subflowPorts.length; i++) {
23666
+ const sf = subflowPorts[i];
23667
+ if (RED.view.tools.isPointInNode(sf, mouse_position)) {
23668
+ found = true;
23669
+ mouseup_node = sf;
23670
+ portType = mouseup_node.direction === "in" ? PORT_TYPE_OUTPUT : PORT_TYPE_INPUT;
23671
+ portIndex = 0;
23605
23672
  break;
23606
23673
  }
23607
23674
  }
@@ -24631,21 +24698,27 @@ RED.view = (function() {
24631
24698
  nodeEl.__statusGroup__.style.display = "none";
24632
24699
  } else {
24633
24700
  nodeEl.__statusGroup__.style.display = "inline";
24701
+ let backgroundWidth = 12
24634
24702
  var fill = status_colours[d.status.fill]; // Only allow our colours for now
24635
24703
  if (d.status.shape == null && fill == null) {
24704
+ backgroundWidth = 0
24636
24705
  nodeEl.__statusShape__.style.display = "none";
24706
+ nodeEl.__statusBackground__.setAttribute("x", 17)
24637
24707
  nodeEl.__statusGroup__.setAttribute("transform","translate(-14,"+(d.h+3)+")");
24638
24708
  } else {
24639
24709
  nodeEl.__statusGroup__.setAttribute("transform","translate(3,"+(d.h+3)+")");
24640
24710
  var statusClass = "red-ui-flow-node-status-"+(d.status.shape||"dot")+"-"+d.status.fill;
24641
24711
  nodeEl.__statusShape__.style.display = "inline";
24642
24712
  nodeEl.__statusShape__.setAttribute("class","red-ui-flow-node-status "+statusClass);
24713
+ nodeEl.__statusBackground__.setAttribute("x", 3)
24643
24714
  }
24644
24715
  if (d.status.hasOwnProperty('text')) {
24645
24716
  nodeEl.__statusLabel__.textContent = d.status.text;
24646
24717
  } else {
24647
24718
  nodeEl.__statusLabel__.textContent = "";
24648
24719
  }
24720
+ const textSize = nodeEl.__statusLabel__.getBBox()
24721
+ nodeEl.__statusBackground__.setAttribute('width', backgroundWidth + textSize.width + 6)
24649
24722
  }
24650
24723
  delete d.dirtyStatus;
24651
24724
  }
@@ -25051,17 +25124,30 @@ RED.view = (function() {
25051
25124
  statusEl.style.display = "none";
25052
25125
  node[0][0].__statusGroup__ = statusEl;
25053
25126
 
25054
- var statusRect = document.createElementNS("http://www.w3.org/2000/svg","rect");
25055
- statusRect.setAttribute("class","red-ui-flow-node-status");
25056
- statusRect.setAttribute("x",6);
25057
- statusRect.setAttribute("y",1);
25058
- statusRect.setAttribute("width",9);
25059
- statusRect.setAttribute("height",9);
25060
- statusRect.setAttribute("rx",2);
25061
- statusRect.setAttribute("ry",2);
25062
- statusRect.setAttribute("stroke-width","3");
25063
- statusEl.appendChild(statusRect);
25064
- node[0][0].__statusShape__ = statusRect;
25127
+ var statusBackground = document.createElementNS("http://www.w3.org/2000/svg","rect");
25128
+ statusBackground.setAttribute("class","red-ui-flow-node-status-background");
25129
+ statusBackground.setAttribute("x",3);
25130
+ statusBackground.setAttribute("y",-1);
25131
+ statusBackground.setAttribute("width",200);
25132
+ statusBackground.setAttribute("height",13);
25133
+ statusBackground.setAttribute("rx",1);
25134
+ statusBackground.setAttribute("ry",1);
25135
+
25136
+ statusEl.appendChild(statusBackground);
25137
+ node[0][0].__statusBackground__ = statusBackground;
25138
+
25139
+
25140
+ var statusIcon = document.createElementNS("http://www.w3.org/2000/svg","rect");
25141
+ statusIcon.setAttribute("class","red-ui-flow-node-status");
25142
+ statusIcon.setAttribute("x",6);
25143
+ statusIcon.setAttribute("y",1);
25144
+ statusIcon.setAttribute("width",9);
25145
+ statusIcon.setAttribute("height",9);
25146
+ statusIcon.setAttribute("rx",2);
25147
+ statusIcon.setAttribute("ry",2);
25148
+ statusIcon.setAttribute("stroke-width","3");
25149
+ statusEl.appendChild(statusIcon);
25150
+ node[0][0].__statusShape__ = statusIcon;
25065
25151
 
25066
25152
  var statusLabel = document.createElementNS("http://www.w3.org/2000/svg","text");
25067
25153
  statusLabel.setAttribute("class","red-ui-flow-node-status-label");
@@ -25467,16 +25553,25 @@ RED.view = (function() {
25467
25553
  contents.appendChild(junctionOutput);
25468
25554
  junctionOutput.addEventListener("mouseup", portMouseUpProxy);
25469
25555
  junctionOutput.addEventListener("mousedown", portMouseDownProxy);
25470
-
25471
25556
  junctionOutput.addEventListener("mouseover", junctionMouseOverProxy);
25472
25557
  junctionOutput.addEventListener("mouseout", junctionMouseOutProxy);
25558
+ junctionOutput.addEventListener("touchmove", junctionMouseOverProxy);
25559
+ junctionOutput.addEventListener("touchend", portMouseUpProxy);
25560
+ junctionOutput.addEventListener("touchstart", portMouseDownProxy);
25561
+
25473
25562
  junctionInput.addEventListener("mouseover", junctionMouseOverProxy);
25474
25563
  junctionInput.addEventListener("mouseout", junctionMouseOutProxy);
25564
+ junctionInput.addEventListener("touchmove", junctionMouseOverProxy);
25565
+ junctionInput.addEventListener("touchend", portMouseUpProxy);
25566
+ junctionInput.addEventListener("touchstart", portMouseDownProxy);
25567
+
25475
25568
  junctionBack.addEventListener("mouseover", junctionMouseOverProxy);
25476
25569
  junctionBack.addEventListener("mouseout", junctionMouseOutProxy);
25570
+ junctionBack.addEventListener("touchmove", junctionMouseOverProxy);
25477
25571
 
25478
25572
  // These handlers expect to be registered as d3 events
25479
25573
  d3.select(junctionBack).on("mousedown", nodeMouseDown).on("mouseup", nodeMouseUp);
25574
+ d3.select(junctionBack).on("touchstart", nodeMouseDown).on("touchend", nodeMouseUp);
25480
25575
 
25481
25576
  junction[0][0].appendChild(contents);
25482
25577
  })
@@ -25586,7 +25681,7 @@ RED.view = (function() {
25586
25681
  // " C "+(d.x1+scale*node_width)+" "+(d.y1+scaleY*node_height)+" "+
25587
25682
  // (d.x2-scale*node_width)+" "+(d.y2-scaleY*node_height)+" "+
25588
25683
  // d.x2+" "+d.y2;
25589
- var path = generateLinkPath(d.x1,d.y1,d.x2,d.y2,1);
25684
+ var path = generateLinkPath(d.x1,d.y1,d.x2,d.y2,1, !!(d.source.status || d.target.status));
25590
25685
  if (/NaN/.test(path)) {
25591
25686
  path = ""
25592
25687
  }
@@ -28467,6 +28562,39 @@ RED.view.tools = (function() {
28467
28562
  }
28468
28563
  }
28469
28564
 
28565
+ /**
28566
+ * Determine if a point is within a node
28567
+ * @param {*} node - A Node or Junction node
28568
+ * @param {[Number,Number]} mouse_position The x,y position of the mouse
28569
+ * @param {Number} [marginX=0] - A margin to add or deduct from the x position (to increase the hit area)
28570
+ * @param {Number} [marginY=0] - A margin to add or deduct from the y position (to increase the hit area)
28571
+ * @returns
28572
+ */
28573
+ function isPointInNode (node, [x, y], marginX, marginY) {
28574
+ marginX = marginX || 0
28575
+ marginY = marginY || 0
28576
+
28577
+ let w = node.w || 10 // junctions dont have any w or h value
28578
+ let h = node.h || 10
28579
+ let x1, x2, y1, y2
28580
+
28581
+ if (node.type === "junction" || node.type === "group") {
28582
+ // x/y is the top left of the node
28583
+ x1 = node.x
28584
+ y1 = node.y
28585
+ x2 = node.x + w
28586
+ y2 = node.y + h
28587
+ } else {
28588
+ // x/y is the center of the node
28589
+ const [xMid, yMid] = [w/2, h/2]
28590
+ x1 = node.x - xMid
28591
+ y1 = node.y - yMid
28592
+ x2 = node.x + xMid
28593
+ y2 = node.y + yMid
28594
+ }
28595
+ return (x >= (x1 - marginX) && x <= (x2 + marginX) && y >= (y1 - marginY) && y <= (y2 + marginY))
28596
+ }
28597
+
28470
28598
  return {
28471
28599
  init: function() {
28472
28600
  RED.actions.add("core:show-selected-node-labels", function() { setSelectedNodeLabelState(true); })
@@ -28549,7 +28677,8 @@ RED.view.tools = (function() {
28549
28677
  * @param {Number} dy
28550
28678
  */
28551
28679
  moveSelection: moveSelection,
28552
- calculateGridSnapOffsets: calculateGridSnapOffsets
28680
+ calculateGridSnapOffsets: calculateGridSnapOffsets,
28681
+ isPointInNode: isPointInNode
28553
28682
  }
28554
28683
 
28555
28684
  })();
@@ -32140,15 +32269,17 @@ RED.sidebar.context = (function() {
32140
32269
  RED.palette.editor = (function() {
32141
32270
 
32142
32271
  var disabled = false;
32143
-
32272
+ let catalogues = []
32273
+ const loadedCatalogs = []
32144
32274
  var editorTabs;
32145
- var filterInput;
32146
- var searchInput;
32147
- var nodeList;
32148
- var packageList;
32149
- var loadedList = [];
32150
- var filteredList = [];
32151
- var loadedIndex = {};
32275
+ let filterInput;
32276
+ let searchInput;
32277
+ let nodeList;
32278
+ let packageList;
32279
+ let fullList = []
32280
+ let loadedList = [];
32281
+ let filteredList = [];
32282
+ let loadedIndex = {};
32152
32283
 
32153
32284
  var typesInUse = {};
32154
32285
  var nodeEntries = {};
@@ -32286,7 +32417,6 @@ RED.palette.editor = (function() {
32286
32417
  }
32287
32418
  }
32288
32419
 
32289
-
32290
32420
  function getContrastingBorder(rgbColor){
32291
32421
  var parts = /^rgba?\(\s*(\d+),\s*(\d+),\s*(\d+)[,)]/.exec(rgbColor);
32292
32422
  if (parts) {
@@ -32493,10 +32623,10 @@ RED.palette.editor = (function() {
32493
32623
  var activeSort = sortModulesRelevance;
32494
32624
 
32495
32625
  function handleCatalogResponse(err,catalog,index,v) {
32626
+ const url = catalog.url
32496
32627
  catalogueLoadStatus.push(err||v);
32497
32628
  if (!err) {
32498
32629
  if (v.modules) {
32499
- var a = false;
32500
32630
  v.modules = v.modules.filter(function(m) {
32501
32631
  if (RED.utils.checkModuleAllowed(m.id,m.version,installAllowList,installDenyList)) {
32502
32632
  loadedIndex[m.id] = m;
@@ -32513,13 +32643,14 @@ RED.palette.editor = (function() {
32513
32643
  m.timestamp = 0;
32514
32644
  }
32515
32645
  m.index = m.index.join(",").toLowerCase();
32646
+ m.catalog = catalog;
32647
+ m.catalogIndex = index;
32516
32648
  return true;
32517
32649
  }
32518
32650
  return false;
32519
32651
  })
32520
32652
  loadedList = loadedList.concat(v.modules);
32521
32653
  }
32522
- searchInput.searchBox('count',loadedList.length);
32523
32654
  } else {
32524
32655
  catalogueLoadErrors = true;
32525
32656
  }
@@ -32528,7 +32659,7 @@ RED.palette.editor = (function() {
32528
32659
  }
32529
32660
  if (catalogueLoadStatus.length === catalogueCount) {
32530
32661
  if (catalogueLoadErrors) {
32531
- RED.notify(RED._('palette.editor.errors.catalogLoadFailed',{url: catalog}),"error",false,8000);
32662
+ RED.notify(RED._('palette.editor.errors.catalogLoadFailed',{url: url}),"error",false,8000);
32532
32663
  }
32533
32664
  var delta = 250-(Date.now() - catalogueLoadStart);
32534
32665
  setTimeout(function() {
@@ -32540,12 +32671,13 @@ RED.palette.editor = (function() {
32540
32671
 
32541
32672
  function initInstallTab() {
32542
32673
  if (loadedList.length === 0) {
32674
+ fullList = [];
32543
32675
  loadedList = [];
32544
32676
  loadedIndex = {};
32545
32677
  packageList.editableList('empty');
32546
32678
 
32547
32679
  $(".red-ui-palette-module-shade-status").text(RED._('palette.editor.loading'));
32548
- var catalogues = RED.settings.theme('palette.catalogues')||['https://catalogue.nodered.org/catalogue.json'];
32680
+
32549
32681
  catalogueLoadStatus = [];
32550
32682
  catalogueLoadErrors = false;
32551
32683
  catalogueCount = catalogues.length;
@@ -32555,21 +32687,90 @@ RED.palette.editor = (function() {
32555
32687
  $("#red-ui-palette-module-install-shade").show();
32556
32688
  catalogueLoadStart = Date.now();
32557
32689
  var handled = 0;
32558
- catalogues.forEach(function(catalog,index) {
32559
- $.getJSON(catalog, {_: new Date().getTime()},function(v) {
32560
- handleCatalogResponse(null,catalog,index,v);
32690
+ loadedCatalogs.length = 0; // clear the loadedCatalogs array
32691
+ for (let index = 0; index < catalogues.length; index++) {
32692
+ const url = catalogues[index];
32693
+ $.getJSON(url, {_: new Date().getTime()},function(v) {
32694
+ loadedCatalogs.push({ index: index, url: url, name: v.name, updated_at: v.updated_at, modules_count: (v.modules || []).length })
32695
+ handleCatalogResponse(null,{ url: url, name: v.name},index,v);
32561
32696
  refreshNodeModuleList();
32562
32697
  }).fail(function(jqxhr, textStatus, error) {
32563
- console.warn("Error loading catalog",catalog,":",error);
32564
- handleCatalogResponse(jqxhr,catalog,index);
32698
+ console.warn("Error loading catalog",url,":",error);
32699
+ handleCatalogResponse(jqxhr,url,index);
32565
32700
  }).always(function() {
32566
32701
  handled++;
32567
32702
  if (handled === catalogueCount) {
32568
- searchInput.searchBox('change');
32703
+ //sort loadedCatalogs by e.index ascending
32704
+ loadedCatalogs.sort((a, b) => a.index - b.index)
32705
+ updateCatalogFilter(loadedCatalogs)
32569
32706
  }
32570
32707
  })
32571
- });
32708
+ }
32709
+ }
32710
+ }
32711
+
32712
+ /**
32713
+ * Refreshes the catalog filter dropdown and updates local variables
32714
+ * @param {[{url:String, name:String, updated_at:String, modules_count:Number}]} catalogEntries
32715
+ */
32716
+ function updateCatalogFilter(catalogEntries, maxRetry = 3) {
32717
+ // clean up existing filters
32718
+ const catalogSelection = $('#red-catalogue-filter-select')
32719
+ if (catalogSelection.length === 0) {
32720
+ // sidebar not yet loaded (red-catalogue-filter-select is not in dom)
32721
+ if (maxRetry > 0) {
32722
+ // console.log("updateCatalogFilter: sidebar not yet loaded, retrying in 100ms")
32723
+ // try again in 100ms
32724
+ setTimeout(() => {
32725
+ updateCatalogFilter(catalogEntries, maxRetry - 1)
32726
+ }, 100);
32727
+ return;
32728
+ }
32729
+ return; // give up
32730
+ }
32731
+ catalogSelection.off("change") // remove any existing event handlers
32732
+ catalogSelection.attr('disabled', 'disabled')
32733
+ catalogSelection.empty()
32734
+ catalogSelection.append($('<option>', { value: "loading", text: RED._('palette.editor.loading'), disabled: true, selected: true }));
32735
+
32736
+ fullList = loadedList.slice()
32737
+ catalogSelection.empty() // clear the select list
32738
+
32739
+ // loop through catalogTypes, and an option entry per catalog
32740
+ for (let index = 0; index < catalogEntries.length; index++) {
32741
+ const catalog = catalogEntries[index];
32742
+ catalogSelection.append(`<option value="${catalog.name}">${catalog.name}</option>`)
32572
32743
  }
32744
+ // select the 1st option in the select list
32745
+ catalogSelection.val(catalogSelection.find('option:first').val())
32746
+
32747
+ // if there is only 1 catalog, hide the select
32748
+ if (catalogEntries.length > 1) {
32749
+ catalogSelection.prepend(`<option value="all">${RED._('palette.editor.allCatalogs')}</option>`)
32750
+ catalogSelection.removeAttr('disabled') // permit the user to select a catalog
32751
+ }
32752
+ // refresh the searchInput counter and trigger a change
32753
+ filterByCatalog(catalogSelection.val())
32754
+ searchInput.searchBox('change');
32755
+
32756
+ // hook up the change event handler
32757
+ catalogSelection.on("change", function() {
32758
+ const selectedCatalog = $(this).val();
32759
+ filterByCatalog(selectedCatalog);
32760
+ searchInput.searchBox('change');
32761
+ })
32762
+ }
32763
+
32764
+ function filterByCatalog(selectedCatalog) {
32765
+ if (loadedCatalogs.length <= 1 || selectedCatalog === "all") {
32766
+ loadedList = fullList.slice();
32767
+ } else {
32768
+ loadedList = fullList.filter(function(m) {
32769
+ return (m.catalog.name === selectedCatalog);
32770
+ })
32771
+ }
32772
+ refreshFilteredItems();
32773
+ searchInput.searchBox('count',filteredList.length+" / "+loadedList.length);
32573
32774
  }
32574
32775
 
32575
32776
  function refreshFilteredItems() {
@@ -32586,7 +32787,6 @@ RED.palette.editor = (function() {
32586
32787
  if (filteredList.length === 0) {
32587
32788
  packageList.editableList('addItem',{});
32588
32789
  }
32589
-
32590
32790
  if (filteredList.length > 10) {
32591
32791
  packageList.editableList('addItem',{start:10,more:filteredList.length-10})
32592
32792
  }
@@ -32616,6 +32816,7 @@ RED.palette.editor = (function() {
32616
32816
  var updateDenyList = [];
32617
32817
 
32618
32818
  function init() {
32819
+ catalogues = RED.settings.theme('palette.catalogues')||['https://catalogue.nodered.org/catalogue.json']
32619
32820
  if (RED.settings.get('externalModules.palette.allowInstall', true) === false) {
32620
32821
  return;
32621
32822
  }
@@ -32793,7 +32994,8 @@ RED.palette.editor = (function() {
32793
32994
  });
32794
32995
 
32795
32996
 
32796
- nodeList = $('<ol>',{id:"red-ui-palette-module-list", style:"position: absolute;top: 35px;bottom: 0;left: 0;right: 0px;"}).appendTo(modulesTab).editableList({
32997
+ nodeList = $('<ol>',{id:"red-ui-palette-module-list"}).appendTo(modulesTab).editableList({
32998
+ class: "scrollable",
32797
32999
  addButton: false,
32798
33000
  scrollOnAdd: false,
32799
33001
  sort: function(A,B) {
@@ -32924,21 +33126,20 @@ RED.palette.editor = (function() {
32924
33126
  $('<div>',{class:"red-ui-search-empty"}).text(RED._('search.empty')).appendTo(container);
32925
33127
  }
32926
33128
  }
32927
- });
33129
+ })
32928
33130
  }
32929
33131
 
32930
33132
  function createInstallTab(content) {
32931
- var installTab = $('<div>',{class:"red-ui-palette-editor-tab hide"}).appendTo(content);
32932
-
33133
+ const installTab = $('<div>',{class:"red-ui-palette-editor-tab", style: "display: none;"}).appendTo(content);
32933
33134
  editorTabs.addTab({
32934
33135
  id: 'install',
32935
33136
  label: RED._('palette.editor.tab-install'),
32936
33137
  content: installTab
32937
33138
  })
32938
33139
 
32939
- var toolBar = $('<div>',{class:"red-ui-palette-editor-toolbar"}).appendTo(installTab);
32940
-
32941
- var searchDiv = $('<div>',{class:"red-ui-palette-search"}).appendTo(installTab);
33140
+ const toolBar = $('<div>',{class:"red-ui-palette-editor-toolbar"}).appendTo(installTab);
33141
+
33142
+ const searchDiv = $('<div>',{class:"red-ui-palette-search"}).appendTo(installTab);
32942
33143
  searchInput = $('<input type="text" data-i18n="[placeholder]palette.search"></input>')
32943
33144
  .appendTo(searchDiv)
32944
33145
  .searchBox({
@@ -32955,19 +33156,25 @@ RED.palette.editor = (function() {
32955
33156
  searchInput.searchBox('count',loadedList.length);
32956
33157
  packageList.editableList('empty');
32957
33158
  packageList.editableList('addItem',{count:loadedList.length});
32958
-
32959
33159
  }
32960
33160
  }
32961
33161
  });
32962
33162
 
32963
- $('<span>').text(RED._("palette.editor.sort")+' ').appendTo(toolBar);
32964
- var sortGroup = $('<span class="button-group"></span>').appendTo(toolBar);
32965
- var sortRelevance = $('<a href="#" class="red-ui-palette-editor-install-sort-option red-ui-sidebar-header-button-toggle selected"><i class="fa fa-sort-amount-desc"></i></a>').appendTo(sortGroup);
32966
- var sortAZ = $('<a href="#" class="red-ui-palette-editor-install-sort-option red-ui-sidebar-header-button-toggle" data-i18n="palette.editor.sortAZ"></a>').appendTo(sortGroup);
32967
- var sortRecent = $('<a href="#" class="red-ui-palette-editor-install-sort-option red-ui-sidebar-header-button-toggle" data-i18n="palette.editor.sortRecent"></a>').appendTo(sortGroup);
33163
+ const catalogSelection = $('<select id="red-catalogue-filter-select">').appendTo(toolBar);
33164
+ catalogSelection.addClass('red-ui-palette-editor-catalogue-filter');
33165
+
33166
+ const toolBarActions = $('<div>',{class:"red-ui-palette-editor-toolbar-actions"}).appendTo(toolBar);
32968
33167
 
33168
+ $('<span>').text(RED._("palette.editor.sort")+' ').appendTo(toolBarActions);
33169
+ const sortGroup = $('<span class="button-group"></span>').appendTo(toolBarActions);
33170
+ const sortRelevance = $('<a href="#" class="red-ui-palette-editor-install-sort-option red-ui-sidebar-header-button-toggle selected"><i class="fa fa-sort-amount-desc"></i></a>').appendTo(sortGroup);
33171
+ const sortAZ = $('<a href="#" class="red-ui-palette-editor-install-sort-option red-ui-sidebar-header-button-toggle"><i class="fa fa-sort-alpha-asc"></i></a>').appendTo(sortGroup);
33172
+ const sortRecent = $('<a href="#" class="red-ui-palette-editor-install-sort-option red-ui-sidebar-header-button-toggle"><i class="fa fa-calendar"></i></a>').appendTo(sortGroup);
33173
+ RED.popover.tooltip(sortAZ,RED._("palette.editor.sortAZ"));
33174
+ RED.popover.tooltip(sortRecent,RED._("palette.editor.sortRecent"));
32969
33175
 
32970
- var sortOpts = [
33176
+
33177
+ const sortOpts = [
32971
33178
  {button: sortRelevance, func: sortModulesRelevance},
32972
33179
  {button: sortAZ, func: sortModulesAZ},
32973
33180
  {button: sortRecent, func: sortModulesRecent}
@@ -32985,7 +33192,7 @@ RED.palette.editor = (function() {
32985
33192
  });
32986
33193
  });
32987
33194
 
32988
- var refreshSpan = $('<span>').appendTo(toolBar);
33195
+ var refreshSpan = $('<span>').appendTo(toolBarActions);
32989
33196
  var refreshButton = $('<a href="#" class="red-ui-sidebar-header-button"><i class="fa fa-refresh"></i></a>').appendTo(refreshSpan);
32990
33197
  refreshButton.on("click", function(e) {
32991
33198
  e.preventDefault();
@@ -32995,7 +33202,8 @@ RED.palette.editor = (function() {
32995
33202
  })
32996
33203
  RED.popover.tooltip(refreshButton,RED._("palette.editor.refresh"));
32997
33204
 
32998
- packageList = $('<ol>',{style:"position: absolute;top: 79px;bottom: 0;left: 0;right: 0px;"}).appendTo(installTab).editableList({
33205
+ packageList = $('<ol>').appendTo(installTab).editableList({
33206
+ class: "scrollable",
32999
33207
  addButton: false,
33000
33208
  scrollOnAdd: false,
33001
33209
  addItem: function(container,i,object) {
@@ -33030,6 +33238,9 @@ RED.palette.editor = (function() {
33030
33238
  var metaRow = $('<div class="red-ui-palette-module-meta"></div>').appendTo(headerRow);
33031
33239
  $('<span class="red-ui-palette-module-version"><i class="fa fa-tag"></i> '+entry.version+'</span>').appendTo(metaRow);
33032
33240
  $('<span class="red-ui-palette-module-updated"><i class="fa fa-calendar"></i> '+formatUpdatedAt(entry.updated_at)+'</span>').appendTo(metaRow);
33241
+ if (loadedCatalogs.length > 1) {
33242
+ $('<span class="red-ui-palette-module-updated"><i class="fa fa-cubes"></i>' + (entry.catalog.name || entry.catalog.url) + '</span>').appendTo(metaRow);
33243
+ }
33033
33244
 
33034
33245
  var duplicateType = false;
33035
33246
  if (entry.types && entry.types.length > 0) {
@@ -33076,9 +33287,10 @@ RED.palette.editor = (function() {
33076
33287
  }
33077
33288
  }
33078
33289
  });
33290
+
33079
33291
 
33080
33292
  if (RED.settings.get('externalModules.palette.allowUpload', true) !== false) {
33081
- var uploadSpan = $('<span class="button-group">').prependTo(toolBar);
33293
+ var uploadSpan = $('<span class="button-group">').prependTo(toolBarActions);
33082
33294
  var uploadButton = $('<button type="button" class="red-ui-sidebar-header-button red-ui-palette-editor-upload-button"><label><i class="fa fa-upload"></i><form id="red-ui-palette-editor-upload-form" enctype="multipart/form-data"><input name="tarball" type="file" accept=".tgz"></label></button>').appendTo(uploadSpan);
33083
33295
 
33084
33296
  var uploadInput = uploadButton.find('input[type="file"]');
@@ -34074,7 +34286,10 @@ RED.editor = (function() {
34074
34286
  if (typeof editing_node[d] === "string" || typeof editing_node[d] === "number") {
34075
34287
  oldValues[d] = editing_node[d];
34076
34288
  } else {
34077
- oldValues[d] = $.extend(true,{},{v:editing_node[d]}).v;
34289
+ // Dont clone the group node `nodes` array
34290
+ if (editing_node.type !== 'group' || d !== "nodes") {
34291
+ oldValues[d] = $.extend(true,{},{v:editing_node[d]}).v;
34292
+ }
34078
34293
  }
34079
34294
  }
34080
34295
  }
@@ -46278,7 +46493,7 @@ RED.subflow = (function() {
46278
46493
  for (i=0; i<nodeList.length;i++) {
46279
46494
  if (nodeList[i].g && !includedGroups.has(nodeList[i].g)) {
46280
46495
  if (containingGroup !== nodeList[i].g) {
46281
- RED.notify("Cannot create subflow across multiple groups","error");
46496
+ RED.notify(RED._("subflow.errors.acrossMultipleGroups"), "error");
46282
46497
  return;
46283
46498
  }
46284
46499
  }