@node-red/editor-client 3.1.0-beta.2 → 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.
Files changed (46) hide show
  1. package/locales/en-US/editor.json +3 -0
  2. package/locales/fr/editor.json +1 -0
  3. package/locales/ja/editor.json +4 -1
  4. package/package.json +1 -1
  5. package/public/red/about +65 -0
  6. package/public/red/red.js +562 -278
  7. package/public/red/red.min.js +3 -3
  8. package/public/red/style.min.css +1 -1
  9. package/public/red/tours/3.0/welcome.js +58 -16
  10. package/public/red/tours/first-flow.js +14 -7
  11. package/public/red/tours/welcome.js +97 -26
  12. package/public/types/node/crypto.d.ts +13 -6
  13. package/public/types/node/dns.d.ts +1 -1
  14. package/public/types/node/globals.d.ts +1 -1
  15. package/public/types/node/stream.d.ts +99 -0
  16. package/public/types/node/test.d.ts +2 -2
  17. package/public/types/node-red/func.d.ts +2 -3
  18. package/public/vendor/mermaid/mermaid.min.js +713 -417
  19. package/public/vendor/monaco/dist/css.worker.js +1 -1
  20. package/public/vendor/monaco/dist/css.worker.js.LICENSE.txt +1 -1
  21. package/public/vendor/monaco/dist/editor.js +29 -1
  22. package/public/vendor/monaco/dist/editor.js.LICENSE.txt +1 -1
  23. package/public/vendor/monaco/dist/editor.worker.js +1 -1
  24. package/public/vendor/monaco/dist/fa2cc0ab9f0bec2b3365.ttf +0 -0
  25. package/public/vendor/monaco/dist/html.worker.js +1 -1
  26. package/public/vendor/monaco/dist/html.worker.js.LICENSE.txt +1 -1
  27. package/public/vendor/monaco/dist/json.worker.js +1 -1
  28. package/public/vendor/monaco/dist/json.worker.js.LICENSE.txt +1 -1
  29. package/public/vendor/monaco/dist/locale/cs.js +100 -39
  30. package/public/vendor/monaco/dist/locale/de.js +100 -39
  31. package/public/vendor/monaco/dist/locale/es.js +100 -39
  32. package/public/vendor/monaco/dist/locale/fr.js +100 -39
  33. package/public/vendor/monaco/dist/locale/it.js +100 -39
  34. package/public/vendor/monaco/dist/locale/ja.js +101 -40
  35. package/public/vendor/monaco/dist/locale/ko.js +100 -39
  36. package/public/vendor/monaco/dist/locale/pl.js +100 -39
  37. package/public/vendor/monaco/dist/locale/pt-br.js +102 -41
  38. package/public/vendor/monaco/dist/locale/qps-ploc.js +100 -39
  39. package/public/vendor/monaco/dist/locale/ru.js +100 -39
  40. package/public/vendor/monaco/dist/locale/tr.js +100 -39
  41. package/public/vendor/monaco/dist/locale/zh-hans.js +101 -40
  42. package/public/vendor/monaco/dist/locale/zh-hant.js +100 -39
  43. package/public/vendor/monaco/dist/theme/forge-dark.json +213 -0
  44. package/public/vendor/monaco/dist/theme/forge-light.json +227 -0
  45. package/public/vendor/monaco/dist/ts.worker.js +6 -1
  46. package/public/vendor/vendor.js +2 -2
package/public/red/red.js CHANGED
@@ -354,6 +354,7 @@ var RED = (function() {
354
354
  setTimeout(() => {
355
355
  RED.view.reveal(nodeToShow.id)
356
356
  window.location.hash = currentHash
357
+ RED.view.select(nodeToShow.id)
357
358
  if (showEditDialog) {
358
359
  RED.editor.edit(nodeToShow)
359
360
  }
@@ -364,6 +365,7 @@ var RED = (function() {
364
365
  if (nodeToShow) {
365
366
  RED.view.reveal(nodeToShow.id)
366
367
  window.location.hash = currentHash
368
+ RED.view.select(nodeToShow.id)
367
369
  if (showEditDialog) {
368
370
  RED.editor.editGroup(nodeToShow)
369
371
  }
@@ -1380,8 +1382,8 @@ RED.settings = (function () {
1380
1382
  if (!hasLocalStorage()) {
1381
1383
  return;
1382
1384
  }
1383
- if (key === "auth-tokens") {
1384
- localStorage.setItem(key, JSON.stringify(value));
1385
+ if (key.startsWith("auth-tokens")) {
1386
+ localStorage.setItem(key+this.authTokensSuffix, JSON.stringify(value));
1385
1387
  } else {
1386
1388
  RED.utils.setMessageProperty(userSettings,key,value);
1387
1389
  saveUserSettings();
@@ -1399,8 +1401,8 @@ RED.settings = (function () {
1399
1401
  if (!hasLocalStorage()) {
1400
1402
  return undefined;
1401
1403
  }
1402
- if (key === "auth-tokens") {
1403
- return JSON.parse(localStorage.getItem(key));
1404
+ if (key.startsWith("auth-tokens")) {
1405
+ return JSON.parse(localStorage.getItem(key+this.authTokensSuffix));
1404
1406
  } else {
1405
1407
  var v;
1406
1408
  try { v = RED.utils.getMessageProperty(userSettings,key); } catch(err) {}
@@ -1418,8 +1420,8 @@ RED.settings = (function () {
1418
1420
  if (!hasLocalStorage()) {
1419
1421
  return;
1420
1422
  }
1421
- if (key === "auth-tokens") {
1422
- localStorage.removeItem(key);
1423
+ if (key.startsWith("auth-tokens")) {
1424
+ localStorage.removeItem(key+this.authTokensSuffix);
1423
1425
  } else {
1424
1426
  delete userSettings[key];
1425
1427
  saveUserSettings();
@@ -1446,6 +1448,8 @@ RED.settings = (function () {
1446
1448
 
1447
1449
  var init = function (options, done) {
1448
1450
  var accessTokenMatch = /[?&]access_token=(.*?)(?:$|&)/.exec(window.location.search);
1451
+ var path=window.location.pathname.slice(0,-1);
1452
+ RED.settings.authTokensSuffix=path.replace(/\//g, '-');
1449
1453
  if (accessTokenMatch) {
1450
1454
  var accessToken = accessTokenMatch[1];
1451
1455
  RED.settings.set("auth-tokens",{access_token: accessToken});
@@ -5160,7 +5164,7 @@ RED.nodes = (function() {
5160
5164
  }
5161
5165
  }
5162
5166
  if (node.type !== "subflow") {
5163
- var convertedNode = RED.nodes.convertNode(node);
5167
+ var convertedNode = RED.nodes.convertNode(node, { credentials: false });
5164
5168
  for (var d in node._def.defaults) {
5165
5169
  if (node._def.defaults[d].type) {
5166
5170
  var nodeList = node[d];
@@ -5193,7 +5197,7 @@ RED.nodes = (function() {
5193
5197
  nns = nns.concat(createExportableNodeSet(node.nodes, exportedIds, exportedSubflows, exportedConfigNodes));
5194
5198
  }
5195
5199
  } else {
5196
- var convertedSubflow = convertSubflow(node);
5200
+ var convertedSubflow = convertSubflow(node, { credentials: false });
5197
5201
  nns.push(convertedSubflow);
5198
5202
  }
5199
5203
  }
@@ -5893,16 +5897,27 @@ RED.nodes = (function() {
5893
5897
  } else if (n.type.substring(0,7) === "subflow") {
5894
5898
  var parentId = n.type.split(":")[1];
5895
5899
  var subflow = subflow_denylist[parentId]||subflow_map[parentId]||getSubflow(parentId);
5896
- if (createNewIds || options.importMap[n.id] === "copy") {
5897
- parentId = subflow.id;
5898
- node.type = "subflow:"+parentId;
5899
- node._def = registry.getNodeType(node.type);
5900
- delete node.i;
5901
- }
5902
- node.name = n.name;
5903
- node.outputs = subflow.out.length;
5904
- node.inputs = subflow.in.length;
5905
- node.env = n.env;
5900
+ if (!subflow){
5901
+ node._def = {
5902
+ color:"#fee",
5903
+ defaults: {},
5904
+ label: "unknown: "+n.type,
5905
+ labelStyle: "red-ui-flow-node-label-italic",
5906
+ outputs: n.outputs|| (n.wires && n.wires.length) || 0,
5907
+ set: registry.getNodeSet("node-red/unknown")
5908
+ }
5909
+ } else {
5910
+ if (createNewIds || options.importMap[n.id] === "copy") {
5911
+ parentId = subflow.id;
5912
+ node.type = "subflow:"+parentId;
5913
+ node._def = registry.getNodeType(node.type);
5914
+ delete node.i;
5915
+ }
5916
+ node.name = n.name;
5917
+ node.outputs = subflow.out.length;
5918
+ node.inputs = subflow.in.length;
5919
+ node.env = n.env;
5920
+ }
5906
5921
  } else if (n.type === 'junction') {
5907
5922
  node._def = {defaults:{}}
5908
5923
  node._config.x = node.x
@@ -18908,7 +18923,10 @@ RED.keyboard = (function() {
18908
18923
  // One exception is shortcuts that include both Cmd and Ctrl. We don't
18909
18924
  // support them - but we need to make sure we don't block browser-specific
18910
18925
  // shortcuts (such as Cmd-Ctrl-F for fullscreen).
18911
- 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) {
18912
18930
  slot = slot.ctrl;
18913
18931
  }
18914
18932
  if (slot && evt.shiftKey) {
@@ -19150,7 +19168,11 @@ RED.keyboard = (function() {
19150
19168
  okButton.attr("disabled",!valid);
19151
19169
  });
19152
19170
 
19153
- var scopeSelect = $('<select><option value="*" data-i18n="keyboard.global"></option><option value="red-ui-workspace" data-i18n="keyboard.workspace"></option></select>').appendTo(scope);
19171
+ var scopeSelect = $('<select>'+
19172
+ '<option value="*" data-i18n="keyboard.global"></option>'+
19173
+ '<option value="red-ui-workspace" data-i18n="keyboard.workspace"></option>'+
19174
+ '<option value="red-ui-editor-stack" data-i18n="keyboard.editor"></option>'+
19175
+ '</select>').appendTo(scope);
19154
19176
  scopeSelect.i18n();
19155
19177
  if (object.scope === "workspace") {
19156
19178
  object.scope = "red-ui-workspace";
@@ -19407,7 +19429,11 @@ RED.keyboard = (function() {
19407
19429
  var new_env = [];
19408
19430
  var items = list.editableList('items');
19409
19431
  var credentials = gconf ? gconf.credentials : null;
19410
-
19432
+ if (!gconf && list.editableList('length') === 0) {
19433
+ // No existing global-config node and nothing in the list,
19434
+ // so no need to do anything more
19435
+ return
19436
+ }
19411
19437
  if (!credentials) {
19412
19438
  credentials = {
19413
19439
  _ : {},
@@ -19435,6 +19461,12 @@ RED.keyboard = (function() {
19435
19461
  if (gconf === null) {
19436
19462
  gconf = getGlobalConf(true);
19437
19463
  }
19464
+ if (!gconf.credentials) {
19465
+ gconf.credentials = {
19466
+ _ : {},
19467
+ map: {}
19468
+ };
19469
+ }
19438
19470
  if ((JSON.stringify(new_env) !== JSON.stringify(gconf.env)) ||
19439
19471
  (JSON.stringify(credentials) !== JSON.stringify(gconf.credentials))) {
19440
19472
  gconf.env = new_env;
@@ -20591,7 +20623,7 @@ RED.view = (function() {
20591
20623
 
20592
20624
  // Note: these are the permitted status colour aliases. The actual RGB values
20593
20625
  // are set in the CSS - flow.scss/colors.scss
20594
- var status_colours = {
20626
+ const status_colours = {
20595
20627
  "red": "#c00",
20596
20628
  "green": "#5a8",
20597
20629
  "yellow": "#F9DF31",
@@ -20600,19 +20632,32 @@ RED.view = (function() {
20600
20632
  "gray": "#d3d3d3"
20601
20633
  }
20602
20634
 
20603
- var PORT_TYPE_INPUT = 1;
20604
- var PORT_TYPE_OUTPUT = 0;
20635
+ const PORT_TYPE_INPUT = 1;
20636
+ const PORT_TYPE_OUTPUT = 0;
20605
20637
 
20606
- var chart;
20607
- 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
+ */
20608
20652
  var eventLayer;
20609
- var gridLayer;
20610
- var linkLayer;
20611
- var junctionLayer;
20612
- var dragGroupLayer;
20613
- var groupSelectLayer;
20614
- var nodeLayer;
20615
- 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;
20616
20661
  var drag_lines;
20617
20662
 
20618
20663
  const movingSet = (function() {
@@ -20679,7 +20724,13 @@ RED.view = (function() {
20679
20724
  set.unshift(...removed)
20680
20725
  }
20681
20726
  },
20682
- find: function(func) { return set.find(func) }
20727
+ find: function(func) { return set.find(func) },
20728
+ dump: function () {
20729
+ console.log('MovingSet Contents')
20730
+ api.forEach((n, i) => {
20731
+ console.log(`${i+1}\t${n.n.id}\t${n.n.type}`)
20732
+ })
20733
+ }
20683
20734
  }
20684
20735
  return api;
20685
20736
  })();
@@ -20716,6 +20767,63 @@ RED.view = (function() {
20716
20767
  return api
20717
20768
  })();
20718
20769
 
20770
+ const selectedGroups = (function() {
20771
+ let groups = new Set()
20772
+ const api = {
20773
+ add: function(g, includeNodes, addToMovingSet) {
20774
+ groups.add(g)
20775
+ if (!g.selected) {
20776
+ g.selected = true;
20777
+ g.dirty = true;
20778
+ }
20779
+ if (addToMovingSet !== false) {
20780
+ movingSet.add(g);
20781
+ }
20782
+ if (includeNodes) {
20783
+ var currentSet = new Set(movingSet.nodes());
20784
+ var allNodes = RED.group.getNodes(g,true);
20785
+ allNodes.forEach(function(n) {
20786
+ if (!currentSet.has(n)) {
20787
+ movingSet.add(n)
20788
+ }
20789
+ n.dirty = true;
20790
+ })
20791
+ }
20792
+ selectedLinks.clearUnselected()
20793
+ },
20794
+ remove: function(g) {
20795
+ groups.delete(g)
20796
+ if (g.selected) {
20797
+ g.selected = false;
20798
+ g.dirty = true;
20799
+ }
20800
+ const allNodes = RED.group.getNodes(g,true);
20801
+ const nodeSet = new Set(allNodes);
20802
+ nodeSet.add(g);
20803
+ for (let i = movingSet.length()-1; i >= 0; i -= 1) {
20804
+ const msn = movingSet.get(i);
20805
+ if (nodeSet.has(msn.n) || msn.n === g) {
20806
+ msn.n.selected = false;
20807
+ msn.n.dirty = true;
20808
+ movingSet.remove(msn.n,i)
20809
+ }
20810
+ }
20811
+ selectedLinks.clearUnselected()
20812
+ },
20813
+ length: () => groups.length,
20814
+ forEach: (func) => { groups.forEach(func) },
20815
+ toArray: () => [...groups],
20816
+ clear: function () {
20817
+ groups.forEach(g => {
20818
+ g.selected = false
20819
+ g.dirty = true
20820
+ })
20821
+ groups.clear()
20822
+ }
20823
+ }
20824
+ return api
20825
+ })()
20826
+
20719
20827
 
20720
20828
  function init() {
20721
20829
 
@@ -20818,16 +20926,6 @@ RED.view = (function() {
20818
20926
  touchStartTime = setTimeout(function() {
20819
20927
  touchStartTime = null;
20820
20928
  showTouchMenu(obj,pos);
20821
- //lasso = eventLayer.append("rect")
20822
- // .attr("ox",point[0])
20823
- // .attr("oy",point[1])
20824
- // .attr("rx",2)
20825
- // .attr("ry",2)
20826
- // .attr("x",point[0])
20827
- // .attr("y",point[1])
20828
- // .attr("width",0)
20829
- // .attr("height",0)
20830
- // .attr("class","nr-ui-view-lasso");
20831
20929
  },touchLongPressTimeout);
20832
20930
  }
20833
20931
  d3.event.preventDefault();
@@ -21460,7 +21558,7 @@ RED.view = (function() {
21460
21558
  })
21461
21559
  }
21462
21560
 
21463
- function generateLinkPath(origX,origY, destX, destY, sc) {
21561
+ function generateLinkPath(origX,origY, destX, destY, sc, hasStatus = false) {
21464
21562
  var dy = destY-origY;
21465
21563
  var dx = destX-origX;
21466
21564
  var delta = Math.sqrt(dy*dy+dx*dx);
@@ -21477,62 +21575,110 @@ RED.view = (function() {
21477
21575
  } else {
21478
21576
  scale = 0.4-0.2*(Math.max(0,(node_width-Math.min(Math.abs(dx),Math.abs(dy)))/node_width));
21479
21577
  }
21578
+ function genCP(cp) {
21579
+ return ` M ${cp[0]-5} ${cp[1]} h 10 M ${cp[0]} ${cp[1]-5} v 10 `
21580
+ }
21480
21581
  if (dx*sc > 0) {
21481
- return "M "+origX+" "+origY+
21482
- " C "+(origX+sc*(node_width*scale))+" "+(origY+scaleY*node_height)+" "+
21483
- (destX-sc*(scale)*node_width)+" "+(destY-scaleY*node_height)+" "+
21484
- 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])}`
21485
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
+ ]
21486
21614
 
21487
- var midX = Math.floor(destX-dx/2);
21488
- var midY = Math.floor(destY-dy/2);
21489
- //
21490
- if (dy === 0) {
21491
- midY = destY + node_height;
21492
- }
21493
- var cp_height = node_height/2;
21494
- var y1 = (destY + midY)/2
21495
- var topX =origX + sc*node_width*scale;
21496
- var topY = dy>0?Math.min(y1 - dy/2 , origY+cp_height):Math.max(y1 - dy/2 , origY-cp_height);
21497
- var bottomX = destX - sc*node_width*scale;
21498
- var bottomY = dy>0?Math.max(y1, destY-cp_height):Math.min(y1, destY+cp_height);
21499
- var x1 = (origX+topX)/2;
21500
- var scy = dy>0?1:-1;
21501
- var cp = [
21502
- // Orig -> Top
21503
- [x1,origY],
21504
- [topX,dy>0?Math.max(origY, topY-cp_height):Math.min(origY, topY+cp_height)],
21505
- // Top -> Mid
21506
- // [Mirror previous cp]
21507
- [x1,dy>0?Math.min(midY, topY+cp_height):Math.max(midY, topY-cp_height)],
21508
- // Mid -> Bottom
21509
- // [Mirror previous cp]
21510
- [bottomX,dy>0?Math.max(midY, bottomY-cp_height):Math.min(midY, bottomY+cp_height)],
21511
- // Bottom -> Dest
21512
- // [Mirror previous cp]
21513
- [(destX+bottomX)/2,destY]
21514
- ];
21515
- if (cp[2][1] === topY+scy*cp_height) {
21516
- if (Math.abs(dy) < cp_height*10) {
21517
- cp[1][1] = topY-scy*cp_height/2;
21518
- cp[3][1] = bottomY-scy*cp_height/2;
21519
- }
21520
- cp[2][0] = topX;
21521
- }
21522
- return "M "+origX+" "+origY+
21523
- " C "+
21524
- cp[0][0]+" "+cp[0][1]+" "+
21525
- cp[1][0]+" "+cp[1][1]+" "+
21526
- topX+" "+topY+
21527
- " S "+
21528
- cp[2][0]+" "+cp[2][1]+" "+
21529
- midX+" "+midY+
21530
- " S "+
21531
- cp[3][0]+" "+cp[3][1]+" "+
21532
- bottomX+" "+bottomY+
21533
- " 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]+" "+
21534
21622
  cp[4][0]+" "+cp[4][1]+" "+
21535
- 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
+ }
21536
21682
  }
21537
21683
  }
21538
21684
 
@@ -21620,7 +21766,7 @@ RED.view = (function() {
21620
21766
  var touchTrigger = options.touchTrigger;
21621
21767
 
21622
21768
  if (targetGroup) {
21623
- selectGroup(targetGroup,false);
21769
+ selectedGroups.add(targetGroup,false);
21624
21770
  RED.view.redraw();
21625
21771
  }
21626
21772
 
@@ -21946,7 +22092,7 @@ RED.view = (function() {
21946
22092
  clearSelection();
21947
22093
  nn.selected = true;
21948
22094
  if (targetGroup) {
21949
- selectGroup(targetGroup,false);
22095
+ selectedGroups.add(targetGroup,false);
21950
22096
  }
21951
22097
  movingSet.add(nn);
21952
22098
  updateActiveNodes();
@@ -22149,7 +22295,7 @@ RED.view = (function() {
22149
22295
  var portY = -((numOutputs-1)/2)*13 +13*sourcePort;
22150
22296
 
22151
22297
  var sc = (drag_line.portType === PORT_TYPE_OUTPUT)?1:-1;
22152
- 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));
22153
22299
  }
22154
22300
  d3.event.preventDefault();
22155
22301
  } else if (mouse_mode == RED.state.MOVING) {
@@ -22410,10 +22556,7 @@ RED.view = (function() {
22410
22556
  if (!movingSet.has(n) && !n.selected) {
22411
22557
  // group entirely within lasso
22412
22558
  if (n.x > x && n.y > y && n.x + n.w < x2 && n.y + n.h < y2) {
22413
- n.selected = true
22414
- n.dirty = true
22415
- var groupNodes = RED.group.getNodes(n,true);
22416
- groupNodes.forEach(gn => movingSet.add(gn))
22559
+ selectedGroups.add(n, true)
22417
22560
  }
22418
22561
  }
22419
22562
  })
@@ -22763,7 +22906,7 @@ RED.view = (function() {
22763
22906
  clearSelection();
22764
22907
  activeGroups.forEach(function(g) {
22765
22908
  if (!g.g) {
22766
- selectGroup(g, true);
22909
+ selectedGroups.add(g, true);
22767
22910
  if (!g.selected) {
22768
22911
  g.selected = true;
22769
22912
  g.dirty = true;
@@ -22833,10 +22976,7 @@ RED.view = (function() {
22833
22976
  }
22834
22977
  movingSet.clear();
22835
22978
  selectedLinks.clear();
22836
- activeGroups.forEach(function(g) {
22837
- g.selected = false;
22838
- g.dirty = true;
22839
- })
22979
+ selectedGroups.clear();
22840
22980
  }
22841
22981
 
22842
22982
  var lastSelection = null;
@@ -23093,6 +23233,16 @@ RED.view = (function() {
23093
23233
  var result = RED.nodes.removeJunction(node)
23094
23234
  removedJunctions.push(node);
23095
23235
  removedLinks = removedLinks.concat(result.links);
23236
+ if (node.g) {
23237
+ var group = RED.nodes.group(node.g);
23238
+ if (selectedGroups.indexOf(group) === -1) {
23239
+ // Don't use RED.group.removeFromGroup as that emits
23240
+ // a change event on the node - but we're deleting it
23241
+ var index = group.nodes.indexOf(node);
23242
+ group.nodes.splice(index,1);
23243
+ RED.group.markDirty(group);
23244
+ }
23245
+ }
23096
23246
  } else {
23097
23247
  if (node.direction === "out") {
23098
23248
  removedSubflowOutputs.push(node);
@@ -23469,22 +23619,38 @@ RED.view = (function() {
23469
23619
  }
23470
23620
  }
23471
23621
  document.body.style.cursor = "";
23622
+
23472
23623
  if (mouse_mode == RED.state.JOINING || mouse_mode == RED.state.QUICK_JOINING) {
23473
23624
  if (typeof TouchEvent != "undefined" && evt instanceof TouchEvent) {
23474
- var found = false;
23475
- RED.nodes.eachNode(function(n) {
23476
- if (n.z == RED.workspaces.active()) {
23477
- var hw = n.w/2;
23478
- var hh = n.h/2;
23479
- if (n.x-hw<mouse_position[0] && n.x+hw> mouse_position[0] &&
23480
- n.y-hh<mouse_position[1] && n.y+hh>mouse_position[1]) {
23481
- found = true;
23482
- mouseup_node = n;
23483
- portType = mouseup_node.inputs>0?PORT_TYPE_INPUT:PORT_TYPE_OUTPUT;
23484
- 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
23485
23650
  }
23486
23651
  }
23487
- });
23652
+ }
23653
+
23488
23654
  if (!found && activeSubflow) {
23489
23655
  var subflowPorts = [];
23490
23656
  if (activeSubflow.status) {
@@ -23496,16 +23662,13 @@ RED.view = (function() {
23496
23662
  if (activeSubflow.out) {
23497
23663
  subflowPorts = subflowPorts.concat(activeSubflow.out)
23498
23664
  }
23499
- for (var i=0;i<subflowPorts.length;i++) {
23500
- var n = subflowPorts[i];
23501
- var hw = n.w/2;
23502
- var hh = n.h/2;
23503
- if (n.x-hw<mouse_position[0] && n.x+hw> mouse_position[0] &&
23504
- n.y-hh<mouse_position[1] && n.y+hh>mouse_position[1]) {
23505
- found = true;
23506
- mouseup_node = n;
23507
- portType = mouseup_node.direction === "in"?PORT_TYPE_OUTPUT:PORT_TYPE_INPUT;
23508
- 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;
23509
23672
  break;
23510
23673
  }
23511
23674
  }
@@ -23915,7 +24078,7 @@ RED.view = (function() {
23915
24078
  if (!groupNodeSelectPrimed && !d.selected && d.g && RED.nodes.group(d.g).selected) {
23916
24079
  clearSelection();
23917
24080
 
23918
- selectGroup(RED.nodes.group(d.g), false);
24081
+ selectedGroups.add(RED.nodes.group(d.g), false);
23919
24082
 
23920
24083
  mousedown_node.selected = true;
23921
24084
  movingSet.add(mousedown_node);
@@ -24336,14 +24499,14 @@ RED.view = (function() {
24336
24499
  lastClickNode = g;
24337
24500
 
24338
24501
  if (g.selected && (d3.event.ctrlKey||d3.event.metaKey)) {
24339
- deselectGroup(g);
24502
+ selectedGroups.remove(g);
24340
24503
  d3.event.stopPropagation();
24341
24504
  } else {
24342
24505
  if (!g.selected) {
24343
24506
  if (!d3.event.ctrlKey && !d3.event.metaKey) {
24344
24507
  clearSelection();
24345
24508
  }
24346
- selectGroup(g,true);//!wasSelected);
24509
+ selectedGroups.add(g,true);//!wasSelected);
24347
24510
  }
24348
24511
 
24349
24512
  if (d3.event.button != 2) {
@@ -24359,45 +24522,6 @@ RED.view = (function() {
24359
24522
  d3.event.stopPropagation();
24360
24523
  }
24361
24524
 
24362
- function selectGroup(g, includeNodes, addToMovingSet) {
24363
- if (!g.selected) {
24364
- g.selected = true;
24365
- g.dirty = true;
24366
- }
24367
- if (addToMovingSet !== false) {
24368
- movingSet.add(g);
24369
- }
24370
- if (includeNodes) {
24371
- var currentSet = new Set(movingSet.nodes());
24372
- var allNodes = RED.group.getNodes(g,true);
24373
- allNodes.forEach(function(n) {
24374
- if (!currentSet.has(n)) {
24375
- movingSet.add(n)
24376
- }
24377
- n.dirty = true;
24378
- })
24379
- }
24380
- selectedLinks.clearUnselected()
24381
- }
24382
-
24383
- function deselectGroup(g) {
24384
- if (g.selected) {
24385
- g.selected = false;
24386
- g.dirty = true;
24387
- }
24388
- const allNodes = RED.group.getNodes(g,true);
24389
- const nodeSet = new Set(allNodes);
24390
- nodeSet.add(g);
24391
- for (let i = movingSet.length()-1; i >= 0; i -= 1) {
24392
- const msn = movingSet.get(i);
24393
- if (nodeSet.has(msn.n) || msn.n === g) {
24394
- msn.n.selected = false;
24395
- msn.n.dirty = true;
24396
- movingSet.remove(msn.n,i)
24397
- }
24398
- }
24399
- selectedLinks.clearUnselected()
24400
- }
24401
24525
  function getGroupAt(x, y, ignoreSelected) {
24402
24526
  // x,y expected to be in node-co-ordinate space
24403
24527
  var candidateGroups = {};
@@ -24574,21 +24698,27 @@ RED.view = (function() {
24574
24698
  nodeEl.__statusGroup__.style.display = "none";
24575
24699
  } else {
24576
24700
  nodeEl.__statusGroup__.style.display = "inline";
24701
+ let backgroundWidth = 12
24577
24702
  var fill = status_colours[d.status.fill]; // Only allow our colours for now
24578
24703
  if (d.status.shape == null && fill == null) {
24704
+ backgroundWidth = 0
24579
24705
  nodeEl.__statusShape__.style.display = "none";
24706
+ nodeEl.__statusBackground__.setAttribute("x", 17)
24580
24707
  nodeEl.__statusGroup__.setAttribute("transform","translate(-14,"+(d.h+3)+")");
24581
24708
  } else {
24582
24709
  nodeEl.__statusGroup__.setAttribute("transform","translate(3,"+(d.h+3)+")");
24583
24710
  var statusClass = "red-ui-flow-node-status-"+(d.status.shape||"dot")+"-"+d.status.fill;
24584
24711
  nodeEl.__statusShape__.style.display = "inline";
24585
24712
  nodeEl.__statusShape__.setAttribute("class","red-ui-flow-node-status "+statusClass);
24713
+ nodeEl.__statusBackground__.setAttribute("x", 3)
24586
24714
  }
24587
24715
  if (d.status.hasOwnProperty('text')) {
24588
24716
  nodeEl.__statusLabel__.textContent = d.status.text;
24589
24717
  } else {
24590
24718
  nodeEl.__statusLabel__.textContent = "";
24591
24719
  }
24720
+ const textSize = nodeEl.__statusLabel__.getBBox()
24721
+ nodeEl.__statusBackground__.setAttribute('width', backgroundWidth + textSize.width + 6)
24592
24722
  }
24593
24723
  delete d.dirtyStatus;
24594
24724
  }
@@ -24836,6 +24966,7 @@ RED.view = (function() {
24836
24966
  this.__port__.setAttribute("transform","translate(-5,"+((d.h/2)-5)+")");
24837
24967
  this.__outputOutput__.setAttribute("transform","translate(20,"+((d.h/2)-8)+")");
24838
24968
  this.__outputNumber__.setAttribute("transform","translate(20,"+((d.h/2)+7)+")");
24969
+ this.__outputNumber__.textContent = d.i+1;
24839
24970
  }
24840
24971
  d.dirty = false;
24841
24972
  }
@@ -24993,17 +25124,30 @@ RED.view = (function() {
24993
25124
  statusEl.style.display = "none";
24994
25125
  node[0][0].__statusGroup__ = statusEl;
24995
25126
 
24996
- var statusRect = document.createElementNS("http://www.w3.org/2000/svg","rect");
24997
- statusRect.setAttribute("class","red-ui-flow-node-status");
24998
- statusRect.setAttribute("x",6);
24999
- statusRect.setAttribute("y",1);
25000
- statusRect.setAttribute("width",9);
25001
- statusRect.setAttribute("height",9);
25002
- statusRect.setAttribute("rx",2);
25003
- statusRect.setAttribute("ry",2);
25004
- statusRect.setAttribute("stroke-width","3");
25005
- statusEl.appendChild(statusRect);
25006
- 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;
25007
25151
 
25008
25152
  var statusLabel = document.createElementNS("http://www.w3.org/2000/svg","text");
25009
25153
  statusLabel.setAttribute("class","red-ui-flow-node-status-label");
@@ -25409,16 +25553,25 @@ RED.view = (function() {
25409
25553
  contents.appendChild(junctionOutput);
25410
25554
  junctionOutput.addEventListener("mouseup", portMouseUpProxy);
25411
25555
  junctionOutput.addEventListener("mousedown", portMouseDownProxy);
25412
-
25413
25556
  junctionOutput.addEventListener("mouseover", junctionMouseOverProxy);
25414
25557
  junctionOutput.addEventListener("mouseout", junctionMouseOutProxy);
25558
+ junctionOutput.addEventListener("touchmove", junctionMouseOverProxy);
25559
+ junctionOutput.addEventListener("touchend", portMouseUpProxy);
25560
+ junctionOutput.addEventListener("touchstart", portMouseDownProxy);
25561
+
25415
25562
  junctionInput.addEventListener("mouseover", junctionMouseOverProxy);
25416
25563
  junctionInput.addEventListener("mouseout", junctionMouseOutProxy);
25564
+ junctionInput.addEventListener("touchmove", junctionMouseOverProxy);
25565
+ junctionInput.addEventListener("touchend", portMouseUpProxy);
25566
+ junctionInput.addEventListener("touchstart", portMouseDownProxy);
25567
+
25417
25568
  junctionBack.addEventListener("mouseover", junctionMouseOverProxy);
25418
25569
  junctionBack.addEventListener("mouseout", junctionMouseOutProxy);
25570
+ junctionBack.addEventListener("touchmove", junctionMouseOverProxy);
25419
25571
 
25420
25572
  // These handlers expect to be registered as d3 events
25421
25573
  d3.select(junctionBack).on("mousedown", nodeMouseDown).on("mouseup", nodeMouseUp);
25574
+ d3.select(junctionBack).on("touchstart", nodeMouseDown).on("touchend", nodeMouseUp);
25422
25575
 
25423
25576
  junction[0][0].appendChild(contents);
25424
25577
  })
@@ -25528,7 +25681,7 @@ RED.view = (function() {
25528
25681
  // " C "+(d.x1+scale*node_width)+" "+(d.y1+scaleY*node_height)+" "+
25529
25682
  // (d.x2-scale*node_width)+" "+(d.y2-scaleY*node_height)+" "+
25530
25683
  // d.x2+" "+d.y2;
25531
- 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));
25532
25685
  if (/NaN/.test(path)) {
25533
25686
  path = ""
25534
25687
  }
@@ -26378,11 +26531,10 @@ RED.view = (function() {
26378
26531
  if (movingSet.length() > 0) {
26379
26532
  movingSet.forEach(function(n) {
26380
26533
  if (n.n.type !== 'group') {
26381
- allNodes.add(n.n);
26534
+ allNodes.add(n.n);
26382
26535
  }
26383
26536
  });
26384
26537
  }
26385
- var selectedGroups = activeGroups.filter(function(g) { return g.selected });
26386
26538
  selectedGroups.forEach(function(g) {
26387
26539
  var groupNodes = RED.group.getNodes(g,true);
26388
26540
  groupNodes.forEach(function(n) {
@@ -26576,6 +26728,13 @@ RED.view = (function() {
26576
26728
  selectedNode.dirty = true;
26577
26729
  movingSet.clear();
26578
26730
  movingSet.add(selectedNode);
26731
+ } else {
26732
+ selectedNode = RED.nodes.group(selection);
26733
+ if (selectedNode) {
26734
+ movingSet.clear();
26735
+ selectedGroups.clear()
26736
+ selectedGroups.add(selectedNode)
26737
+ }
26579
26738
  }
26580
26739
  } else if (selection) {
26581
26740
  if (selection.nodes) {
@@ -26591,7 +26750,7 @@ RED.view = (function() {
26591
26750
  n.dirty = true;
26592
26751
  movingSet.add(n);
26593
26752
  } else {
26594
- selectGroup(n,true);
26753
+ selectedGroups.add(n,true);
26595
26754
  }
26596
26755
  })
26597
26756
  }
@@ -26759,8 +26918,12 @@ RED.view = (function() {
26759
26918
  })
26760
26919
  },
26761
26920
  scroll: function(x,y) {
26762
- chart.scrollLeft(chart.scrollLeft()+x);
26763
- chart.scrollTop(chart.scrollTop()+y)
26921
+ if (x !== undefined && y !== undefined) {
26922
+ chart.scrollLeft(chart.scrollLeft()+x);
26923
+ chart.scrollTop(chart.scrollTop()+y)
26924
+ } else {
26925
+ return [chart.scrollLeft(), chart.scrollTop()]
26926
+ }
26764
26927
  },
26765
26928
  clickNodeButton: function(n) {
26766
26929
  if (n._def.button) {
@@ -28399,6 +28562,39 @@ RED.view.tools = (function() {
28399
28562
  }
28400
28563
  }
28401
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
+
28402
28598
  return {
28403
28599
  init: function() {
28404
28600
  RED.actions.add("core:show-selected-node-labels", function() { setSelectedNodeLabelState(true); })
@@ -28481,7 +28677,8 @@ RED.view.tools = (function() {
28481
28677
  * @param {Number} dy
28482
28678
  */
28483
28679
  moveSelection: moveSelection,
28484
- calculateGridSnapOffsets: calculateGridSnapOffsets
28680
+ calculateGridSnapOffsets: calculateGridSnapOffsets,
28681
+ isPointInNode: isPointInNode
28485
28682
  }
28486
28683
 
28487
28684
  })();
@@ -28946,23 +29143,15 @@ RED.palette = (function() {
28946
29143
  }
28947
29144
  metaData += type;
28948
29145
 
29146
+ const safeType = type.replace(/'/g,"\\'");
29147
+ const searchType = type.indexOf(' ') > -1 ? '&quot;' + type + '&quot;' : type
29148
+
28949
29149
  if (/^subflow:/.test(type)) {
28950
29150
  $('<button type="button" onclick="RED.workspaces.show(\''+type.substring(8).replace(/'/g,"\\'")+'\'); return false;" class="red-ui-button red-ui-button-small" style="float: right; margin-left: 5px;"><i class="fa fa-pencil"></i></button>').appendTo(popOverContent)
28951
29151
  }
28952
29152
 
28953
- const safeType = type.replace(/'/g,"\\'");
28954
- const wrapStr = function (str) {
28955
- if(str.indexOf(' ') >= 0) {
28956
- return '"' + str + '"'
28957
- }
28958
- return str
28959
- }
29153
+ $('<button type="button" onclick="RED.search.show(\'type:'+searchType+'\'); return false;" class="red-ui-button red-ui-button-small" style="float: right; margin-left: 5px;"><i class="fa fa-search"></i></button>').appendTo(popOverContent)
28960
29154
 
28961
- $('<button type="button"; return false;" class="red-ui-button red-ui-button-small" style="float: right; margin-left: 5px;"><i class="fa fa-search"></i></button>')
28962
- .appendTo(popOverContent)
28963
- .on('click', function() {
28964
- RED.search.show('type:' + wrapStr(safeType))
28965
- })
28966
29155
  $('<button type="button" onclick="RED.sidebar.help.show(\''+safeType+'\'); return false;" class="red-ui-button red-ui-button-small" style="float: right; margin-left: 5px;"><i class="fa fa-book"></i></button>').appendTo(popOverContent)
28967
29156
 
28968
29157
  $('<p>',{style:"font-size: 0.8em"}).text(metaData).appendTo(popOverContent);
@@ -32080,15 +32269,17 @@ RED.sidebar.context = (function() {
32080
32269
  RED.palette.editor = (function() {
32081
32270
 
32082
32271
  var disabled = false;
32083
-
32272
+ let catalogues = []
32273
+ const loadedCatalogs = []
32084
32274
  var editorTabs;
32085
- var filterInput;
32086
- var searchInput;
32087
- var nodeList;
32088
- var packageList;
32089
- var loadedList = [];
32090
- var filteredList = [];
32091
- 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 = {};
32092
32283
 
32093
32284
  var typesInUse = {};
32094
32285
  var nodeEntries = {};
@@ -32226,7 +32417,6 @@ RED.palette.editor = (function() {
32226
32417
  }
32227
32418
  }
32228
32419
 
32229
-
32230
32420
  function getContrastingBorder(rgbColor){
32231
32421
  var parts = /^rgba?\(\s*(\d+),\s*(\d+),\s*(\d+)[,)]/.exec(rgbColor);
32232
32422
  if (parts) {
@@ -32433,10 +32623,10 @@ RED.palette.editor = (function() {
32433
32623
  var activeSort = sortModulesRelevance;
32434
32624
 
32435
32625
  function handleCatalogResponse(err,catalog,index,v) {
32626
+ const url = catalog.url
32436
32627
  catalogueLoadStatus.push(err||v);
32437
32628
  if (!err) {
32438
32629
  if (v.modules) {
32439
- var a = false;
32440
32630
  v.modules = v.modules.filter(function(m) {
32441
32631
  if (RED.utils.checkModuleAllowed(m.id,m.version,installAllowList,installDenyList)) {
32442
32632
  loadedIndex[m.id] = m;
@@ -32453,13 +32643,14 @@ RED.palette.editor = (function() {
32453
32643
  m.timestamp = 0;
32454
32644
  }
32455
32645
  m.index = m.index.join(",").toLowerCase();
32646
+ m.catalog = catalog;
32647
+ m.catalogIndex = index;
32456
32648
  return true;
32457
32649
  }
32458
32650
  return false;
32459
32651
  })
32460
32652
  loadedList = loadedList.concat(v.modules);
32461
32653
  }
32462
- searchInput.searchBox('count',loadedList.length);
32463
32654
  } else {
32464
32655
  catalogueLoadErrors = true;
32465
32656
  }
@@ -32468,7 +32659,7 @@ RED.palette.editor = (function() {
32468
32659
  }
32469
32660
  if (catalogueLoadStatus.length === catalogueCount) {
32470
32661
  if (catalogueLoadErrors) {
32471
- 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);
32472
32663
  }
32473
32664
  var delta = 250-(Date.now() - catalogueLoadStart);
32474
32665
  setTimeout(function() {
@@ -32480,12 +32671,13 @@ RED.palette.editor = (function() {
32480
32671
 
32481
32672
  function initInstallTab() {
32482
32673
  if (loadedList.length === 0) {
32674
+ fullList = [];
32483
32675
  loadedList = [];
32484
32676
  loadedIndex = {};
32485
32677
  packageList.editableList('empty');
32486
32678
 
32487
32679
  $(".red-ui-palette-module-shade-status").text(RED._('palette.editor.loading'));
32488
- var catalogues = RED.settings.theme('palette.catalogues')||['https://catalogue.nodered.org/catalogue.json'];
32680
+
32489
32681
  catalogueLoadStatus = [];
32490
32682
  catalogueLoadErrors = false;
32491
32683
  catalogueCount = catalogues.length;
@@ -32495,21 +32687,90 @@ RED.palette.editor = (function() {
32495
32687
  $("#red-ui-palette-module-install-shade").show();
32496
32688
  catalogueLoadStart = Date.now();
32497
32689
  var handled = 0;
32498
- catalogues.forEach(function(catalog,index) {
32499
- $.getJSON(catalog, {_: new Date().getTime()},function(v) {
32500
- 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);
32501
32696
  refreshNodeModuleList();
32502
32697
  }).fail(function(jqxhr, textStatus, error) {
32503
- console.warn("Error loading catalog",catalog,":",error);
32504
- handleCatalogResponse(jqxhr,catalog,index);
32698
+ console.warn("Error loading catalog",url,":",error);
32699
+ handleCatalogResponse(jqxhr,url,index);
32505
32700
  }).always(function() {
32506
32701
  handled++;
32507
32702
  if (handled === catalogueCount) {
32508
- searchInput.searchBox('change');
32703
+ //sort loadedCatalogs by e.index ascending
32704
+ loadedCatalogs.sort((a, b) => a.index - b.index)
32705
+ updateCatalogFilter(loadedCatalogs)
32509
32706
  }
32510
32707
  })
32511
- });
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>`)
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
+ })
32512
32771
  }
32772
+ refreshFilteredItems();
32773
+ searchInput.searchBox('count',filteredList.length+" / "+loadedList.length);
32513
32774
  }
32514
32775
 
32515
32776
  function refreshFilteredItems() {
@@ -32526,7 +32787,6 @@ RED.palette.editor = (function() {
32526
32787
  if (filteredList.length === 0) {
32527
32788
  packageList.editableList('addItem',{});
32528
32789
  }
32529
-
32530
32790
  if (filteredList.length > 10) {
32531
32791
  packageList.editableList('addItem',{start:10,more:filteredList.length-10})
32532
32792
  }
@@ -32556,6 +32816,7 @@ RED.palette.editor = (function() {
32556
32816
  var updateDenyList = [];
32557
32817
 
32558
32818
  function init() {
32819
+ catalogues = RED.settings.theme('palette.catalogues')||['https://catalogue.nodered.org/catalogue.json']
32559
32820
  if (RED.settings.get('externalModules.palette.allowInstall', true) === false) {
32560
32821
  return;
32561
32822
  }
@@ -32733,7 +32994,8 @@ RED.palette.editor = (function() {
32733
32994
  });
32734
32995
 
32735
32996
 
32736
- 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",
32737
32999
  addButton: false,
32738
33000
  scrollOnAdd: false,
32739
33001
  sort: function(A,B) {
@@ -32864,21 +33126,20 @@ RED.palette.editor = (function() {
32864
33126
  $('<div>',{class:"red-ui-search-empty"}).text(RED._('search.empty')).appendTo(container);
32865
33127
  }
32866
33128
  }
32867
- });
33129
+ })
32868
33130
  }
32869
33131
 
32870
33132
  function createInstallTab(content) {
32871
- var installTab = $('<div>',{class:"red-ui-palette-editor-tab hide"}).appendTo(content);
32872
-
33133
+ const installTab = $('<div>',{class:"red-ui-palette-editor-tab", style: "display: none;"}).appendTo(content);
32873
33134
  editorTabs.addTab({
32874
33135
  id: 'install',
32875
33136
  label: RED._('palette.editor.tab-install'),
32876
33137
  content: installTab
32877
33138
  })
32878
33139
 
32879
- var toolBar = $('<div>',{class:"red-ui-palette-editor-toolbar"}).appendTo(installTab);
32880
-
32881
- 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);
32882
33143
  searchInput = $('<input type="text" data-i18n="[placeholder]palette.search"></input>')
32883
33144
  .appendTo(searchDiv)
32884
33145
  .searchBox({
@@ -32895,19 +33156,25 @@ RED.palette.editor = (function() {
32895
33156
  searchInput.searchBox('count',loadedList.length);
32896
33157
  packageList.editableList('empty');
32897
33158
  packageList.editableList('addItem',{count:loadedList.length});
32898
-
32899
33159
  }
32900
33160
  }
32901
33161
  });
32902
33162
 
32903
- $('<span>').text(RED._("palette.editor.sort")+' ').appendTo(toolBar);
32904
- var sortGroup = $('<span class="button-group"></span>').appendTo(toolBar);
32905
- 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);
32906
- 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);
32907
- 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);
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"));
32908
33175
 
32909
33176
 
32910
- var sortOpts = [
33177
+ const sortOpts = [
32911
33178
  {button: sortRelevance, func: sortModulesRelevance},
32912
33179
  {button: sortAZ, func: sortModulesAZ},
32913
33180
  {button: sortRecent, func: sortModulesRecent}
@@ -32925,7 +33192,7 @@ RED.palette.editor = (function() {
32925
33192
  });
32926
33193
  });
32927
33194
 
32928
- var refreshSpan = $('<span>').appendTo(toolBar);
33195
+ var refreshSpan = $('<span>').appendTo(toolBarActions);
32929
33196
  var refreshButton = $('<a href="#" class="red-ui-sidebar-header-button"><i class="fa fa-refresh"></i></a>').appendTo(refreshSpan);
32930
33197
  refreshButton.on("click", function(e) {
32931
33198
  e.preventDefault();
@@ -32935,7 +33202,8 @@ RED.palette.editor = (function() {
32935
33202
  })
32936
33203
  RED.popover.tooltip(refreshButton,RED._("palette.editor.refresh"));
32937
33204
 
32938
- 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",
32939
33207
  addButton: false,
32940
33208
  scrollOnAdd: false,
32941
33209
  addItem: function(container,i,object) {
@@ -32970,6 +33238,9 @@ RED.palette.editor = (function() {
32970
33238
  var metaRow = $('<div class="red-ui-palette-module-meta"></div>').appendTo(headerRow);
32971
33239
  $('<span class="red-ui-palette-module-version"><i class="fa fa-tag"></i> '+entry.version+'</span>').appendTo(metaRow);
32972
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
+ }
32973
33244
 
32974
33245
  var duplicateType = false;
32975
33246
  if (entry.types && entry.types.length > 0) {
@@ -33016,9 +33287,10 @@ RED.palette.editor = (function() {
33016
33287
  }
33017
33288
  }
33018
33289
  });
33290
+
33019
33291
 
33020
33292
  if (RED.settings.get('externalModules.palette.allowUpload', true) !== false) {
33021
- var uploadSpan = $('<span class="button-group">').prependTo(toolBar);
33293
+ var uploadSpan = $('<span class="button-group">').prependTo(toolBarActions);
33022
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);
33023
33295
 
33024
33296
  var uploadInput = uploadButton.find('input[type="file"]');
@@ -33339,11 +33611,13 @@ RED.editor = (function() {
33339
33611
  var hasChanged;
33340
33612
  if (node.type.indexOf("subflow:")===0) {
33341
33613
  subflow = RED.nodes.subflow(node.type.substring(8));
33342
- isValid = subflow.valid;
33343
- hasChanged = subflow.changed;
33344
- if (isValid === undefined) {
33345
- isValid = validateNode(subflow);
33614
+ if (subflow){
33615
+ isValid = subflow.valid;
33346
33616
  hasChanged = subflow.changed;
33617
+ if (isValid === undefined) {
33618
+ isValid = validateNode(subflow);
33619
+ hasChanged = subflow.changed;
33620
+ }
33347
33621
  }
33348
33622
  validationErrors = validateNodeProperties(node, node._def.defaults, node);
33349
33623
  node.valid = isValid && validationErrors.length === 0;
@@ -34012,7 +34286,10 @@ RED.editor = (function() {
34012
34286
  if (typeof editing_node[d] === "string" || typeof editing_node[d] === "number") {
34013
34287
  oldValues[d] = editing_node[d];
34014
34288
  } else {
34015
- 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
+ }
34016
34293
  }
34017
34294
  }
34018
34295
  }
@@ -37975,7 +38252,7 @@ RED.editor = (function() {
37975
38252
  }
37976
38253
 
37977
38254
  try {
37978
- expr.evaluate(legacyMode?{msg:parsedData}:parsedData, (err, result) => {
38255
+ expr.evaluate(legacyMode?{msg:parsedData}:parsedData, null, (err, result) => {
37979
38256
  if (err) {
37980
38257
  testResultEditor.setValue(RED._("expressionEditor.errors.eval",{message:err.message}),-1);
37981
38258
  } else {
@@ -39621,7 +39898,7 @@ RED.editor.codeEditor.monaco = (function() {
39621
39898
  "node-red-util": {package: "node-red", module: "util", path: "node-red/util.d.ts" },
39622
39899
  "node-red-func": {package: "node-red", module: "func", path: "node-red/func.d.ts" },
39623
39900
  }
39624
- const defaultServerSideTypes = [ knownModules["node-red-util"], knownModules["node-red-func"], knownModules["globals"], knownModules["console"], knownModules["buffer"] , knownModules["util"] ];
39901
+ const defaultServerSideTypes = [ knownModules["node-red-util"], knownModules["node-red-func"], knownModules["globals"], knownModules["console"], knownModules["buffer"], knownModules["timers"] , knownModules["util"] ];
39625
39902
 
39626
39903
  const modulesCache = {};
39627
39904
 
@@ -40681,19 +40958,19 @@ RED.editor.codeEditor.monaco = (function() {
40681
40958
  // Warning: 4
40682
40959
  // Error: 8
40683
40960
  ed.getAnnotations = function getAnnotations() {
40684
- var aceCompatibleMarkers = [];
40961
+ let aceCompatibleMarkers;
40685
40962
  try {
40686
- var _model = ed.getModel();
40963
+ const _model = ed.getModel();
40687
40964
  if (_model !== null) {
40688
- var id = _model._languageId; // e.g. javascript
40689
- var ra = _model._associatedResource.authority; //e.g. model
40690
- var rp = _model._associatedResource.path; //e.g. /18
40691
- var rs = _model._associatedResource.scheme; //e.g. inmemory
40692
- var modelMarkers = monaco.editor.getModelMarkers(_model) || [];
40693
- var thisEditorsMarkers = modelMarkers.filter(function (marker) {
40694
- var _ra = marker.resource.authority; //e.g. model
40695
- var _rp = marker.resource.path; //e.g. /18
40696
- var _rs = marker.resource.scheme; //e.g. inmemory
40965
+ const id = _model.getLanguageId(); // e.g. javascript
40966
+ const ra = _model.uri.authority; // e.g. model
40967
+ const rp = _model.uri.path; // e.g. /18
40968
+ const rs = _model.uri.scheme; // e.g. inmemory
40969
+ const modelMarkers = monaco.editor.getModelMarkers(_model) || [];
40970
+ const thisEditorsMarkers = modelMarkers.filter(function (marker) {
40971
+ const _ra = marker.resource.authority; // e.g. model
40972
+ const _rp = marker.resource.path; // e.g. /18
40973
+ const _rs = marker.resource.scheme; // e.g. inmemory
40697
40974
  return marker.owner == id && _ra === ra && _rp === rp && _rs === rs;
40698
40975
  })
40699
40976
  aceCompatibleMarkers = thisEditorsMarkers.map(function (marker) {
@@ -41469,13 +41746,13 @@ RED.clipboard = (function() {
41469
41746
  // IE11 workaround
41470
41747
  // IE does not support data uri scheme for downloading data
41471
41748
  var blob = new Blob([data], {
41472
- type: "data:text/plain;charset=utf-8"
41749
+ type: "data:application/json;charset=utf-8"
41473
41750
  });
41474
41751
  navigator.msSaveBlob(blob, file);
41475
41752
  }
41476
41753
  else {
41477
41754
  var element = document.createElement('a');
41478
- element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(data));
41755
+ element.setAttribute('href', 'data:application/json;charset=utf-8,' + encodeURIComponent(data));
41479
41756
  element.setAttribute('download', file);
41480
41757
  element.style.display = 'none';
41481
41758
  document.body.appendChild(element);
@@ -42163,7 +42440,7 @@ RED.clipboard = (function() {
42163
42440
  nodes.unshift(parentNode);
42164
42441
  nodes = RED.nodes.createExportableNodeSet(nodes);
42165
42442
  } else if (type === 'full') {
42166
- nodes = RED.nodes.createCompleteNodeSet(false);
42443
+ nodes = RED.nodes.createCompleteNodeSet({ credentials: false });
42167
42444
  }
42168
42445
  if (nodes !== null) {
42169
42446
  if (format === "red-ui-clipboard-dialog-export-fmt-full") {
@@ -44942,7 +45219,7 @@ RED.actionList = (function() {
44942
45219
  var searchDiv = $("<div>",{class:"red-ui-search-container"}).appendTo(dialog);
44943
45220
  searchInput = $('<input type="text" data-i18n="[placeholder]keyboard.filterActions">').appendTo(searchDiv).searchBox({
44944
45221
  change: function() {
44945
- filterTerm = $(this).val().trim();
45222
+ filterTerm = $(this).val().trim().toLowerCase();
44946
45223
  filterTerms = filterTerm.split(" ");
44947
45224
  searchResults.editableList('filter');
44948
45225
  searchResults.find("li.selected").removeClass("selected");
@@ -45595,7 +45872,9 @@ RED.subflow = (function() {
45595
45872
  '</script>';
45596
45873
 
45597
45874
  function findAvailableSubflowIOPosition(subflow,isInput) {
45598
- var pos = {x:50,y:30};
45875
+ const scrollPos = RED.view.scroll()
45876
+ const scaleFactor = RED.view.scale()
45877
+ var pos = { x: (scrollPos[0]/scaleFactor)+50, y: (scrollPos[1]/scaleFactor)+30 };
45599
45878
  if (!isInput) {
45600
45879
  pos.x += 110;
45601
45880
  }
@@ -46214,7 +46493,7 @@ RED.subflow = (function() {
46214
46493
  for (i=0; i<nodeList.length;i++) {
46215
46494
  if (nodeList[i].g && !includedGroups.has(nodeList[i].g)) {
46216
46495
  if (containingGroup !== nodeList[i].g) {
46217
- RED.notify("Cannot create subflow across multiple groups","error");
46496
+ RED.notify(RED._("subflow.errors.acrossMultipleGroups"), "error");
46218
46497
  return;
46219
46498
  }
46220
46499
  }
@@ -46230,24 +46509,23 @@ RED.subflow = (function() {
46230
46509
  var candidateOutputs = [];
46231
46510
  var candidateInputNodes = {};
46232
46511
 
46233
- var boundingBox = [nodeList[0].x,
46234
- nodeList[0].y,
46235
- nodeList[0].x,
46236
- nodeList[0].y];
46512
+ var boundingBox = [nodeList[0].x-(nodeList[0].w/2),
46513
+ nodeList[0].y-(nodeList[0].h/2),
46514
+ nodeList[0].x+(nodeList[0].w/2),
46515
+ nodeList[0].y+(nodeList[0].h/2)];
46237
46516
 
46238
46517
  for (i=0;i<nodeList.length;i++) {
46239
46518
  n = nodeList[i];
46240
46519
  nodes[n.id] = {n:n,outputs:{}};
46241
46520
  boundingBox = [
46242
- Math.min(boundingBox[0],n.x),
46243
- Math.min(boundingBox[1],n.y),
46244
- Math.max(boundingBox[2],n.x),
46245
- Math.max(boundingBox[3],n.y)
46521
+ Math.min(boundingBox[0],n.x-(n.w/2)),
46522
+ Math.min(boundingBox[1],n.y-(n.h/2)),
46523
+ Math.max(boundingBox[2],n.x+(n.w/2)),
46524
+ Math.max(boundingBox[3],n.y+(n.h/2))
46246
46525
  ]
46247
46526
  }
46248
- var offsetX = snapToGrid(boundingBox[0] - 200);
46249
- var offsetY = snapToGrid(boundingBox[1] - 80);
46250
-
46527
+ var offsetX = snapToGrid(boundingBox[0] - 140);
46528
+ var offsetY = snapToGrid(boundingBox[1] - 60);
46251
46529
 
46252
46530
  var center = [
46253
46531
  snapToGrid((boundingBox[2]+boundingBox[0]) / 2),
@@ -47315,7 +47593,7 @@ RED.group = (function() {
47315
47593
  }
47316
47594
  }
47317
47595
  var existingGroup;
47318
-
47596
+ var mergedEnv = {}
47319
47597
  // Second pass, ungroup any groups in the selection and add their contents
47320
47598
  // to the selection
47321
47599
  for (var i=0; i<selection.nodes.length; i++) {
@@ -47324,6 +47602,11 @@ RED.group = (function() {
47324
47602
  if (!existingGroup) {
47325
47603
  existingGroup = n;
47326
47604
  }
47605
+ if (n.env && n.env.length > 0) {
47606
+ n.env.forEach(env => {
47607
+ mergedEnv[env.name] = env
47608
+ })
47609
+ }
47327
47610
  ungroupHistoryEvent.groups.push(n);
47328
47611
  nodes = nodes.concat(ungroup(n));
47329
47612
  } else {
@@ -47341,6 +47624,7 @@ RED.group = (function() {
47341
47624
  group.style = existingGroup.style;
47342
47625
  group.name = existingGroup.name;
47343
47626
  }
47627
+ group.env = Object.values(mergedEnv)
47344
47628
  RED.view.select({nodes:[group]})
47345
47629
  }
47346
47630
  historyEvent.events.push({