@node-red/editor-client 2.1.6 → 2.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/public/red/red.js CHANGED
@@ -647,8 +647,7 @@ var RED = (function() {
647
647
 
648
648
  $(".red-ui-header-toolbar").show();
649
649
 
650
-
651
- RED.sidebar.show(":first");
650
+ RED.sidebar.show(":first", true);
652
651
 
653
652
  setTimeout(function() {
654
653
  loader.end();
@@ -3539,7 +3538,9 @@ RED.state = {
3539
3538
  PANNING: 10,
3540
3539
  SELECTING_NODE: 11,
3541
3540
  GROUP_DRAGGING: 12,
3542
- GROUP_RESIZE: 13
3541
+ GROUP_RESIZE: 13,
3542
+ DETACHED_DRAGGING: 14,
3543
+ SLICING: 15
3543
3544
  }
3544
3545
  ;RED.plugins = (function() {
3545
3546
  var plugins = {};
@@ -3604,6 +3605,9 @@ RED.state = {
3604
3605
  **/
3605
3606
  RED.nodes = (function() {
3606
3607
 
3608
+ var PORT_TYPE_INPUT = 1;
3609
+ var PORT_TYPE_OUTPUT = 0;
3610
+
3607
3611
  var node_defs = {};
3608
3612
  var linkTabMap = {};
3609
3613
 
@@ -4186,6 +4190,14 @@ RED.nodes = (function() {
4186
4190
  RED.events.emit('nodes:add',n);
4187
4191
  }
4188
4192
  function addLink(l) {
4193
+ if (nodeLinks[l.source.id]) {
4194
+ const isUnique = nodeLinks[l.source.id].out.every(function(link) {
4195
+ return link.sourcePort !== l.sourcePort || link.target.id !== l.target.id
4196
+ })
4197
+ if (!isUnique) {
4198
+ return
4199
+ }
4200
+ }
4189
4201
  links.push(l);
4190
4202
  if (l.source) {
4191
4203
  // Possible the node hasn't been added yet
@@ -6047,6 +6059,144 @@ RED.nodes = (function() {
6047
6059
  return helpContent;
6048
6060
  }
6049
6061
 
6062
+ function getNodeIslands(nodes) {
6063
+ var selectedNodes = new Set(nodes);
6064
+ // Maps node => island index
6065
+ var nodeToIslandIndex = new Map();
6066
+ // Maps island index => [nodes in island]
6067
+ var islandIndexToNodes = new Map();
6068
+ var internalLinks = new Set();
6069
+ nodes.forEach((node, index) => {
6070
+ nodeToIslandIndex.set(node,index);
6071
+ islandIndexToNodes.set(index, [node]);
6072
+ var inboundLinks = RED.nodes.getNodeLinks(node, PORT_TYPE_INPUT);
6073
+ var outboundLinks = RED.nodes.getNodeLinks(node, PORT_TYPE_OUTPUT);
6074
+ inboundLinks.forEach(l => {
6075
+ if (selectedNodes.has(l.source)) {
6076
+ internalLinks.add(l)
6077
+ }
6078
+ })
6079
+ outboundLinks.forEach(l => {
6080
+ if (selectedNodes.has(l.target)) {
6081
+ internalLinks.add(l)
6082
+ }
6083
+ })
6084
+ })
6085
+
6086
+ internalLinks.forEach(l => {
6087
+ let source = l.source;
6088
+ let target = l.target;
6089
+ if (nodeToIslandIndex.get(source) !== nodeToIslandIndex.get(target)) {
6090
+ let sourceIsland = nodeToIslandIndex.get(source);
6091
+ let islandToMove = nodeToIslandIndex.get(target);
6092
+ let nodesToMove = islandIndexToNodes.get(islandToMove);
6093
+ nodesToMove.forEach(n => {
6094
+ nodeToIslandIndex.set(n,sourceIsland);
6095
+ islandIndexToNodes.get(sourceIsland).push(n);
6096
+ })
6097
+ islandIndexToNodes.delete(islandToMove);
6098
+ }
6099
+ })
6100
+ const result = [];
6101
+ islandIndexToNodes.forEach((nodes,index) => {
6102
+ result.push(nodes);
6103
+ })
6104
+ return result;
6105
+ }
6106
+
6107
+ function detachNodes(nodes) {
6108
+ let allSelectedNodes = [];
6109
+ nodes.forEach(node => {
6110
+ if (node.type === 'group') {
6111
+ let groupNodes = RED.group.getNodes(node,true,true);
6112
+ allSelectedNodes = allSelectedNodes.concat(groupNodes);
6113
+ } else {
6114
+ allSelectedNodes.push(node);
6115
+ }
6116
+ })
6117
+ if (allSelectedNodes.length > 0 ) {
6118
+ const nodeIslands = RED.nodes.getNodeIslands(allSelectedNodes);
6119
+ let removedLinks = [];
6120
+ let newLinks = [];
6121
+ let createdLinkIds = new Set();
6122
+
6123
+ nodeIslands.forEach(nodes => {
6124
+ let selectedNodes = new Set(nodes);
6125
+ let allInboundLinks = [];
6126
+ let allOutboundLinks = [];
6127
+ // Identify links that enter or exit this island of nodes
6128
+ nodes.forEach(node => {
6129
+ var inboundLinks = RED.nodes.getNodeLinks(node, PORT_TYPE_INPUT);
6130
+ var outboundLinks = RED.nodes.getNodeLinks(node, PORT_TYPE_OUTPUT);
6131
+ inboundLinks.forEach(l => {
6132
+ if (!selectedNodes.has(l.source)) {
6133
+ allInboundLinks.push(l)
6134
+ }
6135
+ })
6136
+ outboundLinks.forEach(l => {
6137
+ if (!selectedNodes.has(l.target)) {
6138
+ allOutboundLinks.push(l)
6139
+ }
6140
+ })
6141
+ });
6142
+
6143
+
6144
+ // Identify the links to restore
6145
+ allInboundLinks.forEach(inLink => {
6146
+ // For Each inbound link,
6147
+ // - get source node.
6148
+ // - trace through to all outbound links
6149
+ let sourceNode = inLink.source;
6150
+ let targetNodes = new Set();
6151
+ let visited = new Set();
6152
+ let stack = [inLink.target];
6153
+ while (stack.length > 0) {
6154
+ let node = stack.pop(stack);
6155
+ visited.add(node)
6156
+ let links = RED.nodes.getNodeLinks(node, PORT_TYPE_OUTPUT);
6157
+ links.forEach(l => {
6158
+ if (visited.has(l.target)) {
6159
+ return
6160
+ }
6161
+ visited.add(l.target);
6162
+ if (selectedNodes.has(l.target)) {
6163
+ // internal link
6164
+ stack.push(l.target)
6165
+ } else {
6166
+ targetNodes.add(l.target)
6167
+ }
6168
+ })
6169
+ }
6170
+ targetNodes.forEach(target => {
6171
+ let linkId = `${sourceNode.id}[${inLink.sourcePort}] -> ${target.id}`
6172
+ if (!createdLinkIds.has(linkId)) {
6173
+ createdLinkIds.add(linkId);
6174
+ let link = {
6175
+ source: sourceNode,
6176
+ sourcePort: inLink.sourcePort,
6177
+ target: target
6178
+ }
6179
+ let existingLinks = RED.nodes.filterLinks(link)
6180
+ if (existingLinks.length === 0) {
6181
+ newLinks.push(link);
6182
+ }
6183
+ }
6184
+ })
6185
+ })
6186
+
6187
+ // 2. delete all those links
6188
+ allInboundLinks.forEach(l => { RED.nodes.removeLink(l); removedLinks.push(l)})
6189
+ allOutboundLinks.forEach(l => { RED.nodes.removeLink(l); removedLinks.push(l)})
6190
+ })
6191
+
6192
+ newLinks.forEach(l => RED.nodes.addLink(l));
6193
+ return {
6194
+ newLinks,
6195
+ removedLinks
6196
+ }
6197
+ }
6198
+ }
6199
+
6050
6200
  return {
6051
6201
  init: function() {
6052
6202
  RED.events.on("registry:node-type-added",function(type) {
@@ -6128,7 +6278,7 @@ RED.nodes = (function() {
6128
6278
  add: addNode,
6129
6279
  remove: removeNode,
6130
6280
  clear: clear,
6131
-
6281
+ detachNodes: detachNodes,
6132
6282
  moveNodesForwards: moveNodesForwards,
6133
6283
  moveNodesBackwards: moveNodesBackwards,
6134
6284
  moveNodesToFront: moveNodesToFront,
@@ -6140,7 +6290,20 @@ RED.nodes = (function() {
6140
6290
 
6141
6291
  addLink: addLink,
6142
6292
  removeLink: removeLink,
6143
-
6293
+ getNodeLinks: function(id, portType) {
6294
+ if (typeof id !== 'string') {
6295
+ id = id.id;
6296
+ }
6297
+ if (nodeLinks[id]) {
6298
+ if (portType === 1) {
6299
+ // Return cloned arrays so they can be safely modified by caller
6300
+ return [].concat(nodeLinks[id].in)
6301
+ } else {
6302
+ return [].concat(nodeLinks[id].out)
6303
+ }
6304
+ }
6305
+ return [];
6306
+ },
6144
6307
  addWorkspace: addWorkspace,
6145
6308
  removeWorkspace: removeWorkspace,
6146
6309
  getWorkspaceOrder: function() { return workspacesOrder },
@@ -6214,6 +6377,7 @@ RED.nodes = (function() {
6214
6377
  getAllFlowNodes: getAllFlowNodes,
6215
6378
  getAllUpstreamNodes: getAllUpstreamNodes,
6216
6379
  getAllDownstreamNodes: getAllDownstreamNodes,
6380
+ getNodeIslands: getNodeIslands,
6217
6381
  createExportableNodeSet: createExportableNodeSet,
6218
6382
  createCompleteNodeSet: createCompleteNodeSet,
6219
6383
  updateConfigNodeUsers: updateConfigNodeUsers,
@@ -7734,6 +7898,13 @@ RED.history = (function() {
7734
7898
  peek: function() {
7735
7899
  return undoHistory[undoHistory.length-1];
7736
7900
  },
7901
+ replace: function(ev) {
7902
+ if (undoHistory.length === 0) {
7903
+ RED.history.push(ev);
7904
+ } else {
7905
+ undoHistory[undoHistory.length-1] = ev;
7906
+ }
7907
+ },
7737
7908
  clear: function() {
7738
7909
  undoHistory = [];
7739
7910
  redoHistory = [];
@@ -11491,6 +11662,15 @@ RED.popover = (function() {
11491
11662
  }
11492
11663
  }
11493
11664
 
11665
+ target.on("remove", function (ev) {
11666
+ if (timer) {
11667
+ clearTimeout(timer);
11668
+ }
11669
+ if (active) {
11670
+ active = false;
11671
+ setTimeout(closePopup,delay.hide);
11672
+ }
11673
+ });
11494
11674
  if (trigger === 'hover') {
11495
11675
  target.on('mouseenter',function(e) {
11496
11676
  clearTimeout(timer);
@@ -12536,23 +12716,19 @@ RED.tabs = (function() {
12536
12716
  }
12537
12717
  }
12538
12718
 
12539
- li.one("transitionend", function(evt) {
12540
- li.remove();
12541
- if (tabs[id].pinned) {
12542
- pinnedTabsCount--;
12543
- }
12544
- if (options.onremove) {
12545
- options.onremove(tabs[id]);
12546
- }
12547
- delete tabs[id];
12548
- updateTabWidths();
12549
- if (collapsibleMenu) {
12550
- collapsibleMenu.remove();
12551
- collapsibleMenu = null;
12552
- }
12553
- })
12554
- li.addClass("hide-tab");
12555
- li.width(0);
12719
+ li.remove();
12720
+ if (tabs[id].pinned) {
12721
+ pinnedTabsCount--;
12722
+ }
12723
+ if (options.onremove) {
12724
+ options.onremove(tabs[id]);
12725
+ }
12726
+ delete tabs[id];
12727
+ updateTabWidths();
12728
+ if (collapsibleMenu) {
12729
+ collapsibleMenu.remove();
12730
+ collapsibleMenu = null;
12731
+ }
12556
12732
  }
12557
12733
 
12558
12734
  function findPreviousVisibleTab(li) {
@@ -14661,33 +14837,39 @@ RED.stack = (function() {
14661
14837
  ;RED.actions = (function() {
14662
14838
  var actions = {
14663
14839
 
14664
- }
14840
+ };
14665
14841
 
14666
- function addAction(name,handler) {
14842
+ function addAction(name,handler,options) {
14667
14843
  if (typeof handler !== 'function') {
14668
14844
  throw new Error("Action handler not a function");
14669
14845
  }
14670
14846
  if (actions[name]) {
14671
14847
  throw new Error("Cannot override existing action");
14672
14848
  }
14673
- actions[name] = handler;
14849
+ actions[name] = {
14850
+ handler: handler,
14851
+ options: options,
14852
+ };
14674
14853
  }
14675
14854
  function removeAction(name) {
14676
14855
  delete actions[name];
14677
14856
  }
14678
14857
  function getAction(name) {
14679
- return actions[name];
14858
+ return actions[name].handler;
14680
14859
  }
14681
14860
  function invokeAction() {
14682
14861
  var args = Array.prototype.slice.call(arguments);
14683
14862
  var name = args.shift();
14684
14863
  if (actions.hasOwnProperty(name)) {
14685
- actions[name].apply(null, args);
14864
+ var handler = actions[name].handler;
14865
+ handler.apply(null, args);
14686
14866
  }
14687
14867
  }
14688
14868
  function listActions() {
14689
14869
  var result = [];
14870
+ var missing = [];
14690
14871
  Object.keys(actions).forEach(function(action) {
14872
+ var def = actions[action];
14691
14873
  var shortcut = RED.keyboard.getShortcut(action);
14692
14874
  var isUser = false;
14693
14875
  if (shortcut) {
@@ -14695,13 +14877,38 @@ RED.stack = (function() {
14695
14877
  } else {
14696
14878
  isUser = !!RED.keyboard.getUserShortcut(action);
14697
14879
  }
14880
+ if (!def.label) {
14881
+ var name = action;
14882
+ var options = def.options;
14883
+ var key = options ? options.label : undefined;
14884
+ if (!key) {
14885
+ key = "action-list." +name.replace(/^.*:/,"");
14886
+ }
14887
+ var label = RED._(key);
14888
+ if (label === key) {
14889
+ // no translation. convert `name` to description
14890
+ label = name.replace(/(^.+:([a-z]))|(-([a-z]))/g, function() {
14891
+ if (arguments[5] === 0) {
14892
+ return arguments[2].toUpperCase();
14893
+ } else {
14894
+ return " "+arguments[4].toUpperCase();
14895
+ }
14896
+ });
14897
+ missing.push(key);
14898
+ }
14899
+ def.label = label;
14900
+ }
14901
+ //console.log("; missing:", missing);
14902
+
14698
14903
  result.push({
14699
14904
  id:action,
14700
14905
  scope:shortcut?shortcut.scope:undefined,
14701
14906
  key:shortcut?shortcut.key:undefined,
14702
- user:isUser
14703
- })
14704
- })
14907
+ user:isUser,
14908
+ label: def.label,
14909
+ options: def.options,
14910
+ });
14911
+ });
14705
14912
  return result;
14706
14913
  }
14707
14914
  return {
@@ -15047,6 +15254,19 @@ RED.deploy = (function() {
15047
15254
  var unknownNodes = [];
15048
15255
  var invalidNodes = [];
15049
15256
 
15257
+ RED.nodes.eachConfig(function(node) {
15258
+ if (node.valid === undefined) {
15259
+ RED.editor.validateNode(node);
15260
+ }
15261
+ if (!node.valid && !node.d) {
15262
+ invalidNodes.push(getNodeInfo(node));
15263
+ }
15264
+ if (node.type === "unknown") {
15265
+ if (unknownNodes.indexOf(node.name) == -1) {
15266
+ unknownNodes.push(node.name);
15267
+ }
15268
+ }
15269
+ });
15050
15270
  RED.nodes.eachNode(function(node) {
15051
15271
  if (!node.valid && !node.d) {
15052
15272
  invalidNodes.push(getNodeInfo(node));
@@ -17516,15 +17736,15 @@ RED.keyboard = (function() {
17516
17736
  "]": 221,
17517
17737
  "{": 219,// <- QWERTY specific
17518
17738
  "}": 221 // <- QWERTY specific
17519
- }
17739
+ };
17520
17740
  var metaKeyCodes = {
17521
17741
  16: true,
17522
17742
  17: true,
17523
17743
  18: true,
17524
17744
  91: true,
17525
17745
  93: true
17526
- }
17527
- var actionToKeyMap = {}
17746
+ };
17747
+ var actionToKeyMap = {};
17528
17748
  var defaultKeyMap = {};
17529
17749
 
17530
17750
  // FF generates some different keycodes because reasons.
@@ -17532,7 +17752,7 @@ RED.keyboard = (function() {
17532
17752
  59:186,
17533
17753
  61:187,
17534
17754
  173:189
17535
- }
17755
+ };
17536
17756
 
17537
17757
  function migrateOldKeymap() {
17538
17758
  // pre-0.18
@@ -17547,7 +17767,7 @@ RED.keyboard = (function() {
17547
17767
  }
17548
17768
 
17549
17769
  function getUserKey(action) {
17550
- return RED.settings.get('editor.keymap',{})[action]
17770
+ return RED.settings.get('editor.keymap',{})[action];
17551
17771
  }
17552
17772
 
17553
17773
  function mergeKeymaps(defaultKeymap, themeKeymap) {
@@ -17572,7 +17792,7 @@ RED.keyboard = (function() {
17572
17792
  scope:scope,
17573
17793
  key:key,
17574
17794
  user:false
17575
- })
17795
+ });
17576
17796
  }
17577
17797
  }
17578
17798
  }
@@ -17582,13 +17802,13 @@ RED.keyboard = (function() {
17582
17802
  if (themeKeymap.hasOwnProperty(action)) {
17583
17803
  if (!themeKeymap[action].key) {
17584
17804
  // No key for this action - default is no keybinding
17585
- delete mergedKeymap[action]
17805
+ delete mergedKeymap[action];
17586
17806
  } else {
17587
17807
  mergedKeymap[action] = [{
17588
17808
  scope: themeKeymap[action].scope || "*",
17589
17809
  key: themeKeymap[action].key,
17590
17810
  user: false
17591
- }]
17811
+ }];
17592
17812
  if (mergedKeymap[action][0].scope === "workspace") {
17593
17813
  mergedKeymap[action][0].scope = "red-ui-workspace";
17594
17814
  }
@@ -17646,7 +17866,7 @@ RED.keyboard = (function() {
17646
17866
  close: function() {
17647
17867
  RED.menu.refreshShortcuts();
17648
17868
  }
17649
- })
17869
+ });
17650
17870
  }
17651
17871
 
17652
17872
  function revertToDefault(action) {
@@ -17794,7 +18014,7 @@ RED.keyboard = (function() {
17794
18014
  scope:scope,
17795
18015
  key:key,
17796
18016
  user:false
17797
- }
18017
+ };
17798
18018
  }
17799
18019
  if (!ondown) {
17800
18020
  var userAction = getUserKey(cbdown);
@@ -17817,7 +18037,7 @@ RED.keyboard = (function() {
17817
18037
  }
17818
18038
  }
17819
18039
  } else {
17820
- keys.push([key,mod])
18040
+ keys.push([key,mod]);
17821
18041
  }
17822
18042
  var slot = handlers;
17823
18043
  for (i=0;i<keys.length;i++) {
@@ -17840,7 +18060,7 @@ RED.keyboard = (function() {
17840
18060
  //slot[key] = {scope: scope, ondown:cbdown};
17841
18061
  }
17842
18062
  slot.handlers = slot.handlers || [];
17843
- slot.handlers.push({scope:scope,ondown:cbdown})
18063
+ slot.handlers.push({scope:scope,ondown:cbdown});
17844
18064
  slot.scope = scope;
17845
18065
  slot.ondown = cbdown;
17846
18066
  }
@@ -17857,12 +18077,12 @@ RED.keyboard = (function() {
17857
18077
  if (parsedKey) {
17858
18078
  keys.push(parsedKey);
17859
18079
  } else {
17860
- console.log("Unrecognised key specifier:",key)
18080
+ console.log("Unrecognised key specifier:",key);
17861
18081
  return;
17862
18082
  }
17863
18083
  }
17864
18084
  } else {
17865
- keys.push([key,mod])
18085
+ keys.push([key,mod]);
17866
18086
  }
17867
18087
  var slot = handlers;
17868
18088
  for (i=0;i<keys.length;i++) {
@@ -17884,7 +18104,7 @@ RED.keyboard = (function() {
17884
18104
  }
17885
18105
  if (typeof slot.ondown === "string") {
17886
18106
  if (typeof modifiers === 'boolean' && modifiers) {
17887
- actionToKeyMap[slot.ondown] = {user: modifiers}
18107
+ actionToKeyMap[slot.ondown] = {user: modifiers};
17888
18108
  } else {
17889
18109
  delete actionToKeyMap[slot.ondown];
17890
18110
  }
@@ -17900,11 +18120,11 @@ RED.keyboard = (function() {
17900
18120
  function formatKey(key,plain) {
17901
18121
  var formattedKey = isMac?key.replace(/ctrl-?/,"&#8984;"):key;
17902
18122
  formattedKey = isMac?formattedKey.replace(/alt-?/,"&#8997;"):key;
17903
- formattedKey = formattedKey.replace(/shift-?/,"&#8679;")
17904
- formattedKey = formattedKey.replace(/left/,"&#x2190;")
17905
- formattedKey = formattedKey.replace(/up/,"&#x2191;")
17906
- formattedKey = formattedKey.replace(/right/,"&#x2192;")
17907
- formattedKey = formattedKey.replace(/down/,"&#x2193;")
18123
+ formattedKey = formattedKey.replace(/shift-?/,"&#8679;");
18124
+ formattedKey = formattedKey.replace(/left/,"&#x2190;");
18125
+ formattedKey = formattedKey.replace(/up/,"&#x2191;");
18126
+ formattedKey = formattedKey.replace(/right/,"&#x2192;");
18127
+ formattedKey = formattedKey.replace(/down/,"&#x2193;");
17908
18128
  if (plain) {
17909
18129
  return formattedKey;
17910
18130
  }
@@ -17928,7 +18148,6 @@ RED.keyboard = (function() {
17928
18148
  var container = $(this);
17929
18149
  var object = container.data('data');
17930
18150
 
17931
-
17932
18151
  if (!container.hasClass('keyboard-shortcut-entry-expanded')) {
17933
18152
  endEditShortcut();
17934
18153
 
@@ -17952,7 +18171,7 @@ RED.keyboard = (function() {
17952
18171
  }
17953
18172
  $(this).toggleClass("input-error",!valid);
17954
18173
  okButton.attr("disabled",!valid);
17955
- })
18174
+ });
17956
18175
 
17957
18176
  var scopeSelect = $('<select><option value="*" data-i18n="keyboard.global"></option><option value="red-ui-workspace" data-i18n="keyboard.workspace"></option></select>').appendTo(scope);
17958
18177
  scopeSelect.i18n();
@@ -17962,7 +18181,7 @@ RED.keyboard = (function() {
17962
18181
  scopeSelect.val(object.scope||'*');
17963
18182
  scopeSelect.on("change", function() {
17964
18183
  keyInput.trigger("change");
17965
- })
18184
+ });
17966
18185
 
17967
18186
  var div = $('<div class="keyboard-shortcut-edit button-group-vertical"></div>').appendTo(scope);
17968
18187
  var okButton = $('<button class="red-ui-button red-ui-button-small"><i class="fa fa-check"></i></button>').appendTo(div);
@@ -17988,10 +18207,13 @@ RED.keyboard = (function() {
17988
18207
  id:object.id,
17989
18208
  scope:shortcut?shortcut.scope:undefined,
17990
18209
  key:shortcut?shortcut.key:undefined,
17991
- user:shortcut?shortcut.user:undefined
17992
- }
18210
+ user:shortcut?shortcut.user:undefined,
18211
+
18212
+ label: object.label,
18213
+ options: object.options,
18214
+ };
17993
18215
  buildShortcutRow(container,obj);
17994
- })
18216
+ });
17995
18217
 
17996
18218
  keyInput.trigger("focus");
17997
18219
  }
@@ -18026,7 +18248,7 @@ RED.keyboard = (function() {
18026
18248
  delete object.scope;
18027
18249
  } else {
18028
18250
  keyDiv.parent().removeClass("keyboard-shortcut-entry-unassigned");
18029
- keyDiv.append(RED.keyboard.formatKey(key))
18251
+ keyDiv.append(RED.keyboard.formatKey(key));
18030
18252
  $("<span>").text(scope).appendTo(scopeDiv);
18031
18253
  object.key = key;
18032
18254
  object.scope = scope;
@@ -18039,7 +18261,7 @@ RED.keyboard = (function() {
18039
18261
  userKeymap[object.id] = {
18040
18262
  scope:shortcut.scope,
18041
18263
  key:shortcut.key
18042
- }
18264
+ };
18043
18265
  RED.settings.set('editor.keymap',userKeymap);
18044
18266
  }
18045
18267
  }
@@ -18055,13 +18277,7 @@ RED.keyboard = (function() {
18055
18277
  var item = $('<div class="keyboard-shortcut-entry">').appendTo(container);
18056
18278
  container.data('data',object);
18057
18279
 
18058
- var text = object.id.replace(/(^.+:([a-z]))|(-([a-z]))/g,function() {
18059
- if (arguments[5] === 0) {
18060
- return arguments[2].toUpperCase();
18061
- } else {
18062
- return " "+arguments[4].toUpperCase();
18063
- }
18064
- });
18280
+ var text = object.label;
18065
18281
  var label = $('<div>').addClass("keyboard-shortcut-entry-text").text(text).appendTo(item);
18066
18282
 
18067
18283
  var user = $('<i class="fa fa-user"></i>').prependTo(label);
@@ -18096,14 +18312,15 @@ RED.keyboard = (function() {
18096
18312
  pane.find("#red-ui-settings-tab-keyboard-filter").searchBox({
18097
18313
  delay: 100,
18098
18314
  change: function() {
18099
- var filterValue = $(this).val().trim();
18315
+ var filterValue = $(this).val().trim().toLowerCase();
18100
18316
  if (filterValue === "") {
18101
18317
  shortcutList.editableList('filter', null);
18102
18318
  } else {
18103
18319
  filterValue = filterValue.replace(/\s/g,"");
18104
18320
  shortcutList.editableList('filter', function(data) {
18105
- return data.id.toLowerCase().replace(/^.*:/,"").replace("-","").indexOf(filterValue) > -1;
18106
- })
18321
+ var label = data.label.toLowerCase();
18322
+ return label.indexOf(filterValue) > -1;
18323
+ });
18107
18324
  }
18108
18325
  }
18109
18326
  });
@@ -18124,9 +18341,9 @@ RED.keyboard = (function() {
18124
18341
  });
18125
18342
  var shortcuts = RED.actions.list();
18126
18343
  shortcuts.sort(function(A,B) {
18127
- var Aid = A.id.replace(/^.*:/,"").replace(/[ -]/g,"").toLowerCase();
18128
- var Bid = B.id.replace(/^.*:/,"").replace(/[ -]/g,"").toLowerCase();
18129
- return Aid.localeCompare(Bid);
18344
+ var Akey = A.label;
18345
+ var Bkey = B.label;
18346
+ return Akey.localeCompare(Bkey);
18130
18347
  });
18131
18348
  knownShortcuts = new Set();
18132
18349
  shortcuts.forEach(function(s) {
@@ -18880,7 +19097,6 @@ RED.view = (function() {
18880
19097
  var activeGroups = [];
18881
19098
  var dirtyGroups = {};
18882
19099
 
18883
- var selected_link = null;
18884
19100
  var mousedown_link = null;
18885
19101
  var mousedown_node = null;
18886
19102
  var mousedown_group = null;
@@ -18892,6 +19108,8 @@ RED.view = (function() {
18892
19108
  var mouse_mode = 0;
18893
19109
  var mousedown_group_handle = null;
18894
19110
  var lasso = null;
19111
+ var slicePath = null;
19112
+ var slicePathLast = null;
18895
19113
  var ghostNode = null;
18896
19114
  var showStatus = false;
18897
19115
  var lastClickNode = null;
@@ -18946,6 +19164,14 @@ RED.view = (function() {
18946
19164
  if (!setIds.has(node.id)) {
18947
19165
  set.push({n:node});
18948
19166
  setIds.add(node.id);
19167
+ var links = RED.nodes.getNodeLinks(node.id,PORT_TYPE_INPUT).concat(RED.nodes.getNodeLinks(node.id,PORT_TYPE_OUTPUT))
19168
+ for (var i=0,l=links.length;i<l;i++) {
19169
+ var link = links[i]
19170
+ if (link.source === node && setIds.has(link.target.id) ||
19171
+ link.target === node && setIds.has(link.source.id)) {
19172
+ selectedLinks.add(link)
19173
+ }
19174
+ }
18949
19175
  }
18950
19176
  }
18951
19177
  },
@@ -18962,6 +19188,10 @@ RED.view = (function() {
18962
19188
  }
18963
19189
  }
18964
19190
  }
19191
+ var links = RED.nodes.getNodeLinks(node.id,PORT_TYPE_INPUT).concat(RED.nodes.getNodeLinks(node.id,PORT_TYPE_OUTPUT))
19192
+ for (var i=0,l=links.length;i<l;i++) {
19193
+ selectedLinks.remove(links[i]);
19194
+ }
18965
19195
  }
18966
19196
  },
18967
19197
  clear: function() {
@@ -18976,6 +19206,31 @@ RED.view = (function() {
18976
19206
  return api;
18977
19207
  })();
18978
19208
 
19209
+ var selectedLinks = (function() {
19210
+ var links = new Set();
19211
+ return {
19212
+ add: function(link) {
19213
+ links.add(link);
19214
+ link.selected = true;
19215
+ },
19216
+ remove: function(link) {
19217
+ links.delete(link);
19218
+ link.selected = false;
19219
+ },
19220
+ clear: function() {
19221
+ links.forEach(function(link) { link.selected = false })
19222
+ links.clear();
19223
+ },
19224
+ length: function() {
19225
+ return links.size;
19226
+ },
19227
+ forEach: function(func) { links.forEach(func) },
19228
+ has: function(link) { return links.has(link) },
19229
+ toArray: function() { return Array.from(links) }
19230
+ }
19231
+ })();
19232
+
19233
+
18979
19234
  function init() {
18980
19235
 
18981
19236
  chart = $("#red-ui-workspace-chart");
@@ -19010,6 +19265,12 @@ RED.view = (function() {
19010
19265
  }
19011
19266
  } else if (mouse_mode === RED.state.PANNING && d3.event.buttons !== 4) {
19012
19267
  resetMouseVars();
19268
+ } else if (slicePath) {
19269
+ if (d3.event.buttons !== 2) {
19270
+ slicePath.remove();
19271
+ slicePath = null;
19272
+ resetMouseVars()
19273
+ }
19013
19274
  }
19014
19275
  })
19015
19276
  .on("touchend", function() {
@@ -19229,26 +19490,56 @@ RED.view = (function() {
19229
19490
  var historyEvent = result.historyEvent;
19230
19491
  var nn = result.node;
19231
19492
 
19493
+ RED.nodes.add(nn);
19494
+
19232
19495
  var showLabel = RED.utils.getMessageProperty(RED.settings.get('editor'),"view.view-node-show-label");
19233
19496
  if (showLabel !== undefined && (nn._def.hasOwnProperty("showLabel")?nn._def.showLabel:true) && !nn._def.defaults.hasOwnProperty("l")) {
19234
19497
  nn.l = showLabel;
19235
19498
  }
19236
19499
 
19237
19500
  var helperOffset = d3.touches(ui.helper.get(0))[0]||d3.mouse(ui.helper.get(0));
19501
+ var helperWidth = ui.helper.width();
19502
+ var helperHeight = ui.helper.height();
19238
19503
  var mousePos = d3.touches(this)[0]||d3.mouse(this);
19239
19504
 
19240
- mousePos[1] += this.scrollTop + ((nn.h/2)-helperOffset[1]);
19241
- mousePos[0] += this.scrollLeft + ((nn.w/2)-helperOffset[0]);
19505
+ try {
19506
+ var isLink = (nn.type === "link in" || nn.type === "link out")
19507
+ var hideLabel = nn.hasOwnProperty('l')?!nn.l : isLink;
19508
+
19509
+ var label = RED.utils.getNodeLabel(nn, nn.type);
19510
+ var labelParts = getLabelParts(label, "red-ui-flow-node-label");
19511
+ if (hideLabel) {
19512
+ nn.w = node_height;
19513
+ nn.h = Math.max(node_height,(nn.outputs || 0) * 15);
19514
+ } else {
19515
+ nn.w = Math.max(node_width,20*(Math.ceil((labelParts.width+50+(nn._def.inputs>0?7:0))/20)) );
19516
+ nn.h = Math.max(6+24*labelParts.lines.length,(nn.outputs || 0) * 15, 30);
19517
+ }
19518
+ } catch(err) {
19519
+ }
19520
+
19521
+ mousePos[1] += this.scrollTop + ((helperHeight/2)-helperOffset[1]);
19522
+ mousePos[0] += this.scrollLeft + ((helperWidth/2)-helperOffset[0]);
19242
19523
  mousePos[1] /= scaleFactor;
19243
19524
  mousePos[0] /= scaleFactor;
19244
19525
 
19245
- if (snapGrid) {
19246
- mousePos[0] = gridSize*(Math.ceil(mousePos[0]/gridSize));
19247
- mousePos[1] = gridSize*(Math.ceil(mousePos[1]/gridSize));
19248
- }
19249
19526
  nn.x = mousePos[0];
19250
19527
  nn.y = mousePos[1];
19251
19528
 
19529
+ if (snapGrid) {
19530
+ var gridOffset = [0,0];
19531
+ var offsetLeft = nn.x-(gridSize*Math.round((nn.x-nn.w/2)/gridSize)+nn.w/2);
19532
+ var offsetRight = nn.x-(gridSize*Math.round((nn.x+nn.w/2)/gridSize)-nn.w/2);
19533
+ if (Math.abs(offsetLeft) < Math.abs(offsetRight)) {
19534
+ gridOffset[0] = offsetLeft
19535
+ } else {
19536
+ gridOffset[0] = offsetRight
19537
+ }
19538
+ gridOffset[1] = nn.y-(gridSize*Math.round(nn.y/gridSize));
19539
+ nn.x -= gridOffset[0];
19540
+ nn.y -= gridOffset[1];
19541
+ }
19542
+
19252
19543
  var spliceLink = $(ui.helper).data("splice");
19253
19544
  if (spliceLink) {
19254
19545
  // TODO: DRY - droppable/nodeMouseDown/canvasMouseUp/showQuickAddDialog
@@ -19269,7 +19560,6 @@ RED.view = (function() {
19269
19560
  historyEvent.removedLinks = [spliceLink];
19270
19561
  }
19271
19562
 
19272
- RED.nodes.add(nn);
19273
19563
 
19274
19564
  var group = $(ui.helper).data("group");
19275
19565
  if (group) {
@@ -19319,6 +19609,8 @@ RED.view = (function() {
19319
19609
  RED.actions.add("core:cut-selection-to-internal-clipboard",function(){copySelection();deleteSelection();});
19320
19610
  RED.actions.add("core:paste-from-internal-clipboard",function(){importNodes(clipboard,{generateIds: true});});
19321
19611
 
19612
+ RED.actions.add("core:detach-selected-nodes", function() { detachSelectedNodes() })
19613
+
19322
19614
  RED.events.on("view:selection-changed", function(selection) {
19323
19615
  var hasSelection = (selection.nodes && selection.nodes.length > 0);
19324
19616
  var hasMultipleSelection = hasSelection && selection.nodes.length > 1;
@@ -19341,6 +19633,7 @@ RED.view = (function() {
19341
19633
  })
19342
19634
 
19343
19635
  RED.actions.add("core:delete-selection",deleteSelection);
19636
+ RED.actions.add("core:delete-selection-and-reconnect",function() { deleteSelection(true) });
19344
19637
  RED.actions.add("core:edit-selected-node",editSelection);
19345
19638
  RED.actions.add("core:go-to-selection",function() {
19346
19639
  if (movingSet.length() > 0) {
@@ -19419,6 +19712,39 @@ RED.view = (function() {
19419
19712
  show: function(n) { return !n.valid }
19420
19713
  })
19421
19714
 
19715
+ if (RED.settings.get("editor.view.view-store-zoom")) {
19716
+ var userZoomLevel = parseFloat(RED.settings.getLocal('zoom-level'))
19717
+ if (!isNaN(userZoomLevel)) {
19718
+ scaleFactor = userZoomLevel
19719
+ }
19720
+ }
19721
+
19722
+ var onScrollTimer = null;
19723
+ function storeScrollPosition() {
19724
+ workspaceScrollPositions[RED.workspaces.active()] = {
19725
+ left:chart.scrollLeft(),
19726
+ top:chart.scrollTop()
19727
+ };
19728
+ RED.settings.setLocal('scroll-positions', JSON.stringify(workspaceScrollPositions) )
19729
+ }
19730
+ chart.on("scroll", function() {
19731
+ if (RED.settings.get("editor.view.view-store-position")) {
19732
+ if (onScrollTimer) {
19733
+ clearTimeout(onScrollTimer)
19734
+ }
19735
+ onScrollTimer = setTimeout(storeScrollPosition, 200);
19736
+ }
19737
+ })
19738
+
19739
+ if (RED.settings.get("editor.view.view-store-position")) {
19740
+ var scrollPositions = RED.settings.getLocal('scroll-positions')
19741
+ if (scrollPositions) {
19742
+ try {
19743
+ workspaceScrollPositions = JSON.parse(scrollPositions)
19744
+ } catch(err) {
19745
+ }
19746
+ }
19747
+ }
19422
19748
  }
19423
19749
 
19424
19750
 
@@ -19726,7 +20052,7 @@ RED.view = (function() {
19726
20052
  return;
19727
20053
  }
19728
20054
  if (!mousedown_node && !mousedown_link && !mousedown_group) {
19729
- selected_link = null;
20055
+ selectedLinks.clear();
19730
20056
  updateSelection();
19731
20057
  }
19732
20058
  if (mouse_mode === 0) {
@@ -19735,19 +20061,18 @@ RED.view = (function() {
19735
20061
  lasso = null;
19736
20062
  }
19737
20063
  }
19738
- if (mouse_mode === 0 || mouse_mode === RED.state.QUICK_JOINING) {
19739
- if (d3.event.metaKey || d3.event.ctrlKey) {
19740
- d3.event.stopPropagation();
19741
- clearSelection();
19742
- point = d3.mouse(this);
19743
- var clickedGroup = getGroupAt(point[0],point[1]);
19744
- if (drag_lines.length > 0) {
19745
- clickedGroup = clickedGroup || RED.nodes.group(drag_lines[0].node.g)
19746
- }
19747
- showQuickAddDialog({position:point, group:clickedGroup});
20064
+ if ((mouse_mode === 0 || mouse_mode === RED.state.QUICK_JOINING) && (d3.event.touches || d3.event.button === 0) && (d3.event.metaKey || d3.event.ctrlKey)) {
20065
+ // Trigger quick add dialog
20066
+ d3.event.stopPropagation();
20067
+ clearSelection();
20068
+ point = d3.mouse(this);
20069
+ var clickedGroup = getGroupAt(point[0],point[1]);
20070
+ if (drag_lines.length > 0) {
20071
+ clickedGroup = clickedGroup || RED.nodes.group(drag_lines[0].node.g)
19748
20072
  }
19749
- }
19750
- if (mouse_mode === 0 && !(d3.event.metaKey || d3.event.ctrlKey)) {
20073
+ showQuickAddDialog({position:point, group:clickedGroup});
20074
+ } else if (mouse_mode === 0 && (d3.event.touches || d3.event.button === 0) && !(d3.event.metaKey || d3.event.ctrlKey)) {
20075
+ // Tigger lasso
19751
20076
  if (!touchStartTime) {
19752
20077
  point = d3.mouse(this);
19753
20078
  lasso = eventLayer.append("rect")
@@ -19762,6 +20087,13 @@ RED.view = (function() {
19762
20087
  .attr("class","nr-ui-view-lasso");
19763
20088
  d3.event.preventDefault();
19764
20089
  }
20090
+ } else if (mouse_mode === 0 && d3.event.button === 2 && (d3.event.metaKey || d3.event.ctrlKey)) {
20091
+ clearSelection();
20092
+ mouse_mode = RED.state.SLICING;
20093
+ point = d3.mouse(this);
20094
+ slicePath = eventLayer.append("path").attr("class","nr-ui-view-slice").attr("d",`M${point[0]} ${point[1]}`)
20095
+ slicePathLast = point;
20096
+ RED.view.redraw();
19765
20097
  }
19766
20098
  }
19767
20099
 
@@ -20158,6 +20490,17 @@ RED.view = (function() {
20158
20490
  .attr("height",h)
20159
20491
  ;
20160
20492
  return;
20493
+ } else if (mouse_mode === RED.state.SLICING) {
20494
+ if (slicePath) {
20495
+ var delta = Math.max(1,Math.abs(slicePathLast[0]-mouse_position[0]))*Math.max(1,Math.abs(slicePathLast[1]-mouse_position[1]))
20496
+ if (delta > 20) {
20497
+ var currentPath = slicePath.attr("d")
20498
+ currentPath += " L"+mouse_position[0]+" "+mouse_position[1]
20499
+ slicePath.attr("d",currentPath);
20500
+ slicePathLast = mouse_position
20501
+ }
20502
+ }
20503
+ return
20161
20504
  }
20162
20505
 
20163
20506
  if (mouse_mode === RED.state.SELECTING_NODE) {
@@ -20165,7 +20508,7 @@ RED.view = (function() {
20165
20508
  return;
20166
20509
  }
20167
20510
 
20168
- if (mouse_mode != RED.state.QUICK_JOINING && mouse_mode != RED.state.IMPORT_DRAGGING && !mousedown_node && !mousedown_group && selected_link == null) {
20511
+ if (mouse_mode != RED.state.QUICK_JOINING && mouse_mode != RED.state.IMPORT_DRAGGING && mouse_mode != RED.state.DETACHED_DRAGGING && !mousedown_node && !mousedown_group && selectedLinks.length() === 0) {
20169
20512
  return;
20170
20513
  }
20171
20514
 
@@ -20189,16 +20532,18 @@ RED.view = (function() {
20189
20532
  // Get all the wires we need to detach.
20190
20533
  var links = [];
20191
20534
  var existingLinks = [];
20192
- if (selected_link &&
20193
- ((mousedown_port_type === PORT_TYPE_OUTPUT &&
20194
- selected_link.source === mousedown_node &&
20195
- selected_link.sourcePort === mousedown_port_index
20196
- ) ||
20197
- (mousedown_port_type === PORT_TYPE_INPUT &&
20198
- selected_link.target === mousedown_node
20199
- ))
20200
- ) {
20201
- existingLinks = [selected_link];
20535
+ if (selectedLinks.length() > 0) {
20536
+ selectedLinks.forEach(function(link) {
20537
+ if (((mousedown_port_type === PORT_TYPE_OUTPUT &&
20538
+ link.source === mousedown_node &&
20539
+ link.sourcePort === mousedown_port_index
20540
+ ) ||
20541
+ (mousedown_port_type === PORT_TYPE_INPUT &&
20542
+ link.target === mousedown_node
20543
+ ))) {
20544
+ existingLinks.push(link);
20545
+ }
20546
+ })
20202
20547
  } else {
20203
20548
  var filter;
20204
20549
  if (mousedown_port_type === PORT_TYPE_OUTPUT) {
@@ -20236,7 +20581,7 @@ RED.view = (function() {
20236
20581
  } else if (mousedown_node && !quickAddLink) {
20237
20582
  showDragLines([{node:mousedown_node,port:mousedown_port_index,portType:mousedown_port_type}]);
20238
20583
  }
20239
- selected_link = null;
20584
+ selectedLinks.clear();
20240
20585
  }
20241
20586
  mousePos = mouse_position;
20242
20587
  for (i=0;i<drag_lines.length;i++) {
@@ -20268,7 +20613,7 @@ RED.view = (function() {
20268
20613
  RED.nodes.filterLinks({ target: node.n }).length === 0;
20269
20614
  }
20270
20615
  }
20271
- } else if (mouse_mode == RED.state.MOVING_ACTIVE || mouse_mode == RED.state.IMPORT_DRAGGING) {
20616
+ } else if (mouse_mode == RED.state.MOVING_ACTIVE || mouse_mode == RED.state.IMPORT_DRAGGING || mouse_mode == RED.state.DETACHED_DRAGGING) {
20272
20617
  mousePos = mouse_position;
20273
20618
  var minX = 0;
20274
20619
  var minY = 0;
@@ -20332,8 +20677,16 @@ RED.view = (function() {
20332
20677
  gridOffset[0] = node.n.x-(gridSize*Math.floor(node.n.x/gridSize))-gridSize/2;
20333
20678
  gridOffset[1] = node.n.y-(gridSize*Math.floor(node.n.y/gridSize))-gridSize/2;
20334
20679
  } else {
20335
- gridOffset[0] = node.n.x-(gridSize*Math.floor((node.n.x-node.n.w/2)/gridSize)+node.n.w/2);
20336
- gridOffset[1] = node.n.y-(gridSize*Math.floor(node.n.y/gridSize));
20680
+ var offsetLeft = node.n.x-(gridSize*Math.round((node.n.x-node.n.w/2)/gridSize)+node.n.w/2);
20681
+ var offsetRight = node.n.x-(gridSize*Math.round((node.n.x+node.n.w/2)/gridSize)-node.n.w/2);
20682
+ // gridOffset[0] = node.n.x-(gridSize*Math.floor((node.n.x-node.n.w/2)/gridSize)+node.n.w/2);
20683
+ if (Math.abs(offsetLeft) < Math.abs(offsetRight)) {
20684
+ gridOffset[0] = offsetLeft
20685
+ } else {
20686
+ gridOffset[0] = offsetRight
20687
+ }
20688
+ gridOffset[1] = node.n.y-(gridSize*Math.round(node.n.y/gridSize));
20689
+ // console.log(offsetLeft, offsetRight);
20337
20690
  }
20338
20691
  if (gridOffset[0] !== 0 || gridOffset[1] !== 0) {
20339
20692
  for (i = 0; i<movingSet.length(); i++) {
@@ -20553,6 +20906,11 @@ RED.view = (function() {
20553
20906
  } else if (mouse_mode == RED.state.DEFAULT && mousedown_link == null && !d3.event.ctrlKey && !d3.event.metaKey ) {
20554
20907
  clearSelection();
20555
20908
  updateSelection();
20909
+ } else if (slicePath) {
20910
+ deleteSelection();
20911
+ slicePath.remove();
20912
+ slicePath = null;
20913
+ RED.view.redraw(true);
20556
20914
  }
20557
20915
  if (mouse_mode == RED.state.MOVING_ACTIVE) {
20558
20916
  if (movingSet.length() > 0) {
@@ -20624,10 +20982,30 @@ RED.view = (function() {
20624
20982
  // movingSet.add(mousedown_node);
20625
20983
  // }
20626
20984
  // }
20627
- if (mouse_mode == RED.state.MOVING || mouse_mode == RED.state.MOVING_ACTIVE) {
20985
+ if (mouse_mode == RED.state.MOVING || mouse_mode == RED.state.MOVING_ACTIVE || mouse_mode == RED.state.DETACHED_DRAGGING) {
20628
20986
  // if (mousedown_node) {
20629
20987
  // delete mousedown_node.gSelected;
20630
20988
  // }
20989
+ if (mouse_mode === RED.state.DETACHED_DRAGGING) {
20990
+ var ns = [];
20991
+ for (var j=0;j<movingSet.length();j++) {
20992
+ var n = movingSet.get(j);
20993
+ if (n.ox !== n.n.x || n.oy !== n.n.y) {
20994
+ ns.push({n:n.n,ox:n.ox,oy:n.oy,moved:n.n.moved});
20995
+ n.n.dirty = true;
20996
+ n.n.moved = true;
20997
+ }
20998
+ }
20999
+ var detachEvent = RED.history.peek();
21000
+ // The last event in the stack *should* be a multi-event from
21001
+ // where the links were added/removed
21002
+ var historyEvent = {t:"move",nodes:ns,dirty:RED.nodes.dirty()}
21003
+ if (detachEvent.t === "multi") {
21004
+ detachEvent.events.push(historyEvent)
21005
+ } else {
21006
+ RED.history.push(historyEvent)
21007
+ }
21008
+ }
20631
21009
  for (i=0;i<movingSet.length();i++) {
20632
21010
  var node = movingSet.get(i);
20633
21011
  delete node.ox;
@@ -20654,6 +21032,7 @@ RED.view = (function() {
20654
21032
  }
20655
21033
  function zoomZero() { zoomView(1); }
20656
21034
 
21035
+
20657
21036
  function zoomView(factor) {
20658
21037
  var screenSize = [chart.width(),chart.height()];
20659
21038
  var scrollPos = [chart.scrollLeft(),chart.scrollTop()];
@@ -20666,16 +21045,38 @@ RED.view = (function() {
20666
21045
 
20667
21046
  RED.view.navigator.resize();
20668
21047
  redraw();
21048
+ if (RED.settings.get("editor.view.view-store-zoom")) {
21049
+ RED.settings.setLocal('zoom-level', factor.toFixed(1))
21050
+ }
20669
21051
  }
20670
21052
 
20671
21053
  function selectNone() {
20672
21054
  if (mouse_mode === RED.state.MOVING || mouse_mode === RED.state.MOVING_ACTIVE) {
20673
21055
  return;
20674
21056
  }
20675
- if (mouse_mode === RED.state.IMPORT_DRAGGING) {
21057
+ if (mouse_mode === RED.state.DETACHED_DRAGGING) {
21058
+ for (var j=0;j<movingSet.length();j++) {
21059
+ var n = movingSet.get(j);
21060
+ n.n.x = n.ox;
21061
+ n.n.y = n.oy;
21062
+ }
20676
21063
  clearSelection();
20677
21064
  RED.history.pop();
20678
21065
  mouse_mode = 0;
21066
+ } else if (mouse_mode === RED.state.IMPORT_DRAGGING) {
21067
+ clearSelection();
21068
+ RED.history.pop();
21069
+ mouse_mode = 0;
21070
+ } else if (mouse_mode === RED.state.SLICING) {
21071
+ if (slicePath) {
21072
+ slicePath.remove();
21073
+ slicePath = null;
21074
+ resetMouseVars()
21075
+ }
21076
+ clearSelection();
21077
+ } else if (lasso) {
21078
+ lasso.remove();
21079
+ lasso = null;
20679
21080
  } else if (activeGroup) {
20680
21081
  exitActiveGroup()
20681
21082
  } else {
@@ -20687,6 +21088,7 @@ RED.view = (function() {
20687
21088
  if (mouse_mode === RED.state.SELECTING_NODE && selectNodesOptions.single) {
20688
21089
  return;
20689
21090
  }
21091
+ selectedLinks.clear();
20690
21092
 
20691
21093
  if (activeGroup) {
20692
21094
  var ag = activeGroup;
@@ -20758,7 +21160,6 @@ RED.view = (function() {
20758
21160
  }
20759
21161
  }
20760
21162
  }
20761
- selected_link = null;
20762
21163
  if (mouse_mode !== RED.state.SELECTING_NODE) {
20763
21164
  updateSelection();
20764
21165
  }
@@ -20773,7 +21174,7 @@ RED.view = (function() {
20773
21174
  n.n.selected = false;
20774
21175
  }
20775
21176
  movingSet.clear();
20776
- selected_link = null;
21177
+ selectedLinks.clear();
20777
21178
  if (activeGroup) {
20778
21179
  activeGroup.active = false
20779
21180
  activeGroup.dirty = true;
@@ -20867,12 +21268,16 @@ RED.view = (function() {
20867
21268
  }
20868
21269
  }
20869
21270
  }
20870
- if (activeFlowLinks.length === 0 && selected_link !== null && selected_link.link) {
20871
- activeLinks.push(selected_link);
20872
- activeLinkNodes[selected_link.source.id] = selected_link.source;
20873
- selected_link.source.dirty = true;
20874
- activeLinkNodes[selected_link.target.id] = selected_link.target;
20875
- selected_link.target.dirty = true;
21271
+ if (activeFlowLinks.length === 0 && selectedLinks.length() > 0) {
21272
+ selectedLinks.forEach(function(link) {
21273
+ if (link.link) {
21274
+ activeLinks.push(link);
21275
+ activeLinkNodes[link.source.id] = link.source;
21276
+ link.source.dirty = true;
21277
+ activeLinkNodes[link.target.id] = link.target;
21278
+ link.target.dirty = true;
21279
+ }
21280
+ })
20876
21281
  }
20877
21282
  } else {
20878
21283
  selection.flows = workspaceSelection;
@@ -20883,6 +21288,10 @@ RED.view = (function() {
20883
21288
  return value.map(function(n) { return n.id })
20884
21289
  } else if (key === 'link') {
20885
21290
  return value.source.id+":"+value.sourcePort+":"+value.target.id;
21291
+ } else if (key === 'links') {
21292
+ return value.map(function(link) {
21293
+ return link.source.id+":"+link.sourcePort+":"+link.target.id;
21294
+ });
20886
21295
  }
20887
21296
  return value;
20888
21297
  });
@@ -20904,7 +21313,7 @@ RED.view = (function() {
20904
21313
  }
20905
21314
  }
20906
21315
  }
20907
- function deleteSelection() {
21316
+ function deleteSelection(reconnectWires) {
20908
21317
  if (mouse_mode === RED.state.SELECTING_NODE) {
20909
21318
  return;
20910
21319
  }
@@ -20952,7 +21361,7 @@ RED.view = (function() {
20952
21361
  updateActiveNodes();
20953
21362
  updateSelection();
20954
21363
  redraw();
20955
- } else if (movingSet.length() > 0 || selected_link != null) {
21364
+ } else if (movingSet.length() > 0 || selectedLinks.length() > 0) {
20956
21365
  var result;
20957
21366
  var node;
20958
21367
  var removedNodes = [];
@@ -20962,6 +21371,23 @@ RED.view = (function() {
20962
21371
  var removedSubflowInputs = [];
20963
21372
  var removedSubflowStatus;
20964
21373
  var subflowInstances = [];
21374
+ var historyEvents = [];
21375
+ var addToRemovedLinks = function(links) {
21376
+ if(!links) { return; }
21377
+ var _links = Array.isArray(links) ? links : [links];
21378
+ _links.forEach(function(l) {
21379
+ removedLinks.push(l);
21380
+ selectedLinks.remove(l);
21381
+ })
21382
+ }
21383
+ if (reconnectWires) {
21384
+ var reconnectResult = RED.nodes.detachNodes(movingSet.nodes())
21385
+ var addedLinks = reconnectResult.newLinks;
21386
+ if (addedLinks.length > 0) {
21387
+ historyEvents.push({ t:'add', links: addedLinks })
21388
+ }
21389
+ addToRemovedLinks(reconnectResult.removedLinks)
21390
+ }
20965
21391
 
20966
21392
  var startDirty = RED.nodes.dirty();
20967
21393
  var startChanged = false;
@@ -20992,7 +21418,7 @@ RED.view = (function() {
20992
21418
  var removedEntities = RED.nodes.remove(node.id);
20993
21419
  removedNodes.push(node);
20994
21420
  removedNodes = removedNodes.concat(removedEntities.nodes);
20995
- removedLinks = removedLinks.concat(removedEntities.links);
21421
+ addToRemovedLinks(removedNodes.removedLinks);
20996
21422
  if (node.g) {
20997
21423
  var group = RED.nodes.group(node.g);
20998
21424
  if (selectedGroups.indexOf(group) === -1) {
@@ -21025,20 +21451,20 @@ RED.view = (function() {
21025
21451
  if (removedSubflowOutputs.length > 0) {
21026
21452
  result = RED.subflow.removeOutput(removedSubflowOutputs);
21027
21453
  if (result) {
21028
- removedLinks = removedLinks.concat(result.links);
21454
+ addToRemovedLinks(result.links);
21029
21455
  }
21030
21456
  }
21031
21457
  // Assume 0/1 inputs
21032
21458
  if (removedSubflowInputs.length == 1) {
21033
21459
  result = RED.subflow.removeInput();
21034
21460
  if (result) {
21035
- removedLinks = removedLinks.concat(result.links);
21461
+ addToRemovedLinks(result.links);
21036
21462
  }
21037
21463
  }
21038
21464
  if (removedSubflowStatus) {
21039
21465
  result = RED.subflow.removeStatus();
21040
21466
  if (result) {
21041
- removedLinks = removedLinks.concat(result.links);
21467
+ addToRemovedLinks(result.links);
21042
21468
  }
21043
21469
  }
21044
21470
 
@@ -21051,71 +21477,71 @@ RED.view = (function() {
21051
21477
  RED.nodes.dirty(true);
21052
21478
  }
21053
21479
  }
21054
- var historyEvent;
21055
21480
 
21056
- if (selected_link && selected_link.link) {
21057
- var sourceId = selected_link.source.id;
21058
- var targetId = selected_link.target.id;
21059
- var sourceIdIndex = selected_link.target.links.indexOf(sourceId);
21060
- var targetIdIndex = selected_link.source.links.indexOf(targetId);
21061
-
21062
- historyEvent = {
21063
- t:"multi",
21064
- events: [
21065
- {
21481
+ if (selectedLinks.length() > 0) {
21482
+ selectedLinks.forEach(function(link) {
21483
+ if (link.link) {
21484
+ var sourceId = link.source.id;
21485
+ var targetId = link.target.id;
21486
+ var sourceIdIndex = link.target.links.indexOf(sourceId);
21487
+ var targetIdIndex = link.source.links.indexOf(targetId);
21488
+ historyEvents.push({
21066
21489
  t: "edit",
21067
- node: selected_link.source,
21068
- changed: selected_link.source.changed,
21490
+ node: link.source,
21491
+ changed: link.source.changed,
21069
21492
  changes: {
21070
- links: $.extend(true,{},{v:selected_link.source.links}).v
21493
+ links: $.extend(true,{},{v:link.source.links}).v
21071
21494
  }
21072
- },
21073
- {
21495
+ })
21496
+ historyEvents.push({
21074
21497
  t: "edit",
21075
- node: selected_link.target,
21076
- changed: selected_link.target.changed,
21498
+ node: link.target,
21499
+ changed: link.target.changed,
21077
21500
  changes: {
21078
- links: $.extend(true,{},{v:selected_link.target.links}).v
21501
+ links: $.extend(true,{},{v:link.target.links}).v
21079
21502
  }
21080
- }
21081
-
21082
- ],
21083
- dirty:RED.nodes.dirty()
21084
- }
21085
- RED.nodes.dirty(true);
21086
- selected_link.source.changed = true;
21087
- selected_link.target.changed = true;
21088
- selected_link.target.links.splice(sourceIdIndex,1);
21089
- selected_link.source.links.splice(targetIdIndex,1);
21090
- selected_link.source.dirty = true;
21091
- selected_link.target.dirty = true;
21503
+ })
21504
+ link.source.changed = true;
21505
+ link.target.changed = true;
21506
+ link.target.links.splice(sourceIdIndex,1);
21507
+ link.source.links.splice(targetIdIndex,1);
21508
+ link.source.dirty = true;
21509
+ link.target.dirty = true;
21092
21510
 
21511
+ } else {
21512
+ RED.nodes.removeLink(link);
21513
+ removedLinks.push(link);
21514
+ }
21515
+ })
21516
+ }
21517
+ RED.nodes.dirty(true);
21518
+ var historyEvent = {
21519
+ t:"delete",
21520
+ nodes:removedNodes,
21521
+ links:removedLinks,
21522
+ groups: removedGroups,
21523
+ subflowOutputs:removedSubflowOutputs,
21524
+ subflowInputs:removedSubflowInputs,
21525
+ subflow: {
21526
+ id: activeSubflow?activeSubflow.id:undefined,
21527
+ instances: subflowInstances
21528
+ },
21529
+ dirty:startDirty
21530
+ };
21531
+ if (removedSubflowStatus) {
21532
+ historyEvent.subflow.status = removedSubflowStatus;
21533
+ }
21534
+ if (historyEvents.length > 0) {
21535
+ historyEvents.unshift(historyEvent);
21536
+ RED.history.push({
21537
+ t:"multi",
21538
+ events: historyEvents
21539
+ })
21093
21540
  } else {
21094
- if (selected_link) {
21095
- RED.nodes.removeLink(selected_link);
21096
- removedLinks.push(selected_link);
21097
- }
21098
- RED.nodes.dirty(true);
21099
- historyEvent = {
21100
- t:"delete",
21101
- nodes:removedNodes,
21102
- links:removedLinks,
21103
- groups: removedGroups,
21104
- subflowOutputs:removedSubflowOutputs,
21105
- subflowInputs:removedSubflowInputs,
21106
- subflow: {
21107
- id: activeSubflow?activeSubflow.id:undefined,
21108
- instances: subflowInstances
21109
- },
21110
- dirty:startDirty
21111
- };
21112
- if (removedSubflowStatus) {
21113
- historyEvent.subflow.status = removedSubflowStatus;
21114
- }
21541
+ RED.history.push(historyEvent);
21115
21542
  }
21116
- RED.history.push(historyEvent);
21117
21543
 
21118
- selected_link = null;
21544
+ selectedLinks.clear();
21119
21545
  updateActiveNodes();
21120
21546
  updateSelection();
21121
21547
  redraw();
@@ -21192,6 +21618,28 @@ RED.view = (function() {
21192
21618
  }
21193
21619
  }
21194
21620
 
21621
+
21622
+ function detachSelectedNodes() {
21623
+ var selection = RED.view.selection();
21624
+ if (selection.nodes) {
21625
+ const {newLinks, removedLinks} = RED.nodes.detachNodes(selection.nodes);
21626
+ if (removedLinks.length || newLinks.length) {
21627
+ RED.history.push({
21628
+ t: "multi",
21629
+ events: [
21630
+ { t:'delete', links: removedLinks },
21631
+ { t:'add', links: newLinks }
21632
+ ],
21633
+ dirty: RED.nodes.dirty()
21634
+ })
21635
+ RED.nodes.dirty(true)
21636
+ }
21637
+ prepareDrag([selection.nodes[0].x,selection.nodes[0].y]);
21638
+ mouse_mode = RED.state.DETACHED_DRAGGING;
21639
+ RED.view.redraw(true);
21640
+ }
21641
+ }
21642
+
21195
21643
  function calculateTextWidth(str, className) {
21196
21644
  var result = convertLineBreakCharacter(str);
21197
21645
  var width = 0;
@@ -21278,7 +21726,7 @@ RED.view = (function() {
21278
21726
  activeHoverGroup.hovered = false;
21279
21727
  activeHoverGroup = null;
21280
21728
  }
21281
- d3.select(".red-ui-flow-link-splice").classed("red-ui-flow-link-splice",false);
21729
+ d3.selectAll(".red-ui-flow-link-splice").classed("red-ui-flow-link-splice",false);
21282
21730
  if (spliceTimer) {
21283
21731
  clearTimeout(spliceTimer);
21284
21732
  spliceTimer = null;
@@ -21526,10 +21974,13 @@ RED.view = (function() {
21526
21974
  } else {
21527
21975
  resetMouseVars();
21528
21976
  }
21529
- selected_link = select_link;
21530
21977
  mousedown_link = select_link;
21531
21978
  if (select_link) {
21979
+ selectedLinks.clear();
21980
+ selectedLinks.add(select_link);
21532
21981
  updateSelection();
21982
+ } else {
21983
+ selectedLinks.clear();
21533
21984
  }
21534
21985
  }
21535
21986
  redraw();
@@ -21538,7 +21989,10 @@ RED.view = (function() {
21538
21989
 
21539
21990
  resetMouseVars();
21540
21991
  hideDragLines();
21541
- selected_link = select_link;
21992
+ if (select_link) {
21993
+ selectedLinks.clear();
21994
+ selectedLinks.add(select_link);
21995
+ }
21542
21996
  mousedown_link = select_link;
21543
21997
  if (select_link) {
21544
21998
  updateSelection();
@@ -21711,10 +22165,13 @@ RED.view = (function() {
21711
22165
  msn.dx = msn.n.x-mouse[0];
21712
22166
  msn.dy = msn.n.y-mouse[1];
21713
22167
  }
21714
-
21715
- mouse_offset = d3.mouse(document.body);
21716
- if (isNaN(mouse_offset[0])) {
21717
- mouse_offset = d3.touches(document.body)[0];
22168
+ try {
22169
+ mouse_offset = d3.mouse(document.body);
22170
+ if (isNaN(mouse_offset[0])) {
22171
+ mouse_offset = d3.touches(document.body)[0];
22172
+ }
22173
+ } catch(err) {
22174
+ mouse_offset = [0,0]
21718
22175
  }
21719
22176
  }
21720
22177
 
@@ -21795,7 +22252,7 @@ RED.view = (function() {
21795
22252
  //var touch0 = d3.event;
21796
22253
  //var pos = [touch0.pageX,touch0.pageY];
21797
22254
  //RED.touch.radialMenu.show(d3.select(this),pos);
21798
- if (mouse_mode == RED.state.IMPORT_DRAGGING) {
22255
+ if (mouse_mode == RED.state.IMPORT_DRAGGING || mouse_mode == RED.state.DETACHED_DRAGGING) {
21799
22256
  var historyEvent = RED.history.peek();
21800
22257
  if (activeSpliceLink) {
21801
22258
  // TODO: DRY - droppable/nodeMouseDown/canvasMouseUp
@@ -21833,6 +22290,18 @@ RED.view = (function() {
21833
22290
  activeHoverGroup = null;
21834
22291
  }
21835
22292
 
22293
+ if (mouse_mode == RED.state.DETACHED_DRAGGING) {
22294
+ var ns = [];
22295
+ for (var j=0;j<movingSet.length();j++) {
22296
+ var n = movingSet.get(j);
22297
+ if (n.ox !== n.n.x || n.oy !== n.n.y) {
22298
+ ns.push({n:n.n,ox:n.ox,oy:n.oy,moved:n.n.moved});
22299
+ n.n.dirty = true;
22300
+ n.n.moved = true;
22301
+ }
22302
+ }
22303
+ RED.history.replace({t:"multi",events:[historyEvent,{t:"move",nodes:ns}],dirty: historyEvent.dirty})
22304
+ }
21836
22305
 
21837
22306
  updateSelection();
21838
22307
  RED.nodes.dirty(true);
@@ -22001,7 +22470,9 @@ RED.view = (function() {
22001
22470
  // }
22002
22471
  // } else
22003
22472
  if (d3.event.shiftKey) {
22004
- clearSelection();
22473
+ if (!(d3.event.ctrlKey||d3.event.metaKey)) {
22474
+ clearSelection();
22475
+ }
22005
22476
  var clickPosition = (d3.event.offsetX/scaleFactor - mousedown_node.x)
22006
22477
  var edgeDelta = (mousedown_node.w/2) - Math.abs(clickPosition);
22007
22478
  var cnodes;
@@ -22029,7 +22500,7 @@ RED.view = (function() {
22029
22500
  mousedown_node.selected = true;
22030
22501
  movingSet.add(mousedown_node);
22031
22502
  }
22032
- selected_link = null;
22503
+ // selectedLinks.clear();
22033
22504
  if (d3.event.button != 2) {
22034
22505
  mouse_mode = RED.state.MOVING;
22035
22506
  var mouse = d3.touches(this)[0]||d3.mouse(this);
@@ -22155,19 +22626,35 @@ RED.view = (function() {
22155
22626
  d3.event.stopPropagation();
22156
22627
  return;
22157
22628
  }
22158
- mousedown_link = d;
22159
- clearSelection();
22160
- selected_link = mousedown_link;
22161
- updateSelection();
22162
- redraw();
22163
- focusView();
22164
- d3.event.stopPropagation();
22165
- if (d3.event.metaKey || d3.event.ctrlKey) {
22166
- d3.select(this).classed("red-ui-flow-link-splice",true);
22167
- var point = d3.mouse(this);
22168
- var clickedGroup = getGroupAt(point[0],point[1]);
22169
- showQuickAddDialog({position:point, splice:selected_link, group:clickedGroup});
22170
- }
22629
+ if (d3.event.button === 2) {
22630
+ return
22631
+ }
22632
+ mousedown_link = d;
22633
+
22634
+ if (!(d3.event.metaKey || d3.event.ctrlKey)) {
22635
+ clearSelection();
22636
+ }
22637
+ if (d3.event.metaKey || d3.event.ctrlKey) {
22638
+ if (!selectedLinks.has(mousedown_link)) {
22639
+ selectedLinks.add(mousedown_link);
22640
+ } else {
22641
+ if (selectedLinks.length() !== 1) {
22642
+ selectedLinks.remove(mousedown_link);
22643
+ }
22644
+ }
22645
+ } else {
22646
+ selectedLinks.add(mousedown_link);
22647
+ }
22648
+ updateSelection();
22649
+ redraw();
22650
+ focusView();
22651
+ d3.event.stopPropagation();
22652
+ if (!mousedown_link.link && movingSet.length() === 0 && (d3.event.touches || d3.event.button === 0) && selectedLinks.length() === 1 && selectedLinks.has(mousedown_link) && (d3.event.metaKey || d3.event.ctrlKey)) {
22653
+ d3.select(this).classed("red-ui-flow-link-splice",true);
22654
+ var point = d3.mouse(this);
22655
+ var clickedGroup = getGroupAt(point[0],point[1]);
22656
+ showQuickAddDialog({position:point, splice:mousedown_link, group:clickedGroup});
22657
+ }
22171
22658
  }
22172
22659
  function linkTouchStart(d) {
22173
22660
  if (mouse_mode === RED.state.SELECTING_NODE) {
@@ -22176,7 +22663,8 @@ RED.view = (function() {
22176
22663
  }
22177
22664
  mousedown_link = d;
22178
22665
  clearSelection();
22179
- selected_link = mousedown_link;
22666
+ selectedLinks.clear();
22667
+ selectedLinks.add(mousedown_link);
22180
22668
  updateSelection();
22181
22669
  redraw();
22182
22670
  focusView();
@@ -22412,7 +22900,7 @@ RED.view = (function() {
22412
22900
  function showTouchMenu(obj,pos) {
22413
22901
  var mdn = mousedown_node;
22414
22902
  var options = [];
22415
- options.push({name:"delete",disabled:(movingSet.length()===0 && selected_link === null),onselect:function() {deleteSelection();}});
22903
+ options.push({name:"delete",disabled:(movingSet.length()===0 && selectedLinks.length() === 0),onselect:function() {deleteSelection();}});
22416
22904
  options.push({name:"cut",disabled:(movingSet.length()===0),onselect:function() {copySelection();deleteSelection();}});
22417
22905
  options.push({name:"copy",disabled:(movingSet.length()===0),onselect:function() {copySelection();}});
22418
22906
  options.push({name:"paste",disabled:(clipboard.length===0),onselect:function() {importNodes(clipboard, {generateIds: true, touchImport: true});}});
@@ -22545,28 +23033,89 @@ RED.view = (function() {
22545
23033
  if (activeSubflow) {
22546
23034
  var subflowOutputs = nodeLayer.selectAll(".red-ui-flow-subflow-port-output").data(activeSubflow.out,function(d,i){ return d.id;});
22547
23035
  subflowOutputs.exit().remove();
22548
- var outGroup = subflowOutputs.enter().insert("svg:g").attr("class","red-ui-flow-node red-ui-flow-subflow-port-output").attr("transform",function(d) { return "translate("+(d.x-20)+","+(d.y-20)+")"});
23036
+ var outGroup = subflowOutputs.enter().insert("svg:g").attr("class","red-ui-flow-node red-ui-flow-subflow-port-output")
22549
23037
  outGroup.each(function(d,i) {
22550
- d.w=40;
22551
- d.h=40;
23038
+ var node = d3.select(this);
23039
+ var nodeContents = document.createDocumentFragment();
23040
+
23041
+ d.h = 40;
23042
+ d.resize = true;
23043
+ d.dirty = true;
23044
+
23045
+ var mainRect = document.createElementNS("http://www.w3.org/2000/svg","rect");
23046
+ mainRect.__data__ = d;
23047
+ mainRect.setAttribute("class", "red-ui-flow-subflow-port");
23048
+ mainRect.setAttribute("rx", 8);
23049
+ mainRect.setAttribute("ry", 8);
23050
+ mainRect.setAttribute("width", 40);
23051
+ mainRect.setAttribute("height", 40);
23052
+ node[0][0].__mainRect__ = mainRect;
23053
+ d3.select(mainRect)
23054
+ .on("mouseup",nodeMouseUp)
23055
+ .on("mousedown",nodeMouseDown)
23056
+ .on("touchstart",nodeTouchStart)
23057
+ .on("touchend",nodeTouchEnd)
23058
+ nodeContents.appendChild(mainRect);
23059
+
23060
+ var output_groupEl = document.createElementNS("http://www.w3.org/2000/svg","g");
23061
+ output_groupEl.setAttribute("x",0);
23062
+ output_groupEl.setAttribute("y",0);
23063
+ node[0][0].__outputLabelGroup__ = output_groupEl;
23064
+
23065
+ var output_output = document.createElementNS("http://www.w3.org/2000/svg","text");
23066
+ output_output.setAttribute("class","red-ui-flow-port-label");
23067
+ output_output.style["font-size"] = "10px";
23068
+ output_output.textContent = "output";
23069
+ output_groupEl.appendChild(output_output);
23070
+ node[0][0].__outputOutput__ = output_output;
23071
+
23072
+ var output_number = document.createElementNS("http://www.w3.org/2000/svg","text");
23073
+ output_number.setAttribute("class","red-ui-flow-port-label red-ui-flow-port-index");
23074
+ output_number.setAttribute("x",0);
23075
+ output_number.setAttribute("y",0);
23076
+ output_number.textContent = d.i+1;
23077
+ output_groupEl.appendChild(output_number);
23078
+ node[0][0].__outputNumber__ = output_number;
23079
+
23080
+ var output_border = document.createElementNS("http://www.w3.org/2000/svg","path");
23081
+ output_border.setAttribute("d","M 40 1 l 0 38")
23082
+ output_border.setAttribute("class", "red-ui-flow-node-icon-shade-border")
23083
+ output_groupEl.appendChild(output_border);
23084
+ node[0][0].__outputBorder__ = output_border;
23085
+
23086
+ nodeContents.appendChild(output_groupEl);
23087
+
23088
+ var text = document.createElementNS("http://www.w3.org/2000/svg","g");
23089
+ text.setAttribute("class","red-ui-flow-port-label");
23090
+ text.setAttribute("transform","translate(38,0)");
23091
+ text.setAttribute('style', 'fill : #888'); // hard coded here!
23092
+ node[0][0].__textGroup__ = text;
23093
+ nodeContents.append(text);
23094
+
23095
+ var portEl = document.createElementNS("http://www.w3.org/2000/svg","g");
23096
+ portEl.setAttribute('transform','translate(-5,15)')
23097
+
23098
+ var port = document.createElementNS("http://www.w3.org/2000/svg","rect");
23099
+ port.setAttribute("class","red-ui-flow-port");
23100
+ port.setAttribute("rx",3);
23101
+ port.setAttribute("ry",3);
23102
+ port.setAttribute("width",10);
23103
+ port.setAttribute("height",10);
23104
+ portEl.appendChild(port);
23105
+ port.__data__ = d;
23106
+
23107
+ d3.select(port)
23108
+ .on("mousedown", function(d,i){portMouseDown(d,PORT_TYPE_INPUT,0);} )
23109
+ .on("touchstart", function(d,i){portMouseDown(d,PORT_TYPE_INPUT,0);d3.event.preventDefault();} )
23110
+ .on("mouseup", function(d,i){portMouseUp(d,PORT_TYPE_INPUT,0);})
23111
+ .on("touchend",function(d,i){portMouseUp(d,PORT_TYPE_INPUT,0);d3.event.preventDefault();} )
23112
+ .on("mouseover",function(d){portMouseOver(d3.select(this),d,PORT_TYPE_INPUT,0);})
23113
+ .on("mouseout",function(d){portMouseOut(d3.select(this),d,PORT_TYPE_INPUT,0);});
23114
+
23115
+ node[0][0].__port__ = portEl
23116
+ nodeContents.appendChild(portEl);
23117
+ node[0][0].appendChild(nodeContents);
22552
23118
  });
22553
- outGroup.append("rect").attr("class","red-ui-flow-subflow-port").attr("rx",8).attr("ry",8).attr("width",40).attr("height",40)
22554
- // TODO: This is exactly the same set of handlers used for regular nodes - DRY
22555
- .on("mouseup",nodeMouseUp)
22556
- .on("mousedown",nodeMouseDown)
22557
- .on("touchstart",nodeTouchStart)
22558
- .on("touchend",nodeTouchEnd)
22559
-
22560
- outGroup.append("g").attr('transform','translate(-5,15)').append("rect").attr("class","red-ui-flow-port").attr("rx",3).attr("ry",3).attr("width",10).attr("height",10)
22561
- .on("mousedown", function(d,i){portMouseDown(d,PORT_TYPE_INPUT,0);} )
22562
- .on("touchstart", function(d,i){portMouseDown(d,PORT_TYPE_INPUT,0);d3.event.preventDefault();} )
22563
- .on("mouseup", function(d,i){portMouseUp(d,PORT_TYPE_INPUT,0);})
22564
- .on("touchend",function(d,i){portMouseUp(d,PORT_TYPE_INPUT,0);d3.event.preventDefault();} )
22565
- .on("mouseover",function(d){portMouseOver(d3.select(this),d,PORT_TYPE_INPUT,0);})
22566
- .on("mouseout",function(d){portMouseOut(d3.select(this),d,PORT_TYPE_INPUT,0);});
22567
-
22568
- outGroup.append("svg:text").attr("class","red-ui-flow-port-label").attr("x",20).attr("y",12).style("font-size","10px").text("output");
22569
- outGroup.append("svg:text").attr("class","red-ui-flow-port-label red-ui-flow-port-index").attr("x",20).attr("y",28).text(function(d,i){ return i+1});
22570
23119
 
22571
23120
  var subflowInputs = nodeLayer.selectAll(".red-ui-flow-subflow-port-input").data(activeSubflow.in,function(d,i){ return d.id;});
22572
23121
  subflowInputs.exit().remove();
@@ -22619,11 +23168,89 @@ RED.view = (function() {
22619
23168
 
22620
23169
  subflowOutputs.each(function(d,i) {
22621
23170
  if (d.dirty) {
22622
- var output = d3.select(this);
22623
- output.classed("red-ui-flow-node-selected",function(d) { return d.selected; })
22624
- output.selectAll(".red-ui-flow-port-index").text(function(d){ return d.i+1});
22625
- output.attr("transform", function(d) { return "translate(" + (d.x-d.w/2) + "," + (d.y-d.h/2) + ")"; });
23171
+
23172
+ var port_height = 40;
23173
+
23174
+ var self = this;
23175
+ var thisNode = d3.select(this);
23176
+
22626
23177
  dirtyNodes[d.id] = d;
23178
+
23179
+ var label = getPortLabel(activeSubflow, PORT_TYPE_OUTPUT, d.i) || "";
23180
+ var hideLabel = (label.length < 1)
23181
+
23182
+ var labelParts;
23183
+ if (d.resize || this.__hideLabel__ !== hideLabel || this.__label__ !== label) {
23184
+ labelParts = getLabelParts(label, "red-ui-flow-node-label");
23185
+ if (labelParts.lines.length !== this.__labelLineCount__ || this.__label__ !== label) {
23186
+ d.resize = true;
23187
+ }
23188
+ this.__label__ = label;
23189
+ this.__labelLineCount__ = labelParts.lines.length;
23190
+
23191
+ if (hideLabel) {
23192
+ d.h = Math.max(port_height,(d.outputs || 0) * 15);
23193
+ } else {
23194
+ d.h = Math.max(6+24*labelParts.lines.length,(d.outputs || 0) * 15, port_height);
23195
+ }
23196
+ this.__hideLabel__ = hideLabel;
23197
+ }
23198
+
23199
+ if (d.resize) {
23200
+ var ow = d.w;
23201
+ if (hideLabel) {
23202
+ d.w = port_height;
23203
+ } else {
23204
+ d.w = Math.max(port_height,20*(Math.ceil((labelParts.width+50+7)/20)) );
23205
+ }
23206
+ if (ow !== undefined) {
23207
+ d.x += (d.w-ow)/2;
23208
+ }
23209
+ d.resize = false;
23210
+ }
23211
+
23212
+ this.setAttribute("transform", "translate(" + (d.x-d.w/2) + "," + (d.y-d.h/2) + ")");
23213
+ // This might be the first redraw after a node has been click-dragged to start a move.
23214
+ // So its selected state might have changed since the last redraw.
23215
+ this.classList.toggle("red-ui-flow-node-selected", !!d.selected )
23216
+ if (mouse_mode != RED.state.MOVING_ACTIVE) {
23217
+ this.classList.toggle("red-ui-flow-node-disabled", d.d === true);
23218
+ this.__mainRect__.setAttribute("width", d.w)
23219
+ this.__mainRect__.setAttribute("height", d.h)
23220
+ this.__mainRect__.classList.toggle("red-ui-flow-node-highlighted",!!d.highlighted );
23221
+
23222
+ if (labelParts) {
23223
+ // The label has changed
23224
+ var sa = labelParts.lines;
23225
+ var sn = labelParts.lines.length;
23226
+ var textLines = this.__textGroup__.childNodes;
23227
+ while(textLines.length > sn) {
23228
+ textLines[textLines.length-1].remove();
23229
+ }
23230
+ for (var i=0; i<sn; i++) {
23231
+ if (i===textLines.length) {
23232
+ var line = document.createElementNS("http://www.w3.org/2000/svg","text");
23233
+ line.setAttribute("class","red-ui-flow-node-label-text");
23234
+ line.setAttribute("x",0);
23235
+ line.setAttribute("y",i*24);
23236
+ this.__textGroup__.appendChild(line);
23237
+ }
23238
+ textLines[i].textContent = sa[i];
23239
+ }
23240
+ }
23241
+
23242
+ var textClass = "red-ui-flow-node-label"+(hideLabel?" hide":"");
23243
+ this.__textGroup__.setAttribute("class", textClass);
23244
+ var yp = d.h / 2 - (this.__labelLineCount__ / 2) * 24 + 13;
23245
+
23246
+ // this.__textGroup__.classList.remove("red-ui-flow-node-label-right");
23247
+ this.__textGroup__.setAttribute("transform", "translate(48,"+yp+")");
23248
+
23249
+ this.__outputBorder__.setAttribute("d","M 40 1 l 0 "+(hideLabel?0:(d.h - 2)));
23250
+ this.__port__.setAttribute("transform","translate(-5,"+((d.h/2)-5)+")");
23251
+ this.__outputOutput__.setAttribute("transform","translate(20,"+((d.h/2)-8)+")");
23252
+ this.__outputNumber__.setAttribute("transform","translate(20,"+((d.h/2)+7)+")");
23253
+ }
22627
23254
  d.dirty = false;
22628
23255
  }
22629
23256
  });
@@ -23149,6 +23776,13 @@ RED.view = (function() {
23149
23776
  d3.select(pathBack)
23150
23777
  .on("mousedown",linkMouseDown)
23151
23778
  .on("touchstart",linkTouchStart)
23779
+ .on("mousemove", function(d) {
23780
+ if (mouse_mode === RED.state.SLICING) {
23781
+ selectedLinks.add(d)
23782
+ l.classed("red-ui-flow-link-splice",true)
23783
+ redraw()
23784
+ }
23785
+ })
23152
23786
 
23153
23787
  var pathOutline = document.createElementNS("http://www.w3.org/2000/svg","path");
23154
23788
  pathOutline.__data__ = d;
@@ -23169,7 +23803,7 @@ RED.view = (function() {
23169
23803
  link.exit().remove();
23170
23804
  link.each(function(d) {
23171
23805
  var link = d3.select(this);
23172
- if (d.added || d===selected_link || d.selected || dirtyNodes[d.source.id] || dirtyNodes[d.target.id]) {
23806
+ if (d.added || d.selected || dirtyNodes[d.source.id] || dirtyNodes[d.target.id]) {
23173
23807
  var numOutputs = d.source.outputs || 1;
23174
23808
  var sourcePort = d.sourcePort || 0;
23175
23809
  var y = -((numOutputs-1)/2)*13 +13*sourcePort;
@@ -23192,7 +23826,8 @@ RED.view = (function() {
23192
23826
  this.__pathLine__.classList.toggle("red-ui-flow-node-disabled",!!(d.source.d || d.target.d));
23193
23827
  this.__pathLine__.classList.toggle("red-ui-flow-subflow-link", !d.link && activeSubflow);
23194
23828
  }
23195
- this.classList.toggle("red-ui-flow-link-selected", !!(d===selected_link||d.selected));
23829
+
23830
+ this.classList.toggle("red-ui-flow-link-selected", !!d.selected);
23196
23831
 
23197
23832
  var connectedToUnknown = !!(d.target.type == "unknown" || d.source.type == "unknown");
23198
23833
  this.classList.toggle("red-ui-flow-link-unknown",!!(d.target.type == "unknown" || d.source.type == "unknown"))
@@ -23900,8 +24535,9 @@ RED.view = (function() {
23900
24535
  if (allNodes.size > 0) {
23901
24536
  selection.nodes = Array.from(allNodes);
23902
24537
  }
23903
- if (selected_link != null) {
23904
- selection.link = selected_link;
24538
+ if (selectedLinks.length() > 0) {
24539
+ selection.links = selectedLinks.toArray();
24540
+ selection.link = selection.links[0];
23905
24541
  }
23906
24542
  return selection;
23907
24543
  }
@@ -25202,6 +25838,90 @@ RED.view.tools = (function() {
25202
25838
  }
25203
25839
  }
25204
25840
 
25841
+
25842
+ function wireSeriesOfNodes() {
25843
+ var selection = RED.view.selection();
25844
+ if (selection.nodes) {
25845
+ if (selection.nodes.length > 1) {
25846
+ var i = 0;
25847
+ var newLinks = [];
25848
+ while (i < selection.nodes.length - 1) {
25849
+ var nodeA = selection.nodes[i];
25850
+ var nodeB = selection.nodes[i+1];
25851
+ if (nodeA.outputs > 0 && nodeB.inputs > 0) {
25852
+ var existingLinks = RED.nodes.filterLinks({
25853
+ source: nodeA,
25854
+ target: nodeB,
25855
+ sourcePort: 0
25856
+ })
25857
+ if (existingLinks.length === 0) {
25858
+ var newLink = {
25859
+ source: nodeA,
25860
+ target: nodeB,
25861
+ sourcePort: 0
25862
+ }
25863
+ RED.nodes.addLink(newLink);
25864
+ newLinks.push(newLink);
25865
+ }
25866
+ }
25867
+ i++;
25868
+ }
25869
+ if (newLinks.length > 0) {
25870
+ RED.history.push({
25871
+ t: 'add',
25872
+ links: newLinks,
25873
+ dirty: RED.nodes.dirty()
25874
+ })
25875
+ RED.nodes.dirty(true);
25876
+ RED.view.redraw(true);
25877
+ }
25878
+ }
25879
+ }
25880
+ }
25881
+
25882
+ function wireNodeToMultiple() {
25883
+ var selection = RED.view.selection();
25884
+ if (selection.nodes) {
25885
+ if (selection.nodes.length > 1) {
25886
+ var sourceNode = selection.nodes[0];
25887
+ if (sourceNode.outputs === 0) {
25888
+ return;
25889
+ }
25890
+ var i = 1;
25891
+ var newLinks = [];
25892
+ while (i < selection.nodes.length) {
25893
+ var targetNode = selection.nodes[i];
25894
+ if (targetNode.inputs > 0) {
25895
+ var existingLinks = RED.nodes.filterLinks({
25896
+ source: sourceNode,
25897
+ target: targetNode,
25898
+ sourcePort: Math.min(sourceNode.outputs-1,i-1)
25899
+ })
25900
+ if (existingLinks.length === 0) {
25901
+ var newLink = {
25902
+ source: sourceNode,
25903
+ target: targetNode,
25904
+ sourcePort: Math.min(sourceNode.outputs-1,i-1)
25905
+ }
25906
+ RED.nodes.addLink(newLink);
25907
+ newLinks.push(newLink);
25908
+ }
25909
+ }
25910
+ i++;
25911
+ }
25912
+ if (newLinks.length > 0) {
25913
+ RED.history.push({
25914
+ t: 'add',
25915
+ links: newLinks,
25916
+ dirty: RED.nodes.dirty()
25917
+ })
25918
+ RED.nodes.dirty(true);
25919
+ RED.view.redraw(true);
25920
+ }
25921
+ }
25922
+ }
25923
+ }
25924
+
25205
25925
  return {
25206
25926
  init: function() {
25207
25927
  RED.actions.add("core:show-selected-node-labels", function() { setSelectedNodeLabelState(true); })
@@ -25260,7 +25980,8 @@ RED.view.tools = (function() {
25260
25980
  RED.actions.add("core:distribute-selection-horizontally", function() { distributeSelection('h') })
25261
25981
  RED.actions.add("core:distribute-selection-vertically", function() { distributeSelection('v') })
25262
25982
 
25263
-
25983
+ RED.actions.add("core:wire-series-of-nodes", function() { wireSeriesOfNodes() })
25984
+ RED.actions.add("core:wire-node-to-multiple", function() { wireNodeToMultiple() })
25264
25985
 
25265
25986
  // RED.actions.add("core:add-node", function() { addNode() })
25266
25987
  },
@@ -25298,6 +26019,15 @@ RED.sidebar = (function() {
25298
26019
  var sidebar_tabs;
25299
26020
  var knownTabs = {};
25300
26021
 
26022
+ // We store the current sidebar tab id in localStorage as 'last-sidebar-tab'
26023
+ // This is restored when the editor is reloaded.
26024
+ // We use sidebar_tabs.onchange to update localStorage. However that will
26025
+ // also get triggered when the first tab gets added to the tabs - typically
26026
+ // the 'info' tab. So we use the following variable to store the retrieved
26027
+ // value from localStorage before we start adding the actual tabs
26028
+ var lastSessionSelectedTab = null;
26029
+
26030
+
25301
26031
  function addTab(title,content,closeable,visible) {
25302
26032
  var options;
25303
26033
  if (typeof title === "string") {
@@ -25473,16 +26203,16 @@ RED.sidebar = (function() {
25473
26203
  RED.events.emit("sidebar:resize");
25474
26204
  }
25475
26205
 
25476
- function showSidebar(id) {
26206
+ function showSidebar(id, skipShowSidebar) {
25477
26207
  if (id === ":first") {
25478
- id = RED.settings.get("editor.sidebar.order",["info", "help", "version-control", "debug"])[0]
26208
+ id = lastSessionSelectedTab || RED.settings.get("editor.sidebar.order",["info", "help", "version-control", "debug"])[0]
25479
26209
  }
25480
26210
  if (id) {
25481
26211
  if (!containsTab(id) && knownTabs[id]) {
25482
26212
  sidebar_tabs.addTab(knownTabs[id]);
25483
26213
  }
25484
26214
  sidebar_tabs.activateTab(id);
25485
- if (!RED.menu.isSelected("menu-item-sidebar")) {
26215
+ if (!skipShowSidebar && !RED.menu.isSelected("menu-item-sidebar")) {
25486
26216
  RED.menu.setSelected("menu-item-sidebar",true);
25487
26217
  }
25488
26218
  }
@@ -25506,6 +26236,7 @@ RED.sidebar = (function() {
25506
26236
  if (tab.toolbar) {
25507
26237
  $(tab.toolbar).show();
25508
26238
  }
26239
+ RED.settings.setLocal("last-sidebar-tab", tab.id)
25509
26240
  },
25510
26241
  onremove: function(tab) {
25511
26242
  $(tab.wrapper).hide();
@@ -25534,7 +26265,9 @@ RED.sidebar = (function() {
25534
26265
  }
25535
26266
  });
25536
26267
  RED.popover.tooltip($("#red-ui-sidebar-separator").find(".red-ui-sidebar-control-right"),RED._("keyboard.toggleSidebar"),"core:toggle-sidebar");
25537
- showSidebar();
26268
+
26269
+ lastSessionSelectedTab = RED.settings.getLocal("last-sidebar-tab")
26270
+
25538
26271
  RED.sidebar.info.init();
25539
26272
  RED.sidebar.help.init();
25540
26273
  RED.sidebar.config.init();
@@ -27593,15 +28326,17 @@ RED.sidebar.help = (function() {
27593
28326
  style: "compact",
27594
28327
  delay: 100,
27595
28328
  change: function() {
27596
- var val = $(this).val().toLowerCase();
27597
- if (val) {
28329
+ const searchFor = $(this).val().toLowerCase();
28330
+ if (searchFor) {
27598
28331
  showTOC();
27599
- var c = treeList.treeList('filter',function(item) {
28332
+ treeList.treeList('filter',function(item) {
27600
28333
  if (item.depth === 0) {
27601
28334
  return true;
27602
28335
  }
27603
- return (item.nodeType && item.nodeType.indexOf(val) > -1) ||
27604
- (item.subflowLabel && item.subflowLabel.indexOf(val) > -1)
28336
+ let found = item.nodeType && item.nodeType.toLowerCase().indexOf(searchFor) > -1;
28337
+ found = found || item.subflowLabel && item.subflowLabel.toLowerCase().indexOf(searchFor) > -1;
28338
+ found = found || item.palleteLabel && item.palleteLabel.toLowerCase().indexOf(searchFor) > -1;
28339
+ return found;
27605
28340
  },true)
27606
28341
  } else {
27607
28342
  treeList.treeList('filter',null);
@@ -27753,17 +28488,21 @@ RED.sidebar.help = (function() {
27753
28488
 
27754
28489
 
27755
28490
  moduleNames.forEach(function(moduleName) {
27756
- var module = modules[moduleName];
27757
- var nodeTypes = [];
27758
-
27759
- var setNames = Object.keys(module.sets);
28491
+ const module = modules[moduleName];
28492
+ const nodeTypes = [];
28493
+ const moduleSets = module.sets;
28494
+ const setNames = Object.keys(moduleSets);
27760
28495
  setNames.forEach(function(setName) {
27761
- module.sets[setName].types.forEach(function(nodeType) {
28496
+ const moduleSet = moduleSets[setName];
28497
+ moduleSet.types.forEach(function(nodeType) {
27762
28498
  if ($("script[data-help-name='"+nodeType+"']").length) {
28499
+ const n = {_def:RED.nodes.getType(nodeType),type:nodeType}
28500
+ n.name = getNodePaletteLabel(n);
27763
28501
  nodeTypes.push({
27764
28502
  id: "node-type:"+nodeType,
27765
28503
  nodeType: nodeType,
27766
- element:getNodeLabel({_def:RED.nodes.getType(nodeType),type:nodeType})
28504
+ palleteLabel: n.name,
28505
+ element: getNodeLabel(n)
27767
28506
  })
27768
28507
  }
27769
28508
  })
@@ -27783,18 +28522,21 @@ RED.sidebar.help = (function() {
27783
28522
  treeList.treeList("data",helpData);
27784
28523
  }
27785
28524
 
27786
- function getNodeLabel(n) {
27787
- var div = $('<div>',{class:"red-ui-node-list-item"});
27788
- var icon = RED.utils.createNodeIcon(n).appendTo(div);
27789
- var label = n.name;
28525
+ function getNodePaletteLabel(n) {
28526
+ let label = n.name;
27790
28527
  if (!label && n._def && n._def.paletteLabel) {
27791
28528
  try {
27792
28529
  label = (typeof n._def.paletteLabel === "function" ? n._def.paletteLabel.call(n._def) : n._def.paletteLabel)||"";
27793
28530
  } catch (err) {
27794
28531
  }
27795
28532
  }
27796
- label = label || n.type;
27797
- $('<div>',{class:"red-ui-node-label"}).text(n.name||n.type).appendTo(icon);
28533
+ return label || n.type;
28534
+ }
28535
+
28536
+ function getNodeLabel(n) {
28537
+ const div = $('<div>',{class:"red-ui-node-list-item"});
28538
+ const icon = RED.utils.createNodeIcon(n).appendTo(div);
28539
+ $('<div>',{class:"red-ui-node-label"}).text(getNodePaletteLabel(n)).appendTo(icon);
27798
28540
  return div;
27799
28541
  }
27800
28542
 
@@ -31929,9 +32671,10 @@ RED.editor = (function() {
31929
32671
  editState.changed = true;
31930
32672
  }
31931
32673
  if (!node._def.defaults || !node._def.defaults.hasOwnProperty("icon")) {
31932
- var icon = $("#red-ui-editor-node-icon").val()||""
32674
+ var icon = $("#red-ui-editor-node-icon").val()||"";
31933
32675
  if (!this.isDefaultIcon) {
31934
- if (icon !== node.icon) {
32676
+ if ((icon !== node.icon) &&
32677
+ (icon !== "")) {
31935
32678
  editState.changes.icon = node.icon;
31936
32679
  node.icon = icon;
31937
32680
  editState.changed = true;
@@ -31995,14 +32738,14 @@ RED.editor = (function() {
31995
32738
  if (showLabel) {
31996
32739
  // Default to show label
31997
32740
  if (node.l !== false) {
31998
- editState.changes.l = node.l
32741
+ editState.changes.l = node.l;
31999
32742
  editState.changed = true;
32000
32743
  }
32001
32744
  node.l = false;
32002
32745
  } else {
32003
32746
  // Node has showLabel:false (eg link nodes)
32004
32747
  if (node.hasOwnProperty('l') && node.l) {
32005
- editState.changes.l = node.l
32748
+ editState.changes.l = node.l;
32006
32749
  editState.changed = true;
32007
32750
  }
32008
32751
  delete node.l;
@@ -32012,20 +32755,20 @@ RED.editor = (function() {
32012
32755
  if (showLabel) {
32013
32756
  // Default to show label
32014
32757
  if (node.hasOwnProperty('l') && !node.l) {
32015
- editState.changes.l = node.l
32758
+ editState.changes.l = node.l;
32016
32759
  editState.changed = true;
32017
32760
  }
32018
32761
  delete node.l;
32019
32762
  } else {
32020
32763
  if (!node.l) {
32021
- editState.changes.l = node.l
32764
+ editState.changes.l = node.l;
32022
32765
  editState.changed = true;
32023
32766
  }
32024
32767
  node.l = true;
32025
32768
  }
32026
32769
  }
32027
32770
  }
32028
- }
32771
+ };
32029
32772
  });
32030
32773
 
32031
32774
  function buildAppearanceForm(container,node) {
@@ -32058,10 +32801,10 @@ RED.editor = (function() {
32058
32801
  var categories = RED.palette.getCategories();
32059
32802
  categories.sort(function(A,B) {
32060
32803
  return A.label.localeCompare(B.label);
32061
- })
32804
+ });
32062
32805
  categories.forEach(function(cat) {
32063
32806
  categorySelector.append($("<option/>").val(cat.id).text(cat.label));
32064
- })
32807
+ });
32065
32808
  categorySelector.append($("<option/>").attr('disabled',true).text("---"));
32066
32809
  categorySelector.append($("<option/>").val("_custom_").text(RED._("palette.addCategory")));
32067
32810
 
@@ -32074,7 +32817,7 @@ RED.editor = (function() {
32074
32817
  $("#subflow-appearance-input-category").width(250);
32075
32818
  $("#subflow-appearance-input-custom-category").hide();
32076
32819
  }
32077
- })
32820
+ });
32078
32821
 
32079
32822
  $("#subflow-appearance-input-category").val(node.category||"subflows");
32080
32823
  var userCount = 0;
@@ -32098,7 +32841,7 @@ RED.editor = (function() {
32098
32841
  $("#node-input-show-label").toggleButton({
32099
32842
  enabledLabel: RED._("editor.show"),
32100
32843
  disabledLabel: RED._("editor.hide")
32101
- })
32844
+ });
32102
32845
 
32103
32846
  if (!node.hasOwnProperty("l")) {
32104
32847
  // Show label unless def.showLabel set to false
@@ -32124,7 +32867,7 @@ RED.editor = (function() {
32124
32867
  "#E9967A", "#F3B567", "#FDD0A2",
32125
32868
  "#FDF0C2", "#FFAAAA", "#FFCC66",
32126
32869
  "#FFF0F0", "#FFFFFF"
32127
- ]
32870
+ ];
32128
32871
 
32129
32872
  RED.editor.colorPicker.create({
32130
32873
  id: "red-ui-editor-node-color",
@@ -32139,9 +32882,9 @@ RED.editor = (function() {
32139
32882
  nodeDiv.css('backgroundColor',colour);
32140
32883
  var borderColor = RED.utils.getDarkerColor(colour);
32141
32884
  if (borderColor !== colour) {
32142
- nodeDiv.css('border-color',borderColor)
32885
+ nodeDiv.css('border-color',borderColor);
32143
32886
  }
32144
- })
32887
+ });
32145
32888
  }
32146
32889
 
32147
32890
 
@@ -32158,7 +32901,7 @@ RED.editor = (function() {
32158
32901
  nodeDiv.css('backgroundColor',colour);
32159
32902
  var borderColor = RED.utils.getDarkerColor(colour);
32160
32903
  if (borderColor !== colour) {
32161
- nodeDiv.css('border-color',borderColor)
32904
+ nodeDiv.css('border-color',borderColor);
32162
32905
  }
32163
32906
 
32164
32907
  var iconContainer = $('<div/>',{class:"red-ui-palette-icon-container"}).appendTo(nodeDiv);
@@ -32186,7 +32929,7 @@ RED.editor = (function() {
32186
32929
 
32187
32930
  RED.popover.tooltip(iconButton, function() {
32188
32931
  return $("#red-ui-editor-node-icon").val() || RED._("editor.default");
32189
- })
32932
+ });
32190
32933
  $('<input type="hidden" id="red-ui-editor-node-icon">').val(node.icon).appendTo(iconRow);
32191
32934
  }
32192
32935
 
@@ -32311,11 +33054,11 @@ RED.editor = (function() {
32311
33054
  });
32312
33055
  rows.sort(function(A,B) {
32313
33056
  return A.i-B.i;
32314
- })
33057
+ });
32315
33058
  rows.forEach(function(r,i) {
32316
33059
  r.r.find("label").text((i+1)+".");
32317
33060
  r.r.appendTo(outputsDiv);
32318
- })
33061
+ });
32319
33062
  if (rows.length === 0) {
32320
33063
  buildLabelRow("output",i,"").appendTo(outputsDiv);
32321
33064
  } else {
@@ -32361,7 +33104,7 @@ RED.editor = (function() {
32361
33104
  clear.on("click", function(evt) {
32362
33105
  evt.preventDefault();
32363
33106
  input.val("");
32364
- })
33107
+ });
32365
33108
  }
32366
33109
  return result;
32367
33110
  }
@@ -32395,6 +33138,12 @@ RED.editor = (function() {
32395
33138
  }
32396
33139
  var v = $(this).val();
32397
33140
  hasNonBlankLabel = hasNonBlankLabel || v!== "";
33141
+
33142
+ // mark changed output port labels as dirty
33143
+ if (node.type === "subflow" && (!node.outputLabels || node.outputLabels[index] !== v)) {
33144
+ node.out[index].dirty = true;
33145
+ }
33146
+
32398
33147
  newValue[index] = v;
32399
33148
  });
32400
33149
 
@@ -32403,6 +33152,12 @@ RED.editor = (function() {
32403
33152
  changes.outputLabels = node.outputLabels;
32404
33153
  node.outputLabels = newValue;
32405
33154
  changed = true;
33155
+
33156
+ // trigger redraw of dirty port labels
33157
+ if (node.type === "subflow") {
33158
+ RED.view.redraw();
33159
+ }
33160
+
32406
33161
  }
32407
33162
  return changed;
32408
33163
  }
@@ -36330,7 +37085,7 @@ RED.editor.codeEditor.monaco = (function() {
36330
37085
  createMonacoCompletionItem("set (flow context)", 'flow.set("${1:name}", ${1:value});','Set a value in flow context',range),
36331
37086
  createMonacoCompletionItem("get (global context)", 'global.get("${1:name}");','Get a value from global context',range),
36332
37087
  createMonacoCompletionItem("set (global context)", 'global.set("${1:name}", ${1:value});','Set a value in global context',range),
36333
- createMonacoCompletionItem("get (env)", 'env.get("${1:name}");','Get env variable value',range),
37088
+ createMonacoCompletionItem("get (env)", 'env.get("${1|NR_NODE_ID,NR_NODE_NAME,NR_NODE_PATH,NR_GROUP_ID,NR_GROUP_NAME,NR_FLOW_ID,NR_FLOW_NAME|}");','Get env variable value',range),
36334
37089
  createMonacoCompletionItem("cloneMessage (RED.util)", 'RED.util.cloneMessage(${1:msg});',
36335
37090
  ["```typescript",
36336
37091
  "RED.util.cloneMessage<T extends registry.NodeMessage>(msg: T): T",
@@ -40133,6 +40888,7 @@ RED.search = (function() {
40133
40888
  var selected = -1;
40134
40889
  var visible = false;
40135
40890
 
40891
+ var searchHistory = [];
40136
40892
  var index = {};
40137
40893
  var currentResults = [];
40138
40894
  var previousActiveElement;
@@ -40163,10 +40919,22 @@ RED.search = (function() {
40163
40919
  }
40164
40920
  l = l||n.label||n.name||n.id||"";
40165
40921
 
40166
-
40167
40922
  var properties = ['id','type','name','label','info'];
40168
- if (n._def && n._def.defaults) {
40169
- properties = properties.concat(Object.keys(n._def.defaults));
40923
+ const node_def = n && n._def;
40924
+ if (node_def) {
40925
+ if (node_def.defaults) {
40926
+ properties = properties.concat(Object.keys(node_def.defaults));
40927
+ }
40928
+ if (n.type !== "group" && node_def.paletteLabel && node_def.paletteLabel !== node_def.type) {
40929
+ try {
40930
+ const label = ("" + (typeof node_def.paletteLabel === "function" ? node_def.paletteLabel.call(node_def) : node_def.paletteLabel)).toLowerCase();
40931
+ if(label && label !== (""+node_def.type).toLowerCase()) {
40932
+ indexProperty(n, l, label);
40933
+ }
40934
+ } catch(err) {
40935
+ console.warn(`error indexing ${l}`, err);
40936
+ }
40937
+ }
40170
40938
  }
40171
40939
  for (var i=0;i<properties.length;i++) {
40172
40940
  if (n.hasOwnProperty(properties[i])) {
@@ -40316,6 +41084,20 @@ RED.search = (function() {
40316
41084
  }
40317
41085
  }
40318
41086
 
41087
+ function populateSearchHistory() {
41088
+ if (searchHistory.length > 0) {
41089
+ searchResults.editableList('addItem',{
41090
+ historyHeader: true
41091
+ });
41092
+ searchHistory.forEach(function(entry) {
41093
+ searchResults.editableList('addItem',{
41094
+ history: true,
41095
+ value: entry
41096
+ });
41097
+ })
41098
+ }
41099
+
41100
+ }
40319
41101
  function createDialog() {
40320
41102
  dialog = $("<div>",{id:"red-ui-search",class:"red-ui-search"}).appendTo("#red-ui-main-container");
40321
41103
  var searchDiv = $("<div>",{class:"red-ui-search-container"}).appendTo(dialog);
@@ -40324,7 +41106,12 @@ RED.search = (function() {
40324
41106
  change: function() {
40325
41107
  searchResults.editableList('empty');
40326
41108
  selected = -1;
40327
- currentResults = search($(this).val());
41109
+ var value = $(this).val();
41110
+ if (value === "") {
41111
+ populateSearchHistory();
41112
+ return;
41113
+ }
41114
+ currentResults = search(value);
40328
41115
  if (currentResults.length > 0) {
40329
41116
  for (i=0;i<Math.min(currentResults.length,25);i++) {
40330
41117
  searchResults.editableList('addItem',currentResults[i])
@@ -40396,7 +41183,12 @@ RED.search = (function() {
40396
41183
  })
40397
41184
  }
40398
41185
  }
40399
- } else {
41186
+ } if ($(children[selected]).hasClass("red-ui-search-history")) {
41187
+ var object = $(children[selected]).find(".red-ui-editableList-item-content").data('data');
41188
+ if (object) {
41189
+ searchInput.searchBox('value',object.value)
41190
+ }
41191
+ } else if (!$(children[selected]).hasClass("red-ui-search-historyHeader")) {
40400
41192
  if (currentResults.length > 0) {
40401
41193
  reveal(currentResults[Math.max(0,selected)].node);
40402
41194
  }
@@ -40412,7 +41204,32 @@ RED.search = (function() {
40412
41204
  addItem: function(container,i,object) {
40413
41205
  var node = object.node;
40414
41206
  var div;
40415
- if (object.more) {
41207
+ if (object.historyHeader) {
41208
+ container.parent().addClass("red-ui-search-historyHeader")
41209
+ $('<div>',{class:"red-ui-search-empty"}).text(RED._("search.history")).appendTo(container);
41210
+ $('<button type="button" class="red-ui-button red-ui-button-small"></button>').text(RED._("search.clear")).appendTo(container).on("click", function(evt) {
41211
+ evt.preventDefault();
41212
+ searchHistory = [];
41213
+ searchResults.editableList('empty');
41214
+ });
41215
+ } else if (object.history) {
41216
+ container.parent().addClass("red-ui-search-history")
41217
+ div = $('<a>',{href:'#',class:"red-ui-search-result"}).appendTo(container);
41218
+ div.text(object.value);
41219
+ div.on("click", function(evt) {
41220
+ evt.preventDefault();
41221
+ searchInput.searchBox('value',object.value)
41222
+ searchInput.focus();
41223
+ })
41224
+ $('<button type="button" class="red-ui-button red-ui-button-small"><i class="fa fa-remove"></i></button>').appendTo(container).on("click", function(evt) {
41225
+ evt.preventDefault();
41226
+ var index = searchHistory.indexOf(object.value);
41227
+ searchHistory.splice(index,1);
41228
+ searchResults.editableList('removeItem', object);
41229
+ });
41230
+
41231
+
41232
+ } else if (object.more) {
40416
41233
  container.parent().addClass("red-ui-search-more")
40417
41234
  div = $('<a>',{href:'#',class:"red-ui-search-result red-ui-search-empty"}).appendTo(container);
40418
41235
  div.text(RED._("palette.editor.more",{count:object.more.results.length-object.more.start}));
@@ -40467,6 +41284,12 @@ RED.search = (function() {
40467
41284
  }
40468
41285
 
40469
41286
  function reveal(node) {
41287
+ var searchVal = searchInput.val();
41288
+ var existingIndex = searchHistory.indexOf(searchVal);
41289
+ if (existingIndex > -1) {
41290
+ searchHistory.splice(existingIndex,1);
41291
+ }
41292
+ searchHistory.unshift(searchInput.val());
40470
41293
  hide();
40471
41294
  RED.view.reveal(node.id);
40472
41295
  }
@@ -40485,9 +41308,14 @@ RED.search = (function() {
40485
41308
 
40486
41309
  if (dialog === null) {
40487
41310
  createDialog();
41311
+ } else {
41312
+ searchResults.editableList('empty');
40488
41313
  }
40489
41314
  dialog.slideDown(300);
40490
41315
  searchInput.searchBox('value',v)
41316
+ if (!v || v === "") {
41317
+ populateSearchHistory();
41318
+ }
40491
41319
  RED.events.emit("search:open");
40492
41320
  visible = true;
40493
41321
  }
@@ -40745,18 +41573,19 @@ RED.actionList = (function() {
40745
41573
  createDialog();
40746
41574
  }
40747
41575
  dialog.slideDown(300);
40748
- searchInput.searchBox('value',v)
41576
+ searchInput.searchBox('value',v);
40749
41577
  searchResults.editableList('empty');
40750
41578
  results = [];
40751
41579
  var actions = RED.actions.list();
40752
41580
  actions.sort(function(A,B) {
40753
- return A.id.localeCompare(B.id);
41581
+ var Akey = A.label;
41582
+ var Bkey = B.label;
41583
+ return Akey.localeCompare(Bkey);
40754
41584
  });
40755
41585
  actions.forEach(function(action) {
40756
- action.label = action.id.replace(/:/,": ").replace(/-/g," ").replace(/(^| )./g,function() { return arguments[0].toUpperCase()});
40757
41586
  action._label = action.label.toLowerCase();
40758
- searchResults.editableList('addItem',action)
40759
- })
41587
+ searchResults.editableList('addItem',action);
41588
+ });
40760
41589
  RED.events.emit("actionList:open");
40761
41590
  visible = true;
40762
41591
  }
@@ -43118,12 +43947,14 @@ RED.group = (function() {
43118
43947
  markDirty(group);
43119
43948
  }
43120
43949
 
43121
- function getNodes(group,recursive) {
43950
+ function getNodes(group,recursive,excludeGroup) {
43122
43951
  var nodes = [];
43123
43952
  group.nodes.forEach(function(n) {
43124
- nodes.push(n);
43953
+ if (n.type !== 'group' || !excludeGroup) {
43954
+ nodes.push(n);
43955
+ }
43125
43956
  if (recursive && n.type === 'group') {
43126
- nodes = nodes.concat(getNodes(n,recursive))
43957
+ nodes = nodes.concat(getNodes(n,recursive,excludeGroup))
43127
43958
  }
43128
43959
  })
43129
43960
  return nodes;
@@ -43357,6 +44188,13 @@ RED.userSettings = (function() {
43357
44188
  // {setting:"theme", label:"Theme",options:function(done){ done([{val:'',text:'default'}].concat(RED.settings.theme("themes"))) }},
43358
44189
  // ]
43359
44190
  // },
44191
+ {
44192
+ title: "menu.label.view.view",
44193
+ options: [
44194
+ {setting:"view-store-zoom",label:"menu.label.view.storeZoom", default: false, toggle:true, onchange: function(val) { if (!val) { RED.settings.removeLocal("zoom-level")}}},
44195
+ {setting:"view-store-position",label:"menu.label.view.storePosition", default: false, toggle:true, onchange: function(val) { if (!val) { RED.settings.removeLocal("scroll-positions")}}},
44196
+ ]
44197
+ },
43360
44198
  {
43361
44199
  title: "menu.label.view.grid",
43362
44200
  options: [
@@ -44744,6 +45582,9 @@ RED.projects = (function() {
44744
45582
  }
44745
45583
  }).appendTo(row);
44746
45584
 
45585
+ row = $('<div class="form-row red-ui-projects-dialog-screen-create-row red-ui-projects-dialog-screen-create-row-open"></div>').hide().appendTo(container);
45586
+ $('<span style="display: flex; align-items: center;"><input style="padding:0; margin: 0 5px 0 0" checked type="checkbox" id="red-ui-projects-dialog-screen-clear-context"> <label for="red-ui-projects-dialog-screen-clear-context" style="padding:0; margin: 0"> <span data-i18n="projects.create.clearContext"></span></label></span>').appendTo(row).i18n();
45587
+
44747
45588
  row = $('<div class="form-row red-ui-projects-dialog-screen-create-row red-ui-projects-dialog-screen-create-row-empty red-ui-projects-dialog-screen-create-row-clone"></div>').appendTo(container);
44748
45589
  $('<label for="red-ui-projects-dialog-screen-create-project-name">'+RED._("projects.create.project-name")+'</label>').appendTo(row);
44749
45590
 
@@ -45033,7 +45874,8 @@ RED.projects = (function() {
45033
45874
  };
45034
45875
  }
45035
45876
  } else if (projectType === 'open') {
45036
- return switchProject(selectedProject.name,function(err,data) {
45877
+ var clearContext = $("#red-ui-projects-dialog-screen-clear-context").prop("checked")
45878
+ return switchProject(selectedProject.name, clearContext, function(err,data) {
45037
45879
  if (err) {
45038
45880
  if (err.code !== 'credentials_load_failed') {
45039
45881
  console.log(RED._("projects.create.unexpected_error"),err)
@@ -45127,7 +45969,7 @@ RED.projects = (function() {
45127
45969
  }
45128
45970
  }
45129
45971
 
45130
- function switchProject(name,done) {
45972
+ function switchProject(name,clearContext,done) {
45131
45973
  RED.deploy.setDeployInflight(true);
45132
45974
  RED.projects.settings.switchProject(name);
45133
45975
  sendRequest({
@@ -45146,7 +45988,7 @@ RED.projects = (function() {
45146
45988
  '*': done
45147
45989
  },
45148
45990
  }
45149
- },{active:true}).then(function() {
45991
+ },{active:true, clearContext:clearContext}).then(function() {
45150
45992
  dialog.dialog( "close" );
45151
45993
  RED.events.emit("project:change", {name:name});
45152
45994
  }).always(function() {
@@ -45219,7 +46061,7 @@ RED.projects = (function() {
45219
46061
  dialogHeight = 590 - (750 - winHeight);
45220
46062
  }
45221
46063
  $(".red-ui-projects-dialog-box").height(dialogHeight);
45222
- $(".red-ui-projects-dialog-project-list-inner-container").height(Math.max(500,dialogHeight) - 180);
46064
+ $(".red-ui-projects-dialog-project-list-inner-container").height(Math.max(500,dialogHeight) - 210);
45223
46065
  dialog.dialog('option','title',screen.title||"");
45224
46066
  dialog.dialog("open");
45225
46067
  }
@@ -49926,6 +50768,10 @@ RED.touch.radialMenu = (function() {
49926
50768
  }
49927
50769
  $('<div>').css("text-align","left").html(getLocaleText(step.description)).appendTo(stepDescription);
49928
50770
 
50771
+ if (step.image) {
50772
+ $(`<img src="red/tours/${step.image}" />`).appendTo(stepDescription)
50773
+ }
50774
+
49929
50775
  var stepToolbar = $('<div>',{class:"red-ui-tourGuide-toolbar"}).appendTo(stepContent);
49930
50776
 
49931
50777
  // var breadcrumbs = $('<div>',{class:"red-ui-tourGuide-breadcrumbs"}).appendTo(stepToolbar);