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

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 (111) hide show
  1. package/locales/de/editor.json +2 -0
  2. package/locales/en-US/editor.json +9 -2
  3. package/locales/fr/editor.json +1238 -0
  4. package/locales/fr/infotips.json +23 -0
  5. package/locales/fr/jsonata.json +274 -0
  6. package/locales/ja/editor.json +15 -3
  7. package/locales/ko/editor.json +233 -31
  8. package/locales/pt-BR/editor.json +1208 -0
  9. package/locales/pt-BR/infotips.json +23 -0
  10. package/locales/pt-BR/jsonata.json +274 -0
  11. package/locales/ru/editor.json +2 -0
  12. package/locales/zh-CN/editor.json +1175 -1049
  13. package/locales/zh-TW/editor.json +3 -0
  14. package/package.json +1 -1
  15. package/public/red/about +85 -1
  16. package/public/red/red.js +1250 -1092
  17. package/public/red/red.min.js +3 -3
  18. package/public/red/style.min.css +1 -1
  19. package/public/red/tours/3.0/welcome.js +6 -6
  20. package/public/red/tours/images/node-help.png +0 -0
  21. package/public/red/tours/images/tab-changes.png +0 -0
  22. package/public/red/tours/welcome.js +82 -7
  23. package/public/types/node/assert/strict.d.ts +11 -0
  24. package/public/types/node/assert.d.ts +898 -64
  25. package/public/types/node/async_hooks.d.ts +362 -94
  26. package/public/types/node/buffer.d.ts +2158 -14
  27. package/public/types/node/child_process.d.ts +1109 -257
  28. package/public/types/node/cluster.d.ts +349 -200
  29. package/public/types/node/console.d.ts +313 -43
  30. package/public/types/node/crypto.d.ts +3338 -658
  31. package/public/types/node/dgram.d.ts +459 -58
  32. package/public/types/node/diagnostics_channel.d.ts +155 -0
  33. package/public/types/node/dns/promises.d.ts +371 -0
  34. package/public/types/node/dns.d.ts +532 -265
  35. package/public/types/node/domain.d.ts +159 -16
  36. package/public/types/node/events.d.ts +589 -30
  37. package/public/types/node/fs/promises.d.ts +1097 -0
  38. package/public/types/node/fs.d.ts +2484 -958
  39. package/public/types/node/globals.d.ts +44 -504
  40. package/public/types/node/http.d.ts +1156 -145
  41. package/public/types/node/http2.d.ts +1610 -470
  42. package/public/types/node/https.d.ts +462 -72
  43. package/public/types/node/module.d.ts +72 -13
  44. package/public/types/node/net.d.ts +663 -131
  45. package/public/types/node/os.d.ts +238 -25
  46. package/public/types/node/path.d.ts +57 -23
  47. package/public/types/node/perf_hooks.d.ts +424 -112
  48. package/public/types/node/process.d.ts +1261 -193
  49. package/public/types/node/querystring.d.ts +107 -7
  50. package/public/types/node/readline.d.ts +443 -74
  51. package/public/types/node/stream/consumers.d.ts +15 -0
  52. package/public/types/node/stream/promises.d.ts +45 -0
  53. package/public/types/node/stream/web.d.ts +395 -0
  54. package/public/types/node/stream.d.ts +1180 -177
  55. package/public/types/node/string_decoder.d.ts +57 -0
  56. package/public/types/node/test.d.ts +193 -0
  57. package/public/types/node/timers/promises.d.ts +96 -0
  58. package/public/types/node/timers.d.ts +87 -12
  59. package/public/types/node/tls.d.ts +457 -222
  60. package/public/types/node/trace_events.d.ts +107 -10
  61. package/public/types/node/tty.d.ts +158 -23
  62. package/public/types/node/url.d.ts +734 -28
  63. package/public/types/node/util.d.ts +1542 -164
  64. package/public/types/node/v8.d.ts +261 -73
  65. package/public/types/node/vm.d.ts +384 -32
  66. package/public/types/node/wasi.d.ts +92 -23
  67. package/public/types/node/worker_threads.d.ts +531 -123
  68. package/public/types/node/zlib.d.ts +216 -63
  69. package/public/types/node-red/func.d.ts +2 -3
  70. package/public/vendor/jquery/css/base/images/ui-icons_444444_256x240.png +0 -0
  71. package/public/vendor/jquery/css/base/images/ui-icons_555555_256x240.png +0 -0
  72. package/public/vendor/jquery/css/base/images/ui-icons_777620_256x240.png +0 -0
  73. package/public/vendor/jquery/css/base/images/ui-icons_777777_256x240.png +0 -0
  74. package/public/vendor/jquery/css/base/images/ui-icons_cc0000_256x240.png +0 -0
  75. package/public/vendor/jquery/css/base/images/ui-icons_ffffff_256x240.png +0 -0
  76. package/public/vendor/jquery/css/base/jquery-ui.min.css +4 -4
  77. package/public/vendor/mermaid/mermaid.min.js +713 -417
  78. package/public/vendor/monaco/dist/{ade705761eb7e702770d.ttf → 7064e66c3890a12c47b4.ttf} +0 -0
  79. package/public/vendor/monaco/dist/css.worker.js +1 -1
  80. package/public/vendor/monaco/dist/css.worker.js.LICENSE.txt +1 -1
  81. package/public/vendor/monaco/dist/editor.js +29 -1
  82. package/public/vendor/monaco/dist/editor.js.LICENSE.txt +5 -1
  83. package/public/vendor/monaco/dist/editor.worker.js +1 -1
  84. package/public/vendor/monaco/dist/fa2cc0ab9f0bec2b3365.ttf +0 -0
  85. package/public/vendor/monaco/dist/html.worker.js +1 -1
  86. package/public/vendor/monaco/dist/html.worker.js.LICENSE.txt +1 -1
  87. package/public/vendor/monaco/dist/json.worker.js +1 -1
  88. package/public/vendor/monaco/dist/json.worker.js.LICENSE.txt +1 -1
  89. package/public/vendor/monaco/dist/locale/cs.js +393 -84
  90. package/public/vendor/monaco/dist/locale/de.js +397 -88
  91. package/public/vendor/monaco/dist/locale/es.js +392 -83
  92. package/public/vendor/monaco/dist/locale/fr.js +401 -92
  93. package/public/vendor/monaco/dist/locale/it.js +399 -90
  94. package/public/vendor/monaco/dist/locale/ja.js +411 -102
  95. package/public/vendor/monaco/dist/locale/ko.js +398 -89
  96. package/public/vendor/monaco/dist/locale/pl.js +396 -87
  97. package/public/vendor/monaco/dist/locale/pt-br.js +397 -88
  98. package/public/vendor/monaco/dist/locale/qps-ploc.js +1643 -1334
  99. package/public/vendor/monaco/dist/locale/ru.js +398 -89
  100. package/public/vendor/monaco/dist/locale/tr.js +400 -91
  101. package/public/vendor/monaco/dist/locale/zh-hans.js +403 -94
  102. package/public/vendor/monaco/dist/locale/zh-hant.js +395 -86
  103. package/public/vendor/monaco/dist/theme/forge-dark.json +213 -0
  104. package/public/vendor/monaco/dist/theme/forge-light.json +227 -0
  105. package/public/vendor/monaco/dist/theme/forge.json +236 -0
  106. package/public/vendor/monaco/dist/theme/github-dark.json +348 -0
  107. package/public/vendor/monaco/dist/theme/github-light.json +348 -0
  108. package/public/vendor/monaco/dist/theme/nord.json +93 -0
  109. package/public/vendor/monaco/dist/ts.worker.js +6 -1
  110. package/public/vendor/monaco/dist/ts.worker.js.LICENSE.txt +14 -6
  111. package/public/vendor/vendor.js +6 -13
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});
@@ -3738,6 +3742,9 @@ RED.nodes = (function() {
3738
3742
 
3739
3743
  function setDirty(d) {
3740
3744
  dirty = d;
3745
+ if (!d) {
3746
+ allNodes.clearState()
3747
+ }
3741
3748
  RED.events.emit("workspace:dirty",{dirty:dirty});
3742
3749
  }
3743
3750
 
@@ -3760,7 +3767,6 @@ RED.nodes = (function() {
3760
3767
  }
3761
3768
  };
3762
3769
 
3763
-
3764
3770
  var exports = {
3765
3771
  setModulePendingUpdated: function(module,version) {
3766
3772
  moduleList[module].pending_version = version;
@@ -3930,22 +3936,72 @@ RED.nodes = (function() {
3930
3936
 
3931
3937
  // allNodes holds information about the Flow nodes.
3932
3938
  var allNodes = (function() {
3939
+ // Map node.id -> node
3933
3940
  var nodes = {};
3941
+ // Map tab.id -> Array of nodes on that tab
3934
3942
  var tabMap = {};
3943
+ // Map tab.id -> Set of dirty object ids on that tab
3944
+ var tabDirtyMap = {};
3945
+ // Map tab.id -> Set of object ids of things deleted from the tab that weren't otherwise dirty
3946
+ var tabDeletedNodesMap = {};
3947
+ // Set of object ids of things added to a tab after initial import
3948
+ var addedDirtyObjects = new Set()
3949
+
3950
+ function changeCollectionDepth(tabNodes, toMove, direction, singleStep) {
3951
+ const result = []
3952
+ const moved = new Set();
3953
+ const startIndex = direction ? tabNodes.length - 1 : 0
3954
+ const endIndex = direction ? -1 : tabNodes.length
3955
+ const step = direction ? -1 : 1
3956
+ let target = startIndex // Only used for all-the-way moves
3957
+ for (let i = startIndex; i != endIndex; i += step) {
3958
+ if (toMove.size === 0) {
3959
+ break;
3960
+ }
3961
+ const n = tabNodes[i]
3962
+ if (toMove.has(n)) {
3963
+ if (singleStep) {
3964
+ if (i !== startIndex && !moved.has(tabNodes[i - step])) {
3965
+ tabNodes.splice(i, 1)
3966
+ tabNodes.splice(i - step, 0, n)
3967
+ n._reordered = true
3968
+ result.push(n)
3969
+ }
3970
+ } else {
3971
+ if (i !== target) {
3972
+ tabNodes.splice(i, 1)
3973
+ tabNodes.splice(target, 0, n)
3974
+ n._reordered = true
3975
+ result.push(n)
3976
+ }
3977
+ target += step
3978
+ }
3979
+ toMove.delete(n);
3980
+ moved.add(n);
3981
+ }
3982
+ }
3983
+ return result
3984
+ }
3985
+
3935
3986
  var api = {
3936
3987
  addTab: function(id) {
3937
3988
  tabMap[id] = [];
3989
+ tabDirtyMap[id] = new Set();
3990
+ tabDeletedNodesMap[id] = new Set();
3938
3991
  },
3939
3992
  hasTab: function(z) {
3940
3993
  return tabMap.hasOwnProperty(z)
3941
3994
  },
3942
3995
  removeTab: function(id) {
3943
3996
  delete tabMap[id];
3997
+ delete tabDirtyMap[id];
3998
+ delete tabDeletedNodesMap[id];
3944
3999
  },
3945
4000
  addNode: function(n) {
3946
4001
  nodes[n.id] = n;
3947
4002
  if (tabMap.hasOwnProperty(n.z)) {
3948
4003
  tabMap[n.z].push(n);
4004
+ api.addObjectToWorkspace(n.z, n.id, n.changed || n.moved)
3949
4005
  } else {
3950
4006
  console.warn("Node added to unknown tab/subflow:",n);
3951
4007
  tabMap["_"] = tabMap["_"] || [];
@@ -3959,7 +4015,36 @@ RED.nodes = (function() {
3959
4015
  if (i > -1) {
3960
4016
  tabMap[n.z].splice(i,1);
3961
4017
  }
4018
+ api.removeObjectFromWorkspace(n.z, n.id)
4019
+ }
4020
+ },
4021
+ /**
4022
+ * Add an object to our dirty/clean tracking state
4023
+ * @param {String} z
4024
+ * @param {String} id
4025
+ * @param {Boolean} isDirty
4026
+ */
4027
+ addObjectToWorkspace: function (z, id, isDirty) {
4028
+ if (isDirty) {
4029
+ addedDirtyObjects.add(id)
4030
+ }
4031
+ if (tabDeletedNodesMap[z].has(id)) {
4032
+ tabDeletedNodesMap[z].delete(id)
4033
+ }
4034
+ api.markNodeDirty(z, id, isDirty)
4035
+ },
4036
+ /**
4037
+ * Remove an object from our dirty/clean tracking state
4038
+ * @param {String} z
4039
+ * @param {String} id
4040
+ */
4041
+ removeObjectFromWorkspace: function (z, id) {
4042
+ if (!addedDirtyObjects.has(id)) {
4043
+ tabDeletedNodesMap[z].add(id)
4044
+ } else {
4045
+ addedDirtyObjects.delete(id)
3962
4046
  }
4047
+ api.markNodeDirty(z, id, false)
3963
4048
  },
3964
4049
  hasNode: function(id) {
3965
4050
  return nodes.hasOwnProperty(id);
@@ -3972,152 +4057,54 @@ RED.nodes = (function() {
3972
4057
  n.z = newZ;
3973
4058
  api.addNode(n)
3974
4059
  },
3975
- moveNodesForwards: function(nodes) {
3976
- var result = [];
4060
+ /**
4061
+ * @param {array} nodes
4062
+ * @param {boolean} direction true:forwards false:back
4063
+ * @param {boolean} singleStep true:single-step false:all-the-way
4064
+ */
4065
+ changeDepth: function(nodes, direction, singleStep) {
3977
4066
  if (!Array.isArray(nodes)) {
3978
4067
  nodes = [nodes]
3979
4068
  }
3980
- // Can only do this for nodes on the same tab.
3981
- // Use nodes[0] to get the z
3982
- var tabNodes = tabMap[nodes[0].z];
3983
- var toMove = new Set(nodes.filter(function(n) { return n.type !== "group" && n.type !== "subflow" }));
3984
- var moved = new Set();
3985
- for (var i = tabNodes.length-1; i >= 0; i--) {
3986
- if (toMove.size === 0) {
3987
- break;
4069
+ let result = []
4070
+ const tabNodes = tabMap[nodes[0].z];
4071
+ const toMove = new Set(nodes.filter(function(n) { return n.type !== "group" && n.type !== "subflow" }));
4072
+ if (toMove.size > 0) {
4073
+ result = result.concat(changeCollectionDepth(tabNodes, toMove, direction, singleStep))
4074
+ if (result.length > 0) {
4075
+ RED.events.emit('nodes:reorder',{
4076
+ z: nodes[0].z,
4077
+ nodes: result
4078
+ });
3988
4079
  }
3989
- var n = tabNodes[i];
3990
- if (toMove.has(n)) {
3991
- // This is a node to move.
3992
- if (i < tabNodes.length-1 && !moved.has(tabNodes[i+1])) {
3993
- // Remove from current position
3994
- tabNodes.splice(i,1);
3995
- // Add it back one position higher
3996
- tabNodes.splice(i+1,0,n);
3997
- n._reordered = true;
3998
- result.push(n);
3999
- }
4000
- toMove.delete(n);
4001
- moved.add(n);
4002
- }
4003
- }
4004
- if (result.length > 0) {
4005
- RED.events.emit('nodes:reorder',{
4006
- z: nodes[0].z,
4007
- nodes: result
4008
- });
4009
- }
4010
- return result;
4011
- },
4012
- moveNodesBackwards: function(nodes) {
4013
- var result = [];
4014
- if (!Array.isArray(nodes)) {
4015
- nodes = [nodes]
4016
4080
  }
4017
- // Can only do this for nodes on the same tab.
4018
- // Use nodes[0] to get the z
4019
- var tabNodes = tabMap[nodes[0].z];
4020
- var toMove = new Set(nodes.filter(function(n) { return n.type !== "group" && n.type !== "subflow" }));
4021
- var moved = new Set();
4022
- for (var i = 0; i < tabNodes.length; i++) {
4023
- if (toMove.size === 0) {
4024
- break;
4081
+
4082
+ const groupNodes = groupsByZ[nodes[0].z] || []
4083
+ const groupsToMove = new Set(nodes.filter(function(n) { return n.type === 'group'}))
4084
+ if (groupsToMove.size > 0) {
4085
+ const groupResult = changeCollectionDepth(groupNodes, groupsToMove, direction, singleStep)
4086
+ if (groupResult.length > 0) {
4087
+ result = result.concat(groupResult)
4088
+ RED.events.emit('groups:reorder',{
4089
+ z: nodes[0].z,
4090
+ nodes: groupResult
4091
+ });
4025
4092
  }
4026
- var n = tabNodes[i];
4027
- if (toMove.has(n)) {
4028
- // This is a node to move.
4029
- if (i > 0 && !moved.has(tabNodes[i-1])) {
4030
- // Remove from current position
4031
- tabNodes.splice(i,1);
4032
- // Add it back one position lower
4033
- tabNodes.splice(i-1,0,n);
4034
- n._reordered = true;
4035
- result.push(n);
4036
- }
4037
- toMove.delete(n);
4038
- moved.add(n);
4039
- }
4040
- }
4041
- if (result.length > 0) {
4042
- RED.events.emit('nodes:reorder',{
4043
- z: nodes[0].z,
4044
- nodes: result
4045
- });
4046
4093
  }
4047
- return result;
4094
+ RED.view.redraw(true)
4095
+ return result
4096
+ },
4097
+ moveNodesForwards: function(nodes) {
4098
+ return api.changeDepth(nodes, true, true)
4099
+ },
4100
+ moveNodesBackwards: function(nodes) {
4101
+ return api.changeDepth(nodes, false, true)
4048
4102
  },
4049
4103
  moveNodesToFront: function(nodes) {
4050
- var result = [];
4051
- if (!Array.isArray(nodes)) {
4052
- nodes = [nodes]
4053
- }
4054
- // Can only do this for nodes on the same tab.
4055
- // Use nodes[0] to get the z
4056
- var tabNodes = tabMap[nodes[0].z];
4057
- var toMove = new Set(nodes.filter(function(n) { return n.type !== "group" && n.type !== "subflow" }));
4058
- var target = tabNodes.length-1;
4059
- for (var i = tabNodes.length-1; i >= 0; i--) {
4060
- if (toMove.size === 0) {
4061
- break;
4062
- }
4063
- var n = tabNodes[i];
4064
- if (toMove.has(n)) {
4065
- // This is a node to move.
4066
- if (i < target) {
4067
- // Remove from current position
4068
- tabNodes.splice(i,1);
4069
- tabNodes.splice(target,0,n);
4070
- n._reordered = true;
4071
- result.push(n);
4072
- }
4073
- target--;
4074
- toMove.delete(n);
4075
- }
4076
- }
4077
- if (result.length > 0) {
4078
- RED.events.emit('nodes:reorder',{
4079
- z: nodes[0].z,
4080
- nodes: result
4081
- });
4082
- }
4083
- return result;
4104
+ return api.changeDepth(nodes, true, false)
4084
4105
  },
4085
4106
  moveNodesToBack: function(nodes) {
4086
- var result = [];
4087
- if (!Array.isArray(nodes)) {
4088
- nodes = [nodes]
4089
- }
4090
- // Can only do this for nodes on the same tab.
4091
- // Use nodes[0] to get the z
4092
- var tabNodes = tabMap[nodes[0].z];
4093
- var toMove = new Set(nodes.filter(function(n) { return n.type !== "group" && n.type !== "subflow" }));
4094
- var target = 0;
4095
- for (var i = 0; i < tabNodes.length; i++) {
4096
- if (toMove.size === 0) {
4097
- break;
4098
- }
4099
- var n = tabNodes[i];
4100
- if (toMove.has(n)) {
4101
- // This is a node to move.
4102
- if (i > target) {
4103
- // Remove from current position
4104
- tabNodes.splice(i,1);
4105
- // Add it back one position lower
4106
- tabNodes.splice(target,0,n);
4107
- n._reordered = true;
4108
- result.push(n);
4109
- }
4110
- target++;
4111
- toMove.delete(n);
4112
- }
4113
- }
4114
- if (result.length > 0) {
4115
- RED.events.emit('nodes:reorder',{
4116
- z: nodes[0].z,
4117
- nodes: result
4118
- });
4119
- }
4120
- return result;
4107
+ return api.changeDepth(nodes, false, false)
4121
4108
  },
4122
4109
  getNodes: function(z) {
4123
4110
  return tabMap[z];
@@ -4125,6 +4112,33 @@ RED.nodes = (function() {
4125
4112
  clear: function() {
4126
4113
  nodes = {};
4127
4114
  tabMap = {};
4115
+ tabDirtyMap = {};
4116
+ tabDeletedNodesMap = {};
4117
+ addedDirtyObjects = new Set();
4118
+ },
4119
+ /**
4120
+ * Clear all internal state on what is dirty.
4121
+ */
4122
+ clearState: function () {
4123
+ // Called when a deploy happens, we can forget about added/remove
4124
+ // items as they have now been deployed.
4125
+ addedDirtyObjects = new Set()
4126
+ const flowsToCheck = new Set()
4127
+ for (const [z, set] of Object.entries(tabDeletedNodesMap)) {
4128
+ if (set.size > 0) {
4129
+ set.clear()
4130
+ flowsToCheck.add(z)
4131
+ }
4132
+ }
4133
+ for (const [z, set] of Object.entries(tabDirtyMap)) {
4134
+ if (set.size > 0) {
4135
+ set.clear()
4136
+ flowsToCheck.add(z)
4137
+ }
4138
+ }
4139
+ for (const z of flowsToCheck) {
4140
+ api.checkTabState(z)
4141
+ }
4128
4142
  },
4129
4143
  eachNode: function(cb) {
4130
4144
  var nodeList,i,j;
@@ -4190,7 +4204,7 @@ RED.nodes = (function() {
4190
4204
  return result;
4191
4205
  },
4192
4206
  getNodeOrder: function(z) {
4193
- return tabMap[z].map(function(n) { return n.id })
4207
+ return (groupsByZ[z] || []).concat(tabMap[z]).map(n => n.id)
4194
4208
  },
4195
4209
  setNodeOrder: function(z, order) {
4196
4210
  var orderMap = {};
@@ -4202,6 +4216,41 @@ RED.nodes = (function() {
4202
4216
  B._reordered = true;
4203
4217
  return orderMap[A.id] - orderMap[B.id];
4204
4218
  })
4219
+ if (groupsByZ[z]) {
4220
+ groupsByZ[z].sort(function(A,B) {
4221
+ return orderMap[A.id] - orderMap[B.id];
4222
+ })
4223
+ }
4224
+ },
4225
+ /**
4226
+ * Update our records if an object is dirty or not
4227
+ * @param {String} z tab id
4228
+ * @param {String} id object id
4229
+ * @param {Boolean} dirty whether the object is dirty or not
4230
+ */
4231
+ markNodeDirty: function(z, id, dirty) {
4232
+ if (tabDirtyMap[z]) {
4233
+ if (dirty) {
4234
+ tabDirtyMap[z].add(id)
4235
+ } else {
4236
+ tabDirtyMap[z].delete(id)
4237
+ }
4238
+ api.checkTabState(z)
4239
+ }
4240
+ },
4241
+ /**
4242
+ * Check if a tab should update its contentsChange flag
4243
+ * @param {String} z tab id
4244
+ */
4245
+ checkTabState: function (z) {
4246
+ const ws = workspaces[z]
4247
+ if (ws) {
4248
+ const contentsChanged = tabDirtyMap[z].size > 0 || tabDeletedNodesMap[z].size > 0
4249
+ if (Boolean(ws.contentsChanged) !== contentsChanged) {
4250
+ ws.contentsChanged = contentsChanged
4251
+ RED.events.emit("flows:change", ws);
4252
+ }
4253
+ }
4205
4254
  }
4206
4255
  }
4207
4256
  return api;
@@ -4289,6 +4338,11 @@ RED.nodes = (function() {
4289
4338
  throw new Error(`Cannot modified property '${prop}' of locked object '${node.type}:${node.id}'`)
4290
4339
  }
4291
4340
  }
4341
+ if (node.z && (prop === 'changed' || prop === 'moved')) {
4342
+ setTimeout(() => {
4343
+ allNodes.markNodeDirty(node.z, node.id, node.changed || node.moved)
4344
+ }, 0)
4345
+ }
4292
4346
  node[prop] = value;
4293
4347
  return true
4294
4348
  }
@@ -4358,10 +4412,16 @@ RED.nodes = (function() {
4358
4412
  }
4359
4413
  if (l.source.z === l.target.z && linkTabMap[l.source.z]) {
4360
4414
  linkTabMap[l.source.z].push(l);
4415
+ allNodes.addObjectToWorkspace(l.source.z, getLinkId(l), true)
4361
4416
  }
4362
4417
  RED.events.emit("links:add",l);
4363
4418
  }
4364
4419
 
4420
+ function getLinkId(link) {
4421
+ return link.source.id + ':' + link.sourcePort + ':' + link.target.id
4422
+ }
4423
+
4424
+
4365
4425
  function getNode(id) {
4366
4426
  if (id in configNodes) {
4367
4427
  return configNodes[id];
@@ -4556,6 +4616,7 @@ RED.nodes = (function() {
4556
4616
  if (index !== -1) {
4557
4617
  linkTabMap[l.source.z].splice(index,1)
4558
4618
  }
4619
+ allNodes.removeObjectFromWorkspace(l.source.z, getLinkId(l))
4559
4620
  }
4560
4621
  }
4561
4622
  RED.events.emit("links:remove",l);
@@ -4725,6 +4786,11 @@ RED.nodes = (function() {
4725
4786
  return false;
4726
4787
  }
4727
4788
 
4789
+ function getDownstreamNodes(node) {
4790
+ const downstreamLinks = nodeLinks[node.id].out
4791
+ const downstreamNodes = new Set(downstreamLinks.map(l => l.target))
4792
+ return Array.from(downstreamNodes)
4793
+ }
4728
4794
  function getAllDownstreamNodes(node) {
4729
4795
  return getAllFlowNodes(node,'down').filter(function(n) { return n !== node });
4730
4796
  }
@@ -5098,7 +5164,7 @@ RED.nodes = (function() {
5098
5164
  }
5099
5165
  }
5100
5166
  if (node.type !== "subflow") {
5101
- var convertedNode = RED.nodes.convertNode(node);
5167
+ var convertedNode = RED.nodes.convertNode(node, { credentials: false });
5102
5168
  for (var d in node._def.defaults) {
5103
5169
  if (node._def.defaults[d].type) {
5104
5170
  var nodeList = node[d];
@@ -5131,7 +5197,7 @@ RED.nodes = (function() {
5131
5197
  nns = nns.concat(createExportableNodeSet(node.nodes, exportedIds, exportedSubflows, exportedConfigNodes));
5132
5198
  }
5133
5199
  } else {
5134
- var convertedSubflow = convertSubflow(node);
5200
+ var convertedSubflow = convertSubflow(node, { credentials: false });
5135
5201
  nns.push(convertedSubflow);
5136
5202
  }
5137
5203
  }
@@ -5380,6 +5446,7 @@ RED.nodes = (function() {
5380
5446
  * Options:
5381
5447
  * - generateIds - whether to replace all node ids
5382
5448
  * - addFlow - whether to import nodes to a new tab
5449
+ * - markChanged - whether to set changed=true on all newly imported objects
5383
5450
  * - reimport - if node has a .z property, dont overwrite it
5384
5451
  * Only applicible when `generateIds` is false
5385
5452
  * - importMap - how to resolve any conflicts.
@@ -5388,7 +5455,7 @@ RED.nodes = (function() {
5388
5455
  * - id:replace - import over the top of existing
5389
5456
  */
5390
5457
  function importNodes(newNodesObj,options) { // createNewIds,createMissingWorkspace) {
5391
- const defOpts = { generateIds: false, addFlow: false, reimport: false, importMap: {} }
5458
+ const defOpts = { generateIds: false, addFlow: false, markChanged: false, reimport: false, importMap: {} }
5392
5459
  options = Object.assign({}, defOpts, options)
5393
5460
  options.importMap = options.importMap || {}
5394
5461
  const createNewIds = options.generateIds;
@@ -5414,7 +5481,7 @@ RED.nodes = (function() {
5414
5481
  newNodes = newNodesObj;
5415
5482
  }
5416
5483
 
5417
- if (!$.isArray(newNodes)) {
5484
+ if (!Array.isArray(newNodes)) {
5418
5485
  newNodes = [newNodes];
5419
5486
  }
5420
5487
 
@@ -5712,6 +5779,9 @@ RED.nodes = (function() {
5712
5779
  if (!n.z) {
5713
5780
  delete configNode.z;
5714
5781
  }
5782
+ if (options.markChanged) {
5783
+ configNode.changed = true
5784
+ }
5715
5785
  if (n.hasOwnProperty('d')) {
5716
5786
  configNode.d = n.d;
5717
5787
  }
@@ -5774,6 +5844,9 @@ RED.nodes = (function() {
5774
5844
  if (n.hasOwnProperty('g')) {
5775
5845
  node.g = n.g;
5776
5846
  }
5847
+ if (options.markChanged) {
5848
+ node.changed = true
5849
+ }
5777
5850
  if (createNewIds || options.importMap[n.id] === "copy") {
5778
5851
  if (subflow_denylist[n.z]) {
5779
5852
  continue;
@@ -5824,16 +5897,27 @@ RED.nodes = (function() {
5824
5897
  } else if (n.type.substring(0,7) === "subflow") {
5825
5898
  var parentId = n.type.split(":")[1];
5826
5899
  var subflow = subflow_denylist[parentId]||subflow_map[parentId]||getSubflow(parentId);
5827
- if (createNewIds || options.importMap[n.id] === "copy") {
5828
- parentId = subflow.id;
5829
- node.type = "subflow:"+parentId;
5830
- node._def = registry.getNodeType(node.type);
5831
- delete node.i;
5832
- }
5833
- node.name = n.name;
5834
- node.outputs = subflow.out.length;
5835
- node.inputs = subflow.in.length;
5836
- 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
+ }
5837
5921
  } else if (n.type === 'junction') {
5838
5922
  node._def = {defaults:{}}
5839
5923
  node._config.x = node.x
@@ -5994,7 +6078,7 @@ RED.nodes = (function() {
5994
6078
  // get added
5995
6079
  if (activeSubflow && /^link /.test(n.type) && n.links) {
5996
6080
  n.links = n.links.filter(function(id) {
5997
- var otherNode = RED.nodes.node(id);
6081
+ const otherNode = node_map[id] || RED.nodes.node(id);
5998
6082
  return (otherNode && otherNode.z === activeWorkspace)
5999
6083
  });
6000
6084
  }
@@ -6287,6 +6371,7 @@ RED.nodes = (function() {
6287
6371
  groupsByZ[group.z] = groupsByZ[group.z] || [];
6288
6372
  groupsByZ[group.z].push(group);
6289
6373
  groups[group.id] = group;
6374
+ allNodes.addObjectToWorkspace(group.z, group.id, group.changed || group.moved)
6290
6375
  RED.events.emit("groups:add",group);
6291
6376
  return group
6292
6377
  }
@@ -6303,10 +6388,14 @@ RED.nodes = (function() {
6303
6388
  }
6304
6389
  }
6305
6390
  RED.group.markDirty(group);
6306
-
6391
+ allNodes.removeObjectFromWorkspace(group.z, group.id)
6307
6392
  delete groups[group.id];
6308
6393
  RED.events.emit("groups:remove",group);
6309
6394
  }
6395
+ function getGroupOrder(z) {
6396
+ const groups = groupsByZ[z]
6397
+ return groups.map(g => g.id)
6398
+ }
6310
6399
 
6311
6400
  function addJunction(junction) {
6312
6401
  if (!junction.__isProxy__) {
@@ -6318,6 +6407,7 @@ RED.nodes = (function() {
6318
6407
  if (!nodeLinks[junction.id]) {
6319
6408
  nodeLinks[junction.id] = {in:[],out:[]};
6320
6409
  }
6410
+ allNodes.addObjectToWorkspace(junction.z, junction.id, junction.changed || junction.moved)
6321
6411
  RED.events.emit("junctions:add", junction)
6322
6412
  return junction
6323
6413
  }
@@ -6329,6 +6419,7 @@ RED.nodes = (function() {
6329
6419
  }
6330
6420
  delete junctions[junction.id]
6331
6421
  delete nodeLinks[junction.id];
6422
+ allNodes.removeObjectFromWorkspace(junction.z, junction.id)
6332
6423
  RED.events.emit("junctions:remove", junction)
6333
6424
 
6334
6425
  var removedLinks = links.filter(function(l) { return (l.source === junction) || (l.target === junction); });
@@ -6566,6 +6657,9 @@ RED.nodes = (function() {
6566
6657
  RED.view.redraw(true);
6567
6658
  }
6568
6659
  });
6660
+ RED.events.on('deploy', function () {
6661
+ allNodes.clearState()
6662
+ })
6569
6663
  },
6570
6664
  registry:registry,
6571
6665
  setNodeList: registry.setNodeList,
@@ -6668,6 +6762,20 @@ RED.nodes = (function() {
6668
6762
  }
6669
6763
  }
6670
6764
  },
6765
+ eachGroup: function(cb) {
6766
+ for (var group of Object.values(groups)) {
6767
+ if (cb(group) === false) {
6768
+ break
6769
+ }
6770
+ }
6771
+ },
6772
+ eachJunction: function(cb) {
6773
+ for (var junction of Object.values(junctions)) {
6774
+ if (cb(junction) === false) {
6775
+ break
6776
+ }
6777
+ }
6778
+ },
6671
6779
 
6672
6780
  node: getNode,
6673
6781
 
@@ -6690,6 +6798,7 @@ RED.nodes = (function() {
6690
6798
  getAllFlowNodes: getAllFlowNodes,
6691
6799
  getAllUpstreamNodes: getAllUpstreamNodes,
6692
6800
  getAllDownstreamNodes: getAllDownstreamNodes,
6801
+ getDownstreamNodes: getDownstreamNodes,
6693
6802
  getNodeIslands: getNodeIslands,
6694
6803
  createExportableNodeSet: createExportableNodeSet,
6695
6804
  createCompleteNodeSet: createCompleteNodeSet,
@@ -7905,7 +8014,8 @@ RED.history = (function() {
7905
8014
  if (ev.addToGroup) {
7906
8015
  RED.group.removeFromGroup(ev.addToGroup,ev.nodes.map(function(n) { return n.n }),false);
7907
8016
  inverseEv.removeFromGroup = ev.addToGroup;
7908
- } else if (ev.removeFromGroup) {
8017
+ }
8018
+ if (ev.removeFromGroup) {
7909
8019
  RED.group.addToGroup(ev.removeFromGroup,ev.nodes.map(function(n) { return n.n }));
7910
8020
  inverseEv.addToGroup = ev.removeFromGroup;
7911
8021
  }
@@ -7948,6 +8058,9 @@ RED.history = (function() {
7948
8058
  ev.node[i] = ev.changes[i];
7949
8059
  }
7950
8060
  }
8061
+ ev.node.dirty = true;
8062
+ ev.node.changed = ev.changed;
8063
+
7951
8064
  var eventType;
7952
8065
  switch(ev.node.type) {
7953
8066
  case 'tab': eventType = "flows"; break;
@@ -8038,8 +8151,6 @@ RED.history = (function() {
8038
8151
  inverseEv.links.push(ev.createdLinks[i]);
8039
8152
  }
8040
8153
  }
8041
- ev.node.dirty = true;
8042
- ev.node.changed = ev.changed;
8043
8154
  } else if (ev.t == "createSubflow") {
8044
8155
  inverseEv = {
8045
8156
  t: "deleteSubflow",
@@ -8175,6 +8286,12 @@ RED.history = (function() {
8175
8286
  ev.groups[i].nodes = [];
8176
8287
  RED.nodes.addGroup(ev.groups[i]);
8177
8288
  RED.group.addToGroup(ev.groups[i],nodes);
8289
+ if (ev.groups[i].g) {
8290
+ const parentGroup = RED.nodes.group(ev.groups[i].g)
8291
+ if (parentGroup) {
8292
+ RED.group.addToGroup(parentGroup, ev.groups[i])
8293
+ }
8294
+ }
8178
8295
  }
8179
8296
  }
8180
8297
  } else if (ev.t == "addToGroup") {
@@ -10292,6 +10409,9 @@ RED.utils = (function() {
10292
10409
  } else {
10293
10410
  return null;
10294
10411
  }
10412
+ },
10413
+ cancel: function() {
10414
+ this.element.sortable("cancel");
10295
10415
  }
10296
10416
  });
10297
10417
  })(jQuery);
@@ -12937,7 +13057,7 @@ RED.tabs = (function() {
12937
13057
  // Assume this is wheel event which might not trigger
12938
13058
  // the scroll event, so do things manually
12939
13059
  var sl = scrollContainer.scrollLeft();
12940
- sl -= evt.originalEvent.deltaY;
13060
+ sl += evt.originalEvent.deltaY;
12941
13061
  scrollContainer.scrollLeft(sl);
12942
13062
  }
12943
13063
  })
@@ -13599,7 +13719,6 @@ RED.tabs = (function() {
13599
13719
 
13600
13720
  var badges = $('<span class="red-ui-tabs-badges"></span>').appendTo(li);
13601
13721
  if (options.onselect) {
13602
- $('<i class="red-ui-tabs-badge-changed fa fa-circle"></i>').appendTo(badges);
13603
13722
  $('<i class="red-ui-tabs-badge-selected fa fa-check-circle"></i>').appendTo(badges);
13604
13723
  }
13605
13724
 
@@ -16154,12 +16273,17 @@ RED.deploy = (function() {
16154
16273
  } else {
16155
16274
  RED.notify('<p>' + RED._("deploy.successfulDeploy") + '</p>', "success");
16156
16275
  }
16157
- RED.nodes.eachNode(function (node) {
16158
- const flow = node.z && (RED.nodes.workspace(node.z) || RED.nodes.subflow(node.z) || null);
16276
+ const flowsToLock = new Set()
16277
+ function ensureUnlocked(id) {
16278
+ const flow = id && (RED.nodes.workspace(id) || RED.nodes.subflow(id) || null);
16159
16279
  const isLocked = flow ? flow.locked : false;
16160
16280
  if (flow && isLocked) {
16161
16281
  flow.locked = false;
16282
+ flowsToLock.add(flow)
16162
16283
  }
16284
+ }
16285
+ RED.nodes.eachNode(function (node) {
16286
+ ensureUnlocked(node.z)
16163
16287
  if (node.changed) {
16164
16288
  node.dirty = true;
16165
16289
  node.changed = false;
@@ -16171,11 +16295,33 @@ RED.deploy = (function() {
16171
16295
  if (node.credentials) {
16172
16296
  delete node.credentials;
16173
16297
  }
16174
- if (flow && isLocked) {
16175
- flow.locked = isLocked;
16176
- }
16177
16298
  });
16299
+ RED.nodes.eachGroup(function (node) {
16300
+ ensureUnlocked(node.z)
16301
+ if (node.changed) {
16302
+ node.dirty = true;
16303
+ node.changed = false;
16304
+ }
16305
+ if (node.moved) {
16306
+ node.dirty = true;
16307
+ node.moved = false;
16308
+ }
16309
+ })
16310
+ RED.nodes.eachJunction(function (node) {
16311
+ ensureUnlocked(node.z)
16312
+ if (node.changed) {
16313
+ node.dirty = true;
16314
+ node.changed = false;
16315
+ }
16316
+ if (node.moved) {
16317
+ node.dirty = true;
16318
+ node.moved = false;
16319
+ }
16320
+ })
16178
16321
  RED.nodes.eachConfig(function (confNode) {
16322
+ if (confNode.z) {
16323
+ ensureUnlocked(confNode.z)
16324
+ }
16179
16325
  confNode.changed = false;
16180
16326
  if (confNode.credentials) {
16181
16327
  delete confNode.credentials;
@@ -16185,8 +16331,16 @@ RED.deploy = (function() {
16185
16331
  subflow.changed = false;
16186
16332
  });
16187
16333
  RED.nodes.eachWorkspace(function (ws) {
16188
- ws.changed = false;
16334
+ if (ws.changed || ws.added) {
16335
+ ensureUnlocked(ws.z)
16336
+ ws.changed = false;
16337
+ delete ws.added
16338
+ RED.events.emit("flows:change", ws)
16339
+ }
16189
16340
  });
16341
+ flowsToLock.forEach(flow => {
16342
+ flow.locked = true
16343
+ })
16190
16344
  // Once deployed, cannot undo back to a clean state
16191
16345
  RED.history.markAllDirty();
16192
16346
  RED.view.redraw();
@@ -19011,7 +19165,11 @@ RED.keyboard = (function() {
19011
19165
  okButton.attr("disabled",!valid);
19012
19166
  });
19013
19167
 
19014
- var scopeSelect = $('<select><option value="*" data-i18n="keyboard.global"></option><option value="red-ui-workspace" data-i18n="keyboard.workspace"></option></select>').appendTo(scope);
19168
+ var scopeSelect = $('<select>'+
19169
+ '<option value="*" data-i18n="keyboard.global"></option>'+
19170
+ '<option value="red-ui-workspace" data-i18n="keyboard.workspace"></option>'+
19171
+ '<option value="red-ui-editor-stack" data-i18n="keyboard.editor"></option>'+
19172
+ '</select>').appendTo(scope);
19015
19173
  scopeSelect.i18n();
19016
19174
  if (object.scope === "workspace") {
19017
19175
  object.scope = "red-ui-workspace";
@@ -19268,7 +19426,11 @@ RED.keyboard = (function() {
19268
19426
  var new_env = [];
19269
19427
  var items = list.editableList('items');
19270
19428
  var credentials = gconf ? gconf.credentials : null;
19271
-
19429
+ if (!gconf && list.editableList('length') === 0) {
19430
+ // No existing global-config node and nothing in the list,
19431
+ // so no need to do anything more
19432
+ return
19433
+ }
19272
19434
  if (!credentials) {
19273
19435
  credentials = {
19274
19436
  _ : {},
@@ -19296,6 +19458,12 @@ RED.keyboard = (function() {
19296
19458
  if (gconf === null) {
19297
19459
  gconf = getGlobalConf(true);
19298
19460
  }
19461
+ if (!gconf.credentials) {
19462
+ gconf.credentials = {
19463
+ _ : {},
19464
+ map: {}
19465
+ };
19466
+ }
19299
19467
  if ((JSON.stringify(new_env) !== JSON.stringify(gconf.env)) ||
19300
19468
  (JSON.stringify(credentials) !== JSON.stringify(gconf.credentials))) {
19301
19469
  gconf.env = new_env;
@@ -19384,6 +19552,10 @@ RED.keyboard = (function() {
19384
19552
  }
19385
19553
  }
19386
19554
  });
19555
+
19556
+ RED.actions.add("core:show-global-env", function() {
19557
+ RED.userSettings.show('envvar');
19558
+ });
19387
19559
  }
19388
19560
 
19389
19561
  return {
@@ -19475,8 +19647,11 @@ RED.workspaces = (function() {
19475
19647
  info: "",
19476
19648
  label: RED._('workspace.defaultName',{number:workspaceIndex}),
19477
19649
  env: [],
19478
- hideable: true
19650
+ hideable: true,
19479
19651
  };
19652
+ if (!skipHistoryEntry) {
19653
+ ws.added = true
19654
+ }
19480
19655
  RED.nodes.addWorkspace(ws,targetIndex);
19481
19656
  workspace_tabs.addTab(ws,targetIndex);
19482
19657
 
@@ -19486,8 +19661,7 @@ RED.workspaces = (function() {
19486
19661
  RED.nodes.dirty(true);
19487
19662
  }
19488
19663
  }
19489
- $("#red-ui-tab-"+(ws.id.replace(".","-"))).attr("flowname",ws.label)
19490
-
19664
+ $("#red-ui-tab-"+(ws.id.replace(".","-"))).attr("flowname",ws.label).toggleClass('red-ui-workspace-changed',!!(ws.contentsChanged || ws.changed || ws.added));
19491
19665
  RED.view.focus();
19492
19666
  return ws;
19493
19667
  }
@@ -19553,7 +19727,7 @@ RED.workspaces = (function() {
19553
19727
  }
19554
19728
  });
19555
19729
 
19556
- let isCurrentLocked = RED.workspaces.isActiveLocked()
19730
+ let isCurrentLocked = RED.workspaces.isLocked()
19557
19731
  if (tab) {
19558
19732
  isCurrentLocked = tab.locked
19559
19733
  }
@@ -19768,6 +19942,12 @@ RED.workspaces = (function() {
19768
19942
  $("#red-ui-tab-"+(tab.id.replace(".","-"))).addClass('red-ui-workspace-locked');
19769
19943
  }
19770
19944
 
19945
+ const changeBadgeContainer = $('<svg class="red-ui-flow-tab-changed red-ui-flow-node-changed" width="10" height="10" viewBox="-1 -1 12 12"></svg>').appendTo("#red-ui-tab-"+(tab.id.replace(".","-")))
19946
+ const changeBadge = document.createElementNS("http://www.w3.org/2000/svg","circle");
19947
+ changeBadge.setAttribute("cx",5);
19948
+ changeBadge.setAttribute("cy",5);
19949
+ changeBadge.setAttribute("r",5);
19950
+ changeBadgeContainer.append(changeBadge)
19771
19951
 
19772
19952
  RED.menu.setDisabled("menu-item-workspace-delete",activeWorkspace === 0 || workspaceTabCount <= 1);
19773
19953
  if (workspaceTabCount === 1) {
@@ -20030,6 +20210,11 @@ RED.workspaces = (function() {
20030
20210
  RED.workspaces.show(viewStack[++viewStackPos],true);
20031
20211
  }
20032
20212
  })
20213
+
20214
+ RED.events.on("flows:change", (ws) => {
20215
+ $("#red-ui-tab-"+(ws.id.replace(".","-"))).toggleClass('red-ui-workspace-changed',!!(ws.contentsChanged || ws.changed || ws.added));
20216
+ })
20217
+
20033
20218
  hideWorkspace();
20034
20219
  }
20035
20220
 
@@ -20108,19 +20293,8 @@ RED.workspaces = (function() {
20108
20293
  RED.history.push(historyEvent);
20109
20294
  RED.events.emit("flows:change",workspace);
20110
20295
  RED.nodes.dirty(true);
20111
- // RED.sidebar.config.refresh();
20112
- // var selection = RED.view.selection();
20113
- // if (!selection.nodes && !selection.links && workspace.id === activeWorkspace) {
20114
- // RED.sidebar.info.refresh(workspace);
20115
- // }
20116
- // if (changes.hasOwnProperty('disabled')) {
20117
- // RED.nodes.eachNode(function(n) {
20118
- // if (n.z === workspace.id) {
20119
- // n.dirty = true;
20120
- // }
20121
- // });
20122
- // RED.view.redraw();
20123
- // }
20296
+ RED.nodes.filterNodes({z:workspace.id}).forEach(n => n.dirty = true)
20297
+ RED.view.redraw(true);
20124
20298
  }
20125
20299
  }
20126
20300
 
@@ -20226,8 +20400,9 @@ RED.workspaces = (function() {
20226
20400
  active: function() {
20227
20401
  return activeWorkspace
20228
20402
  },
20229
- isActiveLocked: function() {
20230
- var ws = RED.nodes.workspace(activeWorkspace) || RED.nodes.subflow(activeWorkspace)
20403
+ isLocked: function(id) {
20404
+ id = id || activeWorkspace
20405
+ var ws = RED.nodes.workspace(id) || RED.nodes.subflow(id)
20231
20406
  return ws && ws.locked
20232
20407
  },
20233
20408
  selection: function() {
@@ -20374,8 +20549,8 @@ RED.statusBar = (function() {
20374
20549
  */
20375
20550
 
20376
20551
  RED.view = (function() {
20377
- var space_width = 5000,
20378
- space_height = 5000,
20552
+ var space_width = 8000,
20553
+ space_height = 8000,
20379
20554
  lineCurveScale = 0.75,
20380
20555
  scaleFactor = 1,
20381
20556
  node_width = 100,
@@ -20405,8 +20580,9 @@ RED.view = (function() {
20405
20580
  var activeJunctions = [];
20406
20581
  var activeFlowLinks = [];
20407
20582
  var activeLinkNodes = {};
20408
- var activeGroup = null;
20409
20583
  var activeHoverGroup = null;
20584
+ var groupAddActive = false;
20585
+ var groupAddParentGroup = null;
20410
20586
  var activeGroups = [];
20411
20587
  var dirtyGroups = {};
20412
20588
 
@@ -20468,10 +20644,10 @@ RED.view = (function() {
20468
20644
  var groupLayer;
20469
20645
  var drag_lines;
20470
20646
 
20471
- var movingSet = (function() {
20647
+ const movingSet = (function() {
20472
20648
  var setIds = new Set();
20473
20649
  var set = [];
20474
- var api = {
20650
+ const api = {
20475
20651
  add: function(node) {
20476
20652
  if (Array.isArray(node)) {
20477
20653
  for (var i=0;i<node.length;i++) {
@@ -20519,14 +20695,33 @@ RED.view = (function() {
20519
20695
  get: function(i) { return set[i] },
20520
20696
  forEach: function(func) { set.forEach(func) },
20521
20697
  nodes: function() { return set.map(function(n) { return n.n })},
20522
- has: function(node) { return setIds.has(node.id) }
20698
+ has: function(node) { return setIds.has(node.id) },
20699
+ /**
20700
+ * Make the specified node the first node of the moving set, if
20701
+ * it is already in the set.
20702
+ * @param {Node} node
20703
+ */
20704
+ makePrimary: function (node) {
20705
+ const index = set.findIndex(n => n.n === node)
20706
+ if (index > -1) {
20707
+ const removed = set.splice(index, 1)
20708
+ set.unshift(...removed)
20709
+ }
20710
+ },
20711
+ find: function(func) { return set.find(func) },
20712
+ dump: function () {
20713
+ console.log('MovingSet Contents')
20714
+ api.forEach((n, i) => {
20715
+ console.log(`${i+1}\t${n.n.id}\t${n.n.type}`)
20716
+ })
20717
+ }
20523
20718
  }
20524
20719
  return api;
20525
20720
  })();
20526
20721
 
20527
- var selectedLinks = (function() {
20722
+ const selectedLinks = (function() {
20528
20723
  var links = new Set();
20529
- return {
20724
+ const api = {
20530
20725
  add: function(link) {
20531
20726
  links.add(link);
20532
20727
  link.selected = true;
@@ -20544,10 +20739,75 @@ RED.view = (function() {
20544
20739
  },
20545
20740
  forEach: function(func) { links.forEach(func) },
20546
20741
  has: function(link) { return links.has(link) },
20547
- toArray: function() { return Array.from(links) }
20742
+ toArray: function() { return Array.from(links) },
20743
+ clearUnselected: function () {
20744
+ api.forEach(l => {
20745
+ if (!l.source.selected || !l.target.selected) {
20746
+ api.remove(l)
20747
+ }
20748
+ })
20749
+ }
20548
20750
  }
20751
+ return api
20549
20752
  })();
20550
20753
 
20754
+ const selectedGroups = (function() {
20755
+ let groups = new Set()
20756
+ const api = {
20757
+ add: function(g, includeNodes, addToMovingSet) {
20758
+ groups.add(g)
20759
+ if (!g.selected) {
20760
+ g.selected = true;
20761
+ g.dirty = true;
20762
+ }
20763
+ if (addToMovingSet !== false) {
20764
+ movingSet.add(g);
20765
+ }
20766
+ if (includeNodes) {
20767
+ var currentSet = new Set(movingSet.nodes());
20768
+ var allNodes = RED.group.getNodes(g,true);
20769
+ allNodes.forEach(function(n) {
20770
+ if (!currentSet.has(n)) {
20771
+ movingSet.add(n)
20772
+ }
20773
+ n.dirty = true;
20774
+ })
20775
+ }
20776
+ selectedLinks.clearUnselected()
20777
+ },
20778
+ remove: function(g) {
20779
+ groups.delete(g)
20780
+ if (g.selected) {
20781
+ g.selected = false;
20782
+ g.dirty = true;
20783
+ }
20784
+ const allNodes = RED.group.getNodes(g,true);
20785
+ const nodeSet = new Set(allNodes);
20786
+ nodeSet.add(g);
20787
+ for (let i = movingSet.length()-1; i >= 0; i -= 1) {
20788
+ const msn = movingSet.get(i);
20789
+ if (nodeSet.has(msn.n) || msn.n === g) {
20790
+ msn.n.selected = false;
20791
+ msn.n.dirty = true;
20792
+ movingSet.remove(msn.n,i)
20793
+ }
20794
+ }
20795
+ selectedLinks.clearUnselected()
20796
+ },
20797
+ length: () => groups.length,
20798
+ forEach: (func) => { groups.forEach(func) },
20799
+ toArray: () => [...groups],
20800
+ clear: function () {
20801
+ groups.forEach(g => {
20802
+ g.selected = false
20803
+ g.dirty = true
20804
+ })
20805
+ groups.clear()
20806
+ }
20807
+ }
20808
+ return api
20809
+ })()
20810
+
20551
20811
 
20552
20812
  function init() {
20553
20813
 
@@ -20588,6 +20848,7 @@ RED.view = (function() {
20588
20848
  d3.select(document).on('mouseup.red-ui-workspace-tracker', null)
20589
20849
  if (lasso) {
20590
20850
  if (d3.event.buttons !== 1) {
20851
+ outer.classed('red-ui-workspace-lasso-active', false)
20591
20852
  lasso.remove();
20592
20853
  lasso = null;
20593
20854
  }
@@ -20722,6 +20983,31 @@ RED.view = (function() {
20722
20983
  }
20723
20984
  d3.event.preventDefault();
20724
20985
  });
20986
+
20987
+
20988
+ const handleAltToggle = (event) => {
20989
+ if (mouse_mode === RED.state.MOVING_ACTIVE && event.key === 'Alt' && groupAddParentGroup) {
20990
+ RED.nodes.group(groupAddParentGroup).dirty = true
20991
+ for (let n = 0; n<movingSet.length(); n++) {
20992
+ const node = movingSet.get(n);
20993
+ node.n._detachFromGroup = event.altKey
20994
+ }
20995
+ if (!event.altKey) {
20996
+ if (groupHoverTimer) {
20997
+ clearTimeout(groupHoverTimer)
20998
+ groupHoverTimer = null
20999
+ }
21000
+ if (activeHoverGroup) {
21001
+ activeHoverGroup.hovered = false
21002
+ activeHoverGroup.dirty = true
21003
+ activeHoverGroup = null
21004
+ }
21005
+ }
21006
+ RED.view.redraw()
21007
+ }
21008
+ }
21009
+ document.addEventListener("keyup", handleAltToggle)
21010
+ document.addEventListener("keydown", handleAltToggle)
20725
21011
 
20726
21012
  // Workspace Background
20727
21013
  eventLayer.append("svg:rect")
@@ -20930,27 +21216,11 @@ RED.view = (function() {
20930
21216
  nn.y -= gridOffset.y;
20931
21217
  }
20932
21218
 
20933
- var spliceLink = $(ui.helper).data("splice");
20934
- if (spliceLink) {
20935
- // TODO: DRY - droppable/nodeMouseDown/canvasMouseUp/showQuickAddDialog
20936
- RED.nodes.removeLink(spliceLink);
20937
- var link1 = {
20938
- source:spliceLink.source,
20939
- sourcePort:spliceLink.sourcePort,
20940
- target: nn
20941
- };
20942
- var link2 = {
20943
- source:nn,
20944
- sourcePort:0,
20945
- target: spliceLink.target
20946
- };
20947
- RED.nodes.addLink(link1);
20948
- RED.nodes.addLink(link2);
20949
- historyEvent.links = [link1,link2];
20950
- historyEvent.removedLinks = [spliceLink];
21219
+ var linkToSplice = $(ui.helper).data("splice");
21220
+ if (linkToSplice) {
21221
+ spliceLink(linkToSplice, nn, historyEvent)
20951
21222
  }
20952
21223
 
20953
-
20954
21224
  var group = $(ui.helper).data("group");
20955
21225
  if (group) {
20956
21226
  var oldX = group.x;
@@ -20987,15 +21257,9 @@ RED.view = (function() {
20987
21257
  RED.editor.validateNode(nn);
20988
21258
  RED.nodes.dirty(true);
20989
21259
  // auto select dropped node - so info shows (if visible)
20990
- exitActiveGroup();
20991
21260
  clearSelection();
20992
21261
  nn.selected = true;
20993
21262
  movingSet.add(nn);
20994
- if (group) {
20995
- selectGroup(group,false);
20996
- enterActiveGroup(group);
20997
- activeGroup = group;
20998
- }
20999
21263
  updateActiveNodes();
21000
21264
  updateSelection();
21001
21265
  redraw();
@@ -21015,7 +21279,7 @@ RED.view = (function() {
21015
21279
  RED.actions.add("core:copy-selection-to-internal-clipboard",copySelection);
21016
21280
  RED.actions.add("core:cut-selection-to-internal-clipboard",function(){copySelection(true);deleteSelection();});
21017
21281
  RED.actions.add("core:paste-from-internal-clipboard",function(){
21018
- if (RED.workspaces.isActiveLocked()) {
21282
+ if (RED.workspaces.isLocked()) {
21019
21283
  return
21020
21284
  }
21021
21285
  importNodes(clipboard,{generateIds: clipboardSource === 'copy', generateDefaultNames: clipboardSource === 'copy'});
@@ -21058,7 +21322,7 @@ RED.view = (function() {
21058
21322
  if (/^subflow:/.test(node.type)) {
21059
21323
  RED.workspaces.show(node.type.substring(8))
21060
21324
  } else if (node.type === 'group') {
21061
- enterActiveGroup(node);
21325
+ // enterActiveGroup(node);
21062
21326
  redraw();
21063
21327
  }
21064
21328
  }
@@ -21246,16 +21510,31 @@ RED.view = (function() {
21246
21510
  });
21247
21511
  activeJunctions = RED.nodes.junctions(activeWorkspace) || [];
21248
21512
  activeGroups = RED.nodes.groups(activeWorkspace)||[];
21249
- activeGroups.forEach(function(g, i) {
21250
- g._index = i;
21251
- if (g.g) {
21252
- g._root = g.g;
21253
- g._depth = 1;
21254
- } else {
21255
- g._root = g.id;
21256
- g._depth = 0;
21513
+ if (activeGroups.length) {
21514
+ const groupTree = {}
21515
+ const rootGroups = []
21516
+ activeGroups.forEach(function(g, i) {
21517
+ groupTree[g.id] = g
21518
+ g._index = i;
21519
+ g._childGroups = []
21520
+ if (!g.g) {
21521
+ rootGroups.push(g)
21522
+ }
21523
+ });
21524
+ activeGroups.forEach(function(g) {
21525
+ if (g.g) {
21526
+ groupTree[g.g]._childGroups.push(g)
21527
+ g._parentGroup = groupTree[g.g]
21528
+ }
21529
+ })
21530
+ let ii = 0
21531
+ // Depth-first walk of the groups
21532
+ const processGroup = g => {
21533
+ g._order = ii++
21534
+ g._childGroups.forEach(processGroup)
21257
21535
  }
21258
- });
21536
+ rootGroups.forEach(processGroup)
21537
+ }
21259
21538
  } else {
21260
21539
  activeNodes = [];
21261
21540
  activeLinks = [];
@@ -21263,43 +21542,13 @@ RED.view = (function() {
21263
21542
  activeGroups = [];
21264
21543
  }
21265
21544
 
21266
- var changed = false;
21267
- do {
21268
- changed = false;
21269
- activeGroups.forEach(function(g) {
21270
- if (g.g) {
21271
- var parentGroup = RED.nodes.group(g.g);
21272
- if (parentGroup) {
21273
- var parentDepth = parentGroup._depth;
21274
- if (g._depth !== parentDepth + 1) {
21275
- g._depth = parentDepth + 1;
21276
- changed = true;
21277
- }
21278
- if (g._root !== parentGroup._root) {
21279
- g._root = parentGroup._root;
21280
- changed = true;
21281
- }
21282
- }
21283
- }
21284
- });
21285
- } while (changed)
21286
21545
  activeGroups.sort(function(a,b) {
21287
- if (a._root === b._root) {
21288
- return a._depth - b._depth;
21289
- } else {
21290
- // return a._root.localeCompare(b._root);
21291
- return a._index - b._index;
21292
- }
21546
+ return a._order - b._order
21293
21547
  });
21294
21548
 
21295
21549
  var group = groupLayer.selectAll(".red-ui-flow-group").data(activeGroups,function(d) { return d.id });
21296
21550
  group.sort(function(a,b) {
21297
- if (a._root === b._root) {
21298
- return a._depth - b._depth;
21299
- } else {
21300
- return a._index - b._index;
21301
- // return a._root.localeCompare(b._root);
21302
- }
21551
+ return a._order - b._order
21303
21552
  })
21304
21553
  }
21305
21554
 
@@ -21404,6 +21653,7 @@ RED.view = (function() {
21404
21653
  updateSelection();
21405
21654
  }
21406
21655
  if (mouse_mode === 0 && lasso) {
21656
+ outer.classed('red-ui-workspace-lasso-active', false)
21407
21657
  lasso.remove();
21408
21658
  lasso = null;
21409
21659
  }
@@ -21435,6 +21685,7 @@ RED.view = (function() {
21435
21685
  .attr("height", 0)
21436
21686
  .attr("class", "nr-ui-view-lasso");
21437
21687
  d3.event.preventDefault();
21688
+ outer.classed('red-ui-workspace-lasso-active', true)
21438
21689
  }
21439
21690
  } else if (d3.event.altKey && !activeFlowLocked) {
21440
21691
  //Alt [+shift] held - Begin slicing
@@ -21455,14 +21706,13 @@ RED.view = (function() {
21455
21706
  }
21456
21707
  options = options || {};
21457
21708
  var point = options.position || lastClickPosition;
21458
- var spliceLink = options.splice;
21709
+ var linkToSplice = options.splice;
21459
21710
  var spliceMultipleLinks = options.spliceMultiple
21460
21711
  var targetGroup = options.group;
21461
21712
  var touchTrigger = options.touchTrigger;
21462
21713
 
21463
- if (targetGroup && !targetGroup.active) {
21464
- selectGroup(targetGroup,false);
21465
- enterActiveGroup(targetGroup);
21714
+ if (targetGroup) {
21715
+ selectedGroups.add(targetGroup,false);
21466
21716
  RED.view.redraw();
21467
21717
  }
21468
21718
 
@@ -21527,7 +21777,7 @@ RED.view = (function() {
21527
21777
  }
21528
21778
  hideDragLines();
21529
21779
  }
21530
- if (spliceLink || spliceMultipleLinks) {
21780
+ if (linkToSplice || spliceMultipleLinks) {
21531
21781
  filter = {
21532
21782
  input:true,
21533
21783
  output:true,
@@ -21611,7 +21861,8 @@ RED.view = (function() {
21611
21861
  w: 0, h: 0,
21612
21862
  outputs: 1,
21613
21863
  inputs: 1,
21614
- dirty: true
21864
+ dirty: true,
21865
+ moved: true
21615
21866
  }
21616
21867
  historyEvent = {
21617
21868
  t:'add',
@@ -21777,24 +22028,9 @@ RED.view = (function() {
21777
22028
  }
21778
22029
  }
21779
22030
 
21780
- if (spliceLink) {
22031
+ if (linkToSplice) {
21781
22032
  resetMouseVars();
21782
- // TODO: DRY - droppable/nodeMouseDown/canvasMouseUp/showQuickAddDialog
21783
- RED.nodes.removeLink(spliceLink);
21784
- var link1 = {
21785
- source:spliceLink.source,
21786
- sourcePort:spliceLink.sourcePort,
21787
- target: nn
21788
- };
21789
- var link2 = {
21790
- source:nn,
21791
- sourcePort:0,
21792
- target: spliceLink.target
21793
- };
21794
- RED.nodes.addLink(link1);
21795
- RED.nodes.addLink(link2);
21796
- historyEvent.links = (historyEvent.links || []).concat([link1,link2]);
21797
- historyEvent.removedLinks = [spliceLink];
22033
+ spliceLink(linkToSplice, nn, historyEvent)
21798
22034
  }
21799
22035
  RED.history.push(historyEvent);
21800
22036
  RED.nodes.dirty(true);
@@ -21802,8 +22038,7 @@ RED.view = (function() {
21802
22038
  clearSelection();
21803
22039
  nn.selected = true;
21804
22040
  if (targetGroup) {
21805
- selectGroup(targetGroup,false);
21806
- enterActiveGroup(targetGroup);
22041
+ selectedGroups.add(targetGroup,false);
21807
22042
  }
21808
22043
  movingSet.add(nn);
21809
22044
  updateActiveNodes();
@@ -22018,16 +22253,11 @@ RED.view = (function() {
22018
22253
  if ((d > 3 && !dblClickPrimed) || (dblClickPrimed && d > 10)) {
22019
22254
  clickElapsed = 0;
22020
22255
  if (!activeFlowLocked) {
22021
- mouse_mode = RED.state.MOVING_ACTIVE;
22022
- spliceActive = false;
22023
- if (movingSet.length() === 1) {
22024
- node = movingSet.get(0);
22025
- spliceActive = node.n.hasOwnProperty("_def") &&
22026
- ((node.n.hasOwnProperty("inputs") && node.n.inputs > 0) || (!node.n.hasOwnProperty("inputs") && node.n._def.inputs > 0)) &&
22027
- ((node.n.hasOwnProperty("outputs") && node.n.outputs > 0) || (!node.n.hasOwnProperty("outputs") && node.n._def.outputs > 0)) &&
22028
- RED.nodes.filterLinks({ source: node.n }).length === 0 &&
22029
- RED.nodes.filterLinks({ target: node.n }).length === 0;
22256
+ if (mousedown_node) {
22257
+ movingSet.makePrimary(mousedown_node)
22030
22258
  }
22259
+ mouse_mode = RED.state.MOVING_ACTIVE;
22260
+ startSelectionMove()
22031
22261
  }
22032
22262
  }
22033
22263
  } else if (mouse_mode == RED.state.MOVING_ACTIVE || mouse_mode == RED.state.IMPORT_DRAGGING || mouse_mode == RED.state.DETACHED_DRAGGING) {
@@ -22042,6 +22272,7 @@ RED.view = (function() {
22042
22272
  node.n.ox = node.n.x;
22043
22273
  node.n.oy = node.n.y;
22044
22274
  }
22275
+ node.n._detachFromGroup = d3.event.altKey
22045
22276
  node.n.x = mousePos[0]+node.dx;
22046
22277
  node.n.y = mousePos[1]+node.dy;
22047
22278
  node.n.dirty = true;
@@ -22110,9 +22341,8 @@ RED.view = (function() {
22110
22341
  }
22111
22342
  }
22112
22343
 
22113
- // Check link splice or group-add
22344
+ // Check link splice
22114
22345
  if (movingSet.length() === 1 && movingSet.get(0).n.type !== "group") {
22115
- //}{//NIS
22116
22346
  node = movingSet.get(0);
22117
22347
  if (spliceActive) {
22118
22348
  if (!spliceTimer) {
@@ -22160,23 +22390,39 @@ RED.view = (function() {
22160
22390
  },100);
22161
22391
  }
22162
22392
  }
22163
- if (node.n.type !== 'subflow' && !node.n.g && activeGroups) {
22164
- if (!groupHoverTimer) {
22165
- groupHoverTimer = setTimeout(function() {
22166
- activeHoverGroup = getGroupAt(node.n.x,node.n.y);
22167
- for (var i=0;i<activeGroups.length;i++) {
22168
- var g = activeGroups[i];
22169
- if (g === activeHoverGroup) {
22170
- g.hovered = true;
22171
- g.dirty = true;
22172
- } else if (g.hovered) {
22173
- g.hovered = false;
22174
- g.dirty = true;
22393
+ }
22394
+ // Check merge into group
22395
+ if (groupAddActive) {
22396
+ if (!groupHoverTimer) {
22397
+ const isDetachFromGroup = d3.event.altKey
22398
+ groupHoverTimer = setTimeout(function() {
22399
+ node = movingSet.get(0);
22400
+ const hoveredGroup = getGroupAt(mousePos[0],mousePos[1], true);
22401
+ if (hoveredGroup !== activeHoverGroup) {
22402
+ if (activeHoverGroup) {
22403
+ activeHoverGroup.hovered = false
22404
+ activeHoverGroup.dirty = true
22405
+ }
22406
+ activeHoverGroup = hoveredGroup
22407
+ }
22408
+ if (activeHoverGroup && groupAddParentGroup && !isDetachFromGroup) {
22409
+ if (groupAddParentGroup === activeHoverGroup.id) {
22410
+ activeHoverGroup = null
22411
+ } else {
22412
+ const nodeGroup = RED.nodes.group(groupAddParentGroup)
22413
+ // This node is already in a group. It should only be draggable
22414
+ // into a group that is a child of the group its in
22415
+ if (!RED.group.contains(nodeGroup, activeHoverGroup)) {
22416
+ activeHoverGroup = null
22175
22417
  }
22176
22418
  }
22177
- groupHoverTimer = null;
22178
- },50);
22179
- }
22419
+ }
22420
+ if (activeHoverGroup) {
22421
+ activeHoverGroup.hovered = true
22422
+ activeHoverGroup.dirty = true
22423
+ }
22424
+ groupHoverTimer = null;
22425
+ }, 50);
22180
22426
  }
22181
22427
  }
22182
22428
 
@@ -22230,6 +22476,16 @@ RED.view = (function() {
22230
22476
  };
22231
22477
  RED.history.push(historyEvent);
22232
22478
  RED.nodes.dirty(true);
22479
+ } else {
22480
+ // Trigger quick add dialog
22481
+ d3.event.stopPropagation();
22482
+ clearSelection();
22483
+ const point = d3.mouse(this);
22484
+ var clickedGroup = getGroupAt(point[0], point[1]);
22485
+ if (drag_lines.length > 0) {
22486
+ clickedGroup = clickedGroup || RED.nodes.group(drag_lines[0].node.g)
22487
+ }
22488
+ showQuickAddDialog({ position: point, group: clickedGroup });
22233
22489
  }
22234
22490
  hideDragLines();
22235
22491
  }
@@ -22238,56 +22494,29 @@ RED.view = (function() {
22238
22494
  var y = parseInt(lasso.attr("y"));
22239
22495
  var x2 = x+parseInt(lasso.attr("width"));
22240
22496
  var y2 = y+parseInt(lasso.attr("height"));
22241
- var ag = activeGroup;
22242
22497
  if (!d3.event.shiftKey) {
22243
22498
  clearSelection();
22244
- if (ag) {
22245
- if (x < ag.x+ag.w && x2 > ag.x && y < ag.y+ag.h && y2 > ag.y) {
22246
- // There was an active group and the lasso intersects with it,
22247
- // so reenter the group
22248
- enterActiveGroup(ag);
22249
- activeGroup.selected = true;
22250
- }
22251
- }
22252
22499
  }
22253
- activeGroups.forEach(function(g) {
22254
- if (!g.selected) {
22255
- if (g.x > x && g.x+g.w < x2 && g.y > y && g.y+g.h < y2) {
22256
- if (!activeGroup || RED.group.contains(activeGroup,g)) {
22257
- while (g.g && (!activeGroup || g.g !== activeGroup.id)) {
22258
- g = RED.nodes.group(g.g);
22259
- }
22260
- if (!g.selected) {
22261
- selectGroup(g,true);
22262
- }
22263
- }
22500
+
22501
+ activeGroups.forEach(function(n) {
22502
+ if (!movingSet.has(n) && !n.selected) {
22503
+ // group entirely within lasso
22504
+ if (n.x > x && n.y > y && n.x + n.w < x2 && n.y + n.h < y2) {
22505
+ selectedGroups.add(n, true)
22264
22506
  }
22265
22507
  }
22266
22508
  })
22267
-
22268
22509
  activeNodes.forEach(function(n) {
22269
- if (!n.selected) {
22510
+ if (!movingSet.has(n) && !n.selected) {
22270
22511
  if (n.x > x && n.x < x2 && n.y > y && n.y < y2) {
22271
- if (!activeGroup || RED.group.contains(activeGroup,n)) {
22272
- if (n.g && (!activeGroup || n.g !== activeGroup.id)) {
22273
- var group = RED.nodes.group(n.g);
22274
- while (group.g && (!activeGroup || group.g !== activeGroup.id)) {
22275
- group = RED.nodes.group(group.g);
22276
- }
22277
- if (!group.selected) {
22278
- selectGroup(group,true);
22279
- }
22280
- } else {
22281
- n.selected = true;
22282
- n.dirty = true;
22283
- movingSet.add(n);
22284
- }
22285
- }
22512
+ n.selected = true;
22513
+ n.dirty = true;
22514
+ movingSet.add(n);
22286
22515
  }
22287
22516
  }
22288
22517
  });
22289
22518
  activeJunctions.forEach(function(n) {
22290
- if (!n.selected) {
22519
+ if (!movingSet.has(n) && !n.selected) {
22291
22520
  if (n.x > x && n.x < x2 && n.y > y && n.y < y2) {
22292
22521
  n.selected = true;
22293
22522
  n.dirty = true;
@@ -22310,17 +22539,6 @@ RED.view = (function() {
22310
22539
  }
22311
22540
  })
22312
22541
 
22313
- // var selectionChanged = false;
22314
- // do {
22315
- // selectionChanged = false;
22316
- // selectedGroups.forEach(function(g) {
22317
- // if (g.g && g.selected && RED.nodes.group(g.g).selected) {
22318
- // g.selected = false;
22319
- // selectionChanged = true;
22320
- // }
22321
- // })
22322
- // } while(selectionChanged);
22323
-
22324
22542
  if (activeSubflow) {
22325
22543
  activeSubflow.in.forEach(function(n) {
22326
22544
  n.selected = (n.x > x && n.x < x2 && n.y > y && n.y < y2);
@@ -22345,6 +22563,7 @@ RED.view = (function() {
22345
22563
  }
22346
22564
  }
22347
22565
  updateSelection();
22566
+ outer.classed('red-ui-workspace-lasso-active', false)
22348
22567
  lasso.remove();
22349
22568
  lasso = null;
22350
22569
  } else if (mouse_mode == RED.state.DEFAULT && mousedown_link == null && !d3.event.ctrlKey && !d3.event.metaKey ) {
@@ -22359,210 +22578,80 @@ RED.view = (function() {
22359
22578
  RED.actions.invoke("core:split-wires-with-junctions")
22360
22579
  slicePath.remove();
22361
22580
  slicePath = null;
22362
-
22363
- // var removedLinks = new Set()
22364
- // var addedLinks = []
22365
- // var addedJunctions = []
22366
- //
22367
- // var groupedLinks = {}
22368
- // selectedLinks.forEach(function(l) {
22369
- // var sourceId = l.source.id+":"+l.sourcePort
22370
- // groupedLinks[sourceId] = groupedLinks[sourceId] || []
22371
- // groupedLinks[sourceId].push(l)
22372
- //
22373
- // groupedLinks[l.target.id] = groupedLinks[l.target.id] || []
22374
- // groupedLinks[l.target.id].push(l)
22375
- // });
22376
- // var linkGroups = Object.keys(groupedLinks)
22377
- // linkGroups.sort(function(A,B) {
22378
- // return groupedLinks[B].length - groupedLinks[A].length
22379
- // })
22380
- // linkGroups.forEach(function(gid) {
22381
- // var links = groupedLinks[gid]
22382
- // var junction = {
22383
- // _def: {defaults:{}},
22384
- // type: 'junction',
22385
- // z: RED.workspaces.active(),
22386
- // id: RED.nodes.id(),
22387
- // x: 0,
22388
- // y: 0,
22389
- // w: 0, h: 0,
22390
- // outputs: 1,
22391
- // inputs: 1,
22392
- // dirty: true
22393
- // }
22394
- // links = links.filter(function(l) { return !removedLinks.has(l) })
22395
- // if (links.length === 0) {
22396
- // return
22397
- // }
22398
- // links.forEach(function(l) {
22399
- // junction.x += l._sliceLocation.x
22400
- // junction.y += l._sliceLocation.y
22401
- // })
22402
- // junction.x = Math.round(junction.x/links.length)
22403
- // junction.y = Math.round(junction.y/links.length)
22404
- // if (snapGrid) {
22405
- // junction.x = (gridSize*Math.round(junction.x/gridSize));
22406
- // junction.y = (gridSize*Math.round(junction.y/gridSize));
22407
- // }
22408
- //
22409
- // var nodeGroups = new Set()
22410
- //
22411
- // RED.nodes.addJunction(junction)
22412
- // addedJunctions.push(junction)
22413
- // let newLink
22414
- // if (gid === links[0].source.id+":"+links[0].sourcePort) {
22415
- // newLink = {
22416
- // source: links[0].source,
22417
- // sourcePort: links[0].sourcePort,
22418
- // target: junction
22419
- // }
22420
- // } else {
22421
- // newLink = {
22422
- // source: junction,
22423
- // sourcePort: 0,
22424
- // target: links[0].target
22425
- // }
22426
- // }
22427
- // addedLinks.push(newLink)
22428
- // RED.nodes.addLink(newLink)
22429
- // links.forEach(function(l) {
22430
- // removedLinks.add(l)
22431
- // RED.nodes.removeLink(l)
22432
- // let newLink
22433
- // if (gid === l.target.id) {
22434
- // newLink = {
22435
- // source: l.source,
22436
- // sourcePort: l.sourcePort,
22437
- // target: junction
22438
- // }
22439
- // } else {
22440
- // newLink = {
22441
- // source: junction,
22442
- // sourcePort: 0,
22443
- // target: l.target
22444
- // }
22445
- // }
22446
- // addedLinks.push(newLink)
22447
- // RED.nodes.addLink(newLink)
22448
- // nodeGroups.add(l.source.g || "__NONE__")
22449
- // nodeGroups.add(l.target.g || "__NONE__")
22450
- // })
22451
- // if (nodeGroups.size === 1) {
22452
- // var group = nodeGroups.values().next().value
22453
- // if (group !== "__NONE__") {
22454
- // RED.group.addToGroup(RED.nodes.group(group), junction)
22455
- // }
22456
- // }
22457
- // })
22458
- // slicePath.remove();
22459
- // slicePath = null;
22460
- //
22461
- // if (addedJunctions.length > 0) {
22462
- // RED.history.push({
22463
- // t: 'add',
22464
- // links: addedLinks,
22465
- // junctions: addedJunctions,
22466
- // removedLinks: Array.from(removedLinks)
22467
- // })
22468
- // RED.nodes.dirty(true)
22469
- // }
22470
- // RED.view.redraw(true);
22471
22581
  }
22472
22582
  if (mouse_mode == RED.state.MOVING_ACTIVE) {
22473
22583
  if (movingSet.length() > 0) {
22474
- var addedToGroup = null;
22475
- var moveEvent = null;
22476
- if (activeHoverGroup) {
22477
- var oldX = activeHoverGroup.x;
22478
- var oldY = activeHoverGroup.y;
22479
- for (var j=0;j<movingSet.length();j++) {
22480
- var n = movingSet.get(j);
22481
- RED.group.addToGroup(activeHoverGroup,n.n);
22482
- }
22483
- if ((activeHoverGroup.x !== oldX) ||
22484
- (activeHoverGroup.y !== oldY)) {
22485
- moveEvent = {
22486
- t: "move",
22487
- nodes: [{n: activeHoverGroup,
22488
- ox: oldX, oy: oldY,
22489
- dx: activeHoverGroup.x -oldX,
22490
- dy: activeHoverGroup.y -oldY}],
22491
- dirty: true
22492
- };
22493
- }
22494
- addedToGroup = activeHoverGroup;
22584
+ historyEvent = { t: 'multi', events: [] }
22585
+
22586
+ // Check to see if we're dropping into a group
22587
+ const {
22588
+ addedToGroup,
22589
+ removedFromGroup,
22590
+ groupMoveEvent,
22591
+ rehomedNodes
22592
+ } = addMovingSetToGroup()
22495
22593
 
22496
- activeHoverGroup.hovered = false;
22497
- enterActiveGroup(activeHoverGroup)
22498
- // TODO: check back whether this should add to moving_set
22499
- activeGroup.selected = true;
22500
- activeHoverGroup = null;
22594
+ if (groupMoveEvent) {
22595
+ historyEvent.events.push(groupMoveEvent)
22501
22596
  }
22502
22597
 
22503
- var ns = [];
22504
- for (var j=0;j<movingSet.length();j++) {
22505
- var n = movingSet.get(j);
22506
- if (n.ox !== n.n.x || n.oy !== n.n.y) {
22507
- ns.push({n:n.n,ox:n.ox,oy:n.oy,moved:n.n.moved});
22598
+ // Create two lists of nodes:
22599
+ // - nodes that have moved without changing group
22600
+ // - nodes that have moved AND changed group
22601
+ const moveEvent = {
22602
+ t: 'move',
22603
+ nodes: [],
22604
+ dirty: RED.nodes.dirty()
22605
+ }
22606
+ const moveAndChangedGroupEvent = {
22607
+ t: 'move',
22608
+ nodes: [],
22609
+ dirty: RED.nodes.dirty(),
22610
+ addToGroup: addedToGroup,
22611
+ removeFromGroup: removedFromGroup
22612
+ }
22613
+ for (let j = 0; j < movingSet.length(); j++) {
22614
+ const n = movingSet.get(j);
22615
+ delete n.n._detachFromGroup
22616
+ if (n.ox !== n.n.x || n.oy !== n.n.y || addedToGroup) {
22617
+ // This node has moved or added to a group
22618
+ if (rehomedNodes.has(n)) {
22619
+ moveAndChangedGroupEvent.nodes.push({...n})
22620
+ } else {
22621
+ moveEvent.nodes.push({...n})
22622
+ }
22508
22623
  n.n.dirty = true;
22509
22624
  n.n.moved = true;
22510
22625
  }
22511
22626
  }
22512
22627
 
22513
- if (ns.length > 0 && mouse_mode == RED.state.MOVING_ACTIVE) {
22514
- historyEvent = {t:"move",nodes:ns,dirty:RED.nodes.dirty()};
22628
+ // Check to see if we need to splice a link
22629
+ if (moveEvent.nodes.length > 0) {
22630
+ historyEvent.events.push(moveEvent)
22515
22631
  if (activeSpliceLink) {
22516
- // TODO: DRY - droppable/nodeMouseDown/canvasMouseUp
22517
- var spliceLink = d3.select(activeSpliceLink).data()[0];
22518
- RED.nodes.removeLink(spliceLink);
22519
- var link1 = {
22520
- source:spliceLink.source,
22521
- sourcePort:spliceLink.sourcePort,
22522
- target: movingSet.get(0).n
22523
- };
22524
- var link2 = {
22525
- source:movingSet.get(0).n,
22526
- sourcePort:0,
22527
- target: spliceLink.target
22528
- };
22529
- RED.nodes.addLink(link1);
22530
- RED.nodes.addLink(link2);
22531
- historyEvent.links = [link1,link2];
22532
- historyEvent.removedLinks = [spliceLink];
22533
- updateActiveNodes();
22534
- }
22535
- if (addedToGroup) {
22536
- historyEvent.addToGroup = addedToGroup;
22632
+ var linkToSplice = d3.select(activeSpliceLink).data()[0];
22633
+ spliceLink(linkToSplice, movingSet.get(0).n, moveEvent)
22537
22634
  }
22635
+ }
22636
+ if (moveAndChangedGroupEvent.nodes.length > 0) {
22637
+ historyEvent.events.push(moveAndChangedGroupEvent)
22638
+ }
22639
+
22640
+ // Only continue if something has moved
22641
+ if (historyEvent.events.length > 0) {
22538
22642
  RED.nodes.dirty(true);
22539
- if (moveEvent) {
22540
- historyEvent = {
22541
- t: "multi",
22542
- events: [moveEvent, historyEvent]
22543
- };
22643
+ if (historyEvent.events.length === 1) {
22644
+ // Keep history tidy - no need for multi-event
22645
+ RED.history.push(historyEvent.events[0]);
22646
+ } else {
22647
+ // Multiple events - push the whole lot as one
22648
+ RED.history.push(historyEvent);
22544
22649
  }
22545
- RED.history.push(historyEvent);
22650
+ updateActiveNodes();
22546
22651
  }
22547
22652
  }
22548
22653
  }
22549
- // if (mouse_mode === RED.state.MOVING && mousedown_node && mousedown_node.g) {
22550
- // if (mousedown_node.gSelected) {
22551
- // delete mousedown_node.gSelected
22552
- // } else {
22553
- // if (!d3.event.ctrlKey && !d3.event.metaKey) {
22554
- // clearSelection();
22555
- // }
22556
- // RED.nodes.group(mousedown_node.g).selected = true;
22557
- // mousedown_node.selected = true;
22558
- // mousedown_node.dirty = true;
22559
- // movingSet.add(mousedown_node);
22560
- // }
22561
- // }
22562
22654
  if (mouse_mode == RED.state.MOVING || mouse_mode == RED.state.MOVING_ACTIVE || mouse_mode == RED.state.DETACHED_DRAGGING) {
22563
- // if (mousedown_node) {
22564
- // delete mousedown_node.gSelected;
22565
- // }
22566
22655
  if (mouse_mode === RED.state.DETACHED_DRAGGING) {
22567
22656
  var ns = [];
22568
22657
  for (var j=0;j<movingSet.length();j++) {
@@ -22600,6 +22689,95 @@ RED.view = (function() {
22600
22689
  redraw();
22601
22690
  }
22602
22691
 
22692
+
22693
+ function spliceLink(link, node, historyEvent) {
22694
+ RED.nodes.removeLink(link);
22695
+ const link1 = {
22696
+ source: link.source,
22697
+ sourcePort: link.sourcePort,
22698
+ target: node
22699
+ };
22700
+ const link2 = {
22701
+ source: node,
22702
+ sourcePort: 0,
22703
+ target: link.target
22704
+ };
22705
+ RED.nodes.addLink(link1);
22706
+ RED.nodes.addLink(link2);
22707
+
22708
+ historyEvent.links = (historyEvent.links || []).concat([link1,link2]);
22709
+ historyEvent.removedLinks = [link];
22710
+ }
22711
+
22712
+ function addMovingSetToGroup() {
22713
+
22714
+ const isDetachFromGroup = groupAddParentGroup && d3.event.altKey
22715
+
22716
+ let addedToGroup = null;
22717
+ let removedFromGroup = null;
22718
+ let groupMoveEvent = null;
22719
+ let rehomedNodes = new Set()
22720
+
22721
+ if (activeHoverGroup) {
22722
+ // Nodes are being dropped into a group. We have to assume at
22723
+ // this point that everything in the movingSet is valid for adding
22724
+ // to this group. But it could be a mix of nodes and existing groups.
22725
+ // In which case, we don't want to rehome all of the nodes inside
22726
+ // existing groups - we just want to rehome the top level objects.
22727
+ var oldX = activeHoverGroup.x;
22728
+ var oldY = activeHoverGroup.y;
22729
+ if (groupAddParentGroup) {
22730
+ removedFromGroup = RED.nodes.group(groupAddParentGroup)
22731
+ }
22732
+ // Second pass - now we know what to move, we can move it
22733
+ for (let j=0;j<movingSet.length();j++) {
22734
+ const n = movingSet.get(j)
22735
+ if (!n.n.g || (removedFromGroup && n.n.g === removedFromGroup.id)) {
22736
+ rehomedNodes.add(n)
22737
+ RED.group.addToGroup(activeHoverGroup, n.n);
22738
+ }
22739
+ }
22740
+ if ((activeHoverGroup.x !== oldX) ||
22741
+ (activeHoverGroup.y !== oldY)) {
22742
+ groupMoveEvent = {
22743
+ t: "move",
22744
+ nodes: [{n: activeHoverGroup,
22745
+ ox: oldX, oy: oldY,
22746
+ dx: activeHoverGroup.x -oldX,
22747
+ dy: activeHoverGroup.y -oldY}],
22748
+ dirty: true
22749
+ };
22750
+ }
22751
+ addedToGroup = activeHoverGroup;
22752
+ activeHoverGroup.hovered = false;
22753
+ activeHoverGroup = null;
22754
+ } else if (isDetachFromGroup) {
22755
+ // The nodes are being removed from their group
22756
+ removedFromGroup = RED.nodes.group(groupAddParentGroup)
22757
+ for (let j=0;j<movingSet.length();j++) {
22758
+ const n = movingSet.get(j)
22759
+ if (n.n.g && n.n.g === removedFromGroup.id) {
22760
+ rehomedNodes.add(n)
22761
+ RED.group.removeFromGroup(removedFromGroup, n.n);
22762
+ }
22763
+ }
22764
+ }
22765
+ activeGroups.forEach(g => {
22766
+ if (g.hovered) {
22767
+ g.hovered = false
22768
+ g.dirty = true
22769
+ }
22770
+ })
22771
+
22772
+ return {
22773
+ addedToGroup,
22774
+ removedFromGroup,
22775
+ groupMoveEvent,
22776
+ rehomedNodes
22777
+ }
22778
+
22779
+ }
22780
+
22603
22781
  function zoomIn() {
22604
22782
  if (scaleFactor < 2) {
22605
22783
  zoomView(scaleFactor+0.1);
@@ -22658,10 +22836,9 @@ RED.view = (function() {
22658
22836
  }
22659
22837
  clearSelection();
22660
22838
  } else if (lasso) {
22839
+ outer.classed('red-ui-workspace-lasso-active', false)
22661
22840
  lasso.remove();
22662
22841
  lasso = null;
22663
- } else if (activeGroup) {
22664
- exitActiveGroup()
22665
22842
  } else {
22666
22843
  clearSelection();
22667
22844
  }
@@ -22672,82 +22849,61 @@ RED.view = (function() {
22672
22849
  return;
22673
22850
  }
22674
22851
  selectedLinks.clear();
22675
-
22676
- if (activeGroup) {
22677
- var ag = activeGroup;
22678
- clearSelection();
22679
- enterActiveGroup(ag);
22680
-
22681
- var groupNodes = RED.group.getNodes(ag,false);
22682
- groupNodes.forEach(function(n) {
22683
- if (n.type === 'group') {
22684
- selectGroup(n,true,true);
22685
- } else {
22686
- movingSet.add(n)
22687
- n.selected = true;
22688
- n.dirty = true;
22689
- }
22690
- })
22691
- activeGroup.selected = true;
22692
- } else {
22693
-
22694
- clearSelection();
22695
- exitActiveGroup();
22696
- activeGroups.forEach(function(g) {
22697
- if (!g.g) {
22698
- selectGroup(g, true);
22699
- if (!g.selected) {
22700
- g.selected = true;
22701
- g.dirty = true;
22702
- }
22703
- } else {
22704
- g.selected = false;
22852
+ clearSelection();
22853
+ activeGroups.forEach(function(g) {
22854
+ if (!g.g) {
22855
+ selectedGroups.add(g, true);
22856
+ if (!g.selected) {
22857
+ g.selected = true;
22705
22858
  g.dirty = true;
22706
22859
  }
22707
- })
22860
+ } else {
22861
+ g.selected = false;
22862
+ g.dirty = true;
22863
+ }
22864
+ })
22708
22865
 
22709
- activeNodes.forEach(function(n) {
22710
- if (mouse_mode === RED.state.SELECTING_NODE) {
22711
- if (selectNodesOptions.filter && !selectNodesOptions.filter(n)) {
22712
- return;
22713
- }
22866
+ activeNodes.forEach(function(n) {
22867
+ if (mouse_mode === RED.state.SELECTING_NODE) {
22868
+ if (selectNodesOptions.filter && !selectNodesOptions.filter(n)) {
22869
+ return;
22714
22870
  }
22715
- if (!n.g && !n.selected) {
22871
+ }
22872
+ if (!n.g && !n.selected) {
22873
+ n.selected = true;
22874
+ n.dirty = true;
22875
+ movingSet.add(n);
22876
+ }
22877
+ });
22878
+
22879
+ activeJunctions.forEach(function(n) {
22880
+ if (!n.selected) {
22881
+ n.selected = true;
22882
+ n.dirty = true;
22883
+ movingSet.add(n);
22884
+ }
22885
+ })
22886
+
22887
+ if (mouse_mode !== RED.state.SELECTING_NODE && activeSubflow) {
22888
+ activeSubflow.in.forEach(function(n) {
22889
+ if (!n.selected) {
22716
22890
  n.selected = true;
22717
22891
  n.dirty = true;
22718
22892
  movingSet.add(n);
22719
22893
  }
22720
22894
  });
22721
-
22722
- activeJunctions.forEach(function(n) {
22895
+ activeSubflow.out.forEach(function(n) {
22723
22896
  if (!n.selected) {
22724
22897
  n.selected = true;
22725
22898
  n.dirty = true;
22726
22899
  movingSet.add(n);
22727
22900
  }
22728
- })
22729
-
22730
- if (mouse_mode !== RED.state.SELECTING_NODE && activeSubflow) {
22731
- activeSubflow.in.forEach(function(n) {
22732
- if (!n.selected) {
22733
- n.selected = true;
22734
- n.dirty = true;
22735
- movingSet.add(n);
22736
- }
22737
- });
22738
- activeSubflow.out.forEach(function(n) {
22739
- if (!n.selected) {
22740
- n.selected = true;
22741
- n.dirty = true;
22742
- movingSet.add(n);
22743
- }
22744
- });
22745
- if (activeSubflow.status) {
22746
- if (!activeSubflow.status.selected) {
22747
- activeSubflow.status.selected = true;
22748
- activeSubflow.status.dirty = true;
22749
- movingSet.add(activeSubflow.status);
22750
- }
22901
+ });
22902
+ if (activeSubflow.status) {
22903
+ if (!activeSubflow.status.selected) {
22904
+ activeSubflow.status.selected = true;
22905
+ activeSubflow.status.dirty = true;
22906
+ movingSet.add(activeSubflow.status);
22751
22907
  }
22752
22908
  }
22753
22909
  }
@@ -22766,15 +22922,7 @@ RED.view = (function() {
22766
22922
  }
22767
22923
  movingSet.clear();
22768
22924
  selectedLinks.clear();
22769
- if (activeGroup) {
22770
- activeGroup.active = false
22771
- activeGroup.dirty = true;
22772
- activeGroup = null;
22773
- }
22774
- activeGroups.forEach(function(g) {
22775
- g.selected = false;
22776
- g.dirty = true;
22777
- })
22925
+ selectedGroups.clear();
22778
22926
  }
22779
22927
 
22780
22928
  var lastSelection = null;
@@ -22893,7 +23041,7 @@ RED.view = (function() {
22893
23041
  }
22894
23042
 
22895
23043
  function editSelection() {
22896
- if (RED.workspaces.isActiveLocked()) { return }
23044
+ if (RED.workspaces.isLocked()) { return }
22897
23045
  if (movingSet.length() > 0) {
22898
23046
  var node = movingSet.get(0).n;
22899
23047
  if (node.type === "subflow") {
@@ -23031,6 +23179,16 @@ RED.view = (function() {
23031
23179
  var result = RED.nodes.removeJunction(node)
23032
23180
  removedJunctions.push(node);
23033
23181
  removedLinks = removedLinks.concat(result.links);
23182
+ if (node.g) {
23183
+ var group = RED.nodes.group(node.g);
23184
+ if (selectedGroups.indexOf(group) === -1) {
23185
+ // Don't use RED.group.removeFromGroup as that emits
23186
+ // a change event on the node - but we're deleting it
23187
+ var index = group.nodes.indexOf(node);
23188
+ group.nodes.splice(index,1);
23189
+ RED.group.markDirty(group);
23190
+ }
23191
+ }
23034
23192
  } else {
23035
23193
  if (node.direction === "out") {
23036
23194
  removedSubflowOutputs.push(node);
@@ -23227,7 +23385,7 @@ RED.view = (function() {
23227
23385
 
23228
23386
 
23229
23387
  function detachSelectedNodes() {
23230
- if (RED.workspaces.isActiveLocked()) { return }
23388
+ if (RED.workspaces.isLocked()) { return }
23231
23389
  var selection = RED.view.selection();
23232
23390
  if (selection.nodes) {
23233
23391
  const {newLinks, removedLinks} = RED.nodes.detachNodes(selection.nodes);
@@ -23330,6 +23488,7 @@ RED.view = (function() {
23330
23488
  mousedown_port_type = null;
23331
23489
  activeSpliceLink = null;
23332
23490
  spliceActive = false;
23491
+ groupAddActive = false;
23333
23492
  if (activeHoverGroup) {
23334
23493
  activeHoverGroup.hovered = false;
23335
23494
  activeHoverGroup = null;
@@ -23532,8 +23691,25 @@ RED.view = (function() {
23532
23691
  (drag_line.portType === PORT_TYPE_INPUT && mouseup_node.type === "subflow" && (mouseup_node.direction === "status" || mouseup_node.direction === "out")) ||
23533
23692
  (drag_line.portType === PORT_TYPE_OUTPUT && mouseup_node.type === "subflow" && mouseup_node.direction === "in")
23534
23693
  )) {
23694
+ let hasJunctionLoop = false
23695
+ if (link.source.type === 'junction' && link.target.type === 'junction') {
23696
+ // This is joining two junctions together. We want to avoid creating a loop
23697
+ // of pure junction nodes as there is no way to break out of it.
23698
+
23699
+ const visited = new Set()
23700
+ let toVisit = [link.target]
23701
+ while (toVisit.length > 0) {
23702
+ const next = toVisit.shift()
23703
+ if (next === link.source) {
23704
+ hasJunctionLoop = true
23705
+ break
23706
+ }
23707
+ visited.add(next)
23708
+ toVisit = toVisit.concat(RED.nodes.getDownstreamNodes(next).filter(n => n.type === 'junction' && !visited.has(n)))
23709
+ }
23710
+ }
23535
23711
  var existingLink = RED.nodes.filterLinks({source:src,target:dst,sourcePort: src_port}).length !== 0;
23536
- if (!existingLink) {
23712
+ if (!hasJunctionLoop && !existingLink) {
23537
23713
  RED.nodes.addLink(link);
23538
23714
  addedLinks.push(link);
23539
23715
  }
@@ -23651,7 +23827,7 @@ RED.view = (function() {
23651
23827
  console.log("Definition error: "+node.type+"."+((portType === PORT_TYPE_INPUT)?"inputLabels":"outputLabels"),err);
23652
23828
  result = null;
23653
23829
  }
23654
- } else if ($.isArray(portLabels)) {
23830
+ } else if (Array.isArray(portLabels)) {
23655
23831
  result = portLabels[portIndex];
23656
23832
  }
23657
23833
  return result;
@@ -23809,7 +23985,7 @@ RED.view = (function() {
23809
23985
  }
23810
23986
  if (dblClickPrimed && mousedown_node == d && clickElapsed > 0 && clickElapsed < dblClickInterval) {
23811
23987
  mouse_mode = RED.state.DEFAULT;
23812
- if (RED.workspaces.isActiveLocked()) {
23988
+ if (RED.workspaces.isLocked()) {
23813
23989
  clickElapsed = 0;
23814
23990
  d3.event.stopPropagation();
23815
23991
  return
@@ -23835,8 +24011,7 @@ RED.view = (function() {
23835
24011
  if (!groupNodeSelectPrimed && !d.selected && d.g && RED.nodes.group(d.g).selected) {
23836
24012
  clearSelection();
23837
24013
 
23838
- selectGroup(RED.nodes.group(d.g), false);
23839
- enterActiveGroup(RED.nodes.group(d.g))
24014
+ selectedGroups.add(RED.nodes.group(d.g), false);
23840
24015
 
23841
24016
  mousedown_node.selected = true;
23842
24017
  movingSet.add(mousedown_node);
@@ -23889,57 +24064,25 @@ RED.view = (function() {
23889
24064
  //RED.touch.radialMenu.show(d3.select(this),pos);
23890
24065
  if (mouse_mode == RED.state.IMPORT_DRAGGING || mouse_mode == RED.state.DETACHED_DRAGGING) {
23891
24066
  var historyEvent = RED.history.peek();
23892
- if (activeSpliceLink) {
23893
- // TODO: DRY - droppable/nodeMouseDown/canvasMouseUp
23894
- var spliceLink = d3.select(activeSpliceLink).data()[0];
23895
- RED.nodes.removeLink(spliceLink);
23896
- var link1 = {
23897
- source:spliceLink.source,
23898
- sourcePort:spliceLink.sourcePort,
23899
- target: movingSet.get(0).n
23900
- };
23901
- var link2 = {
23902
- source:movingSet.get(0).n,
23903
- sourcePort:0,
23904
- target: spliceLink.target
23905
- };
23906
- RED.nodes.addLink(link1);
23907
- RED.nodes.addLink(link2);
24067
+ // Check to see if we're dropping into a group
24068
+ const {
24069
+ addedToGroup,
24070
+ removedFromGroup,
24071
+ groupMoveEvent,
24072
+ rehomedNodes
24073
+ } = addMovingSetToGroup()
23908
24074
 
23909
- historyEvent.links = [link1,link2];
23910
- historyEvent.removedLinks = [spliceLink];
24075
+ if (activeSpliceLink) {
24076
+ var linkToSplice = d3.select(activeSpliceLink).data()[0];
24077
+ spliceLink(linkToSplice, movingSet.get(0).n, historyEvent)
23911
24078
  updateActiveNodes();
23912
24079
  }
23913
-
23914
- var moveEvent = null;
23915
- if (activeHoverGroup) {
23916
- var oldX = activeHoverGroup.x;
23917
- var oldY = activeHoverGroup.y;
23918
- for (var j=0;j<movingSet.length();j++) {
23919
- var n = movingSet.get(j);
23920
- RED.group.addToGroup(activeHoverGroup,n.n);
23921
- }
23922
- if ((activeHoverGroup.x !== oldX) ||
23923
- (activeHoverGroup.y !== oldY)) {
23924
- moveEvent = {
23925
- t: "move",
23926
- nodes: [{n: activeHoverGroup,
23927
- ox: oldX, oy: oldY,
23928
- dx: activeHoverGroup.x -oldX,
23929
- dy: activeHoverGroup.y -oldY}],
23930
- dirty: true
23931
- };
23932
- }
23933
- historyEvent.addedToGroup = activeHoverGroup;
23934
-
23935
- activeHoverGroup.hovered = false;
23936
- enterActiveGroup(activeHoverGroup)
23937
- // TODO: check back whether this should add to moving_set
23938
- activeGroup.selected = true;
23939
- activeHoverGroup = null;
23940
- }
23941
24080
  if (mouse_mode == RED.state.DETACHED_DRAGGING) {
23942
- var ns = [];
24081
+ // Create two lists of nodes:
24082
+ // - nodes that have moved without changing group
24083
+ // - nodes that have moved AND changed group
24084
+ const ns = [];
24085
+ const rehomedNodeList = [];
23943
24086
  for (var j=0;j<movingSet.length();j++) {
23944
24087
  var n = movingSet.get(j);
23945
24088
  if (n.ox !== n.n.x || n.oy !== n.n.y) {
@@ -23948,14 +24091,20 @@ RED.view = (function() {
23948
24091
  n.n.moved = true;
23949
24092
  }
23950
24093
  }
23951
- var event = {t:"multi",events:[historyEvent,{t:"move",nodes:ns}],dirty: historyEvent.dirty};
23952
- if (moveEvent) {
23953
- event.events.push(moveEvent);
24094
+ var event = {
24095
+ t: "multi",
24096
+ events: [
24097
+ historyEvent,
24098
+ { t: "move", nodes: ns }
24099
+ ],
24100
+ dirty: historyEvent.dirty
24101
+ };
24102
+ if (groupMoveEvent) {
24103
+ event.events.push(groupMoveEvent);
23954
24104
  }
23955
24105
  RED.history.replace(event)
23956
- }
23957
- else if(moveEvent) {
23958
- var event = {t:"multi", events:[historyEvent, moveEvent], dirty: true};
24106
+ } else if (groupMoveEvent) {
24107
+ var event = { t:"multi", events: [historyEvent, groupMoveEvent], dirty: true};
23959
24108
  RED.history.replace(event);
23960
24109
  }
23961
24110
 
@@ -24008,126 +24157,11 @@ RED.view = (function() {
24008
24157
  clickElapsed < dblClickInterval &&
24009
24158
  d.type !== 'junction'
24010
24159
  lastClickNode = mousedown_node;
24011
-
24012
- if (!d.selected && d.g /*&& !RED.nodes.group(d.g).selected*/) {
24013
- var nodeGroup = RED.nodes.group(d.g);
24014
-
24015
- if (nodeGroup !== activeGroup && (d3.event.ctrlKey || d3.event.metaKey)) {
24016
- if (activeGroup && nodeGroup.g === activeGroup.id) {
24017
- // Clicked on a node in a non-active group, inside the activeGroup, with ctrl pressed
24018
- // - add/remove the group from the current selection
24019
- groupNodeSelectPrimed = true;
24020
- if (nodeGroup.selected) {
24021
- deselectGroup(nodeGroup);
24022
- } else {
24023
- selectGroup(nodeGroup,true);
24024
- }
24025
- } else {
24026
- // Clicked on a node in a non-active group with ctrl pressed
24027
- // - exit active group
24028
- // - toggle the select state of the group
24029
- exitActiveGroup();
24030
- groupNodeSelectPrimed = true;
24031
- if (nodeGroup.selected) {
24032
- deselectGroup(nodeGroup);
24033
- } else {
24034
- selectGroup(nodeGroup,true);
24035
- }
24036
- }
24037
- } else if (nodeGroup === activeGroup ) {
24038
- if (d3.event.shiftKey) {
24039
- if (!d3.event.ctrlKey && !d3.event.metaKey) {
24040
- var ag = activeGroup;
24041
- clearSelection();
24042
- enterActiveGroup(ag);
24043
- activeGroup.selected = true;
24044
- }
24045
- var cnodes = RED.nodes.getAllFlowNodes(mousedown_node);
24046
- for (var n=0;n<cnodes.length;n++) {
24047
- if (!cnodes[n].selected) {
24048
- cnodes[n].selected = true;
24049
- cnodes[n].dirty = true;
24050
- movingSet.add(cnodes[n]);
24051
- }
24052
- }
24053
- } else {
24054
- // Clicked on a node in the active group
24055
- if (!d3.event.ctrlKey && !d3.event.metaKey) {
24056
- // Ctrl not pressed so clear selection
24057
- var ag = activeGroup;
24058
- clearSelection();
24059
- deselectGroup(nodeGroup);
24060
- selectGroup(nodeGroup,false,false);
24061
- if (ag) {
24062
- enterActiveGroup(ag);
24063
- activeGroup.selected = true;
24064
- }
24065
- }
24066
-
24067
- // Select this node
24068
- mousedown_node.selected = true;
24069
- movingSet.add(mousedown_node);
24070
- }
24071
- } else {
24072
- // Clicked on a node in a group
24073
- // - if this group is not selected, clear current selection
24074
- // and select this group
24075
- // - if this group is not the active group, exit the active group
24076
- // and select the group
24077
- // - if this group is the active group, keep it active and
24078
- // change node selection
24079
-
24080
- // Set groupNodeSelectPrimed to true as this is a (de)select of the
24081
- // group and NOT meant to trigger going into the group - see nodeMouseUp
24082
- groupNodeSelectPrimed = !nodeGroup.selected;
24083
- var ag = activeGroup;
24084
- if (!nodeGroup.selected) {
24085
- clearSelection();
24086
- }
24087
- if (ag) {
24088
- if (ag !== nodeGroup && ag.id !== nodeGroup.g) {
24089
- ag.active = false;
24090
- ag.dirty = true;
24091
- } else {
24092
- activeGroup = ag;
24093
- activeGroup.active = true;
24094
- }
24095
- } else {
24096
- dblClickPrimed = false;
24097
- }
24098
- selectGroup(nodeGroup, !(activeGroup && activeGroup === nodeGroup), !!groupNodeSelectPrimed);
24099
- if (activeGroup && activeGroup === nodeGroup) {
24100
- mousedown_node.selected = true;
24101
- movingSet.add(mousedown_node);
24102
- }
24103
- }
24104
-
24105
-
24106
- if (d3.event.button != 2) {
24107
- var mouse = d3.touches(this)[0]||d3.mouse(this);
24108
- mouse[0] += d.x-d.w/2;
24109
- mouse[1] += d.y-d.h/2;
24110
- prepareDrag(mouse);
24111
- }
24112
- } else if (d.selected && (d3.event.ctrlKey||d3.event.metaKey)) {
24160
+
24161
+ if (d.selected && (d3.event.ctrlKey||d3.event.metaKey)) {
24113
24162
  mousedown_node.selected = false;
24114
24163
  movingSet.remove(mousedown_node);
24115
24164
  } else {
24116
-
24117
- // if (d.g && !RED.nodes.group(d.g).selected) {
24118
- // selectGroup(RED.nodes.group(d.g), false);
24119
- // }
24120
-
24121
-
24122
- // if (!d.selected && d.g) {
24123
- // if (!RED.nodes.group(d.g).selected) {// && !RED.nodes.group(d.g).selected) {
24124
- // clearSelection();
24125
- // selectGroup(RED.nodes.group(d.g));
24126
- // d.selected = true;
24127
- // console.log(d.id,"Setting selected")
24128
- // d.gSelected = true;
24129
- // }
24130
- // } else
24131
24165
  if (d3.event.shiftKey) {
24132
24166
  if (!(d3.event.ctrlKey||d3.event.metaKey)) {
24133
24167
  clearSelection();
@@ -24153,8 +24187,6 @@ RED.view = (function() {
24153
24187
  } else if (!d.selected) {
24154
24188
  if (!d3.event.ctrlKey && !d3.event.metaKey) {
24155
24189
  clearSelection();
24156
- } else {
24157
- exitActiveGroup();
24158
24190
  }
24159
24191
  mousedown_node.selected = true;
24160
24192
  movingSet.add(mousedown_node);
@@ -24353,7 +24385,7 @@ RED.view = (function() {
24353
24385
  if (RED.view.DEBUG) {
24354
24386
  console.warn("groupMouseUp", { mouse_mode, event: d3.event });
24355
24387
  }
24356
- if (RED.workspaces.isActiveLocked()) { return }
24388
+ if (RED.workspaces.isLocked()) { return }
24357
24389
  if (dblClickPrimed && mousedown_group == g && clickElapsed > 0 && clickElapsed < dblClickInterval) {
24358
24390
  mouse_mode = RED.state.DEFAULT;
24359
24391
  RED.editor.editGroup(g);
@@ -24379,7 +24411,6 @@ RED.view = (function() {
24379
24411
  }
24380
24412
 
24381
24413
  if (mouse_mode == RED.state.QUICK_JOINING) {
24382
- d3.event.stopPropagation();
24383
24414
  return;
24384
24415
  } else if (mouse_mode === RED.state.SELECTING_NODE) {
24385
24416
  d3.event.stopPropagation();
@@ -24401,34 +24432,16 @@ RED.view = (function() {
24401
24432
  lastClickNode = g;
24402
24433
 
24403
24434
  if (g.selected && (d3.event.ctrlKey||d3.event.metaKey)) {
24404
- if (g === activeGroup) {
24405
- exitActiveGroup();
24406
- }
24407
- deselectGroup(g);
24435
+ selectedGroups.remove(g);
24408
24436
  d3.event.stopPropagation();
24409
24437
  } else {
24410
24438
  if (!g.selected) {
24411
24439
  if (!d3.event.ctrlKey && !d3.event.metaKey) {
24412
- var ag = activeGroup;
24413
24440
  clearSelection();
24414
- if (ag && g.g === ag.id) {
24415
- enterActiveGroup(ag);
24416
- activeGroup.selected = true;
24417
- }
24418
24441
  }
24419
- if (activeGroup) {
24420
- if (!RED.group.contains(activeGroup,g)) {
24421
- // Clicked on a group that is outside the activeGroup
24422
- exitActiveGroup();
24423
- } else {
24424
- }
24425
- }
24426
- selectGroup(g,true);//!wasSelected);
24427
- } else if (activeGroup && g.g !== activeGroup.id){
24428
- exitActiveGroup();
24442
+ selectedGroups.add(g,true);//!wasSelected);
24429
24443
  }
24430
24444
 
24431
-
24432
24445
  if (d3.event.button != 2) {
24433
24446
  var d = g.nodes[0];
24434
24447
  prepareDrag(mouse);
@@ -24442,65 +24455,17 @@ RED.view = (function() {
24442
24455
  d3.event.stopPropagation();
24443
24456
  }
24444
24457
 
24445
- function selectGroup(g, includeNodes, addToMovingSet) {
24446
- if (!g.selected) {
24447
- g.selected = true;
24448
- g.dirty = true;
24449
- }
24450
- if (addToMovingSet !== false) {
24451
- movingSet.add(g);
24452
- }
24453
- if (includeNodes) {
24454
- var currentSet = new Set(movingSet.nodes());
24455
- var allNodes = RED.group.getNodes(g,true);
24456
- allNodes.forEach(function(n) {
24457
- if (!currentSet.has(n)) {
24458
- movingSet.add(n)
24459
- // n.selected = true;
24460
- }
24461
- n.dirty = true;
24462
- })
24463
- }
24464
- }
24465
- function enterActiveGroup(group) {
24466
- if (activeGroup) {
24467
- exitActiveGroup();
24468
- }
24469
- group.active = true;
24470
- group.dirty = true;
24471
- activeGroup = group;
24472
- movingSet.remove(group);
24473
- }
24474
- function exitActiveGroup() {
24475
- if (activeGroup) {
24476
- activeGroup.active = false;
24477
- activeGroup.dirty = true;
24478
- deselectGroup(activeGroup);
24479
- selectGroup(activeGroup,true);
24480
- activeGroup = null;
24481
- }
24482
- }
24483
- function deselectGroup(g) {
24484
- if (g.selected) {
24485
- g.selected = false;
24486
- g.dirty = true;
24487
- }
24488
- var nodeSet = new Set(g.nodes);
24489
- nodeSet.add(g);
24490
- for (var i = movingSet.length()-1; i >= 0; i -= 1) {
24491
- var msn = movingSet.get(i);
24492
- if (nodeSet.has(msn.n) || msn.n === g) {
24493
- msn.n.selected = false;
24494
- msn.n.dirty = true;
24495
- movingSet.remove(msn.n,i)
24496
- }
24497
- }
24498
- }
24499
- function getGroupAt(x,y) {
24458
+ function getGroupAt(x, y, ignoreSelected) {
24500
24459
  // x,y expected to be in node-co-ordinate space
24501
24460
  var candidateGroups = {};
24502
24461
  for (var i=0;i<activeGroups.length;i++) {
24503
24462
  var g = activeGroups[i];
24463
+ if (ignoreSelected && movingSet.has(g)) {
24464
+ // When ignoreSelected is set, do not match any group in the
24465
+ // current movingSet. This is used when dragging a selection
24466
+ // to find a candidate group for adding the selection to
24467
+ continue
24468
+ }
24504
24469
  if (x >= g.x && x <= g.x + g.w && y >= g.y && y <= g.y + g.h) {
24505
24470
  candidateGroups[g.id] = g;
24506
24471
  }
@@ -24577,7 +24542,7 @@ RED.view = (function() {
24577
24542
  function showTouchMenu(obj,pos) {
24578
24543
  var mdn = mousedown_node;
24579
24544
  var options = [];
24580
- const isActiveLocked = RED.workspaces.isActiveLocked()
24545
+ const isActiveLocked = RED.workspaces.isLocked()
24581
24546
  options.push({name:"delete",disabled:(isActiveLocked || movingSet.length()===0 && selectedLinks.length() === 0),onselect:function() {deleteSelection();}});
24582
24547
  options.push({name:"cut",disabled:(isActiveLocked || movingSet.length()===0),onselect:function() {copySelection(true);deleteSelection();}});
24583
24548
  options.push({name:"copy",disabled:(isActiveLocked || movingSet.length()===0),onselect:function() {copySelection();}});
@@ -24928,6 +24893,7 @@ RED.view = (function() {
24928
24893
  this.__port__.setAttribute("transform","translate(-5,"+((d.h/2)-5)+")");
24929
24894
  this.__outputOutput__.setAttribute("transform","translate(20,"+((d.h/2)-8)+")");
24930
24895
  this.__outputNumber__.setAttribute("transform","translate(20,"+((d.h/2)+7)+")");
24896
+ this.__outputNumber__.textContent = d.i+1;
24931
24897
  }
24932
24898
  d.dirty = false;
24933
24899
  }
@@ -25117,6 +25083,7 @@ RED.view = (function() {
25117
25083
  nodesReordered = true;
25118
25084
  delete d._reordered;
25119
25085
  }
25086
+
25120
25087
  if (d.dirty) {
25121
25088
  var self = this;
25122
25089
  var thisNode = d3.select(this);
@@ -25770,23 +25737,30 @@ RED.view = (function() {
25770
25737
  g.attr("id",d.id);
25771
25738
 
25772
25739
  var groupBorderRadius = 4;
25773
-
25740
+ var groupOutlineBorderRadius = 6
25774
25741
  var selectGroup = groupSelectLayer.append('g').attr("class", "red-ui-flow-group").attr("id","group_select_"+d.id);
25775
- selectGroup.append('rect').classed("red-ui-flow-group-outline-select",true)
25742
+ const groupBackground = selectGroup.append('rect')
25743
+ .classed("red-ui-flow-group-outline-select",true)
25776
25744
  .classed("red-ui-flow-group-outline-select-background",true)
25777
- .attr('rx',groupBorderRadius).attr('ry',groupBorderRadius)
25778
- .attr("x",-4)
25779
- .attr("y",-4);
25780
-
25781
-
25782
- selectGroup.append('rect').classed("red-ui-flow-group-outline-select",true)
25783
- .attr('rx',groupBorderRadius).attr('ry',groupBorderRadius)
25784
- .attr("x",-4)
25785
- .attr("y",-4)
25786
- selectGroup.on("mousedown", function() {groupMouseDown.call(g[0][0],d)});
25787
- selectGroup.on("mouseup", function() {groupMouseUp.call(g[0][0],d)});
25788
- selectGroup.on("touchstart", function() {groupMouseDown.call(g[0][0],d); d3.event.preventDefault();});
25789
- selectGroup.on("touchend", function() {groupMouseUp.call(g[0][0],d); d3.event.preventDefault();});
25745
+ .attr('rx',groupOutlineBorderRadius).attr('ry',groupOutlineBorderRadius)
25746
+ .attr("x",-3)
25747
+ .attr("y",-3);
25748
+ selectGroup.append('rect')
25749
+ .classed("red-ui-flow-group-outline-select",true)
25750
+ .classed("red-ui-flow-group-outline-select-outline",true)
25751
+ .attr('rx',groupOutlineBorderRadius).attr('ry',groupOutlineBorderRadius)
25752
+ .attr("x",-3)
25753
+ .attr("y",-3)
25754
+ selectGroup.append('rect')
25755
+ .classed("red-ui-flow-group-outline-select",true)
25756
+ .classed("red-ui-flow-group-outline-select-line",true)
25757
+ .attr('rx',groupOutlineBorderRadius).attr('ry',groupOutlineBorderRadius)
25758
+ .attr("x",-3)
25759
+ .attr("y",-3)
25760
+ groupBackground.on("mousedown", function() {groupMouseDown.call(g[0][0],d)});
25761
+ groupBackground.on("mouseup", function() {groupMouseUp.call(g[0][0],d)});
25762
+ groupBackground.on("touchstart", function() {groupMouseDown.call(g[0][0],d); d3.event.preventDefault();});
25763
+ groupBackground.on("touchend", function() {groupMouseUp.call(g[0][0],d); d3.event.preventDefault();});
25790
25764
 
25791
25765
  g.append('rect').classed("red-ui-flow-group-outline",true).attr('rx',0.5).attr('ry',0.5);
25792
25766
 
@@ -25804,11 +25778,7 @@ RED.view = (function() {
25804
25778
  });
25805
25779
  if (addedGroups) {
25806
25780
  group.sort(function(a,b) {
25807
- if (a._root === b._root) {
25808
- return a._depth - b._depth;
25809
- } else {
25810
- return a._index - b._index;
25811
- }
25781
+ return a._order - b._order
25812
25782
  })
25813
25783
  }
25814
25784
  group[0].reverse();
@@ -25833,6 +25803,11 @@ RED.view = (function() {
25833
25803
  var margin = 26;
25834
25804
  d.nodes.forEach(function(n) {
25835
25805
  groupOpCount++
25806
+ if (n._detachFromGroup) {
25807
+ // Do not include this node when recalulating
25808
+ // the group dimensions
25809
+ return
25810
+ }
25836
25811
  if (n.type !== "group") {
25837
25812
  minX = Math.min(minX,n.x-n.w/2-margin-((n._def.button && n._def.align!=="right")?20:0));
25838
25813
  minY = Math.min(minY,n.y-n.h/2-margin);
@@ -25845,11 +25820,12 @@ RED.view = (function() {
25845
25820
  maxY = Math.max(maxY,n.y+n.h+margin)
25846
25821
  }
25847
25822
  });
25848
-
25849
- d.x = minX;
25850
- d.y = minY;
25851
- d.w = maxX - minX;
25852
- d.h = maxY - minY;
25823
+ if (minX !== Number.POSITIVE_INFINITY && minY !== Number.POSITIVE_INFINITY) {
25824
+ d.x = minX;
25825
+ d.y = minY;
25826
+ d.w = maxX - minX;
25827
+ d.h = maxY - minY;
25828
+ }
25853
25829
  recalculateLabelOffsets = true;
25854
25830
  // if set explicitly to false, this group has just been
25855
25831
  // imported so needed this initial resize calculation.
@@ -25906,16 +25882,25 @@ RED.view = (function() {
25906
25882
  } else {
25907
25883
  selectGroup.classList.remove("red-ui-flow-group-hovered")
25908
25884
  }
25885
+ if (d.selected) {
25886
+ selectGroup.classList.add("red-ui-flow-group-selected")
25887
+ } else {
25888
+ selectGroup.classList.remove("red-ui-flow-group-selected")
25889
+ }
25909
25890
  var selectGroupRect = selectGroup.children[0];
25910
- selectGroupRect.setAttribute("width",d.w+8)
25911
- selectGroupRect.setAttribute("height",d.h+8)
25912
- selectGroupRect.style.strokeOpacity = (d.active || d.selected || d.highlighted)?0.8:0;
25913
- selectGroupRect.style.strokeDasharray = (d.active)?"10 4":"";
25891
+ // Background
25892
+ selectGroupRect.setAttribute("width",d.w+6)
25893
+ selectGroupRect.setAttribute("height",d.h+6)
25894
+ // Outline
25914
25895
  selectGroupRect = selectGroup.children[1];
25915
- selectGroupRect.setAttribute("width",d.w+8)
25916
- selectGroupRect.setAttribute("height",d.h+8)
25917
- selectGroupRect.style.strokeOpacity = (d.active || d.selected || d.highlighted)?0.8:0;
25918
- selectGroupRect.style.strokeDasharray = (d.active)?"10 4":"";
25896
+ selectGroupRect.setAttribute("width",d.w+6)
25897
+ selectGroupRect.setAttribute("height",d.h+6)
25898
+ selectGroupRect.style.strokeOpacity = (d.selected || d.highlighted)?0.8:0;
25899
+ // Line
25900
+ selectGroupRect = selectGroup.children[2];
25901
+ selectGroupRect.setAttribute("width",d.w+6)
25902
+ selectGroupRect.setAttribute("height",d.h+6)
25903
+ selectGroupRect.style.strokeOpacity = (d.selected || d.highlighted)?0.8:0;
25919
25904
 
25920
25905
  if (d.highlighted) {
25921
25906
  selectGroup.classList.add("red-ui-flow-node-highlighted");
@@ -26043,7 +26028,7 @@ RED.view = (function() {
26043
26028
  if (mouse_mode === RED.state.SELECTING_NODE) {
26044
26029
  return;
26045
26030
  }
26046
-
26031
+ const wasDirty = RED.nodes.dirty()
26047
26032
  var nodesToImport;
26048
26033
  if (typeof newNodesObj === "string") {
26049
26034
  if (newNodesObj === "") {
@@ -26060,7 +26045,7 @@ RED.view = (function() {
26060
26045
  nodesToImport = newNodesObj;
26061
26046
  }
26062
26047
 
26063
- if (!$.isArray(nodesToImport)) {
26048
+ if (!Array.isArray(nodesToImport)) {
26064
26049
  nodesToImport = [nodesToImport];
26065
26050
  }
26066
26051
  if (options.generateDefaultNames) {
@@ -26093,7 +26078,12 @@ RED.view = (function() {
26093
26078
  return (n.type === "global-config");
26094
26079
  });
26095
26080
  }
26096
- var result = RED.nodes.import(filteredNodesToImport,{generateIds:options.generateIds, addFlow: addNewFlow, importMap: options.importMap});
26081
+ var result = RED.nodes.import(filteredNodesToImport,{
26082
+ generateIds: options.generateIds,
26083
+ addFlow: addNewFlow,
26084
+ importMap: options.importMap,
26085
+ markChanged: true
26086
+ });
26097
26087
  if (result) {
26098
26088
  var new_nodes = result.nodes;
26099
26089
  var new_links = result.links;
@@ -26109,7 +26099,7 @@ RED.view = (function() {
26109
26099
  var new_ms = new_nodes.filter(function(n) { return n.hasOwnProperty("x") && n.hasOwnProperty("y") && n.z == RED.workspaces.active() });
26110
26100
  new_ms = new_ms.concat(new_groups.filter(function(g) { return g.z === RED.workspaces.active()}))
26111
26101
  new_ms = new_ms.concat(new_junctions.filter(function(j) { return j.z === RED.workspaces.active()}))
26112
- var new_node_ids = new_nodes.map(function(n){ n.changed = true; return n.id; });
26102
+ var new_node_ids = new_nodes.map(function(n){ return n.id; });
26113
26103
 
26114
26104
  clearSelection();
26115
26105
  movingSet.clear();
@@ -26175,28 +26165,19 @@ RED.view = (function() {
26175
26165
  }
26176
26166
  if (!touchImport) {
26177
26167
  mouse_mode = RED.state.IMPORT_DRAGGING;
26178
- spliceActive = false;
26179
- if (movingSet.length() === 1) {
26180
- node = movingSet.get(0);
26181
- spliceActive = node.n.hasOwnProperty("_def") &&
26182
- ((node.n.hasOwnProperty("inputs") && node.n.inputs > 0) || (!node.n.hasOwnProperty("inputs") && node.n._def.inputs > 0)) &&
26183
- ((node.n.hasOwnProperty("outputs") && node.n.outputs > 0) || (!node.n.hasOwnProperty("outputs") && node.n._def.outputs > 0))
26184
-
26185
-
26186
- }
26168
+ startSelectionMove()
26187
26169
  }
26188
-
26189
26170
  }
26190
26171
 
26191
26172
  var historyEvent = {
26192
- t:"add",
26193
- nodes:new_node_ids,
26194
- links:new_links,
26195
- groups:new_groups,
26173
+ t: "add",
26174
+ nodes: new_node_ids,
26175
+ links: new_links,
26176
+ groups: new_groups,
26196
26177
  junctions: new_junctions,
26197
- workspaces:new_workspaces,
26198
- subflows:new_subflows,
26199
- dirty:RED.nodes.dirty()
26178
+ workspaces: new_workspaces,
26179
+ subflows: new_subflows,
26180
+ dirty: wasDirty
26200
26181
  };
26201
26182
  if (movingSet.length() === 0) {
26202
26183
  RED.nodes.dirty(true);
@@ -26205,7 +26186,7 @@ RED.view = (function() {
26205
26186
  var subflowRefresh = RED.subflow.refresh(true);
26206
26187
  if (subflowRefresh) {
26207
26188
  historyEvent.subflow = {
26208
- id:activeSubflow.id,
26189
+ id: activeSubflow.id,
26209
26190
  changed: activeSubflowChanged,
26210
26191
  instances: subflowRefresh.instances
26211
26192
  }
@@ -26323,6 +26304,59 @@ RED.view = (function() {
26323
26304
  }
26324
26305
  }
26325
26306
 
26307
+ function startSelectionMove() {
26308
+ spliceActive = false;
26309
+ if (movingSet.length() === 1) {
26310
+ const node = movingSet.get(0);
26311
+ spliceActive = node.n.hasOwnProperty("_def") &&
26312
+ ((node.n.hasOwnProperty("inputs") && node.n.inputs > 0) || (!node.n.hasOwnProperty("inputs") && node.n._def.inputs > 0)) &&
26313
+ ((node.n.hasOwnProperty("outputs") && node.n.outputs > 0) || (!node.n.hasOwnProperty("outputs") && node.n._def.outputs > 0)) &&
26314
+ RED.nodes.filterLinks({ source: node.n }).length === 0 &&
26315
+ RED.nodes.filterLinks({ target: node.n }).length === 0;
26316
+ }
26317
+ groupAddActive = false
26318
+ groupAddParentGroup = null
26319
+ if (movingSet.length() > 0 && activeGroups) {
26320
+ // movingSet includes the selection AND any nodes inside selected groups
26321
+ // So we cannot simply check the `g` of all nodes match.
26322
+ // Instead, we have to:
26323
+ // - note all groups in movingSet
26324
+ // - note all .g values referenced in movingSet
26325
+ // - then check for g values for groups not in movingSet
26326
+ let isValidSelection = true
26327
+ let hasNullGroup = false
26328
+ const selectedGroups = []
26329
+ const referencedGroups = new Set()
26330
+ movingSet.forEach(n => {
26331
+ if (n.n.type === 'subflow') {
26332
+ isValidSelection = false
26333
+ }
26334
+ if (n.n.type === 'group') {
26335
+ selectedGroups.push(n.n.id)
26336
+ }
26337
+ if (n.n.g) {
26338
+ referencedGroups.add(n.n.g)
26339
+ } else {
26340
+ hasNullGroup = true
26341
+ }
26342
+ })
26343
+ if (isValidSelection) {
26344
+ selectedGroups.forEach(g => referencedGroups.delete(g))
26345
+ // console.log('selectedGroups', selectedGroups)
26346
+ // console.log('referencedGroups',referencedGroups)
26347
+ // console.log('hasNullGroup', hasNullGroup)
26348
+ if (referencedGroups.size === 0) {
26349
+ groupAddActive = true
26350
+ } else if (!hasNullGroup && referencedGroups.size === 1) {
26351
+ groupAddParentGroup = referencedGroups.values().next().value
26352
+ groupAddActive = true
26353
+ }
26354
+ }
26355
+ // console.log('groupAddActive', groupAddActive)
26356
+ // console.log('groupAddParentGroup', groupAddParentGroup)
26357
+ }
26358
+ }
26359
+
26326
26360
  function toggleShowGrid(state) {
26327
26361
  if (state) {
26328
26362
  gridLayer.style("visibility","visible");
@@ -26406,20 +26440,13 @@ RED.view = (function() {
26406
26440
  }
26407
26441
  });
26408
26442
  }
26409
- var selectedGroups = activeGroups.filter(function(g) { return g.selected && !g.active });
26410
- if (selectedGroups.length > 0) {
26411
- if (selectedGroups.length === 1 && selectedGroups[0].active) {
26412
- // Let nodes be nodes
26413
- } else {
26414
- selectedGroups.forEach(function(g) {
26415
- var groupNodes = RED.group.getNodes(g,true);
26416
- groupNodes.forEach(function(n) {
26417
- allNodes.delete(n);
26418
- });
26419
- allNodes.add(g);
26420
- });
26421
- }
26422
- }
26443
+ selectedGroups.forEach(function(g) {
26444
+ var groupNodes = RED.group.getNodes(g,true);
26445
+ groupNodes.forEach(function(n) {
26446
+ allNodes.delete(n);
26447
+ });
26448
+ allNodes.add(g);
26449
+ });
26423
26450
  if (allNodes.size > 0) {
26424
26451
  selection.nodes = Array.from(allNodes);
26425
26452
  }
@@ -26606,6 +26633,13 @@ RED.view = (function() {
26606
26633
  selectedNode.dirty = true;
26607
26634
  movingSet.clear();
26608
26635
  movingSet.add(selectedNode);
26636
+ } else {
26637
+ selectedNode = RED.nodes.group(selection);
26638
+ if (selectedNode) {
26639
+ movingSet.clear();
26640
+ selectedGroups.clear()
26641
+ selectedGroups.add(selectedNode)
26642
+ }
26609
26643
  }
26610
26644
  } else if (selection) {
26611
26645
  if (selection.nodes) {
@@ -26621,7 +26655,7 @@ RED.view = (function() {
26621
26655
  n.dirty = true;
26622
26656
  movingSet.add(n);
26623
26657
  } else {
26624
- selectGroup(n,true);
26658
+ selectedGroups.add(n,true);
26625
26659
  }
26626
26660
  })
26627
26661
  }
@@ -26664,7 +26698,7 @@ RED.view = (function() {
26664
26698
  return result;
26665
26699
  },
26666
26700
  getGroupAtPoint: getGroupAt,
26667
- getActiveGroup: function() { return activeGroup },
26701
+ getActiveGroup: function() { return null },
26668
26702
  reveal: function(id,triggerHighlight) {
26669
26703
  if (RED.nodes.workspace(id) || RED.nodes.subflow(id)) {
26670
26704
  RED.workspaces.show(id, null, null, true);
@@ -26789,8 +26823,12 @@ RED.view = (function() {
26789
26823
  })
26790
26824
  },
26791
26825
  scroll: function(x,y) {
26792
- chart.scrollLeft(chart.scrollLeft()+x);
26793
- chart.scrollTop(chart.scrollTop()+y)
26826
+ if (x !== undefined && y !== undefined) {
26827
+ chart.scrollLeft(chart.scrollLeft()+x);
26828
+ chart.scrollTop(chart.scrollTop()+y)
26829
+ } else {
26830
+ return [chart.scrollLeft(), chart.scrollTop()]
26831
+ }
26794
26832
  },
26795
26833
  clickNodeButton: function(n) {
26796
26834
  if (n._def.button) {
@@ -26983,9 +27021,9 @@ RED.view = (function() {
26983
27021
 
26984
27022
  RED.view.navigator = (function() {
26985
27023
 
26986
- var nav_scale = 25;
26987
- var nav_width = 5000/nav_scale;
26988
- var nav_height = 5000/nav_scale;
27024
+ var nav_scale = 50;
27025
+ var nav_width = 8000/nav_scale;
27026
+ var nav_height = 8000/nav_scale;
26989
27027
 
26990
27028
  var navContainer;
26991
27029
  var navBox;
@@ -27163,7 +27201,7 @@ RED.view.tools = (function() {
27163
27201
  }
27164
27202
 
27165
27203
  function alignToGrid() {
27166
- if (RED.workspaces.isActiveLocked()) {
27204
+ if (RED.workspaces.isLocked()) {
27167
27205
  return
27168
27206
  }
27169
27207
  var selection = RED.view.selection();
@@ -27214,7 +27252,7 @@ RED.view.tools = (function() {
27214
27252
  }
27215
27253
 
27216
27254
  function moveSelection(dx,dy) {
27217
- if (RED.workspaces.isActiveLocked()) {
27255
+ if (RED.workspaces.isLocked()) {
27218
27256
  return
27219
27257
  }
27220
27258
  if (moving_set === null) {
@@ -27283,7 +27321,7 @@ RED.view.tools = (function() {
27283
27321
  }
27284
27322
 
27285
27323
  function setSelectedNodeLabelState(labelShown) {
27286
- if (RED.workspaces.isActiveLocked()) {
27324
+ if (RED.workspaces.isLocked()) {
27287
27325
  return
27288
27326
  }
27289
27327
  var selection = RED.view.selection();
@@ -27572,9 +27610,9 @@ RED.view.tools = (function() {
27572
27610
  }
27573
27611
 
27574
27612
  function alignSelectionToEdge(direction) {
27575
- // if (RED.workspaces.isActiveLocked()) {
27576
- // return
27577
- // }
27613
+ if (RED.workspaces.isLocked()) {
27614
+ return;
27615
+ }
27578
27616
  var selection = RED.view.selection();
27579
27617
 
27580
27618
  if (selection.nodes && selection.nodes.length > 1) {
@@ -27676,7 +27714,7 @@ RED.view.tools = (function() {
27676
27714
  }
27677
27715
 
27678
27716
  function distributeSelection(direction) {
27679
- if (RED.workspaces.isActiveLocked()) {
27717
+ if (RED.workspaces.isLocked()) {
27680
27718
  return
27681
27719
  }
27682
27720
  var selection = RED.view.selection();
@@ -27837,7 +27875,7 @@ RED.view.tools = (function() {
27837
27875
  }
27838
27876
 
27839
27877
  function reorderSelection(dir) {
27840
- if (RED.workspaces.isActiveLocked()) {
27878
+ if (RED.workspaces.isLocked()) {
27841
27879
  return
27842
27880
  }
27843
27881
  var selection = RED.view.selection();
@@ -27845,9 +27883,8 @@ RED.view.tools = (function() {
27845
27883
  var nodesToMove = [];
27846
27884
  selection.nodes.forEach(function(n) {
27847
27885
  if (n.type === "group") {
27848
- nodesToMove = nodesToMove.concat(RED.group.getNodes(n, true).filter(function(n) {
27849
- return n.type !== "group";
27850
- }))
27886
+ nodesToMove.push(n)
27887
+ nodesToMove = nodesToMove.concat(RED.group.getNodes(n, true))
27851
27888
  } else if (n.type !== "subflow"){
27852
27889
  nodesToMove.push(n);
27853
27890
  }
@@ -27876,7 +27913,7 @@ RED.view.tools = (function() {
27876
27913
  }
27877
27914
 
27878
27915
  function wireSeriesOfNodes() {
27879
- if (RED.workspaces.isActiveLocked()) {
27916
+ if (RED.workspaces.isLocked()) {
27880
27917
  return
27881
27918
  }
27882
27919
  var selection = RED.view.selection();
@@ -27919,7 +27956,7 @@ RED.view.tools = (function() {
27919
27956
  }
27920
27957
 
27921
27958
  function wireNodeToMultiple() {
27922
- if (RED.workspaces.isActiveLocked()) {
27959
+ if (RED.workspaces.isLocked()) {
27923
27960
  return
27924
27961
  }
27925
27962
  var selection = RED.view.selection();
@@ -27964,12 +28001,70 @@ RED.view.tools = (function() {
27964
28001
  }
27965
28002
  }
27966
28003
 
28004
+ function wireMultipleToNode() {
28005
+ if (RED.workspaces.isLocked()) {
28006
+ return
28007
+ }
28008
+ var selection = RED.view.selection();
28009
+ if (selection.nodes) {
28010
+ if (selection.nodes.length > 1) {
28011
+ var targetNode = selection.nodes[selection.nodes.length - 1];
28012
+ if (targetNode.inputs === 0) {
28013
+ return;
28014
+ }
28015
+ var i = 0;
28016
+ var newLinks = [];
28017
+ for (i = 0; i < selection.nodes.length - 1; i++) {
28018
+ var sourceNode = selection.nodes[i];
28019
+ if (sourceNode.outputs > 0) {
28020
+
28021
+ // Wire the first output to the target that has no link to the target yet.
28022
+ // This allows for connecting all combinations of inputs/outputs.
28023
+ // The user may then delete links quickly that aren't needed.
28024
+ var sourceConnectedOutports = RED.nodes.filterLinks({
28025
+ source: sourceNode,
28026
+ target: targetNode
28027
+ });
28028
+
28029
+ // Get outport indices that have no link yet
28030
+ var sourceOutportIndices = Array.from({ length: sourceNode.outputs }, (_, i) => i);
28031
+ var sourceConnectedOutportIndices = sourceConnectedOutports.map( x => x.sourcePort );
28032
+ var sourceFreeOutportIndices = sourceOutportIndices.filter(x => !sourceConnectedOutportIndices.includes(x));
28033
+
28034
+ // Does an unconnected source port exist?
28035
+ if (sourceFreeOutportIndices.length == 0) {
28036
+ continue;
28037
+ }
28038
+
28039
+ // Connect the first free outport to the target
28040
+ var newLink = {
28041
+ source: sourceNode,
28042
+ target: targetNode,
28043
+ sourcePort: sourceFreeOutportIndices[0]
28044
+ }
28045
+ RED.nodes.addLink(newLink);
28046
+ newLinks.push(newLink);
28047
+ }
28048
+ }
28049
+ if (newLinks.length > 0) {
28050
+ RED.history.push({
28051
+ t: 'add',
28052
+ links: newLinks,
28053
+ dirty: RED.nodes.dirty()
28054
+ })
28055
+ RED.nodes.dirty(true);
28056
+ RED.view.redraw(true);
28057
+ }
28058
+ }
28059
+ }
28060
+ }
28061
+
27967
28062
  /**
27968
28063
  * Splits selected wires and re-joins them with link-out+link-in
27969
28064
  * @param {Object || Object[]} wires The wire(s) to split and replace with link-out, link-in nodes.
27970
28065
  */
27971
28066
  function splitWiresWithLinkNodes(wires) {
27972
- if (RED.workspaces.isActiveLocked()) {
28067
+ if (RED.workspaces.isLocked()) {
27973
28068
  return
27974
28069
  }
27975
28070
  let wiresToSplit = wires || (RED.view.selection().links && RED.view.selection().links.filter(e => !e.link));
@@ -28140,7 +28235,7 @@ RED.view.tools = (function() {
28140
28235
  * @param {{ renameBlank: boolean, renameClash: boolean, generateHistory: boolean }} options Possible options are `renameBlank`, `renameClash` and `generateHistory`
28141
28236
  */
28142
28237
  function generateNodeNames(node, options) {
28143
- if (RED.workspaces.isActiveLocked()) {
28238
+ if (RED.workspaces.isLocked()) {
28144
28239
  return
28145
28240
  }
28146
28241
  options = Object.assign({
@@ -28213,7 +28308,7 @@ RED.view.tools = (function() {
28213
28308
  }
28214
28309
 
28215
28310
  function addJunctionsToWires(wires) {
28216
- if (RED.workspaces.isActiveLocked()) {
28311
+ if (RED.workspaces.isLocked()) {
28217
28312
  return
28218
28313
  }
28219
28314
  let wiresToSplit = wires || (RED.view.selection().links && RED.view.selection().links.filter(e => !e.link));
@@ -28257,7 +28352,8 @@ RED.view.tools = (function() {
28257
28352
  w: 0, h: 0,
28258
28353
  outputs: 1,
28259
28354
  inputs: 1,
28260
- dirty: true
28355
+ dirty: true,
28356
+ moved: true
28261
28357
  }
28262
28358
  links = links.filter(function(l) { return !removedLinks.has(l) })
28263
28359
  if (links.length === 0) {
@@ -28431,6 +28527,7 @@ RED.view.tools = (function() {
28431
28527
 
28432
28528
  RED.actions.add("core:wire-series-of-nodes", function() { wireSeriesOfNodes() })
28433
28529
  RED.actions.add("core:wire-node-to-multiple", function() { wireNodeToMultiple() })
28530
+ RED.actions.add("core:wire-multiple-to-node", function() { wireMultipleToNode() })
28434
28531
 
28435
28532
  RED.actions.add("core:split-wire-with-link-nodes", function () { splitWiresWithLinkNodes() });
28436
28533
  RED.actions.add("core:split-wires-with-junctions", function () { addJunctionsToWires() });
@@ -28917,23 +29014,15 @@ RED.palette = (function() {
28917
29014
  }
28918
29015
  metaData += type;
28919
29016
 
29017
+ const safeType = type.replace(/'/g,"\\'");
29018
+ const searchType = type.indexOf(' ') > -1 ? '&quot;' + type + '&quot;' : type
29019
+
28920
29020
  if (/^subflow:/.test(type)) {
28921
29021
  $('<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)
28922
29022
  }
28923
29023
 
28924
- const safeType = type.replace(/'/g,"\\'");
28925
- const wrapStr = function (str) {
28926
- if(str.indexOf(' ') >= 0) {
28927
- return '"' + str + '"'
28928
- }
28929
- return str
28930
- }
29024
+ $('<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)
28931
29025
 
28932
- $('<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>')
28933
- .appendTo(popOverContent)
28934
- .on('click', function() {
28935
- RED.search.show('type:' + wrapStr(safeType))
28936
- })
28937
29026
  $('<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)
28938
29027
 
28939
29028
  $('<p>',{style:"font-size: 0.8em"}).text(metaData).appendTo(popOverContent);
@@ -30198,6 +30287,10 @@ RED.sidebar.info = (function() {
30198
30287
  RED.workspaces.show(n.id, null, true);
30199
30288
  }
30200
30289
  });
30290
+ RED.popover.tooltip(toggleVisibleButton, function () {
30291
+ var isHidden = !div.hasClass("red-ui-info-outline-item-hidden");
30292
+ return RED._("sidebar.info." + (isHidden ? "hideFlow" : "showFlow"));
30293
+ });
30201
30294
  }
30202
30295
  if (n.type !== 'subflow') {
30203
30296
  var toggleButton = $('<button type="button" class="red-ui-info-outline-item-control-disable red-ui-button red-ui-button-small"><i class="fa fa-circle-thin"></i><i class="fa fa-ban"></i></button>').appendTo(controls).on("click",function(evt) {
@@ -30696,6 +30789,9 @@ RED.sidebar.info = (function() {
30696
30789
  objects[n.id].children = missingParents[n.id];
30697
30790
  delete missingParents[n.id]
30698
30791
  }
30792
+ if (objects[n.id].children.length === 0) {
30793
+ objects[n.id].children.push(getEmptyItem(n.id));
30794
+ }
30699
30795
  }
30700
30796
  var parent = n.g||n.z||"__global__";
30701
30797
 
@@ -31407,17 +31503,19 @@ RED.sidebar.config = (function() {
31407
31503
  } else {
31408
31504
  var currentType = "";
31409
31505
  nodes.forEach(function(node) {
31410
- var label = RED.utils.getNodeLabel(node,node.id);
31506
+ var labelText = RED.utils.getNodeLabel(node,node.id);
31411
31507
  if (node.type != currentType) {
31412
31508
  $('<li class="red-ui-palette-node-config-type">'+node.type+'</li>').appendTo(list);
31413
31509
  currentType = node.type;
31414
31510
  }
31415
-
31511
+ if (node.changed) {
31512
+ labelText += "!!"
31513
+ }
31416
31514
  var entry = $('<li class="red-ui-palette-node_id_'+node.id.replace(/\./g,"-")+'"></li>').appendTo(list);
31417
31515
  var nodeDiv = $('<div class="red-ui-palette-node-config red-ui-palette-node"></div>').appendTo(entry);
31418
31516
  entry.data('node',node.id);
31419
31517
  nodeDiv.data('node',node.id);
31420
- var label = $('<div class="red-ui-palette-label"></div>').text(label).appendTo(nodeDiv);
31518
+ var label = $('<div class="red-ui-palette-label"></div>').text(labelText).appendTo(nodeDiv);
31421
31519
  if (node.d) {
31422
31520
  nodeDiv.addClass("red-ui-palette-node-config-disabled");
31423
31521
  $('<i class="fa fa-ban"></i>').prependTo(label);
@@ -33301,11 +33399,13 @@ RED.editor = (function() {
33301
33399
  var hasChanged;
33302
33400
  if (node.type.indexOf("subflow:")===0) {
33303
33401
  subflow = RED.nodes.subflow(node.type.substring(8));
33304
- isValid = subflow.valid;
33305
- hasChanged = subflow.changed;
33306
- if (isValid === undefined) {
33307
- isValid = validateNode(subflow);
33402
+ if (subflow){
33403
+ isValid = subflow.valid;
33308
33404
  hasChanged = subflow.changed;
33405
+ if (isValid === undefined) {
33406
+ isValid = validateNode(subflow);
33407
+ hasChanged = subflow.changed;
33408
+ }
33309
33409
  }
33310
33410
  validationErrors = validateNodeProperties(node, node._def.defaults, node);
33311
33411
  node.valid = isValid && validationErrors.length === 0;
@@ -34116,6 +34216,7 @@ RED.editor = (function() {
34116
34216
  function showEditDialog(node, defaultTab) {
34117
34217
  if (buildingEditDialog) { return }
34118
34218
  buildingEditDialog = true;
34219
+ if (node.z && RED.workspaces.isLocked(node.z)) { return }
34119
34220
  var editing_node = node;
34120
34221
  var removeInfoEditorOnClose = false;
34121
34222
  var skipInfoRefreshOnClose = false;
@@ -34301,6 +34402,13 @@ RED.editor = (function() {
34301
34402
 
34302
34403
  var trayFooterLeft = $('<div class="red-ui-tray-footer-left"></div>').appendTo(trayFooter)
34303
34404
 
34405
+ var helpButton = $('<button type="button" class="red-ui-button"><i class="fa fa-book"></button>').on("click", function(evt) {
34406
+ evt.preventDefault();
34407
+ evt.stopPropagation();
34408
+ RED.sidebar.help.show(editing_node.type);
34409
+ }).appendTo(trayFooterLeft);
34410
+ RED.popover.tooltip(helpButton, RED._("sidebar.help.showHelp"));
34411
+
34304
34412
  $('<input id="node-input-node-disabled" type="checkbox">').prop("checked",!!node.d).appendTo(trayFooterLeft).toggleButton({
34305
34413
  enabledIcon: "fa-circle-thin",
34306
34414
  disabledIcon: "fa-ban",
@@ -34404,6 +34512,8 @@ RED.editor = (function() {
34404
34512
  var editing_config_node = RED.nodes.node(id);
34405
34513
  var activeEditPanes = [];
34406
34514
 
34515
+ if (editing_config_node && editing_config_node.z && RED.workspaces.isLocked(editing_config_node.z)) { return }
34516
+
34407
34517
  var configNodeScope = ""; // default to global
34408
34518
  var activeSubflow = RED.nodes.subflow(RED.workspaces.active());
34409
34519
  if (activeSubflow) {
@@ -34446,6 +34556,13 @@ RED.editor = (function() {
34446
34556
 
34447
34557
  var trayFooterLeft = $('<div class="red-ui-tray-footer-left"></div>').appendTo(trayFooter)
34448
34558
 
34559
+ var helpButton = $('<button type="button" class="red-ui-button"><i class="fa fa-book"></button>').on("click", function(evt) {
34560
+ evt.preventDefault();
34561
+ evt.stopPropagation();
34562
+ RED.sidebar.help.show(editing_config_node.type);
34563
+ }).appendTo(trayFooterLeft);
34564
+ RED.popover.tooltip(helpButton, RED._("sidebar.help.showHelp"));
34565
+
34449
34566
  $('<input id="node-config-input-node-disabled" type="checkbox">').prop("checked",!!editing_config_node.d).appendTo(trayFooterLeft).toggleButton({
34450
34567
  enabledIcon: "fa-circle-thin",
34451
34568
  disabledIcon: "fa-ban",
@@ -34950,6 +35067,7 @@ RED.editor = (function() {
34950
35067
  function showEditGroupDialog(group, defaultTab) {
34951
35068
  if (buildingEditDialog) { return }
34952
35069
  buildingEditDialog = true;
35070
+ if (group.z && RED.workspaces.isLocked(group.z)) { return }
34953
35071
  var editing_node = group;
34954
35072
  editStack.push(group);
34955
35073
  RED.view.state(RED.state.EDITING);
@@ -35178,9 +35296,9 @@ RED.editor = (function() {
35178
35296
  workspace.locked = false;
35179
35297
  }
35180
35298
  $('<input id="node-input-locked" type="checkbox">').prop("checked",workspace.locked).appendTo(trayFooterRight).toggleButton({
35181
- enabledLabel: 'Unlocked',
35299
+ enabledLabel: RED._("common.label.unlocked"),
35182
35300
  enabledIcon: "fa-unlock-alt",
35183
- disabledLabel: 'Locked',
35301
+ disabledLabel: RED._("common.label.locked"),
35184
35302
  disabledIcon: "fa-lock",
35185
35303
  invertState: true
35186
35304
  })
@@ -37919,32 +38037,37 @@ RED.editor = (function() {
37919
38037
  }
37920
38038
 
37921
38039
  try {
37922
- var result = expr.evaluate(legacyMode?{msg:parsedData}:parsedData);
37923
- if (usesContext) {
37924
- testResultEditor.setValue(RED._("expressionEditor.errors.context-unsupported"),-1);
37925
- return;
37926
- }
37927
- if (usesEnv) {
37928
- testResultEditor.setValue(RED._("expressionEditor.errors.env-unsupported"),-1);
37929
- return;
37930
- }
37931
- if (usesMoment) {
37932
- testResultEditor.setValue(RED._("expressionEditor.errors.moment-unsupported"),-1);
37933
- return;
37934
- }
37935
- if (usesClone) {
37936
- testResultEditor.setValue(RED._("expressionEditor.errors.clone-unsupported"),-1);
37937
- return;
37938
- }
37939
-
37940
- var formattedResult;
37941
- if (result !== undefined) {
37942
- formattedResult = JSON.stringify(result,null,4);
37943
- } else {
37944
- formattedResult = RED._("expressionEditor.noMatch");
37945
- }
37946
- testResultEditor.setValue(formattedResult,-1);
37947
- } catch(err) {
38040
+ expr.evaluate(legacyMode?{msg:parsedData}:parsedData, null, (err, result) => {
38041
+ if (err) {
38042
+ testResultEditor.setValue(RED._("expressionEditor.errors.eval",{message:err.message}),-1);
38043
+ } else {
38044
+ if (usesContext) {
38045
+ testResultEditor.setValue(RED._("expressionEditor.errors.context-unsupported"),-1);
38046
+ return;
38047
+ }
38048
+ if (usesEnv) {
38049
+ testResultEditor.setValue(RED._("expressionEditor.errors.env-unsupported"),-1);
38050
+ return;
38051
+ }
38052
+ if (usesMoment) {
38053
+ testResultEditor.setValue(RED._("expressionEditor.errors.moment-unsupported"),-1);
38054
+ return;
38055
+ }
38056
+ if (usesClone) {
38057
+ testResultEditor.setValue(RED._("expressionEditor.errors.clone-unsupported"),-1);
38058
+ return;
38059
+ }
38060
+
38061
+ var formattedResult;
38062
+ if (result !== undefined) {
38063
+ formattedResult = JSON.stringify(result,null,4);
38064
+ } else {
38065
+ formattedResult = RED._("expressionEditor.noMatch");
38066
+ }
38067
+ testResultEditor.setValue(formattedResult,-1);
38068
+ }
38069
+ });
38070
+ } catch(err) {
37948
38071
  testResultEditor.setValue(RED._("expressionEditor.errors.eval",{message:err.message}),-1);
37949
38072
  }
37950
38073
  }
@@ -39511,18 +39634,21 @@ RED.editor.codeEditor.monaco = (function() {
39511
39634
  //TODO: get from externalModules.js For now this is enough for feature parity with ACE (and then some).
39512
39635
  const knownModules = {
39513
39636
  "assert": {package: "node", module: "assert", path: "node/assert.d.ts" },
39637
+ "assert/strict": {package: "node", module: "assert/strict", path: "node/assert/strict.d.ts" },
39514
39638
  "async_hooks": {package: "node", module: "async_hooks", path: "node/async_hooks.d.ts" },
39515
39639
  "buffer": {package: "node", module: "buffer", path: "node/buffer.d.ts" },
39516
39640
  "child_process": {package: "node", module: "child_process", path: "node/child_process.d.ts" },
39517
39641
  "cluster": {package: "node", module: "cluster", path: "node/cluster.d.ts" },
39518
39642
  "console": {package: "node", module: "console", path: "node/console.d.ts" },
39519
- "constants": {package: "node", module: "constants", path: "node/constants.d.ts" },
39520
39643
  "crypto": {package: "node", module: "crypto", path: "node/crypto.d.ts" },
39521
39644
  "dgram": {package: "node", module: "dgram", path: "node/dgram.d.ts" },
39645
+ "diagnostics_channel.d": {package: "node", module: "diagnostics_channel", path: "node/diagnostics_channel.d.ts" },
39522
39646
  "dns": {package: "node", module: "dns", path: "node/dns.d.ts" },
39647
+ "dns/promises": {package: "node", module: "dns/promises", path: "node/dns/promises.d.ts" },
39523
39648
  "domain": {package: "node", module: "domain", path: "node/domain.d.ts" },
39524
39649
  "events": {package: "node", module: "events", path: "node/events.d.ts" },
39525
39650
  "fs": {package: "node", module: "fs", path: "node/fs.d.ts" },
39651
+ "fs/promises": {package: "node", module: "fs/promises", path: "node/fs/promises.d.ts" },
39526
39652
  "globals": {package: "node", module: "globals", path: "node/globals.d.ts" },
39527
39653
  "http": {package: "node", module: "http", path: "node/http.d.ts" },
39528
39654
  "http2": {package: "node", module: "http2", path: "node/http2.d.ts" },
@@ -39536,8 +39662,13 @@ RED.editor.codeEditor.monaco = (function() {
39536
39662
  "querystring": {package: "node", module: "querystring", path: "node/querystring.d.ts" },
39537
39663
  "readline": {package: "node", module: "readline", path: "node/readline.d.ts" },
39538
39664
  "stream": {package: "node", module: "stream", path: "node/stream.d.ts" },
39665
+ "stream/consumers": {package: "node", module: "stream/consumers", path: "node/stream/consumers.d.ts" },
39666
+ "stream/promises": {package: "node", module: "stream/promises", path: "node/stream/promises.d.ts" },
39667
+ "stream/web": {package: "node", module: "stream/web", path: "node/stream/web.d.ts" },
39539
39668
  "string_decoder": {package: "node", module: "string_decoder", path: "node/string_decoder.d.ts" },
39669
+ "test": {package: "node", module: "test", path: "node/test.d.ts" },
39540
39670
  "timers": {package: "node", module: "timers", path: "node/timers.d.ts" },
39671
+ "timers/promises": {package: "node", module: "timers/promises", path: "node/timers/promises.d.ts" },
39541
39672
  "tls": {package: "node", module: "tls", path: "node/tls.d.ts" },
39542
39673
  "trace_events": {package: "node", module: "trace_events", path: "node/trace_events.d.ts" },
39543
39674
  "tty": {package: "node", module: "tty", path: "node/tty.d.ts" },
@@ -39552,7 +39683,7 @@ RED.editor.codeEditor.monaco = (function() {
39552
39683
  "node-red-util": {package: "node-red", module: "util", path: "node-red/util.d.ts" },
39553
39684
  "node-red-func": {package: "node-red", module: "func", path: "node-red/func.d.ts" },
39554
39685
  }
39555
- const defaultServerSideTypes = [ knownModules["node-red-util"], knownModules["node-red-func"], knownModules["globals"], knownModules["console"], knownModules["buffer"] , knownModules["util"] ];
39686
+ const defaultServerSideTypes = [ knownModules["node-red-util"], knownModules["node-red-func"], knownModules["globals"], knownModules["console"], knownModules["buffer"], knownModules["timers"] , knownModules["util"] ];
39556
39687
 
39557
39688
  const modulesCache = {};
39558
39689
 
@@ -40612,19 +40743,19 @@ RED.editor.codeEditor.monaco = (function() {
40612
40743
  // Warning: 4
40613
40744
  // Error: 8
40614
40745
  ed.getAnnotations = function getAnnotations() {
40615
- var aceCompatibleMarkers = [];
40746
+ let aceCompatibleMarkers;
40616
40747
  try {
40617
- var _model = ed.getModel();
40748
+ const _model = ed.getModel();
40618
40749
  if (_model !== null) {
40619
- var id = _model._languageId; // e.g. javascript
40620
- var ra = _model._associatedResource.authority; //e.g. model
40621
- var rp = _model._associatedResource.path; //e.g. /18
40622
- var rs = _model._associatedResource.scheme; //e.g. inmemory
40623
- var modelMarkers = monaco.editor.getModelMarkers(_model) || [];
40624
- var thisEditorsMarkers = modelMarkers.filter(function (marker) {
40625
- var _ra = marker.resource.authority; //e.g. model
40626
- var _rp = marker.resource.path; //e.g. /18
40627
- var _rs = marker.resource.scheme; //e.g. inmemory
40750
+ const id = _model.getLanguageId(); // e.g. javascript
40751
+ const ra = _model.uri.authority; // e.g. model
40752
+ const rp = _model.uri.path; // e.g. /18
40753
+ const rs = _model.uri.scheme; // e.g. inmemory
40754
+ const modelMarkers = monaco.editor.getModelMarkers(_model) || [];
40755
+ const thisEditorsMarkers = modelMarkers.filter(function (marker) {
40756
+ const _ra = marker.resource.authority; // e.g. model
40757
+ const _rp = marker.resource.path; // e.g. /18
40758
+ const _rs = marker.resource.scheme; // e.g. inmemory
40628
40759
  return marker.owner == id && _ra === ra && _rp === rp && _rs === rs;
40629
40760
  })
40630
40761
  aceCompatibleMarkers = thisEditorsMarkers.map(function (marker) {
@@ -41400,13 +41531,13 @@ RED.clipboard = (function() {
41400
41531
  // IE11 workaround
41401
41532
  // IE does not support data uri scheme for downloading data
41402
41533
  var blob = new Blob([data], {
41403
- type: "data:text/plain;charset=utf-8"
41534
+ type: "data:application/json;charset=utf-8"
41404
41535
  });
41405
41536
  navigator.msSaveBlob(blob, file);
41406
41537
  }
41407
41538
  else {
41408
41539
  var element = document.createElement('a');
41409
- element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(data));
41540
+ element.setAttribute('href', 'data:application/json;charset=utf-8,' + encodeURIComponent(data));
41410
41541
  element.setAttribute('download', file);
41411
41542
  element.style.display = 'none';
41412
41543
  document.body.appendChild(element);
@@ -41866,7 +41997,7 @@ RED.clipboard = (function() {
41866
41997
  $("#red-ui-clipboard-dialog-import-text").on("keyup", validateImport);
41867
41998
  $("#red-ui-clipboard-dialog-import-text").on('paste',function() { setTimeout(validateImport,10)});
41868
41999
 
41869
- if (RED.workspaces.active() === 0 || RED.workspaces.isActiveLocked()) {
42000
+ if (RED.workspaces.active() === 0 || RED.workspaces.isLocked()) {
41870
42001
  $("#red-ui-clipboard-dialog-import-opt-current").addClass('disabled').removeClass("selected");
41871
42002
  $("#red-ui-clipboard-dialog-import-opt-new").addClass("selected");
41872
42003
  } else {
@@ -42094,7 +42225,7 @@ RED.clipboard = (function() {
42094
42225
  nodes.unshift(parentNode);
42095
42226
  nodes = RED.nodes.createExportableNodeSet(nodes);
42096
42227
  } else if (type === 'full') {
42097
- nodes = RED.nodes.createCompleteNodeSet(false);
42228
+ nodes = RED.nodes.createCompleteNodeSet({ credentials: false });
42098
42229
  }
42099
42230
  if (nodes !== null) {
42100
42231
  if (format === "red-ui-clipboard-dialog-export-fmt-full") {
@@ -42641,7 +42772,7 @@ RED.clipboard = (function() {
42641
42772
  RED.keyboard.add("#red-ui-drop-target", "escape" ,hideDropTarget);
42642
42773
 
42643
42774
  $('#red-ui-workspace-chart').on("dragenter",function(event) {
42644
- if (!RED.workspaces.isActiveLocked() && (
42775
+ if (!RED.workspaces.isLocked() && (
42645
42776
  $.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1 ||
42646
42777
  $.inArray("Files",event.originalEvent.dataTransfer.types) != -1)) {
42647
42778
  $("#red-ui-drop-target").css({display:'table'}).focus();
@@ -42651,7 +42782,7 @@ RED.clipboard = (function() {
42651
42782
  $('#red-ui-drop-target').on("dragover",function(event) {
42652
42783
  if ($.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1 ||
42653
42784
  $.inArray("Files",event.originalEvent.dataTransfer.types) != -1 ||
42654
- RED.workspaces.isActiveLocked()) {
42785
+ RED.workspaces.isLocked()) {
42655
42786
  event.preventDefault();
42656
42787
  }
42657
42788
  })
@@ -42659,7 +42790,7 @@ RED.clipboard = (function() {
42659
42790
  hideDropTarget();
42660
42791
  })
42661
42792
  .on("drop",function(event) {
42662
- if (!RED.workspaces.isActiveLocked()) {
42793
+ if (!RED.workspaces.isLocked()) {
42663
42794
  try {
42664
42795
  if ($.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1) {
42665
42796
  var data = event.originalEvent.dataTransfer.getData("text/plain");
@@ -44608,7 +44739,7 @@ RED.search = (function() {
44608
44739
  const isMultipleLinks = !hasSelection && hasLinks && wireLinks.length > 1
44609
44740
  const canDelete = hasSelection || hasLinks
44610
44741
  const isGroup = hasSelection && selection.nodes.length === 1 && selection.nodes[0].type === 'group'
44611
- const canEdit = !RED.workspaces.isActiveLocked()
44742
+ const canEdit = !RED.workspaces.isLocked()
44612
44743
  const canRemoveFromGroup = hasSelection && !!selection.nodes[0].g
44613
44744
  const isAllGroups = hasSelection && selection.nodes.filter(n => n.type !== 'group').length === 0
44614
44745
  const hasGroup = hasSelection && selection.nodes.filter(n => n.type === 'group' ).length > 0
@@ -44659,17 +44790,18 @@ RED.search = (function() {
44659
44790
  w: 0, h: 0,
44660
44791
  outputs: 1,
44661
44792
  inputs: 1,
44662
- dirty: true
44793
+ dirty: true,
44794
+ moved: true
44663
44795
  }
44796
+ const junction = RED.nodes.addJunction(nn);
44664
44797
  const historyEvent = {
44665
44798
  dirty: RED.nodes.dirty(),
44666
44799
  t: 'add',
44667
- junctions: [nn]
44800
+ junctions: [junction]
44668
44801
  }
44669
- RED.nodes.addJunction(nn);
44670
44802
  RED.history.push(historyEvent);
44671
44803
  RED.nodes.dirty(true);
44672
- RED.view.select({nodes: [nn] });
44804
+ RED.view.select({nodes: [junction] });
44673
44805
  RED.view.redraw(true)
44674
44806
  },
44675
44807
  disabled: !canEdit
@@ -44707,17 +44839,24 @@ RED.search = (function() {
44707
44839
  options: [
44708
44840
  { onselect: 'core:group-selection' },
44709
44841
  { onselect: 'core:ungroup-selection', disabled: !hasGroup },
44710
- null,
44711
- { onselect: 'core:copy-group-style', disabled: !hasGroup },
44712
- { onselect: 'core:paste-group-style', disabled: !hasGroup}
44713
44842
  ]
44714
44843
  })
44844
+ if (hasGroup) {
44845
+ menuItems[menuItems.length - 1].options.push(
44846
+ { onselect: 'core:merge-selection-to-group', label: RED._("menu.label.groupMergeSelection") }
44847
+ )
44848
+
44849
+ }
44715
44850
  if (canRemoveFromGroup) {
44716
44851
  menuItems[menuItems.length - 1].options.push(
44717
- null,
44718
44852
  { onselect: 'core:remove-selection-from-group', label: RED._("menu.label.groupRemoveSelection") }
44719
44853
  )
44720
44854
  }
44855
+ menuItems[menuItems.length - 1].options.push(
44856
+ null,
44857
+ { onselect: 'core:copy-group-style', disabled: !hasGroup },
44858
+ { onselect: 'core:paste-group-style', disabled: !hasGroup}
44859
+ )
44721
44860
  }
44722
44861
  if (canEdit && hasMultipleSelection) {
44723
44862
  menuItems.push({
@@ -44865,7 +45004,7 @@ RED.actionList = (function() {
44865
45004
  var searchDiv = $("<div>",{class:"red-ui-search-container"}).appendTo(dialog);
44866
45005
  searchInput = $('<input type="text" data-i18n="[placeholder]keyboard.filterActions">').appendTo(searchDiv).searchBox({
44867
45006
  change: function() {
44868
- filterTerm = $(this).val().trim();
45007
+ filterTerm = $(this).val().trim().toLowerCase();
44869
45008
  filterTerms = filterTerm.split(" ");
44870
45009
  searchResults.editableList('filter');
44871
45010
  searchResults.find("li.selected").removeClass("selected");
@@ -45518,7 +45657,9 @@ RED.subflow = (function() {
45518
45657
  '</script>';
45519
45658
 
45520
45659
  function findAvailableSubflowIOPosition(subflow,isInput) {
45521
- var pos = {x:50,y:30};
45660
+ const scrollPos = RED.view.scroll()
45661
+ const scaleFactor = RED.view.scale()
45662
+ var pos = { x: (scrollPos[0]/scaleFactor)+50, y: (scrollPos[1]/scaleFactor)+30 };
45522
45663
  if (!isInput) {
45523
45664
  pos.x += 110;
45524
45665
  }
@@ -46045,7 +46186,7 @@ RED.subflow = (function() {
46045
46186
  }
46046
46187
  });
46047
46188
  RED.events.on("view:selection-changed",function(selection) {
46048
- if (!selection.nodes || RED.workspaces.isActiveLocked()) {
46189
+ if (!selection.nodes || RED.workspaces.isLocked()) {
46049
46190
  RED.menu.setDisabled("menu-item-subflow-convert",true);
46050
46191
  } else {
46051
46192
  RED.menu.setDisabled("menu-item-subflow-convert",false);
@@ -46108,7 +46249,7 @@ RED.subflow = (function() {
46108
46249
  }
46109
46250
 
46110
46251
  function convertToSubflow() {
46111
- if (RED.workspaces.isActiveLocked()) {
46252
+ if (RED.workspaces.isLocked()) {
46112
46253
  return
46113
46254
  }
46114
46255
  var selection = RED.view.selection();
@@ -46153,24 +46294,23 @@ RED.subflow = (function() {
46153
46294
  var candidateOutputs = [];
46154
46295
  var candidateInputNodes = {};
46155
46296
 
46156
- var boundingBox = [nodeList[0].x,
46157
- nodeList[0].y,
46158
- nodeList[0].x,
46159
- nodeList[0].y];
46297
+ var boundingBox = [nodeList[0].x-(nodeList[0].w/2),
46298
+ nodeList[0].y-(nodeList[0].h/2),
46299
+ nodeList[0].x+(nodeList[0].w/2),
46300
+ nodeList[0].y+(nodeList[0].h/2)];
46160
46301
 
46161
46302
  for (i=0;i<nodeList.length;i++) {
46162
46303
  n = nodeList[i];
46163
46304
  nodes[n.id] = {n:n,outputs:{}};
46164
46305
  boundingBox = [
46165
- Math.min(boundingBox[0],n.x),
46166
- Math.min(boundingBox[1],n.y),
46167
- Math.max(boundingBox[2],n.x),
46168
- Math.max(boundingBox[3],n.y)
46306
+ Math.min(boundingBox[0],n.x-(n.w/2)),
46307
+ Math.min(boundingBox[1],n.y-(n.h/2)),
46308
+ Math.max(boundingBox[2],n.x+(n.w/2)),
46309
+ Math.max(boundingBox[3],n.y+(n.h/2))
46169
46310
  ]
46170
46311
  }
46171
- var offsetX = snapToGrid(boundingBox[0] - 200);
46172
- var offsetY = snapToGrid(boundingBox[1] - 80);
46173
-
46312
+ var offsetX = snapToGrid(boundingBox[0] - 140);
46313
+ var offsetY = snapToGrid(boundingBox[1] - 60);
46174
46314
 
46175
46315
  var center = [
46176
46316
  snapToGrid((boundingBox[2]+boundingBox[0]) / 2),
@@ -47025,7 +47165,7 @@ RED.group = (function() {
47025
47165
  var activateMerge = false;
47026
47166
  var activateRemove = false;
47027
47167
  var singleGroupSelected = false;
47028
- var locked = RED.workspaces.isActiveLocked()
47168
+ var locked = RED.workspaces.isLocked()
47029
47169
 
47030
47170
  if (activateGroup) {
47031
47171
  singleGroupSelected = selection.nodes.length === 1 && selection.nodes[0].type === 'group';
@@ -47103,7 +47243,7 @@ RED.group = (function() {
47103
47243
  }
47104
47244
  }
47105
47245
  function pasteGroupStyle() {
47106
- if (RED.workspaces.isActiveLocked()) { return }
47246
+ if (RED.workspaces.isLocked()) { return }
47107
47247
  if (RED.view.state() !== RED.state.DEFAULT) { return }
47108
47248
  if (groupStyleClipboard) {
47109
47249
  var selection = RED.view.selection();
@@ -47138,7 +47278,7 @@ RED.group = (function() {
47138
47278
  }
47139
47279
 
47140
47280
  function groupSelection() {
47141
- if (RED.workspaces.isActiveLocked()) { return }
47281
+ if (RED.workspaces.isLocked()) { return }
47142
47282
  if (RED.view.state() !== RED.state.DEFAULT) { return }
47143
47283
  var selection = RED.view.selection();
47144
47284
  if (selection.nodes) {
@@ -47157,12 +47297,12 @@ RED.group = (function() {
47157
47297
  }
47158
47298
  }
47159
47299
  function ungroupSelection() {
47160
- if (RED.workspaces.isActiveLocked()) { return }
47300
+ if (RED.workspaces.isLocked()) { return }
47161
47301
  if (RED.view.state() !== RED.state.DEFAULT) { return }
47162
47302
  var selection = RED.view.selection();
47163
47303
  if (selection.nodes) {
47164
47304
  var newSelection = [];
47165
- groups = selection.nodes.filter(function(n) { return n.type === "group" });
47305
+ let groups = selection.nodes.filter(function(n) { return n.type === "group" });
47166
47306
 
47167
47307
  var historyEvent = {
47168
47308
  t:"ungroup",
@@ -47181,7 +47321,7 @@ RED.group = (function() {
47181
47321
  }
47182
47322
 
47183
47323
  function ungroup(g) {
47184
- if (RED.workspaces.isActiveLocked()) { return }
47324
+ if (RED.workspaces.isLocked()) { return }
47185
47325
  var nodes = [];
47186
47326
  var parentGroup = RED.nodes.group(g.g);
47187
47327
  g.nodes.forEach(function(n) {
@@ -47208,7 +47348,7 @@ RED.group = (function() {
47208
47348
  }
47209
47349
 
47210
47350
  function mergeSelection() {
47211
- if (RED.workspaces.isActiveLocked()) { return }
47351
+ if (RED.workspaces.isLocked()) { return }
47212
47352
  if (RED.view.state() !== RED.state.DEFAULT) { return }
47213
47353
  var selection = RED.view.selection();
47214
47354
  if (selection.nodes) {
@@ -47238,7 +47378,7 @@ RED.group = (function() {
47238
47378
  }
47239
47379
  }
47240
47380
  var existingGroup;
47241
-
47381
+ var mergedEnv = {}
47242
47382
  // Second pass, ungroup any groups in the selection and add their contents
47243
47383
  // to the selection
47244
47384
  for (var i=0; i<selection.nodes.length; i++) {
@@ -47247,6 +47387,11 @@ RED.group = (function() {
47247
47387
  if (!existingGroup) {
47248
47388
  existingGroup = n;
47249
47389
  }
47390
+ if (n.env && n.env.length > 0) {
47391
+ n.env.forEach(env => {
47392
+ mergedEnv[env.name] = env
47393
+ })
47394
+ }
47250
47395
  ungroupHistoryEvent.groups.push(n);
47251
47396
  nodes = nodes.concat(ungroup(n));
47252
47397
  } else {
@@ -47264,6 +47409,7 @@ RED.group = (function() {
47264
47409
  group.style = existingGroup.style;
47265
47410
  group.name = existingGroup.name;
47266
47411
  }
47412
+ group.env = Object.values(mergedEnv)
47267
47413
  RED.view.select({nodes:[group]})
47268
47414
  }
47269
47415
  historyEvent.events.push({
@@ -47278,7 +47424,7 @@ RED.group = (function() {
47278
47424
  }
47279
47425
 
47280
47426
  function removeSelection() {
47281
- if (RED.workspaces.isActiveLocked()) { return }
47427
+ if (RED.workspaces.isLocked()) { return }
47282
47428
  if (RED.view.state() !== RED.state.DEFAULT) { return }
47283
47429
  var selection = RED.view.selection();
47284
47430
  if (selection.nodes) {
@@ -47306,13 +47452,21 @@ RED.group = (function() {
47306
47452
  }
47307
47453
  }
47308
47454
  function createGroup(nodes) {
47309
- if (RED.workspaces.isActiveLocked()) { return }
47455
+ if (RED.workspaces.isLocked()) { return }
47310
47456
  if (nodes.length === 0) {
47311
47457
  return;
47312
47458
  }
47313
- if (nodes.filter(function(n) { return n.type === "subflow" }).length > 0) {
47314
- RED.notify(RED._("group.errors.cannotAddSubflowPorts"),"error");
47315
- return;
47459
+ const existingGroup = nodes[0].g
47460
+ for (let i = 0; i < nodes.length; i++) {
47461
+ const n = nodes[i]
47462
+ if (n.type === 'subflow') {
47463
+ RED.notify(RED._("group.errors.cannotAddSubflowPorts"),"error");
47464
+ return;
47465
+ }
47466
+ if (n.g !== existingGroup) {
47467
+ console.warn("Cannot add nooes with different z properties")
47468
+ return
47469
+ }
47316
47470
  }
47317
47471
  // nodes is an array
47318
47472
  // each node must be on the same tab (z)
@@ -47325,12 +47479,17 @@ RED.group = (function() {
47325
47479
  y: Number.POSITIVE_INFINITY,
47326
47480
  w: 0,
47327
47481
  h: 0,
47328
- _def: RED.group.def
47482
+ _def: RED.group.def,
47483
+ changed: true
47329
47484
  }
47330
47485
 
47331
47486
  group.z = nodes[0].z;
47332
47487
  group = RED.nodes.addGroup(group);
47333
47488
 
47489
+ if (existingGroup) {
47490
+ addToGroup(RED.nodes.group(existingGroup), group)
47491
+ }
47492
+
47334
47493
  try {
47335
47494
  addToGroup(group,nodes);
47336
47495
  } catch(err) {
@@ -47354,7 +47513,7 @@ RED.group = (function() {
47354
47513
  if (!z) {
47355
47514
  z = n.z;
47356
47515
  } else if (z !== n.z) {
47357
- throw new Error("Cannot add nooes with different z properties")
47516
+ throw new Error("Cannot add nodes with different z properties")
47358
47517
  }
47359
47518
  if (n.g) {
47360
47519
  // This is already in a group.
@@ -47371,14 +47530,10 @@ RED.group = (function() {
47371
47530
  throw new Error(RED._("group.errors.cannotCreateDiffGroups"))
47372
47531
  }
47373
47532
  }
47374
- // The nodes are already in a group. The assumption is they should be
47375
- // wrapped in the newly provided group, and that group added to in their
47376
- // place to the existing containing group.
47533
+ // The nodes are already in a group - so we need to remove them first
47377
47534
  if (g) {
47378
47535
  g = RED.nodes.group(g);
47379
- g.nodes.push(group);
47380
47536
  g.dirty = true;
47381
- group.g = g.id;
47382
47537
  }
47383
47538
  // Second pass - add them to the group
47384
47539
  for (i=0;i<nodes.length;i++) {
@@ -47412,7 +47567,7 @@ RED.group = (function() {
47412
47567
  markDirty(group);
47413
47568
  }
47414
47569
  function removeFromGroup(group, nodes, reparent) {
47415
- if (RED.workspaces.isActiveLocked()) { return }
47570
+ if (RED.workspaces.isLocked()) { return }
47416
47571
  if (!Array.isArray(nodes)) {
47417
47572
  nodes = [nodes];
47418
47573
  }
@@ -47430,7 +47585,7 @@ RED.group = (function() {
47430
47585
  n.dirty = true;
47431
47586
  var index = group.nodes.indexOf(n);
47432
47587
  group.nodes.splice(index,1);
47433
- if (reparent && group.g) {
47588
+ if (reparent && parentGroup) {
47434
47589
  n.g = group.g
47435
47590
  parentGroup.nodes.push(n);
47436
47591
  } else {
@@ -48617,14 +48772,14 @@ RED.projects = (function() {
48617
48772
  var row = $('<div class="form-row"></div>').appendTo(body);
48618
48773
  $('<label for="red-ui-projects-dialog-screen-create-project-file">'+RED._("projects.default-files.flow-file")+'</label>').appendTo(row);
48619
48774
  var subrow = $('<div style="position:relative;"></div>').appendTo(row);
48620
- var defaultFlowFile = (createProjectOptions.files &&createProjectOptions.files.flow) || (RED.settings.files && RED.settings.files.flow)||"flow.json";
48775
+ var defaultFlowFile = (createProjectOptions.files &&createProjectOptions.files.flow) || (RED.settings.files && RED.settings.files.flow) || "flows.json";
48621
48776
  projectFlowFileInput = $('<input id="red-ui-projects-dialog-screen-create-project-file" type="text">').val(defaultFlowFile)
48622
48777
  .on("change keyup paste",validateForm)
48623
48778
  .appendTo(subrow);
48624
48779
  $('<div class="red-ui-projects-dialog-screen-input-status"></div>').appendTo(subrow);
48625
48780
  $('<label class="red-ui-projects-edit-form-sublabel"><small>*.json</small></label>').appendTo(row);
48626
48781
 
48627
- var defaultCredentialsFile = (createProjectOptions.files &&createProjectOptions.files.credentials) || (RED.settings.files && RED.settings.files.credentials)||"flow_cred.json";
48782
+ var defaultCredentialsFile = (createProjectOptions.files &&createProjectOptions.files.credentials) || (RED.settings.files && RED.settings.files.credentials) || "flows_cred.json";
48628
48783
  row = $('<div class="form-row"></div>').appendTo(body);
48629
48784
  $('<label for="red-ui-projects-dialog-screen-create-project-credfile">'+RED._("projects.default-files.credentials-file")+'</label>').appendTo(row);
48630
48785
  subrow = $('<div style="position:relative;"></div>').appendTo(row);
@@ -49127,7 +49282,7 @@ RED.projects = (function() {
49127
49282
  row = $('<div class="form-row red-ui-projects-dialog-screen-create-row red-ui-projects-dialog-screen-create-row-empty"></div>').appendTo(container);
49128
49283
  $('<label for="red-ui-projects-dialog-screen-create-project-file">'+RED._("projects.create.flow-file")+'</label>').appendTo(row);
49129
49284
  subrow = $('<div style="position:relative;"></div>').appendTo(row);
49130
- projectFlowFileInput = $('<input id="red-ui-projects-dialog-screen-create-project-file" type="text">').val("flow.json")
49285
+ projectFlowFileInput = $('<input id="red-ui-projects-dialog-screen-create-project-file" type="text">').val("flows.json")
49131
49286
  .on("change keyup paste",validateForm)
49132
49287
  .appendTo(subrow);
49133
49288
  $('<div class="red-ui-projects-dialog-screen-input-status"></div>').appendTo(subrow);
@@ -50498,6 +50653,9 @@ RED.projects.settings = (function() {
50498
50653
  }
50499
50654
  var description = addTargetToExternalLinks($('<span class="red-ui-text-bidi-aware" dir=\"'+RED.text.bidi.resolveBaseTextDir(desc)+'">'+desc+'</span>')).appendTo(container);
50500
50655
  description.find(".red-ui-text-bidi-aware").contents().filter(function() { return this.nodeType === 3 && this.textContent.trim() !== "" }).wrap( "<span></span>" );
50656
+ setTimeout(function () {
50657
+ mermaid.init();
50658
+ }, 200);
50501
50659
  }
50502
50660
 
50503
50661
  function editSummary(activeProject, summary, container, version, versionContainer) {