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

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 (34) hide show
  1. package/locales/de/editor.json +44 -33
  2. package/locales/de/infotips.json +0 -0
  3. package/locales/de/jsonata.json +0 -0
  4. package/locales/en-US/editor.json +31 -6
  5. package/locales/en-US/infotips.json +0 -0
  6. package/locales/en-US/jsonata.json +0 -0
  7. package/locales/ja/editor.json +37 -9
  8. package/locales/ko/editor.json +0 -0
  9. package/locales/ko/infotips.json +0 -0
  10. package/locales/ko/jsonata.json +0 -0
  11. package/locales/ru/editor.json +0 -0
  12. package/locales/ru/infotips.json +0 -0
  13. package/locales/ru/jsonata.json +0 -0
  14. package/package.json +1 -1
  15. package/public/red/about +111 -0
  16. package/public/red/red.js +1781 -557
  17. package/public/red/red.min.js +4 -3
  18. package/public/red/style.min.css +1 -1
  19. package/public/red/tours/3.0/images/context-menu.png +0 -0
  20. package/public/red/tours/{images → 3.0/images}/continuous-search.png +0 -0
  21. package/public/red/tours/{images → 3.0/images}/debug-path-tooltip.png +0 -0
  22. package/public/red/tours/{images → 3.0/images}/junction-quick-add.png +0 -0
  23. package/public/red/tours/{images → 3.0/images}/junction-slice.gif +0 -0
  24. package/public/red/tours/{images → 3.0/images}/split-wire-with-links.gif +0 -0
  25. package/public/red/tours/3.0/welcome.js +155 -0
  26. package/public/red/tours/images/context-menu.png +0 -0
  27. package/public/red/tours/images/global-env-vars.png +0 -0
  28. package/public/red/tours/images/hiding-flows.png +0 -0
  29. package/public/red/tours/images/locking-flows.png +0 -0
  30. package/public/red/tours/images/mermaid.png +0 -0
  31. package/public/red/tours/welcome.js +59 -98
  32. package/public/types/node-red/func.d.ts +3 -0
  33. package/public/vendor/mermaid/mermaid.min.js +1284 -0
  34. package/public/vendor/vendor.js +4 -3
package/public/red/red.js CHANGED
@@ -340,8 +340,35 @@ var RED = (function() {
340
340
  RED.nodes.import(nodes.flows);
341
341
  RED.nodes.dirty(false);
342
342
  RED.view.redraw(true);
343
- if (/^#flow\/.+$/.test(currentHash)) {
344
- RED.workspaces.show(currentHash.substring(6),true);
343
+ if (/^#(flow|node|group)\/.+$/.test(currentHash)) {
344
+ const hashParts = currentHash.split('/')
345
+ const showEditDialog = hashParts.length > 2 && hashParts[2] === 'edit'
346
+ if (hashParts[0] === '#flow') {
347
+ RED.workspaces.show(hashParts[1], true);
348
+ if (showEditDialog) {
349
+ RED.workspaces.edit()
350
+ }
351
+ } else if (hashParts[0] === '#node') {
352
+ const nodeToShow = RED.nodes.node(hashParts[1])
353
+ if (nodeToShow) {
354
+ setTimeout(() => {
355
+ RED.view.reveal(nodeToShow.id)
356
+ window.location.hash = currentHash
357
+ if (showEditDialog) {
358
+ RED.editor.edit(nodeToShow)
359
+ }
360
+ }, 50)
361
+ }
362
+ } else if (hashParts[0] === '#group') {
363
+ const nodeToShow = RED.nodes.group(hashParts[1])
364
+ if (nodeToShow) {
365
+ RED.view.reveal(nodeToShow.id)
366
+ window.location.hash = currentHash
367
+ if (showEditDialog) {
368
+ RED.editor.editGroup(nodeToShow)
369
+ }
370
+ }
371
+ }
345
372
  }
346
373
  if (RED.workspaces.count() > 0) {
347
374
  const hiddenTabs = JSON.parse(RED.settings.getLocal("hiddenTabs")||"{}");
@@ -412,6 +439,8 @@ var RED = (function() {
412
439
  loader.end()
413
440
  RED.notify($("<p>").text(message));
414
441
  RED.sidebar.info.refresh()
442
+ RED.menu.setDisabled('menu-item-projects-open',false);
443
+ RED.menu.setDisabled('menu-item-projects-settings',false);
415
444
  });
416
445
  });
417
446
  return;
@@ -732,11 +761,6 @@ var RED = (function() {
732
761
  ]});
733
762
 
734
763
  menuOptions.push({id:"menu-item-arrange-menu", label:RED._("menu.label.arrange"), options: [
735
- {id: "menu-item-view-tools-move-to-back", label:RED._("menu.label.moveToBack"), disabled: true, onselect: "core:move-selection-to-back"},
736
- {id: "menu-item-view-tools-move-to-front", label:RED._("menu.label.moveToFront"), disabled: true, onselect: "core:move-selection-to-front"},
737
- {id: "menu-item-view-tools-move-backwards", label:RED._("menu.label.moveBackwards"), disabled: true, onselect: "core:move-selection-backwards"},
738
- {id: "menu-item-view-tools-move-forwards", label:RED._("menu.label.moveForwards"), disabled: true, onselect: "core:move-selection-forwards"},
739
- null,
740
764
  {id: "menu-item-view-tools-align-left", label:RED._("menu.label.alignLeft"), disabled: true, onselect: "core:align-selection-to-left"},
741
765
  {id: "menu-item-view-tools-align-center", label:RED._("menu.label.alignCenter"), disabled: true, onselect: "core:align-selection-to-center"},
742
766
  {id: "menu-item-view-tools-align-right", label:RED._("menu.label.alignRight"), disabled: true, onselect: "core:align-selection-to-right"},
@@ -746,7 +770,12 @@ var RED = (function() {
746
770
  {id: "menu-item-view-tools-align-bottom", label:RED._("menu.label.alignBottom"), disabled: true, onselect: "core:align-selection-to-bottom"},
747
771
  null,
748
772
  {id: "menu-item-view-tools-distribute-horizontally", label:RED._("menu.label.distributeHorizontally"), disabled: true, onselect: "core:distribute-selection-horizontally"},
749
- {id: "menu-item-view-tools-distribute-veritcally", label:RED._("menu.label.distributeVertically"), disabled: true, onselect: "core:distribute-selection-vertically"}
773
+ {id: "menu-item-view-tools-distribute-veritcally", label:RED._("menu.label.distributeVertically"), disabled: true, onselect: "core:distribute-selection-vertically"},
774
+ null,
775
+ {id: "menu-item-view-tools-move-to-back", label:RED._("menu.label.moveToBack"), disabled: true, onselect: "core:move-selection-to-back"},
776
+ {id: "menu-item-view-tools-move-to-front", label:RED._("menu.label.moveToFront"), disabled: true, onselect: "core:move-selection-to-front"},
777
+ {id: "menu-item-view-tools-move-backwards", label:RED._("menu.label.moveBackwards"), disabled: true, onselect: "core:move-selection-backwards"},
778
+ {id: "menu-item-view-tools-move-forwards", label:RED._("menu.label.moveForwards"), disabled: true, onselect: "core:move-selection-forwards"}
750
779
  ]});
751
780
 
752
781
  menuOptions.push(null);
@@ -839,6 +868,7 @@ var RED = (function() {
839
868
  RED.deploy.init(RED.settings.theme("deployButton",null));
840
869
 
841
870
  RED.keyboard.init(buildMainMenu);
871
+ RED.envVar.init();
842
872
 
843
873
  RED.nodes.init();
844
874
  RED.runtime.init()
@@ -3681,7 +3711,6 @@ RED.state = {
3681
3711
  * @namespace RED.nodes
3682
3712
  */
3683
3713
  RED.nodes = (function() {
3684
-
3685
3714
  var PORT_TYPE_INPUT = 1;
3686
3715
  var PORT_TYPE_OUTPUT = 0;
3687
3716
 
@@ -3725,6 +3754,7 @@ RED.nodes = (function() {
3725
3754
  defaults: {
3726
3755
  label: {value:""},
3727
3756
  disabled: {value: false},
3757
+ locked: {value: false},
3728
3758
  info: {value: ""},
3729
3759
  env: {value: []}
3730
3760
  }
@@ -4237,15 +4267,48 @@ RED.nodes = (function() {
4237
4267
  }
4238
4268
  }
4239
4269
 
4270
+ const nodeProxyHandler = {
4271
+ get(node, prop) {
4272
+ if (prop === '__isProxy__') {
4273
+ return true
4274
+ } else if (prop == '__node__') {
4275
+ return node
4276
+ }
4277
+ return node[prop]
4278
+ },
4279
+ set(node, prop, value) {
4280
+ if (node.z && (RED.nodes.workspace(node.z)?.locked || RED.nodes.subflow(node.z)?.locked)) {
4281
+ if (
4282
+ node._def.defaults[prop] ||
4283
+ prop === 'z' ||
4284
+ prop === 'l' ||
4285
+ prop === 'd' ||
4286
+ (prop === 'changed' && (!!node.changed) !== (!!value)) || // jshint ignore:line
4287
+ ((prop === 'x' || prop === 'y') && !node.resize && node.type !== 'group')
4288
+ ) {
4289
+ throw new Error(`Cannot modified property '${prop}' of locked object '${node.type}:${node.id}'`)
4290
+ }
4291
+ }
4292
+ node[prop] = value;
4293
+ return true
4294
+ }
4295
+ }
4240
4296
 
4241
4297
  function addNode(n) {
4298
+ let newNode
4299
+ if (!n.__isProxy__) {
4300
+ newNode = new Proxy(n, nodeProxyHandler)
4301
+ } else {
4302
+ newNode = n
4303
+ }
4304
+
4242
4305
  if (n.type.indexOf("subflow") !== 0) {
4243
4306
  n["_"] = n._def._;
4244
4307
  } else {
4245
4308
  var subflowId = n.type.substring(8);
4246
4309
  var sf = RED.nodes.subflow(subflowId);
4247
4310
  if (sf) {
4248
- sf.instances.push(sf);
4311
+ sf.instances.push(newNode);
4249
4312
  }
4250
4313
  n["_"] = RED._;
4251
4314
  }
@@ -4262,12 +4325,13 @@ RED.nodes = (function() {
4262
4325
  });
4263
4326
  n.i = nextId+1;
4264
4327
  }
4265
- allNodes.addNode(n);
4328
+ allNodes.addNode(newNode);
4266
4329
  if (!nodeLinks[n.id]) {
4267
4330
  nodeLinks[n.id] = {in:[],out:[]};
4268
4331
  }
4269
4332
  }
4270
- RED.events.emit('nodes:add',n);
4333
+ RED.events.emit('nodes:add',newNode);
4334
+ return newNode
4271
4335
  }
4272
4336
  function addLink(l) {
4273
4337
  if (nodeLinks[l.source.id]) {
@@ -4530,14 +4594,7 @@ RED.nodes = (function() {
4530
4594
  var node;
4531
4595
 
4532
4596
  if (allNodes.hasTab(id)) {
4533
- removedNodes = allNodes.getNodes(id).filter(n => {
4534
- if (n.type === 'junction') {
4535
- removedJunctions.push(n)
4536
- return false
4537
- } else {
4538
- return true
4539
- }
4540
- })
4597
+ removedNodes = allNodes.getNodes(id).slice()
4541
4598
  }
4542
4599
  for (i in configNodes) {
4543
4600
  if (configNodes.hasOwnProperty(i)) {
@@ -4547,6 +4604,7 @@ RED.nodes = (function() {
4547
4604
  }
4548
4605
  }
4549
4606
  }
4607
+ removedJunctions = RED.nodes.junctions(id)
4550
4608
 
4551
4609
  for (i=0;i<removedNodes.length;i++) {
4552
4610
  var result = removeNode(removedNodes[i].id);
@@ -4714,6 +4772,9 @@ RED.nodes = (function() {
4714
4772
  node.type = n.type;
4715
4773
  for (var d in n._def.defaults) {
4716
4774
  if (n._def.defaults.hasOwnProperty(d)) {
4775
+ if (d === 'locked' && !n.locked) {
4776
+ continue
4777
+ }
4717
4778
  node[d] = n[d];
4718
4779
  }
4719
4780
  }
@@ -4993,7 +5054,6 @@ RED.nodes = (function() {
4993
5054
  } else {
4994
5055
  nodeSet = [sf];
4995
5056
  }
4996
- console.log(nodeSet);
4997
5057
  return createExportableNodeSet(nodeSet);
4998
5058
  }
4999
5059
  /**
@@ -5029,6 +5089,10 @@ RED.nodes = (function() {
5029
5089
  exportedConfigNodes[n.id] = true;
5030
5090
  }
5031
5091
  });
5092
+
5093
+ subflowSet = subflowSet.concat(RED.nodes.junctions(subflowId))
5094
+ subflowSet = subflowSet.concat(RED.nodes.groups(subflowId))
5095
+
5032
5096
  var exportableSubflow = createExportableNodeSet(subflowSet, exportedIds, exportedSubflows, exportedConfigNodes);
5033
5097
  nns = exportableSubflow.concat(nns);
5034
5098
  }
@@ -5630,7 +5694,7 @@ RED.nodes = (function() {
5630
5694
  }
5631
5695
  }
5632
5696
  } else {
5633
- const keepNodesCurrentZ = reimport && n.z && RED.workspaces.contains(n.z)
5697
+ const keepNodesCurrentZ = reimport && n.z && (RED.workspaces.contains(n.z) || RED.nodes.subflow(n.z))
5634
5698
  if (!keepNodesCurrentZ && n.z && !workspace_map[n.z] && !subflow_map[n.z]) {
5635
5699
  n.z = activeWorkspace;
5636
5700
  }
@@ -5732,7 +5796,7 @@ RED.nodes = (function() {
5732
5796
  node.id = getID();
5733
5797
  } else {
5734
5798
  node.id = n.id;
5735
- const keepNodesCurrentZ = reimport && node.z && RED.workspaces.contains(node.z)
5799
+ const keepNodesCurrentZ = reimport && node.z && (RED.workspaces.contains(node.z) || RED.nodes.subflow(node.z))
5736
5800
  if (!keepNodesCurrentZ && (node.z == null || (!workspace_map[node.z] && !subflow_map[node.z]))) {
5737
5801
  if (createMissingWorkspace) {
5738
5802
  if (missingWorkspace === null) {
@@ -5980,19 +6044,6 @@ RED.nodes = (function() {
5980
6044
  if (n.g && !new_group_set.has(n.g)) {
5981
6045
  delete n.g;
5982
6046
  }
5983
- n.nodes = n.nodes.map(function(id) {
5984
- return node_map[id];
5985
- })
5986
- // Just in case the group references a node that doesn't exist for some reason
5987
- n.nodes = n.nodes.filter(function(v) {
5988
- if (v) {
5989
- // Repair any nodes that have forgotten they are in this group
5990
- if (v.g !== n.id) {
5991
- v.g = n.id;
5992
- }
5993
- }
5994
- return !!v
5995
- });
5996
6047
  if (!n.g) {
5997
6048
  groupDepthMap[n.id] = 0;
5998
6049
  }
@@ -6015,21 +6066,22 @@ RED.nodes = (function() {
6015
6066
  return groupDepthMap[A.id] - groupDepthMap[B.id];
6016
6067
  });
6017
6068
  for (i=0;i<new_groups.length;i++) {
6018
- n = new_groups[i];
6019
- addGroup(n);
6069
+ new_groups[i] = addGroup(new_groups[i]);
6070
+ node_map[new_groups[i].id] = new_groups[i]
6020
6071
  }
6021
6072
 
6022
6073
  for (i=0;i<new_junctions.length;i++) {
6023
- var junction = new_junctions[i];
6024
- addJunction(junction);
6074
+ new_junctions[i] = addJunction(new_junctions[i]);
6075
+ node_map[new_junctions[i].id] = new_junctions[i]
6025
6076
  }
6026
6077
 
6027
6078
 
6028
6079
  // Now the nodes have been fully updated, add them.
6029
6080
  for (i=0;i<new_nodes.length;i++) {
6030
- var node = new_nodes[i];
6031
- addNode(node);
6081
+ new_nodes[i] = addNode(new_nodes[i])
6082
+ node_map[new_nodes[i].id] = new_nodes[i]
6032
6083
  }
6084
+
6033
6085
  // Finally validate them all.
6034
6086
  // This has to be done after everything is added so that any checks for
6035
6087
  // dependent config nodes will pass
@@ -6037,6 +6089,39 @@ RED.nodes = (function() {
6037
6089
  var node = new_nodes[i];
6038
6090
  RED.editor.validateNode(node);
6039
6091
  }
6092
+ const lookupNode = (id) => {
6093
+ const mappedNode = node_map[id]
6094
+ if (!mappedNode) {
6095
+ return null
6096
+ }
6097
+ if (mappedNode.__isProxy__) {
6098
+ return mappedNode
6099
+ } else {
6100
+ return node_map[mappedNode.id]
6101
+ }
6102
+ }
6103
+ // Update groups to reference proxy node objects
6104
+ for (i=0;i<new_groups.length;i++) {
6105
+ n = new_groups[i];
6106
+ // bypass the proxy in case the flow is locked
6107
+ n.__node__.nodes = n.nodes.map(lookupNode)
6108
+ // Just in case the group references a node that doesn't exist for some reason
6109
+ n.__node__.nodes = n.nodes.filter(function(v) {
6110
+ if (v) {
6111
+ // Repair any nodes that have forgotten they are in this group
6112
+ if (v.g !== n.id) {
6113
+ v.g = n.id;
6114
+ }
6115
+ }
6116
+ return !!v
6117
+ });
6118
+ }
6119
+
6120
+ // Update links to use proxy node objects
6121
+ for (i=0;i<new_links.length;i++) {
6122
+ new_links[i].source = lookupNode(new_links[i].source.id) || new_links[i].source
6123
+ new_links[i].target = lookupNode(new_links[i].target.id) || new_links[i].target
6124
+ }
6040
6125
 
6041
6126
  RED.workspaces.refresh();
6042
6127
 
@@ -6165,11 +6250,17 @@ RED.nodes = (function() {
6165
6250
  junctions = {};
6166
6251
  junctionsByZ = {};
6167
6252
 
6253
+ var workspaceIds = Object.keys(workspaces);
6254
+ // Ensure all workspaces are unlocked so we don't get any edit-protection
6255
+ // preventing removal
6256
+ workspaceIds.forEach(function(id) {
6257
+ workspaces[id].locked = false
6258
+ });
6259
+
6168
6260
  var subflowIds = Object.keys(subflows);
6169
6261
  subflowIds.forEach(function(id) {
6170
6262
  RED.subflow.removeSubflow(id)
6171
6263
  });
6172
- var workspaceIds = Object.keys(workspaces);
6173
6264
  workspaceIds.forEach(function(id) {
6174
6265
  RED.workspaces.remove(workspaces[id]);
6175
6266
  });
@@ -6190,10 +6281,14 @@ RED.nodes = (function() {
6190
6281
  }
6191
6282
 
6192
6283
  function addGroup(group) {
6284
+ if (!group.__isProxy__) {
6285
+ group = new Proxy(group, nodeProxyHandler)
6286
+ }
6193
6287
  groupsByZ[group.z] = groupsByZ[group.z] || [];
6194
6288
  groupsByZ[group.z].push(group);
6195
6289
  groups[group.id] = group;
6196
6290
  RED.events.emit("groups:add",group);
6291
+ return group
6197
6292
  }
6198
6293
  function removeGroup(group) {
6199
6294
  var i = groupsByZ[group.z].indexOf(group);
@@ -6214,6 +6309,9 @@ RED.nodes = (function() {
6214
6309
  }
6215
6310
 
6216
6311
  function addJunction(junction) {
6312
+ if (!junction.__isProxy__) {
6313
+ junction = new Proxy(junction, nodeProxyHandler)
6314
+ }
6217
6315
  junctionsByZ[junction.z] = junctionsByZ[junction.z] || []
6218
6316
  junctionsByZ[junction.z].push(junction)
6219
6317
  junctions[junction.id] = junction;
@@ -6221,6 +6319,7 @@ RED.nodes = (function() {
6221
6319
  nodeLinks[junction.id] = {in:[],out:[]};
6222
6320
  }
6223
6321
  RED.events.emit("junctions:add", junction)
6322
+ return junction
6224
6323
  }
6225
6324
  function removeJunction(junction) {
6226
6325
  var i = junctionsByZ[junction.z].indexOf(junction)
@@ -6405,6 +6504,7 @@ RED.nodes = (function() {
6405
6504
  }
6406
6505
  });
6407
6506
 
6507
+ const nodeGroupMap = {}
6408
6508
  var replaceNodeIds = Object.keys(replaceNodes);
6409
6509
  if (replaceNodeIds.length > 0) {
6410
6510
  var reimportList = [];
@@ -6415,6 +6515,12 @@ RED.nodes = (function() {
6415
6515
  } else {
6416
6516
  allNodes.removeNode(n);
6417
6517
  }
6518
+ if (n.g) {
6519
+ // reimporting a node *without* including its group object
6520
+ // will cause the g property to be cleared. Cache it
6521
+ // here so we can restore it
6522
+ nodeGroupMap[n.id] = n.g
6523
+ }
6418
6524
  reimportList.push(convertNode(n));
6419
6525
  RED.events.emit('nodes:remove',n);
6420
6526
  });
@@ -6436,6 +6542,18 @@ RED.nodes = (function() {
6436
6542
  var newNodeMap = {};
6437
6543
  result.nodes.forEach(function(n) {
6438
6544
  newNodeMap[n.id] = n;
6545
+ if (nodeGroupMap[n.id]) {
6546
+ // This node is in a group - need to substitute the
6547
+ // node reference inside the group
6548
+ n.g = nodeGroupMap[n.id]
6549
+ const group = RED.nodes.group(n.g)
6550
+ if (group) {
6551
+ var index = group.nodes.findIndex(gn => gn.id === n.id)
6552
+ if (index > -1) {
6553
+ group.nodes[index] = n
6554
+ }
6555
+ }
6556
+ }
6439
6557
  });
6440
6558
  RED.nodes.eachLink(function(l) {
6441
6559
  if (newNodeMap.hasOwnProperty(l.source.id)) {
@@ -6496,7 +6614,7 @@ RED.nodes = (function() {
6496
6614
  },
6497
6615
  addWorkspace: addWorkspace,
6498
6616
  removeWorkspace: removeWorkspace,
6499
- getWorkspaceOrder: function() { return workspacesOrder },
6617
+ getWorkspaceOrder: function() { return [...workspacesOrder] },
6500
6618
  setWorkspaceOrder: function(order) { workspacesOrder = order; },
6501
6619
  workspace: getWorkspace,
6502
6620
 
@@ -7423,7 +7541,7 @@ RED.nodes.fontAwesome = (function() {
7423
7541
  * limitations under the License.
7424
7542
  **/
7425
7543
 
7426
- /**
7544
+ /**
7427
7545
  * An API for undo / redo history buffer
7428
7546
  * @namespace RED.history
7429
7547
  */
@@ -7843,7 +7961,9 @@ RED.history = (function() {
7843
7961
 
7844
7962
  if (ev.node.type === 'tab' && ev.changes.hasOwnProperty('disabled')) {
7845
7963
  $("#red-ui-tab-"+(ev.node.id.replace(".","-"))).toggleClass('red-ui-workspace-disabled',!!ev.node.disabled);
7846
- $("#red-ui-workspace").toggleClass("red-ui-workspace-disabled",!!ev.node.disabled);
7964
+ }
7965
+ if (ev.node.type === 'tab' && ev.changes.hasOwnProperty('locked')) {
7966
+ $("#red-ui-tab-"+(ev.node.id.replace(".","-"))).toggleClass('red-ui-workspace-locked',!!ev.node.locked);
7847
7967
  }
7848
7968
  if (ev.subflow) {
7849
7969
  inverseEv.subflow = {};
@@ -8251,6 +8371,52 @@ RED.validators = {
8251
8371
  };
8252
8372
  }
8253
8373
  };
8374
+ ;// Mermaid diagram stub library for on-demand dynamic loading
8375
+ // Will be overwritten after script loading by $.getScript
8376
+ var mermaid = (function () {
8377
+ var enabled /* = undefined */;
8378
+
8379
+ var initializing = false;
8380
+ var initCalled = false;
8381
+
8382
+ function initialize(opt) {
8383
+ if (enabled === undefined) {
8384
+ if (RED.settings.markdownEditor &&
8385
+ RED.settings.markdownEditor.mermaid) {
8386
+ enabled = RED.settings.markdownEditor.mermaid.enabled;
8387
+ }
8388
+ else {
8389
+ enabled = true;
8390
+ }
8391
+ }
8392
+ if (enabled) {
8393
+ initializing = true;
8394
+ $.getScript("vendor/mermaid/mermaid.min.js",
8395
+ function (data, stat, jqxhr) {
8396
+ $(".mermaid").show();
8397
+ // invoke loaded mermaid API
8398
+ initializing = false;
8399
+ mermaid.initialize(opt);
8400
+ if (initCalled) {
8401
+ mermaid.init();
8402
+ initCalled = false;
8403
+ }
8404
+ });
8405
+ }
8406
+ }
8407
+
8408
+ function init() {
8409
+ if (initializing) {
8410
+ $(".mermaid").hide();
8411
+ initCalled = true;
8412
+ }
8413
+ }
8414
+
8415
+ return {
8416
+ initialize: initialize,
8417
+ init: init,
8418
+ };
8419
+ })();
8254
8420
  ;/**
8255
8421
  * Copyright JS Foundation and other contributors, http://js.foundation
8256
8422
  *
@@ -8349,6 +8515,37 @@ RED.utils = (function() {
8349
8515
  }
8350
8516
  }
8351
8517
 
8518
+ var mermaidIsInitialized = false;
8519
+ var mermaidIsEnabled /* = undefined */;
8520
+
8521
+ renderer.code = function (code, lang) {
8522
+ if(lang === "mermaid") {
8523
+ // mermaid diagram rendering
8524
+ if (mermaidIsEnabled === undefined) {
8525
+ if (RED.settings.markdownEditor &&
8526
+ RED.settings.markdownEditor.mermaid) {
8527
+ mermaidIsEnabled = RED.settings.markdownEditor.mermaid.enabled;
8528
+ }
8529
+ else {
8530
+ mermaidIsEnabled = true;
8531
+ }
8532
+ }
8533
+ if (mermaidIsEnabled) {
8534
+ if (!mermaidIsInitialized) {
8535
+ mermaidIsInitialized = true;
8536
+ mermaid.initialize({startOnLoad:false});
8537
+ }
8538
+ return `<pre class='mermaid'>${code}</pre>`;
8539
+ }
8540
+ else {
8541
+ return `<details><summary>${RED._("markdownEditor.mermaid.summary")}</summary><pre><code>${code}</code></pre></details>`;
8542
+ }
8543
+ }
8544
+ else {
8545
+ return "<pre><code>" +code +"</code></pre>";
8546
+ }
8547
+ };
8548
+
8352
8549
  window._marked.setOptions({
8353
8550
  renderer: renderer,
8354
8551
  gfm: true,
@@ -9838,7 +10035,7 @@ RED.utils = (function() {
9838
10035
  this.element.css("maxHeight",null);
9839
10036
  }
9840
10037
  if (this.options.height !== 'auto') {
9841
- this.uiContainer.css("overflow-y","scroll");
10038
+ this.uiContainer.css("overflow-y","auto");
9842
10039
  if (!isNaN(this.options.height)) {
9843
10040
  this.uiHeight = this.options.height;
9844
10041
  }
@@ -11285,8 +11482,8 @@ RED.menu = (function() {
11285
11482
 
11286
11483
  var link = $(linkContent).appendTo(item);
11287
11484
  opt.link = link;
11288
- if (typeof opt.onselect === 'string') {
11289
- var shortcut = RED.keyboard.getShortcut(opt.onselect);
11485
+ if (typeof opt.onselect === 'string' || opt.shortcut) {
11486
+ var shortcut = opt.shortcut || RED.keyboard.getShortcut(opt.onselect);
11290
11487
  if (shortcut && shortcut.key) {
11291
11488
  opt.shortcutSpan = $('<span class="red-ui-popover-key">'+RED.keyboard.formatKey(shortcut.key, true)+'</span>').appendTo(link.find(".red-ui-menu-label"));
11292
11489
  }
@@ -12698,7 +12895,29 @@ RED.tabs = (function() {
12698
12895
  })
12699
12896
  }
12700
12897
 
12701
-
12898
+ if (options.contextmenu) {
12899
+ wrapper.on('contextmenu', function(evt) {
12900
+ let clickedTab
12901
+ let target = evt.target
12902
+ while(target.nodeName !== 'A' && target.nodeName !== 'UL' && target.nodeName !== 'BODY') {
12903
+ target = target.parentNode
12904
+ }
12905
+ if (target.nodeName === 'A') {
12906
+ const href = target.getAttribute('href')
12907
+ if (href) {
12908
+ clickedTab = tabs[href.slice(1)]
12909
+ }
12910
+ }
12911
+ evt.preventDefault()
12912
+ evt.stopPropagation()
12913
+ RED.contextMenu.show({
12914
+ x:evt.clientX-5,
12915
+ y:evt.clientY-5,
12916
+ options: options.contextmenu(clickedTab)
12917
+ })
12918
+ return false
12919
+ })
12920
+ }
12702
12921
 
12703
12922
  var scrollLeft;
12704
12923
  var scrollRight;
@@ -13364,19 +13583,19 @@ RED.tabs = (function() {
13364
13583
  event.preventDefault();
13365
13584
  removeTab(tab.id);
13366
13585
  });
13367
- RED.popover.tooltip(closeLink,RED._("workspace.hideFlow"));
13368
- }
13369
- if (tab.hideable) {
13370
- li.addClass("red-ui-tabs-closeable")
13371
- var closeLink = $("<a/>",{href:"#",class:"red-ui-tab-close red-ui-tab-hide"}).appendTo(li);
13372
- closeLink.append('<i class="fa fa-eye" />');
13373
- closeLink.append('<i class="fa fa-eye-slash" />');
13374
- closeLink.on("click",function(event) {
13375
- event.preventDefault();
13376
- hideTab(tab.id);
13377
- });
13378
- RED.popover.tooltip(closeLink,RED._("workspace.hideFlow"));
13379
- }
13586
+ RED.popover.tooltip(closeLink,RED._("workspace.closeFlow"));
13587
+ }
13588
+ // if (tab.hideable) {
13589
+ // li.addClass("red-ui-tabs-closeable")
13590
+ // var closeLink = $("<a/>",{href:"#",class:"red-ui-tab-close red-ui-tab-hide"}).appendTo(li);
13591
+ // closeLink.append('<i class="fa fa-eye" />');
13592
+ // closeLink.append('<i class="fa fa-eye-slash" />');
13593
+ // closeLink.on("click",function(event) {
13594
+ // event.preventDefault();
13595
+ // hideTab(tab.id);
13596
+ // });
13597
+ // RED.popover.tooltip(closeLink,RED._("workspace.hideFlow"));
13598
+ // }
13380
13599
 
13381
13600
  var badges = $('<span class="red-ui-tabs-badges"></span>').appendTo(li);
13382
13601
  if (options.onselect) {
@@ -13495,6 +13714,9 @@ RED.tabs = (function() {
13495
13714
  activeIndex: function() {
13496
13715
  return ul.find("li.active").index()
13497
13716
  },
13717
+ getTabIndex: function (id) {
13718
+ return ul.find("a[href='#"+id+"']").parent().index()
13719
+ },
13498
13720
  contains: function(id) {
13499
13721
  return ul.find("a[href='#"+id+"']").length > 0;
13500
13722
  },
@@ -13897,7 +14119,7 @@ RED.stack = (function() {
13897
14119
  { value: "reset", source: ["delay","trigger","join","rbe"] },
13898
14120
  { value: "responseCookies", source: ["http request"] },
13899
14121
  { value: "responseTopic", source: ["mqtt"] },
13900
- { value: "responseURL", source: ["http request"] },
14122
+ { value: "responseUrl", source: ["http request"] },
13901
14123
  { value: "restartTimeout", source: ["join"] },
13902
14124
  { value: "retain", source: ["mqtt"] },
13903
14125
  { value: "schema", source: ["json"] },
@@ -15933,6 +16155,11 @@ RED.deploy = (function() {
15933
16155
  RED.notify('<p>' + RED._("deploy.successfulDeploy") + '</p>', "success");
15934
16156
  }
15935
16157
  RED.nodes.eachNode(function (node) {
16158
+ const flow = node.z && (RED.nodes.workspace(node.z) || RED.nodes.subflow(node.z) || null);
16159
+ const isLocked = flow ? flow.locked : false;
16160
+ if (flow && isLocked) {
16161
+ flow.locked = false;
16162
+ }
15936
16163
  if (node.changed) {
15937
16164
  node.dirty = true;
15938
16165
  node.changed = false;
@@ -15944,6 +16171,9 @@ RED.deploy = (function() {
15944
16171
  if (node.credentials) {
15945
16172
  delete node.credentials;
15946
16173
  }
16174
+ if (flow && isLocked) {
16175
+ flow.locked = isLocked;
16176
+ }
15947
16177
  });
15948
16178
  RED.nodes.eachConfig(function (confNode) {
15949
16179
  confNode.changed = false;
@@ -18985,6 +19215,181 @@ RED.keyboard = (function() {
18985
19215
  enable: enable
18986
19216
  }
18987
19217
 
19218
+ })();
19219
+ ;RED.envVar = (function() {
19220
+ function saveEnvList(list) {
19221
+ const items = list.editableList("items")
19222
+ const new_env = [];
19223
+ items.each(function (i,el) {
19224
+ var data = el.data('data');
19225
+ var item;
19226
+ if (data.nameField && data.valueField) {
19227
+ item = {
19228
+ name: data.nameField.val(),
19229
+ value: data.valueField.typedInput("value"),
19230
+ type: data.valueField.typedInput("type")
19231
+ };
19232
+ new_env.push(item);
19233
+ }
19234
+ });
19235
+ return new_env;
19236
+ }
19237
+
19238
+ function getGlobalConf(create) {
19239
+ var gconf = null;
19240
+ RED.nodes.eachConfig(function (conf) {
19241
+ if (conf.type === "global-config") {
19242
+ gconf = conf;
19243
+ }
19244
+ });
19245
+ if ((gconf === null) && create) {
19246
+ var cred = {
19247
+ _ : {},
19248
+ map: {}
19249
+ };
19250
+ gconf = {
19251
+ id: RED.nodes.id(),
19252
+ type: "global-config",
19253
+ env: [],
19254
+ name: "global-config",
19255
+ label: "",
19256
+ hasUsers: false,
19257
+ users: [],
19258
+ credentials: cred,
19259
+ _def: RED.nodes.getType("global-config"),
19260
+ };
19261
+ RED.nodes.add(gconf);
19262
+ }
19263
+ return gconf;
19264
+ }
19265
+
19266
+ function applyChanges(list) {
19267
+ var gconf = getGlobalConf(false);
19268
+ var new_env = [];
19269
+ var items = list.editableList('items');
19270
+ var credentials = gconf ? gconf.credentials : null;
19271
+
19272
+ if (!credentials) {
19273
+ credentials = {
19274
+ _ : {},
19275
+ map: {}
19276
+ };
19277
+ }
19278
+ items.each(function (i,el) {
19279
+ var data = el.data('data');
19280
+ if (data.nameField && data.valueField) {
19281
+ var item = {
19282
+ name: data.nameField.val(),
19283
+ value: data.valueField.typedInput("value"),
19284
+ type: data.valueField.typedInput("type")
19285
+ };
19286
+ if (item.name.trim() !== "") {
19287
+ new_env.push(item);
19288
+ if ((item.type === "cred") && (item.value !== "__PWRD__")) {
19289
+ credentials.map[item.name] = item.value;
19290
+ credentials.map["has_"+item.name] = (item.value !== "");
19291
+ item.value = "__PWRD__";
19292
+ }
19293
+ }
19294
+ }
19295
+ });
19296
+ if (gconf === null) {
19297
+ gconf = getGlobalConf(true);
19298
+ }
19299
+ if ((JSON.stringify(new_env) !== JSON.stringify(gconf.env)) ||
19300
+ (JSON.stringify(credentials) !== JSON.stringify(gconf.credentials))) {
19301
+ gconf.env = new_env;
19302
+ gconf.credentials = credentials;
19303
+ RED.nodes.dirty(true);
19304
+ }
19305
+ }
19306
+
19307
+ function getSettingsPane() {
19308
+ var gconf = getGlobalConf(false);
19309
+ var env = gconf ? gconf.env : [];
19310
+ var cred = gconf ? gconf.credentials : null;
19311
+ if (!cred) {
19312
+ cred = {
19313
+ _ : {},
19314
+ map: {}
19315
+ };
19316
+ }
19317
+
19318
+ var pane = $("<div/>", {
19319
+ id: "red-ui-settings-tab-envvar",
19320
+ class: "form-horizontal"
19321
+ });
19322
+ var content = $("<div/>", {
19323
+ class: "form-row node-input-env-container-row"
19324
+ }).css({
19325
+ "margin": "10px"
19326
+ }).appendTo(pane);
19327
+
19328
+ var label = $("<label></label>").css({
19329
+ width: "100%"
19330
+ }).appendTo(content);
19331
+ $("<i/>", {
19332
+ class: "fa fa-list"
19333
+ }).appendTo(label);
19334
+ $("<span/>").text(" "+RED._("env-var.header")).appendTo(label);
19335
+
19336
+ var list = $("<ol/>", {
19337
+ id: "node-input-env-container"
19338
+ }).appendTo(content);
19339
+ var node = {
19340
+ type: "",
19341
+ env: env,
19342
+ credentials: cred.map,
19343
+ };
19344
+ RED.editor.envVarList.create(list, node);
19345
+
19346
+ var buttons = $("<div/>").css({
19347
+ "text-align": "right",
19348
+ }).appendTo(content);
19349
+ var revertButton = $("<button/>", {
19350
+ class: "red-ui-button"
19351
+ }).css({
19352
+ }).text(RED._("env-var.revert")).appendTo(buttons);
19353
+
19354
+ var items = saveEnvList(list);
19355
+ revertButton.on("click", function (ev) {
19356
+ list.editableList("empty");
19357
+ list.editableList("addItems", items);
19358
+ });
19359
+
19360
+ return pane;
19361
+ }
19362
+
19363
+ function init(done) {
19364
+ if (!RED.user.hasPermission("settings.write")) {
19365
+ RED.notify(RED._("user.errors.settings"),"error");
19366
+ return;
19367
+ }
19368
+ RED.userSettings.add({
19369
+ id:'envvar',
19370
+ title: RED._("env-var.environment"),
19371
+ get: getSettingsPane,
19372
+ focus: function() {
19373
+ var height = $("#red-ui-settings-tab-envvar").parent().height();
19374
+ $("#node-input-env-container").editableList("height", (height -100));
19375
+ },
19376
+ close: function() {
19377
+ var list = $("#node-input-env-container");
19378
+ try {
19379
+ applyChanges(list);
19380
+ }
19381
+ catch (e) {
19382
+ console.log(e);
19383
+ console.log(e.stack);
19384
+ }
19385
+ }
19386
+ });
19387
+ }
19388
+
19389
+ return {
19390
+ init: init,
19391
+ };
19392
+
18988
19393
  })();
18989
19394
  ;/**
18990
19395
  * Copyright JS Foundation and other contributors, http://js.foundation
@@ -19046,6 +19451,9 @@ RED.workspaces = (function() {
19046
19451
  if (!ws.closeable) {
19047
19452
  ws.hideable = true;
19048
19453
  }
19454
+ if (!ws.hasOwnProperty('locked')) {
19455
+ ws.locked = false
19456
+ }
19049
19457
  workspace_tabs.addTab(ws,targetIndex);
19050
19458
 
19051
19459
  var hiddenTabs = JSON.parse(RED.settings.getLocal("hiddenTabs")||"{}");
@@ -19063,6 +19471,7 @@ RED.workspaces = (function() {
19063
19471
  type: "tab",
19064
19472
  id: tabId,
19065
19473
  disabled: false,
19474
+ locked: false,
19066
19475
  info: "",
19067
19476
  label: RED._('workspace.defaultName',{number:workspaceIndex}),
19068
19477
  env: [],
@@ -19087,6 +19496,9 @@ RED.workspaces = (function() {
19087
19496
  if (workspaceTabCount === 1) {
19088
19497
  return;
19089
19498
  }
19499
+ if (ws.locked) {
19500
+ return
19501
+ }
19090
19502
  var workspaceOrder = RED.nodes.getWorkspaceOrder();
19091
19503
  ws._index = workspaceOrder.indexOf(ws.id);
19092
19504
  removeWorkspace(ws);
@@ -19107,13 +19519,206 @@ RED.workspaces = (function() {
19107
19519
  RED.editor.editSubflow(subflow);
19108
19520
  }
19109
19521
  } else {
19110
- RED.editor.editFlow(workspace);
19522
+ if (!workspace.locked) {
19523
+ RED.editor.editFlow(workspace);
19524
+ }
19111
19525
  }
19112
19526
  }
19113
19527
 
19114
19528
 
19115
19529
  var workspace_tabs;
19116
19530
  var workspaceTabCount = 0;
19531
+
19532
+ function getMenuItems(isMenuButton, tab) {
19533
+ let hiddenFlows = new Set()
19534
+ for (let i = 0; i < hideStack.length; i++) {
19535
+ let ids = hideStack[i]
19536
+ if (!Array.isArray(ids)) {
19537
+ ids = [ids]
19538
+ }
19539
+ ids.forEach(id => {
19540
+ if (RED.nodes.workspace(id)) {
19541
+ hiddenFlows.add(id)
19542
+ }
19543
+ })
19544
+ }
19545
+ const hiddenflowCount = hiddenFlows.size;
19546
+ let activeWorkspace = tab || RED.nodes.workspace(RED.workspaces.active()) || RED.nodes.subflow(RED.workspaces.active())
19547
+ let isFlowDisabled = activeWorkspace ? activeWorkspace.disabled : false
19548
+ const currentTabs = workspace_tabs.listTabs();
19549
+ let flowCount = 0;
19550
+ currentTabs.forEach(tab => {
19551
+ if (RED.nodes.workspace(tab)) {
19552
+ flowCount++;
19553
+ }
19554
+ });
19555
+
19556
+ let isCurrentLocked = RED.workspaces.isActiveLocked()
19557
+ if (tab) {
19558
+ isCurrentLocked = tab.locked
19559
+ }
19560
+
19561
+ var menuItems = []
19562
+ if (isMenuButton) {
19563
+ menuItems.push({
19564
+ id:"red-ui-tabs-menu-option-search-flows",
19565
+ label: RED._("workspace.listFlows"),
19566
+ onselect: "core:list-flows"
19567
+ },
19568
+ {
19569
+ id:"red-ui-tabs-menu-option-search-subflows",
19570
+ label: RED._("workspace.listSubflows"),
19571
+ onselect: "core:list-subflows"
19572
+ },
19573
+ null)
19574
+ }
19575
+ menuItems.push(
19576
+ {
19577
+ id:"red-ui-tabs-menu-option-add-flow",
19578
+ label: RED._("workspace.addFlow"),
19579
+ onselect: "core:add-flow"
19580
+ }
19581
+ )
19582
+ if (isMenuButton || !!tab) {
19583
+ menuItems.push(
19584
+ {
19585
+ id:"red-ui-tabs-menu-option-add-flow-right",
19586
+ label: RED._("workspace.addFlowToRight"),
19587
+ shortcut: RED.keyboard.getShortcut("core:add-flow-to-right"),
19588
+ onselect: function() {
19589
+ RED.actions.invoke("core:add-flow-to-right", tab)
19590
+ }
19591
+ },
19592
+ null
19593
+ )
19594
+ if (activeWorkspace && activeWorkspace.type === 'tab') {
19595
+ menuItems.push(
19596
+ isFlowDisabled ? {
19597
+ label: RED._("workspace.enableFlow"),
19598
+ shortcut: RED.keyboard.getShortcut("core:enable-flow"),
19599
+ onselect: function() {
19600
+ RED.actions.invoke("core:enable-flow", tab?tab.id:undefined)
19601
+ },
19602
+ disabled: isCurrentLocked
19603
+ } : {
19604
+ label: RED._("workspace.disableFlow"),
19605
+ shortcut: RED.keyboard.getShortcut("core:disable-flow"),
19606
+ onselect: function() {
19607
+ RED.actions.invoke("core:disable-flow", tab?tab.id:undefined)
19608
+ },
19609
+ disabled: isCurrentLocked
19610
+ },
19611
+ isCurrentLocked? {
19612
+ label: RED._("workspace.unlockFlow"),
19613
+ shortcut: RED.keyboard.getShortcut("core:unlock-flow"),
19614
+ onselect: function() {
19615
+ RED.actions.invoke('core:unlock-flow', tab?tab.id:undefined)
19616
+ }
19617
+ } : {
19618
+ label: RED._("workspace.lockFlow"),
19619
+ shortcut: RED.keyboard.getShortcut("core:lock-flow"),
19620
+ onselect: function() {
19621
+ RED.actions.invoke('core:lock-flow', tab?tab.id:undefined)
19622
+ }
19623
+ },
19624
+ null
19625
+ )
19626
+ }
19627
+ const activeIndex = currentTabs.findIndex(id => (activeWorkspace && (id === activeWorkspace.id)));
19628
+ menuItems.push(
19629
+ {
19630
+ label: RED._("workspace.moveToStart"),
19631
+ shortcut: RED.keyboard.getShortcut("core:move-flow-to-start"),
19632
+ onselect: function() {
19633
+ RED.actions.invoke("core:move-flow-to-start", tab?tab.id:undefined)
19634
+ },
19635
+ disabled: activeIndex === 0
19636
+ },
19637
+ {
19638
+ label: RED._("workspace.moveToEnd"),
19639
+ shortcut: RED.keyboard.getShortcut("core:move-flow-to-end"),
19640
+ onselect: function() {
19641
+ RED.actions.invoke("core:move-flow-to-end", tab?tab.id:undefined)
19642
+ },
19643
+ disabled: activeIndex === currentTabs.length - 1
19644
+ }
19645
+ )
19646
+ }
19647
+ menuItems.push(null)
19648
+ if (isMenuButton || !!tab) {
19649
+ menuItems.push(
19650
+ {
19651
+ id:"red-ui-tabs-menu-option-add-hide-flows",
19652
+ label: RED._("workspace.hideFlow"),
19653
+ shortcut: RED.keyboard.getShortcut("core:hide-flow"),
19654
+ onselect: function() {
19655
+ RED.actions.invoke("core:hide-flow", tab)
19656
+ }
19657
+ },
19658
+ {
19659
+ id:"red-ui-tabs-menu-option-add-hide-other-flows",
19660
+ label: RED._("workspace.hideOtherFlows"),
19661
+ shortcut: RED.keyboard.getShortcut("core:hide-other-flows"),
19662
+ onselect: function() {
19663
+ RED.actions.invoke("core:hide-other-flows", tab)
19664
+ }
19665
+ }
19666
+ )
19667
+
19668
+ }
19669
+
19670
+ menuItems.push(
19671
+ {
19672
+ id:"red-ui-tabs-menu-option-add-hide-all-flows",
19673
+ label: RED._("workspace.hideAllFlows"),
19674
+ onselect: "core:hide-all-flows",
19675
+ disabled: (hiddenflowCount === flowCount)
19676
+ },
19677
+ {
19678
+ id:"red-ui-tabs-menu-option-add-show-all-flows",
19679
+ disabled: hiddenflowCount === 0,
19680
+ label: RED._("workspace.showAllFlows", { count: hiddenflowCount }),
19681
+ onselect: "core:show-all-flows"
19682
+ },
19683
+ {
19684
+ id:"red-ui-tabs-menu-option-add-show-last-flow",
19685
+ disabled: hideStack.length === 0,
19686
+ label: RED._("workspace.showLastHiddenFlow"),
19687
+ onselect: "core:show-last-hidden-flow"
19688
+ }
19689
+ )
19690
+ if (tab) {
19691
+ menuItems.push(
19692
+ null,
19693
+ {
19694
+ label: RED._("common.label.delete"),
19695
+ onselect: function() {
19696
+ if (tab.type === 'tab') {
19697
+ RED.workspaces.delete(tab)
19698
+ } else if (tab.type === 'subflow') {
19699
+ RED.subflow.delete(tab.id)
19700
+ }
19701
+ },
19702
+ disabled: isCurrentLocked || (workspaceTabCount === 1)
19703
+ },
19704
+ {
19705
+ label: RED._("menu.label.export"),
19706
+ shortcut: RED.keyboard.getShortcut("core:show-export-dialog"),
19707
+ onselect: function() {
19708
+ RED.workspaces.show(tab.id)
19709
+ RED.actions.invoke('core:show-export-dialog', null, 'flow')
19710
+ }
19711
+ }
19712
+ )
19713
+ }
19714
+ // if (isMenuButton && hiddenflowCount > 0) {
19715
+ // menuItems.unshift({
19716
+ // label: RED._("workspace.hiddenFlows",{count: hiddenflowCount}),
19717
+ // onselect: "core:list-hidden-flows"
19718
+ // })
19719
+ // }
19720
+ return menuItems;
19721
+ }
19117
19722
  function createWorkspaceTabs() {
19118
19723
  workspace_tabs = RED.tabs.create({
19119
19724
  id: "red-ui-workspace-tabs",
@@ -19125,8 +19730,9 @@ RED.workspaces = (function() {
19125
19730
  $("#red-ui-workspace-chart").show();
19126
19731
  activeWorkspace = tab.id;
19127
19732
  window.location.hash = 'flow/'+tab.id;
19128
- $("#red-ui-workspace").toggleClass("red-ui-workspace-disabled",!!tab.disabled);
19129
- } else {
19733
+ $("#red-ui-workspace").toggleClass("red-ui-workspace-disabled", !!tab.disabled);
19734
+ $("#red-ui-workspace").toggleClass("red-ui-workspace-locked", !!tab.locked);
19735
+ } else {
19130
19736
  $("#red-ui-workspace-chart").hide();
19131
19737
  activeWorkspace = 0;
19132
19738
  window.location.hash = '';
@@ -19157,6 +19763,12 @@ RED.workspaces = (function() {
19157
19763
  if (tab.disabled) {
19158
19764
  $("#red-ui-tab-"+(tab.id.replace(".","-"))).addClass('red-ui-workspace-disabled');
19159
19765
  }
19766
+ $('<span class="red-ui-workspace-locked-icon"><i class="fa fa-lock"></i> </span>').prependTo("#red-ui-tab-"+(tab.id.replace(".","-"))+" .red-ui-tab-label");
19767
+ if (tab.locked) {
19768
+ $("#red-ui-tab-"+(tab.id.replace(".","-"))).addClass('red-ui-workspace-locked');
19769
+ }
19770
+
19771
+
19160
19772
  RED.menu.setDisabled("menu-item-workspace-delete",activeWorkspace === 0 || workspaceTabCount <= 1);
19161
19773
  if (workspaceTabCount === 1) {
19162
19774
  showWorkspace();
@@ -19177,13 +19789,19 @@ RED.workspaces = (function() {
19177
19789
  RED.history.push({
19178
19790
  t:'reorder',
19179
19791
  workspaces: {
19180
- from:oldOrder,
19181
- to:newOrder
19792
+ from: oldOrder,
19793
+ to: newOrder
19182
19794
  },
19183
19795
  dirty:RED.nodes.dirty()
19184
19796
  });
19185
- RED.nodes.dirty(true);
19186
- setWorkspaceOrder(newOrder);
19797
+ // Only mark flows dirty if flow-order has changed (excluding subflows)
19798
+ const filteredOldOrder = oldOrder.filter(id => !!RED.nodes.workspace(id))
19799
+ const filteredNewOrder = newOrder.filter(id => !!RED.nodes.workspace(id))
19800
+
19801
+ if (JSON.stringify(filteredOldOrder) !== JSON.stringify(filteredNewOrder)) {
19802
+ RED.nodes.dirty(true);
19803
+ setWorkspaceOrder(newOrder);
19804
+ }
19187
19805
  },
19188
19806
  onselect: function(selectedTabs) {
19189
19807
  RED.view.select(false)
@@ -19202,12 +19820,12 @@ RED.workspaces = (function() {
19202
19820
  },
19203
19821
  onhide: function(tab) {
19204
19822
  hideStack.push(tab.id);
19205
-
19206
- var hiddenTabs = JSON.parse(RED.settings.getLocal("hiddenTabs")||"{}");
19207
- hiddenTabs[tab.id] = true;
19208
- RED.settings.setLocal("hiddenTabs",JSON.stringify(hiddenTabs));
19209
-
19210
- RED.events.emit("workspace:hide",{workspace: tab.id})
19823
+ if (tab.type === "tab") {
19824
+ var hiddenTabs = JSON.parse(RED.settings.getLocal("hiddenTabs")||"{}");
19825
+ hiddenTabs[tab.id] = true;
19826
+ RED.settings.setLocal("hiddenTabs",JSON.stringify(hiddenTabs));
19827
+ RED.events.emit("workspace:hide",{workspace: tab.id})
19828
+ }
19211
19829
  },
19212
19830
  onshow: function(tab) {
19213
19831
  removeFromHideStack(tab.id);
@@ -19222,77 +19840,8 @@ RED.workspaces = (function() {
19222
19840
  scrollable: true,
19223
19841
  addButton: "core:add-flow",
19224
19842
  addButtonCaption: RED._("workspace.addFlow"),
19225
- menu: function() {
19226
- var menuItems = [
19227
- {
19228
- id:"red-ui-tabs-menu-option-search-flows",
19229
- label: RED._("workspace.listFlows"),
19230
- onselect: "core:list-flows"
19231
- },
19232
- {
19233
- id:"red-ui-tabs-menu-option-search-subflows",
19234
- label: RED._("workspace.listSubflows"),
19235
- onselect: "core:list-subflows"
19236
- },
19237
- null,
19238
- {
19239
- id:"red-ui-tabs-menu-option-add-flow",
19240
- label: RED._("workspace.addFlow"),
19241
- onselect: "core:add-flow"
19242
- },
19243
- {
19244
- id:"red-ui-tabs-menu-option-add-flow-right",
19245
- label: RED._("workspace.addFlowToRight"),
19246
- onselect: "core:add-flow-to-right"
19247
- },
19248
- null,
19249
- {
19250
- id:"red-ui-tabs-menu-option-add-hide-flows",
19251
- label: RED._("workspace.hideFlow"),
19252
- onselect: "core:hide-flow"
19253
- },
19254
- {
19255
- id:"red-ui-tabs-menu-option-add-hide-other-flows",
19256
- label: RED._("workspace.hideOtherFlows"),
19257
- onselect: "core:hide-other-flows"
19258
- },
19259
- {
19260
- id:"red-ui-tabs-menu-option-add-show-all-flows",
19261
- label: RED._("workspace.showAllFlows"),
19262
- onselect: "core:show-all-flows"
19263
- },
19264
- {
19265
- id:"red-ui-tabs-menu-option-add-hide-all-flows",
19266
- label: RED._("workspace.hideAllFlows"),
19267
- onselect: "core:hide-all-flows"
19268
- },
19269
- {
19270
- id:"red-ui-tabs-menu-option-add-show-last-flow",
19271
- label: RED._("workspace.showLastHiddenFlow"),
19272
- onselect: "core:show-last-hidden-flow"
19273
- }
19274
- ]
19275
- let hiddenFlows = new Set()
19276
- for (let i = 0; i < hideStack.length; i++) {
19277
- let ids = hideStack[i]
19278
- if (!Array.isArray(ids)) {
19279
- ids = [ids]
19280
- }
19281
- ids.forEach(id => {
19282
- if (RED.nodes.workspace(id)) {
19283
- hiddenFlows.add(id)
19284
- }
19285
- })
19286
- }
19287
- const flowCount = hiddenFlows.size;
19288
- if (flowCount > 0) {
19289
- menuItems.unshift({
19290
- label: RED._("workspace.hiddenFlows",{count: flowCount}),
19291
- onselect: "core:list-hidden-flows"
19292
- })
19293
- }
19294
- return menuItems;
19295
- }
19843
+ menu: function() { return getMenuItems(true) },
19844
+ contextmenu: function(tab) { return getMenuItems(false, tab) }
19296
19845
  });
19297
19846
  workspaceTabCount = 0;
19298
19847
  }
@@ -19343,16 +19892,33 @@ RED.workspaces = (function() {
19343
19892
  });
19344
19893
 
19345
19894
  RED.actions.add("core:add-flow",function(opts) { addWorkspace(undefined,undefined,opts?opts.index:undefined)});
19346
- RED.actions.add("core:add-flow-to-right",function(opts) { addWorkspace(undefined,undefined,workspace_tabs.activeIndex()+1)});
19895
+ RED.actions.add("core:add-flow-to-right",function(workspace) {
19896
+ let index
19897
+ if (workspace) {
19898
+ index = workspace_tabs.getTabIndex(workspace.id)+1
19899
+ } else {
19900
+ index = workspace_tabs.activeIndex()+1
19901
+ }
19902
+ addWorkspace(undefined,undefined,index)
19903
+ });
19347
19904
  RED.actions.add("core:edit-flow",editWorkspace);
19348
19905
  RED.actions.add("core:remove-flow",removeWorkspace);
19349
19906
  RED.actions.add("core:enable-flow",enableWorkspace);
19350
19907
  RED.actions.add("core:disable-flow",disableWorkspace);
19351
-
19352
- RED.actions.add("core:hide-flow", function() {
19353
- var selection = workspace_tabs.selection();
19354
- if (selection.length === 0) {
19355
- selection = [{id:activeWorkspace}]
19908
+ RED.actions.add("core:lock-flow",lockWorkspace);
19909
+ RED.actions.add("core:unlock-flow",unlockWorkspace);
19910
+ RED.actions.add("core:move-flow-to-start", function(id) { moveWorkspace(id, 'start') });
19911
+ RED.actions.add("core:move-flow-to-end", function(id) { moveWorkspace(id, 'end') });
19912
+
19913
+ RED.actions.add("core:hide-flow", function(workspace) {
19914
+ let selection
19915
+ if (workspace) {
19916
+ selection = [workspace]
19917
+ } else {
19918
+ selection = workspace_tabs.selection();
19919
+ if (selection.length === 0) {
19920
+ selection = [{id:activeWorkspace}]
19921
+ }
19356
19922
  }
19357
19923
  var hiddenTabs = [];
19358
19924
  selection.forEach(function(ws) {
@@ -19366,10 +19932,15 @@ RED.workspaces = (function() {
19366
19932
  workspace_tabs.clearSelection();
19367
19933
  })
19368
19934
 
19369
- RED.actions.add("core:hide-other-flows", function() {
19370
- var selection = workspace_tabs.selection();
19371
- if (selection.length === 0) {
19372
- selection = [{id:activeWorkspace}]
19935
+ RED.actions.add("core:hide-other-flows", function(workspace) {
19936
+ let selection
19937
+ if (workspace) {
19938
+ selection = [workspace]
19939
+ } else {
19940
+ selection = workspace_tabs.selection();
19941
+ if (selection.length === 0) {
19942
+ selection = [{id:activeWorkspace}]
19943
+ }
19373
19944
  }
19374
19945
  var selected = new Set(selection.map(function(ws) { return ws.id }))
19375
19946
 
@@ -19474,7 +20045,7 @@ RED.workspaces = (function() {
19474
20045
  }
19475
20046
  function setWorkspaceState(id,disabled) {
19476
20047
  var workspace = RED.nodes.workspace(id||activeWorkspace);
19477
- if (!workspace) {
20048
+ if (!workspace || workspace.locked) {
19478
20049
  return;
19479
20050
  }
19480
20051
  if (workspace.disabled !== disabled) {
@@ -19509,11 +20080,58 @@ RED.workspaces = (function() {
19509
20080
  }
19510
20081
  }
19511
20082
  }
20083
+ function lockWorkspace(id) {
20084
+ setWorkspaceLockState(id,true);
20085
+ }
20086
+ function unlockWorkspace(id) {
20087
+ setWorkspaceLockState(id,false);
20088
+ }
20089
+ function setWorkspaceLockState(id,locked) {
20090
+ var workspace = RED.nodes.workspace(id||activeWorkspace);
20091
+ if (!workspace) {
20092
+ return;
20093
+ }
20094
+ if (workspace.locked !== locked) {
20095
+ var changes = { locked: workspace.locked };
20096
+ workspace.locked = locked;
20097
+ $("#red-ui-tab-"+(workspace.id.replace(".","-"))).toggleClass('red-ui-workspace-locked',!!workspace.locked);
20098
+ if (!id || (id === activeWorkspace)) {
20099
+ $("#red-ui-workspace").toggleClass("red-ui-workspace-locked",!!workspace.locked);
20100
+ }
20101
+ var historyEvent = {
20102
+ t: "edit",
20103
+ changes:changes,
20104
+ node: workspace,
20105
+ dirty: RED.nodes.dirty()
20106
+ }
20107
+ workspace.changed = true;
20108
+ RED.history.push(historyEvent);
20109
+ RED.events.emit("flows:change",workspace);
20110
+ 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
+ // }
20124
+ }
20125
+ }
19512
20126
 
19513
20127
  function removeWorkspace(ws) {
19514
20128
  if (!ws) {
19515
- deleteWorkspace(RED.nodes.workspace(activeWorkspace));
20129
+ ws = RED.nodes.workspace(activeWorkspace)
20130
+ if (ws && !ws.locked) {
20131
+ deleteWorkspace(RED.nodes.workspace(activeWorkspace));
20132
+ }
19516
20133
  } else {
20134
+ if (ws.locked) { return }
19517
20135
  if (workspace_tabs.contains(ws.id)) {
19518
20136
  workspace_tabs.removeTab(ws.id);
19519
20137
  }
@@ -19523,16 +20141,46 @@ RED.workspaces = (function() {
19523
20141
  }
19524
20142
  }
19525
20143
 
20144
+ function moveWorkspace(id, direction) {
20145
+ const workspace = RED.nodes.workspace(id||activeWorkspace) || RED.nodes.subflow(id||activeWorkspace);
20146
+ if (!workspace) {
20147
+ return;
20148
+ }
20149
+ const currentOrder = workspace_tabs.listTabs()
20150
+ const oldOrder = [...currentOrder]
20151
+ const currentIndex = currentOrder.findIndex(id => id === workspace.id)
20152
+ currentOrder.splice(currentIndex, 1)
20153
+ if (direction === 'start') {
20154
+ currentOrder.unshift(workspace.id)
20155
+ } else if (direction === 'end') {
20156
+ currentOrder.push(workspace.id)
20157
+ }
20158
+ const newOrder = setWorkspaceOrder(currentOrder)
20159
+ if (JSON.stringify(newOrder) !== JSON.stringify(oldOrder)) {
20160
+ RED.history.push({
20161
+ t:'reorder',
20162
+ workspaces: {
20163
+ from:oldOrder,
20164
+ to:newOrder
20165
+ },
20166
+ dirty:RED.nodes.dirty()
20167
+ });
20168
+ const filteredOldOrder = oldOrder.filter(id => !!RED.nodes.workspace(id))
20169
+ const filteredNewOrder = newOrder.filter(id => !!RED.nodes.workspace(id))
20170
+ if (JSON.stringify(filteredOldOrder) !== JSON.stringify(filteredNewOrder)) {
20171
+ RED.nodes.dirty(true);
20172
+ }
20173
+ }
20174
+ }
19526
20175
  function setWorkspaceOrder(order) {
19527
- var newOrder = order.filter(function(id) {
19528
- return RED.nodes.workspace(id) !== undefined;
19529
- })
20176
+ var newOrder = order.filter(id => !!RED.nodes.workspace(id))
19530
20177
  var currentOrder = RED.nodes.getWorkspaceOrder();
19531
20178
  if (JSON.stringify(newOrder) !== JSON.stringify(currentOrder)) {
19532
20179
  RED.nodes.setWorkspaceOrder(newOrder);
19533
20180
  RED.events.emit("flows:reorder",newOrder);
19534
20181
  }
19535
20182
  workspace_tabs.order(order);
20183
+ return newOrder
19536
20184
  }
19537
20185
 
19538
20186
  function flashTab(tabId) {
@@ -19578,6 +20226,10 @@ RED.workspaces = (function() {
19578
20226
  active: function() {
19579
20227
  return activeWorkspace
19580
20228
  },
20229
+ isActiveLocked: function() {
20230
+ var ws = RED.nodes.workspace(activeWorkspace) || RED.nodes.subflow(activeWorkspace)
20231
+ return ws && ws.locked
20232
+ },
19581
20233
  selection: function() {
19582
20234
  return workspace_tabs.selection();
19583
20235
  },
@@ -19634,7 +20286,9 @@ RED.workspaces = (function() {
19634
20286
  workspace_tabs.resize();
19635
20287
  },
19636
20288
  enable: enableWorkspace,
19637
- disable: disableWorkspace
20289
+ disable: disableWorkspace,
20290
+ lock: lockWorkspace,
20291
+ unlock: unlockWorkspace
19638
20292
  }
19639
20293
  })();
19640
20294
  ;/**
@@ -19744,6 +20398,7 @@ RED.view = (function() {
19744
20398
  var spliceTimer;
19745
20399
  var groupHoverTimer;
19746
20400
 
20401
+ var activeFlowLocked = false;
19747
20402
  var activeSubflow = null;
19748
20403
  var activeNodes = [];
19749
20404
  var activeLinks = [];
@@ -19901,6 +20556,7 @@ RED.view = (function() {
19901
20556
  evt.preventDefault()
19902
20557
  evt.stopPropagation()
19903
20558
  RED.contextMenu.show({
20559
+ type: 'workspace',
19904
20560
  x:evt.clientX-5,
19905
20561
  y:evt.clientY-5
19906
20562
  })
@@ -20100,8 +20756,19 @@ RED.view = (function() {
20100
20756
 
20101
20757
  activeSubflow = RED.nodes.subflow(event.workspace);
20102
20758
 
20103
- RED.menu.setDisabled("menu-item-workspace-edit", activeSubflow || event.workspace === 0);
20104
- RED.menu.setDisabled("menu-item-workspace-delete",event.workspace === 0 || RED.workspaces.count() == 1 || activeSubflow);
20759
+ if (activeSubflow) {
20760
+ activeFlowLocked = activeSubflow.locked
20761
+ } else {
20762
+ var activeWorkspace = RED.nodes.workspace(event.workspace)
20763
+ if (activeWorkspace) {
20764
+ activeFlowLocked = activeWorkspace.locked
20765
+ } else {
20766
+ activeFlowLocked = true
20767
+ }
20768
+ }
20769
+
20770
+ RED.menu.setDisabled("menu-item-workspace-edit", activeFlowLocked || activeSubflow || event.workspace === 0);
20771
+ RED.menu.setDisabled("menu-item-workspace-delete",activeFlowLocked || event.workspace === 0 || RED.workspaces.count() == 1 || activeSubflow);
20105
20772
 
20106
20773
  if (workspaceScrollPositions[event.workspace]) {
20107
20774
  chart.scrollLeft(workspaceScrollPositions[event.workspace].left);
@@ -20128,6 +20795,15 @@ RED.view = (function() {
20128
20795
  redraw();
20129
20796
  });
20130
20797
 
20798
+ RED.events.on("flows:change", function(workspace) {
20799
+ if (workspace.id === RED.workspaces.active()) {
20800
+ activeFlowLocked = !!workspace.locked
20801
+ $("#red-ui-workspace").toggleClass("red-ui-workspace-disabled",!!workspace.disabled);
20802
+ $("#red-ui-workspace").toggleClass("red-ui-workspace-locked",!!workspace.locked);
20803
+
20804
+ }
20805
+ })
20806
+
20131
20807
  RED.statusBar.add({
20132
20808
  id: "view-zoom-controls",
20133
20809
  align: "right",
@@ -20185,6 +20861,9 @@ RED.view = (function() {
20185
20861
  chart.droppable({
20186
20862
  accept:".red-ui-palette-node",
20187
20863
  drop: function( event, ui ) {
20864
+ if (activeFlowLocked) {
20865
+ return
20866
+ }
20188
20867
  d3.event = event;
20189
20868
  var selected_tool = $(ui.draggable[0]).attr("data-palette-type");
20190
20869
  var result = createNode(selected_tool);
@@ -20192,9 +20871,7 @@ RED.view = (function() {
20192
20871
  return;
20193
20872
  }
20194
20873
  var historyEvent = result.historyEvent;
20195
- var nn = result.node;
20196
-
20197
- RED.nodes.add(nn);
20874
+ var nn = RED.nodes.add(result.node);
20198
20875
 
20199
20876
  var showLabel = RED.utils.getMessageProperty(RED.settings.get('editor'),"view.view-node-show-label");
20200
20877
  if (showLabel !== undefined && (nn._def.hasOwnProperty("showLabel")?nn._def.showLabel:true) && !nn._def.defaults.hasOwnProperty("l")) {
@@ -20276,11 +20953,28 @@ RED.view = (function() {
20276
20953
 
20277
20954
  var group = $(ui.helper).data("group");
20278
20955
  if (group) {
20956
+ var oldX = group.x;
20957
+ var oldY = group.y;
20279
20958
  RED.group.addToGroup(group, nn);
20959
+ var moveEvent = null;
20960
+ if ((group.x !== oldX) ||
20961
+ (group.y !== oldY)) {
20962
+ moveEvent = {
20963
+ t: "move",
20964
+ nodes: [{n: group,
20965
+ ox: oldX, oy: oldY,
20966
+ dx: group.x -oldX,
20967
+ dy: group.y -oldY}],
20968
+ dirty: true
20969
+ };
20970
+ }
20280
20971
  historyEvent = {
20281
20972
  t: 'multi',
20282
20973
  events: [historyEvent],
20283
20974
 
20975
+ };
20976
+ if (moveEvent) {
20977
+ historyEvent.events.push(moveEvent)
20284
20978
  }
20285
20979
  historyEvent.events.push({
20286
20980
  t: "addToGroup",
@@ -20321,6 +21015,9 @@ RED.view = (function() {
20321
21015
  RED.actions.add("core:copy-selection-to-internal-clipboard",copySelection);
20322
21016
  RED.actions.add("core:cut-selection-to-internal-clipboard",function(){copySelection(true);deleteSelection();});
20323
21017
  RED.actions.add("core:paste-from-internal-clipboard",function(){
21018
+ if (RED.workspaces.isActiveLocked()) {
21019
+ return
21020
+ }
20324
21021
  importNodes(clipboard,{generateIds: clipboardSource === 'copy', generateDefaultNames: clipboardSource === 'copy'});
20325
21022
  });
20326
21023
 
@@ -20329,22 +21026,27 @@ RED.view = (function() {
20329
21026
  RED.events.on("view:selection-changed", function(selection) {
20330
21027
  var hasSelection = (selection.nodes && selection.nodes.length > 0);
20331
21028
  var hasMultipleSelection = hasSelection && selection.nodes.length > 1;
20332
- RED.menu.setDisabled("menu-item-edit-cut",!hasSelection);
20333
- RED.menu.setDisabled("menu-item-edit-copy",!hasSelection);
20334
- RED.menu.setDisabled("menu-item-edit-select-connected",!hasSelection);
20335
- RED.menu.setDisabled("menu-item-view-tools-move-to-back",!hasSelection);
20336
- RED.menu.setDisabled("menu-item-view-tools-move-to-front",!hasSelection);
20337
- RED.menu.setDisabled("menu-item-view-tools-move-backwards",!hasSelection);
20338
- RED.menu.setDisabled("menu-item-view-tools-move-forwards",!hasSelection);
20339
-
20340
- RED.menu.setDisabled("menu-item-view-tools-align-left",!hasMultipleSelection);
20341
- RED.menu.setDisabled("menu-item-view-tools-align-center",!hasMultipleSelection);
20342
- RED.menu.setDisabled("menu-item-view-tools-align-right",!hasMultipleSelection);
20343
- RED.menu.setDisabled("menu-item-view-tools-align-top",!hasMultipleSelection);
20344
- RED.menu.setDisabled("menu-item-view-tools-align-middle",!hasMultipleSelection);
20345
- RED.menu.setDisabled("menu-item-view-tools-align-bottom",!hasMultipleSelection);
20346
- RED.menu.setDisabled("menu-item-view-tools-distribute-horizontally",!hasMultipleSelection);
20347
- RED.menu.setDisabled("menu-item-view-tools-distribute-veritcally",!hasMultipleSelection);
21029
+ var hasLinkSelected = selection.links && selection.links.length > 0;
21030
+ var canEdit = !activeFlowLocked && hasSelection
21031
+ var canEditMultiple = !activeFlowLocked && hasMultipleSelection
21032
+ RED.menu.setDisabled("menu-item-edit-cut", !canEdit);
21033
+ RED.menu.setDisabled("menu-item-edit-copy", !hasSelection);
21034
+ RED.menu.setDisabled("menu-item-edit-select-connected", !hasSelection);
21035
+ RED.menu.setDisabled("menu-item-view-tools-move-to-back", !canEdit);
21036
+ RED.menu.setDisabled("menu-item-view-tools-move-to-front", !canEdit);
21037
+ RED.menu.setDisabled("menu-item-view-tools-move-backwards", !canEdit);
21038
+ RED.menu.setDisabled("menu-item-view-tools-move-forwards", !canEdit);
21039
+
21040
+ RED.menu.setDisabled("menu-item-view-tools-align-left", !canEditMultiple);
21041
+ RED.menu.setDisabled("menu-item-view-tools-align-center", !canEditMultiple);
21042
+ RED.menu.setDisabled("menu-item-view-tools-align-right", !canEditMultiple);
21043
+ RED.menu.setDisabled("menu-item-view-tools-align-top", !canEditMultiple);
21044
+ RED.menu.setDisabled("menu-item-view-tools-align-middle", !canEditMultiple);
21045
+ RED.menu.setDisabled("menu-item-view-tools-align-bottom", !canEditMultiple);
21046
+ RED.menu.setDisabled("menu-item-view-tools-distribute-horizontally", !canEditMultiple);
21047
+ RED.menu.setDisabled("menu-item-view-tools-distribute-veritcally", !canEditMultiple);
21048
+
21049
+ RED.menu.setDisabled("menu-item-edit-split-wire-with-links", activeFlowLocked || !hasLinkSelected);
20348
21050
  })
20349
21051
 
20350
21052
  RED.actions.add("core:delete-selection",deleteSelection);
@@ -20734,7 +21436,7 @@ RED.view = (function() {
20734
21436
  .attr("class", "nr-ui-view-lasso");
20735
21437
  d3.event.preventDefault();
20736
21438
  }
20737
- } else if (d3.event.altKey) {
21439
+ } else if (d3.event.altKey && !activeFlowLocked) {
20738
21440
  //Alt [+shift] held - Begin slicing
20739
21441
  clearSelection();
20740
21442
  mouse_mode = (d3.event.shiftKey) ? RED.state.SLICING_JUNCTION : RED.state.SLICING;
@@ -20748,6 +21450,9 @@ RED.view = (function() {
20748
21450
  }
20749
21451
 
20750
21452
  function showQuickAddDialog(options) {
21453
+ if (activeFlowLocked) {
21454
+ return
21455
+ }
20751
21456
  options = options || {};
20752
21457
  var point = options.position || lastClickPosition;
20753
21458
  var spliceLink = options.splice;
@@ -20930,6 +21635,11 @@ RED.view = (function() {
20930
21635
  if (showLabel !== undefined && (nn._def.hasOwnProperty("showLabel")?nn._def.showLabel:true) && !nn._def.defaults.hasOwnProperty("l")) {
20931
21636
  nn.l = showLabel;
20932
21637
  }
21638
+ if (nn.type === 'junction') {
21639
+ nn = RED.nodes.addJunction(nn);
21640
+ } else {
21641
+ nn = RED.nodes.add(nn);
21642
+ }
20933
21643
  if (quickAddLink) {
20934
21644
  var drag_line = quickAddLink;
20935
21645
  var src = null,dst,src_port;
@@ -21032,27 +21742,39 @@ RED.view = (function() {
21032
21742
  }
21033
21743
  }
21034
21744
  }
21035
- if (nn.type === 'junction') {
21036
- RED.nodes.addJunction(nn);
21037
- } else {
21038
- RED.nodes.add(nn);
21039
- }
21745
+
21040
21746
  RED.editor.validateNode(nn);
21041
21747
 
21042
21748
  if (targetGroup) {
21749
+ var oldX = targetGroup.x;
21750
+ var oldY = targetGroup.y;
21043
21751
  RED.group.addToGroup(targetGroup, nn);
21752
+ var moveEvent = null;
21753
+ if ((targetGroup.x !== oldX) ||
21754
+ (targetGroup.y !== oldY)) {
21755
+ moveEvent = {
21756
+ t: "move",
21757
+ nodes: [{n: targetGroup,
21758
+ ox: oldX, oy: oldY,
21759
+ dx: targetGroup.x -oldX,
21760
+ dy: targetGroup.y -oldY}],
21761
+ dirty: true
21762
+ };
21763
+ }
21044
21764
  if (historyEvent.t !== "multi") {
21045
21765
  historyEvent = {
21046
21766
  t:'multi',
21047
21767
  events: [historyEvent]
21048
- }
21768
+ };
21049
21769
  }
21050
21770
  historyEvent.events.push({
21051
21771
  t: "addToGroup",
21052
21772
  group: targetGroup,
21053
21773
  nodes: nn
21054
- })
21055
-
21774
+ });
21775
+ if (moveEvent) {
21776
+ historyEvent.events.push(moveEvent);
21777
+ }
21056
21778
  }
21057
21779
 
21058
21780
  if (spliceLink) {
@@ -21294,16 +22016,18 @@ RED.view = (function() {
21294
22016
  }
21295
22017
  var d = (mouse_offset[0]-mousePos[0])*(mouse_offset[0]-mousePos[0]) + (mouse_offset[1]-mousePos[1])*(mouse_offset[1]-mousePos[1]);
21296
22018
  if ((d > 3 && !dblClickPrimed) || (dblClickPrimed && d > 10)) {
21297
- mouse_mode = RED.state.MOVING_ACTIVE;
21298
22019
  clickElapsed = 0;
21299
- spliceActive = false;
21300
- if (movingSet.length() === 1) {
21301
- node = movingSet.get(0);
21302
- spliceActive = node.n.hasOwnProperty("_def") &&
21303
- ((node.n.hasOwnProperty("inputs") && node.n.inputs > 0) || (!node.n.hasOwnProperty("inputs") && node.n._def.inputs > 0)) &&
21304
- ((node.n.hasOwnProperty("outputs") && node.n.outputs > 0) || (!node.n.hasOwnProperty("outputs") && node.n._def.outputs > 0)) &&
21305
- RED.nodes.filterLinks({ source: node.n }).length === 0 &&
21306
- RED.nodes.filterLinks({ target: node.n }).length === 0;
22020
+ 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;
22030
+ }
21307
22031
  }
21308
22032
  }
21309
22033
  } else if (mouse_mode == RED.state.MOVING_ACTIVE || mouse_mode == RED.state.IMPORT_DRAGGING || mouse_mode == RED.state.DETACHED_DRAGGING) {
@@ -21388,6 +22112,7 @@ RED.view = (function() {
21388
22112
 
21389
22113
  // Check link splice or group-add
21390
22114
  if (movingSet.length() === 1 && movingSet.get(0).n.type !== "group") {
22115
+ //}{//NIS
21391
22116
  node = movingSet.get(0);
21392
22117
  if (spliceActive) {
21393
22118
  if (!spliceTimer) {
@@ -21747,11 +22472,25 @@ RED.view = (function() {
21747
22472
  if (mouse_mode == RED.state.MOVING_ACTIVE) {
21748
22473
  if (movingSet.length() > 0) {
21749
22474
  var addedToGroup = null;
22475
+ var moveEvent = null;
21750
22476
  if (activeHoverGroup) {
22477
+ var oldX = activeHoverGroup.x;
22478
+ var oldY = activeHoverGroup.y;
21751
22479
  for (var j=0;j<movingSet.length();j++) {
21752
22480
  var n = movingSet.get(j);
21753
22481
  RED.group.addToGroup(activeHoverGroup,n.n);
21754
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
+ }
21755
22494
  addedToGroup = activeHoverGroup;
21756
22495
 
21757
22496
  activeHoverGroup.hovered = false;
@@ -21797,6 +22536,12 @@ RED.view = (function() {
21797
22536
  historyEvent.addToGroup = addedToGroup;
21798
22537
  }
21799
22538
  RED.nodes.dirty(true);
22539
+ if (moveEvent) {
22540
+ historyEvent = {
22541
+ t: "multi",
22542
+ events: [moveEvent, historyEvent]
22543
+ };
22544
+ }
21800
22545
  RED.history.push(historyEvent);
21801
22546
  }
21802
22547
  }
@@ -22148,6 +22893,7 @@ RED.view = (function() {
22148
22893
  }
22149
22894
 
22150
22895
  function editSelection() {
22896
+ if (RED.workspaces.isActiveLocked()) { return }
22151
22897
  if (movingSet.length() > 0) {
22152
22898
  var node = movingSet.get(0).n;
22153
22899
  if (node.type === "subflow") {
@@ -22163,6 +22909,9 @@ RED.view = (function() {
22163
22909
  if (mouse_mode === RED.state.SELECTING_NODE) {
22164
22910
  return;
22165
22911
  }
22912
+ if (activeFlowLocked) {
22913
+ return
22914
+ }
22166
22915
  if (portLabelHover) {
22167
22916
  portLabelHover.remove();
22168
22917
  portLabelHover = null;
@@ -22478,6 +23227,7 @@ RED.view = (function() {
22478
23227
 
22479
23228
 
22480
23229
  function detachSelectedNodes() {
23230
+ if (RED.workspaces.isActiveLocked()) { return }
22481
23231
  var selection = RED.view.selection();
22482
23232
  if (selection.nodes) {
22483
23233
  const {newLinks, removedLinks} = RED.nodes.detachNodes(selection.nodes);
@@ -22619,7 +23369,7 @@ RED.view = (function() {
22619
23369
  mousedown_node = d;
22620
23370
  mousedown_port_type = portType;
22621
23371
  mousedown_port_index = portIndex || 0;
22622
- if (mouse_mode !== RED.state.QUICK_JOINING) {
23372
+ if (mouse_mode !== RED.state.QUICK_JOINING && !activeFlowLocked) {
22623
23373
  mouse_mode = RED.state.JOINING;
22624
23374
  document.body.style.cursor = "crosshair";
22625
23375
  if (evt.ctrlKey || evt.metaKey) {
@@ -23059,6 +23809,14 @@ RED.view = (function() {
23059
23809
  }
23060
23810
  if (dblClickPrimed && mousedown_node == d && clickElapsed > 0 && clickElapsed < dblClickInterval) {
23061
23811
  mouse_mode = RED.state.DEFAULT;
23812
+ if (RED.workspaces.isActiveLocked()) {
23813
+ clickElapsed = 0;
23814
+ d3.event.stopPropagation();
23815
+ return
23816
+ }
23817
+ // Avoid dbl click causing text selection.
23818
+ d3.event.preventDefault()
23819
+ document.getSelection().removeAllRanges()
23062
23820
  if (d.type != "subflow") {
23063
23821
  if (/^subflow:/.test(d.type) && (d3.event.ctrlKey || d3.event.metaKey)) {
23064
23822
  RED.workspaces.show(d.type.substring(8));
@@ -23153,11 +23911,25 @@ RED.view = (function() {
23153
23911
  updateActiveNodes();
23154
23912
  }
23155
23913
 
23914
+ var moveEvent = null;
23156
23915
  if (activeHoverGroup) {
23916
+ var oldX = activeHoverGroup.x;
23917
+ var oldY = activeHoverGroup.y;
23157
23918
  for (var j=0;j<movingSet.length();j++) {
23158
23919
  var n = movingSet.get(j);
23159
23920
  RED.group.addToGroup(activeHoverGroup,n.n);
23160
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
+ }
23161
23933
  historyEvent.addedToGroup = activeHoverGroup;
23162
23934
 
23163
23935
  activeHoverGroup.hovered = false;
@@ -23166,7 +23938,6 @@ RED.view = (function() {
23166
23938
  activeGroup.selected = true;
23167
23939
  activeHoverGroup = null;
23168
23940
  }
23169
-
23170
23941
  if (mouse_mode == RED.state.DETACHED_DRAGGING) {
23171
23942
  var ns = [];
23172
23943
  for (var j=0;j<movingSet.length();j++) {
@@ -23177,7 +23948,15 @@ RED.view = (function() {
23177
23948
  n.n.moved = true;
23178
23949
  }
23179
23950
  }
23180
- RED.history.replace({t:"multi",events:[historyEvent,{t:"move",nodes:ns}],dirty: historyEvent.dirty})
23951
+ var event = {t:"multi",events:[historyEvent,{t:"move",nodes:ns}],dirty: historyEvent.dirty};
23952
+ if (moveEvent) {
23953
+ event.events.push(moveEvent);
23954
+ }
23955
+ RED.history.replace(event)
23956
+ }
23957
+ else if(moveEvent) {
23958
+ var event = {t:"multi", events:[historyEvent, moveEvent], dirty: true};
23959
+ RED.history.replace(event);
23181
23960
  }
23182
23961
 
23183
23962
  updateSelection();
@@ -23382,7 +24161,6 @@ RED.view = (function() {
23382
24161
  }
23383
24162
  // selectedLinks.clear();
23384
24163
  if (d3.event.button != 2) {
23385
- mouse_mode = RED.state.MOVING;
23386
24164
  var mouse = d3.touches(this)[0]||d3.mouse(this);
23387
24165
  mouse[0] += d.x-d.w/2;
23388
24166
  mouse[1] += d.y-d.h/2;
@@ -23575,6 +24353,7 @@ RED.view = (function() {
23575
24353
  if (RED.view.DEBUG) {
23576
24354
  console.warn("groupMouseUp", { mouse_mode, event: d3.event });
23577
24355
  }
24356
+ if (RED.workspaces.isActiveLocked()) { return }
23578
24357
  if (dblClickPrimed && mousedown_group == g && clickElapsed > 0 && clickElapsed < dblClickInterval) {
23579
24358
  mouse_mode = RED.state.DEFAULT;
23580
24359
  RED.editor.editGroup(g);
@@ -23745,7 +24524,7 @@ RED.view = (function() {
23745
24524
  function isButtonEnabled(d) {
23746
24525
  var buttonEnabled = true;
23747
24526
  var ws = RED.nodes.workspace(RED.workspaces.active());
23748
- if (ws && !ws.disabled && !d.d) {
24527
+ if (ws && !ws.disabled && !d.d && !ws.locked) {
23749
24528
  if (d._def.button.hasOwnProperty('enabled')) {
23750
24529
  if (typeof d._def.button.enabled === "function") {
23751
24530
  buttonEnabled = d._def.button.enabled.call(d);
@@ -23768,7 +24547,7 @@ RED.view = (function() {
23768
24547
  }
23769
24548
  var activeWorkspace = RED.workspaces.active();
23770
24549
  var ws = RED.nodes.workspace(activeWorkspace);
23771
- if (ws && !ws.disabled && !d.d) {
24550
+ if (ws && !ws.disabled && !d.d && !ws.locked) {
23772
24551
  if (d._def.button.toggle) {
23773
24552
  d[d._def.button.toggle] = !d[d._def.button.toggle];
23774
24553
  d.dirty = true;
@@ -23783,7 +24562,7 @@ RED.view = (function() {
23783
24562
  if (d.dirty) {
23784
24563
  redraw();
23785
24564
  }
23786
- } else {
24565
+ } else if (!ws || !ws.locked){
23787
24566
  if (activeSubflow) {
23788
24567
  RED.notify(RED._("notification.warning", {message:RED._("notification.warnings.nodeActionDisabledSubflow")}),"warning");
23789
24568
  } else {
@@ -23798,14 +24577,15 @@ RED.view = (function() {
23798
24577
  function showTouchMenu(obj,pos) {
23799
24578
  var mdn = mousedown_node;
23800
24579
  var options = [];
23801
- options.push({name:"delete",disabled:(movingSet.length()===0 && selectedLinks.length() === 0),onselect:function() {deleteSelection();}});
23802
- options.push({name:"cut",disabled:(movingSet.length()===0),onselect:function() {copySelection(true);deleteSelection();}});
23803
- options.push({name:"copy",disabled:(movingSet.length()===0),onselect:function() {copySelection();}});
23804
- options.push({name:"paste",disabled:(clipboard.length===0),onselect:function() {importNodes(clipboard, {generateIds: true, touchImport: true});}});
23805
- options.push({name:"edit",disabled:(movingSet.length() != 1),onselect:function() { RED.editor.edit(mdn);}});
24580
+ const isActiveLocked = RED.workspaces.isActiveLocked()
24581
+ options.push({name:"delete",disabled:(isActiveLocked || movingSet.length()===0 && selectedLinks.length() === 0),onselect:function() {deleteSelection();}});
24582
+ options.push({name:"cut",disabled:(isActiveLocked || movingSet.length()===0),onselect:function() {copySelection(true);deleteSelection();}});
24583
+ options.push({name:"copy",disabled:(isActiveLocked || movingSet.length()===0),onselect:function() {copySelection();}});
24584
+ options.push({name:"paste",disabled:(isActiveLocked || clipboard.length===0),onselect:function() {importNodes(clipboard, {generateIds: true, touchImport: true});}});
24585
+ options.push({name:"edit",disabled:(isActiveLocked || movingSet.length() != 1),onselect:function() { RED.editor.edit(mdn);}});
23806
24586
  options.push({name:"select",onselect:function() {selectAll();}});
23807
24587
  options.push({name:"undo",disabled:(RED.history.depth() === 0),onselect:function() {RED.history.pop();}});
23808
- options.push({name:"add",onselect:function() {
24588
+ options.push({name:"add",disabled:isActiveLocked, onselect:function() {
23809
24589
  chartPos = chart.offset();
23810
24590
  showQuickAddDialog({
23811
24591
  position:[pos[0]-chartPos.left+chart.scrollLeft(),pos[1]-chartPos.top+chart.scrollTop()],
@@ -25296,7 +26076,24 @@ RED.view = (function() {
25296
26076
  if (activeSubflow) {
25297
26077
  activeSubflowChanged = activeSubflow.changed;
25298
26078
  }
25299
- var result = RED.nodes.import(nodesToImport,{generateIds:options.generateIds, addFlow: addNewFlow, importMap: options.importMap});
26079
+ var filteredNodesToImport = nodesToImport;
26080
+ var globalConfig = null;
26081
+ var gconf = null;
26082
+
26083
+ RED.nodes.eachConfig(function (conf) {
26084
+ if (conf.type === "global-config") {
26085
+ gconf = conf;
26086
+ }
26087
+ });
26088
+ if (gconf) {
26089
+ filteredNodesToImport = nodesToImport.filter(function (n) {
26090
+ return (n.type !== "global-config");
26091
+ });
26092
+ globalConfig = nodesToImport.find(function (n) {
26093
+ return (n.type === "global-config");
26094
+ });
26095
+ }
26096
+ var result = RED.nodes.import(filteredNodesToImport,{generateIds:options.generateIds, addFlow: addNewFlow, importMap: options.importMap});
25300
26097
  if (result) {
25301
26098
  var new_nodes = result.nodes;
25302
26099
  var new_links = result.links;
@@ -25428,6 +26225,50 @@ RED.view = (function() {
25428
26225
  }
25429
26226
  }
25430
26227
 
26228
+ if (globalConfig) {
26229
+ // merge global env to existing global-config
26230
+ var env0 = gconf.env;
26231
+ var env1 = globalConfig.env;
26232
+ var newEnv = Array.from(env0);
26233
+ var changed = false;
26234
+
26235
+ env1.forEach(function (item1) {
26236
+ var index = newEnv.findIndex(function (item0) {
26237
+ return (item0.name === item1.name);
26238
+ });
26239
+ if (index >= 0) {
26240
+ var item0 = newEnv[index];
26241
+ if ((item0.type !== item1.type) ||
26242
+ (item0.value !== item1.value)) {
26243
+ newEnv[index] = item1;
26244
+ changed = true;
26245
+ }
26246
+ }
26247
+ else {
26248
+ newEnv.push(item1);
26249
+ changed = true;
26250
+ }
26251
+ });
26252
+ if(changed) {
26253
+ gconf.env = newEnv;
26254
+ var replaceEvent = {
26255
+ t: "edit",
26256
+ node: gconf,
26257
+ changed: true,
26258
+ changes: {
26259
+ env: env0
26260
+ }
26261
+ };
26262
+ historyEvent = {
26263
+ t:"multi",
26264
+ events: [
26265
+ replaceEvent,
26266
+ historyEvent,
26267
+ ]
26268
+ };
26269
+ }
26270
+ }
26271
+
25431
26272
  RED.history.push(historyEvent);
25432
26273
 
25433
26274
  updateActiveNodes();
@@ -25503,6 +26344,9 @@ RED.view = (function() {
25503
26344
  if (mouse_mode === RED.state.SELECTING_NODE) {
25504
26345
  return;
25505
26346
  }
26347
+ if (activeFlowLocked) {
26348
+ return
26349
+ }
25506
26350
  var workspaceSelection = RED.workspaces.selection();
25507
26351
  var changed = false;
25508
26352
  if (workspaceSelection.length > 0) {
@@ -25830,7 +26674,9 @@ RED.view = (function() {
25830
26674
  if (node.z && (node.type === "group" || node._def.category !== 'config')) {
25831
26675
  node.dirty = true;
25832
26676
  RED.workspaces.show(node.z);
25833
-
26677
+ if (node.type === "group" && !node.w && !node.h) {
26678
+ _redraw();
26679
+ }
25834
26680
  var screenSize = [chart[0].clientWidth/scaleFactor,chart[0].clientHeight/scaleFactor];
25835
26681
  var scrollPos = [chart.scrollLeft()/scaleFactor,chart.scrollTop()/scaleFactor];
25836
26682
  var cx = node.x;
@@ -26293,7 +27139,7 @@ RED.view = (function() {
26293
27139
  **/
26294
27140
 
26295
27141
  RED.view.tools = (function() {
26296
-
27142
+ 'use strict';
26297
27143
  function selectConnected(type) {
26298
27144
  var selection = RED.view.selection();
26299
27145
  var visited = new Set();
@@ -26317,6 +27163,9 @@ RED.view.tools = (function() {
26317
27163
  }
26318
27164
 
26319
27165
  function alignToGrid() {
27166
+ if (RED.workspaces.isActiveLocked()) {
27167
+ return
27168
+ }
26320
27169
  var selection = RED.view.selection();
26321
27170
  if (selection.nodes) {
26322
27171
  var changedNodes = [];
@@ -26365,6 +27214,9 @@ RED.view.tools = (function() {
26365
27214
  }
26366
27215
 
26367
27216
  function moveSelection(dx,dy) {
27217
+ if (RED.workspaces.isActiveLocked()) {
27218
+ return
27219
+ }
26368
27220
  if (moving_set === null) {
26369
27221
  moving_set = [];
26370
27222
  var selection = RED.view.selection();
@@ -26431,6 +27283,9 @@ RED.view.tools = (function() {
26431
27283
  }
26432
27284
 
26433
27285
  function setSelectedNodeLabelState(labelShown) {
27286
+ if (RED.workspaces.isActiveLocked()) {
27287
+ return
27288
+ }
26434
27289
  var selection = RED.view.selection();
26435
27290
  var historyEvents = [];
26436
27291
  var nodes = [];
@@ -26717,6 +27572,9 @@ RED.view.tools = (function() {
26717
27572
  }
26718
27573
 
26719
27574
  function alignSelectionToEdge(direction) {
27575
+ // if (RED.workspaces.isActiveLocked()) {
27576
+ // return
27577
+ // }
26720
27578
  var selection = RED.view.selection();
26721
27579
 
26722
27580
  if (selection.nodes && selection.nodes.length > 1) {
@@ -26817,8 +27675,10 @@ RED.view.tools = (function() {
26817
27675
  }
26818
27676
  }
26819
27677
 
26820
-
26821
27678
  function distributeSelection(direction) {
27679
+ if (RED.workspaces.isActiveLocked()) {
27680
+ return
27681
+ }
26822
27682
  var selection = RED.view.selection();
26823
27683
 
26824
27684
  if (selection.nodes && selection.nodes.length > 2) {
@@ -26977,6 +27837,9 @@ RED.view.tools = (function() {
26977
27837
  }
26978
27838
 
26979
27839
  function reorderSelection(dir) {
27840
+ if (RED.workspaces.isActiveLocked()) {
27841
+ return
27842
+ }
26980
27843
  var selection = RED.view.selection();
26981
27844
  if (selection.nodes) {
26982
27845
  var nodesToMove = [];
@@ -27012,8 +27875,10 @@ RED.view.tools = (function() {
27012
27875
  }
27013
27876
  }
27014
27877
 
27015
-
27016
27878
  function wireSeriesOfNodes() {
27879
+ if (RED.workspaces.isActiveLocked()) {
27880
+ return
27881
+ }
27017
27882
  var selection = RED.view.selection();
27018
27883
  if (selection.nodes) {
27019
27884
  if (selection.nodes.length > 1) {
@@ -27054,6 +27919,9 @@ RED.view.tools = (function() {
27054
27919
  }
27055
27920
 
27056
27921
  function wireNodeToMultiple() {
27922
+ if (RED.workspaces.isActiveLocked()) {
27923
+ return
27924
+ }
27057
27925
  var selection = RED.view.selection();
27058
27926
  if (selection.nodes) {
27059
27927
  if (selection.nodes.length > 1) {
@@ -27101,6 +27969,9 @@ RED.view.tools = (function() {
27101
27969
  * @param {Object || Object[]} wires The wire(s) to split and replace with link-out, link-in nodes.
27102
27970
  */
27103
27971
  function splitWiresWithLinkNodes(wires) {
27972
+ if (RED.workspaces.isActiveLocked()) {
27973
+ return
27974
+ }
27104
27975
  let wiresToSplit = wires || (RED.view.selection().links && RED.view.selection().links.filter(e => !e.link));
27105
27976
  if (!wiresToSplit) {
27106
27977
  return
@@ -27155,7 +28026,6 @@ RED.view.tools = (function() {
27155
28026
  if(!nnLinkOut) {
27156
28027
  const nLinkOut = RED.view.createNode("link out"); //create link node
27157
28028
  nnLinkOut = nLinkOut.node;
27158
- nodeSrcMap[linkOutMapId] = nnLinkOut;
27159
28029
  let yOffset = 0;
27160
28030
  if(nSrc.outputs > 1) {
27161
28031
 
@@ -27170,7 +28040,8 @@ RED.view.tools = (function() {
27170
28040
  updateNewNodePosXY(nSrc, nnLinkOut, false, RED.view.snapGrid, yOffset);
27171
28041
  }
27172
28042
  //add created node
27173
- RED.nodes.add(nnLinkOut);
28043
+ nnLinkOut = RED.nodes.add(nnLinkOut);
28044
+ nodeSrcMap[linkOutMapId] = nnLinkOut;
27174
28045
  RED.editor.validateNode(nnLinkOut);
27175
28046
  history.events.push(nLinkOut.historyEvent);
27176
28047
  //connect node to link node
@@ -27191,10 +28062,10 @@ RED.view.tools = (function() {
27191
28062
  if(!nnLinkIn) {
27192
28063
  const nLinkIn = RED.view.createNode("link in"); //create link node
27193
28064
  nnLinkIn = nLinkIn.node;
27194
- nodeTrgMap[nTrg.id] = nnLinkIn;
27195
28065
  updateNewNodePosXY(nTrg, nnLinkIn, true, RED.view.snapGrid, 0);
27196
28066
  //add created node
27197
- RED.nodes.add(nnLinkIn);
28067
+ nnLinkIn = RED.nodes.add(nnLinkIn);
28068
+ nodeTrgMap[nTrg.id] = nnLinkIn;
27198
28069
  RED.editor.validateNode(nnLinkIn);
27199
28070
  history.events.push(nLinkIn.historyEvent);
27200
28071
  //connect node to link node
@@ -27269,6 +28140,9 @@ RED.view.tools = (function() {
27269
28140
  * @param {{ renameBlank: boolean, renameClash: boolean, generateHistory: boolean }} options Possible options are `renameBlank`, `renameClash` and `generateHistory`
27270
28141
  */
27271
28142
  function generateNodeNames(node, options) {
28143
+ if (RED.workspaces.isActiveLocked()) {
28144
+ return
28145
+ }
27272
28146
  options = Object.assign({
27273
28147
  renameBlank: true,
27274
28148
  renameClash: true,
@@ -27293,7 +28167,7 @@ RED.view.tools = (function() {
27293
28167
  const nodeDef = n._def || RED.nodes.getType(n.type)
27294
28168
  if (nodeDef && nodeDef.defaults && nodeDef.defaults.name) {
27295
28169
  const paletteLabel = RED.utils.getPaletteLabel(n.type, nodeDef)
27296
- const defaultNodeNameRE = new RegExp('^'+paletteLabel+' (\\d+)$')
28170
+ const defaultNodeNameRE = new RegExp('^'+paletteLabel.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')+' (\\d+)$')
27297
28171
  if (!typeIndex.hasOwnProperty(n.type)) {
27298
28172
  const existingNodes = RED.nodes.filterNodes({type: n.type})
27299
28173
  let maxNameNumber = 0;
@@ -27339,6 +28213,9 @@ RED.view.tools = (function() {
27339
28213
  }
27340
28214
 
27341
28215
  function addJunctionsToWires(wires) {
28216
+ if (RED.workspaces.isActiveLocked()) {
28217
+ return
28218
+ }
27342
28219
  let wiresToSplit = wires || (RED.view.selection().links && RED.view.selection().links.filter(e => !e.link));
27343
28220
  if (!wiresToSplit) {
27344
28221
  return
@@ -27409,7 +28286,7 @@ RED.view.tools = (function() {
27409
28286
 
27410
28287
  var nodeGroups = new Set()
27411
28288
 
27412
- RED.nodes.addJunction(junction)
28289
+ junction = RED.nodes.addJunction(junction)
27413
28290
  addedJunctions.push(junction)
27414
28291
  let newLink
27415
28292
  if (gid === links[0].source.id+":"+links[0].sourcePort) {
@@ -27470,6 +28347,30 @@ RED.view.tools = (function() {
27470
28347
  RED.view.redraw(true);
27471
28348
  }
27472
28349
 
28350
+ function copyItemUrl(node, isEdit) {
28351
+ if (!node) {
28352
+ const selection = RED.view.selection();
28353
+ if (selection.nodes && selection.nodes.length > 0) {
28354
+ node = selection.nodes[0]
28355
+ }
28356
+ }
28357
+ if (node) {
28358
+ let thingType = 'node'
28359
+ if (node.type === 'group') {
28360
+ thingType = 'group'
28361
+ } else if (node.type === 'tab' || node.type === 'subflow') {
28362
+ thingType = 'flow'
28363
+ }
28364
+ let url = `${window.location.origin}${window.location.pathname}#${thingType}/${node.id}`
28365
+ if (isEdit) {
28366
+ url += '/edit'
28367
+ }
28368
+ if (RED.clipboard.copyText(url)) {
28369
+ RED.notify(RED._("sidebar.info.copyURL2Clipboard"), { timeout: 2000 })
28370
+ }
28371
+ }
28372
+ }
28373
+
27473
28374
  return {
27474
28375
  init: function() {
27475
28376
  RED.actions.add("core:show-selected-node-labels", function() { setSelectedNodeLabelState(true); })
@@ -27536,6 +28437,9 @@ RED.view.tools = (function() {
27536
28437
 
27537
28438
  RED.actions.add("core:generate-node-names", generateNodeNames )
27538
28439
 
28440
+ RED.actions.add("core:copy-item-url", function (node) { copyItemUrl(node) })
28441
+ RED.actions.add("core:copy-item-edit-url", function (node) { copyItemUrl(node, true) })
28442
+
27539
28443
  // RED.actions.add("core:add-node", function() { addNode() })
27540
28444
  },
27541
28445
  /**
@@ -28017,9 +28921,19 @@ RED.palette = (function() {
28017
28921
  $('<button type="button" onclick="RED.workspaces.show(\''+type.substring(8).replace(/'/g,"\\'")+'\'); return false;" class="red-ui-button red-ui-button-small" style="float: right; margin-left: 5px;"><i class="fa fa-pencil"></i></button>').appendTo(popOverContent)
28018
28922
  }
28019
28923
 
28020
- var safeType = type.replace(/'/g,"\\'");
28924
+ const safeType = type.replace(/'/g,"\\'");
28925
+ const wrapStr = function (str) {
28926
+ if(str.indexOf(' ') >= 0) {
28927
+ return '"' + str + '"'
28928
+ }
28929
+ return str
28930
+ }
28021
28931
 
28022
- $('<button type="button" onclick="RED.search.show(\'type:'+safeType+'\'); return false;" class="red-ui-button red-ui-button-small" style="float: right; margin-left: 5px;"><i class="fa fa-search"></i></button>').appendTo(popOverContent)
28932
+ $('<button type="button"; return false;" class="red-ui-button red-ui-button-small" style="float: right; margin-left: 5px;"><i class="fa fa-search"></i></button>')
28933
+ .appendTo(popOverContent)
28934
+ .on('click', function() {
28935
+ RED.search.show('type:' + wrapStr(safeType))
28936
+ })
28023
28937
  $('<button type="button" onclick="RED.sidebar.help.show(\''+safeType+'\'); return false;" class="red-ui-button red-ui-button-small" style="float: right; margin-left: 5px;"><i class="fa fa-book"></i></button>').appendTo(popOverContent)
28024
28938
 
28025
28939
  $('<p>',{style:"font-size: 0.8em"}).text(metaData).appendTo(popOverContent);
@@ -28124,6 +29038,7 @@ RED.palette = (function() {
28124
29038
  var hoverGroup;
28125
29039
  var paletteWidth;
28126
29040
  var paletteTop;
29041
+ var dropEnabled;
28127
29042
  $(d).draggable({
28128
29043
  helper: 'clone',
28129
29044
  appendTo: '#red-ui-editor',
@@ -28131,6 +29046,7 @@ RED.palette = (function() {
28131
29046
  revertDuration: 200,
28132
29047
  containment:'#red-ui-main-container',
28133
29048
  start: function() {
29049
+ dropEnabled = !(RED.nodes.workspace(RED.workspaces.active())?.locked);
28134
29050
  paletteWidth = $("#red-ui-palette").width();
28135
29051
  paletteTop = $("#red-ui-palette").parent().position().top + $("#red-ui-palette-container").position().top;
28136
29052
  hoverGroup = null;
@@ -28141,96 +29057,100 @@ RED.palette = (function() {
28141
29057
  RED.view.focus();
28142
29058
  },
28143
29059
  stop: function() {
28144
- d3.select('.red-ui-flow-link-splice').classed('red-ui-flow-link-splice',false);
28145
- if (hoverGroup) {
28146
- document.getElementById("group_select_"+hoverGroup.id).classList.remove("red-ui-flow-group-hovered");
28147
- }
28148
- if (activeGroup) {
28149
- document.getElementById("group_select_"+activeGroup.id).classList.remove("red-ui-flow-group-active-hovered");
29060
+ if (dropEnabled) {
29061
+ d3.select('.red-ui-flow-link-splice').classed('red-ui-flow-link-splice',false);
29062
+ if (hoverGroup) {
29063
+ document.getElementById("group_select_"+hoverGroup.id).classList.remove("red-ui-flow-group-hovered");
29064
+ }
29065
+ if (activeGroup) {
29066
+ document.getElementById("group_select_"+activeGroup.id).classList.remove("red-ui-flow-group-active-hovered");
29067
+ }
29068
+ if (spliceTimer) { clearTimeout(spliceTimer); spliceTimer = null; }
29069
+ if (groupTimer) { clearTimeout(groupTimer); groupTimer = null; }
28150
29070
  }
28151
- if (spliceTimer) { clearTimeout(spliceTimer); spliceTimer = null; }
28152
- if (groupTimer) { clearTimeout(groupTimer); groupTimer = null; }
28153
29071
  },
28154
29072
  drag: function(e,ui) {
28155
29073
  var paletteNode = getPaletteNode(nt);
28156
29074
  ui.originalPosition.left = paletteNode.offset().left;
28157
- mouseX = ui.position.left - paletteWidth + (ui.helper.width()/2) + chart.scrollLeft();
28158
- mouseY = ui.position.top - paletteTop + (ui.helper.height()/2) + chart.scrollTop() + 10;
28159
- if (!groupTimer) {
28160
- groupTimer = setTimeout(function() {
28161
- var mx = mouseX / RED.view.scale();
28162
- var my = mouseY / RED.view.scale();
28163
- var group = RED.view.getGroupAtPoint(mx,my);
28164
- if (group !== hoverGroup) {
28165
- if (hoverGroup) {
28166
- document.getElementById("group_select_"+hoverGroup.id).classList.remove("red-ui-flow-group-hovered");
28167
- }
28168
- if (group) {
28169
- document.getElementById("group_select_"+group.id).classList.add("red-ui-flow-group-hovered");
28170
- }
28171
- hoverGroup = group;
28172
- if (hoverGroup) {
28173
- $(ui.helper).data('group',hoverGroup);
28174
- } else {
28175
- $(ui.helper).removeData('group');
28176
- }
28177
- }
28178
- groupTimer = null;
28179
-
28180
- },200)
28181
- }
28182
- if (def.inputs > 0 && def.outputs > 0) {
28183
- if (!spliceTimer) {
28184
- spliceTimer = setTimeout(function() {
28185
- var nodes = [];
28186
- var bestDistance = Infinity;
28187
- var bestLink = null;
28188
- if (chartSVG.getIntersectionList) {
28189
- var svgRect = chartSVG.createSVGRect();
28190
- svgRect.x = mouseX;
28191
- svgRect.y = mouseY;
28192
- svgRect.width = 1;
28193
- svgRect.height = 1;
28194
- nodes = chartSVG.getIntersectionList(svgRect,chartSVG);
28195
- } else {
28196
- // Firefox doesn't do getIntersectionList and that
28197
- // makes us sad
28198
- nodes = RED.view.getLinksAtPoint(mouseX,mouseY);
28199
- }
29075
+ if (dropEnabled) {
29076
+ mouseX = ui.position.left - paletteWidth + (ui.helper.width()/2) + chart.scrollLeft();
29077
+ mouseY = ui.position.top - paletteTop + (ui.helper.height()/2) + chart.scrollTop() + 10;
29078
+ if (!groupTimer) {
29079
+ groupTimer = setTimeout(function() {
28200
29080
  var mx = mouseX / RED.view.scale();
28201
29081
  var my = mouseY / RED.view.scale();
28202
- for (var i=0;i<nodes.length;i++) {
28203
- var node = d3.select(nodes[i]);
28204
- if (node.classed('red-ui-flow-link-background') && !node.classed('red-ui-flow-link-link')) {
28205
- var length = nodes[i].getTotalLength();
28206
- for (var j=0;j<length;j+=10) {
28207
- var p = nodes[i].getPointAtLength(j);
28208
- var d2 = ((p.x-mx)*(p.x-mx))+((p.y-my)*(p.y-my));
28209
- if (d2 < 200 && d2 < bestDistance) {
28210
- bestDistance = d2;
28211
- bestLink = nodes[i];
29082
+ var group = RED.view.getGroupAtPoint(mx,my);
29083
+ if (group !== hoverGroup) {
29084
+ if (hoverGroup) {
29085
+ document.getElementById("group_select_"+hoverGroup.id).classList.remove("red-ui-flow-group-hovered");
29086
+ }
29087
+ if (group) {
29088
+ document.getElementById("group_select_"+group.id).classList.add("red-ui-flow-group-hovered");
29089
+ }
29090
+ hoverGroup = group;
29091
+ if (hoverGroup) {
29092
+ $(ui.helper).data('group',hoverGroup);
29093
+ } else {
29094
+ $(ui.helper).removeData('group');
29095
+ }
29096
+ }
29097
+ groupTimer = null;
29098
+
29099
+ },200)
29100
+ }
29101
+ if (def.inputs > 0 && def.outputs > 0) {
29102
+ if (!spliceTimer) {
29103
+ spliceTimer = setTimeout(function() {
29104
+ var nodes = [];
29105
+ var bestDistance = Infinity;
29106
+ var bestLink = null;
29107
+ if (chartSVG.getIntersectionList) {
29108
+ var svgRect = chartSVG.createSVGRect();
29109
+ svgRect.x = mouseX;
29110
+ svgRect.y = mouseY;
29111
+ svgRect.width = 1;
29112
+ svgRect.height = 1;
29113
+ nodes = chartSVG.getIntersectionList(svgRect,chartSVG);
29114
+ } else {
29115
+ // Firefox doesn't do getIntersectionList and that
29116
+ // makes us sad
29117
+ nodes = RED.view.getLinksAtPoint(mouseX,mouseY);
29118
+ }
29119
+ var mx = mouseX / RED.view.scale();
29120
+ var my = mouseY / RED.view.scale();
29121
+ for (var i=0;i<nodes.length;i++) {
29122
+ var node = d3.select(nodes[i]);
29123
+ if (node.classed('red-ui-flow-link-background') && !node.classed('red-ui-flow-link-link')) {
29124
+ var length = nodes[i].getTotalLength();
29125
+ for (var j=0;j<length;j+=10) {
29126
+ var p = nodes[i].getPointAtLength(j);
29127
+ var d2 = ((p.x-mx)*(p.x-mx))+((p.y-my)*(p.y-my));
29128
+ if (d2 < 200 && d2 < bestDistance) {
29129
+ bestDistance = d2;
29130
+ bestLink = nodes[i];
29131
+ }
28212
29132
  }
28213
29133
  }
28214
29134
  }
28215
- }
28216
- if (activeSpliceLink && activeSpliceLink !== bestLink) {
28217
- d3.select(activeSpliceLink.parentNode).classed('red-ui-flow-link-splice',false);
28218
- }
28219
- if (bestLink) {
28220
- d3.select(bestLink.parentNode).classed('red-ui-flow-link-splice',true)
28221
- } else {
28222
- d3.select('.red-ui-flow-link-splice').classed('red-ui-flow-link-splice',false);
28223
- }
28224
- if (activeSpliceLink !== bestLink) {
29135
+ if (activeSpliceLink && activeSpliceLink !== bestLink) {
29136
+ d3.select(activeSpliceLink.parentNode).classed('red-ui-flow-link-splice',false);
29137
+ }
28225
29138
  if (bestLink) {
28226
- $(ui.helper).data('splice',d3.select(bestLink).data()[0]);
29139
+ d3.select(bestLink.parentNode).classed('red-ui-flow-link-splice',true)
28227
29140
  } else {
28228
- $(ui.helper).removeData('splice');
29141
+ d3.select('.red-ui-flow-link-splice').classed('red-ui-flow-link-splice',false);
28229
29142
  }
28230
- }
28231
- activeSpliceLink = bestLink;
28232
- spliceTimer = null;
28233
- },200);
29143
+ if (activeSpliceLink !== bestLink) {
29144
+ if (bestLink) {
29145
+ $(ui.helper).data('splice',d3.select(bestLink).data()[0]);
29146
+ } else {
29147
+ $(ui.helper).removeData('splice');
29148
+ }
29149
+ }
29150
+ activeSpliceLink = bestLink;
29151
+ spliceTimer = null;
29152
+ },200);
29153
+ }
28234
29154
  }
28235
29155
  }
28236
29156
  }
@@ -28264,6 +29184,7 @@ RED.palette = (function() {
28264
29184
  categoryNode.find(".red-ui-palette-content").slideToggle();
28265
29185
  categoryNode.find("i").toggleClass("expanded");
28266
29186
  }
29187
+ categoryNode.hide();
28267
29188
  }
28268
29189
  }
28269
29190
 
@@ -28342,6 +29263,7 @@ RED.palette = (function() {
28342
29263
  currentCategoryNode.find(".red-ui-palette-content").slideToggle();
28343
29264
  currentCategoryNode.find("i").toggleClass("expanded");
28344
29265
  }
29266
+ currentCategoryNode.hide();
28345
29267
  }
28346
29268
  }
28347
29269
 
@@ -28558,6 +29480,7 @@ RED.sidebar.info = (function() {
28558
29480
  var propertiesPanelHeaderLabel;
28559
29481
  var propertiesPanelHeaderReveal;
28560
29482
  var propertiesPanelHeaderHelp;
29483
+ var propertiesPanelHeaderCopyLink;
28561
29484
 
28562
29485
  var selectedObject;
28563
29486
 
@@ -28600,10 +29523,20 @@ RED.sidebar.info = (function() {
28600
29523
 
28601
29524
  propertiesPanelHeaderIcon = $("<span>").appendTo(propertiesPanelHeader);
28602
29525
  propertiesPanelHeaderLabel = $("<span>").appendTo(propertiesPanelHeader);
28603
- propertiesPanelHeaderHelp = $('<button class="red-ui-button red-ui-button-small"><i class="fa fa-book"></button>').css({
29526
+
29527
+ propertiesPanelHeaderCopyLink = $('<button type="button" class="red-ui-button red-ui-button-small"><i class="fa fa-link"></button>').css({
28604
29528
  position: 'absolute',
28605
29529
  top: '12px',
28606
29530
  right: '32px'
29531
+ }).on("click", function(evt) {
29532
+ RED.actions.invoke('core:copy-item-url',selectedObject)
29533
+ }).appendTo(propertiesPanelHeader);
29534
+ RED.popover.tooltip(propertiesPanelHeaderCopyLink,RED._("sidebar.info.copyItemUrl"));
29535
+
29536
+ propertiesPanelHeaderHelp = $('<button type="button" class="red-ui-button red-ui-button-small"><i class="fa fa-book"></button>').css({
29537
+ position: 'absolute',
29538
+ top: '12px',
29539
+ right: '56px'
28607
29540
  }).on("click", function(evt) {
28608
29541
  evt.preventDefault();
28609
29542
  evt.stopPropagation();
@@ -28613,8 +29546,7 @@ RED.sidebar.info = (function() {
28613
29546
  }).appendTo(propertiesPanelHeader);
28614
29547
  RED.popover.tooltip(propertiesPanelHeaderHelp,RED._("sidebar.help.showHelp"));
28615
29548
 
28616
-
28617
- propertiesPanelHeaderReveal = $('<button class="red-ui-button red-ui-button-small"><i class="fa fa-search"></button>').css({
29549
+ propertiesPanelHeaderReveal = $('<button type="button" class="red-ui-button red-ui-button-small"><i class="fa fa-search"></button>').css({
28618
29550
  position: 'absolute',
28619
29551
  top: '12px',
28620
29552
  right: '8px'
@@ -28631,7 +29563,7 @@ RED.sidebar.info = (function() {
28631
29563
 
28632
29564
  propertiesPanelContent = $("<div>").css({
28633
29565
  "flex":"1 1 auto",
28634
- "overflow-y":"scroll",
29566
+ "overflow-y":"auto",
28635
29567
  }).appendTo(propertiesPanel);
28636
29568
 
28637
29569
 
@@ -28718,6 +29650,7 @@ RED.sidebar.info = (function() {
28718
29650
  propertiesPanelHeaderLabel.text("");
28719
29651
  propertiesPanelHeaderReveal.hide();
28720
29652
  propertiesPanelHeaderHelp.hide();
29653
+ propertiesPanelHeaderCopyLink.hide();
28721
29654
  return;
28722
29655
  } else if (Array.isArray(node)) {
28723
29656
  // Multiple things selected
@@ -28729,6 +29662,7 @@ RED.sidebar.info = (function() {
28729
29662
  propertiesPanelHeaderLabel.text("Selection");
28730
29663
  propertiesPanelHeaderReveal.hide();
28731
29664
  propertiesPanelHeaderHelp.hide();
29665
+ propertiesPanelHeaderCopyLink.hide();
28732
29666
  selectedObject = null;
28733
29667
 
28734
29668
  var types = {
@@ -28810,9 +29744,11 @@ RED.sidebar.info = (function() {
28810
29744
  if (node.type === "tab" || node.type === "subflow") {
28811
29745
  // If nothing is selected, but we're on a flow or subflow tab.
28812
29746
  propertiesPanelHeaderHelp.hide();
29747
+ propertiesPanelHeaderCopyLink.show();
28813
29748
 
28814
29749
  } else if (node.type === "group") {
28815
29750
  propertiesPanelHeaderHelp.hide();
29751
+ propertiesPanelHeaderCopyLink.show();
28816
29752
 
28817
29753
  propRow = $('<tr class="red-ui-help-info-row"><td>&nbsp;</td><td></td></tr>').appendTo(tableBody);
28818
29754
 
@@ -28837,8 +29773,10 @@ RED.sidebar.info = (function() {
28837
29773
  }
28838
29774
  } else if (node.type === 'junction') {
28839
29775
  propertiesPanelHeaderHelp.hide();
29776
+ propertiesPanelHeaderCopyLink.hide();
28840
29777
  } else {
28841
29778
  propertiesPanelHeaderHelp.show();
29779
+ propertiesPanelHeaderCopyLink.show();
28842
29780
 
28843
29781
  if (!subflowRegex) {
28844
29782
  propRow = $('<tr class="red-ui-help-info-row"><td>'+RED._("sidebar.info.type")+'</td><td></td></tr>').appendTo(tableBody);
@@ -28980,7 +29918,8 @@ RED.sidebar.info = (function() {
28980
29918
  el = el.next();
28981
29919
  }
28982
29920
  $(this).toggleClass('expanded',!isExpanded);
28983
- })
29921
+ });
29922
+ mermaid.init();
28984
29923
  }
28985
29924
 
28986
29925
  var tips = (function() {
@@ -29345,6 +30284,22 @@ RED.sidebar.info = (function() {
29345
30284
  } else {
29346
30285
  $('<div class="red-ui-info-outline-item-control-spacer">').appendTo(controls)
29347
30286
  }
30287
+ if (n.type === 'tab') {
30288
+ var lockToggleButton = $('<button type="button" class="red-ui-info-outline-item-control-lock red-ui-button red-ui-button-small"><i class="fa fa-unlock-alt"></i><i class="fa fa-lock"></i></button>').appendTo(controls).on("click",function(evt) {
30289
+ evt.preventDefault();
30290
+ evt.stopPropagation();
30291
+ if (n.locked) {
30292
+ RED.workspaces.unlock(n.id)
30293
+ } else {
30294
+ RED.workspaces.lock(n.id)
30295
+ }
30296
+ })
30297
+ RED.popover.tooltip(lockToggleButton,function() {
30298
+ return RED._("common.label."+(n.locked?"unlock":"lock"));
30299
+ });
30300
+ } else {
30301
+ $('<div class="red-ui-info-outline-item-control-spacer">').appendTo(controls)
30302
+ }
29348
30303
  controls.find("button").on("dblclick", function(evt) {
29349
30304
  evt.preventDefault();
29350
30305
  evt.stopPropagation();
@@ -29488,6 +30443,8 @@ RED.sidebar.info = (function() {
29488
30443
  flowList.treeList.addChild(objects[ws.id])
29489
30444
  objects[ws.id].element.toggleClass("red-ui-info-outline-item-disabled", !!ws.disabled)
29490
30445
  objects[ws.id].treeList.container.toggleClass("red-ui-info-outline-item-disabled", !!ws.disabled)
30446
+ objects[ws.id].element.toggleClass("red-ui-info-outline-item-locked", !!ws.locked)
30447
+ objects[ws.id].treeList.container.toggleClass("red-ui-info-outline-item-locked", !!ws.locked)
29491
30448
  updateSearch();
29492
30449
 
29493
30450
  }
@@ -29502,6 +30459,8 @@ RED.sidebar.info = (function() {
29502
30459
  existingObject.element.find(".red-ui-info-outline-item-label").text(label);
29503
30460
  existingObject.element.toggleClass("red-ui-info-outline-item-disabled", !!n.disabled)
29504
30461
  existingObject.treeList.container.toggleClass("red-ui-info-outline-item-disabled", !!n.disabled)
30462
+ existingObject.element.toggleClass("red-ui-info-outline-item-locked", !!n.locked)
30463
+ existingObject.treeList.container.toggleClass("red-ui-info-outline-item-locked", !!n.locked)
29505
30464
  updateSearch();
29506
30465
  }
29507
30466
  function onFlowsReorder(order) {
@@ -29858,7 +30817,7 @@ RED.sidebar.help = (function() {
29858
30817
 
29859
30818
  tocPanel = $("<div>", {class: "red-ui-sidebar-help-toc"}).appendTo(stackContainer);
29860
30819
  var helpPanel = $("<div>").css({
29861
- "overflow-y": "scroll"
30820
+ "overflow-y": "auto"
29862
30821
  }).appendTo(stackContainer);
29863
30822
 
29864
30823
  panels = RED.panels.create({
@@ -29949,7 +30908,8 @@ RED.sidebar.help = (function() {
29949
30908
  RED.events.on('registry:node-type-removed', queueRefresh);
29950
30909
  RED.events.on('subflows:change', refreshSubflow);
29951
30910
 
29952
- RED.actions.add("core:show-help-tab",show);
30911
+ RED.actions.add("core:show-help-tab", show);
30912
+ RED.actions.add("core:show-node-help", showNodeHelp)
29953
30913
 
29954
30914
  }
29955
30915
 
@@ -30146,6 +31106,19 @@ RED.sidebar.help = (function() {
30146
31106
  resizeStack();
30147
31107
  }
30148
31108
 
31109
+ function showNodeHelp(node) {
31110
+ if (!node) {
31111
+ const selection = RED.view.selection()
31112
+ if (selection.nodes && selection.nodes.length > 0) {
31113
+ node = selection.nodes.find(n => n.type !== 'group' && n.type !== 'junction')
31114
+ }
31115
+ }
31116
+ if (node) {
31117
+ show(node.type, true)
31118
+ }
31119
+ }
31120
+
31121
+
30149
31122
  // TODO: DRY - projects.js
30150
31123
  function addTargetToExternalLinks(el) {
30151
31124
  $(el).find("a").each(function(el) {
@@ -30332,12 +31305,15 @@ RED.sidebar.config = (function() {
30332
31305
 
30333
31306
  var categories = {};
30334
31307
 
30335
- function getOrCreateCategory(name,parent,label) {
31308
+ function getOrCreateCategory(name,parent,label,isLocked) {
30336
31309
  name = name.replace(/\./i,"-");
30337
31310
  if (!categories[name]) {
30338
31311
  var container = $('<div class="red-ui-palette-category red-ui-sidebar-config-category" id="red-ui-sidebar-config-category-'+name+'"></div>').appendTo(parent);
30339
31312
  var header = $('<div class="red-ui-sidebar-config-tray-header red-ui-palette-header"><i class="fa fa-angle-down expanded"></i></div>').appendTo(container);
31313
+ let lockIcon
30340
31314
  if (label) {
31315
+ lockIcon = $('<span style="margin-right: 5px"><i class="fa fa-lock"/></span>').appendTo(header)
31316
+ lockIcon.toggle(!!isLocked)
30341
31317
  $('<span class="red-ui-palette-node-config-label"/>').text(label).appendTo(header);
30342
31318
  } else {
30343
31319
  $('<span class="red-ui-palette-node-config-label" data-i18n="sidebar.config.'+name+'">').appendTo(header);
@@ -30351,6 +31327,7 @@ RED.sidebar.config = (function() {
30351
31327
  var icon = header.find("i");
30352
31328
  var result = {
30353
31329
  label: label,
31330
+ lockIcon,
30354
31331
  list: category,
30355
31332
  size: function() {
30356
31333
  return result.list.find("li:not(.red-ui-palette-node-config-none)").length
@@ -30389,6 +31366,9 @@ RED.sidebar.config = (function() {
30389
31366
  });
30390
31367
  categories[name] = result;
30391
31368
  } else {
31369
+ if (isLocked !== undefined && categories[name].lockIcon) {
31370
+ categories[name].lockIcon.toggle(!!isLocked)
31371
+ }
30392
31372
  if (categories[name].label !== label) {
30393
31373
  categories[name].list.parent().find('.red-ui-palette-node-config-label').text(label);
30394
31374
  categories[name].label = label;
@@ -30505,7 +31485,7 @@ RED.sidebar.config = (function() {
30505
31485
 
30506
31486
  RED.nodes.eachWorkspace(function(ws) {
30507
31487
  validList[ws.id.replace(/\./g,"-")] = true;
30508
- getOrCreateCategory(ws.id,flowCategories,ws.label);
31488
+ getOrCreateCategory(ws.id,flowCategories,ws.label, ws.locked);
30509
31489
  })
30510
31490
  RED.nodes.eachSubflow(function(sf) {
30511
31491
  validList[sf.id.replace(/\./g,"-")] = true;
@@ -30563,6 +31543,15 @@ RED.sidebar.config = (function() {
30563
31543
  changes: {},
30564
31544
  dirty: RED.nodes.dirty()
30565
31545
  }
31546
+ for (let i = 0; i < selectedNodes.length; i++) {
31547
+ let node = RED.nodes.node(selectedNodes[i])
31548
+ if (node.z) {
31549
+ let ws = RED.nodes.workspace(node.z)
31550
+ if (ws && ws.locked) {
31551
+ return
31552
+ }
31553
+ }
31554
+ }
30566
31555
  selectedNodes.forEach(function(id) {
30567
31556
  var node = RED.nodes.node(id);
30568
31557
  try {
@@ -32505,6 +33494,7 @@ RED.editor = (function() {
32505
33494
  var valid = validateNodeProperty(node, defaults, property,value);
32506
33495
  if (((typeof valid) === "string") || !valid) {
32507
33496
  input.addClass("input-error");
33497
+ input.next(".red-ui-typedInput-container").addClass("input-error");
32508
33498
  if ((typeof valid) === "string") {
32509
33499
  var tooltip = input.data("tooltip");
32510
33500
  if (tooltip) {
@@ -32517,6 +33507,7 @@ RED.editor = (function() {
32517
33507
  }
32518
33508
  } else {
32519
33509
  input.removeClass("input-error");
33510
+ input.next(".red-ui-typedInput-container").removeClass("input-error");
32520
33511
  var tooltip = input.data("tooltip");
32521
33512
  if (tooltip) {
32522
33513
  input.data("tooltip", null);
@@ -34118,11 +35109,15 @@ RED.editor = (function() {
34118
35109
  workspace.disabled = disabled;
34119
35110
 
34120
35111
  $("#red-ui-tab-"+(workspace.id.replace(".","-"))).toggleClass('red-ui-workspace-disabled',!!workspace.disabled);
34121
- if (workspace.id === RED.workspaces.active()) {
34122
- $("#red-ui-workspace").toggleClass("red-ui-workspace-disabled",!!workspace.disabled);
34123
- }
34124
35112
  }
34125
35113
 
35114
+ var locked = $("#node-input-locked").prop("checked");
35115
+ if (workspace.locked !== locked) {
35116
+ editState.changes.locked = workspace.locked;
35117
+ editState.changed = true;
35118
+ workspace.locked = locked;
35119
+ $("#red-ui-tab-"+(workspace.id.replace(".","-"))).toggleClass('red-ui-workspace-locked',!!workspace.locked);
35120
+ }
34126
35121
  if (editState.changed) {
34127
35122
  var historyEvent = {
34128
35123
  t: "edit",
@@ -34163,6 +35158,7 @@ RED.editor = (function() {
34163
35158
  var trayBody = tray.find('.red-ui-tray-body');
34164
35159
  trayBody.parent().css('overflow','hidden');
34165
35160
  var trayFooterLeft = $('<div class="red-ui-tray-footer-left"></div>').appendTo(trayFooter)
35161
+ var trayFooterRight = $('<div class="red-ui-tray-footer-right"></div>').appendTo(trayFooter)
34166
35162
 
34167
35163
  var nodeEditPanes = [
34168
35164
  'editor-tab-flow-properties',
@@ -34177,6 +35173,18 @@ RED.editor = (function() {
34177
35173
  disabledIcon: "fa-ban",
34178
35174
  invertState: true
34179
35175
  })
35176
+
35177
+ if (!workspace.hasOwnProperty("locked")) {
35178
+ workspace.locked = false;
35179
+ }
35180
+ $('<input id="node-input-locked" type="checkbox">').prop("checked",workspace.locked).appendTo(trayFooterRight).toggleButton({
35181
+ enabledLabel: 'Unlocked',
35182
+ enabledIcon: "fa-unlock-alt",
35183
+ disabledLabel: 'Locked',
35184
+ disabledIcon: "fa-lock",
35185
+ invertState: true
35186
+ })
35187
+
34180
35188
  prepareEditDialog(trayBody, nodeEditPanes, workspace, {}, "node-input", defaultTab, function(_activeEditPanes) {
34181
35189
  activeEditPanes = _activeEditPanes;
34182
35190
  trayBody.i18n();
@@ -34579,6 +35587,7 @@ RED.editor = (function() {
34579
35587
  RED.editor.colorPicker.create({
34580
35588
  id: "red-ui-editor-node-color",
34581
35589
  value: color,
35590
+ defaultValue: "#DDAA99",
34582
35591
  palette: recommendedColors,
34583
35592
  sortPalette: function (a, b) {return a.l - b.l;}
34584
35593
  }).appendTo(colorRow);
@@ -35067,8 +36076,6 @@ RED.editor = (function() {
35067
36076
  node.info = info;
35068
36077
  }
35069
36078
  $("#red-ui-tab-"+(node.id.replace(".","-"))).toggleClass('red-ui-workspace-disabled',!!node.disabled);
35070
- $("#red-ui-workspace").toggleClass("red-ui-workspace-disabled",!!node.disabled);
35071
-
35072
36079
  }
35073
36080
  }
35074
36081
  });
@@ -35711,6 +36718,9 @@ RED.editor = (function() {
35711
36718
  selectedCodeEditor = RED.editor.codeEditor[defaultEditor];
35712
36719
  initialised = selectedCodeEditor.init();
35713
36720
  }
36721
+
36722
+ $('<div id="red-ui-image-drop-target"><div data-i18n="[append]workspace.dropImageHere"><i class="fa fa-download"></i><br></div></div>').appendTo('#red-ui-editor');
36723
+ $("#red-ui-image-drop-target").hide();
35714
36724
  }
35715
36725
 
35716
36726
  function create(options) {
@@ -35730,6 +36740,7 @@ RED.editor = (function() {
35730
36740
  options = {};
35731
36741
  }
35732
36742
 
36743
+ var editor = null;
35733
36744
  if (this.editor.type === MONACO) {
35734
36745
  // compatibility (see above note)
35735
36746
  if (!options.element && !options.id) {
@@ -35740,10 +36751,14 @@ RED.editor = (function() {
35740
36751
  console.warn("createEditor() options.element or options.id is not valid", options);
35741
36752
  $("#dialog-form").append('<div id="' + options.id + '" style="display: none;" />');
35742
36753
  }
35743
- return this.editor.create(options);
36754
+ editor = this.editor.create(options);
35744
36755
  } else {
35745
- return this.editor.create(options);//fallback to ACE
36756
+ editor = this.editor.create(options);//fallback to ACE
35746
36757
  }
36758
+ if (options.mode === "ace/mode/markdown") {
36759
+ RED.editor.customEditTypes['_markdown'].postInit(editor, options);
36760
+ }
36761
+ return editor;
35747
36762
  }
35748
36763
 
35749
36764
  return {
@@ -35849,6 +36864,9 @@ RED.editor = (function() {
35849
36864
  var focusTarget = colorInput;
35850
36865
  colorInput.on("change", function (e) {
35851
36866
  var color = colorInput.val();
36867
+ if (options.defaultValue && !color.match(/^([a-z]+|#[0-9a-fA-F]{6}|#[0-9a-fA-F]{3})$/)) {
36868
+ color = options.defaultValue;
36869
+ }
35852
36870
  colorHiddenInput.val(color).trigger('change');
35853
36871
  refreshDisplay(color);
35854
36872
  });
@@ -35984,7 +37002,7 @@ RED.editor = (function() {
35984
37002
 
35985
37003
  var currentLocale = 'en-US';
35986
37004
  var DEFAULT_ENV_TYPE_LIST = ['str','num','bool','json','bin','env'];
35987
- var DEFAULT_ENV_TYPE_LIST_INC_CRED = ['str','num','bool','json','bin','env','cred'];
37005
+ var DEFAULT_ENV_TYPE_LIST_INC_CRED = ['str','num','bool','json','bin','env','cred','jsonata'];
35988
37006
 
35989
37007
  /**
35990
37008
  * Create env var edit interface
@@ -36862,6 +37880,9 @@ RED.editor = (function() {
36862
37880
  var currentExpression = expressionEditor.getValue();
36863
37881
  var expr;
36864
37882
  var usesContext = false;
37883
+ var usesEnv = false;
37884
+ var usesMoment = false;
37885
+ var usesClone = false;
36865
37886
  var legacyMode = /(^|[^a-zA-Z0-9_'".])msg([^a-zA-Z0-9_'"]|$)/.test(currentExpression);
36866
37887
  $(".red-ui-editor-type-expression-legacy").toggle(legacyMode);
36867
37888
  try {
@@ -36874,6 +37895,18 @@ RED.editor = (function() {
36874
37895
  usesContext = true;
36875
37896
  return null;
36876
37897
  });
37898
+ expr.assign("env", function(name) {
37899
+ usesEnv = true;
37900
+ return null;
37901
+ });
37902
+ expr.assign("moment", function(name) {
37903
+ usesMoment = true;
37904
+ return null;
37905
+ });
37906
+ expr.assign("clone", function(name) {
37907
+ usesClone = true;
37908
+ return null;
37909
+ });
36877
37910
  } catch(err) {
36878
37911
  testResultEditor.setValue(RED._("expressionEditor.errors.invalid-expr",{message:err.message}),-1);
36879
37912
  return;
@@ -36891,6 +37924,18 @@ RED.editor = (function() {
36891
37924
  testResultEditor.setValue(RED._("expressionEditor.errors.context-unsupported"),-1);
36892
37925
  return;
36893
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
+ }
36894
37939
 
36895
37940
  var formattedResult;
36896
37941
  if (result !== undefined) {
@@ -37843,6 +38888,61 @@ RED.editor = (function() {
37843
38888
  * limitations under the License.
37844
38889
  **/
37845
38890
  (function() {
38891
+ /**
38892
+ * Converts dropped image file to date URL
38893
+ */
38894
+ function file2base64Image(file, cb) {
38895
+ var reader = new FileReader();
38896
+ reader.onload = (function (fd) {
38897
+ return function (e) {
38898
+ cb(e.target.result);
38899
+ };
38900
+ })(file);
38901
+ reader.readAsDataURL(file);
38902
+ }
38903
+
38904
+ var initialized = false;
38905
+ var currentEditor = null;
38906
+ /**
38907
+ * Initialize handler for image file drag events
38908
+ */
38909
+ function initImageDrag(elem, editor) {
38910
+ $(elem).on("dragenter", function (ev) {
38911
+ ev.preventDefault();
38912
+ $("#red-ui-image-drop-target").css({display:'table'}).focus();
38913
+ currentEditor = editor;
38914
+ });
38915
+
38916
+ if (!initialized) {
38917
+ initialized = true;
38918
+ $("#red-ui-image-drop-target").on("dragover", function (ev) {
38919
+ ev.preventDefault();
38920
+ }).on("dragleave", function (ev) {
38921
+ $("#red-ui-image-drop-target").hide();
38922
+ }).on("drop", function (ev) {
38923
+ ev.preventDefault();
38924
+ if ($.inArray("Files",ev.originalEvent.dataTransfer.types) != -1) {
38925
+ var files = ev.originalEvent.dataTransfer.files;
38926
+ if (files.length === 1) {
38927
+ var file = files[0];
38928
+ var name = file.name.toLowerCase();
38929
+
38930
+ if (name.match(/\.(apng|avif|gif|jpeg|png|svg|webp)$/)) {
38931
+ file2base64Image(file, function (image) {
38932
+ var session = currentEditor.getSession();
38933
+ var img = `<img src="${image}"/>\n`;
38934
+ var pos = session.getCursorPosition();
38935
+ session.insert(pos, img);
38936
+ $("#red-ui-image-drop-target").hide();
38937
+ });
38938
+ return;
38939
+ }
38940
+ }
38941
+ }
38942
+ $("#red-ui-image-drop-target").hide();
38943
+ });
38944
+ }
38945
+ }
37846
38946
 
37847
38947
  var toolbarTemplate = '<div style="margin-bottom: 5px">'+
37848
38948
  '<span class="button-group">'+
@@ -37943,6 +39043,7 @@ RED.editor = (function() {
37943
39043
  var currentScrollTop = $(".red-ui-editor-type-markdown-panel-preview").scrollTop();
37944
39044
  $(".red-ui-editor-type-markdown-panel-preview").html(RED.utils.renderMarkdown(expressionEditor.getValue()));
37945
39045
  $(".red-ui-editor-type-markdown-panel-preview").scrollTop(currentScrollTop);
39046
+ mermaid.init();
37946
39047
  },200);
37947
39048
  })
37948
39049
  if (options.header) {
@@ -37951,6 +39052,7 @@ RED.editor = (function() {
37951
39052
 
37952
39053
  if (value) {
37953
39054
  $(".red-ui-editor-type-markdown-panel-preview").html(RED.utils.renderMarkdown(expressionEditor.getValue()));
39055
+ mermaid.init();
37954
39056
  }
37955
39057
  panels = RED.panels.create({
37956
39058
  id:"red-ui-editor-type-markdown-panels",
@@ -37977,10 +39079,14 @@ RED.editor = (function() {
37977
39079
  });
37978
39080
  RED.popover.tooltip($("#node-btn-markdown-preview"), RED._("markdownEditor.toggle-preview"));
37979
39081
 
37980
- if (options.cursor && !expressionEditor._initState) {
37981
- expressionEditor.gotoLine(options.cursor.row+1,options.cursor.column,false);
37982
- }
37983
-
39082
+ if(!expressionEditor._initState) {
39083
+ if (options.cursor) {
39084
+ expressionEditor.gotoLine(options.cursor.row+1,options.cursor.column,false);
39085
+ }
39086
+ else {
39087
+ expressionEditor.gotoLine(0, 0, false);
39088
+ }
39089
+ }
37984
39090
  dialogForm.i18n();
37985
39091
  },
37986
39092
  close: function() {
@@ -38044,7 +39150,11 @@ RED.editor = (function() {
38044
39150
  }
38045
39151
  })
38046
39152
  return toolbar;
38047
- }
39153
+ },
39154
+ postInit: function (editor, options) {
39155
+ var elem = $("#"+options.id);
39156
+ initImageDrag(elem, editor);
39157
+ }
38048
39158
  }
38049
39159
  RED.editor.registerTypeEditor("_markdown", definition);
38050
39160
  })();
@@ -38442,7 +39552,7 @@ RED.editor.codeEditor.monaco = (function() {
38442
39552
  "node-red-util": {package: "node-red", module: "util", path: "node-red/util.d.ts" },
38443
39553
  "node-red-func": {package: "node-red", module: "func", path: "node-red/func.d.ts" },
38444
39554
  }
38445
- const defaultServerSideTypes = [ knownModules["node-red-util"], knownModules["node-red-func"], knownModules["globals"], knownModules["console"], knownModules["buffer"] ];
39555
+ const defaultServerSideTypes = [ knownModules["node-red-util"], knownModules["node-red-func"], knownModules["globals"], knownModules["console"], knownModules["buffer"] , knownModules["util"] ];
38446
39556
 
38447
39557
  const modulesCache = {};
38448
39558
 
@@ -40676,11 +41786,10 @@ RED.clipboard = (function() {
40676
41786
  }
40677
41787
  }
40678
41788
 
40679
- function showImportNodes(mode) {
41789
+ function showImportNodes(library = 'clipboard') {
40680
41790
  if (disabled) {
40681
41791
  return;
40682
41792
  }
40683
- mode = mode || "clipboard";
40684
41793
 
40685
41794
  dialogContainer.empty();
40686
41795
  dialogContainer.append($(importNodesDialog));
@@ -40757,7 +41866,7 @@ RED.clipboard = (function() {
40757
41866
  $("#red-ui-clipboard-dialog-import-text").on("keyup", validateImport);
40758
41867
  $("#red-ui-clipboard-dialog-import-text").on('paste',function() { setTimeout(validateImport,10)});
40759
41868
 
40760
- if (RED.workspaces.active() === 0) {
41869
+ if (RED.workspaces.active() === 0 || RED.workspaces.isActiveLocked()) {
40761
41870
  $("#red-ui-clipboard-dialog-import-opt-current").addClass('disabled').removeClass("selected");
40762
41871
  $("#red-ui-clipboard-dialog-import-opt-new").addClass("selected");
40763
41872
  } else {
@@ -40786,8 +41895,8 @@ RED.clipboard = (function() {
40786
41895
  $("#red-ui-clipboard-dialog-import-file-upload").trigger("click");
40787
41896
  })
40788
41897
 
40789
- tabs.activateTab("red-ui-clipboard-dialog-import-tab-"+mode);
40790
- if (mode === 'clipboard') {
41898
+ tabs.activateTab("red-ui-clipboard-dialog-import-tab-"+library);
41899
+ if (library === 'clipboard') {
40791
41900
  setTimeout(function() {
40792
41901
  $("#red-ui-clipboard-dialog-import-text").trigger("focus");
40793
41902
  },100)
@@ -40811,13 +41920,16 @@ RED.clipboard = (function() {
40811
41920
  });
40812
41921
  }
40813
41922
 
40814
- function showExportNodes(mode) {
41923
+ /**
41924
+ * Show the export dialog
41925
+ * @params library which export destination to show
41926
+ * @params mode whether to default to 'auto' (default) or 'flow'
41927
+ **/
41928
+ function showExportNodes(library = 'clipboard', mode = 'auto' ) {
40815
41929
  if (disabled) {
40816
41930
  return;
40817
41931
  }
40818
41932
 
40819
- mode = mode || "clipboard";
40820
-
40821
41933
  dialogContainer.empty();
40822
41934
  dialogContainer.append($(exportNodesDialog));
40823
41935
 
@@ -40907,7 +42019,12 @@ RED.clipboard = (function() {
40907
42019
  $("#red-ui-clipboard-dialog-tab-library-name").val("flows.json").select();
40908
42020
 
40909
42021
  dialogContainer.i18n();
42022
+
40910
42023
  var format = RED.settings.flowFilePretty ? "red-ui-clipboard-dialog-export-fmt-full" : "red-ui-clipboard-dialog-export-fmt-mini";
42024
+ const userFormat = RED.settings.get("editor.dialog.export.pretty")
42025
+ if (userFormat === false || userFormat === true) {
42026
+ format = userFormat ? "red-ui-clipboard-dialog-export-fmt-full" : "red-ui-clipboard-dialog-export-fmt-mini";
42027
+ }
40911
42028
 
40912
42029
  $("#red-ui-clipboard-dialog-export-fmt-group > a").on("click", function(evt) {
40913
42030
  evt.preventDefault();
@@ -40923,7 +42040,8 @@ RED.clipboard = (function() {
40923
42040
  var nodes = JSON.parse(flow);
40924
42041
 
40925
42042
  format = $(this).attr('id');
40926
- if (format === 'red-ui-clipboard-dialog-export-fmt-full') {
42043
+ const pretty = format === "red-ui-clipboard-dialog-export-fmt-full";
42044
+ if (pretty) {
40927
42045
  flow = JSON.stringify(nodes,null,4);
40928
42046
  } else {
40929
42047
  flow = JSON.stringify(nodes);
@@ -40932,6 +42050,7 @@ RED.clipboard = (function() {
40932
42050
  setTimeout(function() { $("#red-ui-clipboard-dialog-export-text").scrollTop(0); },50);
40933
42051
 
40934
42052
  $("#red-ui-clipboard-dialog-export-text").trigger("focus");
42053
+ RED.settings.set("editor.dialog.export.pretty", pretty)
40935
42054
  }
40936
42055
  });
40937
42056
 
@@ -41019,12 +42138,15 @@ RED.clipboard = (function() {
41019
42138
  }
41020
42139
  }
41021
42140
  }
42141
+ if (mode === 'flow' && !$("#red-ui-clipboard-dialog-export-rng-flow").hasClass('disabled')) {
42142
+ $("#red-ui-clipboard-dialog-export-rng-flow").trigger("click");
42143
+ }
41022
42144
  if (format === "red-ui-clipboard-dialog-export-fmt-full") {
41023
42145
  $("#red-ui-clipboard-dialog-export-fmt-full").trigger("click");
41024
42146
  } else {
41025
42147
  $("#red-ui-clipboard-dialog-export-fmt-mini").trigger("click");
41026
42148
  }
41027
- tabs.activateTab("red-ui-clipboard-dialog-export-tab-"+mode);
42149
+ tabs.activateTab("red-ui-clipboard-dialog-export-tab-"+library);
41028
42150
 
41029
42151
  var dialogHeight = 400;
41030
42152
  var winHeight = $(window).height();
@@ -41519,15 +42641,17 @@ RED.clipboard = (function() {
41519
42641
  RED.keyboard.add("#red-ui-drop-target", "escape" ,hideDropTarget);
41520
42642
 
41521
42643
  $('#red-ui-workspace-chart').on("dragenter",function(event) {
41522
- if ($.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1 ||
41523
- $.inArray("Files",event.originalEvent.dataTransfer.types) != -1) {
42644
+ if (!RED.workspaces.isActiveLocked() && (
42645
+ $.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1 ||
42646
+ $.inArray("Files",event.originalEvent.dataTransfer.types) != -1)) {
41524
42647
  $("#red-ui-drop-target").css({display:'table'}).focus();
41525
42648
  }
41526
42649
  });
41527
42650
 
41528
42651
  $('#red-ui-drop-target').on("dragover",function(event) {
41529
42652
  if ($.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1 ||
41530
- $.inArray("Files",event.originalEvent.dataTransfer.types) != -1) {
42653
+ $.inArray("Files",event.originalEvent.dataTransfer.types) != -1 ||
42654
+ RED.workspaces.isActiveLocked()) {
41531
42655
  event.preventDefault();
41532
42656
  }
41533
42657
  })
@@ -41535,27 +42659,29 @@ RED.clipboard = (function() {
41535
42659
  hideDropTarget();
41536
42660
  })
41537
42661
  .on("drop",function(event) {
41538
- try {
41539
- if ($.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1) {
41540
- var data = event.originalEvent.dataTransfer.getData("text/plain");
41541
- data = data.substring(data.indexOf('['),data.lastIndexOf(']')+1);
41542
- importNodes(data);
41543
- } else if ($.inArray("Files",event.originalEvent.dataTransfer.types) != -1) {
41544
- var files = event.originalEvent.dataTransfer.files;
41545
- if (files.length === 1) {
41546
- var file = files[0];
41547
- var reader = new FileReader();
41548
- reader.onload = (function(theFile) {
41549
- return function(e) {
41550
- importNodes(e.target.result);
41551
- };
41552
- })(file);
41553
- reader.readAsText(file);
42662
+ if (!RED.workspaces.isActiveLocked()) {
42663
+ try {
42664
+ if ($.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1) {
42665
+ var data = event.originalEvent.dataTransfer.getData("text/plain");
42666
+ data = data.substring(data.indexOf('['),data.lastIndexOf(']')+1);
42667
+ importNodes(data);
42668
+ } else if ($.inArray("Files",event.originalEvent.dataTransfer.types) != -1) {
42669
+ var files = event.originalEvent.dataTransfer.files;
42670
+ if (files.length === 1) {
42671
+ var file = files[0];
42672
+ var reader = new FileReader();
42673
+ reader.onload = (function(theFile) {
42674
+ return function(e) {
42675
+ importNodes(e.target.result);
42676
+ };
42677
+ })(file);
42678
+ reader.readAsText(file);
42679
+ }
41554
42680
  }
42681
+ } catch(err) {
42682
+ // Ensure any errors throw above doesn't stop the drop target from
42683
+ // being hidden.
41555
42684
  }
41556
- } catch(err) {
41557
- // Ensure any errors throw above doesn't stop the drop target from
41558
- // being hidden.
41559
42685
  }
41560
42686
  hideDropTarget();
41561
42687
  event.preventDefault();
@@ -42877,38 +44003,51 @@ RED.search = (function() {
42877
44003
  return val;
42878
44004
  }
42879
44005
 
42880
- function search(val) {
42881
- var results = [];
42882
- var typeFilter;
42883
- var m = /(?:^| )type:([^ ]+)/.exec(val);
42884
- if (m) {
42885
- val = val.replace(/(?:^| )type:[^ ]+/,"");
42886
- typeFilter = m[1];
44006
+ function extractType(val, flags) {
44007
+ // extracts: type:XYZ & type:"X Y Z"
44008
+ const regEx = /(?:type):\s*(?:"([^"]+)"|([^" ]+))/;
44009
+ let m
44010
+ while ((m = regEx.exec(val)) !== null) {
44011
+ // avoid infinite loops with zero-width matches
44012
+ if (m.index === regEx.lastIndex) {
44013
+ regEx.lastIndex++;
44014
+ }
44015
+ val = val.replace(m[0]," ").trim()
44016
+ const flag = m[2] || m[1] // quoted entries in capture group 1, unquoted in capture group 2
44017
+ flags.type = flags.type || [];
44018
+ flags.type.push(flag);
42887
44019
  }
42888
- var flags = {};
44020
+ return val;
44021
+ }
44022
+
44023
+ function search(val) {
44024
+ const results = [];
44025
+ const flags = {};
42889
44026
  val = extractFlag(val,"invalid",flags);
42890
44027
  val = extractFlag(val,"unused",flags);
42891
44028
  val = extractFlag(val,"config",flags);
42892
44029
  val = extractFlag(val,"subflow",flags);
42893
44030
  val = extractFlag(val,"hidden",flags);
42894
44031
  val = extractFlag(val,"modified",flags);
42895
- val = extractValue(val,"flow",flags);// flow:active or flow:<flow-id>
44032
+ val = extractValue(val,"flow",flags);// flow:current or flow:<flow-id>
42896
44033
  val = extractValue(val,"uses",flags);// uses:<node-id>
44034
+ val = extractType(val,flags);// type:<node-type>
42897
44035
  val = val.trim();
42898
- var hasFlags = Object.keys(flags).length > 0;
44036
+ const hasFlags = Object.keys(flags).length > 0;
44037
+ const hasTypeFilter = flags.type && flags.type.length > 0
42899
44038
  if (flags.flow && flags.flow.indexOf("current") >= 0) {
42900
44039
  let idx = flags.flow.indexOf("current");
42901
- flags.flow[idx] = RED.workspaces.active();//convert active to flow ID
44040
+ flags.flow[idx] = RED.workspaces.active();//convert 'current' to active flow ID
42902
44041
  }
42903
44042
  if (flags.flow && flags.flow.length) {
42904
44043
  flags.flow = [ ...new Set(flags.flow) ]; //deduplicate
42905
44044
  }
42906
- if (val.length > 0 || typeFilter || hasFlags) {
44045
+ if (val.length > 0 || hasFlags) {
42907
44046
  val = val.toLowerCase();
42908
- var i;
42909
- var j;
42910
- var list = [];
42911
- var nodes = {};
44047
+ let i;
44048
+ let j;
44049
+ let list = [];
44050
+ const nodes = {};
42912
44051
  let keys = [];
42913
44052
  if (flags.uses) {
42914
44053
  keys = flags.uses;
@@ -42916,10 +44055,10 @@ RED.search = (function() {
42916
44055
  keys = Object.keys(index);
42917
44056
  }
42918
44057
  for (i=0;i<keys.length;i++) {
42919
- var key = keys[i];
42920
- var kpos = keys[i].indexOf(val);
42921
- if (kpos > -1) {
42922
- var ids = Object.keys(index[key]||{});
44058
+ const key = keys[i];
44059
+ const kpos = val ? keys[i].indexOf(val) : -1;
44060
+ if (kpos > -1 || (val === "" && hasFlags)) {
44061
+ const ids = Object.keys(index[key]||{});
42923
44062
  for (j=0;j<ids.length;j++) {
42924
44063
  var node = index[key][ids[j]];
42925
44064
  var isConfigNode = node.node._def.category === "config" && node.node.type !== 'group';
@@ -42927,7 +44066,7 @@ RED.search = (function() {
42927
44066
  continue;
42928
44067
  }
42929
44068
  if (flags.hasOwnProperty("invalid")) {
42930
- var nodeIsValid = !node.node.hasOwnProperty("valid") || node.node.valid;
44069
+ const nodeIsValid = !node.node.hasOwnProperty("valid") || node.node.valid;
42931
44070
  if (flags.invalid === nodeIsValid) {
42932
44071
  continue;
42933
44072
  }
@@ -42957,7 +44096,7 @@ RED.search = (function() {
42957
44096
  }
42958
44097
  }
42959
44098
  if (flags.hasOwnProperty("unused")) {
42960
- var isUnused = (node.node.type === 'subflow' && node.node.instances.length === 0) ||
44099
+ const isUnused = (node.node.type === 'subflow' && node.node.instances.length === 0) ||
42961
44100
  (isConfigNode && node.node.users.length === 0 && node.node._def.hasUsers !== false)
42962
44101
  if (flags.unused !== isUnused) {
42963
44102
  continue;
@@ -42968,12 +44107,16 @@ RED.search = (function() {
42968
44107
  continue;
42969
44108
  }
42970
44109
  }
42971
- if (!typeFilter || node.node.type === typeFilter) {
42972
- nodes[node.node.id] = nodes[node.node.id] = {
44110
+ let typeIndex = -1
44111
+ if(hasTypeFilter) {
44112
+ typeIndex = flags.type.indexOf(node.node.type)
44113
+ }
44114
+ if (!hasTypeFilter || typeIndex > -1) {
44115
+ nodes[node.node.id] = nodes[node.node.id] || {
42973
44116
  node: node.node,
42974
44117
  label: node.label
42975
44118
  };
42976
- nodes[node.node.id].index = Math.min(nodes[node.node.id].index||Infinity,kpos);
44119
+ nodes[node.node.id].index = Math.min(nodes[node.node.id].index || Infinity, typeIndex > -1 ? typeIndex : kpos);
42977
44120
  }
42978
44121
  }
42979
44122
  }
@@ -43438,21 +44581,6 @@ RED.search = (function() {
43438
44581
  ;RED.contextMenu = (function () {
43439
44582
 
43440
44583
  let menu;
43441
- function createMenu() {
43442
- // menu = RED.popover.menu({
43443
- // options: [
43444
- // {
43445
- // label: 'delete selection',
43446
- // onselect: function() {
43447
- // RED.actions.invoke('core:delete-selection')
43448
- // RED.view.focus()
43449
- // }
43450
- // },
43451
- // { label: 'world' }
43452
- // ],
43453
- // width: 200,
43454
- // })
43455
- }
43456
44584
 
43457
44585
  function disposeMenu() {
43458
44586
  $(document).off("mousedown.red-ui-workspace-context-menu");
@@ -43465,114 +44593,164 @@ RED.search = (function() {
43465
44593
  if (menu) {
43466
44594
  menu.remove()
43467
44595
  }
44596
+ let menuItems = []
44597
+ if (options.options) {
44598
+ menuItems = options.options
44599
+ } else if (options.type === 'workspace') {
44600
+ const selection = RED.view.selection()
44601
+ const noSelection = !selection || Object.keys(selection).length === 0
44602
+ const hasSelection = (selection.nodes && selection.nodes.length > 0);
44603
+ const hasMultipleSelection = hasSelection && selection.nodes.length > 1;
44604
+ const virtulLinks = (selection.links && selection.links.filter(e => !!e.link)) || [];
44605
+ const wireLinks = (selection.links && selection.links.filter(e => !e.link)) || [];
44606
+ const hasLinks = wireLinks.length > 0;
44607
+ const isSingleLink = !hasSelection && hasLinks && wireLinks.length === 1
44608
+ const isMultipleLinks = !hasSelection && hasLinks && wireLinks.length > 1
44609
+ const canDelete = hasSelection || hasLinks
44610
+ const isGroup = hasSelection && selection.nodes.length === 1 && selection.nodes[0].type === 'group'
44611
+ const canEdit = !RED.workspaces.isActiveLocked()
44612
+ const canRemoveFromGroup = hasSelection && !!selection.nodes[0].g
44613
+ const isAllGroups = hasSelection && selection.nodes.filter(n => n.type !== 'group').length === 0
44614
+ const hasGroup = hasSelection && selection.nodes.filter(n => n.type === 'group' ).length > 0
44615
+ const offset = $("#red-ui-workspace-chart").offset()
44616
+
44617
+ let addX = options.x - offset.left + $("#red-ui-workspace-chart").scrollLeft()
44618
+ let addY = options.y - offset.top + $("#red-ui-workspace-chart").scrollTop()
43468
44619
 
43469
- const selection = RED.view.selection()
43470
- const noSelection = !selection || Object.keys(selection).length === 0
43471
- const hasSelection = (selection.nodes && selection.nodes.length > 0);
43472
- const hasMultipleSelection = hasSelection && selection.nodes.length > 1;
43473
- const virtulLinks = (selection.links && selection.links.filter(e => !!e.link)) || [];
43474
- const wireLinks = (selection.links && selection.links.filter(e => !e.link)) || [];
43475
- const hasLinks = wireLinks.length > 0;
43476
- const isSingleLink = !hasSelection && hasLinks && wireLinks.length === 1
43477
- const isMultipleLinks = !hasSelection && hasLinks && wireLinks.length > 1
43478
- const canDelete = hasSelection || hasLinks
43479
- const isGroup = hasSelection && selection.nodes.length === 1 && selection.nodes[0].type === 'group'
43480
-
43481
- const canRemoveFromGroup = hasSelection && !!selection.nodes[0].g
43482
- const offset = $("#red-ui-workspace-chart").offset()
44620
+ if (RED.view.snapGrid) {
44621
+ const gridSize = RED.view.gridSize()
44622
+ addX = gridSize * Math.floor(addX / gridSize)
44623
+ addY = gridSize * Math.floor(addY / gridSize)
44624
+ }
43483
44625
 
43484
- // addX/addY must be the position in the workspace accounting for both scroll and scale
43485
- // The +5 is because we display the contextMenu -5,-5 to actual click position
43486
- let addX = (options.x + 5 - offset.left + $("#red-ui-workspace-chart").scrollLeft()) / RED.view.scale()
43487
- let addY = (options.y + 5 - offset.top + $("#red-ui-workspace-chart").scrollTop()) / RED.view.scale()
44626
+ menuItems.push(
44627
+ { onselect: 'core:show-action-list', onpostselect: function () { } }
44628
+ )
43488
44629
 
43489
- const menuItems = [
43490
- { onselect: 'core:show-action-list', onpostselect: function () { } },
43491
- {
43492
- label: RED._("contextMenu.insert"),
43493
- options: [
43494
- {
43495
- label: RED._("contextMenu.node"),
43496
- onselect: function () {
43497
- RED.view.showQuickAddDialog({
43498
- position: [addX, addY],
43499
- touchTrigger: true,
43500
- splice: isSingleLink ? selection.links[0] : undefined,
43501
- // spliceMultiple: isMultipleLinks
43502
- })
43503
- },
43504
- onpostselect: function() {
43505
- // ensure quick add dialog search input has focus
43506
- $('#red-ui-type-search-input').trigger('focus')
43507
- }
44630
+ const insertOptions = []
44631
+ menuItems.push({ label: RED._("contextMenu.insert"), options: insertOptions })
44632
+ insertOptions.push(
44633
+ {
44634
+ label: RED._("contextMenu.node"),
44635
+ onselect: function () {
44636
+ RED.view.showQuickAddDialog({
44637
+ position: [addX, addY],
44638
+ touchTrigger: true,
44639
+ splice: isSingleLink ? selection.links[0] : undefined,
44640
+ // spliceMultiple: isMultipleLinks
44641
+ })
43508
44642
  },
43509
- (hasLinks) ? { // has least 1 wire selected
43510
- label: RED._("contextMenu.junction"),
43511
- onselect: 'core:split-wires-with-junctions',
43512
- disabled: !hasLinks
43513
- } : {
43514
- label: RED._("contextMenu.junction"),
43515
- onselect: function () {
43516
- const nn = {
43517
- _def: { defaults: {} },
43518
- type: 'junction',
43519
- z: RED.workspaces.active(),
43520
- id: RED.nodes.id(),
43521
- x: addX,
43522
- y: addY,
43523
- w: 0, h: 0,
43524
- outputs: 1,
43525
- inputs: 1,
43526
- dirty: true
43527
- }
43528
- const historyEvent = {
43529
- dirty: RED.nodes.dirty(),
43530
- t: 'add',
43531
- junctions: [nn]
43532
- }
43533
- RED.nodes.addJunction(nn);
43534
- RED.history.push(historyEvent);
43535
- RED.nodes.dirty(true);
43536
- RED.view.select({nodes: [nn] });
43537
- RED.view.redraw(true)
43538
- }
44643
+ disabled: !canEdit
44644
+ },
44645
+ (hasLinks) ? { // has least 1 wire selected
44646
+ label: RED._("contextMenu.junction"),
44647
+ onselect: 'core:split-wires-with-junctions',
44648
+ disabled: !canEdit || !hasLinks
44649
+ } : {
44650
+ label: RED._("contextMenu.junction"),
44651
+ onselect: function () {
44652
+ const nn = {
44653
+ _def: { defaults: {} },
44654
+ type: 'junction',
44655
+ z: RED.workspaces.active(),
44656
+ id: RED.nodes.id(),
44657
+ x: addX,
44658
+ y: addY,
44659
+ w: 0, h: 0,
44660
+ outputs: 1,
44661
+ inputs: 1,
44662
+ dirty: true
44663
+ }
44664
+ const historyEvent = {
44665
+ dirty: RED.nodes.dirty(),
44666
+ t: 'add',
44667
+ junctions: [nn]
44668
+ }
44669
+ RED.nodes.addJunction(nn);
44670
+ RED.history.push(historyEvent);
44671
+ RED.nodes.dirty(true);
44672
+ RED.view.select({nodes: [nn] });
44673
+ RED.view.redraw(true)
43539
44674
  },
43540
- {
43541
- label: RED._("contextMenu.linkNodes"),
43542
- onselect: 'core:split-wire-with-link-nodes',
43543
- disabled: !hasLinks
43544
- }
43545
- ]
43546
-
43547
-
43548
-
44675
+ disabled: !canEdit
44676
+ },
44677
+ {
44678
+ label: RED._("contextMenu.linkNodes"),
44679
+ onselect: 'core:split-wire-with-link-nodes',
44680
+ disabled: !canEdit || !hasLinks
44681
+ },
44682
+ null,
44683
+ { onselect: 'core:show-import-dialog', label: RED._('common.label.import')},
44684
+ { onselect: 'core:show-examples-import-dialog', label: RED._('menu.label.importExample') }
44685
+ )
44686
+ if (hasSelection && canEdit) {
44687
+ const nodeOptions = []
44688
+ if (!hasMultipleSelection && !isGroup) {
44689
+ nodeOptions.push(
44690
+ { onselect: 'core:show-node-help' },
44691
+ null
44692
+ )
44693
+ }
44694
+ nodeOptions.push(
44695
+ { onselect: 'core:enable-selected-nodes' },
44696
+ { onselect: 'core:disable-selected-nodes' },
44697
+ null,
44698
+ { onselect: 'core:show-selected-node-labels' },
44699
+ { onselect: 'core:hide-selected-node-labels' }
44700
+ )
44701
+ menuItems.push({
44702
+ label: RED._('sidebar.info.node'),
44703
+ options: nodeOptions
44704
+ })
44705
+ menuItems.push({
44706
+ label: RED._('sidebar.info.group'),
44707
+ options: [
44708
+ { onselect: 'core:group-selection' },
44709
+ { 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
+ ]
44714
+ })
44715
+ if (canRemoveFromGroup) {
44716
+ menuItems[menuItems.length - 1].options.push(
44717
+ null,
44718
+ { onselect: 'core:remove-selection-from-group', label: RED._("menu.label.groupRemoveSelection") }
44719
+ )
44720
+ }
44721
+ }
44722
+ if (canEdit && hasMultipleSelection) {
44723
+ menuItems.push({
44724
+ label: RED._('menu.label.arrange'),
44725
+ options: [
44726
+ { label:RED._("menu.label.alignLeft"), onselect: "core:align-selection-to-left"},
44727
+ { label:RED._("menu.label.alignCenter"), onselect: "core:align-selection-to-center"},
44728
+ { label:RED._("menu.label.alignRight"), onselect: "core:align-selection-to-right"},
44729
+ null,
44730
+ { label:RED._("menu.label.alignTop"), onselect: "core:align-selection-to-top"},
44731
+ { label:RED._("menu.label.alignMiddle"), onselect: "core:align-selection-to-middle"},
44732
+ { label:RED._("menu.label.alignBottom"), onselect: "core:align-selection-to-bottom"},
44733
+ null,
44734
+ { label:RED._("menu.label.distributeHorizontally"), onselect: "core:distribute-selection-horizontally"},
44735
+ { label:RED._("menu.label.distributeVertically"), onselect: "core:distribute-selection-vertically"}
44736
+ ]
44737
+ })
43549
44738
  }
43550
- ]
43551
44739
 
43552
- menuItems.push(
43553
- null,
43554
- { onselect: 'core:undo', disabled: RED.history.list().length === 0 },
43555
- { onselect: 'core:redo', disabled: RED.history.listRedo().length === 0 },
43556
- null,
43557
- { onselect: 'core:cut-selection-to-internal-clipboard', label: RED._("keyboard.cutNode"), disabled: !hasSelection },
43558
- { onselect: 'core:copy-selection-to-internal-clipboard', label: RED._("keyboard.copyNode"), disabled: !hasSelection },
43559
- { onselect: 'core:paste-from-internal-clipboard', label: RED._("keyboard.pasteNode"), disabled: !RED.view.clipboard() },
43560
- { onselect: 'core:delete-selection', disabled: !canDelete },
43561
- { onselect: 'core:show-export-dialog', label: RED._("menu.label.export") },
43562
- { onselect: 'core:select-all-nodes' }
43563
- )
43564
44740
 
43565
- if (hasSelection) {
43566
44741
  menuItems.push(
43567
44742
  null,
43568
- isGroup ?
43569
- { onselect: 'core:ungroup-selection', disabled: !isGroup }
43570
- : { onselect: 'core:group-selection', disabled: !hasSelection }
44743
+ { onselect: 'core:undo', disabled: RED.history.list().length === 0 },
44744
+ { onselect: 'core:redo', disabled: RED.history.listRedo().length === 0 },
44745
+ null,
44746
+ { onselect: 'core:cut-selection-to-internal-clipboard', label: RED._("keyboard.cutNode"), disabled: !canEdit || !hasSelection },
44747
+ { onselect: 'core:copy-selection-to-internal-clipboard', label: RED._("keyboard.copyNode"), disabled: !hasSelection },
44748
+ { onselect: 'core:paste-from-internal-clipboard', label: RED._("keyboard.pasteNode"), disabled: !canEdit || !RED.view.clipboard() },
44749
+ { onselect: 'core:delete-selection', disabled: !canEdit || !canDelete },
44750
+ { onselect: 'core:delete-selection-and-reconnect', label: RED._('keyboard.deleteReconnect'), disabled: !canEdit || !canDelete },
44751
+ { onselect: 'core:show-export-dialog', label: RED._("menu.label.export") },
44752
+ { onselect: 'core:select-all-nodes' },
43571
44753
  )
43572
- if (canRemoveFromGroup) {
43573
- menuItems.push({ onselect: 'core:remove-selection-from-group', label: RED._("menu.label.groupRemoveSelection") })
43574
- }
43575
-
43576
44754
  }
43577
44755
 
43578
44756
  var direction = "right";
@@ -44567,6 +45745,11 @@ RED.subflow = (function() {
44567
45745
  var subflowInstances = [];
44568
45746
  if (activeSubflow) {
44569
45747
  RED.nodes.filterNodes({type:"subflow:"+activeSubflow.id}).forEach(function(n) {
45748
+ const parentFlow = RED.nodes.workspace(n.z)
45749
+ const wasLocked = parentFlow && parentFlow.locked
45750
+ if (wasLocked) {
45751
+ parentFlow.locked = false
45752
+ }
44570
45753
  subflowInstances.push({
44571
45754
  id: n.id,
44572
45755
  changed: n.changed
@@ -44579,6 +45762,9 @@ RED.subflow = (function() {
44579
45762
  n.resize = true;
44580
45763
  n.dirty = true;
44581
45764
  RED.editor.updateNodeProperties(n);
45765
+ if (wasLocked) {
45766
+ parentFlow.locked = true
45767
+ }
44582
45768
  });
44583
45769
  RED.editor.validateNode(activeSubflow);
44584
45770
  return {
@@ -44725,44 +45911,7 @@ RED.subflow = (function() {
44725
45911
 
44726
45912
  $("#red-ui-subflow-delete").on("click", function(event) {
44727
45913
  event.preventDefault();
44728
- var subflow = RED.nodes.subflow(RED.workspaces.active());
44729
- if (subflow.instances.length > 0) {
44730
- var msg = $('<div>')
44731
- $('<p>').text(RED._("subflow.subflowInstances",{count: subflow.instances.length})).appendTo(msg);
44732
- $('<p>').text(RED._("subflow.confirmDelete")).appendTo(msg);
44733
- var confirmDeleteNotification = RED.notify(msg, {
44734
- modal: true,
44735
- fixed: true,
44736
- buttons: [
44737
- {
44738
- text: RED._('common.label.cancel'),
44739
- click: function() {
44740
- confirmDeleteNotification.close();
44741
- }
44742
- },
44743
- {
44744
- text: RED._('workspace.confirmDelete'),
44745
- class: "primary",
44746
- click: function() {
44747
- confirmDeleteNotification.close();
44748
- completeDelete();
44749
- }
44750
- }
44751
- ]
44752
- });
44753
-
44754
- return;
44755
- } else {
44756
- completeDelete();
44757
- }
44758
- function completeDelete() {
44759
- var startDirty = RED.nodes.dirty();
44760
- var historyEvent = removeSubflow(RED.workspaces.active());
44761
- historyEvent.t = 'delete';
44762
- historyEvent.dirty = startDirty;
44763
- RED.history.push(historyEvent);
44764
- }
44765
-
45914
+ RED.subflow.delete(RED.workspaces.active())
44766
45915
  });
44767
45916
 
44768
45917
  refreshToolbar(activeSubflow);
@@ -44775,7 +45924,51 @@ RED.subflow = (function() {
44775
45924
  $("#red-ui-workspace-toolbar").hide().empty();
44776
45925
  $("#red-ui-workspace-chart").css({"margin-top": "0"});
44777
45926
  }
45927
+ function deleteSubflow(id) {
45928
+ const subflow = RED.nodes.subflow(id || RED.workspaces.active());
45929
+ if (!subflow) {
45930
+ return
45931
+ }
45932
+ if (subflow.instances.length > 0) {
45933
+ if (subflow.instances.some(sf => { const ws = RED.nodes.workspace(sf.z); return ws?ws.locked:false })) {
45934
+ return
45935
+ }
45936
+ const msg = $('<div>')
45937
+ $('<p>').text(RED._("subflow.subflowInstances",{count: subflow.instances.length})).appendTo(msg);
45938
+ $('<p>').text(RED._("subflow.confirmDelete")).appendTo(msg);
45939
+ const confirmDeleteNotification = RED.notify(msg, {
45940
+ modal: true,
45941
+ fixed: true,
45942
+ buttons: [
45943
+ {
45944
+ text: RED._('common.label.cancel'),
45945
+ click: function() {
45946
+ confirmDeleteNotification.close();
45947
+ }
45948
+ },
45949
+ {
45950
+ text: RED._('workspace.confirmDelete'),
45951
+ class: "primary",
45952
+ click: function() {
45953
+ confirmDeleteNotification.close();
45954
+ completeDelete();
45955
+ }
45956
+ }
45957
+ ]
45958
+ });
44778
45959
 
45960
+ return;
45961
+ } else {
45962
+ completeDelete();
45963
+ }
45964
+ function completeDelete() {
45965
+ const startDirty = RED.nodes.dirty();
45966
+ const historyEvent = removeSubflow(subflow.id);
45967
+ historyEvent.t = 'delete';
45968
+ historyEvent.dirty = startDirty;
45969
+ RED.history.push(historyEvent);
45970
+ }
45971
+ }
44779
45972
  function removeSubflow(id, keepInstanceNodes) {
44780
45973
  // TODO: A lot of this logic is common with RED.nodes.removeWorkspace
44781
45974
  var removedNodes = [];
@@ -44800,6 +45993,13 @@ RED.subflow = (function() {
44800
45993
  RED.nodes.groups(id).forEach(function(n) {
44801
45994
  removedGroups.push(n);
44802
45995
  })
45996
+
45997
+ var removedJunctions = RED.nodes.junctions(id)
45998
+ for (var i=0;i<removedJunctions.length;i++) {
45999
+ var removedEntities = RED.nodes.removeJunction(removedJunctions[i])
46000
+ removedLinks = removedLinks.concat(removedEntities.links)
46001
+ }
46002
+
44803
46003
  var removedConfigNodes = [];
44804
46004
  for (var i=0;i<removedNodes.length;i++) {
44805
46005
  var removedEntities = RED.nodes.remove(removedNodes[i].id);
@@ -44830,6 +46030,7 @@ RED.subflow = (function() {
44830
46030
  nodes:removedNodes,
44831
46031
  links:removedLinks,
44832
46032
  groups: removedGroups,
46033
+ junctions: removedJunctions,
44833
46034
  subflows: [activeSubflow]
44834
46035
  }
44835
46036
  }
@@ -44844,7 +46045,7 @@ RED.subflow = (function() {
44844
46045
  }
44845
46046
  });
44846
46047
  RED.events.on("view:selection-changed",function(selection) {
44847
- if (!selection.nodes) {
46048
+ if (!selection.nodes || RED.workspaces.isActiveLocked()) {
44848
46049
  RED.menu.setDisabled("menu-item-subflow-convert",true);
44849
46050
  } else {
44850
46051
  RED.menu.setDisabled("menu-item-subflow-convert",false);
@@ -44907,6 +46108,9 @@ RED.subflow = (function() {
44907
46108
  }
44908
46109
 
44909
46110
  function convertToSubflow() {
46111
+ if (RED.workspaces.isActiveLocked()) {
46112
+ return
46113
+ }
44910
46114
  var selection = RED.view.selection();
44911
46115
  if (!selection.nodes) {
44912
46116
  RED.notify(RED._("subflow.errors.noNodesSelected"),"error");
@@ -45062,7 +46266,7 @@ RED.subflow = (function() {
45062
46266
  }
45063
46267
  subflowInstance._def = RED.nodes.getType(subflowInstance.type);
45064
46268
  RED.editor.validateNode(subflowInstance);
45065
- RED.nodes.add(subflowInstance);
46269
+ subflowInstance = RED.nodes.add(subflowInstance);
45066
46270
 
45067
46271
  if (containingGroup) {
45068
46272
  RED.group.addToGroup(containingGroup, subflowInstance);
@@ -45226,7 +46430,6 @@ RED.subflow = (function() {
45226
46430
 
45227
46431
 
45228
46432
  function buildEnvUIRow(row, tenv, ui, node) {
45229
- console.log(tenv, ui)
45230
46433
  ui.label = ui.label||{};
45231
46434
  if ((tenv.type === "cred" || (tenv.parent && tenv.parent.type === "cred")) && !ui.type) {
45232
46435
  ui.type = "cred";
@@ -45617,7 +46820,10 @@ RED.subflow = (function() {
45617
46820
  init: init,
45618
46821
  createSubflow: createSubflow,
45619
46822
  convertToSubflow: convertToSubflow,
46823
+ // removeSubflow: Internal function to remove subflow
45620
46824
  removeSubflow: removeSubflow,
46825
+ // delete: Prompt user for confirmation
46826
+ delete: deleteSubflow,
45621
46827
  refresh: refresh,
45622
46828
  removeInput: removeSubflowInput,
45623
46829
  removeOutput: removeSubflowOutput,
@@ -45732,6 +46938,7 @@ RED.group = (function() {
45732
46938
  RED.editor.colorPicker.create({
45733
46939
  id:"node-input-style-stroke",
45734
46940
  value: style.stroke || defaultGroupStyle.stroke || "#a4a4a4",
46941
+ defaultValue: "#a4a4a4",
45735
46942
  palette: colorPalette,
45736
46943
  cellPerRow: colorCount,
45737
46944
  cellWidth: 16,
@@ -45743,6 +46950,7 @@ RED.group = (function() {
45743
46950
  RED.editor.colorPicker.create({
45744
46951
  id:"node-input-style-fill",
45745
46952
  value: style.fill || defaultGroupStyle.fill ||"none",
46953
+ defaultValue: "none",
45746
46954
  palette: colorPalette,
45747
46955
  cellPerRow: colorCount,
45748
46956
  cellWidth: 16,
@@ -45760,6 +46968,7 @@ RED.group = (function() {
45760
46968
  RED.editor.colorPicker.create({
45761
46969
  id:"node-input-style-color",
45762
46970
  value: style.color || defaultGroupStyle.color ||"#a4a4a4",
46971
+ defaultValue: "#a4a4a4",
45763
46972
  palette: colorPalette,
45764
46973
  cellPerRow: colorCount,
45765
46974
  cellWidth: 16,
@@ -45816,6 +47025,8 @@ RED.group = (function() {
45816
47025
  var activateMerge = false;
45817
47026
  var activateRemove = false;
45818
47027
  var singleGroupSelected = false;
47028
+ var locked = RED.workspaces.isActiveLocked()
47029
+
45819
47030
  if (activateGroup) {
45820
47031
  singleGroupSelected = selection.nodes.length === 1 && selection.nodes[0].type === 'group';
45821
47032
  selection.nodes.forEach(function (n) {
@@ -45830,12 +47041,12 @@ RED.group = (function() {
45830
47041
  activateMerge = (selection.nodes.length > 1);
45831
47042
  }
45832
47043
  }
45833
- RED.menu.setDisabled("menu-item-group-group", !activateGroup);
45834
- RED.menu.setDisabled("menu-item-group-ungroup", !activateUngroup);
45835
- RED.menu.setDisabled("menu-item-group-merge", !activateMerge);
45836
- RED.menu.setDisabled("menu-item-group-remove", !activateRemove);
47044
+ RED.menu.setDisabled("menu-item-group-group", locked || !activateGroup);
47045
+ RED.menu.setDisabled("menu-item-group-ungroup", locked || !activateUngroup);
47046
+ RED.menu.setDisabled("menu-item-group-merge", locked || !activateMerge);
47047
+ RED.menu.setDisabled("menu-item-group-remove", locked || !activateRemove);
45837
47048
  RED.menu.setDisabled("menu-item-edit-copy-group-style", !singleGroupSelected);
45838
- RED.menu.setDisabled("menu-item-edit-paste-group-style", !activateUngroup);
47049
+ RED.menu.setDisabled("menu-item-edit-paste-group-style", locked || !activateUngroup);
45839
47050
  });
45840
47051
 
45841
47052
  RED.actions.add("core:group-selection", function() { groupSelection() })
@@ -45892,6 +47103,7 @@ RED.group = (function() {
45892
47103
  }
45893
47104
  }
45894
47105
  function pasteGroupStyle() {
47106
+ if (RED.workspaces.isActiveLocked()) { return }
45895
47107
  if (RED.view.state() !== RED.state.DEFAULT) { return }
45896
47108
  if (groupStyleClipboard) {
45897
47109
  var selection = RED.view.selection();
@@ -45926,6 +47138,7 @@ RED.group = (function() {
45926
47138
  }
45927
47139
 
45928
47140
  function groupSelection() {
47141
+ if (RED.workspaces.isActiveLocked()) { return }
45929
47142
  if (RED.view.state() !== RED.state.DEFAULT) { return }
45930
47143
  var selection = RED.view.selection();
45931
47144
  if (selection.nodes) {
@@ -45944,6 +47157,7 @@ RED.group = (function() {
45944
47157
  }
45945
47158
  }
45946
47159
  function ungroupSelection() {
47160
+ if (RED.workspaces.isActiveLocked()) { return }
45947
47161
  if (RED.view.state() !== RED.state.DEFAULT) { return }
45948
47162
  var selection = RED.view.selection();
45949
47163
  if (selection.nodes) {
@@ -45967,6 +47181,7 @@ RED.group = (function() {
45967
47181
  }
45968
47182
 
45969
47183
  function ungroup(g) {
47184
+ if (RED.workspaces.isActiveLocked()) { return }
45970
47185
  var nodes = [];
45971
47186
  var parentGroup = RED.nodes.group(g.g);
45972
47187
  g.nodes.forEach(function(n) {
@@ -45993,6 +47208,7 @@ RED.group = (function() {
45993
47208
  }
45994
47209
 
45995
47210
  function mergeSelection() {
47211
+ if (RED.workspaces.isActiveLocked()) { return }
45996
47212
  if (RED.view.state() !== RED.state.DEFAULT) { return }
45997
47213
  var selection = RED.view.selection();
45998
47214
  if (selection.nodes) {
@@ -46062,6 +47278,7 @@ RED.group = (function() {
46062
47278
  }
46063
47279
 
46064
47280
  function removeSelection() {
47281
+ if (RED.workspaces.isActiveLocked()) { return }
46065
47282
  if (RED.view.state() !== RED.state.DEFAULT) { return }
46066
47283
  var selection = RED.view.selection();
46067
47284
  if (selection.nodes) {
@@ -46089,6 +47306,7 @@ RED.group = (function() {
46089
47306
  }
46090
47307
  }
46091
47308
  function createGroup(nodes) {
47309
+ if (RED.workspaces.isActiveLocked()) { return }
46092
47310
  if (nodes.length === 0) {
46093
47311
  return;
46094
47312
  }
@@ -46111,7 +47329,7 @@ RED.group = (function() {
46111
47329
  }
46112
47330
 
46113
47331
  group.z = nodes[0].z;
46114
- RED.nodes.addGroup(group);
47332
+ group = RED.nodes.addGroup(group);
46115
47333
 
46116
47334
  try {
46117
47335
  addToGroup(group,nodes);
@@ -46194,6 +47412,7 @@ RED.group = (function() {
46194
47412
  markDirty(group);
46195
47413
  }
46196
47414
  function removeFromGroup(group, nodes, reparent) {
47415
+ if (RED.workspaces.isActiveLocked()) { return }
46197
47416
  if (!Array.isArray(nodes)) {
46198
47417
  nodes = [nodes];
46199
47418
  }
@@ -47196,7 +48415,7 @@ RED.projects = (function() {
47196
48415
  var sshwarningRow = $('<div class="red-ui-projects-dialog-screen-create-row-auth-error-no-keys"></div>').hide().appendTo(subrow);
47197
48416
  $('<div class="form-row"><i class="fa fa-warning"></i> '+RED._("projects.clone-project.ssh-key-desc")+'</div>').appendTo(sshwarningRow);
47198
48417
  subrow = $('<div style="text-align: center">').appendTo(sshwarningRow);
47199
- $('<button class="red-ui-button red-ui-projects-dialog-button">'+RED._("projects.clone-project.ssh-key-add")+'</button>').appendTo(subrow).on("click", function(e) {
48418
+ $('<button type="button" class="red-ui-button red-ui-projects-dialog-button">'+RED._("projects.clone-project.ssh-key-add")+'</button>').appendTo(subrow).on("click", function(e) {
47200
48419
  e.preventDefault();
47201
48420
  dialog.dialog( "close" );
47202
48421
  RED.userSettings.show('gitconfig');
@@ -47822,11 +49041,11 @@ RED.projects = (function() {
47822
49041
 
47823
49042
  row = $('<div class="form-row button-group"></div>').appendTo(container);
47824
49043
 
47825
- var openProject = $('<button data-type="open" class="red-ui-button red-ui-projects-dialog-button red-ui-projects-dialog-screen-create-type toggle"><i class="fa fa-archive fa-2x"></i><i style="position: absolute;" class="fa fa-folder-open"></i><br/>'+RED._("projects.create.open")+'</button>').appendTo(row);
47826
- var createAsEmpty = $('<button data-type="empty" class="red-ui-button red-ui-projects-dialog-button red-ui-projects-dialog-screen-create-type toggle"><i class="fa fa-archive fa-2x"></i><i style="position: absolute;" class="fa fa-asterisk"></i><br/>'+RED._("projects.create.create")+'</button>').appendTo(row);
47827
- // var createAsCopy = $('<button data-type="copy" class="red-ui-button red-ui-projects-dialog-button red-ui-projects-dialog-screen-create-type toggle"><i class="fa fa-archive fa-2x"></i><i class="fa fa-long-arrow-right fa-2x"></i><i class="fa fa-archive fa-2x"></i><br/>Copy existing</button>').appendTo(row);
47828
- var createAsClone = $('<button data-type="clone" class="red-ui-button red-ui-projects-dialog-button red-ui-projects-dialog-screen-create-type toggle"><i class="fa fa-archive fa-2x"></i><i style="position: absolute;" class="fa fa-git"></i><br/>'+RED._("projects.create.clone")+'</button>').appendTo(row);
47829
- // var createAsClone = $('<button data-type="clone" class="red-ui-button red-ui-projects-dialog-button red-ui-projects-dialog-screen-create-type toggle"><i class="fa fa-git fa-2x"></i><i class="fa fa-arrows-h fa-2x"></i><i class="fa fa-archive fa-2x"></i><br/>Clone Repository</button>').appendTo(row);
49044
+ var openProject = $('<button type="button" data-type="open" class="red-ui-button red-ui-projects-dialog-button red-ui-projects-dialog-screen-create-type toggle"><i class="fa fa-archive fa-2x"></i><i style="position: absolute;" class="fa fa-folder-open"></i><br/>'+RED._("projects.create.open")+'</button>').appendTo(row);
49045
+ var createAsEmpty = $('<button type="button" data-type="empty" class="red-ui-button red-ui-projects-dialog-button red-ui-projects-dialog-screen-create-type toggle"><i class="fa fa-archive fa-2x"></i><i style="position: absolute;" class="fa fa-asterisk"></i><br/>'+RED._("projects.create.create")+'</button>').appendTo(row);
49046
+ // var createAsCopy = $('<button type="button" data-type="copy" class="red-ui-button red-ui-projects-dialog-button red-ui-projects-dialog-screen-create-type toggle"><i class="fa fa-archive fa-2x"></i><i class="fa fa-long-arrow-right fa-2x"></i><i class="fa fa-archive fa-2x"></i><br/>Copy existing</button>').appendTo(row);
49047
+ var createAsClone = $('<button type="button" data-type="clone" class="red-ui-button red-ui-projects-dialog-button red-ui-projects-dialog-screen-create-type toggle"><i class="fa fa-archive fa-2x"></i><i style="position: absolute;" class="fa fa-git"></i><br/>'+RED._("projects.create.clone")+'</button>').appendTo(row);
49048
+ // var createAsClone = $('<button type="button" data-type="clone" class="red-ui-button red-ui-projects-dialog-button red-ui-projects-dialog-screen-create-type toggle"><i class="fa fa-git fa-2x"></i><i class="fa fa-arrows-h fa-2x"></i><i class="fa fa-archive fa-2x"></i><br/>Clone Repository</button>').appendTo(row);
47830
49049
  row.find(".red-ui-projects-dialog-screen-create-type").on("click", function(evt) {
47831
49050
  evt.preventDefault();
47832
49051
  container.find(".red-ui-projects-dialog-screen-create-type").removeClass('selected');
@@ -47922,7 +49141,7 @@ RED.projects = (function() {
47922
49141
  var credentialsLeftBox = $('<div class="red-ui-projects-dialog-credentials-box-left">').appendTo(credentialsBox);
47923
49142
 
47924
49143
  var credentialsEnabledBox = $('<div class="form-row red-ui-projects-dialog-credentials-box-enabled"></div>').appendTo(credentialsLeftBox);
47925
- $('<label class="red-ui-projects-edit-form-inline-label"><input type="radio" name="projects-encryption-type" value="enabled"> <i class="fa fa-lock"></i> <span>'+RED._("projects.encryption-config.enable")+'</span></label>').appendTo(credentialsEnabledBox);
49144
+ $('<label class="red-ui-projects-edit-form-inline-label"><input type="radio" name="projects-encryption-type" value="enabled" checked> <i class="fa fa-lock"></i> <span>'+RED._("projects.encryption-config.enable")+'</span></label>').appendTo(credentialsEnabledBox);
47926
49145
  var credentialsDisabledBox = $('<div class="form-row red-ui-projects-dialog-credentials-box-disabled disabled"></div>').appendTo(credentialsLeftBox);
47927
49146
  $('<label class="red-ui-projects-edit-form-inline-label"><input type="radio" name="projects-encryption-type" value="disabled"> <i class="fa fa-unlock"></i> <span>'+RED._("projects.encryption-config.disable")+'</span></label>').appendTo(credentialsDisabledBox);
47928
49147
 
@@ -48048,7 +49267,7 @@ RED.projects = (function() {
48048
49267
  var sshwarningRow = $('<div class="red-ui-projects-dialog-screen-create-row-auth-error-no-keys"></div>').hide().appendTo(subrow);
48049
49268
  $('<div class="form-row"><i class="fa fa-warning"></i> '+RED._("projects.create.desc2")+'</div>').appendTo(sshwarningRow);
48050
49269
  subrow = $('<div style="text-align: center">').appendTo(sshwarningRow);
48051
- $('<button class="red-ui-button red-ui-projects-dialog-button">'+RED._("projects.create.add-ssh-key")+'</button>').appendTo(subrow).on("click", function(e) {
49270
+ $('<button type="button" class="red-ui-button red-ui-projects-dialog-button">'+RED._("projects.create.add-ssh-key")+'</button>').appendTo(subrow).on("click", function(e) {
48052
49271
  e.preventDefault();
48053
49272
  $('#red-ui-projects-dialog-cancel').trigger("click");
48054
49273
  RED.userSettings.show('gitconfig');
@@ -48282,14 +49501,14 @@ RED.projects = (function() {
48282
49501
  function deleteProject(row,name,done) {
48283
49502
  var cover = $('<div class="red-ui-projects-dialog-project-list-entry-delete-confirm"></div>').on("click", function(evt) { evt.stopPropagation(); }).appendTo(row);
48284
49503
  $('<span>').text(RED._("projects.delete.confirm")).appendTo(cover);
48285
- $('<button class="red-ui-button red-ui-projects-dialog-button">'+RED._("common.label.cancel")+'</button>')
49504
+ $('<button type="button" class="red-ui-button red-ui-projects-dialog-button">'+RED._("common.label.cancel")+'</button>')
48286
49505
  .appendTo(cover)
48287
49506
  .on("click", function(e) {
48288
49507
  e.stopPropagation();
48289
49508
  cover.remove();
48290
49509
  done(true);
48291
49510
  });
48292
- $('<button class="red-ui-button red-ui-projects-dialog-button primary">'+RED._("common.label.delete")+'</button>')
49511
+ $('<button type="button" class="red-ui-button red-ui-projects-dialog-button primary">'+RED._("common.label.delete")+'</button>')
48293
49512
  .appendTo(cover)
48294
49513
  .on("click", function(e) {
48295
49514
  e.stopPropagation();
@@ -48473,7 +49692,7 @@ RED.projects = (function() {
48473
49692
  header.addClass("selectable");
48474
49693
 
48475
49694
  var tools = $('<div class="red-ui-projects-dialog-project-list-entry-tools"></div>').appendTo(header);
48476
- $('<button class="red-ui-button red-ui-projects-dialog-button red-ui-button-small" style="float: right;"><i class="fa fa-trash"></i></button>')
49695
+ $('<button type="button" class="red-ui-button red-ui-projects-dialog-button red-ui-button-small" style="float: right;"><i class="fa fa-trash"></i></button>')
48477
49696
  .appendTo(tools)
48478
49697
  .on("click", function(e) {
48479
49698
  e.stopPropagation();
@@ -53229,10 +54448,15 @@ RED.touch.radialMenu = (function() {
53229
54448
 
53230
54449
  function listTour() {
53231
54450
  return [
54451
+ {
54452
+ id: "3_1",
54453
+ label: "3.1",
54454
+ path: "./tours/welcome.js"
54455
+ },
53232
54456
  {
53233
54457
  id: "3_0",
53234
54458
  label: "3.0",
53235
- path: "./tours/welcome.js"
54459
+ path: "./tours/3.0/welcome.js"
53236
54460
  },
53237
54461
  {
53238
54462
  id: "2_2",