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

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 (106) hide show
  1. package/locales/de/editor.json +2 -0
  2. package/locales/en-US/editor.json +8 -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 +14 -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 +50 -1
  16. package/public/red/red.js +1073 -984
  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 +60 -4
  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 +3329 -656
  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 +43 -503
  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 +1081 -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/vendor/jquery/css/base/images/ui-icons_444444_256x240.png +0 -0
  70. package/public/vendor/jquery/css/base/images/ui-icons_555555_256x240.png +0 -0
  71. package/public/vendor/jquery/css/base/images/ui-icons_777620_256x240.png +0 -0
  72. package/public/vendor/jquery/css/base/images/ui-icons_777777_256x240.png +0 -0
  73. package/public/vendor/jquery/css/base/images/ui-icons_cc0000_256x240.png +0 -0
  74. package/public/vendor/jquery/css/base/images/ui-icons_ffffff_256x240.png +0 -0
  75. package/public/vendor/jquery/css/base/jquery-ui.min.css +4 -4
  76. package/public/vendor/monaco/dist/{ade705761eb7e702770d.ttf → 7064e66c3890a12c47b4.ttf} +0 -0
  77. package/public/vendor/monaco/dist/css.worker.js +1 -1
  78. package/public/vendor/monaco/dist/css.worker.js.LICENSE.txt +1 -1
  79. package/public/vendor/monaco/dist/editor.js +1 -1
  80. package/public/vendor/monaco/dist/editor.js.LICENSE.txt +5 -1
  81. package/public/vendor/monaco/dist/editor.worker.js +1 -1
  82. package/public/vendor/monaco/dist/html.worker.js +1 -1
  83. package/public/vendor/monaco/dist/html.worker.js.LICENSE.txt +1 -1
  84. package/public/vendor/monaco/dist/json.worker.js +1 -1
  85. package/public/vendor/monaco/dist/json.worker.js.LICENSE.txt +1 -1
  86. package/public/vendor/monaco/dist/locale/cs.js +319 -71
  87. package/public/vendor/monaco/dist/locale/de.js +323 -75
  88. package/public/vendor/monaco/dist/locale/es.js +318 -70
  89. package/public/vendor/monaco/dist/locale/fr.js +327 -79
  90. package/public/vendor/monaco/dist/locale/it.js +325 -77
  91. package/public/vendor/monaco/dist/locale/ja.js +337 -89
  92. package/public/vendor/monaco/dist/locale/ko.js +324 -76
  93. package/public/vendor/monaco/dist/locale/pl.js +322 -74
  94. package/public/vendor/monaco/dist/locale/pt-br.js +321 -73
  95. package/public/vendor/monaco/dist/locale/qps-ploc.js +1580 -1332
  96. package/public/vendor/monaco/dist/locale/ru.js +324 -76
  97. package/public/vendor/monaco/dist/locale/tr.js +326 -78
  98. package/public/vendor/monaco/dist/locale/zh-hans.js +328 -80
  99. package/public/vendor/monaco/dist/locale/zh-hant.js +321 -73
  100. package/public/vendor/monaco/dist/theme/forge.json +236 -0
  101. package/public/vendor/monaco/dist/theme/github-dark.json +348 -0
  102. package/public/vendor/monaco/dist/theme/github-light.json +348 -0
  103. package/public/vendor/monaco/dist/theme/nord.json +93 -0
  104. package/public/vendor/monaco/dist/ts.worker.js +1 -1
  105. package/public/vendor/monaco/dist/ts.worker.js.LICENSE.txt +14 -6
  106. package/public/vendor/vendor.js +6 -13
package/public/red/red.js CHANGED
@@ -3738,6 +3738,9 @@ RED.nodes = (function() {
3738
3738
 
3739
3739
  function setDirty(d) {
3740
3740
  dirty = d;
3741
+ if (!d) {
3742
+ allNodes.clearState()
3743
+ }
3741
3744
  RED.events.emit("workspace:dirty",{dirty:dirty});
3742
3745
  }
3743
3746
 
@@ -3760,7 +3763,6 @@ RED.nodes = (function() {
3760
3763
  }
3761
3764
  };
3762
3765
 
3763
-
3764
3766
  var exports = {
3765
3767
  setModulePendingUpdated: function(module,version) {
3766
3768
  moduleList[module].pending_version = version;
@@ -3930,22 +3932,72 @@ RED.nodes = (function() {
3930
3932
 
3931
3933
  // allNodes holds information about the Flow nodes.
3932
3934
  var allNodes = (function() {
3935
+ // Map node.id -> node
3933
3936
  var nodes = {};
3937
+ // Map tab.id -> Array of nodes on that tab
3934
3938
  var tabMap = {};
3939
+ // Map tab.id -> Set of dirty object ids on that tab
3940
+ var tabDirtyMap = {};
3941
+ // Map tab.id -> Set of object ids of things deleted from the tab that weren't otherwise dirty
3942
+ var tabDeletedNodesMap = {};
3943
+ // Set of object ids of things added to a tab after initial import
3944
+ var addedDirtyObjects = new Set()
3945
+
3946
+ function changeCollectionDepth(tabNodes, toMove, direction, singleStep) {
3947
+ const result = []
3948
+ const moved = new Set();
3949
+ const startIndex = direction ? tabNodes.length - 1 : 0
3950
+ const endIndex = direction ? -1 : tabNodes.length
3951
+ const step = direction ? -1 : 1
3952
+ let target = startIndex // Only used for all-the-way moves
3953
+ for (let i = startIndex; i != endIndex; i += step) {
3954
+ if (toMove.size === 0) {
3955
+ break;
3956
+ }
3957
+ const n = tabNodes[i]
3958
+ if (toMove.has(n)) {
3959
+ if (singleStep) {
3960
+ if (i !== startIndex && !moved.has(tabNodes[i - step])) {
3961
+ tabNodes.splice(i, 1)
3962
+ tabNodes.splice(i - step, 0, n)
3963
+ n._reordered = true
3964
+ result.push(n)
3965
+ }
3966
+ } else {
3967
+ if (i !== target) {
3968
+ tabNodes.splice(i, 1)
3969
+ tabNodes.splice(target, 0, n)
3970
+ n._reordered = true
3971
+ result.push(n)
3972
+ }
3973
+ target += step
3974
+ }
3975
+ toMove.delete(n);
3976
+ moved.add(n);
3977
+ }
3978
+ }
3979
+ return result
3980
+ }
3981
+
3935
3982
  var api = {
3936
3983
  addTab: function(id) {
3937
3984
  tabMap[id] = [];
3985
+ tabDirtyMap[id] = new Set();
3986
+ tabDeletedNodesMap[id] = new Set();
3938
3987
  },
3939
3988
  hasTab: function(z) {
3940
3989
  return tabMap.hasOwnProperty(z)
3941
3990
  },
3942
3991
  removeTab: function(id) {
3943
3992
  delete tabMap[id];
3993
+ delete tabDirtyMap[id];
3994
+ delete tabDeletedNodesMap[id];
3944
3995
  },
3945
3996
  addNode: function(n) {
3946
3997
  nodes[n.id] = n;
3947
3998
  if (tabMap.hasOwnProperty(n.z)) {
3948
3999
  tabMap[n.z].push(n);
4000
+ api.addObjectToWorkspace(n.z, n.id, n.changed || n.moved)
3949
4001
  } else {
3950
4002
  console.warn("Node added to unknown tab/subflow:",n);
3951
4003
  tabMap["_"] = tabMap["_"] || [];
@@ -3959,7 +4011,36 @@ RED.nodes = (function() {
3959
4011
  if (i > -1) {
3960
4012
  tabMap[n.z].splice(i,1);
3961
4013
  }
4014
+ api.removeObjectFromWorkspace(n.z, n.id)
4015
+ }
4016
+ },
4017
+ /**
4018
+ * Add an object to our dirty/clean tracking state
4019
+ * @param {String} z
4020
+ * @param {String} id
4021
+ * @param {Boolean} isDirty
4022
+ */
4023
+ addObjectToWorkspace: function (z, id, isDirty) {
4024
+ if (isDirty) {
4025
+ addedDirtyObjects.add(id)
4026
+ }
4027
+ if (tabDeletedNodesMap[z].has(id)) {
4028
+ tabDeletedNodesMap[z].delete(id)
4029
+ }
4030
+ api.markNodeDirty(z, id, isDirty)
4031
+ },
4032
+ /**
4033
+ * Remove an object from our dirty/clean tracking state
4034
+ * @param {String} z
4035
+ * @param {String} id
4036
+ */
4037
+ removeObjectFromWorkspace: function (z, id) {
4038
+ if (!addedDirtyObjects.has(id)) {
4039
+ tabDeletedNodesMap[z].add(id)
4040
+ } else {
4041
+ addedDirtyObjects.delete(id)
3962
4042
  }
4043
+ api.markNodeDirty(z, id, false)
3963
4044
  },
3964
4045
  hasNode: function(id) {
3965
4046
  return nodes.hasOwnProperty(id);
@@ -3972,152 +4053,54 @@ RED.nodes = (function() {
3972
4053
  n.z = newZ;
3973
4054
  api.addNode(n)
3974
4055
  },
3975
- moveNodesForwards: function(nodes) {
3976
- var result = [];
4056
+ /**
4057
+ * @param {array} nodes
4058
+ * @param {boolean} direction true:forwards false:back
4059
+ * @param {boolean} singleStep true:single-step false:all-the-way
4060
+ */
4061
+ changeDepth: function(nodes, direction, singleStep) {
3977
4062
  if (!Array.isArray(nodes)) {
3978
4063
  nodes = [nodes]
3979
4064
  }
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;
4065
+ let result = []
4066
+ const tabNodes = tabMap[nodes[0].z];
4067
+ const toMove = new Set(nodes.filter(function(n) { return n.type !== "group" && n.type !== "subflow" }));
4068
+ if (toMove.size > 0) {
4069
+ result = result.concat(changeCollectionDepth(tabNodes, toMove, direction, singleStep))
4070
+ if (result.length > 0) {
4071
+ RED.events.emit('nodes:reorder',{
4072
+ z: nodes[0].z,
4073
+ nodes: result
4074
+ });
3988
4075
  }
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
4076
  }
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;
4077
+
4078
+ const groupNodes = groupsByZ[nodes[0].z] || []
4079
+ const groupsToMove = new Set(nodes.filter(function(n) { return n.type === 'group'}))
4080
+ if (groupsToMove.size > 0) {
4081
+ const groupResult = changeCollectionDepth(groupNodes, groupsToMove, direction, singleStep)
4082
+ if (groupResult.length > 0) {
4083
+ result = result.concat(groupResult)
4084
+ RED.events.emit('groups:reorder',{
4085
+ z: nodes[0].z,
4086
+ nodes: groupResult
4087
+ });
4025
4088
  }
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
4089
  }
4047
- return result;
4090
+ RED.view.redraw(true)
4091
+ return result
4092
+ },
4093
+ moveNodesForwards: function(nodes) {
4094
+ return api.changeDepth(nodes, true, true)
4095
+ },
4096
+ moveNodesBackwards: function(nodes) {
4097
+ return api.changeDepth(nodes, false, true)
4048
4098
  },
4049
4099
  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;
4100
+ return api.changeDepth(nodes, true, false)
4084
4101
  },
4085
4102
  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;
4103
+ return api.changeDepth(nodes, false, false)
4121
4104
  },
4122
4105
  getNodes: function(z) {
4123
4106
  return tabMap[z];
@@ -4125,6 +4108,33 @@ RED.nodes = (function() {
4125
4108
  clear: function() {
4126
4109
  nodes = {};
4127
4110
  tabMap = {};
4111
+ tabDirtyMap = {};
4112
+ tabDeletedNodesMap = {};
4113
+ addedDirtyObjects = new Set();
4114
+ },
4115
+ /**
4116
+ * Clear all internal state on what is dirty.
4117
+ */
4118
+ clearState: function () {
4119
+ // Called when a deploy happens, we can forget about added/remove
4120
+ // items as they have now been deployed.
4121
+ addedDirtyObjects = new Set()
4122
+ const flowsToCheck = new Set()
4123
+ for (const [z, set] of Object.entries(tabDeletedNodesMap)) {
4124
+ if (set.size > 0) {
4125
+ set.clear()
4126
+ flowsToCheck.add(z)
4127
+ }
4128
+ }
4129
+ for (const [z, set] of Object.entries(tabDirtyMap)) {
4130
+ if (set.size > 0) {
4131
+ set.clear()
4132
+ flowsToCheck.add(z)
4133
+ }
4134
+ }
4135
+ for (const z of flowsToCheck) {
4136
+ api.checkTabState(z)
4137
+ }
4128
4138
  },
4129
4139
  eachNode: function(cb) {
4130
4140
  var nodeList,i,j;
@@ -4190,7 +4200,7 @@ RED.nodes = (function() {
4190
4200
  return result;
4191
4201
  },
4192
4202
  getNodeOrder: function(z) {
4193
- return tabMap[z].map(function(n) { return n.id })
4203
+ return (groupsByZ[z] || []).concat(tabMap[z]).map(n => n.id)
4194
4204
  },
4195
4205
  setNodeOrder: function(z, order) {
4196
4206
  var orderMap = {};
@@ -4202,6 +4212,41 @@ RED.nodes = (function() {
4202
4212
  B._reordered = true;
4203
4213
  return orderMap[A.id] - orderMap[B.id];
4204
4214
  })
4215
+ if (groupsByZ[z]) {
4216
+ groupsByZ[z].sort(function(A,B) {
4217
+ return orderMap[A.id] - orderMap[B.id];
4218
+ })
4219
+ }
4220
+ },
4221
+ /**
4222
+ * Update our records if an object is dirty or not
4223
+ * @param {String} z tab id
4224
+ * @param {String} id object id
4225
+ * @param {Boolean} dirty whether the object is dirty or not
4226
+ */
4227
+ markNodeDirty: function(z, id, dirty) {
4228
+ if (tabDirtyMap[z]) {
4229
+ if (dirty) {
4230
+ tabDirtyMap[z].add(id)
4231
+ } else {
4232
+ tabDirtyMap[z].delete(id)
4233
+ }
4234
+ api.checkTabState(z)
4235
+ }
4236
+ },
4237
+ /**
4238
+ * Check if a tab should update its contentsChange flag
4239
+ * @param {String} z tab id
4240
+ */
4241
+ checkTabState: function (z) {
4242
+ const ws = workspaces[z]
4243
+ if (ws) {
4244
+ const contentsChanged = tabDirtyMap[z].size > 0 || tabDeletedNodesMap[z].size > 0
4245
+ if (Boolean(ws.contentsChanged) !== contentsChanged) {
4246
+ ws.contentsChanged = contentsChanged
4247
+ RED.events.emit("flows:change", ws);
4248
+ }
4249
+ }
4205
4250
  }
4206
4251
  }
4207
4252
  return api;
@@ -4289,6 +4334,11 @@ RED.nodes = (function() {
4289
4334
  throw new Error(`Cannot modified property '${prop}' of locked object '${node.type}:${node.id}'`)
4290
4335
  }
4291
4336
  }
4337
+ if (node.z && (prop === 'changed' || prop === 'moved')) {
4338
+ setTimeout(() => {
4339
+ allNodes.markNodeDirty(node.z, node.id, node.changed || node.moved)
4340
+ }, 0)
4341
+ }
4292
4342
  node[prop] = value;
4293
4343
  return true
4294
4344
  }
@@ -4358,10 +4408,16 @@ RED.nodes = (function() {
4358
4408
  }
4359
4409
  if (l.source.z === l.target.z && linkTabMap[l.source.z]) {
4360
4410
  linkTabMap[l.source.z].push(l);
4411
+ allNodes.addObjectToWorkspace(l.source.z, getLinkId(l), true)
4361
4412
  }
4362
4413
  RED.events.emit("links:add",l);
4363
4414
  }
4364
4415
 
4416
+ function getLinkId(link) {
4417
+ return link.source.id + ':' + link.sourcePort + ':' + link.target.id
4418
+ }
4419
+
4420
+
4365
4421
  function getNode(id) {
4366
4422
  if (id in configNodes) {
4367
4423
  return configNodes[id];
@@ -4556,6 +4612,7 @@ RED.nodes = (function() {
4556
4612
  if (index !== -1) {
4557
4613
  linkTabMap[l.source.z].splice(index,1)
4558
4614
  }
4615
+ allNodes.removeObjectFromWorkspace(l.source.z, getLinkId(l))
4559
4616
  }
4560
4617
  }
4561
4618
  RED.events.emit("links:remove",l);
@@ -4725,6 +4782,11 @@ RED.nodes = (function() {
4725
4782
  return false;
4726
4783
  }
4727
4784
 
4785
+ function getDownstreamNodes(node) {
4786
+ const downstreamLinks = nodeLinks[node.id].out
4787
+ const downstreamNodes = new Set(downstreamLinks.map(l => l.target))
4788
+ return Array.from(downstreamNodes)
4789
+ }
4728
4790
  function getAllDownstreamNodes(node) {
4729
4791
  return getAllFlowNodes(node,'down').filter(function(n) { return n !== node });
4730
4792
  }
@@ -5380,6 +5442,7 @@ RED.nodes = (function() {
5380
5442
  * Options:
5381
5443
  * - generateIds - whether to replace all node ids
5382
5444
  * - addFlow - whether to import nodes to a new tab
5445
+ * - markChanged - whether to set changed=true on all newly imported objects
5383
5446
  * - reimport - if node has a .z property, dont overwrite it
5384
5447
  * Only applicible when `generateIds` is false
5385
5448
  * - importMap - how to resolve any conflicts.
@@ -5388,7 +5451,7 @@ RED.nodes = (function() {
5388
5451
  * - id:replace - import over the top of existing
5389
5452
  */
5390
5453
  function importNodes(newNodesObj,options) { // createNewIds,createMissingWorkspace) {
5391
- const defOpts = { generateIds: false, addFlow: false, reimport: false, importMap: {} }
5454
+ const defOpts = { generateIds: false, addFlow: false, markChanged: false, reimport: false, importMap: {} }
5392
5455
  options = Object.assign({}, defOpts, options)
5393
5456
  options.importMap = options.importMap || {}
5394
5457
  const createNewIds = options.generateIds;
@@ -5414,7 +5477,7 @@ RED.nodes = (function() {
5414
5477
  newNodes = newNodesObj;
5415
5478
  }
5416
5479
 
5417
- if (!$.isArray(newNodes)) {
5480
+ if (!Array.isArray(newNodes)) {
5418
5481
  newNodes = [newNodes];
5419
5482
  }
5420
5483
 
@@ -5712,6 +5775,9 @@ RED.nodes = (function() {
5712
5775
  if (!n.z) {
5713
5776
  delete configNode.z;
5714
5777
  }
5778
+ if (options.markChanged) {
5779
+ configNode.changed = true
5780
+ }
5715
5781
  if (n.hasOwnProperty('d')) {
5716
5782
  configNode.d = n.d;
5717
5783
  }
@@ -5774,6 +5840,9 @@ RED.nodes = (function() {
5774
5840
  if (n.hasOwnProperty('g')) {
5775
5841
  node.g = n.g;
5776
5842
  }
5843
+ if (options.markChanged) {
5844
+ node.changed = true
5845
+ }
5777
5846
  if (createNewIds || options.importMap[n.id] === "copy") {
5778
5847
  if (subflow_denylist[n.z]) {
5779
5848
  continue;
@@ -5994,7 +6063,7 @@ RED.nodes = (function() {
5994
6063
  // get added
5995
6064
  if (activeSubflow && /^link /.test(n.type) && n.links) {
5996
6065
  n.links = n.links.filter(function(id) {
5997
- var otherNode = RED.nodes.node(id);
6066
+ const otherNode = node_map[id] || RED.nodes.node(id);
5998
6067
  return (otherNode && otherNode.z === activeWorkspace)
5999
6068
  });
6000
6069
  }
@@ -6287,6 +6356,7 @@ RED.nodes = (function() {
6287
6356
  groupsByZ[group.z] = groupsByZ[group.z] || [];
6288
6357
  groupsByZ[group.z].push(group);
6289
6358
  groups[group.id] = group;
6359
+ allNodes.addObjectToWorkspace(group.z, group.id, group.changed || group.moved)
6290
6360
  RED.events.emit("groups:add",group);
6291
6361
  return group
6292
6362
  }
@@ -6303,10 +6373,14 @@ RED.nodes = (function() {
6303
6373
  }
6304
6374
  }
6305
6375
  RED.group.markDirty(group);
6306
-
6376
+ allNodes.removeObjectFromWorkspace(group.z, group.id)
6307
6377
  delete groups[group.id];
6308
6378
  RED.events.emit("groups:remove",group);
6309
6379
  }
6380
+ function getGroupOrder(z) {
6381
+ const groups = groupsByZ[z]
6382
+ return groups.map(g => g.id)
6383
+ }
6310
6384
 
6311
6385
  function addJunction(junction) {
6312
6386
  if (!junction.__isProxy__) {
@@ -6318,6 +6392,7 @@ RED.nodes = (function() {
6318
6392
  if (!nodeLinks[junction.id]) {
6319
6393
  nodeLinks[junction.id] = {in:[],out:[]};
6320
6394
  }
6395
+ allNodes.addObjectToWorkspace(junction.z, junction.id, junction.changed || junction.moved)
6321
6396
  RED.events.emit("junctions:add", junction)
6322
6397
  return junction
6323
6398
  }
@@ -6329,6 +6404,7 @@ RED.nodes = (function() {
6329
6404
  }
6330
6405
  delete junctions[junction.id]
6331
6406
  delete nodeLinks[junction.id];
6407
+ allNodes.removeObjectFromWorkspace(junction.z, junction.id)
6332
6408
  RED.events.emit("junctions:remove", junction)
6333
6409
 
6334
6410
  var removedLinks = links.filter(function(l) { return (l.source === junction) || (l.target === junction); });
@@ -6566,6 +6642,9 @@ RED.nodes = (function() {
6566
6642
  RED.view.redraw(true);
6567
6643
  }
6568
6644
  });
6645
+ RED.events.on('deploy', function () {
6646
+ allNodes.clearState()
6647
+ })
6569
6648
  },
6570
6649
  registry:registry,
6571
6650
  setNodeList: registry.setNodeList,
@@ -6668,6 +6747,20 @@ RED.nodes = (function() {
6668
6747
  }
6669
6748
  }
6670
6749
  },
6750
+ eachGroup: function(cb) {
6751
+ for (var group of Object.values(groups)) {
6752
+ if (cb(group) === false) {
6753
+ break
6754
+ }
6755
+ }
6756
+ },
6757
+ eachJunction: function(cb) {
6758
+ for (var junction of Object.values(junctions)) {
6759
+ if (cb(junction) === false) {
6760
+ break
6761
+ }
6762
+ }
6763
+ },
6671
6764
 
6672
6765
  node: getNode,
6673
6766
 
@@ -6690,6 +6783,7 @@ RED.nodes = (function() {
6690
6783
  getAllFlowNodes: getAllFlowNodes,
6691
6784
  getAllUpstreamNodes: getAllUpstreamNodes,
6692
6785
  getAllDownstreamNodes: getAllDownstreamNodes,
6786
+ getDownstreamNodes: getDownstreamNodes,
6693
6787
  getNodeIslands: getNodeIslands,
6694
6788
  createExportableNodeSet: createExportableNodeSet,
6695
6789
  createCompleteNodeSet: createCompleteNodeSet,
@@ -7905,7 +7999,8 @@ RED.history = (function() {
7905
7999
  if (ev.addToGroup) {
7906
8000
  RED.group.removeFromGroup(ev.addToGroup,ev.nodes.map(function(n) { return n.n }),false);
7907
8001
  inverseEv.removeFromGroup = ev.addToGroup;
7908
- } else if (ev.removeFromGroup) {
8002
+ }
8003
+ if (ev.removeFromGroup) {
7909
8004
  RED.group.addToGroup(ev.removeFromGroup,ev.nodes.map(function(n) { return n.n }));
7910
8005
  inverseEv.addToGroup = ev.removeFromGroup;
7911
8006
  }
@@ -7948,6 +8043,9 @@ RED.history = (function() {
7948
8043
  ev.node[i] = ev.changes[i];
7949
8044
  }
7950
8045
  }
8046
+ ev.node.dirty = true;
8047
+ ev.node.changed = ev.changed;
8048
+
7951
8049
  var eventType;
7952
8050
  switch(ev.node.type) {
7953
8051
  case 'tab': eventType = "flows"; break;
@@ -8038,8 +8136,6 @@ RED.history = (function() {
8038
8136
  inverseEv.links.push(ev.createdLinks[i]);
8039
8137
  }
8040
8138
  }
8041
- ev.node.dirty = true;
8042
- ev.node.changed = ev.changed;
8043
8139
  } else if (ev.t == "createSubflow") {
8044
8140
  inverseEv = {
8045
8141
  t: "deleteSubflow",
@@ -8175,6 +8271,12 @@ RED.history = (function() {
8175
8271
  ev.groups[i].nodes = [];
8176
8272
  RED.nodes.addGroup(ev.groups[i]);
8177
8273
  RED.group.addToGroup(ev.groups[i],nodes);
8274
+ if (ev.groups[i].g) {
8275
+ const parentGroup = RED.nodes.group(ev.groups[i].g)
8276
+ if (parentGroup) {
8277
+ RED.group.addToGroup(parentGroup, ev.groups[i])
8278
+ }
8279
+ }
8178
8280
  }
8179
8281
  }
8180
8282
  } else if (ev.t == "addToGroup") {
@@ -10292,6 +10394,9 @@ RED.utils = (function() {
10292
10394
  } else {
10293
10395
  return null;
10294
10396
  }
10397
+ },
10398
+ cancel: function() {
10399
+ this.element.sortable("cancel");
10295
10400
  }
10296
10401
  });
10297
10402
  })(jQuery);
@@ -12937,7 +13042,7 @@ RED.tabs = (function() {
12937
13042
  // Assume this is wheel event which might not trigger
12938
13043
  // the scroll event, so do things manually
12939
13044
  var sl = scrollContainer.scrollLeft();
12940
- sl -= evt.originalEvent.deltaY;
13045
+ sl += evt.originalEvent.deltaY;
12941
13046
  scrollContainer.scrollLeft(sl);
12942
13047
  }
12943
13048
  })
@@ -13599,7 +13704,6 @@ RED.tabs = (function() {
13599
13704
 
13600
13705
  var badges = $('<span class="red-ui-tabs-badges"></span>').appendTo(li);
13601
13706
  if (options.onselect) {
13602
- $('<i class="red-ui-tabs-badge-changed fa fa-circle"></i>').appendTo(badges);
13603
13707
  $('<i class="red-ui-tabs-badge-selected fa fa-check-circle"></i>').appendTo(badges);
13604
13708
  }
13605
13709
 
@@ -16154,12 +16258,17 @@ RED.deploy = (function() {
16154
16258
  } else {
16155
16259
  RED.notify('<p>' + RED._("deploy.successfulDeploy") + '</p>', "success");
16156
16260
  }
16157
- RED.nodes.eachNode(function (node) {
16158
- const flow = node.z && (RED.nodes.workspace(node.z) || RED.nodes.subflow(node.z) || null);
16261
+ const flowsToLock = new Set()
16262
+ function ensureUnlocked(id) {
16263
+ const flow = id && (RED.nodes.workspace(id) || RED.nodes.subflow(id) || null);
16159
16264
  const isLocked = flow ? flow.locked : false;
16160
16265
  if (flow && isLocked) {
16161
16266
  flow.locked = false;
16267
+ flowsToLock.add(flow)
16162
16268
  }
16269
+ }
16270
+ RED.nodes.eachNode(function (node) {
16271
+ ensureUnlocked(node.z)
16163
16272
  if (node.changed) {
16164
16273
  node.dirty = true;
16165
16274
  node.changed = false;
@@ -16171,11 +16280,33 @@ RED.deploy = (function() {
16171
16280
  if (node.credentials) {
16172
16281
  delete node.credentials;
16173
16282
  }
16174
- if (flow && isLocked) {
16175
- flow.locked = isLocked;
16176
- }
16177
16283
  });
16284
+ RED.nodes.eachGroup(function (node) {
16285
+ ensureUnlocked(node.z)
16286
+ if (node.changed) {
16287
+ node.dirty = true;
16288
+ node.changed = false;
16289
+ }
16290
+ if (node.moved) {
16291
+ node.dirty = true;
16292
+ node.moved = false;
16293
+ }
16294
+ })
16295
+ RED.nodes.eachJunction(function (node) {
16296
+ ensureUnlocked(node.z)
16297
+ if (node.changed) {
16298
+ node.dirty = true;
16299
+ node.changed = false;
16300
+ }
16301
+ if (node.moved) {
16302
+ node.dirty = true;
16303
+ node.moved = false;
16304
+ }
16305
+ })
16178
16306
  RED.nodes.eachConfig(function (confNode) {
16307
+ if (confNode.z) {
16308
+ ensureUnlocked(confNode.z)
16309
+ }
16179
16310
  confNode.changed = false;
16180
16311
  if (confNode.credentials) {
16181
16312
  delete confNode.credentials;
@@ -16185,8 +16316,16 @@ RED.deploy = (function() {
16185
16316
  subflow.changed = false;
16186
16317
  });
16187
16318
  RED.nodes.eachWorkspace(function (ws) {
16188
- ws.changed = false;
16319
+ if (ws.changed || ws.added) {
16320
+ ensureUnlocked(ws.z)
16321
+ ws.changed = false;
16322
+ delete ws.added
16323
+ RED.events.emit("flows:change", ws)
16324
+ }
16189
16325
  });
16326
+ flowsToLock.forEach(flow => {
16327
+ flow.locked = true
16328
+ })
16190
16329
  // Once deployed, cannot undo back to a clean state
16191
16330
  RED.history.markAllDirty();
16192
16331
  RED.view.redraw();
@@ -19384,6 +19523,10 @@ RED.keyboard = (function() {
19384
19523
  }
19385
19524
  }
19386
19525
  });
19526
+
19527
+ RED.actions.add("core:show-global-env", function() {
19528
+ RED.userSettings.show('envvar');
19529
+ });
19387
19530
  }
19388
19531
 
19389
19532
  return {
@@ -19475,8 +19618,11 @@ RED.workspaces = (function() {
19475
19618
  info: "",
19476
19619
  label: RED._('workspace.defaultName',{number:workspaceIndex}),
19477
19620
  env: [],
19478
- hideable: true
19621
+ hideable: true,
19479
19622
  };
19623
+ if (!skipHistoryEntry) {
19624
+ ws.added = true
19625
+ }
19480
19626
  RED.nodes.addWorkspace(ws,targetIndex);
19481
19627
  workspace_tabs.addTab(ws,targetIndex);
19482
19628
 
@@ -19486,8 +19632,7 @@ RED.workspaces = (function() {
19486
19632
  RED.nodes.dirty(true);
19487
19633
  }
19488
19634
  }
19489
- $("#red-ui-tab-"+(ws.id.replace(".","-"))).attr("flowname",ws.label)
19490
-
19635
+ $("#red-ui-tab-"+(ws.id.replace(".","-"))).attr("flowname",ws.label).toggleClass('red-ui-workspace-changed',!!(ws.contentsChanged || ws.changed || ws.added));
19491
19636
  RED.view.focus();
19492
19637
  return ws;
19493
19638
  }
@@ -19553,7 +19698,7 @@ RED.workspaces = (function() {
19553
19698
  }
19554
19699
  });
19555
19700
 
19556
- let isCurrentLocked = RED.workspaces.isActiveLocked()
19701
+ let isCurrentLocked = RED.workspaces.isLocked()
19557
19702
  if (tab) {
19558
19703
  isCurrentLocked = tab.locked
19559
19704
  }
@@ -19768,6 +19913,12 @@ RED.workspaces = (function() {
19768
19913
  $("#red-ui-tab-"+(tab.id.replace(".","-"))).addClass('red-ui-workspace-locked');
19769
19914
  }
19770
19915
 
19916
+ 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(".","-")))
19917
+ const changeBadge = document.createElementNS("http://www.w3.org/2000/svg","circle");
19918
+ changeBadge.setAttribute("cx",5);
19919
+ changeBadge.setAttribute("cy",5);
19920
+ changeBadge.setAttribute("r",5);
19921
+ changeBadgeContainer.append(changeBadge)
19771
19922
 
19772
19923
  RED.menu.setDisabled("menu-item-workspace-delete",activeWorkspace === 0 || workspaceTabCount <= 1);
19773
19924
  if (workspaceTabCount === 1) {
@@ -20030,6 +20181,11 @@ RED.workspaces = (function() {
20030
20181
  RED.workspaces.show(viewStack[++viewStackPos],true);
20031
20182
  }
20032
20183
  })
20184
+
20185
+ RED.events.on("flows:change", (ws) => {
20186
+ $("#red-ui-tab-"+(ws.id.replace(".","-"))).toggleClass('red-ui-workspace-changed',!!(ws.contentsChanged || ws.changed || ws.added));
20187
+ })
20188
+
20033
20189
  hideWorkspace();
20034
20190
  }
20035
20191
 
@@ -20108,19 +20264,8 @@ RED.workspaces = (function() {
20108
20264
  RED.history.push(historyEvent);
20109
20265
  RED.events.emit("flows:change",workspace);
20110
20266
  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
- // }
20267
+ RED.nodes.filterNodes({z:workspace.id}).forEach(n => n.dirty = true)
20268
+ RED.view.redraw(true);
20124
20269
  }
20125
20270
  }
20126
20271
 
@@ -20226,8 +20371,9 @@ RED.workspaces = (function() {
20226
20371
  active: function() {
20227
20372
  return activeWorkspace
20228
20373
  },
20229
- isActiveLocked: function() {
20230
- var ws = RED.nodes.workspace(activeWorkspace) || RED.nodes.subflow(activeWorkspace)
20374
+ isLocked: function(id) {
20375
+ id = id || activeWorkspace
20376
+ var ws = RED.nodes.workspace(id) || RED.nodes.subflow(id)
20231
20377
  return ws && ws.locked
20232
20378
  },
20233
20379
  selection: function() {
@@ -20374,8 +20520,8 @@ RED.statusBar = (function() {
20374
20520
  */
20375
20521
 
20376
20522
  RED.view = (function() {
20377
- var space_width = 5000,
20378
- space_height = 5000,
20523
+ var space_width = 8000,
20524
+ space_height = 8000,
20379
20525
  lineCurveScale = 0.75,
20380
20526
  scaleFactor = 1,
20381
20527
  node_width = 100,
@@ -20405,8 +20551,9 @@ RED.view = (function() {
20405
20551
  var activeJunctions = [];
20406
20552
  var activeFlowLinks = [];
20407
20553
  var activeLinkNodes = {};
20408
- var activeGroup = null;
20409
20554
  var activeHoverGroup = null;
20555
+ var groupAddActive = false;
20556
+ var groupAddParentGroup = null;
20410
20557
  var activeGroups = [];
20411
20558
  var dirtyGroups = {};
20412
20559
 
@@ -20468,10 +20615,10 @@ RED.view = (function() {
20468
20615
  var groupLayer;
20469
20616
  var drag_lines;
20470
20617
 
20471
- var movingSet = (function() {
20618
+ const movingSet = (function() {
20472
20619
  var setIds = new Set();
20473
20620
  var set = [];
20474
- var api = {
20621
+ const api = {
20475
20622
  add: function(node) {
20476
20623
  if (Array.isArray(node)) {
20477
20624
  for (var i=0;i<node.length;i++) {
@@ -20519,14 +20666,27 @@ RED.view = (function() {
20519
20666
  get: function(i) { return set[i] },
20520
20667
  forEach: function(func) { set.forEach(func) },
20521
20668
  nodes: function() { return set.map(function(n) { return n.n })},
20522
- has: function(node) { return setIds.has(node.id) }
20669
+ has: function(node) { return setIds.has(node.id) },
20670
+ /**
20671
+ * Make the specified node the first node of the moving set, if
20672
+ * it is already in the set.
20673
+ * @param {Node} node
20674
+ */
20675
+ makePrimary: function (node) {
20676
+ const index = set.findIndex(n => n.n === node)
20677
+ if (index > -1) {
20678
+ const removed = set.splice(index, 1)
20679
+ set.unshift(...removed)
20680
+ }
20681
+ },
20682
+ find: function(func) { return set.find(func) }
20523
20683
  }
20524
20684
  return api;
20525
20685
  })();
20526
20686
 
20527
- var selectedLinks = (function() {
20687
+ const selectedLinks = (function() {
20528
20688
  var links = new Set();
20529
- return {
20689
+ const api = {
20530
20690
  add: function(link) {
20531
20691
  links.add(link);
20532
20692
  link.selected = true;
@@ -20544,8 +20704,16 @@ RED.view = (function() {
20544
20704
  },
20545
20705
  forEach: function(func) { links.forEach(func) },
20546
20706
  has: function(link) { return links.has(link) },
20547
- toArray: function() { return Array.from(links) }
20707
+ toArray: function() { return Array.from(links) },
20708
+ clearUnselected: function () {
20709
+ api.forEach(l => {
20710
+ if (!l.source.selected || !l.target.selected) {
20711
+ api.remove(l)
20712
+ }
20713
+ })
20714
+ }
20548
20715
  }
20716
+ return api
20549
20717
  })();
20550
20718
 
20551
20719
 
@@ -20588,6 +20756,7 @@ RED.view = (function() {
20588
20756
  d3.select(document).on('mouseup.red-ui-workspace-tracker', null)
20589
20757
  if (lasso) {
20590
20758
  if (d3.event.buttons !== 1) {
20759
+ outer.classed('red-ui-workspace-lasso-active', false)
20591
20760
  lasso.remove();
20592
20761
  lasso = null;
20593
20762
  }
@@ -20722,6 +20891,31 @@ RED.view = (function() {
20722
20891
  }
20723
20892
  d3.event.preventDefault();
20724
20893
  });
20894
+
20895
+
20896
+ const handleAltToggle = (event) => {
20897
+ if (mouse_mode === RED.state.MOVING_ACTIVE && event.key === 'Alt' && groupAddParentGroup) {
20898
+ RED.nodes.group(groupAddParentGroup).dirty = true
20899
+ for (let n = 0; n<movingSet.length(); n++) {
20900
+ const node = movingSet.get(n);
20901
+ node.n._detachFromGroup = event.altKey
20902
+ }
20903
+ if (!event.altKey) {
20904
+ if (groupHoverTimer) {
20905
+ clearTimeout(groupHoverTimer)
20906
+ groupHoverTimer = null
20907
+ }
20908
+ if (activeHoverGroup) {
20909
+ activeHoverGroup.hovered = false
20910
+ activeHoverGroup.dirty = true
20911
+ activeHoverGroup = null
20912
+ }
20913
+ }
20914
+ RED.view.redraw()
20915
+ }
20916
+ }
20917
+ document.addEventListener("keyup", handleAltToggle)
20918
+ document.addEventListener("keydown", handleAltToggle)
20725
20919
 
20726
20920
  // Workspace Background
20727
20921
  eventLayer.append("svg:rect")
@@ -20930,27 +21124,11 @@ RED.view = (function() {
20930
21124
  nn.y -= gridOffset.y;
20931
21125
  }
20932
21126
 
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];
21127
+ var linkToSplice = $(ui.helper).data("splice");
21128
+ if (linkToSplice) {
21129
+ spliceLink(linkToSplice, nn, historyEvent)
20951
21130
  }
20952
21131
 
20953
-
20954
21132
  var group = $(ui.helper).data("group");
20955
21133
  if (group) {
20956
21134
  var oldX = group.x;
@@ -20987,15 +21165,9 @@ RED.view = (function() {
20987
21165
  RED.editor.validateNode(nn);
20988
21166
  RED.nodes.dirty(true);
20989
21167
  // auto select dropped node - so info shows (if visible)
20990
- exitActiveGroup();
20991
21168
  clearSelection();
20992
21169
  nn.selected = true;
20993
21170
  movingSet.add(nn);
20994
- if (group) {
20995
- selectGroup(group,false);
20996
- enterActiveGroup(group);
20997
- activeGroup = group;
20998
- }
20999
21171
  updateActiveNodes();
21000
21172
  updateSelection();
21001
21173
  redraw();
@@ -21015,7 +21187,7 @@ RED.view = (function() {
21015
21187
  RED.actions.add("core:copy-selection-to-internal-clipboard",copySelection);
21016
21188
  RED.actions.add("core:cut-selection-to-internal-clipboard",function(){copySelection(true);deleteSelection();});
21017
21189
  RED.actions.add("core:paste-from-internal-clipboard",function(){
21018
- if (RED.workspaces.isActiveLocked()) {
21190
+ if (RED.workspaces.isLocked()) {
21019
21191
  return
21020
21192
  }
21021
21193
  importNodes(clipboard,{generateIds: clipboardSource === 'copy', generateDefaultNames: clipboardSource === 'copy'});
@@ -21058,7 +21230,7 @@ RED.view = (function() {
21058
21230
  if (/^subflow:/.test(node.type)) {
21059
21231
  RED.workspaces.show(node.type.substring(8))
21060
21232
  } else if (node.type === 'group') {
21061
- enterActiveGroup(node);
21233
+ // enterActiveGroup(node);
21062
21234
  redraw();
21063
21235
  }
21064
21236
  }
@@ -21246,16 +21418,31 @@ RED.view = (function() {
21246
21418
  });
21247
21419
  activeJunctions = RED.nodes.junctions(activeWorkspace) || [];
21248
21420
  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;
21421
+ if (activeGroups.length) {
21422
+ const groupTree = {}
21423
+ const rootGroups = []
21424
+ activeGroups.forEach(function(g, i) {
21425
+ groupTree[g.id] = g
21426
+ g._index = i;
21427
+ g._childGroups = []
21428
+ if (!g.g) {
21429
+ rootGroups.push(g)
21430
+ }
21431
+ });
21432
+ activeGroups.forEach(function(g) {
21433
+ if (g.g) {
21434
+ groupTree[g.g]._childGroups.push(g)
21435
+ g._parentGroup = groupTree[g.g]
21436
+ }
21437
+ })
21438
+ let ii = 0
21439
+ // Depth-first walk of the groups
21440
+ const processGroup = g => {
21441
+ g._order = ii++
21442
+ g._childGroups.forEach(processGroup)
21257
21443
  }
21258
- });
21444
+ rootGroups.forEach(processGroup)
21445
+ }
21259
21446
  } else {
21260
21447
  activeNodes = [];
21261
21448
  activeLinks = [];
@@ -21263,43 +21450,13 @@ RED.view = (function() {
21263
21450
  activeGroups = [];
21264
21451
  }
21265
21452
 
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
21453
  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
- }
21454
+ return a._order - b._order
21293
21455
  });
21294
21456
 
21295
21457
  var group = groupLayer.selectAll(".red-ui-flow-group").data(activeGroups,function(d) { return d.id });
21296
21458
  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
- }
21459
+ return a._order - b._order
21303
21460
  })
21304
21461
  }
21305
21462
 
@@ -21404,6 +21561,7 @@ RED.view = (function() {
21404
21561
  updateSelection();
21405
21562
  }
21406
21563
  if (mouse_mode === 0 && lasso) {
21564
+ outer.classed('red-ui-workspace-lasso-active', false)
21407
21565
  lasso.remove();
21408
21566
  lasso = null;
21409
21567
  }
@@ -21435,6 +21593,7 @@ RED.view = (function() {
21435
21593
  .attr("height", 0)
21436
21594
  .attr("class", "nr-ui-view-lasso");
21437
21595
  d3.event.preventDefault();
21596
+ outer.classed('red-ui-workspace-lasso-active', true)
21438
21597
  }
21439
21598
  } else if (d3.event.altKey && !activeFlowLocked) {
21440
21599
  //Alt [+shift] held - Begin slicing
@@ -21455,14 +21614,13 @@ RED.view = (function() {
21455
21614
  }
21456
21615
  options = options || {};
21457
21616
  var point = options.position || lastClickPosition;
21458
- var spliceLink = options.splice;
21617
+ var linkToSplice = options.splice;
21459
21618
  var spliceMultipleLinks = options.spliceMultiple
21460
21619
  var targetGroup = options.group;
21461
21620
  var touchTrigger = options.touchTrigger;
21462
21621
 
21463
- if (targetGroup && !targetGroup.active) {
21622
+ if (targetGroup) {
21464
21623
  selectGroup(targetGroup,false);
21465
- enterActiveGroup(targetGroup);
21466
21624
  RED.view.redraw();
21467
21625
  }
21468
21626
 
@@ -21527,7 +21685,7 @@ RED.view = (function() {
21527
21685
  }
21528
21686
  hideDragLines();
21529
21687
  }
21530
- if (spliceLink || spliceMultipleLinks) {
21688
+ if (linkToSplice || spliceMultipleLinks) {
21531
21689
  filter = {
21532
21690
  input:true,
21533
21691
  output:true,
@@ -21611,7 +21769,8 @@ RED.view = (function() {
21611
21769
  w: 0, h: 0,
21612
21770
  outputs: 1,
21613
21771
  inputs: 1,
21614
- dirty: true
21772
+ dirty: true,
21773
+ moved: true
21615
21774
  }
21616
21775
  historyEvent = {
21617
21776
  t:'add',
@@ -21777,24 +21936,9 @@ RED.view = (function() {
21777
21936
  }
21778
21937
  }
21779
21938
 
21780
- if (spliceLink) {
21939
+ if (linkToSplice) {
21781
21940
  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];
21941
+ spliceLink(linkToSplice, nn, historyEvent)
21798
21942
  }
21799
21943
  RED.history.push(historyEvent);
21800
21944
  RED.nodes.dirty(true);
@@ -21803,7 +21947,6 @@ RED.view = (function() {
21803
21947
  nn.selected = true;
21804
21948
  if (targetGroup) {
21805
21949
  selectGroup(targetGroup,false);
21806
- enterActiveGroup(targetGroup);
21807
21950
  }
21808
21951
  movingSet.add(nn);
21809
21952
  updateActiveNodes();
@@ -22018,16 +22161,11 @@ RED.view = (function() {
22018
22161
  if ((d > 3 && !dblClickPrimed) || (dblClickPrimed && d > 10)) {
22019
22162
  clickElapsed = 0;
22020
22163
  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;
22164
+ if (mousedown_node) {
22165
+ movingSet.makePrimary(mousedown_node)
22030
22166
  }
22167
+ mouse_mode = RED.state.MOVING_ACTIVE;
22168
+ startSelectionMove()
22031
22169
  }
22032
22170
  }
22033
22171
  } else if (mouse_mode == RED.state.MOVING_ACTIVE || mouse_mode == RED.state.IMPORT_DRAGGING || mouse_mode == RED.state.DETACHED_DRAGGING) {
@@ -22042,6 +22180,7 @@ RED.view = (function() {
22042
22180
  node.n.ox = node.n.x;
22043
22181
  node.n.oy = node.n.y;
22044
22182
  }
22183
+ node.n._detachFromGroup = d3.event.altKey
22045
22184
  node.n.x = mousePos[0]+node.dx;
22046
22185
  node.n.y = mousePos[1]+node.dy;
22047
22186
  node.n.dirty = true;
@@ -22110,9 +22249,8 @@ RED.view = (function() {
22110
22249
  }
22111
22250
  }
22112
22251
 
22113
- // Check link splice or group-add
22252
+ // Check link splice
22114
22253
  if (movingSet.length() === 1 && movingSet.get(0).n.type !== "group") {
22115
- //}{//NIS
22116
22254
  node = movingSet.get(0);
22117
22255
  if (spliceActive) {
22118
22256
  if (!spliceTimer) {
@@ -22160,23 +22298,39 @@ RED.view = (function() {
22160
22298
  },100);
22161
22299
  }
22162
22300
  }
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;
22301
+ }
22302
+ // Check merge into group
22303
+ if (groupAddActive) {
22304
+ if (!groupHoverTimer) {
22305
+ const isDetachFromGroup = d3.event.altKey
22306
+ groupHoverTimer = setTimeout(function() {
22307
+ node = movingSet.get(0);
22308
+ const hoveredGroup = getGroupAt(mousePos[0],mousePos[1], true);
22309
+ if (hoveredGroup !== activeHoverGroup) {
22310
+ if (activeHoverGroup) {
22311
+ activeHoverGroup.hovered = false
22312
+ activeHoverGroup.dirty = true
22313
+ }
22314
+ activeHoverGroup = hoveredGroup
22315
+ }
22316
+ if (activeHoverGroup && groupAddParentGroup && !isDetachFromGroup) {
22317
+ if (groupAddParentGroup === activeHoverGroup.id) {
22318
+ activeHoverGroup = null
22319
+ } else {
22320
+ const nodeGroup = RED.nodes.group(groupAddParentGroup)
22321
+ // This node is already in a group. It should only be draggable
22322
+ // into a group that is a child of the group its in
22323
+ if (!RED.group.contains(nodeGroup, activeHoverGroup)) {
22324
+ activeHoverGroup = null
22175
22325
  }
22176
22326
  }
22177
- groupHoverTimer = null;
22178
- },50);
22179
- }
22327
+ }
22328
+ if (activeHoverGroup) {
22329
+ activeHoverGroup.hovered = true
22330
+ activeHoverGroup.dirty = true
22331
+ }
22332
+ groupHoverTimer = null;
22333
+ }, 50);
22180
22334
  }
22181
22335
  }
22182
22336
 
@@ -22230,6 +22384,16 @@ RED.view = (function() {
22230
22384
  };
22231
22385
  RED.history.push(historyEvent);
22232
22386
  RED.nodes.dirty(true);
22387
+ } else {
22388
+ // Trigger quick add dialog
22389
+ d3.event.stopPropagation();
22390
+ clearSelection();
22391
+ const point = d3.mouse(this);
22392
+ var clickedGroup = getGroupAt(point[0], point[1]);
22393
+ if (drag_lines.length > 0) {
22394
+ clickedGroup = clickedGroup || RED.nodes.group(drag_lines[0].node.g)
22395
+ }
22396
+ showQuickAddDialog({ position: point, group: clickedGroup });
22233
22397
  }
22234
22398
  hideDragLines();
22235
22399
  }
@@ -22238,56 +22402,32 @@ RED.view = (function() {
22238
22402
  var y = parseInt(lasso.attr("y"));
22239
22403
  var x2 = x+parseInt(lasso.attr("width"));
22240
22404
  var y2 = y+parseInt(lasso.attr("height"));
22241
- var ag = activeGroup;
22242
22405
  if (!d3.event.shiftKey) {
22243
22406
  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
22407
  }
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
- }
22408
+
22409
+ activeGroups.forEach(function(n) {
22410
+ if (!movingSet.has(n) && !n.selected) {
22411
+ // group entirely within lasso
22412
+ if (n.x > x && n.y > y && n.x + n.w < x2 && n.y + n.h < y2) {
22413
+ n.selected = true
22414
+ n.dirty = true
22415
+ var groupNodes = RED.group.getNodes(n,true);
22416
+ groupNodes.forEach(gn => movingSet.add(gn))
22264
22417
  }
22265
22418
  }
22266
22419
  })
22267
-
22268
22420
  activeNodes.forEach(function(n) {
22269
- if (!n.selected) {
22421
+ if (!movingSet.has(n) && !n.selected) {
22270
22422
  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
- }
22423
+ n.selected = true;
22424
+ n.dirty = true;
22425
+ movingSet.add(n);
22286
22426
  }
22287
22427
  }
22288
22428
  });
22289
22429
  activeJunctions.forEach(function(n) {
22290
- if (!n.selected) {
22430
+ if (!movingSet.has(n) && !n.selected) {
22291
22431
  if (n.x > x && n.x < x2 && n.y > y && n.y < y2) {
22292
22432
  n.selected = true;
22293
22433
  n.dirty = true;
@@ -22310,17 +22450,6 @@ RED.view = (function() {
22310
22450
  }
22311
22451
  })
22312
22452
 
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
22453
  if (activeSubflow) {
22325
22454
  activeSubflow.in.forEach(function(n) {
22326
22455
  n.selected = (n.x > x && n.x < x2 && n.y > y && n.y < y2);
@@ -22345,6 +22474,7 @@ RED.view = (function() {
22345
22474
  }
22346
22475
  }
22347
22476
  updateSelection();
22477
+ outer.classed('red-ui-workspace-lasso-active', false)
22348
22478
  lasso.remove();
22349
22479
  lasso = null;
22350
22480
  } else if (mouse_mode == RED.state.DEFAULT && mousedown_link == null && !d3.event.ctrlKey && !d3.event.metaKey ) {
@@ -22359,210 +22489,80 @@ RED.view = (function() {
22359
22489
  RED.actions.invoke("core:split-wires-with-junctions")
22360
22490
  slicePath.remove();
22361
22491
  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
22492
  }
22472
22493
  if (mouse_mode == RED.state.MOVING_ACTIVE) {
22473
22494
  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;
22495
+ historyEvent = { t: 'multi', events: [] }
22495
22496
 
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;
22497
+ // Check to see if we're dropping into a group
22498
+ const {
22499
+ addedToGroup,
22500
+ removedFromGroup,
22501
+ groupMoveEvent,
22502
+ rehomedNodes
22503
+ } = addMovingSetToGroup()
22504
+
22505
+ if (groupMoveEvent) {
22506
+ historyEvent.events.push(groupMoveEvent)
22501
22507
  }
22502
22508
 
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});
22509
+ // Create two lists of nodes:
22510
+ // - nodes that have moved without changing group
22511
+ // - nodes that have moved AND changed group
22512
+ const moveEvent = {
22513
+ t: 'move',
22514
+ nodes: [],
22515
+ dirty: RED.nodes.dirty()
22516
+ }
22517
+ const moveAndChangedGroupEvent = {
22518
+ t: 'move',
22519
+ nodes: [],
22520
+ dirty: RED.nodes.dirty(),
22521
+ addToGroup: addedToGroup,
22522
+ removeFromGroup: removedFromGroup
22523
+ }
22524
+ for (let j = 0; j < movingSet.length(); j++) {
22525
+ const n = movingSet.get(j);
22526
+ delete n.n._detachFromGroup
22527
+ if (n.ox !== n.n.x || n.oy !== n.n.y || addedToGroup) {
22528
+ // This node has moved or added to a group
22529
+ if (rehomedNodes.has(n)) {
22530
+ moveAndChangedGroupEvent.nodes.push({...n})
22531
+ } else {
22532
+ moveEvent.nodes.push({...n})
22533
+ }
22508
22534
  n.n.dirty = true;
22509
22535
  n.n.moved = true;
22510
22536
  }
22511
22537
  }
22512
22538
 
22513
- if (ns.length > 0 && mouse_mode == RED.state.MOVING_ACTIVE) {
22514
- historyEvent = {t:"move",nodes:ns,dirty:RED.nodes.dirty()};
22539
+ // Check to see if we need to splice a link
22540
+ if (moveEvent.nodes.length > 0) {
22541
+ historyEvent.events.push(moveEvent)
22515
22542
  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;
22543
+ var linkToSplice = d3.select(activeSpliceLink).data()[0];
22544
+ spliceLink(linkToSplice, movingSet.get(0).n, moveEvent)
22537
22545
  }
22546
+ }
22547
+ if (moveAndChangedGroupEvent.nodes.length > 0) {
22548
+ historyEvent.events.push(moveAndChangedGroupEvent)
22549
+ }
22550
+
22551
+ // Only continue if something has moved
22552
+ if (historyEvent.events.length > 0) {
22538
22553
  RED.nodes.dirty(true);
22539
- if (moveEvent) {
22540
- historyEvent = {
22541
- t: "multi",
22542
- events: [moveEvent, historyEvent]
22543
- };
22554
+ if (historyEvent.events.length === 1) {
22555
+ // Keep history tidy - no need for multi-event
22556
+ RED.history.push(historyEvent.events[0]);
22557
+ } else {
22558
+ // Multiple events - push the whole lot as one
22559
+ RED.history.push(historyEvent);
22544
22560
  }
22545
- RED.history.push(historyEvent);
22561
+ updateActiveNodes();
22546
22562
  }
22547
22563
  }
22548
22564
  }
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
22565
  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
22566
  if (mouse_mode === RED.state.DETACHED_DRAGGING) {
22567
22567
  var ns = [];
22568
22568
  for (var j=0;j<movingSet.length();j++) {
@@ -22600,6 +22600,95 @@ RED.view = (function() {
22600
22600
  redraw();
22601
22601
  }
22602
22602
 
22603
+
22604
+ function spliceLink(link, node, historyEvent) {
22605
+ RED.nodes.removeLink(link);
22606
+ const link1 = {
22607
+ source: link.source,
22608
+ sourcePort: link.sourcePort,
22609
+ target: node
22610
+ };
22611
+ const link2 = {
22612
+ source: node,
22613
+ sourcePort: 0,
22614
+ target: link.target
22615
+ };
22616
+ RED.nodes.addLink(link1);
22617
+ RED.nodes.addLink(link2);
22618
+
22619
+ historyEvent.links = (historyEvent.links || []).concat([link1,link2]);
22620
+ historyEvent.removedLinks = [link];
22621
+ }
22622
+
22623
+ function addMovingSetToGroup() {
22624
+
22625
+ const isDetachFromGroup = groupAddParentGroup && d3.event.altKey
22626
+
22627
+ let addedToGroup = null;
22628
+ let removedFromGroup = null;
22629
+ let groupMoveEvent = null;
22630
+ let rehomedNodes = new Set()
22631
+
22632
+ if (activeHoverGroup) {
22633
+ // Nodes are being dropped into a group. We have to assume at
22634
+ // this point that everything in the movingSet is valid for adding
22635
+ // to this group. But it could be a mix of nodes and existing groups.
22636
+ // In which case, we don't want to rehome all of the nodes inside
22637
+ // existing groups - we just want to rehome the top level objects.
22638
+ var oldX = activeHoverGroup.x;
22639
+ var oldY = activeHoverGroup.y;
22640
+ if (groupAddParentGroup) {
22641
+ removedFromGroup = RED.nodes.group(groupAddParentGroup)
22642
+ }
22643
+ // Second pass - now we know what to move, we can move it
22644
+ for (let j=0;j<movingSet.length();j++) {
22645
+ const n = movingSet.get(j)
22646
+ if (!n.n.g || (removedFromGroup && n.n.g === removedFromGroup.id)) {
22647
+ rehomedNodes.add(n)
22648
+ RED.group.addToGroup(activeHoverGroup, n.n);
22649
+ }
22650
+ }
22651
+ if ((activeHoverGroup.x !== oldX) ||
22652
+ (activeHoverGroup.y !== oldY)) {
22653
+ groupMoveEvent = {
22654
+ t: "move",
22655
+ nodes: [{n: activeHoverGroup,
22656
+ ox: oldX, oy: oldY,
22657
+ dx: activeHoverGroup.x -oldX,
22658
+ dy: activeHoverGroup.y -oldY}],
22659
+ dirty: true
22660
+ };
22661
+ }
22662
+ addedToGroup = activeHoverGroup;
22663
+ activeHoverGroup.hovered = false;
22664
+ activeHoverGroup = null;
22665
+ } else if (isDetachFromGroup) {
22666
+ // The nodes are being removed from their group
22667
+ removedFromGroup = RED.nodes.group(groupAddParentGroup)
22668
+ for (let j=0;j<movingSet.length();j++) {
22669
+ const n = movingSet.get(j)
22670
+ if (n.n.g && n.n.g === removedFromGroup.id) {
22671
+ rehomedNodes.add(n)
22672
+ RED.group.removeFromGroup(removedFromGroup, n.n);
22673
+ }
22674
+ }
22675
+ }
22676
+ activeGroups.forEach(g => {
22677
+ if (g.hovered) {
22678
+ g.hovered = false
22679
+ g.dirty = true
22680
+ }
22681
+ })
22682
+
22683
+ return {
22684
+ addedToGroup,
22685
+ removedFromGroup,
22686
+ groupMoveEvent,
22687
+ rehomedNodes
22688
+ }
22689
+
22690
+ }
22691
+
22603
22692
  function zoomIn() {
22604
22693
  if (scaleFactor < 2) {
22605
22694
  zoomView(scaleFactor+0.1);
@@ -22658,10 +22747,9 @@ RED.view = (function() {
22658
22747
  }
22659
22748
  clearSelection();
22660
22749
  } else if (lasso) {
22750
+ outer.classed('red-ui-workspace-lasso-active', false)
22661
22751
  lasso.remove();
22662
22752
  lasso = null;
22663
- } else if (activeGroup) {
22664
- exitActiveGroup()
22665
22753
  } else {
22666
22754
  clearSelection();
22667
22755
  }
@@ -22672,82 +22760,61 @@ RED.view = (function() {
22672
22760
  return;
22673
22761
  }
22674
22762
  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;
22763
+ clearSelection();
22764
+ activeGroups.forEach(function(g) {
22765
+ if (!g.g) {
22766
+ selectGroup(g, true);
22767
+ if (!g.selected) {
22768
+ g.selected = true;
22705
22769
  g.dirty = true;
22706
22770
  }
22707
- })
22771
+ } else {
22772
+ g.selected = false;
22773
+ g.dirty = true;
22774
+ }
22775
+ })
22708
22776
 
22709
- activeNodes.forEach(function(n) {
22710
- if (mouse_mode === RED.state.SELECTING_NODE) {
22711
- if (selectNodesOptions.filter && !selectNodesOptions.filter(n)) {
22712
- return;
22713
- }
22777
+ activeNodes.forEach(function(n) {
22778
+ if (mouse_mode === RED.state.SELECTING_NODE) {
22779
+ if (selectNodesOptions.filter && !selectNodesOptions.filter(n)) {
22780
+ return;
22714
22781
  }
22715
- if (!n.g && !n.selected) {
22782
+ }
22783
+ if (!n.g && !n.selected) {
22784
+ n.selected = true;
22785
+ n.dirty = true;
22786
+ movingSet.add(n);
22787
+ }
22788
+ });
22789
+
22790
+ activeJunctions.forEach(function(n) {
22791
+ if (!n.selected) {
22792
+ n.selected = true;
22793
+ n.dirty = true;
22794
+ movingSet.add(n);
22795
+ }
22796
+ })
22797
+
22798
+ if (mouse_mode !== RED.state.SELECTING_NODE && activeSubflow) {
22799
+ activeSubflow.in.forEach(function(n) {
22800
+ if (!n.selected) {
22716
22801
  n.selected = true;
22717
22802
  n.dirty = true;
22718
22803
  movingSet.add(n);
22719
22804
  }
22720
22805
  });
22721
-
22722
- activeJunctions.forEach(function(n) {
22806
+ activeSubflow.out.forEach(function(n) {
22723
22807
  if (!n.selected) {
22724
22808
  n.selected = true;
22725
22809
  n.dirty = true;
22726
22810
  movingSet.add(n);
22727
22811
  }
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
- }
22812
+ });
22813
+ if (activeSubflow.status) {
22814
+ if (!activeSubflow.status.selected) {
22815
+ activeSubflow.status.selected = true;
22816
+ activeSubflow.status.dirty = true;
22817
+ movingSet.add(activeSubflow.status);
22751
22818
  }
22752
22819
  }
22753
22820
  }
@@ -22766,11 +22833,6 @@ RED.view = (function() {
22766
22833
  }
22767
22834
  movingSet.clear();
22768
22835
  selectedLinks.clear();
22769
- if (activeGroup) {
22770
- activeGroup.active = false
22771
- activeGroup.dirty = true;
22772
- activeGroup = null;
22773
- }
22774
22836
  activeGroups.forEach(function(g) {
22775
22837
  g.selected = false;
22776
22838
  g.dirty = true;
@@ -22893,7 +22955,7 @@ RED.view = (function() {
22893
22955
  }
22894
22956
 
22895
22957
  function editSelection() {
22896
- if (RED.workspaces.isActiveLocked()) { return }
22958
+ if (RED.workspaces.isLocked()) { return }
22897
22959
  if (movingSet.length() > 0) {
22898
22960
  var node = movingSet.get(0).n;
22899
22961
  if (node.type === "subflow") {
@@ -23227,7 +23289,7 @@ RED.view = (function() {
23227
23289
 
23228
23290
 
23229
23291
  function detachSelectedNodes() {
23230
- if (RED.workspaces.isActiveLocked()) { return }
23292
+ if (RED.workspaces.isLocked()) { return }
23231
23293
  var selection = RED.view.selection();
23232
23294
  if (selection.nodes) {
23233
23295
  const {newLinks, removedLinks} = RED.nodes.detachNodes(selection.nodes);
@@ -23330,6 +23392,7 @@ RED.view = (function() {
23330
23392
  mousedown_port_type = null;
23331
23393
  activeSpliceLink = null;
23332
23394
  spliceActive = false;
23395
+ groupAddActive = false;
23333
23396
  if (activeHoverGroup) {
23334
23397
  activeHoverGroup.hovered = false;
23335
23398
  activeHoverGroup = null;
@@ -23532,8 +23595,25 @@ RED.view = (function() {
23532
23595
  (drag_line.portType === PORT_TYPE_INPUT && mouseup_node.type === "subflow" && (mouseup_node.direction === "status" || mouseup_node.direction === "out")) ||
23533
23596
  (drag_line.portType === PORT_TYPE_OUTPUT && mouseup_node.type === "subflow" && mouseup_node.direction === "in")
23534
23597
  )) {
23598
+ let hasJunctionLoop = false
23599
+ if (link.source.type === 'junction' && link.target.type === 'junction') {
23600
+ // This is joining two junctions together. We want to avoid creating a loop
23601
+ // of pure junction nodes as there is no way to break out of it.
23602
+
23603
+ const visited = new Set()
23604
+ let toVisit = [link.target]
23605
+ while (toVisit.length > 0) {
23606
+ const next = toVisit.shift()
23607
+ if (next === link.source) {
23608
+ hasJunctionLoop = true
23609
+ break
23610
+ }
23611
+ visited.add(next)
23612
+ toVisit = toVisit.concat(RED.nodes.getDownstreamNodes(next).filter(n => n.type === 'junction' && !visited.has(n)))
23613
+ }
23614
+ }
23535
23615
  var existingLink = RED.nodes.filterLinks({source:src,target:dst,sourcePort: src_port}).length !== 0;
23536
- if (!existingLink) {
23616
+ if (!hasJunctionLoop && !existingLink) {
23537
23617
  RED.nodes.addLink(link);
23538
23618
  addedLinks.push(link);
23539
23619
  }
@@ -23651,7 +23731,7 @@ RED.view = (function() {
23651
23731
  console.log("Definition error: "+node.type+"."+((portType === PORT_TYPE_INPUT)?"inputLabels":"outputLabels"),err);
23652
23732
  result = null;
23653
23733
  }
23654
- } else if ($.isArray(portLabels)) {
23734
+ } else if (Array.isArray(portLabels)) {
23655
23735
  result = portLabels[portIndex];
23656
23736
  }
23657
23737
  return result;
@@ -23809,7 +23889,7 @@ RED.view = (function() {
23809
23889
  }
23810
23890
  if (dblClickPrimed && mousedown_node == d && clickElapsed > 0 && clickElapsed < dblClickInterval) {
23811
23891
  mouse_mode = RED.state.DEFAULT;
23812
- if (RED.workspaces.isActiveLocked()) {
23892
+ if (RED.workspaces.isLocked()) {
23813
23893
  clickElapsed = 0;
23814
23894
  d3.event.stopPropagation();
23815
23895
  return
@@ -23836,7 +23916,6 @@ RED.view = (function() {
23836
23916
  clearSelection();
23837
23917
 
23838
23918
  selectGroup(RED.nodes.group(d.g), false);
23839
- enterActiveGroup(RED.nodes.group(d.g))
23840
23919
 
23841
23920
  mousedown_node.selected = true;
23842
23921
  movingSet.add(mousedown_node);
@@ -23889,57 +23968,25 @@ RED.view = (function() {
23889
23968
  //RED.touch.radialMenu.show(d3.select(this),pos);
23890
23969
  if (mouse_mode == RED.state.IMPORT_DRAGGING || mouse_mode == RED.state.DETACHED_DRAGGING) {
23891
23970
  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);
23971
+ // Check to see if we're dropping into a group
23972
+ const {
23973
+ addedToGroup,
23974
+ removedFromGroup,
23975
+ groupMoveEvent,
23976
+ rehomedNodes
23977
+ } = addMovingSetToGroup()
23908
23978
 
23909
- historyEvent.links = [link1,link2];
23910
- historyEvent.removedLinks = [spliceLink];
23979
+ if (activeSpliceLink) {
23980
+ var linkToSplice = d3.select(activeSpliceLink).data()[0];
23981
+ spliceLink(linkToSplice, movingSet.get(0).n, historyEvent)
23911
23982
  updateActiveNodes();
23912
23983
  }
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
23984
  if (mouse_mode == RED.state.DETACHED_DRAGGING) {
23942
- var ns = [];
23985
+ // Create two lists of nodes:
23986
+ // - nodes that have moved without changing group
23987
+ // - nodes that have moved AND changed group
23988
+ const ns = [];
23989
+ const rehomedNodeList = [];
23943
23990
  for (var j=0;j<movingSet.length();j++) {
23944
23991
  var n = movingSet.get(j);
23945
23992
  if (n.ox !== n.n.x || n.oy !== n.n.y) {
@@ -23948,14 +23995,20 @@ RED.view = (function() {
23948
23995
  n.n.moved = true;
23949
23996
  }
23950
23997
  }
23951
- var event = {t:"multi",events:[historyEvent,{t:"move",nodes:ns}],dirty: historyEvent.dirty};
23952
- if (moveEvent) {
23953
- event.events.push(moveEvent);
23998
+ var event = {
23999
+ t: "multi",
24000
+ events: [
24001
+ historyEvent,
24002
+ { t: "move", nodes: ns }
24003
+ ],
24004
+ dirty: historyEvent.dirty
24005
+ };
24006
+ if (groupMoveEvent) {
24007
+ event.events.push(groupMoveEvent);
23954
24008
  }
23955
24009
  RED.history.replace(event)
23956
- }
23957
- else if(moveEvent) {
23958
- var event = {t:"multi", events:[historyEvent, moveEvent], dirty: true};
24010
+ } else if (groupMoveEvent) {
24011
+ var event = { t:"multi", events: [historyEvent, groupMoveEvent], dirty: true};
23959
24012
  RED.history.replace(event);
23960
24013
  }
23961
24014
 
@@ -24008,126 +24061,11 @@ RED.view = (function() {
24008
24061
  clickElapsed < dblClickInterval &&
24009
24062
  d.type !== 'junction'
24010
24063
  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)) {
24064
+
24065
+ if (d.selected && (d3.event.ctrlKey||d3.event.metaKey)) {
24113
24066
  mousedown_node.selected = false;
24114
24067
  movingSet.remove(mousedown_node);
24115
24068
  } 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
24069
  if (d3.event.shiftKey) {
24132
24070
  if (!(d3.event.ctrlKey||d3.event.metaKey)) {
24133
24071
  clearSelection();
@@ -24153,8 +24091,6 @@ RED.view = (function() {
24153
24091
  } else if (!d.selected) {
24154
24092
  if (!d3.event.ctrlKey && !d3.event.metaKey) {
24155
24093
  clearSelection();
24156
- } else {
24157
- exitActiveGroup();
24158
24094
  }
24159
24095
  mousedown_node.selected = true;
24160
24096
  movingSet.add(mousedown_node);
@@ -24353,7 +24289,7 @@ RED.view = (function() {
24353
24289
  if (RED.view.DEBUG) {
24354
24290
  console.warn("groupMouseUp", { mouse_mode, event: d3.event });
24355
24291
  }
24356
- if (RED.workspaces.isActiveLocked()) { return }
24292
+ if (RED.workspaces.isLocked()) { return }
24357
24293
  if (dblClickPrimed && mousedown_group == g && clickElapsed > 0 && clickElapsed < dblClickInterval) {
24358
24294
  mouse_mode = RED.state.DEFAULT;
24359
24295
  RED.editor.editGroup(g);
@@ -24379,7 +24315,6 @@ RED.view = (function() {
24379
24315
  }
24380
24316
 
24381
24317
  if (mouse_mode == RED.state.QUICK_JOINING) {
24382
- d3.event.stopPropagation();
24383
24318
  return;
24384
24319
  } else if (mouse_mode === RED.state.SELECTING_NODE) {
24385
24320
  d3.event.stopPropagation();
@@ -24401,34 +24336,16 @@ RED.view = (function() {
24401
24336
  lastClickNode = g;
24402
24337
 
24403
24338
  if (g.selected && (d3.event.ctrlKey||d3.event.metaKey)) {
24404
- if (g === activeGroup) {
24405
- exitActiveGroup();
24406
- }
24407
24339
  deselectGroup(g);
24408
24340
  d3.event.stopPropagation();
24409
24341
  } else {
24410
24342
  if (!g.selected) {
24411
24343
  if (!d3.event.ctrlKey && !d3.event.metaKey) {
24412
- var ag = activeGroup;
24413
24344
  clearSelection();
24414
- if (ag && g.g === ag.id) {
24415
- enterActiveGroup(ag);
24416
- activeGroup.selected = true;
24417
- }
24418
- }
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
24345
  }
24426
24346
  selectGroup(g,true);//!wasSelected);
24427
- } else if (activeGroup && g.g !== activeGroup.id){
24428
- exitActiveGroup();
24429
24347
  }
24430
24348
 
24431
-
24432
24349
  if (d3.event.button != 2) {
24433
24350
  var d = g.nodes[0];
24434
24351
  prepareDrag(mouse);
@@ -24456,51 +24373,42 @@ RED.view = (function() {
24456
24373
  allNodes.forEach(function(n) {
24457
24374
  if (!currentSet.has(n)) {
24458
24375
  movingSet.add(n)
24459
- // n.selected = true;
24460
24376
  }
24461
24377
  n.dirty = true;
24462
24378
  })
24463
24379
  }
24380
+ selectedLinks.clearUnselected()
24464
24381
  }
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
- }
24382
+
24483
24383
  function deselectGroup(g) {
24484
24384
  if (g.selected) {
24485
24385
  g.selected = false;
24486
24386
  g.dirty = true;
24487
24387
  }
24488
- var nodeSet = new Set(g.nodes);
24388
+ const allNodes = RED.group.getNodes(g,true);
24389
+ const nodeSet = new Set(allNodes);
24489
24390
  nodeSet.add(g);
24490
- for (var i = movingSet.length()-1; i >= 0; i -= 1) {
24491
- var msn = movingSet.get(i);
24391
+ for (let i = movingSet.length()-1; i >= 0; i -= 1) {
24392
+ const msn = movingSet.get(i);
24492
24393
  if (nodeSet.has(msn.n) || msn.n === g) {
24493
24394
  msn.n.selected = false;
24494
24395
  msn.n.dirty = true;
24495
24396
  movingSet.remove(msn.n,i)
24496
24397
  }
24497
24398
  }
24399
+ selectedLinks.clearUnselected()
24498
24400
  }
24499
- function getGroupAt(x,y) {
24401
+ function getGroupAt(x, y, ignoreSelected) {
24500
24402
  // x,y expected to be in node-co-ordinate space
24501
24403
  var candidateGroups = {};
24502
24404
  for (var i=0;i<activeGroups.length;i++) {
24503
24405
  var g = activeGroups[i];
24406
+ if (ignoreSelected && movingSet.has(g)) {
24407
+ // When ignoreSelected is set, do not match any group in the
24408
+ // current movingSet. This is used when dragging a selection
24409
+ // to find a candidate group for adding the selection to
24410
+ continue
24411
+ }
24504
24412
  if (x >= g.x && x <= g.x + g.w && y >= g.y && y <= g.y + g.h) {
24505
24413
  candidateGroups[g.id] = g;
24506
24414
  }
@@ -24577,7 +24485,7 @@ RED.view = (function() {
24577
24485
  function showTouchMenu(obj,pos) {
24578
24486
  var mdn = mousedown_node;
24579
24487
  var options = [];
24580
- const isActiveLocked = RED.workspaces.isActiveLocked()
24488
+ const isActiveLocked = RED.workspaces.isLocked()
24581
24489
  options.push({name:"delete",disabled:(isActiveLocked || movingSet.length()===0 && selectedLinks.length() === 0),onselect:function() {deleteSelection();}});
24582
24490
  options.push({name:"cut",disabled:(isActiveLocked || movingSet.length()===0),onselect:function() {copySelection(true);deleteSelection();}});
24583
24491
  options.push({name:"copy",disabled:(isActiveLocked || movingSet.length()===0),onselect:function() {copySelection();}});
@@ -25117,6 +25025,7 @@ RED.view = (function() {
25117
25025
  nodesReordered = true;
25118
25026
  delete d._reordered;
25119
25027
  }
25028
+
25120
25029
  if (d.dirty) {
25121
25030
  var self = this;
25122
25031
  var thisNode = d3.select(this);
@@ -25770,23 +25679,30 @@ RED.view = (function() {
25770
25679
  g.attr("id",d.id);
25771
25680
 
25772
25681
  var groupBorderRadius = 4;
25773
-
25682
+ var groupOutlineBorderRadius = 6
25774
25683
  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)
25684
+ const groupBackground = selectGroup.append('rect')
25685
+ .classed("red-ui-flow-group-outline-select",true)
25776
25686
  .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();});
25687
+ .attr('rx',groupOutlineBorderRadius).attr('ry',groupOutlineBorderRadius)
25688
+ .attr("x",-3)
25689
+ .attr("y",-3);
25690
+ selectGroup.append('rect')
25691
+ .classed("red-ui-flow-group-outline-select",true)
25692
+ .classed("red-ui-flow-group-outline-select-outline",true)
25693
+ .attr('rx',groupOutlineBorderRadius).attr('ry',groupOutlineBorderRadius)
25694
+ .attr("x",-3)
25695
+ .attr("y",-3)
25696
+ selectGroup.append('rect')
25697
+ .classed("red-ui-flow-group-outline-select",true)
25698
+ .classed("red-ui-flow-group-outline-select-line",true)
25699
+ .attr('rx',groupOutlineBorderRadius).attr('ry',groupOutlineBorderRadius)
25700
+ .attr("x",-3)
25701
+ .attr("y",-3)
25702
+ groupBackground.on("mousedown", function() {groupMouseDown.call(g[0][0],d)});
25703
+ groupBackground.on("mouseup", function() {groupMouseUp.call(g[0][0],d)});
25704
+ groupBackground.on("touchstart", function() {groupMouseDown.call(g[0][0],d); d3.event.preventDefault();});
25705
+ groupBackground.on("touchend", function() {groupMouseUp.call(g[0][0],d); d3.event.preventDefault();});
25790
25706
 
25791
25707
  g.append('rect').classed("red-ui-flow-group-outline",true).attr('rx',0.5).attr('ry',0.5);
25792
25708
 
@@ -25804,11 +25720,7 @@ RED.view = (function() {
25804
25720
  });
25805
25721
  if (addedGroups) {
25806
25722
  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
- }
25723
+ return a._order - b._order
25812
25724
  })
25813
25725
  }
25814
25726
  group[0].reverse();
@@ -25833,6 +25745,11 @@ RED.view = (function() {
25833
25745
  var margin = 26;
25834
25746
  d.nodes.forEach(function(n) {
25835
25747
  groupOpCount++
25748
+ if (n._detachFromGroup) {
25749
+ // Do not include this node when recalulating
25750
+ // the group dimensions
25751
+ return
25752
+ }
25836
25753
  if (n.type !== "group") {
25837
25754
  minX = Math.min(minX,n.x-n.w/2-margin-((n._def.button && n._def.align!=="right")?20:0));
25838
25755
  minY = Math.min(minY,n.y-n.h/2-margin);
@@ -25845,11 +25762,12 @@ RED.view = (function() {
25845
25762
  maxY = Math.max(maxY,n.y+n.h+margin)
25846
25763
  }
25847
25764
  });
25848
-
25849
- d.x = minX;
25850
- d.y = minY;
25851
- d.w = maxX - minX;
25852
- d.h = maxY - minY;
25765
+ if (minX !== Number.POSITIVE_INFINITY && minY !== Number.POSITIVE_INFINITY) {
25766
+ d.x = minX;
25767
+ d.y = minY;
25768
+ d.w = maxX - minX;
25769
+ d.h = maxY - minY;
25770
+ }
25853
25771
  recalculateLabelOffsets = true;
25854
25772
  // if set explicitly to false, this group has just been
25855
25773
  // imported so needed this initial resize calculation.
@@ -25906,16 +25824,25 @@ RED.view = (function() {
25906
25824
  } else {
25907
25825
  selectGroup.classList.remove("red-ui-flow-group-hovered")
25908
25826
  }
25827
+ if (d.selected) {
25828
+ selectGroup.classList.add("red-ui-flow-group-selected")
25829
+ } else {
25830
+ selectGroup.classList.remove("red-ui-flow-group-selected")
25831
+ }
25909
25832
  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":"";
25833
+ // Background
25834
+ selectGroupRect.setAttribute("width",d.w+6)
25835
+ selectGroupRect.setAttribute("height",d.h+6)
25836
+ // Outline
25914
25837
  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":"";
25838
+ selectGroupRect.setAttribute("width",d.w+6)
25839
+ selectGroupRect.setAttribute("height",d.h+6)
25840
+ selectGroupRect.style.strokeOpacity = (d.selected || d.highlighted)?0.8:0;
25841
+ // Line
25842
+ selectGroupRect = selectGroup.children[2];
25843
+ selectGroupRect.setAttribute("width",d.w+6)
25844
+ selectGroupRect.setAttribute("height",d.h+6)
25845
+ selectGroupRect.style.strokeOpacity = (d.selected || d.highlighted)?0.8:0;
25919
25846
 
25920
25847
  if (d.highlighted) {
25921
25848
  selectGroup.classList.add("red-ui-flow-node-highlighted");
@@ -26043,7 +25970,7 @@ RED.view = (function() {
26043
25970
  if (mouse_mode === RED.state.SELECTING_NODE) {
26044
25971
  return;
26045
25972
  }
26046
-
25973
+ const wasDirty = RED.nodes.dirty()
26047
25974
  var nodesToImport;
26048
25975
  if (typeof newNodesObj === "string") {
26049
25976
  if (newNodesObj === "") {
@@ -26060,7 +25987,7 @@ RED.view = (function() {
26060
25987
  nodesToImport = newNodesObj;
26061
25988
  }
26062
25989
 
26063
- if (!$.isArray(nodesToImport)) {
25990
+ if (!Array.isArray(nodesToImport)) {
26064
25991
  nodesToImport = [nodesToImport];
26065
25992
  }
26066
25993
  if (options.generateDefaultNames) {
@@ -26093,7 +26020,12 @@ RED.view = (function() {
26093
26020
  return (n.type === "global-config");
26094
26021
  });
26095
26022
  }
26096
- var result = RED.nodes.import(filteredNodesToImport,{generateIds:options.generateIds, addFlow: addNewFlow, importMap: options.importMap});
26023
+ var result = RED.nodes.import(filteredNodesToImport,{
26024
+ generateIds: options.generateIds,
26025
+ addFlow: addNewFlow,
26026
+ importMap: options.importMap,
26027
+ markChanged: true
26028
+ });
26097
26029
  if (result) {
26098
26030
  var new_nodes = result.nodes;
26099
26031
  var new_links = result.links;
@@ -26109,7 +26041,7 @@ RED.view = (function() {
26109
26041
  var new_ms = new_nodes.filter(function(n) { return n.hasOwnProperty("x") && n.hasOwnProperty("y") && n.z == RED.workspaces.active() });
26110
26042
  new_ms = new_ms.concat(new_groups.filter(function(g) { return g.z === RED.workspaces.active()}))
26111
26043
  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; });
26044
+ var new_node_ids = new_nodes.map(function(n){ return n.id; });
26113
26045
 
26114
26046
  clearSelection();
26115
26047
  movingSet.clear();
@@ -26175,28 +26107,19 @@ RED.view = (function() {
26175
26107
  }
26176
26108
  if (!touchImport) {
26177
26109
  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
- }
26110
+ startSelectionMove()
26187
26111
  }
26188
-
26189
26112
  }
26190
26113
 
26191
26114
  var historyEvent = {
26192
- t:"add",
26193
- nodes:new_node_ids,
26194
- links:new_links,
26195
- groups:new_groups,
26115
+ t: "add",
26116
+ nodes: new_node_ids,
26117
+ links: new_links,
26118
+ groups: new_groups,
26196
26119
  junctions: new_junctions,
26197
- workspaces:new_workspaces,
26198
- subflows:new_subflows,
26199
- dirty:RED.nodes.dirty()
26120
+ workspaces: new_workspaces,
26121
+ subflows: new_subflows,
26122
+ dirty: wasDirty
26200
26123
  };
26201
26124
  if (movingSet.length() === 0) {
26202
26125
  RED.nodes.dirty(true);
@@ -26205,7 +26128,7 @@ RED.view = (function() {
26205
26128
  var subflowRefresh = RED.subflow.refresh(true);
26206
26129
  if (subflowRefresh) {
26207
26130
  historyEvent.subflow = {
26208
- id:activeSubflow.id,
26131
+ id: activeSubflow.id,
26209
26132
  changed: activeSubflowChanged,
26210
26133
  instances: subflowRefresh.instances
26211
26134
  }
@@ -26323,6 +26246,59 @@ RED.view = (function() {
26323
26246
  }
26324
26247
  }
26325
26248
 
26249
+ function startSelectionMove() {
26250
+ spliceActive = false;
26251
+ if (movingSet.length() === 1) {
26252
+ const node = movingSet.get(0);
26253
+ spliceActive = node.n.hasOwnProperty("_def") &&
26254
+ ((node.n.hasOwnProperty("inputs") && node.n.inputs > 0) || (!node.n.hasOwnProperty("inputs") && node.n._def.inputs > 0)) &&
26255
+ ((node.n.hasOwnProperty("outputs") && node.n.outputs > 0) || (!node.n.hasOwnProperty("outputs") && node.n._def.outputs > 0)) &&
26256
+ RED.nodes.filterLinks({ source: node.n }).length === 0 &&
26257
+ RED.nodes.filterLinks({ target: node.n }).length === 0;
26258
+ }
26259
+ groupAddActive = false
26260
+ groupAddParentGroup = null
26261
+ if (movingSet.length() > 0 && activeGroups) {
26262
+ // movingSet includes the selection AND any nodes inside selected groups
26263
+ // So we cannot simply check the `g` of all nodes match.
26264
+ // Instead, we have to:
26265
+ // - note all groups in movingSet
26266
+ // - note all .g values referenced in movingSet
26267
+ // - then check for g values for groups not in movingSet
26268
+ let isValidSelection = true
26269
+ let hasNullGroup = false
26270
+ const selectedGroups = []
26271
+ const referencedGroups = new Set()
26272
+ movingSet.forEach(n => {
26273
+ if (n.n.type === 'subflow') {
26274
+ isValidSelection = false
26275
+ }
26276
+ if (n.n.type === 'group') {
26277
+ selectedGroups.push(n.n.id)
26278
+ }
26279
+ if (n.n.g) {
26280
+ referencedGroups.add(n.n.g)
26281
+ } else {
26282
+ hasNullGroup = true
26283
+ }
26284
+ })
26285
+ if (isValidSelection) {
26286
+ selectedGroups.forEach(g => referencedGroups.delete(g))
26287
+ // console.log('selectedGroups', selectedGroups)
26288
+ // console.log('referencedGroups',referencedGroups)
26289
+ // console.log('hasNullGroup', hasNullGroup)
26290
+ if (referencedGroups.size === 0) {
26291
+ groupAddActive = true
26292
+ } else if (!hasNullGroup && referencedGroups.size === 1) {
26293
+ groupAddParentGroup = referencedGroups.values().next().value
26294
+ groupAddActive = true
26295
+ }
26296
+ }
26297
+ // console.log('groupAddActive', groupAddActive)
26298
+ // console.log('groupAddParentGroup', groupAddParentGroup)
26299
+ }
26300
+ }
26301
+
26326
26302
  function toggleShowGrid(state) {
26327
26303
  if (state) {
26328
26304
  gridLayer.style("visibility","visible");
@@ -26402,24 +26378,18 @@ RED.view = (function() {
26402
26378
  if (movingSet.length() > 0) {
26403
26379
  movingSet.forEach(function(n) {
26404
26380
  if (n.n.type !== 'group') {
26405
- allNodes.add(n.n);
26381
+ allNodes.add(n.n);
26406
26382
  }
26407
26383
  });
26408
26384
  }
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
- }
26385
+ var selectedGroups = activeGroups.filter(function(g) { return g.selected });
26386
+ selectedGroups.forEach(function(g) {
26387
+ var groupNodes = RED.group.getNodes(g,true);
26388
+ groupNodes.forEach(function(n) {
26389
+ allNodes.delete(n);
26390
+ });
26391
+ allNodes.add(g);
26392
+ });
26423
26393
  if (allNodes.size > 0) {
26424
26394
  selection.nodes = Array.from(allNodes);
26425
26395
  }
@@ -26664,7 +26634,7 @@ RED.view = (function() {
26664
26634
  return result;
26665
26635
  },
26666
26636
  getGroupAtPoint: getGroupAt,
26667
- getActiveGroup: function() { return activeGroup },
26637
+ getActiveGroup: function() { return null },
26668
26638
  reveal: function(id,triggerHighlight) {
26669
26639
  if (RED.nodes.workspace(id) || RED.nodes.subflow(id)) {
26670
26640
  RED.workspaces.show(id, null, null, true);
@@ -26983,9 +26953,9 @@ RED.view = (function() {
26983
26953
 
26984
26954
  RED.view.navigator = (function() {
26985
26955
 
26986
- var nav_scale = 25;
26987
- var nav_width = 5000/nav_scale;
26988
- var nav_height = 5000/nav_scale;
26956
+ var nav_scale = 50;
26957
+ var nav_width = 8000/nav_scale;
26958
+ var nav_height = 8000/nav_scale;
26989
26959
 
26990
26960
  var navContainer;
26991
26961
  var navBox;
@@ -27163,7 +27133,7 @@ RED.view.tools = (function() {
27163
27133
  }
27164
27134
 
27165
27135
  function alignToGrid() {
27166
- if (RED.workspaces.isActiveLocked()) {
27136
+ if (RED.workspaces.isLocked()) {
27167
27137
  return
27168
27138
  }
27169
27139
  var selection = RED.view.selection();
@@ -27214,7 +27184,7 @@ RED.view.tools = (function() {
27214
27184
  }
27215
27185
 
27216
27186
  function moveSelection(dx,dy) {
27217
- if (RED.workspaces.isActiveLocked()) {
27187
+ if (RED.workspaces.isLocked()) {
27218
27188
  return
27219
27189
  }
27220
27190
  if (moving_set === null) {
@@ -27283,7 +27253,7 @@ RED.view.tools = (function() {
27283
27253
  }
27284
27254
 
27285
27255
  function setSelectedNodeLabelState(labelShown) {
27286
- if (RED.workspaces.isActiveLocked()) {
27256
+ if (RED.workspaces.isLocked()) {
27287
27257
  return
27288
27258
  }
27289
27259
  var selection = RED.view.selection();
@@ -27572,9 +27542,9 @@ RED.view.tools = (function() {
27572
27542
  }
27573
27543
 
27574
27544
  function alignSelectionToEdge(direction) {
27575
- // if (RED.workspaces.isActiveLocked()) {
27576
- // return
27577
- // }
27545
+ if (RED.workspaces.isLocked()) {
27546
+ return;
27547
+ }
27578
27548
  var selection = RED.view.selection();
27579
27549
 
27580
27550
  if (selection.nodes && selection.nodes.length > 1) {
@@ -27676,7 +27646,7 @@ RED.view.tools = (function() {
27676
27646
  }
27677
27647
 
27678
27648
  function distributeSelection(direction) {
27679
- if (RED.workspaces.isActiveLocked()) {
27649
+ if (RED.workspaces.isLocked()) {
27680
27650
  return
27681
27651
  }
27682
27652
  var selection = RED.view.selection();
@@ -27837,7 +27807,7 @@ RED.view.tools = (function() {
27837
27807
  }
27838
27808
 
27839
27809
  function reorderSelection(dir) {
27840
- if (RED.workspaces.isActiveLocked()) {
27810
+ if (RED.workspaces.isLocked()) {
27841
27811
  return
27842
27812
  }
27843
27813
  var selection = RED.view.selection();
@@ -27845,9 +27815,8 @@ RED.view.tools = (function() {
27845
27815
  var nodesToMove = [];
27846
27816
  selection.nodes.forEach(function(n) {
27847
27817
  if (n.type === "group") {
27848
- nodesToMove = nodesToMove.concat(RED.group.getNodes(n, true).filter(function(n) {
27849
- return n.type !== "group";
27850
- }))
27818
+ nodesToMove.push(n)
27819
+ nodesToMove = nodesToMove.concat(RED.group.getNodes(n, true))
27851
27820
  } else if (n.type !== "subflow"){
27852
27821
  nodesToMove.push(n);
27853
27822
  }
@@ -27876,7 +27845,7 @@ RED.view.tools = (function() {
27876
27845
  }
27877
27846
 
27878
27847
  function wireSeriesOfNodes() {
27879
- if (RED.workspaces.isActiveLocked()) {
27848
+ if (RED.workspaces.isLocked()) {
27880
27849
  return
27881
27850
  }
27882
27851
  var selection = RED.view.selection();
@@ -27919,7 +27888,7 @@ RED.view.tools = (function() {
27919
27888
  }
27920
27889
 
27921
27890
  function wireNodeToMultiple() {
27922
- if (RED.workspaces.isActiveLocked()) {
27891
+ if (RED.workspaces.isLocked()) {
27923
27892
  return
27924
27893
  }
27925
27894
  var selection = RED.view.selection();
@@ -27964,12 +27933,70 @@ RED.view.tools = (function() {
27964
27933
  }
27965
27934
  }
27966
27935
 
27936
+ function wireMultipleToNode() {
27937
+ if (RED.workspaces.isLocked()) {
27938
+ return
27939
+ }
27940
+ var selection = RED.view.selection();
27941
+ if (selection.nodes) {
27942
+ if (selection.nodes.length > 1) {
27943
+ var targetNode = selection.nodes[selection.nodes.length - 1];
27944
+ if (targetNode.inputs === 0) {
27945
+ return;
27946
+ }
27947
+ var i = 0;
27948
+ var newLinks = [];
27949
+ for (i = 0; i < selection.nodes.length - 1; i++) {
27950
+ var sourceNode = selection.nodes[i];
27951
+ if (sourceNode.outputs > 0) {
27952
+
27953
+ // Wire the first output to the target that has no link to the target yet.
27954
+ // This allows for connecting all combinations of inputs/outputs.
27955
+ // The user may then delete links quickly that aren't needed.
27956
+ var sourceConnectedOutports = RED.nodes.filterLinks({
27957
+ source: sourceNode,
27958
+ target: targetNode
27959
+ });
27960
+
27961
+ // Get outport indices that have no link yet
27962
+ var sourceOutportIndices = Array.from({ length: sourceNode.outputs }, (_, i) => i);
27963
+ var sourceConnectedOutportIndices = sourceConnectedOutports.map( x => x.sourcePort );
27964
+ var sourceFreeOutportIndices = sourceOutportIndices.filter(x => !sourceConnectedOutportIndices.includes(x));
27965
+
27966
+ // Does an unconnected source port exist?
27967
+ if (sourceFreeOutportIndices.length == 0) {
27968
+ continue;
27969
+ }
27970
+
27971
+ // Connect the first free outport to the target
27972
+ var newLink = {
27973
+ source: sourceNode,
27974
+ target: targetNode,
27975
+ sourcePort: sourceFreeOutportIndices[0]
27976
+ }
27977
+ RED.nodes.addLink(newLink);
27978
+ newLinks.push(newLink);
27979
+ }
27980
+ }
27981
+ if (newLinks.length > 0) {
27982
+ RED.history.push({
27983
+ t: 'add',
27984
+ links: newLinks,
27985
+ dirty: RED.nodes.dirty()
27986
+ })
27987
+ RED.nodes.dirty(true);
27988
+ RED.view.redraw(true);
27989
+ }
27990
+ }
27991
+ }
27992
+ }
27993
+
27967
27994
  /**
27968
27995
  * Splits selected wires and re-joins them with link-out+link-in
27969
27996
  * @param {Object || Object[]} wires The wire(s) to split and replace with link-out, link-in nodes.
27970
27997
  */
27971
27998
  function splitWiresWithLinkNodes(wires) {
27972
- if (RED.workspaces.isActiveLocked()) {
27999
+ if (RED.workspaces.isLocked()) {
27973
28000
  return
27974
28001
  }
27975
28002
  let wiresToSplit = wires || (RED.view.selection().links && RED.view.selection().links.filter(e => !e.link));
@@ -28140,7 +28167,7 @@ RED.view.tools = (function() {
28140
28167
  * @param {{ renameBlank: boolean, renameClash: boolean, generateHistory: boolean }} options Possible options are `renameBlank`, `renameClash` and `generateHistory`
28141
28168
  */
28142
28169
  function generateNodeNames(node, options) {
28143
- if (RED.workspaces.isActiveLocked()) {
28170
+ if (RED.workspaces.isLocked()) {
28144
28171
  return
28145
28172
  }
28146
28173
  options = Object.assign({
@@ -28213,7 +28240,7 @@ RED.view.tools = (function() {
28213
28240
  }
28214
28241
 
28215
28242
  function addJunctionsToWires(wires) {
28216
- if (RED.workspaces.isActiveLocked()) {
28243
+ if (RED.workspaces.isLocked()) {
28217
28244
  return
28218
28245
  }
28219
28246
  let wiresToSplit = wires || (RED.view.selection().links && RED.view.selection().links.filter(e => !e.link));
@@ -28257,7 +28284,8 @@ RED.view.tools = (function() {
28257
28284
  w: 0, h: 0,
28258
28285
  outputs: 1,
28259
28286
  inputs: 1,
28260
- dirty: true
28287
+ dirty: true,
28288
+ moved: true
28261
28289
  }
28262
28290
  links = links.filter(function(l) { return !removedLinks.has(l) })
28263
28291
  if (links.length === 0) {
@@ -28431,6 +28459,7 @@ RED.view.tools = (function() {
28431
28459
 
28432
28460
  RED.actions.add("core:wire-series-of-nodes", function() { wireSeriesOfNodes() })
28433
28461
  RED.actions.add("core:wire-node-to-multiple", function() { wireNodeToMultiple() })
28462
+ RED.actions.add("core:wire-multiple-to-node", function() { wireMultipleToNode() })
28434
28463
 
28435
28464
  RED.actions.add("core:split-wire-with-link-nodes", function () { splitWiresWithLinkNodes() });
28436
28465
  RED.actions.add("core:split-wires-with-junctions", function () { addJunctionsToWires() });
@@ -30198,6 +30227,10 @@ RED.sidebar.info = (function() {
30198
30227
  RED.workspaces.show(n.id, null, true);
30199
30228
  }
30200
30229
  });
30230
+ RED.popover.tooltip(toggleVisibleButton, function () {
30231
+ var isHidden = !div.hasClass("red-ui-info-outline-item-hidden");
30232
+ return RED._("sidebar.info." + (isHidden ? "hideFlow" : "showFlow"));
30233
+ });
30201
30234
  }
30202
30235
  if (n.type !== 'subflow') {
30203
30236
  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 +30729,9 @@ RED.sidebar.info = (function() {
30696
30729
  objects[n.id].children = missingParents[n.id];
30697
30730
  delete missingParents[n.id]
30698
30731
  }
30732
+ if (objects[n.id].children.length === 0) {
30733
+ objects[n.id].children.push(getEmptyItem(n.id));
30734
+ }
30699
30735
  }
30700
30736
  var parent = n.g||n.z||"__global__";
30701
30737
 
@@ -31407,17 +31443,19 @@ RED.sidebar.config = (function() {
31407
31443
  } else {
31408
31444
  var currentType = "";
31409
31445
  nodes.forEach(function(node) {
31410
- var label = RED.utils.getNodeLabel(node,node.id);
31446
+ var labelText = RED.utils.getNodeLabel(node,node.id);
31411
31447
  if (node.type != currentType) {
31412
31448
  $('<li class="red-ui-palette-node-config-type">'+node.type+'</li>').appendTo(list);
31413
31449
  currentType = node.type;
31414
31450
  }
31415
-
31451
+ if (node.changed) {
31452
+ labelText += "!!"
31453
+ }
31416
31454
  var entry = $('<li class="red-ui-palette-node_id_'+node.id.replace(/\./g,"-")+'"></li>').appendTo(list);
31417
31455
  var nodeDiv = $('<div class="red-ui-palette-node-config red-ui-palette-node"></div>').appendTo(entry);
31418
31456
  entry.data('node',node.id);
31419
31457
  nodeDiv.data('node',node.id);
31420
- var label = $('<div class="red-ui-palette-label"></div>').text(label).appendTo(nodeDiv);
31458
+ var label = $('<div class="red-ui-palette-label"></div>').text(labelText).appendTo(nodeDiv);
31421
31459
  if (node.d) {
31422
31460
  nodeDiv.addClass("red-ui-palette-node-config-disabled");
31423
31461
  $('<i class="fa fa-ban"></i>').prependTo(label);
@@ -34116,6 +34154,7 @@ RED.editor = (function() {
34116
34154
  function showEditDialog(node, defaultTab) {
34117
34155
  if (buildingEditDialog) { return }
34118
34156
  buildingEditDialog = true;
34157
+ if (node.z && RED.workspaces.isLocked(node.z)) { return }
34119
34158
  var editing_node = node;
34120
34159
  var removeInfoEditorOnClose = false;
34121
34160
  var skipInfoRefreshOnClose = false;
@@ -34301,6 +34340,13 @@ RED.editor = (function() {
34301
34340
 
34302
34341
  var trayFooterLeft = $('<div class="red-ui-tray-footer-left"></div>').appendTo(trayFooter)
34303
34342
 
34343
+ var helpButton = $('<button type="button" class="red-ui-button"><i class="fa fa-book"></button>').on("click", function(evt) {
34344
+ evt.preventDefault();
34345
+ evt.stopPropagation();
34346
+ RED.sidebar.help.show(editing_node.type);
34347
+ }).appendTo(trayFooterLeft);
34348
+ RED.popover.tooltip(helpButton, RED._("sidebar.help.showHelp"));
34349
+
34304
34350
  $('<input id="node-input-node-disabled" type="checkbox">').prop("checked",!!node.d).appendTo(trayFooterLeft).toggleButton({
34305
34351
  enabledIcon: "fa-circle-thin",
34306
34352
  disabledIcon: "fa-ban",
@@ -34404,6 +34450,8 @@ RED.editor = (function() {
34404
34450
  var editing_config_node = RED.nodes.node(id);
34405
34451
  var activeEditPanes = [];
34406
34452
 
34453
+ if (editing_config_node && editing_config_node.z && RED.workspaces.isLocked(editing_config_node.z)) { return }
34454
+
34407
34455
  var configNodeScope = ""; // default to global
34408
34456
  var activeSubflow = RED.nodes.subflow(RED.workspaces.active());
34409
34457
  if (activeSubflow) {
@@ -34446,6 +34494,13 @@ RED.editor = (function() {
34446
34494
 
34447
34495
  var trayFooterLeft = $('<div class="red-ui-tray-footer-left"></div>').appendTo(trayFooter)
34448
34496
 
34497
+ var helpButton = $('<button type="button" class="red-ui-button"><i class="fa fa-book"></button>').on("click", function(evt) {
34498
+ evt.preventDefault();
34499
+ evt.stopPropagation();
34500
+ RED.sidebar.help.show(editing_config_node.type);
34501
+ }).appendTo(trayFooterLeft);
34502
+ RED.popover.tooltip(helpButton, RED._("sidebar.help.showHelp"));
34503
+
34449
34504
  $('<input id="node-config-input-node-disabled" type="checkbox">').prop("checked",!!editing_config_node.d).appendTo(trayFooterLeft).toggleButton({
34450
34505
  enabledIcon: "fa-circle-thin",
34451
34506
  disabledIcon: "fa-ban",
@@ -34950,6 +35005,7 @@ RED.editor = (function() {
34950
35005
  function showEditGroupDialog(group, defaultTab) {
34951
35006
  if (buildingEditDialog) { return }
34952
35007
  buildingEditDialog = true;
35008
+ if (group.z && RED.workspaces.isLocked(group.z)) { return }
34953
35009
  var editing_node = group;
34954
35010
  editStack.push(group);
34955
35011
  RED.view.state(RED.state.EDITING);
@@ -35178,9 +35234,9 @@ RED.editor = (function() {
35178
35234
  workspace.locked = false;
35179
35235
  }
35180
35236
  $('<input id="node-input-locked" type="checkbox">').prop("checked",workspace.locked).appendTo(trayFooterRight).toggleButton({
35181
- enabledLabel: 'Unlocked',
35237
+ enabledLabel: RED._("common.label.unlocked"),
35182
35238
  enabledIcon: "fa-unlock-alt",
35183
- disabledLabel: 'Locked',
35239
+ disabledLabel: RED._("common.label.locked"),
35184
35240
  disabledIcon: "fa-lock",
35185
35241
  invertState: true
35186
35242
  })
@@ -37919,32 +37975,37 @@ RED.editor = (function() {
37919
37975
  }
37920
37976
 
37921
37977
  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) {
37978
+ expr.evaluate(legacyMode?{msg:parsedData}:parsedData, (err, result) => {
37979
+ if (err) {
37980
+ testResultEditor.setValue(RED._("expressionEditor.errors.eval",{message:err.message}),-1);
37981
+ } else {
37982
+ if (usesContext) {
37983
+ testResultEditor.setValue(RED._("expressionEditor.errors.context-unsupported"),-1);
37984
+ return;
37985
+ }
37986
+ if (usesEnv) {
37987
+ testResultEditor.setValue(RED._("expressionEditor.errors.env-unsupported"),-1);
37988
+ return;
37989
+ }
37990
+ if (usesMoment) {
37991
+ testResultEditor.setValue(RED._("expressionEditor.errors.moment-unsupported"),-1);
37992
+ return;
37993
+ }
37994
+ if (usesClone) {
37995
+ testResultEditor.setValue(RED._("expressionEditor.errors.clone-unsupported"),-1);
37996
+ return;
37997
+ }
37998
+
37999
+ var formattedResult;
38000
+ if (result !== undefined) {
38001
+ formattedResult = JSON.stringify(result,null,4);
38002
+ } else {
38003
+ formattedResult = RED._("expressionEditor.noMatch");
38004
+ }
38005
+ testResultEditor.setValue(formattedResult,-1);
38006
+ }
38007
+ });
38008
+ } catch(err) {
37948
38009
  testResultEditor.setValue(RED._("expressionEditor.errors.eval",{message:err.message}),-1);
37949
38010
  }
37950
38011
  }
@@ -39511,18 +39572,21 @@ RED.editor.codeEditor.monaco = (function() {
39511
39572
  //TODO: get from externalModules.js For now this is enough for feature parity with ACE (and then some).
39512
39573
  const knownModules = {
39513
39574
  "assert": {package: "node", module: "assert", path: "node/assert.d.ts" },
39575
+ "assert/strict": {package: "node", module: "assert/strict", path: "node/assert/strict.d.ts" },
39514
39576
  "async_hooks": {package: "node", module: "async_hooks", path: "node/async_hooks.d.ts" },
39515
39577
  "buffer": {package: "node", module: "buffer", path: "node/buffer.d.ts" },
39516
39578
  "child_process": {package: "node", module: "child_process", path: "node/child_process.d.ts" },
39517
39579
  "cluster": {package: "node", module: "cluster", path: "node/cluster.d.ts" },
39518
39580
  "console": {package: "node", module: "console", path: "node/console.d.ts" },
39519
- "constants": {package: "node", module: "constants", path: "node/constants.d.ts" },
39520
39581
  "crypto": {package: "node", module: "crypto", path: "node/crypto.d.ts" },
39521
39582
  "dgram": {package: "node", module: "dgram", path: "node/dgram.d.ts" },
39583
+ "diagnostics_channel.d": {package: "node", module: "diagnostics_channel", path: "node/diagnostics_channel.d.ts" },
39522
39584
  "dns": {package: "node", module: "dns", path: "node/dns.d.ts" },
39585
+ "dns/promises": {package: "node", module: "dns/promises", path: "node/dns/promises.d.ts" },
39523
39586
  "domain": {package: "node", module: "domain", path: "node/domain.d.ts" },
39524
39587
  "events": {package: "node", module: "events", path: "node/events.d.ts" },
39525
39588
  "fs": {package: "node", module: "fs", path: "node/fs.d.ts" },
39589
+ "fs/promises": {package: "node", module: "fs/promises", path: "node/fs/promises.d.ts" },
39526
39590
  "globals": {package: "node", module: "globals", path: "node/globals.d.ts" },
39527
39591
  "http": {package: "node", module: "http", path: "node/http.d.ts" },
39528
39592
  "http2": {package: "node", module: "http2", path: "node/http2.d.ts" },
@@ -39536,8 +39600,13 @@ RED.editor.codeEditor.monaco = (function() {
39536
39600
  "querystring": {package: "node", module: "querystring", path: "node/querystring.d.ts" },
39537
39601
  "readline": {package: "node", module: "readline", path: "node/readline.d.ts" },
39538
39602
  "stream": {package: "node", module: "stream", path: "node/stream.d.ts" },
39603
+ "stream/consumers": {package: "node", module: "stream/consumers", path: "node/stream/consumers.d.ts" },
39604
+ "stream/promises": {package: "node", module: "stream/promises", path: "node/stream/promises.d.ts" },
39605
+ "stream/web": {package: "node", module: "stream/web", path: "node/stream/web.d.ts" },
39539
39606
  "string_decoder": {package: "node", module: "string_decoder", path: "node/string_decoder.d.ts" },
39607
+ "test": {package: "node", module: "test", path: "node/test.d.ts" },
39540
39608
  "timers": {package: "node", module: "timers", path: "node/timers.d.ts" },
39609
+ "timers/promises": {package: "node", module: "timers/promises", path: "node/timers/promises.d.ts" },
39541
39610
  "tls": {package: "node", module: "tls", path: "node/tls.d.ts" },
39542
39611
  "trace_events": {package: "node", module: "trace_events", path: "node/trace_events.d.ts" },
39543
39612
  "tty": {package: "node", module: "tty", path: "node/tty.d.ts" },
@@ -41866,7 +41935,7 @@ RED.clipboard = (function() {
41866
41935
  $("#red-ui-clipboard-dialog-import-text").on("keyup", validateImport);
41867
41936
  $("#red-ui-clipboard-dialog-import-text").on('paste',function() { setTimeout(validateImport,10)});
41868
41937
 
41869
- if (RED.workspaces.active() === 0 || RED.workspaces.isActiveLocked()) {
41938
+ if (RED.workspaces.active() === 0 || RED.workspaces.isLocked()) {
41870
41939
  $("#red-ui-clipboard-dialog-import-opt-current").addClass('disabled').removeClass("selected");
41871
41940
  $("#red-ui-clipboard-dialog-import-opt-new").addClass("selected");
41872
41941
  } else {
@@ -42641,7 +42710,7 @@ RED.clipboard = (function() {
42641
42710
  RED.keyboard.add("#red-ui-drop-target", "escape" ,hideDropTarget);
42642
42711
 
42643
42712
  $('#red-ui-workspace-chart').on("dragenter",function(event) {
42644
- if (!RED.workspaces.isActiveLocked() && (
42713
+ if (!RED.workspaces.isLocked() && (
42645
42714
  $.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1 ||
42646
42715
  $.inArray("Files",event.originalEvent.dataTransfer.types) != -1)) {
42647
42716
  $("#red-ui-drop-target").css({display:'table'}).focus();
@@ -42651,7 +42720,7 @@ RED.clipboard = (function() {
42651
42720
  $('#red-ui-drop-target').on("dragover",function(event) {
42652
42721
  if ($.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1 ||
42653
42722
  $.inArray("Files",event.originalEvent.dataTransfer.types) != -1 ||
42654
- RED.workspaces.isActiveLocked()) {
42723
+ RED.workspaces.isLocked()) {
42655
42724
  event.preventDefault();
42656
42725
  }
42657
42726
  })
@@ -42659,7 +42728,7 @@ RED.clipboard = (function() {
42659
42728
  hideDropTarget();
42660
42729
  })
42661
42730
  .on("drop",function(event) {
42662
- if (!RED.workspaces.isActiveLocked()) {
42731
+ if (!RED.workspaces.isLocked()) {
42663
42732
  try {
42664
42733
  if ($.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1) {
42665
42734
  var data = event.originalEvent.dataTransfer.getData("text/plain");
@@ -44608,7 +44677,7 @@ RED.search = (function() {
44608
44677
  const isMultipleLinks = !hasSelection && hasLinks && wireLinks.length > 1
44609
44678
  const canDelete = hasSelection || hasLinks
44610
44679
  const isGroup = hasSelection && selection.nodes.length === 1 && selection.nodes[0].type === 'group'
44611
- const canEdit = !RED.workspaces.isActiveLocked()
44680
+ const canEdit = !RED.workspaces.isLocked()
44612
44681
  const canRemoveFromGroup = hasSelection && !!selection.nodes[0].g
44613
44682
  const isAllGroups = hasSelection && selection.nodes.filter(n => n.type !== 'group').length === 0
44614
44683
  const hasGroup = hasSelection && selection.nodes.filter(n => n.type === 'group' ).length > 0
@@ -44659,17 +44728,18 @@ RED.search = (function() {
44659
44728
  w: 0, h: 0,
44660
44729
  outputs: 1,
44661
44730
  inputs: 1,
44662
- dirty: true
44731
+ dirty: true,
44732
+ moved: true
44663
44733
  }
44734
+ const junction = RED.nodes.addJunction(nn);
44664
44735
  const historyEvent = {
44665
44736
  dirty: RED.nodes.dirty(),
44666
44737
  t: 'add',
44667
- junctions: [nn]
44738
+ junctions: [junction]
44668
44739
  }
44669
- RED.nodes.addJunction(nn);
44670
44740
  RED.history.push(historyEvent);
44671
44741
  RED.nodes.dirty(true);
44672
- RED.view.select({nodes: [nn] });
44742
+ RED.view.select({nodes: [junction] });
44673
44743
  RED.view.redraw(true)
44674
44744
  },
44675
44745
  disabled: !canEdit
@@ -44707,17 +44777,24 @@ RED.search = (function() {
44707
44777
  options: [
44708
44778
  { onselect: 'core:group-selection' },
44709
44779
  { 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
44780
  ]
44714
44781
  })
44782
+ if (hasGroup) {
44783
+ menuItems[menuItems.length - 1].options.push(
44784
+ { onselect: 'core:merge-selection-to-group', label: RED._("menu.label.groupMergeSelection") }
44785
+ )
44786
+
44787
+ }
44715
44788
  if (canRemoveFromGroup) {
44716
44789
  menuItems[menuItems.length - 1].options.push(
44717
- null,
44718
44790
  { onselect: 'core:remove-selection-from-group', label: RED._("menu.label.groupRemoveSelection") }
44719
44791
  )
44720
44792
  }
44793
+ menuItems[menuItems.length - 1].options.push(
44794
+ null,
44795
+ { onselect: 'core:copy-group-style', disabled: !hasGroup },
44796
+ { onselect: 'core:paste-group-style', disabled: !hasGroup}
44797
+ )
44721
44798
  }
44722
44799
  if (canEdit && hasMultipleSelection) {
44723
44800
  menuItems.push({
@@ -46045,7 +46122,7 @@ RED.subflow = (function() {
46045
46122
  }
46046
46123
  });
46047
46124
  RED.events.on("view:selection-changed",function(selection) {
46048
- if (!selection.nodes || RED.workspaces.isActiveLocked()) {
46125
+ if (!selection.nodes || RED.workspaces.isLocked()) {
46049
46126
  RED.menu.setDisabled("menu-item-subflow-convert",true);
46050
46127
  } else {
46051
46128
  RED.menu.setDisabled("menu-item-subflow-convert",false);
@@ -46108,7 +46185,7 @@ RED.subflow = (function() {
46108
46185
  }
46109
46186
 
46110
46187
  function convertToSubflow() {
46111
- if (RED.workspaces.isActiveLocked()) {
46188
+ if (RED.workspaces.isLocked()) {
46112
46189
  return
46113
46190
  }
46114
46191
  var selection = RED.view.selection();
@@ -47025,7 +47102,7 @@ RED.group = (function() {
47025
47102
  var activateMerge = false;
47026
47103
  var activateRemove = false;
47027
47104
  var singleGroupSelected = false;
47028
- var locked = RED.workspaces.isActiveLocked()
47105
+ var locked = RED.workspaces.isLocked()
47029
47106
 
47030
47107
  if (activateGroup) {
47031
47108
  singleGroupSelected = selection.nodes.length === 1 && selection.nodes[0].type === 'group';
@@ -47103,7 +47180,7 @@ RED.group = (function() {
47103
47180
  }
47104
47181
  }
47105
47182
  function pasteGroupStyle() {
47106
- if (RED.workspaces.isActiveLocked()) { return }
47183
+ if (RED.workspaces.isLocked()) { return }
47107
47184
  if (RED.view.state() !== RED.state.DEFAULT) { return }
47108
47185
  if (groupStyleClipboard) {
47109
47186
  var selection = RED.view.selection();
@@ -47138,7 +47215,7 @@ RED.group = (function() {
47138
47215
  }
47139
47216
 
47140
47217
  function groupSelection() {
47141
- if (RED.workspaces.isActiveLocked()) { return }
47218
+ if (RED.workspaces.isLocked()) { return }
47142
47219
  if (RED.view.state() !== RED.state.DEFAULT) { return }
47143
47220
  var selection = RED.view.selection();
47144
47221
  if (selection.nodes) {
@@ -47157,12 +47234,12 @@ RED.group = (function() {
47157
47234
  }
47158
47235
  }
47159
47236
  function ungroupSelection() {
47160
- if (RED.workspaces.isActiveLocked()) { return }
47237
+ if (RED.workspaces.isLocked()) { return }
47161
47238
  if (RED.view.state() !== RED.state.DEFAULT) { return }
47162
47239
  var selection = RED.view.selection();
47163
47240
  if (selection.nodes) {
47164
47241
  var newSelection = [];
47165
- groups = selection.nodes.filter(function(n) { return n.type === "group" });
47242
+ let groups = selection.nodes.filter(function(n) { return n.type === "group" });
47166
47243
 
47167
47244
  var historyEvent = {
47168
47245
  t:"ungroup",
@@ -47181,7 +47258,7 @@ RED.group = (function() {
47181
47258
  }
47182
47259
 
47183
47260
  function ungroup(g) {
47184
- if (RED.workspaces.isActiveLocked()) { return }
47261
+ if (RED.workspaces.isLocked()) { return }
47185
47262
  var nodes = [];
47186
47263
  var parentGroup = RED.nodes.group(g.g);
47187
47264
  g.nodes.forEach(function(n) {
@@ -47208,7 +47285,7 @@ RED.group = (function() {
47208
47285
  }
47209
47286
 
47210
47287
  function mergeSelection() {
47211
- if (RED.workspaces.isActiveLocked()) { return }
47288
+ if (RED.workspaces.isLocked()) { return }
47212
47289
  if (RED.view.state() !== RED.state.DEFAULT) { return }
47213
47290
  var selection = RED.view.selection();
47214
47291
  if (selection.nodes) {
@@ -47278,7 +47355,7 @@ RED.group = (function() {
47278
47355
  }
47279
47356
 
47280
47357
  function removeSelection() {
47281
- if (RED.workspaces.isActiveLocked()) { return }
47358
+ if (RED.workspaces.isLocked()) { return }
47282
47359
  if (RED.view.state() !== RED.state.DEFAULT) { return }
47283
47360
  var selection = RED.view.selection();
47284
47361
  if (selection.nodes) {
@@ -47306,13 +47383,21 @@ RED.group = (function() {
47306
47383
  }
47307
47384
  }
47308
47385
  function createGroup(nodes) {
47309
- if (RED.workspaces.isActiveLocked()) { return }
47386
+ if (RED.workspaces.isLocked()) { return }
47310
47387
  if (nodes.length === 0) {
47311
47388
  return;
47312
47389
  }
47313
- if (nodes.filter(function(n) { return n.type === "subflow" }).length > 0) {
47314
- RED.notify(RED._("group.errors.cannotAddSubflowPorts"),"error");
47315
- return;
47390
+ const existingGroup = nodes[0].g
47391
+ for (let i = 0; i < nodes.length; i++) {
47392
+ const n = nodes[i]
47393
+ if (n.type === 'subflow') {
47394
+ RED.notify(RED._("group.errors.cannotAddSubflowPorts"),"error");
47395
+ return;
47396
+ }
47397
+ if (n.g !== existingGroup) {
47398
+ console.warn("Cannot add nooes with different z properties")
47399
+ return
47400
+ }
47316
47401
  }
47317
47402
  // nodes is an array
47318
47403
  // each node must be on the same tab (z)
@@ -47325,12 +47410,17 @@ RED.group = (function() {
47325
47410
  y: Number.POSITIVE_INFINITY,
47326
47411
  w: 0,
47327
47412
  h: 0,
47328
- _def: RED.group.def
47413
+ _def: RED.group.def,
47414
+ changed: true
47329
47415
  }
47330
47416
 
47331
47417
  group.z = nodes[0].z;
47332
47418
  group = RED.nodes.addGroup(group);
47333
47419
 
47420
+ if (existingGroup) {
47421
+ addToGroup(RED.nodes.group(existingGroup), group)
47422
+ }
47423
+
47334
47424
  try {
47335
47425
  addToGroup(group,nodes);
47336
47426
  } catch(err) {
@@ -47354,7 +47444,7 @@ RED.group = (function() {
47354
47444
  if (!z) {
47355
47445
  z = n.z;
47356
47446
  } else if (z !== n.z) {
47357
- throw new Error("Cannot add nooes with different z properties")
47447
+ throw new Error("Cannot add nodes with different z properties")
47358
47448
  }
47359
47449
  if (n.g) {
47360
47450
  // This is already in a group.
@@ -47371,14 +47461,10 @@ RED.group = (function() {
47371
47461
  throw new Error(RED._("group.errors.cannotCreateDiffGroups"))
47372
47462
  }
47373
47463
  }
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.
47464
+ // The nodes are already in a group - so we need to remove them first
47377
47465
  if (g) {
47378
47466
  g = RED.nodes.group(g);
47379
- g.nodes.push(group);
47380
47467
  g.dirty = true;
47381
- group.g = g.id;
47382
47468
  }
47383
47469
  // Second pass - add them to the group
47384
47470
  for (i=0;i<nodes.length;i++) {
@@ -47412,7 +47498,7 @@ RED.group = (function() {
47412
47498
  markDirty(group);
47413
47499
  }
47414
47500
  function removeFromGroup(group, nodes, reparent) {
47415
- if (RED.workspaces.isActiveLocked()) { return }
47501
+ if (RED.workspaces.isLocked()) { return }
47416
47502
  if (!Array.isArray(nodes)) {
47417
47503
  nodes = [nodes];
47418
47504
  }
@@ -47430,7 +47516,7 @@ RED.group = (function() {
47430
47516
  n.dirty = true;
47431
47517
  var index = group.nodes.indexOf(n);
47432
47518
  group.nodes.splice(index,1);
47433
- if (reparent && group.g) {
47519
+ if (reparent && parentGroup) {
47434
47520
  n.g = group.g
47435
47521
  parentGroup.nodes.push(n);
47436
47522
  } else {
@@ -48617,14 +48703,14 @@ RED.projects = (function() {
48617
48703
  var row = $('<div class="form-row"></div>').appendTo(body);
48618
48704
  $('<label for="red-ui-projects-dialog-screen-create-project-file">'+RED._("projects.default-files.flow-file")+'</label>').appendTo(row);
48619
48705
  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";
48706
+ var defaultFlowFile = (createProjectOptions.files &&createProjectOptions.files.flow) || (RED.settings.files && RED.settings.files.flow) || "flows.json";
48621
48707
  projectFlowFileInput = $('<input id="red-ui-projects-dialog-screen-create-project-file" type="text">').val(defaultFlowFile)
48622
48708
  .on("change keyup paste",validateForm)
48623
48709
  .appendTo(subrow);
48624
48710
  $('<div class="red-ui-projects-dialog-screen-input-status"></div>').appendTo(subrow);
48625
48711
  $('<label class="red-ui-projects-edit-form-sublabel"><small>*.json</small></label>').appendTo(row);
48626
48712
 
48627
- var defaultCredentialsFile = (createProjectOptions.files &&createProjectOptions.files.credentials) || (RED.settings.files && RED.settings.files.credentials)||"flow_cred.json";
48713
+ var defaultCredentialsFile = (createProjectOptions.files &&createProjectOptions.files.credentials) || (RED.settings.files && RED.settings.files.credentials) || "flows_cred.json";
48628
48714
  row = $('<div class="form-row"></div>').appendTo(body);
48629
48715
  $('<label for="red-ui-projects-dialog-screen-create-project-credfile">'+RED._("projects.default-files.credentials-file")+'</label>').appendTo(row);
48630
48716
  subrow = $('<div style="position:relative;"></div>').appendTo(row);
@@ -49127,7 +49213,7 @@ RED.projects = (function() {
49127
49213
  row = $('<div class="form-row red-ui-projects-dialog-screen-create-row red-ui-projects-dialog-screen-create-row-empty"></div>').appendTo(container);
49128
49214
  $('<label for="red-ui-projects-dialog-screen-create-project-file">'+RED._("projects.create.flow-file")+'</label>').appendTo(row);
49129
49215
  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")
49216
+ projectFlowFileInput = $('<input id="red-ui-projects-dialog-screen-create-project-file" type="text">').val("flows.json")
49131
49217
  .on("change keyup paste",validateForm)
49132
49218
  .appendTo(subrow);
49133
49219
  $('<div class="red-ui-projects-dialog-screen-input-status"></div>').appendTo(subrow);
@@ -50498,6 +50584,9 @@ RED.projects.settings = (function() {
50498
50584
  }
50499
50585
  var description = addTargetToExternalLinks($('<span class="red-ui-text-bidi-aware" dir=\"'+RED.text.bidi.resolveBaseTextDir(desc)+'">'+desc+'</span>')).appendTo(container);
50500
50586
  description.find(".red-ui-text-bidi-aware").contents().filter(function() { return this.nodeType === 3 && this.textContent.trim() !== "" }).wrap( "<span></span>" );
50587
+ setTimeout(function () {
50588
+ mermaid.init();
50589
+ }, 200);
50501
50590
  }
50502
50591
 
50503
50592
  function editSummary(activeProject, summary, container, version, versionContainer) {