@node-red/editor-client 3.0.2 → 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 +0 -0
  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 +91 -0
  16. package/public/red/red.js +1756 -543
  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]) {
@@ -4708,6 +4772,9 @@ RED.nodes = (function() {
4708
4772
  node.type = n.type;
4709
4773
  for (var d in n._def.defaults) {
4710
4774
  if (n._def.defaults.hasOwnProperty(d)) {
4775
+ if (d === 'locked' && !n.locked) {
4776
+ continue
4777
+ }
4711
4778
  node[d] = n[d];
4712
4779
  }
4713
4780
  }
@@ -5627,7 +5694,7 @@ RED.nodes = (function() {
5627
5694
  }
5628
5695
  }
5629
5696
  } else {
5630
- 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))
5631
5698
  if (!keepNodesCurrentZ && n.z && !workspace_map[n.z] && !subflow_map[n.z]) {
5632
5699
  n.z = activeWorkspace;
5633
5700
  }
@@ -5729,7 +5796,7 @@ RED.nodes = (function() {
5729
5796
  node.id = getID();
5730
5797
  } else {
5731
5798
  node.id = n.id;
5732
- 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))
5733
5800
  if (!keepNodesCurrentZ && (node.z == null || (!workspace_map[node.z] && !subflow_map[node.z]))) {
5734
5801
  if (createMissingWorkspace) {
5735
5802
  if (missingWorkspace === null) {
@@ -5977,19 +6044,6 @@ RED.nodes = (function() {
5977
6044
  if (n.g && !new_group_set.has(n.g)) {
5978
6045
  delete n.g;
5979
6046
  }
5980
- n.nodes = n.nodes.map(function(id) {
5981
- return node_map[id];
5982
- })
5983
- // Just in case the group references a node that doesn't exist for some reason
5984
- n.nodes = n.nodes.filter(function(v) {
5985
- if (v) {
5986
- // Repair any nodes that have forgotten they are in this group
5987
- if (v.g !== n.id) {
5988
- v.g = n.id;
5989
- }
5990
- }
5991
- return !!v
5992
- });
5993
6047
  if (!n.g) {
5994
6048
  groupDepthMap[n.id] = 0;
5995
6049
  }
@@ -6012,21 +6066,22 @@ RED.nodes = (function() {
6012
6066
  return groupDepthMap[A.id] - groupDepthMap[B.id];
6013
6067
  });
6014
6068
  for (i=0;i<new_groups.length;i++) {
6015
- n = new_groups[i];
6016
- addGroup(n);
6069
+ new_groups[i] = addGroup(new_groups[i]);
6070
+ node_map[new_groups[i].id] = new_groups[i]
6017
6071
  }
6018
6072
 
6019
6073
  for (i=0;i<new_junctions.length;i++) {
6020
- var junction = new_junctions[i];
6021
- addJunction(junction);
6074
+ new_junctions[i] = addJunction(new_junctions[i]);
6075
+ node_map[new_junctions[i].id] = new_junctions[i]
6022
6076
  }
6023
6077
 
6024
6078
 
6025
6079
  // Now the nodes have been fully updated, add them.
6026
6080
  for (i=0;i<new_nodes.length;i++) {
6027
- var node = new_nodes[i];
6028
- addNode(node);
6081
+ new_nodes[i] = addNode(new_nodes[i])
6082
+ node_map[new_nodes[i].id] = new_nodes[i]
6029
6083
  }
6084
+
6030
6085
  // Finally validate them all.
6031
6086
  // This has to be done after everything is added so that any checks for
6032
6087
  // dependent config nodes will pass
@@ -6034,6 +6089,39 @@ RED.nodes = (function() {
6034
6089
  var node = new_nodes[i];
6035
6090
  RED.editor.validateNode(node);
6036
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
+ }
6037
6125
 
6038
6126
  RED.workspaces.refresh();
6039
6127
 
@@ -6162,11 +6250,17 @@ RED.nodes = (function() {
6162
6250
  junctions = {};
6163
6251
  junctionsByZ = {};
6164
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
+
6165
6260
  var subflowIds = Object.keys(subflows);
6166
6261
  subflowIds.forEach(function(id) {
6167
6262
  RED.subflow.removeSubflow(id)
6168
6263
  });
6169
- var workspaceIds = Object.keys(workspaces);
6170
6264
  workspaceIds.forEach(function(id) {
6171
6265
  RED.workspaces.remove(workspaces[id]);
6172
6266
  });
@@ -6187,10 +6281,14 @@ RED.nodes = (function() {
6187
6281
  }
6188
6282
 
6189
6283
  function addGroup(group) {
6284
+ if (!group.__isProxy__) {
6285
+ group = new Proxy(group, nodeProxyHandler)
6286
+ }
6190
6287
  groupsByZ[group.z] = groupsByZ[group.z] || [];
6191
6288
  groupsByZ[group.z].push(group);
6192
6289
  groups[group.id] = group;
6193
6290
  RED.events.emit("groups:add",group);
6291
+ return group
6194
6292
  }
6195
6293
  function removeGroup(group) {
6196
6294
  var i = groupsByZ[group.z].indexOf(group);
@@ -6211,6 +6309,9 @@ RED.nodes = (function() {
6211
6309
  }
6212
6310
 
6213
6311
  function addJunction(junction) {
6312
+ if (!junction.__isProxy__) {
6313
+ junction = new Proxy(junction, nodeProxyHandler)
6314
+ }
6214
6315
  junctionsByZ[junction.z] = junctionsByZ[junction.z] || []
6215
6316
  junctionsByZ[junction.z].push(junction)
6216
6317
  junctions[junction.id] = junction;
@@ -6218,6 +6319,7 @@ RED.nodes = (function() {
6218
6319
  nodeLinks[junction.id] = {in:[],out:[]};
6219
6320
  }
6220
6321
  RED.events.emit("junctions:add", junction)
6322
+ return junction
6221
6323
  }
6222
6324
  function removeJunction(junction) {
6223
6325
  var i = junctionsByZ[junction.z].indexOf(junction)
@@ -6402,6 +6504,7 @@ RED.nodes = (function() {
6402
6504
  }
6403
6505
  });
6404
6506
 
6507
+ const nodeGroupMap = {}
6405
6508
  var replaceNodeIds = Object.keys(replaceNodes);
6406
6509
  if (replaceNodeIds.length > 0) {
6407
6510
  var reimportList = [];
@@ -6412,6 +6515,12 @@ RED.nodes = (function() {
6412
6515
  } else {
6413
6516
  allNodes.removeNode(n);
6414
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
+ }
6415
6524
  reimportList.push(convertNode(n));
6416
6525
  RED.events.emit('nodes:remove',n);
6417
6526
  });
@@ -6433,6 +6542,18 @@ RED.nodes = (function() {
6433
6542
  var newNodeMap = {};
6434
6543
  result.nodes.forEach(function(n) {
6435
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
+ }
6436
6557
  });
6437
6558
  RED.nodes.eachLink(function(l) {
6438
6559
  if (newNodeMap.hasOwnProperty(l.source.id)) {
@@ -6493,7 +6614,7 @@ RED.nodes = (function() {
6493
6614
  },
6494
6615
  addWorkspace: addWorkspace,
6495
6616
  removeWorkspace: removeWorkspace,
6496
- getWorkspaceOrder: function() { return workspacesOrder },
6617
+ getWorkspaceOrder: function() { return [...workspacesOrder] },
6497
6618
  setWorkspaceOrder: function(order) { workspacesOrder = order; },
6498
6619
  workspace: getWorkspace,
6499
6620
 
@@ -7420,7 +7541,7 @@ RED.nodes.fontAwesome = (function() {
7420
7541
  * limitations under the License.
7421
7542
  **/
7422
7543
 
7423
- /**
7544
+ /**
7424
7545
  * An API for undo / redo history buffer
7425
7546
  * @namespace RED.history
7426
7547
  */
@@ -7840,7 +7961,9 @@ RED.history = (function() {
7840
7961
 
7841
7962
  if (ev.node.type === 'tab' && ev.changes.hasOwnProperty('disabled')) {
7842
7963
  $("#red-ui-tab-"+(ev.node.id.replace(".","-"))).toggleClass('red-ui-workspace-disabled',!!ev.node.disabled);
7843
- $("#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);
7844
7967
  }
7845
7968
  if (ev.subflow) {
7846
7969
  inverseEv.subflow = {};
@@ -8248,6 +8371,52 @@ RED.validators = {
8248
8371
  };
8249
8372
  }
8250
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
+ })();
8251
8420
  ;/**
8252
8421
  * Copyright JS Foundation and other contributors, http://js.foundation
8253
8422
  *
@@ -8346,6 +8515,37 @@ RED.utils = (function() {
8346
8515
  }
8347
8516
  }
8348
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
+
8349
8549
  window._marked.setOptions({
8350
8550
  renderer: renderer,
8351
8551
  gfm: true,
@@ -11282,8 +11482,8 @@ RED.menu = (function() {
11282
11482
 
11283
11483
  var link = $(linkContent).appendTo(item);
11284
11484
  opt.link = link;
11285
- if (typeof opt.onselect === 'string') {
11286
- 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);
11287
11487
  if (shortcut && shortcut.key) {
11288
11488
  opt.shortcutSpan = $('<span class="red-ui-popover-key">'+RED.keyboard.formatKey(shortcut.key, true)+'</span>').appendTo(link.find(".red-ui-menu-label"));
11289
11489
  }
@@ -12695,7 +12895,29 @@ RED.tabs = (function() {
12695
12895
  })
12696
12896
  }
12697
12897
 
12698
-
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
+ }
12699
12921
 
12700
12922
  var scrollLeft;
12701
12923
  var scrollRight;
@@ -13361,19 +13583,19 @@ RED.tabs = (function() {
13361
13583
  event.preventDefault();
13362
13584
  removeTab(tab.id);
13363
13585
  });
13364
- RED.popover.tooltip(closeLink,RED._("workspace.hideFlow"));
13365
- }
13366
- if (tab.hideable) {
13367
- li.addClass("red-ui-tabs-closeable")
13368
- var closeLink = $("<a/>",{href:"#",class:"red-ui-tab-close red-ui-tab-hide"}).appendTo(li);
13369
- closeLink.append('<i class="fa fa-eye" />');
13370
- closeLink.append('<i class="fa fa-eye-slash" />');
13371
- closeLink.on("click",function(event) {
13372
- event.preventDefault();
13373
- hideTab(tab.id);
13374
- });
13375
- RED.popover.tooltip(closeLink,RED._("workspace.hideFlow"));
13376
- }
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
+ // }
13377
13599
 
13378
13600
  var badges = $('<span class="red-ui-tabs-badges"></span>').appendTo(li);
13379
13601
  if (options.onselect) {
@@ -13492,6 +13714,9 @@ RED.tabs = (function() {
13492
13714
  activeIndex: function() {
13493
13715
  return ul.find("li.active").index()
13494
13716
  },
13717
+ getTabIndex: function (id) {
13718
+ return ul.find("a[href='#"+id+"']").parent().index()
13719
+ },
13495
13720
  contains: function(id) {
13496
13721
  return ul.find("a[href='#"+id+"']").length > 0;
13497
13722
  },
@@ -13894,7 +14119,7 @@ RED.stack = (function() {
13894
14119
  { value: "reset", source: ["delay","trigger","join","rbe"] },
13895
14120
  { value: "responseCookies", source: ["http request"] },
13896
14121
  { value: "responseTopic", source: ["mqtt"] },
13897
- { value: "responseURL", source: ["http request"] },
14122
+ { value: "responseUrl", source: ["http request"] },
13898
14123
  { value: "restartTimeout", source: ["join"] },
13899
14124
  { value: "retain", source: ["mqtt"] },
13900
14125
  { value: "schema", source: ["json"] },
@@ -15930,6 +16155,11 @@ RED.deploy = (function() {
15930
16155
  RED.notify('<p>' + RED._("deploy.successfulDeploy") + '</p>', "success");
15931
16156
  }
15932
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
+ }
15933
16163
  if (node.changed) {
15934
16164
  node.dirty = true;
15935
16165
  node.changed = false;
@@ -15941,6 +16171,9 @@ RED.deploy = (function() {
15941
16171
  if (node.credentials) {
15942
16172
  delete node.credentials;
15943
16173
  }
16174
+ if (flow && isLocked) {
16175
+ flow.locked = isLocked;
16176
+ }
15944
16177
  });
15945
16178
  RED.nodes.eachConfig(function (confNode) {
15946
16179
  confNode.changed = false;
@@ -18982,6 +19215,181 @@ RED.keyboard = (function() {
18982
19215
  enable: enable
18983
19216
  }
18984
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
+
18985
19393
  })();
18986
19394
  ;/**
18987
19395
  * Copyright JS Foundation and other contributors, http://js.foundation
@@ -19043,6 +19451,9 @@ RED.workspaces = (function() {
19043
19451
  if (!ws.closeable) {
19044
19452
  ws.hideable = true;
19045
19453
  }
19454
+ if (!ws.hasOwnProperty('locked')) {
19455
+ ws.locked = false
19456
+ }
19046
19457
  workspace_tabs.addTab(ws,targetIndex);
19047
19458
 
19048
19459
  var hiddenTabs = JSON.parse(RED.settings.getLocal("hiddenTabs")||"{}");
@@ -19060,6 +19471,7 @@ RED.workspaces = (function() {
19060
19471
  type: "tab",
19061
19472
  id: tabId,
19062
19473
  disabled: false,
19474
+ locked: false,
19063
19475
  info: "",
19064
19476
  label: RED._('workspace.defaultName',{number:workspaceIndex}),
19065
19477
  env: [],
@@ -19084,6 +19496,9 @@ RED.workspaces = (function() {
19084
19496
  if (workspaceTabCount === 1) {
19085
19497
  return;
19086
19498
  }
19499
+ if (ws.locked) {
19500
+ return
19501
+ }
19087
19502
  var workspaceOrder = RED.nodes.getWorkspaceOrder();
19088
19503
  ws._index = workspaceOrder.indexOf(ws.id);
19089
19504
  removeWorkspace(ws);
@@ -19104,13 +19519,206 @@ RED.workspaces = (function() {
19104
19519
  RED.editor.editSubflow(subflow);
19105
19520
  }
19106
19521
  } else {
19107
- RED.editor.editFlow(workspace);
19522
+ if (!workspace.locked) {
19523
+ RED.editor.editFlow(workspace);
19524
+ }
19108
19525
  }
19109
19526
  }
19110
19527
 
19111
19528
 
19112
19529
  var workspace_tabs;
19113
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
+ }
19114
19722
  function createWorkspaceTabs() {
19115
19723
  workspace_tabs = RED.tabs.create({
19116
19724
  id: "red-ui-workspace-tabs",
@@ -19122,8 +19730,9 @@ RED.workspaces = (function() {
19122
19730
  $("#red-ui-workspace-chart").show();
19123
19731
  activeWorkspace = tab.id;
19124
19732
  window.location.hash = 'flow/'+tab.id;
19125
- $("#red-ui-workspace").toggleClass("red-ui-workspace-disabled",!!tab.disabled);
19126
- } 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 {
19127
19736
  $("#red-ui-workspace-chart").hide();
19128
19737
  activeWorkspace = 0;
19129
19738
  window.location.hash = '';
@@ -19154,6 +19763,12 @@ RED.workspaces = (function() {
19154
19763
  if (tab.disabled) {
19155
19764
  $("#red-ui-tab-"+(tab.id.replace(".","-"))).addClass('red-ui-workspace-disabled');
19156
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
+
19157
19772
  RED.menu.setDisabled("menu-item-workspace-delete",activeWorkspace === 0 || workspaceTabCount <= 1);
19158
19773
  if (workspaceTabCount === 1) {
19159
19774
  showWorkspace();
@@ -19174,13 +19789,19 @@ RED.workspaces = (function() {
19174
19789
  RED.history.push({
19175
19790
  t:'reorder',
19176
19791
  workspaces: {
19177
- from:oldOrder,
19178
- to:newOrder
19792
+ from: oldOrder,
19793
+ to: newOrder
19179
19794
  },
19180
19795
  dirty:RED.nodes.dirty()
19181
19796
  });
19182
- RED.nodes.dirty(true);
19183
- 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
+ }
19184
19805
  },
19185
19806
  onselect: function(selectedTabs) {
19186
19807
  RED.view.select(false)
@@ -19199,12 +19820,12 @@ RED.workspaces = (function() {
19199
19820
  },
19200
19821
  onhide: function(tab) {
19201
19822
  hideStack.push(tab.id);
19202
-
19203
- var hiddenTabs = JSON.parse(RED.settings.getLocal("hiddenTabs")||"{}");
19204
- hiddenTabs[tab.id] = true;
19205
- RED.settings.setLocal("hiddenTabs",JSON.stringify(hiddenTabs));
19206
-
19207
- 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
+ }
19208
19829
  },
19209
19830
  onshow: function(tab) {
19210
19831
  removeFromHideStack(tab.id);
@@ -19219,77 +19840,8 @@ RED.workspaces = (function() {
19219
19840
  scrollable: true,
19220
19841
  addButton: "core:add-flow",
19221
19842
  addButtonCaption: RED._("workspace.addFlow"),
19222
- menu: function() {
19223
- var menuItems = [
19224
- {
19225
- id:"red-ui-tabs-menu-option-search-flows",
19226
- label: RED._("workspace.listFlows"),
19227
- onselect: "core:list-flows"
19228
- },
19229
- {
19230
- id:"red-ui-tabs-menu-option-search-subflows",
19231
- label: RED._("workspace.listSubflows"),
19232
- onselect: "core:list-subflows"
19233
- },
19234
- null,
19235
- {
19236
- id:"red-ui-tabs-menu-option-add-flow",
19237
- label: RED._("workspace.addFlow"),
19238
- onselect: "core:add-flow"
19239
- },
19240
- {
19241
- id:"red-ui-tabs-menu-option-add-flow-right",
19242
- label: RED._("workspace.addFlowToRight"),
19243
- onselect: "core:add-flow-to-right"
19244
- },
19245
- null,
19246
- {
19247
- id:"red-ui-tabs-menu-option-add-hide-flows",
19248
- label: RED._("workspace.hideFlow"),
19249
- onselect: "core:hide-flow"
19250
- },
19251
- {
19252
- id:"red-ui-tabs-menu-option-add-hide-other-flows",
19253
- label: RED._("workspace.hideOtherFlows"),
19254
- onselect: "core:hide-other-flows"
19255
- },
19256
- {
19257
- id:"red-ui-tabs-menu-option-add-show-all-flows",
19258
- label: RED._("workspace.showAllFlows"),
19259
- onselect: "core:show-all-flows"
19260
- },
19261
- {
19262
- id:"red-ui-tabs-menu-option-add-hide-all-flows",
19263
- label: RED._("workspace.hideAllFlows"),
19264
- onselect: "core:hide-all-flows"
19265
- },
19266
- {
19267
- id:"red-ui-tabs-menu-option-add-show-last-flow",
19268
- label: RED._("workspace.showLastHiddenFlow"),
19269
- onselect: "core:show-last-hidden-flow"
19270
- }
19271
- ]
19272
- let hiddenFlows = new Set()
19273
- for (let i = 0; i < hideStack.length; i++) {
19274
- let ids = hideStack[i]
19275
- if (!Array.isArray(ids)) {
19276
- ids = [ids]
19277
- }
19278
- ids.forEach(id => {
19279
- if (RED.nodes.workspace(id)) {
19280
- hiddenFlows.add(id)
19281
- }
19282
- })
19283
- }
19284
- const flowCount = hiddenFlows.size;
19285
- if (flowCount > 0) {
19286
- menuItems.unshift({
19287
- label: RED._("workspace.hiddenFlows",{count: flowCount}),
19288
- onselect: "core:list-hidden-flows"
19289
- })
19290
- }
19291
- return menuItems;
19292
- }
19843
+ menu: function() { return getMenuItems(true) },
19844
+ contextmenu: function(tab) { return getMenuItems(false, tab) }
19293
19845
  });
19294
19846
  workspaceTabCount = 0;
19295
19847
  }
@@ -19340,16 +19892,33 @@ RED.workspaces = (function() {
19340
19892
  });
19341
19893
 
19342
19894
  RED.actions.add("core:add-flow",function(opts) { addWorkspace(undefined,undefined,opts?opts.index:undefined)});
19343
- 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
+ });
19344
19904
  RED.actions.add("core:edit-flow",editWorkspace);
19345
19905
  RED.actions.add("core:remove-flow",removeWorkspace);
19346
19906
  RED.actions.add("core:enable-flow",enableWorkspace);
19347
19907
  RED.actions.add("core:disable-flow",disableWorkspace);
19348
-
19349
- RED.actions.add("core:hide-flow", function() {
19350
- var selection = workspace_tabs.selection();
19351
- if (selection.length === 0) {
19352
- 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
+ }
19353
19922
  }
19354
19923
  var hiddenTabs = [];
19355
19924
  selection.forEach(function(ws) {
@@ -19363,10 +19932,15 @@ RED.workspaces = (function() {
19363
19932
  workspace_tabs.clearSelection();
19364
19933
  })
19365
19934
 
19366
- RED.actions.add("core:hide-other-flows", function() {
19367
- var selection = workspace_tabs.selection();
19368
- if (selection.length === 0) {
19369
- 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
+ }
19370
19944
  }
19371
19945
  var selected = new Set(selection.map(function(ws) { return ws.id }))
19372
19946
 
@@ -19471,7 +20045,7 @@ RED.workspaces = (function() {
19471
20045
  }
19472
20046
  function setWorkspaceState(id,disabled) {
19473
20047
  var workspace = RED.nodes.workspace(id||activeWorkspace);
19474
- if (!workspace) {
20048
+ if (!workspace || workspace.locked) {
19475
20049
  return;
19476
20050
  }
19477
20051
  if (workspace.disabled !== disabled) {
@@ -19506,11 +20080,58 @@ RED.workspaces = (function() {
19506
20080
  }
19507
20081
  }
19508
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
+ }
19509
20126
 
19510
20127
  function removeWorkspace(ws) {
19511
20128
  if (!ws) {
19512
- deleteWorkspace(RED.nodes.workspace(activeWorkspace));
20129
+ ws = RED.nodes.workspace(activeWorkspace)
20130
+ if (ws && !ws.locked) {
20131
+ deleteWorkspace(RED.nodes.workspace(activeWorkspace));
20132
+ }
19513
20133
  } else {
20134
+ if (ws.locked) { return }
19514
20135
  if (workspace_tabs.contains(ws.id)) {
19515
20136
  workspace_tabs.removeTab(ws.id);
19516
20137
  }
@@ -19520,16 +20141,46 @@ RED.workspaces = (function() {
19520
20141
  }
19521
20142
  }
19522
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
+ }
19523
20175
  function setWorkspaceOrder(order) {
19524
- var newOrder = order.filter(function(id) {
19525
- return RED.nodes.workspace(id) !== undefined;
19526
- })
20176
+ var newOrder = order.filter(id => !!RED.nodes.workspace(id))
19527
20177
  var currentOrder = RED.nodes.getWorkspaceOrder();
19528
20178
  if (JSON.stringify(newOrder) !== JSON.stringify(currentOrder)) {
19529
20179
  RED.nodes.setWorkspaceOrder(newOrder);
19530
20180
  RED.events.emit("flows:reorder",newOrder);
19531
20181
  }
19532
20182
  workspace_tabs.order(order);
20183
+ return newOrder
19533
20184
  }
19534
20185
 
19535
20186
  function flashTab(tabId) {
@@ -19575,6 +20226,10 @@ RED.workspaces = (function() {
19575
20226
  active: function() {
19576
20227
  return activeWorkspace
19577
20228
  },
20229
+ isActiveLocked: function() {
20230
+ var ws = RED.nodes.workspace(activeWorkspace) || RED.nodes.subflow(activeWorkspace)
20231
+ return ws && ws.locked
20232
+ },
19578
20233
  selection: function() {
19579
20234
  return workspace_tabs.selection();
19580
20235
  },
@@ -19631,7 +20286,9 @@ RED.workspaces = (function() {
19631
20286
  workspace_tabs.resize();
19632
20287
  },
19633
20288
  enable: enableWorkspace,
19634
- disable: disableWorkspace
20289
+ disable: disableWorkspace,
20290
+ lock: lockWorkspace,
20291
+ unlock: unlockWorkspace
19635
20292
  }
19636
20293
  })();
19637
20294
  ;/**
@@ -19741,6 +20398,7 @@ RED.view = (function() {
19741
20398
  var spliceTimer;
19742
20399
  var groupHoverTimer;
19743
20400
 
20401
+ var activeFlowLocked = false;
19744
20402
  var activeSubflow = null;
19745
20403
  var activeNodes = [];
19746
20404
  var activeLinks = [];
@@ -19898,6 +20556,7 @@ RED.view = (function() {
19898
20556
  evt.preventDefault()
19899
20557
  evt.stopPropagation()
19900
20558
  RED.contextMenu.show({
20559
+ type: 'workspace',
19901
20560
  x:evt.clientX-5,
19902
20561
  y:evt.clientY-5
19903
20562
  })
@@ -20097,8 +20756,19 @@ RED.view = (function() {
20097
20756
 
20098
20757
  activeSubflow = RED.nodes.subflow(event.workspace);
20099
20758
 
20100
- RED.menu.setDisabled("menu-item-workspace-edit", activeSubflow || event.workspace === 0);
20101
- 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);
20102
20772
 
20103
20773
  if (workspaceScrollPositions[event.workspace]) {
20104
20774
  chart.scrollLeft(workspaceScrollPositions[event.workspace].left);
@@ -20125,6 +20795,15 @@ RED.view = (function() {
20125
20795
  redraw();
20126
20796
  });
20127
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
+
20128
20807
  RED.statusBar.add({
20129
20808
  id: "view-zoom-controls",
20130
20809
  align: "right",
@@ -20182,6 +20861,9 @@ RED.view = (function() {
20182
20861
  chart.droppable({
20183
20862
  accept:".red-ui-palette-node",
20184
20863
  drop: function( event, ui ) {
20864
+ if (activeFlowLocked) {
20865
+ return
20866
+ }
20185
20867
  d3.event = event;
20186
20868
  var selected_tool = $(ui.draggable[0]).attr("data-palette-type");
20187
20869
  var result = createNode(selected_tool);
@@ -20189,9 +20871,7 @@ RED.view = (function() {
20189
20871
  return;
20190
20872
  }
20191
20873
  var historyEvent = result.historyEvent;
20192
- var nn = result.node;
20193
-
20194
- RED.nodes.add(nn);
20874
+ var nn = RED.nodes.add(result.node);
20195
20875
 
20196
20876
  var showLabel = RED.utils.getMessageProperty(RED.settings.get('editor'),"view.view-node-show-label");
20197
20877
  if (showLabel !== undefined && (nn._def.hasOwnProperty("showLabel")?nn._def.showLabel:true) && !nn._def.defaults.hasOwnProperty("l")) {
@@ -20273,11 +20953,28 @@ RED.view = (function() {
20273
20953
 
20274
20954
  var group = $(ui.helper).data("group");
20275
20955
  if (group) {
20956
+ var oldX = group.x;
20957
+ var oldY = group.y;
20276
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
+ }
20277
20971
  historyEvent = {
20278
20972
  t: 'multi',
20279
20973
  events: [historyEvent],
20280
20974
 
20975
+ };
20976
+ if (moveEvent) {
20977
+ historyEvent.events.push(moveEvent)
20281
20978
  }
20282
20979
  historyEvent.events.push({
20283
20980
  t: "addToGroup",
@@ -20318,6 +21015,9 @@ RED.view = (function() {
20318
21015
  RED.actions.add("core:copy-selection-to-internal-clipboard",copySelection);
20319
21016
  RED.actions.add("core:cut-selection-to-internal-clipboard",function(){copySelection(true);deleteSelection();});
20320
21017
  RED.actions.add("core:paste-from-internal-clipboard",function(){
21018
+ if (RED.workspaces.isActiveLocked()) {
21019
+ return
21020
+ }
20321
21021
  importNodes(clipboard,{generateIds: clipboardSource === 'copy', generateDefaultNames: clipboardSource === 'copy'});
20322
21022
  });
20323
21023
 
@@ -20326,22 +21026,27 @@ RED.view = (function() {
20326
21026
  RED.events.on("view:selection-changed", function(selection) {
20327
21027
  var hasSelection = (selection.nodes && selection.nodes.length > 0);
20328
21028
  var hasMultipleSelection = hasSelection && selection.nodes.length > 1;
20329
- RED.menu.setDisabled("menu-item-edit-cut",!hasSelection);
20330
- RED.menu.setDisabled("menu-item-edit-copy",!hasSelection);
20331
- RED.menu.setDisabled("menu-item-edit-select-connected",!hasSelection);
20332
- RED.menu.setDisabled("menu-item-view-tools-move-to-back",!hasSelection);
20333
- RED.menu.setDisabled("menu-item-view-tools-move-to-front",!hasSelection);
20334
- RED.menu.setDisabled("menu-item-view-tools-move-backwards",!hasSelection);
20335
- RED.menu.setDisabled("menu-item-view-tools-move-forwards",!hasSelection);
20336
-
20337
- RED.menu.setDisabled("menu-item-view-tools-align-left",!hasMultipleSelection);
20338
- RED.menu.setDisabled("menu-item-view-tools-align-center",!hasMultipleSelection);
20339
- RED.menu.setDisabled("menu-item-view-tools-align-right",!hasMultipleSelection);
20340
- RED.menu.setDisabled("menu-item-view-tools-align-top",!hasMultipleSelection);
20341
- RED.menu.setDisabled("menu-item-view-tools-align-middle",!hasMultipleSelection);
20342
- RED.menu.setDisabled("menu-item-view-tools-align-bottom",!hasMultipleSelection);
20343
- RED.menu.setDisabled("menu-item-view-tools-distribute-horizontally",!hasMultipleSelection);
20344
- 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);
20345
21050
  })
20346
21051
 
20347
21052
  RED.actions.add("core:delete-selection",deleteSelection);
@@ -20731,7 +21436,7 @@ RED.view = (function() {
20731
21436
  .attr("class", "nr-ui-view-lasso");
20732
21437
  d3.event.preventDefault();
20733
21438
  }
20734
- } else if (d3.event.altKey) {
21439
+ } else if (d3.event.altKey && !activeFlowLocked) {
20735
21440
  //Alt [+shift] held - Begin slicing
20736
21441
  clearSelection();
20737
21442
  mouse_mode = (d3.event.shiftKey) ? RED.state.SLICING_JUNCTION : RED.state.SLICING;
@@ -20745,6 +21450,9 @@ RED.view = (function() {
20745
21450
  }
20746
21451
 
20747
21452
  function showQuickAddDialog(options) {
21453
+ if (activeFlowLocked) {
21454
+ return
21455
+ }
20748
21456
  options = options || {};
20749
21457
  var point = options.position || lastClickPosition;
20750
21458
  var spliceLink = options.splice;
@@ -20927,6 +21635,11 @@ RED.view = (function() {
20927
21635
  if (showLabel !== undefined && (nn._def.hasOwnProperty("showLabel")?nn._def.showLabel:true) && !nn._def.defaults.hasOwnProperty("l")) {
20928
21636
  nn.l = showLabel;
20929
21637
  }
21638
+ if (nn.type === 'junction') {
21639
+ nn = RED.nodes.addJunction(nn);
21640
+ } else {
21641
+ nn = RED.nodes.add(nn);
21642
+ }
20930
21643
  if (quickAddLink) {
20931
21644
  var drag_line = quickAddLink;
20932
21645
  var src = null,dst,src_port;
@@ -21029,27 +21742,39 @@ RED.view = (function() {
21029
21742
  }
21030
21743
  }
21031
21744
  }
21032
- if (nn.type === 'junction') {
21033
- RED.nodes.addJunction(nn);
21034
- } else {
21035
- RED.nodes.add(nn);
21036
- }
21745
+
21037
21746
  RED.editor.validateNode(nn);
21038
21747
 
21039
21748
  if (targetGroup) {
21749
+ var oldX = targetGroup.x;
21750
+ var oldY = targetGroup.y;
21040
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
+ }
21041
21764
  if (historyEvent.t !== "multi") {
21042
21765
  historyEvent = {
21043
21766
  t:'multi',
21044
21767
  events: [historyEvent]
21045
- }
21768
+ };
21046
21769
  }
21047
21770
  historyEvent.events.push({
21048
21771
  t: "addToGroup",
21049
21772
  group: targetGroup,
21050
21773
  nodes: nn
21051
- })
21052
-
21774
+ });
21775
+ if (moveEvent) {
21776
+ historyEvent.events.push(moveEvent);
21777
+ }
21053
21778
  }
21054
21779
 
21055
21780
  if (spliceLink) {
@@ -21291,16 +22016,18 @@ RED.view = (function() {
21291
22016
  }
21292
22017
  var d = (mouse_offset[0]-mousePos[0])*(mouse_offset[0]-mousePos[0]) + (mouse_offset[1]-mousePos[1])*(mouse_offset[1]-mousePos[1]);
21293
22018
  if ((d > 3 && !dblClickPrimed) || (dblClickPrimed && d > 10)) {
21294
- mouse_mode = RED.state.MOVING_ACTIVE;
21295
22019
  clickElapsed = 0;
21296
- spliceActive = false;
21297
- if (movingSet.length() === 1) {
21298
- node = movingSet.get(0);
21299
- spliceActive = node.n.hasOwnProperty("_def") &&
21300
- ((node.n.hasOwnProperty("inputs") && node.n.inputs > 0) || (!node.n.hasOwnProperty("inputs") && node.n._def.inputs > 0)) &&
21301
- ((node.n.hasOwnProperty("outputs") && node.n.outputs > 0) || (!node.n.hasOwnProperty("outputs") && node.n._def.outputs > 0)) &&
21302
- RED.nodes.filterLinks({ source: node.n }).length === 0 &&
21303
- 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
+ }
21304
22031
  }
21305
22032
  }
21306
22033
  } else if (mouse_mode == RED.state.MOVING_ACTIVE || mouse_mode == RED.state.IMPORT_DRAGGING || mouse_mode == RED.state.DETACHED_DRAGGING) {
@@ -21385,6 +22112,7 @@ RED.view = (function() {
21385
22112
 
21386
22113
  // Check link splice or group-add
21387
22114
  if (movingSet.length() === 1 && movingSet.get(0).n.type !== "group") {
22115
+ //}{//NIS
21388
22116
  node = movingSet.get(0);
21389
22117
  if (spliceActive) {
21390
22118
  if (!spliceTimer) {
@@ -21744,11 +22472,25 @@ RED.view = (function() {
21744
22472
  if (mouse_mode == RED.state.MOVING_ACTIVE) {
21745
22473
  if (movingSet.length() > 0) {
21746
22474
  var addedToGroup = null;
22475
+ var moveEvent = null;
21747
22476
  if (activeHoverGroup) {
22477
+ var oldX = activeHoverGroup.x;
22478
+ var oldY = activeHoverGroup.y;
21748
22479
  for (var j=0;j<movingSet.length();j++) {
21749
22480
  var n = movingSet.get(j);
21750
22481
  RED.group.addToGroup(activeHoverGroup,n.n);
21751
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
+ }
21752
22494
  addedToGroup = activeHoverGroup;
21753
22495
 
21754
22496
  activeHoverGroup.hovered = false;
@@ -21794,6 +22536,12 @@ RED.view = (function() {
21794
22536
  historyEvent.addToGroup = addedToGroup;
21795
22537
  }
21796
22538
  RED.nodes.dirty(true);
22539
+ if (moveEvent) {
22540
+ historyEvent = {
22541
+ t: "multi",
22542
+ events: [moveEvent, historyEvent]
22543
+ };
22544
+ }
21797
22545
  RED.history.push(historyEvent);
21798
22546
  }
21799
22547
  }
@@ -22145,6 +22893,7 @@ RED.view = (function() {
22145
22893
  }
22146
22894
 
22147
22895
  function editSelection() {
22896
+ if (RED.workspaces.isActiveLocked()) { return }
22148
22897
  if (movingSet.length() > 0) {
22149
22898
  var node = movingSet.get(0).n;
22150
22899
  if (node.type === "subflow") {
@@ -22160,6 +22909,9 @@ RED.view = (function() {
22160
22909
  if (mouse_mode === RED.state.SELECTING_NODE) {
22161
22910
  return;
22162
22911
  }
22912
+ if (activeFlowLocked) {
22913
+ return
22914
+ }
22163
22915
  if (portLabelHover) {
22164
22916
  portLabelHover.remove();
22165
22917
  portLabelHover = null;
@@ -22475,6 +23227,7 @@ RED.view = (function() {
22475
23227
 
22476
23228
 
22477
23229
  function detachSelectedNodes() {
23230
+ if (RED.workspaces.isActiveLocked()) { return }
22478
23231
  var selection = RED.view.selection();
22479
23232
  if (selection.nodes) {
22480
23233
  const {newLinks, removedLinks} = RED.nodes.detachNodes(selection.nodes);
@@ -22616,7 +23369,7 @@ RED.view = (function() {
22616
23369
  mousedown_node = d;
22617
23370
  mousedown_port_type = portType;
22618
23371
  mousedown_port_index = portIndex || 0;
22619
- if (mouse_mode !== RED.state.QUICK_JOINING) {
23372
+ if (mouse_mode !== RED.state.QUICK_JOINING && !activeFlowLocked) {
22620
23373
  mouse_mode = RED.state.JOINING;
22621
23374
  document.body.style.cursor = "crosshair";
22622
23375
  if (evt.ctrlKey || evt.metaKey) {
@@ -23056,6 +23809,14 @@ RED.view = (function() {
23056
23809
  }
23057
23810
  if (dblClickPrimed && mousedown_node == d && clickElapsed > 0 && clickElapsed < dblClickInterval) {
23058
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()
23059
23820
  if (d.type != "subflow") {
23060
23821
  if (/^subflow:/.test(d.type) && (d3.event.ctrlKey || d3.event.metaKey)) {
23061
23822
  RED.workspaces.show(d.type.substring(8));
@@ -23150,11 +23911,25 @@ RED.view = (function() {
23150
23911
  updateActiveNodes();
23151
23912
  }
23152
23913
 
23914
+ var moveEvent = null;
23153
23915
  if (activeHoverGroup) {
23916
+ var oldX = activeHoverGroup.x;
23917
+ var oldY = activeHoverGroup.y;
23154
23918
  for (var j=0;j<movingSet.length();j++) {
23155
23919
  var n = movingSet.get(j);
23156
23920
  RED.group.addToGroup(activeHoverGroup,n.n);
23157
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
+ }
23158
23933
  historyEvent.addedToGroup = activeHoverGroup;
23159
23934
 
23160
23935
  activeHoverGroup.hovered = false;
@@ -23163,7 +23938,6 @@ RED.view = (function() {
23163
23938
  activeGroup.selected = true;
23164
23939
  activeHoverGroup = null;
23165
23940
  }
23166
-
23167
23941
  if (mouse_mode == RED.state.DETACHED_DRAGGING) {
23168
23942
  var ns = [];
23169
23943
  for (var j=0;j<movingSet.length();j++) {
@@ -23174,7 +23948,15 @@ RED.view = (function() {
23174
23948
  n.n.moved = true;
23175
23949
  }
23176
23950
  }
23177
- 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);
23178
23960
  }
23179
23961
 
23180
23962
  updateSelection();
@@ -23379,7 +24161,6 @@ RED.view = (function() {
23379
24161
  }
23380
24162
  // selectedLinks.clear();
23381
24163
  if (d3.event.button != 2) {
23382
- mouse_mode = RED.state.MOVING;
23383
24164
  var mouse = d3.touches(this)[0]||d3.mouse(this);
23384
24165
  mouse[0] += d.x-d.w/2;
23385
24166
  mouse[1] += d.y-d.h/2;
@@ -23572,6 +24353,7 @@ RED.view = (function() {
23572
24353
  if (RED.view.DEBUG) {
23573
24354
  console.warn("groupMouseUp", { mouse_mode, event: d3.event });
23574
24355
  }
24356
+ if (RED.workspaces.isActiveLocked()) { return }
23575
24357
  if (dblClickPrimed && mousedown_group == g && clickElapsed > 0 && clickElapsed < dblClickInterval) {
23576
24358
  mouse_mode = RED.state.DEFAULT;
23577
24359
  RED.editor.editGroup(g);
@@ -23742,7 +24524,7 @@ RED.view = (function() {
23742
24524
  function isButtonEnabled(d) {
23743
24525
  var buttonEnabled = true;
23744
24526
  var ws = RED.nodes.workspace(RED.workspaces.active());
23745
- if (ws && !ws.disabled && !d.d) {
24527
+ if (ws && !ws.disabled && !d.d && !ws.locked) {
23746
24528
  if (d._def.button.hasOwnProperty('enabled')) {
23747
24529
  if (typeof d._def.button.enabled === "function") {
23748
24530
  buttonEnabled = d._def.button.enabled.call(d);
@@ -23765,7 +24547,7 @@ RED.view = (function() {
23765
24547
  }
23766
24548
  var activeWorkspace = RED.workspaces.active();
23767
24549
  var ws = RED.nodes.workspace(activeWorkspace);
23768
- if (ws && !ws.disabled && !d.d) {
24550
+ if (ws && !ws.disabled && !d.d && !ws.locked) {
23769
24551
  if (d._def.button.toggle) {
23770
24552
  d[d._def.button.toggle] = !d[d._def.button.toggle];
23771
24553
  d.dirty = true;
@@ -23780,7 +24562,7 @@ RED.view = (function() {
23780
24562
  if (d.dirty) {
23781
24563
  redraw();
23782
24564
  }
23783
- } else {
24565
+ } else if (!ws || !ws.locked){
23784
24566
  if (activeSubflow) {
23785
24567
  RED.notify(RED._("notification.warning", {message:RED._("notification.warnings.nodeActionDisabledSubflow")}),"warning");
23786
24568
  } else {
@@ -23795,14 +24577,15 @@ RED.view = (function() {
23795
24577
  function showTouchMenu(obj,pos) {
23796
24578
  var mdn = mousedown_node;
23797
24579
  var options = [];
23798
- options.push({name:"delete",disabled:(movingSet.length()===0 && selectedLinks.length() === 0),onselect:function() {deleteSelection();}});
23799
- options.push({name:"cut",disabled:(movingSet.length()===0),onselect:function() {copySelection(true);deleteSelection();}});
23800
- options.push({name:"copy",disabled:(movingSet.length()===0),onselect:function() {copySelection();}});
23801
- options.push({name:"paste",disabled:(clipboard.length===0),onselect:function() {importNodes(clipboard, {generateIds: true, touchImport: true});}});
23802
- 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);}});
23803
24586
  options.push({name:"select",onselect:function() {selectAll();}});
23804
24587
  options.push({name:"undo",disabled:(RED.history.depth() === 0),onselect:function() {RED.history.pop();}});
23805
- options.push({name:"add",onselect:function() {
24588
+ options.push({name:"add",disabled:isActiveLocked, onselect:function() {
23806
24589
  chartPos = chart.offset();
23807
24590
  showQuickAddDialog({
23808
24591
  position:[pos[0]-chartPos.left+chart.scrollLeft(),pos[1]-chartPos.top+chart.scrollTop()],
@@ -25293,7 +26076,24 @@ RED.view = (function() {
25293
26076
  if (activeSubflow) {
25294
26077
  activeSubflowChanged = activeSubflow.changed;
25295
26078
  }
25296
- 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});
25297
26097
  if (result) {
25298
26098
  var new_nodes = result.nodes;
25299
26099
  var new_links = result.links;
@@ -25425,6 +26225,50 @@ RED.view = (function() {
25425
26225
  }
25426
26226
  }
25427
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
+
25428
26272
  RED.history.push(historyEvent);
25429
26273
 
25430
26274
  updateActiveNodes();
@@ -25500,6 +26344,9 @@ RED.view = (function() {
25500
26344
  if (mouse_mode === RED.state.SELECTING_NODE) {
25501
26345
  return;
25502
26346
  }
26347
+ if (activeFlowLocked) {
26348
+ return
26349
+ }
25503
26350
  var workspaceSelection = RED.workspaces.selection();
25504
26351
  var changed = false;
25505
26352
  if (workspaceSelection.length > 0) {
@@ -25827,7 +26674,9 @@ RED.view = (function() {
25827
26674
  if (node.z && (node.type === "group" || node._def.category !== 'config')) {
25828
26675
  node.dirty = true;
25829
26676
  RED.workspaces.show(node.z);
25830
-
26677
+ if (node.type === "group" && !node.w && !node.h) {
26678
+ _redraw();
26679
+ }
25831
26680
  var screenSize = [chart[0].clientWidth/scaleFactor,chart[0].clientHeight/scaleFactor];
25832
26681
  var scrollPos = [chart.scrollLeft()/scaleFactor,chart.scrollTop()/scaleFactor];
25833
26682
  var cx = node.x;
@@ -26290,7 +27139,7 @@ RED.view = (function() {
26290
27139
  **/
26291
27140
 
26292
27141
  RED.view.tools = (function() {
26293
-
27142
+ 'use strict';
26294
27143
  function selectConnected(type) {
26295
27144
  var selection = RED.view.selection();
26296
27145
  var visited = new Set();
@@ -26314,6 +27163,9 @@ RED.view.tools = (function() {
26314
27163
  }
26315
27164
 
26316
27165
  function alignToGrid() {
27166
+ if (RED.workspaces.isActiveLocked()) {
27167
+ return
27168
+ }
26317
27169
  var selection = RED.view.selection();
26318
27170
  if (selection.nodes) {
26319
27171
  var changedNodes = [];
@@ -26362,6 +27214,9 @@ RED.view.tools = (function() {
26362
27214
  }
26363
27215
 
26364
27216
  function moveSelection(dx,dy) {
27217
+ if (RED.workspaces.isActiveLocked()) {
27218
+ return
27219
+ }
26365
27220
  if (moving_set === null) {
26366
27221
  moving_set = [];
26367
27222
  var selection = RED.view.selection();
@@ -26428,6 +27283,9 @@ RED.view.tools = (function() {
26428
27283
  }
26429
27284
 
26430
27285
  function setSelectedNodeLabelState(labelShown) {
27286
+ if (RED.workspaces.isActiveLocked()) {
27287
+ return
27288
+ }
26431
27289
  var selection = RED.view.selection();
26432
27290
  var historyEvents = [];
26433
27291
  var nodes = [];
@@ -26714,6 +27572,9 @@ RED.view.tools = (function() {
26714
27572
  }
26715
27573
 
26716
27574
  function alignSelectionToEdge(direction) {
27575
+ // if (RED.workspaces.isActiveLocked()) {
27576
+ // return
27577
+ // }
26717
27578
  var selection = RED.view.selection();
26718
27579
 
26719
27580
  if (selection.nodes && selection.nodes.length > 1) {
@@ -26814,8 +27675,10 @@ RED.view.tools = (function() {
26814
27675
  }
26815
27676
  }
26816
27677
 
26817
-
26818
27678
  function distributeSelection(direction) {
27679
+ if (RED.workspaces.isActiveLocked()) {
27680
+ return
27681
+ }
26819
27682
  var selection = RED.view.selection();
26820
27683
 
26821
27684
  if (selection.nodes && selection.nodes.length > 2) {
@@ -26974,6 +27837,9 @@ RED.view.tools = (function() {
26974
27837
  }
26975
27838
 
26976
27839
  function reorderSelection(dir) {
27840
+ if (RED.workspaces.isActiveLocked()) {
27841
+ return
27842
+ }
26977
27843
  var selection = RED.view.selection();
26978
27844
  if (selection.nodes) {
26979
27845
  var nodesToMove = [];
@@ -27009,8 +27875,10 @@ RED.view.tools = (function() {
27009
27875
  }
27010
27876
  }
27011
27877
 
27012
-
27013
27878
  function wireSeriesOfNodes() {
27879
+ if (RED.workspaces.isActiveLocked()) {
27880
+ return
27881
+ }
27014
27882
  var selection = RED.view.selection();
27015
27883
  if (selection.nodes) {
27016
27884
  if (selection.nodes.length > 1) {
@@ -27051,6 +27919,9 @@ RED.view.tools = (function() {
27051
27919
  }
27052
27920
 
27053
27921
  function wireNodeToMultiple() {
27922
+ if (RED.workspaces.isActiveLocked()) {
27923
+ return
27924
+ }
27054
27925
  var selection = RED.view.selection();
27055
27926
  if (selection.nodes) {
27056
27927
  if (selection.nodes.length > 1) {
@@ -27098,6 +27969,9 @@ RED.view.tools = (function() {
27098
27969
  * @param {Object || Object[]} wires The wire(s) to split and replace with link-out, link-in nodes.
27099
27970
  */
27100
27971
  function splitWiresWithLinkNodes(wires) {
27972
+ if (RED.workspaces.isActiveLocked()) {
27973
+ return
27974
+ }
27101
27975
  let wiresToSplit = wires || (RED.view.selection().links && RED.view.selection().links.filter(e => !e.link));
27102
27976
  if (!wiresToSplit) {
27103
27977
  return
@@ -27152,7 +28026,6 @@ RED.view.tools = (function() {
27152
28026
  if(!nnLinkOut) {
27153
28027
  const nLinkOut = RED.view.createNode("link out"); //create link node
27154
28028
  nnLinkOut = nLinkOut.node;
27155
- nodeSrcMap[linkOutMapId] = nnLinkOut;
27156
28029
  let yOffset = 0;
27157
28030
  if(nSrc.outputs > 1) {
27158
28031
 
@@ -27167,7 +28040,8 @@ RED.view.tools = (function() {
27167
28040
  updateNewNodePosXY(nSrc, nnLinkOut, false, RED.view.snapGrid, yOffset);
27168
28041
  }
27169
28042
  //add created node
27170
- RED.nodes.add(nnLinkOut);
28043
+ nnLinkOut = RED.nodes.add(nnLinkOut);
28044
+ nodeSrcMap[linkOutMapId] = nnLinkOut;
27171
28045
  RED.editor.validateNode(nnLinkOut);
27172
28046
  history.events.push(nLinkOut.historyEvent);
27173
28047
  //connect node to link node
@@ -27188,10 +28062,10 @@ RED.view.tools = (function() {
27188
28062
  if(!nnLinkIn) {
27189
28063
  const nLinkIn = RED.view.createNode("link in"); //create link node
27190
28064
  nnLinkIn = nLinkIn.node;
27191
- nodeTrgMap[nTrg.id] = nnLinkIn;
27192
28065
  updateNewNodePosXY(nTrg, nnLinkIn, true, RED.view.snapGrid, 0);
27193
28066
  //add created node
27194
- RED.nodes.add(nnLinkIn);
28067
+ nnLinkIn = RED.nodes.add(nnLinkIn);
28068
+ nodeTrgMap[nTrg.id] = nnLinkIn;
27195
28069
  RED.editor.validateNode(nnLinkIn);
27196
28070
  history.events.push(nLinkIn.historyEvent);
27197
28071
  //connect node to link node
@@ -27266,6 +28140,9 @@ RED.view.tools = (function() {
27266
28140
  * @param {{ renameBlank: boolean, renameClash: boolean, generateHistory: boolean }} options Possible options are `renameBlank`, `renameClash` and `generateHistory`
27267
28141
  */
27268
28142
  function generateNodeNames(node, options) {
28143
+ if (RED.workspaces.isActiveLocked()) {
28144
+ return
28145
+ }
27269
28146
  options = Object.assign({
27270
28147
  renameBlank: true,
27271
28148
  renameClash: true,
@@ -27336,6 +28213,9 @@ RED.view.tools = (function() {
27336
28213
  }
27337
28214
 
27338
28215
  function addJunctionsToWires(wires) {
28216
+ if (RED.workspaces.isActiveLocked()) {
28217
+ return
28218
+ }
27339
28219
  let wiresToSplit = wires || (RED.view.selection().links && RED.view.selection().links.filter(e => !e.link));
27340
28220
  if (!wiresToSplit) {
27341
28221
  return
@@ -27406,7 +28286,7 @@ RED.view.tools = (function() {
27406
28286
 
27407
28287
  var nodeGroups = new Set()
27408
28288
 
27409
- RED.nodes.addJunction(junction)
28289
+ junction = RED.nodes.addJunction(junction)
27410
28290
  addedJunctions.push(junction)
27411
28291
  let newLink
27412
28292
  if (gid === links[0].source.id+":"+links[0].sourcePort) {
@@ -27467,6 +28347,30 @@ RED.view.tools = (function() {
27467
28347
  RED.view.redraw(true);
27468
28348
  }
27469
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
+
27470
28374
  return {
27471
28375
  init: function() {
27472
28376
  RED.actions.add("core:show-selected-node-labels", function() { setSelectedNodeLabelState(true); })
@@ -27533,6 +28437,9 @@ RED.view.tools = (function() {
27533
28437
 
27534
28438
  RED.actions.add("core:generate-node-names", generateNodeNames )
27535
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
+
27536
28443
  // RED.actions.add("core:add-node", function() { addNode() })
27537
28444
  },
27538
28445
  /**
@@ -28014,9 +28921,19 @@ RED.palette = (function() {
28014
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)
28015
28922
  }
28016
28923
 
28017
- 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
+ }
28018
28931
 
28019
- $('<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
+ })
28020
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)
28021
28938
 
28022
28939
  $('<p>',{style:"font-size: 0.8em"}).text(metaData).appendTo(popOverContent);
@@ -28121,6 +29038,7 @@ RED.palette = (function() {
28121
29038
  var hoverGroup;
28122
29039
  var paletteWidth;
28123
29040
  var paletteTop;
29041
+ var dropEnabled;
28124
29042
  $(d).draggable({
28125
29043
  helper: 'clone',
28126
29044
  appendTo: '#red-ui-editor',
@@ -28128,6 +29046,7 @@ RED.palette = (function() {
28128
29046
  revertDuration: 200,
28129
29047
  containment:'#red-ui-main-container',
28130
29048
  start: function() {
29049
+ dropEnabled = !(RED.nodes.workspace(RED.workspaces.active())?.locked);
28131
29050
  paletteWidth = $("#red-ui-palette").width();
28132
29051
  paletteTop = $("#red-ui-palette").parent().position().top + $("#red-ui-palette-container").position().top;
28133
29052
  hoverGroup = null;
@@ -28138,96 +29057,100 @@ RED.palette = (function() {
28138
29057
  RED.view.focus();
28139
29058
  },
28140
29059
  stop: function() {
28141
- d3.select('.red-ui-flow-link-splice').classed('red-ui-flow-link-splice',false);
28142
- if (hoverGroup) {
28143
- document.getElementById("group_select_"+hoverGroup.id).classList.remove("red-ui-flow-group-hovered");
28144
- }
28145
- if (activeGroup) {
28146
- 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; }
28147
29070
  }
28148
- if (spliceTimer) { clearTimeout(spliceTimer); spliceTimer = null; }
28149
- if (groupTimer) { clearTimeout(groupTimer); groupTimer = null; }
28150
29071
  },
28151
29072
  drag: function(e,ui) {
28152
29073
  var paletteNode = getPaletteNode(nt);
28153
29074
  ui.originalPosition.left = paletteNode.offset().left;
28154
- mouseX = ui.position.left - paletteWidth + (ui.helper.width()/2) + chart.scrollLeft();
28155
- mouseY = ui.position.top - paletteTop + (ui.helper.height()/2) + chart.scrollTop() + 10;
28156
- if (!groupTimer) {
28157
- groupTimer = setTimeout(function() {
28158
- var mx = mouseX / RED.view.scale();
28159
- var my = mouseY / RED.view.scale();
28160
- var group = RED.view.getGroupAtPoint(mx,my);
28161
- if (group !== hoverGroup) {
28162
- if (hoverGroup) {
28163
- document.getElementById("group_select_"+hoverGroup.id).classList.remove("red-ui-flow-group-hovered");
28164
- }
28165
- if (group) {
28166
- document.getElementById("group_select_"+group.id).classList.add("red-ui-flow-group-hovered");
28167
- }
28168
- hoverGroup = group;
28169
- if (hoverGroup) {
28170
- $(ui.helper).data('group',hoverGroup);
28171
- } else {
28172
- $(ui.helper).removeData('group');
28173
- }
28174
- }
28175
- groupTimer = null;
28176
-
28177
- },200)
28178
- }
28179
- if (def.inputs > 0 && def.outputs > 0) {
28180
- if (!spliceTimer) {
28181
- spliceTimer = setTimeout(function() {
28182
- var nodes = [];
28183
- var bestDistance = Infinity;
28184
- var bestLink = null;
28185
- if (chartSVG.getIntersectionList) {
28186
- var svgRect = chartSVG.createSVGRect();
28187
- svgRect.x = mouseX;
28188
- svgRect.y = mouseY;
28189
- svgRect.width = 1;
28190
- svgRect.height = 1;
28191
- nodes = chartSVG.getIntersectionList(svgRect,chartSVG);
28192
- } else {
28193
- // Firefox doesn't do getIntersectionList and that
28194
- // makes us sad
28195
- nodes = RED.view.getLinksAtPoint(mouseX,mouseY);
28196
- }
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() {
28197
29080
  var mx = mouseX / RED.view.scale();
28198
29081
  var my = mouseY / RED.view.scale();
28199
- for (var i=0;i<nodes.length;i++) {
28200
- var node = d3.select(nodes[i]);
28201
- if (node.classed('red-ui-flow-link-background') && !node.classed('red-ui-flow-link-link')) {
28202
- var length = nodes[i].getTotalLength();
28203
- for (var j=0;j<length;j+=10) {
28204
- var p = nodes[i].getPointAtLength(j);
28205
- var d2 = ((p.x-mx)*(p.x-mx))+((p.y-my)*(p.y-my));
28206
- if (d2 < 200 && d2 < bestDistance) {
28207
- bestDistance = d2;
28208
- 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
+ }
28209
29132
  }
28210
29133
  }
28211
29134
  }
28212
- }
28213
- if (activeSpliceLink && activeSpliceLink !== bestLink) {
28214
- d3.select(activeSpliceLink.parentNode).classed('red-ui-flow-link-splice',false);
28215
- }
28216
- if (bestLink) {
28217
- d3.select(bestLink.parentNode).classed('red-ui-flow-link-splice',true)
28218
- } else {
28219
- d3.select('.red-ui-flow-link-splice').classed('red-ui-flow-link-splice',false);
28220
- }
28221
- if (activeSpliceLink !== bestLink) {
29135
+ if (activeSpliceLink && activeSpliceLink !== bestLink) {
29136
+ d3.select(activeSpliceLink.parentNode).classed('red-ui-flow-link-splice',false);
29137
+ }
28222
29138
  if (bestLink) {
28223
- $(ui.helper).data('splice',d3.select(bestLink).data()[0]);
29139
+ d3.select(bestLink.parentNode).classed('red-ui-flow-link-splice',true)
28224
29140
  } else {
28225
- $(ui.helper).removeData('splice');
29141
+ d3.select('.red-ui-flow-link-splice').classed('red-ui-flow-link-splice',false);
28226
29142
  }
28227
- }
28228
- activeSpliceLink = bestLink;
28229
- spliceTimer = null;
28230
- },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
+ }
28231
29154
  }
28232
29155
  }
28233
29156
  }
@@ -28261,6 +29184,7 @@ RED.palette = (function() {
28261
29184
  categoryNode.find(".red-ui-palette-content").slideToggle();
28262
29185
  categoryNode.find("i").toggleClass("expanded");
28263
29186
  }
29187
+ categoryNode.hide();
28264
29188
  }
28265
29189
  }
28266
29190
 
@@ -28339,6 +29263,7 @@ RED.palette = (function() {
28339
29263
  currentCategoryNode.find(".red-ui-palette-content").slideToggle();
28340
29264
  currentCategoryNode.find("i").toggleClass("expanded");
28341
29265
  }
29266
+ currentCategoryNode.hide();
28342
29267
  }
28343
29268
  }
28344
29269
 
@@ -28555,6 +29480,7 @@ RED.sidebar.info = (function() {
28555
29480
  var propertiesPanelHeaderLabel;
28556
29481
  var propertiesPanelHeaderReveal;
28557
29482
  var propertiesPanelHeaderHelp;
29483
+ var propertiesPanelHeaderCopyLink;
28558
29484
 
28559
29485
  var selectedObject;
28560
29486
 
@@ -28597,10 +29523,20 @@ RED.sidebar.info = (function() {
28597
29523
 
28598
29524
  propertiesPanelHeaderIcon = $("<span>").appendTo(propertiesPanelHeader);
28599
29525
  propertiesPanelHeaderLabel = $("<span>").appendTo(propertiesPanelHeader);
28600
- 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({
28601
29528
  position: 'absolute',
28602
29529
  top: '12px',
28603
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'
28604
29540
  }).on("click", function(evt) {
28605
29541
  evt.preventDefault();
28606
29542
  evt.stopPropagation();
@@ -28610,8 +29546,7 @@ RED.sidebar.info = (function() {
28610
29546
  }).appendTo(propertiesPanelHeader);
28611
29547
  RED.popover.tooltip(propertiesPanelHeaderHelp,RED._("sidebar.help.showHelp"));
28612
29548
 
28613
-
28614
- 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({
28615
29550
  position: 'absolute',
28616
29551
  top: '12px',
28617
29552
  right: '8px'
@@ -28715,6 +29650,7 @@ RED.sidebar.info = (function() {
28715
29650
  propertiesPanelHeaderLabel.text("");
28716
29651
  propertiesPanelHeaderReveal.hide();
28717
29652
  propertiesPanelHeaderHelp.hide();
29653
+ propertiesPanelHeaderCopyLink.hide();
28718
29654
  return;
28719
29655
  } else if (Array.isArray(node)) {
28720
29656
  // Multiple things selected
@@ -28726,6 +29662,7 @@ RED.sidebar.info = (function() {
28726
29662
  propertiesPanelHeaderLabel.text("Selection");
28727
29663
  propertiesPanelHeaderReveal.hide();
28728
29664
  propertiesPanelHeaderHelp.hide();
29665
+ propertiesPanelHeaderCopyLink.hide();
28729
29666
  selectedObject = null;
28730
29667
 
28731
29668
  var types = {
@@ -28807,9 +29744,11 @@ RED.sidebar.info = (function() {
28807
29744
  if (node.type === "tab" || node.type === "subflow") {
28808
29745
  // If nothing is selected, but we're on a flow or subflow tab.
28809
29746
  propertiesPanelHeaderHelp.hide();
29747
+ propertiesPanelHeaderCopyLink.show();
28810
29748
 
28811
29749
  } else if (node.type === "group") {
28812
29750
  propertiesPanelHeaderHelp.hide();
29751
+ propertiesPanelHeaderCopyLink.show();
28813
29752
 
28814
29753
  propRow = $('<tr class="red-ui-help-info-row"><td>&nbsp;</td><td></td></tr>').appendTo(tableBody);
28815
29754
 
@@ -28834,8 +29773,10 @@ RED.sidebar.info = (function() {
28834
29773
  }
28835
29774
  } else if (node.type === 'junction') {
28836
29775
  propertiesPanelHeaderHelp.hide();
29776
+ propertiesPanelHeaderCopyLink.hide();
28837
29777
  } else {
28838
29778
  propertiesPanelHeaderHelp.show();
29779
+ propertiesPanelHeaderCopyLink.show();
28839
29780
 
28840
29781
  if (!subflowRegex) {
28841
29782
  propRow = $('<tr class="red-ui-help-info-row"><td>'+RED._("sidebar.info.type")+'</td><td></td></tr>').appendTo(tableBody);
@@ -28977,7 +29918,8 @@ RED.sidebar.info = (function() {
28977
29918
  el = el.next();
28978
29919
  }
28979
29920
  $(this).toggleClass('expanded',!isExpanded);
28980
- })
29921
+ });
29922
+ mermaid.init();
28981
29923
  }
28982
29924
 
28983
29925
  var tips = (function() {
@@ -29342,6 +30284,22 @@ RED.sidebar.info = (function() {
29342
30284
  } else {
29343
30285
  $('<div class="red-ui-info-outline-item-control-spacer">').appendTo(controls)
29344
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
+ }
29345
30303
  controls.find("button").on("dblclick", function(evt) {
29346
30304
  evt.preventDefault();
29347
30305
  evt.stopPropagation();
@@ -29485,6 +30443,8 @@ RED.sidebar.info = (function() {
29485
30443
  flowList.treeList.addChild(objects[ws.id])
29486
30444
  objects[ws.id].element.toggleClass("red-ui-info-outline-item-disabled", !!ws.disabled)
29487
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)
29488
30448
  updateSearch();
29489
30449
 
29490
30450
  }
@@ -29499,6 +30459,8 @@ RED.sidebar.info = (function() {
29499
30459
  existingObject.element.find(".red-ui-info-outline-item-label").text(label);
29500
30460
  existingObject.element.toggleClass("red-ui-info-outline-item-disabled", !!n.disabled)
29501
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)
29502
30464
  updateSearch();
29503
30465
  }
29504
30466
  function onFlowsReorder(order) {
@@ -29946,7 +30908,8 @@ RED.sidebar.help = (function() {
29946
30908
  RED.events.on('registry:node-type-removed', queueRefresh);
29947
30909
  RED.events.on('subflows:change', refreshSubflow);
29948
30910
 
29949
- 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)
29950
30913
 
29951
30914
  }
29952
30915
 
@@ -30143,6 +31106,19 @@ RED.sidebar.help = (function() {
30143
31106
  resizeStack();
30144
31107
  }
30145
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
+
30146
31122
  // TODO: DRY - projects.js
30147
31123
  function addTargetToExternalLinks(el) {
30148
31124
  $(el).find("a").each(function(el) {
@@ -30329,12 +31305,15 @@ RED.sidebar.config = (function() {
30329
31305
 
30330
31306
  var categories = {};
30331
31307
 
30332
- function getOrCreateCategory(name,parent,label) {
31308
+ function getOrCreateCategory(name,parent,label,isLocked) {
30333
31309
  name = name.replace(/\./i,"-");
30334
31310
  if (!categories[name]) {
30335
31311
  var container = $('<div class="red-ui-palette-category red-ui-sidebar-config-category" id="red-ui-sidebar-config-category-'+name+'"></div>').appendTo(parent);
30336
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
30337
31314
  if (label) {
31315
+ lockIcon = $('<span style="margin-right: 5px"><i class="fa fa-lock"/></span>').appendTo(header)
31316
+ lockIcon.toggle(!!isLocked)
30338
31317
  $('<span class="red-ui-palette-node-config-label"/>').text(label).appendTo(header);
30339
31318
  } else {
30340
31319
  $('<span class="red-ui-palette-node-config-label" data-i18n="sidebar.config.'+name+'">').appendTo(header);
@@ -30348,6 +31327,7 @@ RED.sidebar.config = (function() {
30348
31327
  var icon = header.find("i");
30349
31328
  var result = {
30350
31329
  label: label,
31330
+ lockIcon,
30351
31331
  list: category,
30352
31332
  size: function() {
30353
31333
  return result.list.find("li:not(.red-ui-palette-node-config-none)").length
@@ -30386,6 +31366,9 @@ RED.sidebar.config = (function() {
30386
31366
  });
30387
31367
  categories[name] = result;
30388
31368
  } else {
31369
+ if (isLocked !== undefined && categories[name].lockIcon) {
31370
+ categories[name].lockIcon.toggle(!!isLocked)
31371
+ }
30389
31372
  if (categories[name].label !== label) {
30390
31373
  categories[name].list.parent().find('.red-ui-palette-node-config-label').text(label);
30391
31374
  categories[name].label = label;
@@ -30502,7 +31485,7 @@ RED.sidebar.config = (function() {
30502
31485
 
30503
31486
  RED.nodes.eachWorkspace(function(ws) {
30504
31487
  validList[ws.id.replace(/\./g,"-")] = true;
30505
- getOrCreateCategory(ws.id,flowCategories,ws.label);
31488
+ getOrCreateCategory(ws.id,flowCategories,ws.label, ws.locked);
30506
31489
  })
30507
31490
  RED.nodes.eachSubflow(function(sf) {
30508
31491
  validList[sf.id.replace(/\./g,"-")] = true;
@@ -30560,6 +31543,15 @@ RED.sidebar.config = (function() {
30560
31543
  changes: {},
30561
31544
  dirty: RED.nodes.dirty()
30562
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
+ }
30563
31555
  selectedNodes.forEach(function(id) {
30564
31556
  var node = RED.nodes.node(id);
30565
31557
  try {
@@ -32502,6 +33494,7 @@ RED.editor = (function() {
32502
33494
  var valid = validateNodeProperty(node, defaults, property,value);
32503
33495
  if (((typeof valid) === "string") || !valid) {
32504
33496
  input.addClass("input-error");
33497
+ input.next(".red-ui-typedInput-container").addClass("input-error");
32505
33498
  if ((typeof valid) === "string") {
32506
33499
  var tooltip = input.data("tooltip");
32507
33500
  if (tooltip) {
@@ -32514,6 +33507,7 @@ RED.editor = (function() {
32514
33507
  }
32515
33508
  } else {
32516
33509
  input.removeClass("input-error");
33510
+ input.next(".red-ui-typedInput-container").removeClass("input-error");
32517
33511
  var tooltip = input.data("tooltip");
32518
33512
  if (tooltip) {
32519
33513
  input.data("tooltip", null);
@@ -34115,11 +35109,15 @@ RED.editor = (function() {
34115
35109
  workspace.disabled = disabled;
34116
35110
 
34117
35111
  $("#red-ui-tab-"+(workspace.id.replace(".","-"))).toggleClass('red-ui-workspace-disabled',!!workspace.disabled);
34118
- if (workspace.id === RED.workspaces.active()) {
34119
- $("#red-ui-workspace").toggleClass("red-ui-workspace-disabled",!!workspace.disabled);
34120
- }
34121
35112
  }
34122
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
+ }
34123
35121
  if (editState.changed) {
34124
35122
  var historyEvent = {
34125
35123
  t: "edit",
@@ -34160,6 +35158,7 @@ RED.editor = (function() {
34160
35158
  var trayBody = tray.find('.red-ui-tray-body');
34161
35159
  trayBody.parent().css('overflow','hidden');
34162
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)
34163
35162
 
34164
35163
  var nodeEditPanes = [
34165
35164
  'editor-tab-flow-properties',
@@ -34174,6 +35173,18 @@ RED.editor = (function() {
34174
35173
  disabledIcon: "fa-ban",
34175
35174
  invertState: true
34176
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
+
34177
35188
  prepareEditDialog(trayBody, nodeEditPanes, workspace, {}, "node-input", defaultTab, function(_activeEditPanes) {
34178
35189
  activeEditPanes = _activeEditPanes;
34179
35190
  trayBody.i18n();
@@ -35065,8 +36076,6 @@ RED.editor = (function() {
35065
36076
  node.info = info;
35066
36077
  }
35067
36078
  $("#red-ui-tab-"+(node.id.replace(".","-"))).toggleClass('red-ui-workspace-disabled',!!node.disabled);
35068
- $("#red-ui-workspace").toggleClass("red-ui-workspace-disabled",!!node.disabled);
35069
-
35070
36079
  }
35071
36080
  }
35072
36081
  });
@@ -35709,6 +36718,9 @@ RED.editor = (function() {
35709
36718
  selectedCodeEditor = RED.editor.codeEditor[defaultEditor];
35710
36719
  initialised = selectedCodeEditor.init();
35711
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();
35712
36724
  }
35713
36725
 
35714
36726
  function create(options) {
@@ -35728,6 +36740,7 @@ RED.editor = (function() {
35728
36740
  options = {};
35729
36741
  }
35730
36742
 
36743
+ var editor = null;
35731
36744
  if (this.editor.type === MONACO) {
35732
36745
  // compatibility (see above note)
35733
36746
  if (!options.element && !options.id) {
@@ -35738,10 +36751,14 @@ RED.editor = (function() {
35738
36751
  console.warn("createEditor() options.element or options.id is not valid", options);
35739
36752
  $("#dialog-form").append('<div id="' + options.id + '" style="display: none;" />');
35740
36753
  }
35741
- return this.editor.create(options);
36754
+ editor = this.editor.create(options);
35742
36755
  } else {
35743
- return this.editor.create(options);//fallback to ACE
36756
+ editor = this.editor.create(options);//fallback to ACE
35744
36757
  }
36758
+ if (options.mode === "ace/mode/markdown") {
36759
+ RED.editor.customEditTypes['_markdown'].postInit(editor, options);
36760
+ }
36761
+ return editor;
35745
36762
  }
35746
36763
 
35747
36764
  return {
@@ -35985,7 +37002,7 @@ RED.editor = (function() {
35985
37002
 
35986
37003
  var currentLocale = 'en-US';
35987
37004
  var DEFAULT_ENV_TYPE_LIST = ['str','num','bool','json','bin','env'];
35988
- 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'];
35989
37006
 
35990
37007
  /**
35991
37008
  * Create env var edit interface
@@ -36863,6 +37880,9 @@ RED.editor = (function() {
36863
37880
  var currentExpression = expressionEditor.getValue();
36864
37881
  var expr;
36865
37882
  var usesContext = false;
37883
+ var usesEnv = false;
37884
+ var usesMoment = false;
37885
+ var usesClone = false;
36866
37886
  var legacyMode = /(^|[^a-zA-Z0-9_'".])msg([^a-zA-Z0-9_'"]|$)/.test(currentExpression);
36867
37887
  $(".red-ui-editor-type-expression-legacy").toggle(legacyMode);
36868
37888
  try {
@@ -36875,6 +37895,18 @@ RED.editor = (function() {
36875
37895
  usesContext = true;
36876
37896
  return null;
36877
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
+ });
36878
37910
  } catch(err) {
36879
37911
  testResultEditor.setValue(RED._("expressionEditor.errors.invalid-expr",{message:err.message}),-1);
36880
37912
  return;
@@ -36892,6 +37924,18 @@ RED.editor = (function() {
36892
37924
  testResultEditor.setValue(RED._("expressionEditor.errors.context-unsupported"),-1);
36893
37925
  return;
36894
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
+ }
36895
37939
 
36896
37940
  var formattedResult;
36897
37941
  if (result !== undefined) {
@@ -37844,6 +38888,61 @@ RED.editor = (function() {
37844
38888
  * limitations under the License.
37845
38889
  **/
37846
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
+ }
37847
38946
 
37848
38947
  var toolbarTemplate = '<div style="margin-bottom: 5px">'+
37849
38948
  '<span class="button-group">'+
@@ -37944,6 +39043,7 @@ RED.editor = (function() {
37944
39043
  var currentScrollTop = $(".red-ui-editor-type-markdown-panel-preview").scrollTop();
37945
39044
  $(".red-ui-editor-type-markdown-panel-preview").html(RED.utils.renderMarkdown(expressionEditor.getValue()));
37946
39045
  $(".red-ui-editor-type-markdown-panel-preview").scrollTop(currentScrollTop);
39046
+ mermaid.init();
37947
39047
  },200);
37948
39048
  })
37949
39049
  if (options.header) {
@@ -37952,6 +39052,7 @@ RED.editor = (function() {
37952
39052
 
37953
39053
  if (value) {
37954
39054
  $(".red-ui-editor-type-markdown-panel-preview").html(RED.utils.renderMarkdown(expressionEditor.getValue()));
39055
+ mermaid.init();
37955
39056
  }
37956
39057
  panels = RED.panels.create({
37957
39058
  id:"red-ui-editor-type-markdown-panels",
@@ -37978,10 +39079,14 @@ RED.editor = (function() {
37978
39079
  });
37979
39080
  RED.popover.tooltip($("#node-btn-markdown-preview"), RED._("markdownEditor.toggle-preview"));
37980
39081
 
37981
- if (options.cursor && !expressionEditor._initState) {
37982
- expressionEditor.gotoLine(options.cursor.row+1,options.cursor.column,false);
37983
- }
37984
-
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
+ }
37985
39090
  dialogForm.i18n();
37986
39091
  },
37987
39092
  close: function() {
@@ -38045,7 +39150,11 @@ RED.editor = (function() {
38045
39150
  }
38046
39151
  })
38047
39152
  return toolbar;
38048
- }
39153
+ },
39154
+ postInit: function (editor, options) {
39155
+ var elem = $("#"+options.id);
39156
+ initImageDrag(elem, editor);
39157
+ }
38049
39158
  }
38050
39159
  RED.editor.registerTypeEditor("_markdown", definition);
38051
39160
  })();
@@ -38443,7 +39552,7 @@ RED.editor.codeEditor.monaco = (function() {
38443
39552
  "node-red-util": {package: "node-red", module: "util", path: "node-red/util.d.ts" },
38444
39553
  "node-red-func": {package: "node-red", module: "func", path: "node-red/func.d.ts" },
38445
39554
  }
38446
- 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"] ];
38447
39556
 
38448
39557
  const modulesCache = {};
38449
39558
 
@@ -40677,11 +41786,10 @@ RED.clipboard = (function() {
40677
41786
  }
40678
41787
  }
40679
41788
 
40680
- function showImportNodes(mode) {
41789
+ function showImportNodes(library = 'clipboard') {
40681
41790
  if (disabled) {
40682
41791
  return;
40683
41792
  }
40684
- mode = mode || "clipboard";
40685
41793
 
40686
41794
  dialogContainer.empty();
40687
41795
  dialogContainer.append($(importNodesDialog));
@@ -40758,7 +41866,7 @@ RED.clipboard = (function() {
40758
41866
  $("#red-ui-clipboard-dialog-import-text").on("keyup", validateImport);
40759
41867
  $("#red-ui-clipboard-dialog-import-text").on('paste',function() { setTimeout(validateImport,10)});
40760
41868
 
40761
- if (RED.workspaces.active() === 0) {
41869
+ if (RED.workspaces.active() === 0 || RED.workspaces.isActiveLocked()) {
40762
41870
  $("#red-ui-clipboard-dialog-import-opt-current").addClass('disabled').removeClass("selected");
40763
41871
  $("#red-ui-clipboard-dialog-import-opt-new").addClass("selected");
40764
41872
  } else {
@@ -40787,8 +41895,8 @@ RED.clipboard = (function() {
40787
41895
  $("#red-ui-clipboard-dialog-import-file-upload").trigger("click");
40788
41896
  })
40789
41897
 
40790
- tabs.activateTab("red-ui-clipboard-dialog-import-tab-"+mode);
40791
- if (mode === 'clipboard') {
41898
+ tabs.activateTab("red-ui-clipboard-dialog-import-tab-"+library);
41899
+ if (library === 'clipboard') {
40792
41900
  setTimeout(function() {
40793
41901
  $("#red-ui-clipboard-dialog-import-text").trigger("focus");
40794
41902
  },100)
@@ -40812,13 +41920,16 @@ RED.clipboard = (function() {
40812
41920
  });
40813
41921
  }
40814
41922
 
40815
- 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' ) {
40816
41929
  if (disabled) {
40817
41930
  return;
40818
41931
  }
40819
41932
 
40820
- mode = mode || "clipboard";
40821
-
40822
41933
  dialogContainer.empty();
40823
41934
  dialogContainer.append($(exportNodesDialog));
40824
41935
 
@@ -40908,7 +42019,12 @@ RED.clipboard = (function() {
40908
42019
  $("#red-ui-clipboard-dialog-tab-library-name").val("flows.json").select();
40909
42020
 
40910
42021
  dialogContainer.i18n();
42022
+
40911
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
+ }
40912
42028
 
40913
42029
  $("#red-ui-clipboard-dialog-export-fmt-group > a").on("click", function(evt) {
40914
42030
  evt.preventDefault();
@@ -40924,7 +42040,8 @@ RED.clipboard = (function() {
40924
42040
  var nodes = JSON.parse(flow);
40925
42041
 
40926
42042
  format = $(this).attr('id');
40927
- if (format === 'red-ui-clipboard-dialog-export-fmt-full') {
42043
+ const pretty = format === "red-ui-clipboard-dialog-export-fmt-full";
42044
+ if (pretty) {
40928
42045
  flow = JSON.stringify(nodes,null,4);
40929
42046
  } else {
40930
42047
  flow = JSON.stringify(nodes);
@@ -40933,6 +42050,7 @@ RED.clipboard = (function() {
40933
42050
  setTimeout(function() { $("#red-ui-clipboard-dialog-export-text").scrollTop(0); },50);
40934
42051
 
40935
42052
  $("#red-ui-clipboard-dialog-export-text").trigger("focus");
42053
+ RED.settings.set("editor.dialog.export.pretty", pretty)
40936
42054
  }
40937
42055
  });
40938
42056
 
@@ -41020,12 +42138,15 @@ RED.clipboard = (function() {
41020
42138
  }
41021
42139
  }
41022
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
+ }
41023
42144
  if (format === "red-ui-clipboard-dialog-export-fmt-full") {
41024
42145
  $("#red-ui-clipboard-dialog-export-fmt-full").trigger("click");
41025
42146
  } else {
41026
42147
  $("#red-ui-clipboard-dialog-export-fmt-mini").trigger("click");
41027
42148
  }
41028
- tabs.activateTab("red-ui-clipboard-dialog-export-tab-"+mode);
42149
+ tabs.activateTab("red-ui-clipboard-dialog-export-tab-"+library);
41029
42150
 
41030
42151
  var dialogHeight = 400;
41031
42152
  var winHeight = $(window).height();
@@ -41520,15 +42641,17 @@ RED.clipboard = (function() {
41520
42641
  RED.keyboard.add("#red-ui-drop-target", "escape" ,hideDropTarget);
41521
42642
 
41522
42643
  $('#red-ui-workspace-chart').on("dragenter",function(event) {
41523
- if ($.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1 ||
41524
- $.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)) {
41525
42647
  $("#red-ui-drop-target").css({display:'table'}).focus();
41526
42648
  }
41527
42649
  });
41528
42650
 
41529
42651
  $('#red-ui-drop-target').on("dragover",function(event) {
41530
42652
  if ($.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1 ||
41531
- $.inArray("Files",event.originalEvent.dataTransfer.types) != -1) {
42653
+ $.inArray("Files",event.originalEvent.dataTransfer.types) != -1 ||
42654
+ RED.workspaces.isActiveLocked()) {
41532
42655
  event.preventDefault();
41533
42656
  }
41534
42657
  })
@@ -41536,27 +42659,29 @@ RED.clipboard = (function() {
41536
42659
  hideDropTarget();
41537
42660
  })
41538
42661
  .on("drop",function(event) {
41539
- try {
41540
- if ($.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1) {
41541
- var data = event.originalEvent.dataTransfer.getData("text/plain");
41542
- data = data.substring(data.indexOf('['),data.lastIndexOf(']')+1);
41543
- importNodes(data);
41544
- } else if ($.inArray("Files",event.originalEvent.dataTransfer.types) != -1) {
41545
- var files = event.originalEvent.dataTransfer.files;
41546
- if (files.length === 1) {
41547
- var file = files[0];
41548
- var reader = new FileReader();
41549
- reader.onload = (function(theFile) {
41550
- return function(e) {
41551
- importNodes(e.target.result);
41552
- };
41553
- })(file);
41554
- 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
+ }
41555
42680
  }
42681
+ } catch(err) {
42682
+ // Ensure any errors throw above doesn't stop the drop target from
42683
+ // being hidden.
41556
42684
  }
41557
- } catch(err) {
41558
- // Ensure any errors throw above doesn't stop the drop target from
41559
- // being hidden.
41560
42685
  }
41561
42686
  hideDropTarget();
41562
42687
  event.preventDefault();
@@ -42878,38 +44003,51 @@ RED.search = (function() {
42878
44003
  return val;
42879
44004
  }
42880
44005
 
42881
- function search(val) {
42882
- var results = [];
42883
- var typeFilter;
42884
- var m = /(?:^| )type:([^ ]+)/.exec(val);
42885
- if (m) {
42886
- val = val.replace(/(?:^| )type:[^ ]+/,"");
42887
- 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);
42888
44019
  }
42889
- var flags = {};
44020
+ return val;
44021
+ }
44022
+
44023
+ function search(val) {
44024
+ const results = [];
44025
+ const flags = {};
42890
44026
  val = extractFlag(val,"invalid",flags);
42891
44027
  val = extractFlag(val,"unused",flags);
42892
44028
  val = extractFlag(val,"config",flags);
42893
44029
  val = extractFlag(val,"subflow",flags);
42894
44030
  val = extractFlag(val,"hidden",flags);
42895
44031
  val = extractFlag(val,"modified",flags);
42896
- val = extractValue(val,"flow",flags);// flow:active or flow:<flow-id>
44032
+ val = extractValue(val,"flow",flags);// flow:current or flow:<flow-id>
42897
44033
  val = extractValue(val,"uses",flags);// uses:<node-id>
44034
+ val = extractType(val,flags);// type:<node-type>
42898
44035
  val = val.trim();
42899
- var hasFlags = Object.keys(flags).length > 0;
44036
+ const hasFlags = Object.keys(flags).length > 0;
44037
+ const hasTypeFilter = flags.type && flags.type.length > 0
42900
44038
  if (flags.flow && flags.flow.indexOf("current") >= 0) {
42901
44039
  let idx = flags.flow.indexOf("current");
42902
- flags.flow[idx] = RED.workspaces.active();//convert active to flow ID
44040
+ flags.flow[idx] = RED.workspaces.active();//convert 'current' to active flow ID
42903
44041
  }
42904
44042
  if (flags.flow && flags.flow.length) {
42905
44043
  flags.flow = [ ...new Set(flags.flow) ]; //deduplicate
42906
44044
  }
42907
- if (val.length > 0 || typeFilter || hasFlags) {
44045
+ if (val.length > 0 || hasFlags) {
42908
44046
  val = val.toLowerCase();
42909
- var i;
42910
- var j;
42911
- var list = [];
42912
- var nodes = {};
44047
+ let i;
44048
+ let j;
44049
+ let list = [];
44050
+ const nodes = {};
42913
44051
  let keys = [];
42914
44052
  if (flags.uses) {
42915
44053
  keys = flags.uses;
@@ -42917,10 +44055,10 @@ RED.search = (function() {
42917
44055
  keys = Object.keys(index);
42918
44056
  }
42919
44057
  for (i=0;i<keys.length;i++) {
42920
- var key = keys[i];
42921
- var kpos = keys[i].indexOf(val);
42922
- if (kpos > -1) {
42923
- 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]||{});
42924
44062
  for (j=0;j<ids.length;j++) {
42925
44063
  var node = index[key][ids[j]];
42926
44064
  var isConfigNode = node.node._def.category === "config" && node.node.type !== 'group';
@@ -42928,7 +44066,7 @@ RED.search = (function() {
42928
44066
  continue;
42929
44067
  }
42930
44068
  if (flags.hasOwnProperty("invalid")) {
42931
- var nodeIsValid = !node.node.hasOwnProperty("valid") || node.node.valid;
44069
+ const nodeIsValid = !node.node.hasOwnProperty("valid") || node.node.valid;
42932
44070
  if (flags.invalid === nodeIsValid) {
42933
44071
  continue;
42934
44072
  }
@@ -42958,7 +44096,7 @@ RED.search = (function() {
42958
44096
  }
42959
44097
  }
42960
44098
  if (flags.hasOwnProperty("unused")) {
42961
- var isUnused = (node.node.type === 'subflow' && node.node.instances.length === 0) ||
44099
+ const isUnused = (node.node.type === 'subflow' && node.node.instances.length === 0) ||
42962
44100
  (isConfigNode && node.node.users.length === 0 && node.node._def.hasUsers !== false)
42963
44101
  if (flags.unused !== isUnused) {
42964
44102
  continue;
@@ -42969,12 +44107,16 @@ RED.search = (function() {
42969
44107
  continue;
42970
44108
  }
42971
44109
  }
42972
- if (!typeFilter || node.node.type === typeFilter) {
42973
- 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] || {
42974
44116
  node: node.node,
42975
44117
  label: node.label
42976
44118
  };
42977
- 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);
42978
44120
  }
42979
44121
  }
42980
44122
  }
@@ -43439,21 +44581,6 @@ RED.search = (function() {
43439
44581
  ;RED.contextMenu = (function () {
43440
44582
 
43441
44583
  let menu;
43442
- function createMenu() {
43443
- // menu = RED.popover.menu({
43444
- // options: [
43445
- // {
43446
- // label: 'delete selection',
43447
- // onselect: function() {
43448
- // RED.actions.invoke('core:delete-selection')
43449
- // RED.view.focus()
43450
- // }
43451
- // },
43452
- // { label: 'world' }
43453
- // ],
43454
- // width: 200,
43455
- // })
43456
- }
43457
44584
 
43458
44585
  function disposeMenu() {
43459
44586
  $(document).off("mousedown.red-ui-workspace-context-menu");
@@ -43466,114 +44593,164 @@ RED.search = (function() {
43466
44593
  if (menu) {
43467
44594
  menu.remove()
43468
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()
43469
44619
 
43470
- const selection = RED.view.selection()
43471
- const noSelection = !selection || Object.keys(selection).length === 0
43472
- const hasSelection = (selection.nodes && selection.nodes.length > 0);
43473
- const hasMultipleSelection = hasSelection && selection.nodes.length > 1;
43474
- const virtulLinks = (selection.links && selection.links.filter(e => !!e.link)) || [];
43475
- const wireLinks = (selection.links && selection.links.filter(e => !e.link)) || [];
43476
- const hasLinks = wireLinks.length > 0;
43477
- const isSingleLink = !hasSelection && hasLinks && wireLinks.length === 1
43478
- const isMultipleLinks = !hasSelection && hasLinks && wireLinks.length > 1
43479
- const canDelete = hasSelection || hasLinks
43480
- const isGroup = hasSelection && selection.nodes.length === 1 && selection.nodes[0].type === 'group'
43481
-
43482
- const canRemoveFromGroup = hasSelection && !!selection.nodes[0].g
43483
- 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
+ }
43484
44625
 
43485
- // addX/addY must be the position in the workspace accounting for both scroll and scale
43486
- // The +5 is because we display the contextMenu -5,-5 to actual click position
43487
- let addX = (options.x + 5 - offset.left + $("#red-ui-workspace-chart").scrollLeft()) / RED.view.scale()
43488
- 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
+ )
43489
44629
 
43490
- const menuItems = [
43491
- { onselect: 'core:show-action-list', onpostselect: function () { } },
43492
- {
43493
- label: RED._("contextMenu.insert"),
43494
- options: [
43495
- {
43496
- label: RED._("contextMenu.node"),
43497
- onselect: function () {
43498
- RED.view.showQuickAddDialog({
43499
- position: [addX, addY],
43500
- touchTrigger: true,
43501
- splice: isSingleLink ? selection.links[0] : undefined,
43502
- // spliceMultiple: isMultipleLinks
43503
- })
43504
- },
43505
- onpostselect: function() {
43506
- // ensure quick add dialog search input has focus
43507
- $('#red-ui-type-search-input').trigger('focus')
43508
- }
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
+ })
43509
44642
  },
43510
- (hasLinks) ? { // has least 1 wire selected
43511
- label: RED._("contextMenu.junction"),
43512
- onselect: 'core:split-wires-with-junctions',
43513
- disabled: !hasLinks
43514
- } : {
43515
- label: RED._("contextMenu.junction"),
43516
- onselect: function () {
43517
- const nn = {
43518
- _def: { defaults: {} },
43519
- type: 'junction',
43520
- z: RED.workspaces.active(),
43521
- id: RED.nodes.id(),
43522
- x: addX,
43523
- y: addY,
43524
- w: 0, h: 0,
43525
- outputs: 1,
43526
- inputs: 1,
43527
- dirty: true
43528
- }
43529
- const historyEvent = {
43530
- dirty: RED.nodes.dirty(),
43531
- t: 'add',
43532
- junctions: [nn]
43533
- }
43534
- RED.nodes.addJunction(nn);
43535
- RED.history.push(historyEvent);
43536
- RED.nodes.dirty(true);
43537
- RED.view.select({nodes: [nn] });
43538
- RED.view.redraw(true)
43539
- }
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)
43540
44674
  },
43541
- {
43542
- label: RED._("contextMenu.linkNodes"),
43543
- onselect: 'core:split-wire-with-link-nodes',
43544
- disabled: !hasLinks
43545
- }
43546
- ]
43547
-
43548
-
43549
-
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
+ })
43550
44738
  }
43551
- ]
43552
44739
 
43553
- menuItems.push(
43554
- null,
43555
- { onselect: 'core:undo', disabled: RED.history.list().length === 0 },
43556
- { onselect: 'core:redo', disabled: RED.history.listRedo().length === 0 },
43557
- null,
43558
- { onselect: 'core:cut-selection-to-internal-clipboard', label: RED._("keyboard.cutNode"), disabled: !hasSelection },
43559
- { onselect: 'core:copy-selection-to-internal-clipboard', label: RED._("keyboard.copyNode"), disabled: !hasSelection },
43560
- { onselect: 'core:paste-from-internal-clipboard', label: RED._("keyboard.pasteNode"), disabled: !RED.view.clipboard() },
43561
- { onselect: 'core:delete-selection', disabled: !canDelete },
43562
- { onselect: 'core:show-export-dialog', label: RED._("menu.label.export") },
43563
- { onselect: 'core:select-all-nodes' }
43564
- )
43565
44740
 
43566
- if (hasSelection) {
43567
44741
  menuItems.push(
43568
44742
  null,
43569
- isGroup ?
43570
- { onselect: 'core:ungroup-selection', disabled: !isGroup }
43571
- : { 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' },
43572
44753
  )
43573
- if (canRemoveFromGroup) {
43574
- menuItems.push({ onselect: 'core:remove-selection-from-group', label: RED._("menu.label.groupRemoveSelection") })
43575
- }
43576
-
43577
44754
  }
43578
44755
 
43579
44756
  var direction = "right";
@@ -44568,6 +45745,11 @@ RED.subflow = (function() {
44568
45745
  var subflowInstances = [];
44569
45746
  if (activeSubflow) {
44570
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
+ }
44571
45753
  subflowInstances.push({
44572
45754
  id: n.id,
44573
45755
  changed: n.changed
@@ -44580,6 +45762,9 @@ RED.subflow = (function() {
44580
45762
  n.resize = true;
44581
45763
  n.dirty = true;
44582
45764
  RED.editor.updateNodeProperties(n);
45765
+ if (wasLocked) {
45766
+ parentFlow.locked = true
45767
+ }
44583
45768
  });
44584
45769
  RED.editor.validateNode(activeSubflow);
44585
45770
  return {
@@ -44726,44 +45911,7 @@ RED.subflow = (function() {
44726
45911
 
44727
45912
  $("#red-ui-subflow-delete").on("click", function(event) {
44728
45913
  event.preventDefault();
44729
- var subflow = RED.nodes.subflow(RED.workspaces.active());
44730
- if (subflow.instances.length > 0) {
44731
- var msg = $('<div>')
44732
- $('<p>').text(RED._("subflow.subflowInstances",{count: subflow.instances.length})).appendTo(msg);
44733
- $('<p>').text(RED._("subflow.confirmDelete")).appendTo(msg);
44734
- var confirmDeleteNotification = RED.notify(msg, {
44735
- modal: true,
44736
- fixed: true,
44737
- buttons: [
44738
- {
44739
- text: RED._('common.label.cancel'),
44740
- click: function() {
44741
- confirmDeleteNotification.close();
44742
- }
44743
- },
44744
- {
44745
- text: RED._('workspace.confirmDelete'),
44746
- class: "primary",
44747
- click: function() {
44748
- confirmDeleteNotification.close();
44749
- completeDelete();
44750
- }
44751
- }
44752
- ]
44753
- });
44754
-
44755
- return;
44756
- } else {
44757
- completeDelete();
44758
- }
44759
- function completeDelete() {
44760
- var startDirty = RED.nodes.dirty();
44761
- var historyEvent = removeSubflow(RED.workspaces.active());
44762
- historyEvent.t = 'delete';
44763
- historyEvent.dirty = startDirty;
44764
- RED.history.push(historyEvent);
44765
- }
44766
-
45914
+ RED.subflow.delete(RED.workspaces.active())
44767
45915
  });
44768
45916
 
44769
45917
  refreshToolbar(activeSubflow);
@@ -44776,7 +45924,51 @@ RED.subflow = (function() {
44776
45924
  $("#red-ui-workspace-toolbar").hide().empty();
44777
45925
  $("#red-ui-workspace-chart").css({"margin-top": "0"});
44778
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
+ });
44779
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
+ }
44780
45972
  function removeSubflow(id, keepInstanceNodes) {
44781
45973
  // TODO: A lot of this logic is common with RED.nodes.removeWorkspace
44782
45974
  var removedNodes = [];
@@ -44853,7 +46045,7 @@ RED.subflow = (function() {
44853
46045
  }
44854
46046
  });
44855
46047
  RED.events.on("view:selection-changed",function(selection) {
44856
- if (!selection.nodes) {
46048
+ if (!selection.nodes || RED.workspaces.isActiveLocked()) {
44857
46049
  RED.menu.setDisabled("menu-item-subflow-convert",true);
44858
46050
  } else {
44859
46051
  RED.menu.setDisabled("menu-item-subflow-convert",false);
@@ -44916,6 +46108,9 @@ RED.subflow = (function() {
44916
46108
  }
44917
46109
 
44918
46110
  function convertToSubflow() {
46111
+ if (RED.workspaces.isActiveLocked()) {
46112
+ return
46113
+ }
44919
46114
  var selection = RED.view.selection();
44920
46115
  if (!selection.nodes) {
44921
46116
  RED.notify(RED._("subflow.errors.noNodesSelected"),"error");
@@ -45071,7 +46266,7 @@ RED.subflow = (function() {
45071
46266
  }
45072
46267
  subflowInstance._def = RED.nodes.getType(subflowInstance.type);
45073
46268
  RED.editor.validateNode(subflowInstance);
45074
- RED.nodes.add(subflowInstance);
46269
+ subflowInstance = RED.nodes.add(subflowInstance);
45075
46270
 
45076
46271
  if (containingGroup) {
45077
46272
  RED.group.addToGroup(containingGroup, subflowInstance);
@@ -45625,7 +46820,10 @@ RED.subflow = (function() {
45625
46820
  init: init,
45626
46821
  createSubflow: createSubflow,
45627
46822
  convertToSubflow: convertToSubflow,
46823
+ // removeSubflow: Internal function to remove subflow
45628
46824
  removeSubflow: removeSubflow,
46825
+ // delete: Prompt user for confirmation
46826
+ delete: deleteSubflow,
45629
46827
  refresh: refresh,
45630
46828
  removeInput: removeSubflowInput,
45631
46829
  removeOutput: removeSubflowOutput,
@@ -45827,6 +47025,8 @@ RED.group = (function() {
45827
47025
  var activateMerge = false;
45828
47026
  var activateRemove = false;
45829
47027
  var singleGroupSelected = false;
47028
+ var locked = RED.workspaces.isActiveLocked()
47029
+
45830
47030
  if (activateGroup) {
45831
47031
  singleGroupSelected = selection.nodes.length === 1 && selection.nodes[0].type === 'group';
45832
47032
  selection.nodes.forEach(function (n) {
@@ -45841,12 +47041,12 @@ RED.group = (function() {
45841
47041
  activateMerge = (selection.nodes.length > 1);
45842
47042
  }
45843
47043
  }
45844
- RED.menu.setDisabled("menu-item-group-group", !activateGroup);
45845
- RED.menu.setDisabled("menu-item-group-ungroup", !activateUngroup);
45846
- RED.menu.setDisabled("menu-item-group-merge", !activateMerge);
45847
- 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);
45848
47048
  RED.menu.setDisabled("menu-item-edit-copy-group-style", !singleGroupSelected);
45849
- RED.menu.setDisabled("menu-item-edit-paste-group-style", !activateUngroup);
47049
+ RED.menu.setDisabled("menu-item-edit-paste-group-style", locked || !activateUngroup);
45850
47050
  });
45851
47051
 
45852
47052
  RED.actions.add("core:group-selection", function() { groupSelection() })
@@ -45903,6 +47103,7 @@ RED.group = (function() {
45903
47103
  }
45904
47104
  }
45905
47105
  function pasteGroupStyle() {
47106
+ if (RED.workspaces.isActiveLocked()) { return }
45906
47107
  if (RED.view.state() !== RED.state.DEFAULT) { return }
45907
47108
  if (groupStyleClipboard) {
45908
47109
  var selection = RED.view.selection();
@@ -45937,6 +47138,7 @@ RED.group = (function() {
45937
47138
  }
45938
47139
 
45939
47140
  function groupSelection() {
47141
+ if (RED.workspaces.isActiveLocked()) { return }
45940
47142
  if (RED.view.state() !== RED.state.DEFAULT) { return }
45941
47143
  var selection = RED.view.selection();
45942
47144
  if (selection.nodes) {
@@ -45955,6 +47157,7 @@ RED.group = (function() {
45955
47157
  }
45956
47158
  }
45957
47159
  function ungroupSelection() {
47160
+ if (RED.workspaces.isActiveLocked()) { return }
45958
47161
  if (RED.view.state() !== RED.state.DEFAULT) { return }
45959
47162
  var selection = RED.view.selection();
45960
47163
  if (selection.nodes) {
@@ -45978,6 +47181,7 @@ RED.group = (function() {
45978
47181
  }
45979
47182
 
45980
47183
  function ungroup(g) {
47184
+ if (RED.workspaces.isActiveLocked()) { return }
45981
47185
  var nodes = [];
45982
47186
  var parentGroup = RED.nodes.group(g.g);
45983
47187
  g.nodes.forEach(function(n) {
@@ -46004,6 +47208,7 @@ RED.group = (function() {
46004
47208
  }
46005
47209
 
46006
47210
  function mergeSelection() {
47211
+ if (RED.workspaces.isActiveLocked()) { return }
46007
47212
  if (RED.view.state() !== RED.state.DEFAULT) { return }
46008
47213
  var selection = RED.view.selection();
46009
47214
  if (selection.nodes) {
@@ -46073,6 +47278,7 @@ RED.group = (function() {
46073
47278
  }
46074
47279
 
46075
47280
  function removeSelection() {
47281
+ if (RED.workspaces.isActiveLocked()) { return }
46076
47282
  if (RED.view.state() !== RED.state.DEFAULT) { return }
46077
47283
  var selection = RED.view.selection();
46078
47284
  if (selection.nodes) {
@@ -46100,6 +47306,7 @@ RED.group = (function() {
46100
47306
  }
46101
47307
  }
46102
47308
  function createGroup(nodes) {
47309
+ if (RED.workspaces.isActiveLocked()) { return }
46103
47310
  if (nodes.length === 0) {
46104
47311
  return;
46105
47312
  }
@@ -46122,7 +47329,7 @@ RED.group = (function() {
46122
47329
  }
46123
47330
 
46124
47331
  group.z = nodes[0].z;
46125
- RED.nodes.addGroup(group);
47332
+ group = RED.nodes.addGroup(group);
46126
47333
 
46127
47334
  try {
46128
47335
  addToGroup(group,nodes);
@@ -46205,6 +47412,7 @@ RED.group = (function() {
46205
47412
  markDirty(group);
46206
47413
  }
46207
47414
  function removeFromGroup(group, nodes, reparent) {
47415
+ if (RED.workspaces.isActiveLocked()) { return }
46208
47416
  if (!Array.isArray(nodes)) {
46209
47417
  nodes = [nodes];
46210
47418
  }
@@ -47207,7 +48415,7 @@ RED.projects = (function() {
47207
48415
  var sshwarningRow = $('<div class="red-ui-projects-dialog-screen-create-row-auth-error-no-keys"></div>').hide().appendTo(subrow);
47208
48416
  $('<div class="form-row"><i class="fa fa-warning"></i> '+RED._("projects.clone-project.ssh-key-desc")+'</div>').appendTo(sshwarningRow);
47209
48417
  subrow = $('<div style="text-align: center">').appendTo(sshwarningRow);
47210
- $('<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) {
47211
48419
  e.preventDefault();
47212
48420
  dialog.dialog( "close" );
47213
48421
  RED.userSettings.show('gitconfig');
@@ -47833,11 +49041,11 @@ RED.projects = (function() {
47833
49041
 
47834
49042
  row = $('<div class="form-row button-group"></div>').appendTo(container);
47835
49043
 
47836
- 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);
47837
- 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);
47838
- // 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);
47839
- 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);
47840
- // 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);
47841
49049
  row.find(".red-ui-projects-dialog-screen-create-type").on("click", function(evt) {
47842
49050
  evt.preventDefault();
47843
49051
  container.find(".red-ui-projects-dialog-screen-create-type").removeClass('selected');
@@ -47933,7 +49141,7 @@ RED.projects = (function() {
47933
49141
  var credentialsLeftBox = $('<div class="red-ui-projects-dialog-credentials-box-left">').appendTo(credentialsBox);
47934
49142
 
47935
49143
  var credentialsEnabledBox = $('<div class="form-row red-ui-projects-dialog-credentials-box-enabled"></div>').appendTo(credentialsLeftBox);
47936
- $('<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);
47937
49145
  var credentialsDisabledBox = $('<div class="form-row red-ui-projects-dialog-credentials-box-disabled disabled"></div>').appendTo(credentialsLeftBox);
47938
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);
47939
49147
 
@@ -48059,7 +49267,7 @@ RED.projects = (function() {
48059
49267
  var sshwarningRow = $('<div class="red-ui-projects-dialog-screen-create-row-auth-error-no-keys"></div>').hide().appendTo(subrow);
48060
49268
  $('<div class="form-row"><i class="fa fa-warning"></i> '+RED._("projects.create.desc2")+'</div>').appendTo(sshwarningRow);
48061
49269
  subrow = $('<div style="text-align: center">').appendTo(sshwarningRow);
48062
- $('<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) {
48063
49271
  e.preventDefault();
48064
49272
  $('#red-ui-projects-dialog-cancel').trigger("click");
48065
49273
  RED.userSettings.show('gitconfig');
@@ -48293,14 +49501,14 @@ RED.projects = (function() {
48293
49501
  function deleteProject(row,name,done) {
48294
49502
  var cover = $('<div class="red-ui-projects-dialog-project-list-entry-delete-confirm"></div>').on("click", function(evt) { evt.stopPropagation(); }).appendTo(row);
48295
49503
  $('<span>').text(RED._("projects.delete.confirm")).appendTo(cover);
48296
- $('<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>')
48297
49505
  .appendTo(cover)
48298
49506
  .on("click", function(e) {
48299
49507
  e.stopPropagation();
48300
49508
  cover.remove();
48301
49509
  done(true);
48302
49510
  });
48303
- $('<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>')
48304
49512
  .appendTo(cover)
48305
49513
  .on("click", function(e) {
48306
49514
  e.stopPropagation();
@@ -48484,7 +49692,7 @@ RED.projects = (function() {
48484
49692
  header.addClass("selectable");
48485
49693
 
48486
49694
  var tools = $('<div class="red-ui-projects-dialog-project-list-entry-tools"></div>').appendTo(header);
48487
- $('<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>')
48488
49696
  .appendTo(tools)
48489
49697
  .on("click", function(e) {
48490
49698
  e.stopPropagation();
@@ -53240,10 +54448,15 @@ RED.touch.radialMenu = (function() {
53240
54448
 
53241
54449
  function listTour() {
53242
54450
  return [
54451
+ {
54452
+ id: "3_1",
54453
+ label: "3.1",
54454
+ path: "./tours/welcome.js"
54455
+ },
53243
54456
  {
53244
54457
  id: "3_0",
53245
54458
  label: "3.0",
53246
- path: "./tours/welcome.js"
54459
+ path: "./tours/3.0/welcome.js"
53247
54460
  },
53248
54461
  {
53249
54462
  id: "2_2",