@node-red/editor-client 2.1.4 → 2.2.0

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
 
@@ -6047,6 +6051,144 @@ RED.nodes = (function() {
6047
6051
  return helpContent;
6048
6052
  }
6049
6053
 
6054
+ function getNodeIslands(nodes) {
6055
+ var selectedNodes = new Set(nodes);
6056
+ // Maps node => island index
6057
+ var nodeToIslandIndex = new Map();
6058
+ // Maps island index => [nodes in island]
6059
+ var islandIndexToNodes = new Map();
6060
+ var internalLinks = new Set();
6061
+ nodes.forEach((node, index) => {
6062
+ nodeToIslandIndex.set(node,index);
6063
+ islandIndexToNodes.set(index, [node]);
6064
+ var inboundLinks = RED.nodes.getNodeLinks(node, PORT_TYPE_INPUT);
6065
+ var outboundLinks = RED.nodes.getNodeLinks(node, PORT_TYPE_OUTPUT);
6066
+ inboundLinks.forEach(l => {
6067
+ if (selectedNodes.has(l.source)) {
6068
+ internalLinks.add(l)
6069
+ }
6070
+ })
6071
+ outboundLinks.forEach(l => {
6072
+ if (selectedNodes.has(l.target)) {
6073
+ internalLinks.add(l)
6074
+ }
6075
+ })
6076
+ })
6077
+
6078
+ internalLinks.forEach(l => {
6079
+ let source = l.source;
6080
+ let target = l.target;
6081
+ if (nodeToIslandIndex.get(source) !== nodeToIslandIndex.get(target)) {
6082
+ let sourceIsland = nodeToIslandIndex.get(source);
6083
+ let islandToMove = nodeToIslandIndex.get(target);
6084
+ let nodesToMove = islandIndexToNodes.get(islandToMove);
6085
+ nodesToMove.forEach(n => {
6086
+ nodeToIslandIndex.set(n,sourceIsland);
6087
+ islandIndexToNodes.get(sourceIsland).push(n);
6088
+ })
6089
+ islandIndexToNodes.delete(islandToMove);
6090
+ }
6091
+ })
6092
+ const result = [];
6093
+ islandIndexToNodes.forEach((nodes,index) => {
6094
+ result.push(nodes);
6095
+ })
6096
+ return result;
6097
+ }
6098
+
6099
+ function detachNodes(nodes) {
6100
+ let allSelectedNodes = [];
6101
+ nodes.forEach(node => {
6102
+ if (node.type === 'group') {
6103
+ let groupNodes = RED.group.getNodes(node,true,true);
6104
+ allSelectedNodes = allSelectedNodes.concat(groupNodes);
6105
+ } else {
6106
+ allSelectedNodes.push(node);
6107
+ }
6108
+ })
6109
+ if (allSelectedNodes.length > 0 ) {
6110
+ const nodeIslands = RED.nodes.getNodeIslands(allSelectedNodes);
6111
+ let removedLinks = [];
6112
+ let newLinks = [];
6113
+ let createdLinkIds = new Set();
6114
+
6115
+ nodeIslands.forEach(nodes => {
6116
+ let selectedNodes = new Set(nodes);
6117
+ let allInboundLinks = [];
6118
+ let allOutboundLinks = [];
6119
+ // Identify links that enter or exit this island of nodes
6120
+ nodes.forEach(node => {
6121
+ var inboundLinks = RED.nodes.getNodeLinks(node, PORT_TYPE_INPUT);
6122
+ var outboundLinks = RED.nodes.getNodeLinks(node, PORT_TYPE_OUTPUT);
6123
+ inboundLinks.forEach(l => {
6124
+ if (!selectedNodes.has(l.source)) {
6125
+ allInboundLinks.push(l)
6126
+ }
6127
+ })
6128
+ outboundLinks.forEach(l => {
6129
+ if (!selectedNodes.has(l.target)) {
6130
+ allOutboundLinks.push(l)
6131
+ }
6132
+ })
6133
+ });
6134
+
6135
+
6136
+ // Identify the links to restore
6137
+ allInboundLinks.forEach(inLink => {
6138
+ // For Each inbound link,
6139
+ // - get source node.
6140
+ // - trace through to all outbound links
6141
+ let sourceNode = inLink.source;
6142
+ let targetNodes = new Set();
6143
+ let visited = new Set();
6144
+ let stack = [inLink.target];
6145
+ while (stack.length > 0) {
6146
+ let node = stack.pop(stack);
6147
+ visited.add(node)
6148
+ let links = RED.nodes.getNodeLinks(node, PORT_TYPE_OUTPUT);
6149
+ links.forEach(l => {
6150
+ if (visited.has(l.target)) {
6151
+ return
6152
+ }
6153
+ visited.add(l.target);
6154
+ if (selectedNodes.has(l.target)) {
6155
+ // internal link
6156
+ stack.push(l.target)
6157
+ } else {
6158
+ targetNodes.add(l.target)
6159
+ }
6160
+ })
6161
+ }
6162
+ targetNodes.forEach(target => {
6163
+ let linkId = `${sourceNode.id}[${inLink.sourcePort}] -> ${target.id}`
6164
+ if (!createdLinkIds.has(linkId)) {
6165
+ createdLinkIds.add(linkId);
6166
+ let link = {
6167
+ source: sourceNode,
6168
+ sourcePort: inLink.sourcePort,
6169
+ target: target
6170
+ }
6171
+ let existingLinks = RED.nodes.filterLinks(link)
6172
+ if (existingLinks.length === 0) {
6173
+ newLinks.push(link);
6174
+ }
6175
+ }
6176
+ })
6177
+ })
6178
+
6179
+ // 2. delete all those links
6180
+ allInboundLinks.forEach(l => { RED.nodes.removeLink(l); removedLinks.push(l)})
6181
+ allOutboundLinks.forEach(l => { RED.nodes.removeLink(l); removedLinks.push(l)})
6182
+ })
6183
+
6184
+ newLinks.forEach(l => RED.nodes.addLink(l));
6185
+ return {
6186
+ newLinks,
6187
+ removedLinks
6188
+ }
6189
+ }
6190
+ }
6191
+
6050
6192
  return {
6051
6193
  init: function() {
6052
6194
  RED.events.on("registry:node-type-added",function(type) {
@@ -6128,7 +6270,7 @@ RED.nodes = (function() {
6128
6270
  add: addNode,
6129
6271
  remove: removeNode,
6130
6272
  clear: clear,
6131
-
6273
+ detachNodes: detachNodes,
6132
6274
  moveNodesForwards: moveNodesForwards,
6133
6275
  moveNodesBackwards: moveNodesBackwards,
6134
6276
  moveNodesToFront: moveNodesToFront,
@@ -6140,7 +6282,20 @@ RED.nodes = (function() {
6140
6282
 
6141
6283
  addLink: addLink,
6142
6284
  removeLink: removeLink,
6143
-
6285
+ getNodeLinks: function(id, portType) {
6286
+ if (typeof id !== 'string') {
6287
+ id = id.id;
6288
+ }
6289
+ if (nodeLinks[id]) {
6290
+ if (portType === 1) {
6291
+ // Return cloned arrays so they can be safely modified by caller
6292
+ return [].concat(nodeLinks[id].in)
6293
+ } else {
6294
+ return [].concat(nodeLinks[id].out)
6295
+ }
6296
+ }
6297
+ return [];
6298
+ },
6144
6299
  addWorkspace: addWorkspace,
6145
6300
  removeWorkspace: removeWorkspace,
6146
6301
  getWorkspaceOrder: function() { return workspacesOrder },
@@ -6214,6 +6369,7 @@ RED.nodes = (function() {
6214
6369
  getAllFlowNodes: getAllFlowNodes,
6215
6370
  getAllUpstreamNodes: getAllUpstreamNodes,
6216
6371
  getAllDownstreamNodes: getAllDownstreamNodes,
6372
+ getNodeIslands: getNodeIslands,
6217
6373
  createExportableNodeSet: createExportableNodeSet,
6218
6374
  createCompleteNodeSet: createCompleteNodeSet,
6219
6375
  updateConfigNodeUsers: updateConfigNodeUsers,
@@ -7734,6 +7890,13 @@ RED.history = (function() {
7734
7890
  peek: function() {
7735
7891
  return undoHistory[undoHistory.length-1];
7736
7892
  },
7893
+ replace: function(ev) {
7894
+ if (undoHistory.length === 0) {
7895
+ RED.history.push(ev);
7896
+ } else {
7897
+ undoHistory[undoHistory.length-1] = ev;
7898
+ }
7899
+ },
7737
7900
  clear: function() {
7738
7901
  undoHistory = [];
7739
7902
  redoHistory = [];
@@ -7900,7 +8063,7 @@ RED.utils = (function() {
7900
8063
  window._marked.use({extensions: [descriptionList, description] } );
7901
8064
 
7902
8065
  function renderMarkdown(txt) {
7903
- var rendered = _marked(txt);
8066
+ var rendered = _marked.parse(txt);
7904
8067
  var cleaned = DOMPurify.sanitize(rendered, {SAFE_FOR_JQUERY: true})
7905
8068
  return cleaned;
7906
8069
  }
@@ -12536,28 +12699,24 @@ RED.tabs = (function() {
12536
12699
  }
12537
12700
  }
12538
12701
 
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);
12702
+ li.remove();
12703
+ if (tabs[id].pinned) {
12704
+ pinnedTabsCount--;
12705
+ }
12706
+ if (options.onremove) {
12707
+ options.onremove(tabs[id]);
12708
+ }
12709
+ delete tabs[id];
12710
+ updateTabWidths();
12711
+ if (collapsibleMenu) {
12712
+ collapsibleMenu.remove();
12713
+ collapsibleMenu = null;
12714
+ }
12556
12715
  }
12557
12716
 
12558
12717
  function findPreviousVisibleTab(li) {
12559
12718
  if (!li) {
12560
- li = ul.find("li.active").parent();
12719
+ li = ul.find("li.active");
12561
12720
  }
12562
12721
  var previous = li.prev();
12563
12722
  while(previous.length > 0 && previous.hasClass("hide-tab")) {
@@ -12567,9 +12726,9 @@ RED.tabs = (function() {
12567
12726
  }
12568
12727
  function findNextVisibleTab(li) {
12569
12728
  if (!li) {
12570
- li = ul.find("li.active").parent();
12729
+ li = ul.find("li.active");
12571
12730
  }
12572
- var next = ul.find("li.active").next();
12731
+ var next = li.next();
12573
12732
  while(next.length > 0 && next.hasClass("hide-tab")) {
12574
12733
  next = next.next();
12575
12734
  }
@@ -14661,33 +14820,39 @@ RED.stack = (function() {
14661
14820
  ;RED.actions = (function() {
14662
14821
  var actions = {
14663
14822
 
14664
- }
14823
+ };
14665
14824
 
14666
- function addAction(name,handler) {
14825
+ function addAction(name,handler,options) {
14667
14826
  if (typeof handler !== 'function') {
14668
14827
  throw new Error("Action handler not a function");
14669
14828
  }
14670
14829
  if (actions[name]) {
14671
14830
  throw new Error("Cannot override existing action");
14672
14831
  }
14673
- actions[name] = handler;
14832
+ actions[name] = {
14833
+ handler: handler,
14834
+ options: options,
14835
+ };
14674
14836
  }
14675
14837
  function removeAction(name) {
14676
14838
  delete actions[name];
14677
14839
  }
14678
14840
  function getAction(name) {
14679
- return actions[name];
14841
+ return actions[name].handler;
14680
14842
  }
14681
14843
  function invokeAction() {
14682
14844
  var args = Array.prototype.slice.call(arguments);
14683
14845
  var name = args.shift();
14684
14846
  if (actions.hasOwnProperty(name)) {
14685
- actions[name].apply(null, args);
14847
+ var handler = actions[name].handler;
14848
+ handler.apply(null, args);
14686
14849
  }
14687
14850
  }
14688
14851
  function listActions() {
14689
14852
  var result = [];
14853
+ var missing = [];
14690
14854
  Object.keys(actions).forEach(function(action) {
14855
+ var def = actions[action];
14691
14856
  var shortcut = RED.keyboard.getShortcut(action);
14692
14857
  var isUser = false;
14693
14858
  if (shortcut) {
@@ -14695,13 +14860,38 @@ RED.stack = (function() {
14695
14860
  } else {
14696
14861
  isUser = !!RED.keyboard.getUserShortcut(action);
14697
14862
  }
14863
+ if (!def.label) {
14864
+ var name = action;
14865
+ var options = def.options;
14866
+ var key = options ? options.label : undefined;
14867
+ if (!key) {
14868
+ key = "action-list." +name.replace(/^.*:/,"");
14869
+ }
14870
+ var label = RED._(key);
14871
+ if (label === key) {
14872
+ // no translation. convert `name` to description
14873
+ label = name.replace(/(^.+:([a-z]))|(-([a-z]))/g, function() {
14874
+ if (arguments[5] === 0) {
14875
+ return arguments[2].toUpperCase();
14876
+ } else {
14877
+ return " "+arguments[4].toUpperCase();
14878
+ }
14879
+ });
14880
+ missing.push(key);
14881
+ }
14882
+ def.label = label;
14883
+ }
14884
+ //console.log("; missing:", missing);
14885
+
14698
14886
  result.push({
14699
14887
  id:action,
14700
14888
  scope:shortcut?shortcut.scope:undefined,
14701
14889
  key:shortcut?shortcut.key:undefined,
14702
- user:isUser
14703
- })
14704
- })
14890
+ user:isUser,
14891
+ label: def.label,
14892
+ options: def.options,
14893
+ });
14894
+ });
14705
14895
  return result;
14706
14896
  }
14707
14897
  return {
@@ -15047,6 +15237,16 @@ RED.deploy = (function() {
15047
15237
  var unknownNodes = [];
15048
15238
  var invalidNodes = [];
15049
15239
 
15240
+ RED.nodes.eachConfig(function(node) {
15241
+ if (!node.valid && !node.d) {
15242
+ invalidNodes.push(getNodeInfo(node));
15243
+ }
15244
+ if (node.type === "unknown") {
15245
+ if (unknownNodes.indexOf(node.name) == -1) {
15246
+ unknownNodes.push(node.name);
15247
+ }
15248
+ }
15249
+ });
15050
15250
  RED.nodes.eachNode(function(node) {
15051
15251
  if (!node.valid && !node.d) {
15052
15252
  invalidNodes.push(getNodeInfo(node));
@@ -17516,15 +17716,15 @@ RED.keyboard = (function() {
17516
17716
  "]": 221,
17517
17717
  "{": 219,// <- QWERTY specific
17518
17718
  "}": 221 // <- QWERTY specific
17519
- }
17719
+ };
17520
17720
  var metaKeyCodes = {
17521
17721
  16: true,
17522
17722
  17: true,
17523
17723
  18: true,
17524
17724
  91: true,
17525
17725
  93: true
17526
- }
17527
- var actionToKeyMap = {}
17726
+ };
17727
+ var actionToKeyMap = {};
17528
17728
  var defaultKeyMap = {};
17529
17729
 
17530
17730
  // FF generates some different keycodes because reasons.
@@ -17532,7 +17732,7 @@ RED.keyboard = (function() {
17532
17732
  59:186,
17533
17733
  61:187,
17534
17734
  173:189
17535
- }
17735
+ };
17536
17736
 
17537
17737
  function migrateOldKeymap() {
17538
17738
  // pre-0.18
@@ -17547,7 +17747,7 @@ RED.keyboard = (function() {
17547
17747
  }
17548
17748
 
17549
17749
  function getUserKey(action) {
17550
- return RED.settings.get('editor.keymap',{})[action]
17750
+ return RED.settings.get('editor.keymap',{})[action];
17551
17751
  }
17552
17752
 
17553
17753
  function mergeKeymaps(defaultKeymap, themeKeymap) {
@@ -17572,7 +17772,7 @@ RED.keyboard = (function() {
17572
17772
  scope:scope,
17573
17773
  key:key,
17574
17774
  user:false
17575
- })
17775
+ });
17576
17776
  }
17577
17777
  }
17578
17778
  }
@@ -17582,13 +17782,13 @@ RED.keyboard = (function() {
17582
17782
  if (themeKeymap.hasOwnProperty(action)) {
17583
17783
  if (!themeKeymap[action].key) {
17584
17784
  // No key for this action - default is no keybinding
17585
- delete mergedKeymap[action]
17785
+ delete mergedKeymap[action];
17586
17786
  } else {
17587
17787
  mergedKeymap[action] = [{
17588
17788
  scope: themeKeymap[action].scope || "*",
17589
17789
  key: themeKeymap[action].key,
17590
17790
  user: false
17591
- }]
17791
+ }];
17592
17792
  if (mergedKeymap[action][0].scope === "workspace") {
17593
17793
  mergedKeymap[action][0].scope = "red-ui-workspace";
17594
17794
  }
@@ -17646,7 +17846,7 @@ RED.keyboard = (function() {
17646
17846
  close: function() {
17647
17847
  RED.menu.refreshShortcuts();
17648
17848
  }
17649
- })
17849
+ });
17650
17850
  }
17651
17851
 
17652
17852
  function revertToDefault(action) {
@@ -17794,7 +17994,7 @@ RED.keyboard = (function() {
17794
17994
  scope:scope,
17795
17995
  key:key,
17796
17996
  user:false
17797
- }
17997
+ };
17798
17998
  }
17799
17999
  if (!ondown) {
17800
18000
  var userAction = getUserKey(cbdown);
@@ -17817,7 +18017,7 @@ RED.keyboard = (function() {
17817
18017
  }
17818
18018
  }
17819
18019
  } else {
17820
- keys.push([key,mod])
18020
+ keys.push([key,mod]);
17821
18021
  }
17822
18022
  var slot = handlers;
17823
18023
  for (i=0;i<keys.length;i++) {
@@ -17840,7 +18040,7 @@ RED.keyboard = (function() {
17840
18040
  //slot[key] = {scope: scope, ondown:cbdown};
17841
18041
  }
17842
18042
  slot.handlers = slot.handlers || [];
17843
- slot.handlers.push({scope:scope,ondown:cbdown})
18043
+ slot.handlers.push({scope:scope,ondown:cbdown});
17844
18044
  slot.scope = scope;
17845
18045
  slot.ondown = cbdown;
17846
18046
  }
@@ -17857,12 +18057,12 @@ RED.keyboard = (function() {
17857
18057
  if (parsedKey) {
17858
18058
  keys.push(parsedKey);
17859
18059
  } else {
17860
- console.log("Unrecognised key specifier:",key)
18060
+ console.log("Unrecognised key specifier:",key);
17861
18061
  return;
17862
18062
  }
17863
18063
  }
17864
18064
  } else {
17865
- keys.push([key,mod])
18065
+ keys.push([key,mod]);
17866
18066
  }
17867
18067
  var slot = handlers;
17868
18068
  for (i=0;i<keys.length;i++) {
@@ -17884,7 +18084,7 @@ RED.keyboard = (function() {
17884
18084
  }
17885
18085
  if (typeof slot.ondown === "string") {
17886
18086
  if (typeof modifiers === 'boolean' && modifiers) {
17887
- actionToKeyMap[slot.ondown] = {user: modifiers}
18087
+ actionToKeyMap[slot.ondown] = {user: modifiers};
17888
18088
  } else {
17889
18089
  delete actionToKeyMap[slot.ondown];
17890
18090
  }
@@ -17900,11 +18100,11 @@ RED.keyboard = (function() {
17900
18100
  function formatKey(key,plain) {
17901
18101
  var formattedKey = isMac?key.replace(/ctrl-?/,"&#8984;"):key;
17902
18102
  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;")
18103
+ formattedKey = formattedKey.replace(/shift-?/,"&#8679;");
18104
+ formattedKey = formattedKey.replace(/left/,"&#x2190;");
18105
+ formattedKey = formattedKey.replace(/up/,"&#x2191;");
18106
+ formattedKey = formattedKey.replace(/right/,"&#x2192;");
18107
+ formattedKey = formattedKey.replace(/down/,"&#x2193;");
17908
18108
  if (plain) {
17909
18109
  return formattedKey;
17910
18110
  }
@@ -17928,7 +18128,6 @@ RED.keyboard = (function() {
17928
18128
  var container = $(this);
17929
18129
  var object = container.data('data');
17930
18130
 
17931
-
17932
18131
  if (!container.hasClass('keyboard-shortcut-entry-expanded')) {
17933
18132
  endEditShortcut();
17934
18133
 
@@ -17952,7 +18151,7 @@ RED.keyboard = (function() {
17952
18151
  }
17953
18152
  $(this).toggleClass("input-error",!valid);
17954
18153
  okButton.attr("disabled",!valid);
17955
- })
18154
+ });
17956
18155
 
17957
18156
  var scopeSelect = $('<select><option value="*" data-i18n="keyboard.global"></option><option value="red-ui-workspace" data-i18n="keyboard.workspace"></option></select>').appendTo(scope);
17958
18157
  scopeSelect.i18n();
@@ -17962,7 +18161,7 @@ RED.keyboard = (function() {
17962
18161
  scopeSelect.val(object.scope||'*');
17963
18162
  scopeSelect.on("change", function() {
17964
18163
  keyInput.trigger("change");
17965
- })
18164
+ });
17966
18165
 
17967
18166
  var div = $('<div class="keyboard-shortcut-edit button-group-vertical"></div>').appendTo(scope);
17968
18167
  var okButton = $('<button class="red-ui-button red-ui-button-small"><i class="fa fa-check"></i></button>').appendTo(div);
@@ -17988,10 +18187,13 @@ RED.keyboard = (function() {
17988
18187
  id:object.id,
17989
18188
  scope:shortcut?shortcut.scope:undefined,
17990
18189
  key:shortcut?shortcut.key:undefined,
17991
- user:shortcut?shortcut.user:undefined
17992
- }
18190
+ user:shortcut?shortcut.user:undefined,
18191
+
18192
+ label: object.label,
18193
+ options: object.options,
18194
+ };
17993
18195
  buildShortcutRow(container,obj);
17994
- })
18196
+ });
17995
18197
 
17996
18198
  keyInput.trigger("focus");
17997
18199
  }
@@ -18026,7 +18228,7 @@ RED.keyboard = (function() {
18026
18228
  delete object.scope;
18027
18229
  } else {
18028
18230
  keyDiv.parent().removeClass("keyboard-shortcut-entry-unassigned");
18029
- keyDiv.append(RED.keyboard.formatKey(key))
18231
+ keyDiv.append(RED.keyboard.formatKey(key));
18030
18232
  $("<span>").text(scope).appendTo(scopeDiv);
18031
18233
  object.key = key;
18032
18234
  object.scope = scope;
@@ -18039,7 +18241,7 @@ RED.keyboard = (function() {
18039
18241
  userKeymap[object.id] = {
18040
18242
  scope:shortcut.scope,
18041
18243
  key:shortcut.key
18042
- }
18244
+ };
18043
18245
  RED.settings.set('editor.keymap',userKeymap);
18044
18246
  }
18045
18247
  }
@@ -18055,13 +18257,7 @@ RED.keyboard = (function() {
18055
18257
  var item = $('<div class="keyboard-shortcut-entry">').appendTo(container);
18056
18258
  container.data('data',object);
18057
18259
 
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
- });
18260
+ var text = object.label;
18065
18261
  var label = $('<div>').addClass("keyboard-shortcut-entry-text").text(text).appendTo(item);
18066
18262
 
18067
18263
  var user = $('<i class="fa fa-user"></i>').prependTo(label);
@@ -18102,8 +18298,9 @@ RED.keyboard = (function() {
18102
18298
  } else {
18103
18299
  filterValue = filterValue.replace(/\s/g,"");
18104
18300
  shortcutList.editableList('filter', function(data) {
18105
- return data.id.toLowerCase().replace(/^.*:/,"").replace("-","").indexOf(filterValue) > -1;
18106
- })
18301
+ var label = data.label.toLowerCase();
18302
+ return label.indexOf(filterValue) > -1;
18303
+ });
18107
18304
  }
18108
18305
  }
18109
18306
  });
@@ -18124,9 +18321,9 @@ RED.keyboard = (function() {
18124
18321
  });
18125
18322
  var shortcuts = RED.actions.list();
18126
18323
  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);
18324
+ var Akey = A.label;
18325
+ var Bkey = B.label;
18326
+ return Akey.localeCompare(Bkey);
18130
18327
  });
18131
18328
  knownShortcuts = new Set();
18132
18329
  shortcuts.forEach(function(s) {
@@ -18229,7 +18426,7 @@ RED.workspaces = (function() {
18229
18426
  var tabId = RED.nodes.id();
18230
18427
  do {
18231
18428
  workspaceIndex += 1;
18232
- } while ($("#red-ui-workspace-tabs a[title='"+RED._('workspace.defaultName',{number:workspaceIndex})+"']").size() !== 0);
18429
+ } while ($("#red-ui-workspace-tabs li[flowname='"+RED._('workspace.defaultName',{number:workspaceIndex})+"']").size() !== 0);
18233
18430
 
18234
18431
  ws = {
18235
18432
  type: "tab",
@@ -18242,12 +18439,15 @@ RED.workspaces = (function() {
18242
18439
  };
18243
18440
  RED.nodes.addWorkspace(ws,targetIndex);
18244
18441
  workspace_tabs.addTab(ws,targetIndex);
18442
+
18245
18443
  workspace_tabs.activateTab(tabId);
18246
18444
  if (!skipHistoryEntry) {
18247
18445
  RED.history.push({t:'add',workspaces:[ws],dirty:RED.nodes.dirty()});
18248
18446
  RED.nodes.dirty(true);
18249
18447
  }
18250
18448
  }
18449
+ $("#red-ui-tab-"+(ws.id.replace(".","-"))).attr("flowname",ws.label)
18450
+
18251
18451
  RED.view.focus();
18252
18452
  return ws;
18253
18453
  }
@@ -18371,10 +18571,20 @@ RED.workspaces = (function() {
18371
18571
  },
18372
18572
  onhide: function(tab) {
18373
18573
  hideStack.push(tab.id);
18574
+
18575
+ var hiddenTabs = JSON.parse(RED.settings.getLocal("hiddenTabs")||"{}");
18576
+ hiddenTabs[tab.id] = true;
18577
+ RED.settings.setLocal("hiddenTabs",JSON.stringify(hiddenTabs));
18578
+
18374
18579
  RED.events.emit("workspace:hide",{workspace: tab.id})
18375
18580
  },
18376
18581
  onshow: function(tab) {
18377
18582
  removeFromHideStack(tab.id);
18583
+
18584
+ var hiddenTabs = JSON.parse(RED.settings.getLocal("hiddenTabs")||"{}");
18585
+ delete hiddenTabs[tab.id];
18586
+ RED.settings.setLocal("hiddenTabs",JSON.stringify(hiddenTabs));
18587
+
18378
18588
  RED.events.emit("workspace:show",{workspace: tab.id})
18379
18589
  },
18380
18590
  minimumActiveTabWidth: 150,
@@ -18705,9 +18915,6 @@ RED.workspaces = (function() {
18705
18915
  }
18706
18916
  if (workspace_tabs.contains(id)) {
18707
18917
  workspace_tabs.hideTab(id);
18708
- var hiddenTabs = JSON.parse(RED.settings.getLocal("hiddenTabs")||"{}");
18709
- hiddenTabs[id] = true;
18710
- RED.settings.setLocal("hiddenTabs",JSON.stringify(hiddenTabs));
18711
18918
  }
18712
18919
  },
18713
18920
  isHidden: function(id) {
@@ -18735,14 +18942,11 @@ RED.workspaces = (function() {
18735
18942
  }
18736
18943
  workspace_tabs.activateTab(id);
18737
18944
  }
18738
- var hiddenTabs = JSON.parse(RED.settings.getLocal("hiddenTabs")||"{}");
18739
- delete hiddenTabs[id];
18740
- RED.settings.setLocal("hiddenTabs",JSON.stringify(hiddenTabs));
18741
18945
  },
18742
18946
  refresh: function() {
18743
18947
  RED.nodes.eachWorkspace(function(ws) {
18744
18948
  workspace_tabs.renameTab(ws.id,ws.label);
18745
-
18949
+ $("#red-ui-tab-"+(ws.id.replace(".","-"))).attr("flowname",ws.label)
18746
18950
  })
18747
18951
  RED.nodes.eachSubflow(function(sf) {
18748
18952
  if (workspace_tabs.contains(sf.id)) {
@@ -18873,7 +19077,6 @@ RED.view = (function() {
18873
19077
  var activeGroups = [];
18874
19078
  var dirtyGroups = {};
18875
19079
 
18876
- var selected_link = null;
18877
19080
  var mousedown_link = null;
18878
19081
  var mousedown_node = null;
18879
19082
  var mousedown_group = null;
@@ -18885,6 +19088,8 @@ RED.view = (function() {
18885
19088
  var mouse_mode = 0;
18886
19089
  var mousedown_group_handle = null;
18887
19090
  var lasso = null;
19091
+ var slicePath = null;
19092
+ var slicePathLast = null;
18888
19093
  var ghostNode = null;
18889
19094
  var showStatus = false;
18890
19095
  var lastClickNode = null;
@@ -18939,6 +19144,14 @@ RED.view = (function() {
18939
19144
  if (!setIds.has(node.id)) {
18940
19145
  set.push({n:node});
18941
19146
  setIds.add(node.id);
19147
+ var links = RED.nodes.getNodeLinks(node.id,PORT_TYPE_INPUT).concat(RED.nodes.getNodeLinks(node.id,PORT_TYPE_OUTPUT))
19148
+ for (var i=0,l=links.length;i<l;i++) {
19149
+ var link = links[i]
19150
+ if (link.source === node && setIds.has(link.target.id) ||
19151
+ link.target === node && setIds.has(link.source.id)) {
19152
+ selectedLinks.add(link)
19153
+ }
19154
+ }
18942
19155
  }
18943
19156
  }
18944
19157
  },
@@ -18955,6 +19168,10 @@ RED.view = (function() {
18955
19168
  }
18956
19169
  }
18957
19170
  }
19171
+ var links = RED.nodes.getNodeLinks(node.id,PORT_TYPE_INPUT).concat(RED.nodes.getNodeLinks(node.id,PORT_TYPE_OUTPUT))
19172
+ for (var i=0,l=links.length;i<l;i++) {
19173
+ selectedLinks.remove(links[i]);
19174
+ }
18958
19175
  }
18959
19176
  },
18960
19177
  clear: function() {
@@ -18969,6 +19186,31 @@ RED.view = (function() {
18969
19186
  return api;
18970
19187
  })();
18971
19188
 
19189
+ var selectedLinks = (function() {
19190
+ var links = new Set();
19191
+ return {
19192
+ add: function(link) {
19193
+ links.add(link);
19194
+ link.selected = true;
19195
+ },
19196
+ remove: function(link) {
19197
+ links.delete(link);
19198
+ link.selected = false;
19199
+ },
19200
+ clear: function() {
19201
+ links.forEach(function(link) { link.selected = false })
19202
+ links.clear();
19203
+ },
19204
+ length: function() {
19205
+ return links.size;
19206
+ },
19207
+ forEach: function(func) { links.forEach(func) },
19208
+ has: function(link) { return links.has(link) },
19209
+ toArray: function() { return Array.from(links) }
19210
+ }
19211
+ })();
19212
+
19213
+
18972
19214
  function init() {
18973
19215
 
18974
19216
  chart = $("#red-ui-workspace-chart");
@@ -19003,6 +19245,12 @@ RED.view = (function() {
19003
19245
  }
19004
19246
  } else if (mouse_mode === RED.state.PANNING && d3.event.buttons !== 4) {
19005
19247
  resetMouseVars();
19248
+ } else if (slicePath) {
19249
+ if (d3.event.buttons !== 2) {
19250
+ slicePath.remove();
19251
+ slicePath = null;
19252
+ resetMouseVars()
19253
+ }
19006
19254
  }
19007
19255
  })
19008
19256
  .on("touchend", function() {
@@ -19222,26 +19470,56 @@ RED.view = (function() {
19222
19470
  var historyEvent = result.historyEvent;
19223
19471
  var nn = result.node;
19224
19472
 
19473
+ RED.nodes.add(nn);
19474
+
19225
19475
  var showLabel = RED.utils.getMessageProperty(RED.settings.get('editor'),"view.view-node-show-label");
19226
19476
  if (showLabel !== undefined && (nn._def.hasOwnProperty("showLabel")?nn._def.showLabel:true) && !nn._def.defaults.hasOwnProperty("l")) {
19227
19477
  nn.l = showLabel;
19228
19478
  }
19229
19479
 
19230
19480
  var helperOffset = d3.touches(ui.helper.get(0))[0]||d3.mouse(ui.helper.get(0));
19481
+ var helperWidth = ui.helper.width();
19482
+ var helperHeight = ui.helper.height();
19231
19483
  var mousePos = d3.touches(this)[0]||d3.mouse(this);
19232
19484
 
19233
- mousePos[1] += this.scrollTop + ((nn.h/2)-helperOffset[1]);
19234
- mousePos[0] += this.scrollLeft + ((nn.w/2)-helperOffset[0]);
19485
+ try {
19486
+ var isLink = (nn.type === "link in" || nn.type === "link out")
19487
+ var hideLabel = nn.hasOwnProperty('l')?!nn.l : isLink;
19488
+
19489
+ var label = RED.utils.getNodeLabel(nn, nn.type);
19490
+ var labelParts = getLabelParts(label, "red-ui-flow-node-label");
19491
+ if (hideLabel) {
19492
+ nn.w = node_height;
19493
+ nn.h = Math.max(node_height,(nn.outputs || 0) * 15);
19494
+ } else {
19495
+ nn.w = Math.max(node_width,20*(Math.ceil((labelParts.width+50+(nn._def.inputs>0?7:0))/20)) );
19496
+ nn.h = Math.max(6+24*labelParts.lines.length,(nn.outputs || 0) * 15, 30);
19497
+ }
19498
+ } catch(err) {
19499
+ }
19500
+
19501
+ mousePos[1] += this.scrollTop + ((helperHeight/2)-helperOffset[1]);
19502
+ mousePos[0] += this.scrollLeft + ((helperWidth/2)-helperOffset[0]);
19235
19503
  mousePos[1] /= scaleFactor;
19236
19504
  mousePos[0] /= scaleFactor;
19237
19505
 
19238
- if (snapGrid) {
19239
- mousePos[0] = gridSize*(Math.ceil(mousePos[0]/gridSize));
19240
- mousePos[1] = gridSize*(Math.ceil(mousePos[1]/gridSize));
19241
- }
19242
19506
  nn.x = mousePos[0];
19243
19507
  nn.y = mousePos[1];
19244
19508
 
19509
+ if (snapGrid) {
19510
+ var gridOffset = [0,0];
19511
+ var offsetLeft = nn.x-(gridSize*Math.round((nn.x-nn.w/2)/gridSize)+nn.w/2);
19512
+ var offsetRight = nn.x-(gridSize*Math.round((nn.x+nn.w/2)/gridSize)-nn.w/2);
19513
+ if (Math.abs(offsetLeft) < Math.abs(offsetRight)) {
19514
+ gridOffset[0] = offsetLeft
19515
+ } else {
19516
+ gridOffset[0] = offsetRight
19517
+ }
19518
+ gridOffset[1] = nn.y-(gridSize*Math.round(nn.y/gridSize));
19519
+ nn.x -= gridOffset[0];
19520
+ nn.y -= gridOffset[1];
19521
+ }
19522
+
19245
19523
  var spliceLink = $(ui.helper).data("splice");
19246
19524
  if (spliceLink) {
19247
19525
  // TODO: DRY - droppable/nodeMouseDown/canvasMouseUp/showQuickAddDialog
@@ -19262,7 +19540,6 @@ RED.view = (function() {
19262
19540
  historyEvent.removedLinks = [spliceLink];
19263
19541
  }
19264
19542
 
19265
- RED.nodes.add(nn);
19266
19543
 
19267
19544
  var group = $(ui.helper).data("group");
19268
19545
  if (group) {
@@ -19312,6 +19589,8 @@ RED.view = (function() {
19312
19589
  RED.actions.add("core:cut-selection-to-internal-clipboard",function(){copySelection();deleteSelection();});
19313
19590
  RED.actions.add("core:paste-from-internal-clipboard",function(){importNodes(clipboard,{generateIds: true});});
19314
19591
 
19592
+ RED.actions.add("core:detach-selected-nodes", function() { detachSelectedNodes() })
19593
+
19315
19594
  RED.events.on("view:selection-changed", function(selection) {
19316
19595
  var hasSelection = (selection.nodes && selection.nodes.length > 0);
19317
19596
  var hasMultipleSelection = hasSelection && selection.nodes.length > 1;
@@ -19334,6 +19613,7 @@ RED.view = (function() {
19334
19613
  })
19335
19614
 
19336
19615
  RED.actions.add("core:delete-selection",deleteSelection);
19616
+ RED.actions.add("core:delete-selection-and-reconnect",function() { deleteSelection(true) });
19337
19617
  RED.actions.add("core:edit-selected-node",editSelection);
19338
19618
  RED.actions.add("core:go-to-selection",function() {
19339
19619
  if (movingSet.length() > 0) {
@@ -19406,12 +19686,45 @@ RED.view = (function() {
19406
19686
  },
19407
19687
  tooltip: function(d) {
19408
19688
  if (d.validationErrors && d.validationErrors.length > 0) {
19409
- return RED._("editor.errors.invalidProperties")+"\n - "+d.validationErrors.join("\n - ")
19689
+ return RED._("editor.errors.invalidProperties")+"\n - "+d.validationErrors.join("\n - ")
19410
19690
  }
19411
19691
  },
19412
19692
  show: function(n) { return !n.valid }
19413
19693
  })
19414
19694
 
19695
+ if (RED.settings.get("editor.view.view-store-zoom")) {
19696
+ var userZoomLevel = parseFloat(RED.settings.getLocal('zoom-level'))
19697
+ if (!isNaN(userZoomLevel)) {
19698
+ scaleFactor = userZoomLevel
19699
+ }
19700
+ }
19701
+
19702
+ var onScrollTimer = null;
19703
+ function storeScrollPosition() {
19704
+ workspaceScrollPositions[RED.workspaces.active()] = {
19705
+ left:chart.scrollLeft(),
19706
+ top:chart.scrollTop()
19707
+ };
19708
+ RED.settings.setLocal('scroll-positions', JSON.stringify(workspaceScrollPositions) )
19709
+ }
19710
+ chart.on("scroll", function() {
19711
+ if (RED.settings.get("editor.view.view-store-position")) {
19712
+ if (onScrollTimer) {
19713
+ clearTimeout(onScrollTimer)
19714
+ }
19715
+ onScrollTimer = setTimeout(storeScrollPosition, 200);
19716
+ }
19717
+ })
19718
+
19719
+ if (RED.settings.get("editor.view.view-store-position")) {
19720
+ var scrollPositions = RED.settings.getLocal('scroll-positions')
19721
+ if (scrollPositions) {
19722
+ try {
19723
+ workspaceScrollPositions = JSON.parse(scrollPositions)
19724
+ } catch(err) {
19725
+ }
19726
+ }
19727
+ }
19415
19728
  }
19416
19729
 
19417
19730
 
@@ -19719,7 +20032,7 @@ RED.view = (function() {
19719
20032
  return;
19720
20033
  }
19721
20034
  if (!mousedown_node && !mousedown_link && !mousedown_group) {
19722
- selected_link = null;
20035
+ selectedLinks.clear();
19723
20036
  updateSelection();
19724
20037
  }
19725
20038
  if (mouse_mode === 0) {
@@ -19728,19 +20041,18 @@ RED.view = (function() {
19728
20041
  lasso = null;
19729
20042
  }
19730
20043
  }
19731
- if (mouse_mode === 0 || mouse_mode === RED.state.QUICK_JOINING) {
19732
- if (d3.event.metaKey || d3.event.ctrlKey) {
19733
- d3.event.stopPropagation();
19734
- clearSelection();
19735
- point = d3.mouse(this);
19736
- var clickedGroup = getGroupAt(point[0],point[1]);
19737
- if (drag_lines.length > 0) {
19738
- clickedGroup = clickedGroup || RED.nodes.group(drag_lines[0].node.g)
19739
- }
19740
- showQuickAddDialog({position:point, group:clickedGroup});
20044
+ if ((mouse_mode === 0 || mouse_mode === RED.state.QUICK_JOINING) && (d3.event.touches || d3.event.button === 0) && (d3.event.metaKey || d3.event.ctrlKey)) {
20045
+ // Trigger quick add dialog
20046
+ d3.event.stopPropagation();
20047
+ clearSelection();
20048
+ point = d3.mouse(this);
20049
+ var clickedGroup = getGroupAt(point[0],point[1]);
20050
+ if (drag_lines.length > 0) {
20051
+ clickedGroup = clickedGroup || RED.nodes.group(drag_lines[0].node.g)
19741
20052
  }
19742
- }
19743
- if (mouse_mode === 0 && !(d3.event.metaKey || d3.event.ctrlKey)) {
20053
+ showQuickAddDialog({position:point, group:clickedGroup});
20054
+ } else if (mouse_mode === 0 && (d3.event.touches || d3.event.button === 0) && !(d3.event.metaKey || d3.event.ctrlKey)) {
20055
+ // Tigger lasso
19744
20056
  if (!touchStartTime) {
19745
20057
  point = d3.mouse(this);
19746
20058
  lasso = eventLayer.append("rect")
@@ -19755,6 +20067,13 @@ RED.view = (function() {
19755
20067
  .attr("class","nr-ui-view-lasso");
19756
20068
  d3.event.preventDefault();
19757
20069
  }
20070
+ } else if (mouse_mode === 0 && d3.event.button === 2 && (d3.event.metaKey || d3.event.ctrlKey)) {
20071
+ clearSelection();
20072
+ mouse_mode = RED.state.SLICING;
20073
+ point = d3.mouse(this);
20074
+ slicePath = eventLayer.append("path").attr("class","nr-ui-view-slice").attr("d",`M${point[0]} ${point[1]}`)
20075
+ slicePathLast = point;
20076
+ RED.view.redraw();
19758
20077
  }
19759
20078
  }
19760
20079
 
@@ -20151,6 +20470,17 @@ RED.view = (function() {
20151
20470
  .attr("height",h)
20152
20471
  ;
20153
20472
  return;
20473
+ } else if (mouse_mode === RED.state.SLICING) {
20474
+ if (slicePath) {
20475
+ var delta = Math.max(1,Math.abs(slicePathLast[0]-mouse_position[0]))*Math.max(1,Math.abs(slicePathLast[1]-mouse_position[1]))
20476
+ if (delta > 20) {
20477
+ var currentPath = slicePath.attr("d")
20478
+ currentPath += " L"+mouse_position[0]+" "+mouse_position[1]
20479
+ slicePath.attr("d",currentPath);
20480
+ slicePathLast = mouse_position
20481
+ }
20482
+ }
20483
+ return
20154
20484
  }
20155
20485
 
20156
20486
  if (mouse_mode === RED.state.SELECTING_NODE) {
@@ -20158,7 +20488,7 @@ RED.view = (function() {
20158
20488
  return;
20159
20489
  }
20160
20490
 
20161
- if (mouse_mode != RED.state.QUICK_JOINING && mouse_mode != RED.state.IMPORT_DRAGGING && !mousedown_node && !mousedown_group && selected_link == null) {
20491
+ 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) {
20162
20492
  return;
20163
20493
  }
20164
20494
 
@@ -20182,16 +20512,18 @@ RED.view = (function() {
20182
20512
  // Get all the wires we need to detach.
20183
20513
  var links = [];
20184
20514
  var existingLinks = [];
20185
- if (selected_link &&
20186
- ((mousedown_port_type === PORT_TYPE_OUTPUT &&
20187
- selected_link.source === mousedown_node &&
20188
- selected_link.sourcePort === mousedown_port_index
20189
- ) ||
20190
- (mousedown_port_type === PORT_TYPE_INPUT &&
20191
- selected_link.target === mousedown_node
20192
- ))
20193
- ) {
20194
- existingLinks = [selected_link];
20515
+ if (selectedLinks.length() > 0) {
20516
+ selectedLinks.forEach(function(link) {
20517
+ if (((mousedown_port_type === PORT_TYPE_OUTPUT &&
20518
+ link.source === mousedown_node &&
20519
+ link.sourcePort === mousedown_port_index
20520
+ ) ||
20521
+ (mousedown_port_type === PORT_TYPE_INPUT &&
20522
+ link.target === mousedown_node
20523
+ ))) {
20524
+ existingLinks.push(link);
20525
+ }
20526
+ })
20195
20527
  } else {
20196
20528
  var filter;
20197
20529
  if (mousedown_port_type === PORT_TYPE_OUTPUT) {
@@ -20229,7 +20561,7 @@ RED.view = (function() {
20229
20561
  } else if (mousedown_node && !quickAddLink) {
20230
20562
  showDragLines([{node:mousedown_node,port:mousedown_port_index,portType:mousedown_port_type}]);
20231
20563
  }
20232
- selected_link = null;
20564
+ selectedLinks.clear();
20233
20565
  }
20234
20566
  mousePos = mouse_position;
20235
20567
  for (i=0;i<drag_lines.length;i++) {
@@ -20261,7 +20593,7 @@ RED.view = (function() {
20261
20593
  RED.nodes.filterLinks({ target: node.n }).length === 0;
20262
20594
  }
20263
20595
  }
20264
- } else if (mouse_mode == RED.state.MOVING_ACTIVE || mouse_mode == RED.state.IMPORT_DRAGGING) {
20596
+ } else if (mouse_mode == RED.state.MOVING_ACTIVE || mouse_mode == RED.state.IMPORT_DRAGGING || mouse_mode == RED.state.DETACHED_DRAGGING) {
20265
20597
  mousePos = mouse_position;
20266
20598
  var minX = 0;
20267
20599
  var minY = 0;
@@ -20325,8 +20657,16 @@ RED.view = (function() {
20325
20657
  gridOffset[0] = node.n.x-(gridSize*Math.floor(node.n.x/gridSize))-gridSize/2;
20326
20658
  gridOffset[1] = node.n.y-(gridSize*Math.floor(node.n.y/gridSize))-gridSize/2;
20327
20659
  } else {
20328
- gridOffset[0] = node.n.x-(gridSize*Math.floor((node.n.x-node.n.w/2)/gridSize)+node.n.w/2);
20329
- gridOffset[1] = node.n.y-(gridSize*Math.floor(node.n.y/gridSize));
20660
+ var offsetLeft = node.n.x-(gridSize*Math.round((node.n.x-node.n.w/2)/gridSize)+node.n.w/2);
20661
+ var offsetRight = node.n.x-(gridSize*Math.round((node.n.x+node.n.w/2)/gridSize)-node.n.w/2);
20662
+ // gridOffset[0] = node.n.x-(gridSize*Math.floor((node.n.x-node.n.w/2)/gridSize)+node.n.w/2);
20663
+ if (Math.abs(offsetLeft) < Math.abs(offsetRight)) {
20664
+ gridOffset[0] = offsetLeft
20665
+ } else {
20666
+ gridOffset[0] = offsetRight
20667
+ }
20668
+ gridOffset[1] = node.n.y-(gridSize*Math.round(node.n.y/gridSize));
20669
+ // console.log(offsetLeft, offsetRight);
20330
20670
  }
20331
20671
  if (gridOffset[0] !== 0 || gridOffset[1] !== 0) {
20332
20672
  for (i = 0; i<movingSet.length(); i++) {
@@ -20546,6 +20886,11 @@ RED.view = (function() {
20546
20886
  } else if (mouse_mode == RED.state.DEFAULT && mousedown_link == null && !d3.event.ctrlKey && !d3.event.metaKey ) {
20547
20887
  clearSelection();
20548
20888
  updateSelection();
20889
+ } else if (slicePath) {
20890
+ deleteSelection();
20891
+ slicePath.remove();
20892
+ slicePath = null;
20893
+ RED.view.redraw(true);
20549
20894
  }
20550
20895
  if (mouse_mode == RED.state.MOVING_ACTIVE) {
20551
20896
  if (movingSet.length() > 0) {
@@ -20617,10 +20962,30 @@ RED.view = (function() {
20617
20962
  // movingSet.add(mousedown_node);
20618
20963
  // }
20619
20964
  // }
20620
- if (mouse_mode == RED.state.MOVING || mouse_mode == RED.state.MOVING_ACTIVE) {
20965
+ if (mouse_mode == RED.state.MOVING || mouse_mode == RED.state.MOVING_ACTIVE || mouse_mode == RED.state.DETACHED_DRAGGING) {
20621
20966
  // if (mousedown_node) {
20622
20967
  // delete mousedown_node.gSelected;
20623
20968
  // }
20969
+ if (mouse_mode === RED.state.DETACHED_DRAGGING) {
20970
+ var ns = [];
20971
+ for (var j=0;j<movingSet.length();j++) {
20972
+ var n = movingSet.get(j);
20973
+ if (n.ox !== n.n.x || n.oy !== n.n.y) {
20974
+ ns.push({n:n.n,ox:n.ox,oy:n.oy,moved:n.n.moved});
20975
+ n.n.dirty = true;
20976
+ n.n.moved = true;
20977
+ }
20978
+ }
20979
+ var detachEvent = RED.history.peek();
20980
+ // The last event in the stack *should* be a multi-event from
20981
+ // where the links were added/removed
20982
+ var historyEvent = {t:"move",nodes:ns,dirty:RED.nodes.dirty()}
20983
+ if (detachEvent.t === "multi") {
20984
+ detachEvent.events.push(historyEvent)
20985
+ } else {
20986
+ RED.history.push(historyEvent)
20987
+ }
20988
+ }
20624
20989
  for (i=0;i<movingSet.length();i++) {
20625
20990
  var node = movingSet.get(i);
20626
20991
  delete node.ox;
@@ -20647,6 +21012,7 @@ RED.view = (function() {
20647
21012
  }
20648
21013
  function zoomZero() { zoomView(1); }
20649
21014
 
21015
+
20650
21016
  function zoomView(factor) {
20651
21017
  var screenSize = [chart.width(),chart.height()];
20652
21018
  var scrollPos = [chart.scrollLeft(),chart.scrollTop()];
@@ -20659,16 +21025,38 @@ RED.view = (function() {
20659
21025
 
20660
21026
  RED.view.navigator.resize();
20661
21027
  redraw();
21028
+ if (RED.settings.get("editor.view.view-store-zoom")) {
21029
+ RED.settings.setLocal('zoom-level', factor.toFixed(1))
21030
+ }
20662
21031
  }
20663
21032
 
20664
21033
  function selectNone() {
20665
21034
  if (mouse_mode === RED.state.MOVING || mouse_mode === RED.state.MOVING_ACTIVE) {
20666
21035
  return;
20667
21036
  }
20668
- if (mouse_mode === RED.state.IMPORT_DRAGGING) {
21037
+ if (mouse_mode === RED.state.DETACHED_DRAGGING) {
21038
+ for (var j=0;j<movingSet.length();j++) {
21039
+ var n = movingSet.get(j);
21040
+ n.n.x = n.ox;
21041
+ n.n.y = n.oy;
21042
+ }
20669
21043
  clearSelection();
20670
21044
  RED.history.pop();
20671
21045
  mouse_mode = 0;
21046
+ } else if (mouse_mode === RED.state.IMPORT_DRAGGING) {
21047
+ clearSelection();
21048
+ RED.history.pop();
21049
+ mouse_mode = 0;
21050
+ } else if (mouse_mode === RED.state.SLICING) {
21051
+ if (slicePath) {
21052
+ slicePath.remove();
21053
+ slicePath = null;
21054
+ resetMouseVars()
21055
+ }
21056
+ clearSelection();
21057
+ } else if (lasso) {
21058
+ lasso.remove();
21059
+ lasso = null;
20672
21060
  } else if (activeGroup) {
20673
21061
  exitActiveGroup()
20674
21062
  } else {
@@ -20680,6 +21068,7 @@ RED.view = (function() {
20680
21068
  if (mouse_mode === RED.state.SELECTING_NODE && selectNodesOptions.single) {
20681
21069
  return;
20682
21070
  }
21071
+ selectedLinks.clear();
20683
21072
 
20684
21073
  if (activeGroup) {
20685
21074
  var ag = activeGroup;
@@ -20751,7 +21140,6 @@ RED.view = (function() {
20751
21140
  }
20752
21141
  }
20753
21142
  }
20754
- selected_link = null;
20755
21143
  if (mouse_mode !== RED.state.SELECTING_NODE) {
20756
21144
  updateSelection();
20757
21145
  }
@@ -20766,7 +21154,7 @@ RED.view = (function() {
20766
21154
  n.n.selected = false;
20767
21155
  }
20768
21156
  movingSet.clear();
20769
- selected_link = null;
21157
+ selectedLinks.clear();
20770
21158
  if (activeGroup) {
20771
21159
  activeGroup.active = false
20772
21160
  activeGroup.dirty = true;
@@ -20860,12 +21248,16 @@ RED.view = (function() {
20860
21248
  }
20861
21249
  }
20862
21250
  }
20863
- if (activeFlowLinks.length === 0 && selected_link !== null && selected_link.link) {
20864
- activeLinks.push(selected_link);
20865
- activeLinkNodes[selected_link.source.id] = selected_link.source;
20866
- selected_link.source.dirty = true;
20867
- activeLinkNodes[selected_link.target.id] = selected_link.target;
20868
- selected_link.target.dirty = true;
21251
+ if (activeFlowLinks.length === 0 && selectedLinks.length() > 0) {
21252
+ selectedLinks.forEach(function(link) {
21253
+ if (link.link) {
21254
+ activeLinks.push(link);
21255
+ activeLinkNodes[link.source.id] = link.source;
21256
+ link.source.dirty = true;
21257
+ activeLinkNodes[link.target.id] = link.target;
21258
+ link.target.dirty = true;
21259
+ }
21260
+ })
20869
21261
  }
20870
21262
  } else {
20871
21263
  selection.flows = workspaceSelection;
@@ -20876,6 +21268,10 @@ RED.view = (function() {
20876
21268
  return value.map(function(n) { return n.id })
20877
21269
  } else if (key === 'link') {
20878
21270
  return value.source.id+":"+value.sourcePort+":"+value.target.id;
21271
+ } else if (key === 'links') {
21272
+ return value.map(function(link) {
21273
+ return link.source.id+":"+link.sourcePort+":"+link.target.id;
21274
+ });
20879
21275
  }
20880
21276
  return value;
20881
21277
  });
@@ -20897,7 +21293,7 @@ RED.view = (function() {
20897
21293
  }
20898
21294
  }
20899
21295
  }
20900
- function deleteSelection() {
21296
+ function deleteSelection(reconnectWires) {
20901
21297
  if (mouse_mode === RED.state.SELECTING_NODE) {
20902
21298
  return;
20903
21299
  }
@@ -20945,7 +21341,7 @@ RED.view = (function() {
20945
21341
  updateActiveNodes();
20946
21342
  updateSelection();
20947
21343
  redraw();
20948
- } else if (movingSet.length() > 0 || selected_link != null) {
21344
+ } else if (movingSet.length() > 0 || selectedLinks.length() > 0) {
20949
21345
  var result;
20950
21346
  var node;
20951
21347
  var removedNodes = [];
@@ -20955,6 +21351,16 @@ RED.view = (function() {
20955
21351
  var removedSubflowInputs = [];
20956
21352
  var removedSubflowStatus;
20957
21353
  var subflowInstances = [];
21354
+ var historyEvents = [];
21355
+
21356
+ if (reconnectWires) {
21357
+ var reconnectResult = RED.nodes.detachNodes(movingSet.nodes())
21358
+ var addedLinks = reconnectResult.newLinks;
21359
+ if (addedLinks.length > 0) {
21360
+ historyEvents.push({ t:'add', links: addedLinks })
21361
+ }
21362
+ removedLinks = removedLinks.concat(reconnectResult.removedLinks)
21363
+ }
20958
21364
 
20959
21365
  var startDirty = RED.nodes.dirty();
20960
21366
  var startChanged = false;
@@ -21044,71 +21450,71 @@ RED.view = (function() {
21044
21450
  RED.nodes.dirty(true);
21045
21451
  }
21046
21452
  }
21047
- var historyEvent;
21048
-
21049
- if (selected_link && selected_link.link) {
21050
- var sourceId = selected_link.source.id;
21051
- var targetId = selected_link.target.id;
21052
- var sourceIdIndex = selected_link.target.links.indexOf(sourceId);
21053
- var targetIdIndex = selected_link.source.links.indexOf(targetId);
21054
21453
 
21055
- historyEvent = {
21056
- t:"multi",
21057
- events: [
21058
- {
21454
+ if (selectedLinks.length() > 0) {
21455
+ selectedLinks.forEach(function(link) {
21456
+ if (link.link) {
21457
+ var sourceId = link.source.id;
21458
+ var targetId = link.target.id;
21459
+ var sourceIdIndex = link.target.links.indexOf(sourceId);
21460
+ var targetIdIndex = link.source.links.indexOf(targetId);
21461
+ historyEvents.push({
21059
21462
  t: "edit",
21060
- node: selected_link.source,
21061
- changed: selected_link.source.changed,
21463
+ node: link.source,
21464
+ changed: link.source.changed,
21062
21465
  changes: {
21063
- links: $.extend(true,{},{v:selected_link.source.links}).v
21466
+ links: $.extend(true,{},{v:link.source.links}).v
21064
21467
  }
21065
- },
21066
- {
21468
+ })
21469
+ historyEvents.push({
21067
21470
  t: "edit",
21068
- node: selected_link.target,
21069
- changed: selected_link.target.changed,
21471
+ node: link.target,
21472
+ changed: link.target.changed,
21070
21473
  changes: {
21071
- links: $.extend(true,{},{v:selected_link.target.links}).v
21474
+ links: $.extend(true,{},{v:link.target.links}).v
21072
21475
  }
21073
- }
21074
-
21075
- ],
21076
- dirty:RED.nodes.dirty()
21077
- }
21078
- RED.nodes.dirty(true);
21079
- selected_link.source.changed = true;
21080
- selected_link.target.changed = true;
21081
- selected_link.target.links.splice(sourceIdIndex,1);
21082
- selected_link.source.links.splice(targetIdIndex,1);
21083
- selected_link.source.dirty = true;
21084
- selected_link.target.dirty = true;
21476
+ })
21477
+ link.source.changed = true;
21478
+ link.target.changed = true;
21479
+ link.target.links.splice(sourceIdIndex,1);
21480
+ link.source.links.splice(targetIdIndex,1);
21481
+ link.source.dirty = true;
21482
+ link.target.dirty = true;
21085
21483
 
21484
+ } else {
21485
+ RED.nodes.removeLink(link);
21486
+ removedLinks.push(link);
21487
+ }
21488
+ })
21489
+ }
21490
+ RED.nodes.dirty(true);
21491
+ var historyEvent = {
21492
+ t:"delete",
21493
+ nodes:removedNodes,
21494
+ links:removedLinks,
21495
+ groups: removedGroups,
21496
+ subflowOutputs:removedSubflowOutputs,
21497
+ subflowInputs:removedSubflowInputs,
21498
+ subflow: {
21499
+ id: activeSubflow?activeSubflow.id:undefined,
21500
+ instances: subflowInstances
21501
+ },
21502
+ dirty:startDirty
21503
+ };
21504
+ if (removedSubflowStatus) {
21505
+ historyEvent.subflow.status = removedSubflowStatus;
21506
+ }
21507
+ if (historyEvents.length > 0) {
21508
+ historyEvents.unshift(historyEvent);
21509
+ RED.history.push({
21510
+ t:"multi",
21511
+ events: historyEvents
21512
+ })
21086
21513
  } else {
21087
- if (selected_link) {
21088
- RED.nodes.removeLink(selected_link);
21089
- removedLinks.push(selected_link);
21090
- }
21091
- RED.nodes.dirty(true);
21092
- historyEvent = {
21093
- t:"delete",
21094
- nodes:removedNodes,
21095
- links:removedLinks,
21096
- groups: removedGroups,
21097
- subflowOutputs:removedSubflowOutputs,
21098
- subflowInputs:removedSubflowInputs,
21099
- subflow: {
21100
- id: activeSubflow?activeSubflow.id:undefined,
21101
- instances: subflowInstances
21102
- },
21103
- dirty:startDirty
21104
- };
21105
- if (removedSubflowStatus) {
21106
- historyEvent.subflow.status = removedSubflowStatus;
21107
- }
21514
+ RED.history.push(historyEvent);
21108
21515
  }
21109
- RED.history.push(historyEvent);
21110
21516
 
21111
- selected_link = null;
21517
+ selectedLinks.clear();
21112
21518
  updateActiveNodes();
21113
21519
  updateSelection();
21114
21520
  redraw();
@@ -21185,6 +21591,28 @@ RED.view = (function() {
21185
21591
  }
21186
21592
  }
21187
21593
 
21594
+
21595
+ function detachSelectedNodes() {
21596
+ var selection = RED.view.selection();
21597
+ if (selection.nodes) {
21598
+ const {newLinks, removedLinks} = RED.nodes.detachNodes(selection.nodes);
21599
+ if (removedLinks.length || newLinks.length) {
21600
+ RED.history.push({
21601
+ t: "multi",
21602
+ events: [
21603
+ { t:'delete', links: removedLinks },
21604
+ { t:'add', links: newLinks }
21605
+ ],
21606
+ dirty: RED.nodes.dirty()
21607
+ })
21608
+ RED.nodes.dirty(true)
21609
+ }
21610
+ prepareDrag([selection.nodes[0].x,selection.nodes[0].y]);
21611
+ mouse_mode = RED.state.DETACHED_DRAGGING;
21612
+ RED.view.redraw(true);
21613
+ }
21614
+ }
21615
+
21188
21616
  function calculateTextWidth(str, className) {
21189
21617
  var result = convertLineBreakCharacter(str);
21190
21618
  var width = 0;
@@ -21271,7 +21699,7 @@ RED.view = (function() {
21271
21699
  activeHoverGroup.hovered = false;
21272
21700
  activeHoverGroup = null;
21273
21701
  }
21274
- d3.select(".red-ui-flow-link-splice").classed("red-ui-flow-link-splice",false);
21702
+ d3.selectAll(".red-ui-flow-link-splice").classed("red-ui-flow-link-splice",false);
21275
21703
  if (spliceTimer) {
21276
21704
  clearTimeout(spliceTimer);
21277
21705
  spliceTimer = null;
@@ -21519,10 +21947,13 @@ RED.view = (function() {
21519
21947
  } else {
21520
21948
  resetMouseVars();
21521
21949
  }
21522
- selected_link = select_link;
21523
21950
  mousedown_link = select_link;
21524
21951
  if (select_link) {
21952
+ selectedLinks.clear();
21953
+ selectedLinks.add(select_link);
21525
21954
  updateSelection();
21955
+ } else {
21956
+ selectedLinks.clear();
21526
21957
  }
21527
21958
  }
21528
21959
  redraw();
@@ -21531,7 +21962,10 @@ RED.view = (function() {
21531
21962
 
21532
21963
  resetMouseVars();
21533
21964
  hideDragLines();
21534
- selected_link = select_link;
21965
+ if (select_link) {
21966
+ selectedLinks.clear();
21967
+ selectedLinks.add(select_link);
21968
+ }
21535
21969
  mousedown_link = select_link;
21536
21970
  if (select_link) {
21537
21971
  updateSelection();
@@ -21704,10 +22138,13 @@ RED.view = (function() {
21704
22138
  msn.dx = msn.n.x-mouse[0];
21705
22139
  msn.dy = msn.n.y-mouse[1];
21706
22140
  }
21707
-
21708
- mouse_offset = d3.mouse(document.body);
21709
- if (isNaN(mouse_offset[0])) {
21710
- mouse_offset = d3.touches(document.body)[0];
22141
+ try {
22142
+ mouse_offset = d3.mouse(document.body);
22143
+ if (isNaN(mouse_offset[0])) {
22144
+ mouse_offset = d3.touches(document.body)[0];
22145
+ }
22146
+ } catch(err) {
22147
+ mouse_offset = [0,0]
21711
22148
  }
21712
22149
  }
21713
22150
 
@@ -21788,7 +22225,7 @@ RED.view = (function() {
21788
22225
  //var touch0 = d3.event;
21789
22226
  //var pos = [touch0.pageX,touch0.pageY];
21790
22227
  //RED.touch.radialMenu.show(d3.select(this),pos);
21791
- if (mouse_mode == RED.state.IMPORT_DRAGGING) {
22228
+ if (mouse_mode == RED.state.IMPORT_DRAGGING || mouse_mode == RED.state.DETACHED_DRAGGING) {
21792
22229
  var historyEvent = RED.history.peek();
21793
22230
  if (activeSpliceLink) {
21794
22231
  // TODO: DRY - droppable/nodeMouseDown/canvasMouseUp
@@ -21826,6 +22263,18 @@ RED.view = (function() {
21826
22263
  activeHoverGroup = null;
21827
22264
  }
21828
22265
 
22266
+ if (mouse_mode == RED.state.DETACHED_DRAGGING) {
22267
+ var ns = [];
22268
+ for (var j=0;j<movingSet.length();j++) {
22269
+ var n = movingSet.get(j);
22270
+ if (n.ox !== n.n.x || n.oy !== n.n.y) {
22271
+ ns.push({n:n.n,ox:n.ox,oy:n.oy,moved:n.n.moved});
22272
+ n.n.dirty = true;
22273
+ n.n.moved = true;
22274
+ }
22275
+ }
22276
+ RED.history.replace({t:"multi",events:[historyEvent,{t:"move",nodes:ns}],dirty: historyEvent.dirty})
22277
+ }
21829
22278
 
21830
22279
  updateSelection();
21831
22280
  RED.nodes.dirty(true);
@@ -21994,7 +22443,9 @@ RED.view = (function() {
21994
22443
  // }
21995
22444
  // } else
21996
22445
  if (d3.event.shiftKey) {
21997
- clearSelection();
22446
+ if (!(d3.event.ctrlKey||d3.event.metaKey)) {
22447
+ clearSelection();
22448
+ }
21998
22449
  var clickPosition = (d3.event.offsetX/scaleFactor - mousedown_node.x)
21999
22450
  var edgeDelta = (mousedown_node.w/2) - Math.abs(clickPosition);
22000
22451
  var cnodes;
@@ -22022,7 +22473,7 @@ RED.view = (function() {
22022
22473
  mousedown_node.selected = true;
22023
22474
  movingSet.add(mousedown_node);
22024
22475
  }
22025
- selected_link = null;
22476
+ // selectedLinks.clear();
22026
22477
  if (d3.event.button != 2) {
22027
22478
  mouse_mode = RED.state.MOVING;
22028
22479
  var mouse = d3.touches(this)[0]||d3.mouse(this);
@@ -22148,19 +22599,35 @@ RED.view = (function() {
22148
22599
  d3.event.stopPropagation();
22149
22600
  return;
22150
22601
  }
22151
- mousedown_link = d;
22152
- clearSelection();
22153
- selected_link = mousedown_link;
22154
- updateSelection();
22155
- redraw();
22156
- focusView();
22157
- d3.event.stopPropagation();
22158
- if (d3.event.metaKey || d3.event.ctrlKey) {
22159
- d3.select(this).classed("red-ui-flow-link-splice",true);
22160
- var point = d3.mouse(this);
22161
- var clickedGroup = getGroupAt(point[0],point[1]);
22162
- showQuickAddDialog({position:point, splice:selected_link, group:clickedGroup});
22163
- }
22602
+ if (d3.event.button === 2) {
22603
+ return
22604
+ }
22605
+ mousedown_link = d;
22606
+
22607
+ if (!(d3.event.metaKey || d3.event.ctrlKey)) {
22608
+ clearSelection();
22609
+ }
22610
+ if (d3.event.metaKey || d3.event.ctrlKey) {
22611
+ if (!selectedLinks.has(mousedown_link)) {
22612
+ selectedLinks.add(mousedown_link);
22613
+ } else {
22614
+ if (selectedLinks.length() !== 1) {
22615
+ selectedLinks.remove(mousedown_link);
22616
+ }
22617
+ }
22618
+ } else {
22619
+ selectedLinks.add(mousedown_link);
22620
+ }
22621
+ updateSelection();
22622
+ redraw();
22623
+ focusView();
22624
+ d3.event.stopPropagation();
22625
+ 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)) {
22626
+ d3.select(this).classed("red-ui-flow-link-splice",true);
22627
+ var point = d3.mouse(this);
22628
+ var clickedGroup = getGroupAt(point[0],point[1]);
22629
+ showQuickAddDialog({position:point, splice:mousedown_link, group:clickedGroup});
22630
+ }
22164
22631
  }
22165
22632
  function linkTouchStart(d) {
22166
22633
  if (mouse_mode === RED.state.SELECTING_NODE) {
@@ -22169,7 +22636,8 @@ RED.view = (function() {
22169
22636
  }
22170
22637
  mousedown_link = d;
22171
22638
  clearSelection();
22172
- selected_link = mousedown_link;
22639
+ selectedLinks.clear();
22640
+ selectedLinks.add(mousedown_link);
22173
22641
  updateSelection();
22174
22642
  redraw();
22175
22643
  focusView();
@@ -22405,7 +22873,7 @@ RED.view = (function() {
22405
22873
  function showTouchMenu(obj,pos) {
22406
22874
  var mdn = mousedown_node;
22407
22875
  var options = [];
22408
- options.push({name:"delete",disabled:(movingSet.length()===0 && selected_link === null),onselect:function() {deleteSelection();}});
22876
+ options.push({name:"delete",disabled:(movingSet.length()===0 && selectedLinks.length() === 0),onselect:function() {deleteSelection();}});
22409
22877
  options.push({name:"cut",disabled:(movingSet.length()===0),onselect:function() {copySelection();deleteSelection();}});
22410
22878
  options.push({name:"copy",disabled:(movingSet.length()===0),onselect:function() {copySelection();}});
22411
22879
  options.push({name:"paste",disabled:(clipboard.length===0),onselect:function() {importNodes(clipboard, {generateIds: true, touchImport: true});}});
@@ -22538,28 +23006,89 @@ RED.view = (function() {
22538
23006
  if (activeSubflow) {
22539
23007
  var subflowOutputs = nodeLayer.selectAll(".red-ui-flow-subflow-port-output").data(activeSubflow.out,function(d,i){ return d.id;});
22540
23008
  subflowOutputs.exit().remove();
22541
- 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)+")"});
23009
+ var outGroup = subflowOutputs.enter().insert("svg:g").attr("class","red-ui-flow-node red-ui-flow-subflow-port-output")
22542
23010
  outGroup.each(function(d,i) {
22543
- d.w=40;
22544
- d.h=40;
23011
+ var node = d3.select(this);
23012
+ var nodeContents = document.createDocumentFragment();
23013
+
23014
+ d.h = 40;
23015
+ d.resize = true;
23016
+ d.dirty = true;
23017
+
23018
+ var mainRect = document.createElementNS("http://www.w3.org/2000/svg","rect");
23019
+ mainRect.__data__ = d;
23020
+ mainRect.setAttribute("class", "red-ui-flow-subflow-port");
23021
+ mainRect.setAttribute("rx", 8);
23022
+ mainRect.setAttribute("ry", 8);
23023
+ mainRect.setAttribute("width", 40);
23024
+ mainRect.setAttribute("height", 40);
23025
+ node[0][0].__mainRect__ = mainRect;
23026
+ d3.select(mainRect)
23027
+ .on("mouseup",nodeMouseUp)
23028
+ .on("mousedown",nodeMouseDown)
23029
+ .on("touchstart",nodeTouchStart)
23030
+ .on("touchend",nodeTouchEnd)
23031
+ nodeContents.appendChild(mainRect);
23032
+
23033
+ var output_groupEl = document.createElementNS("http://www.w3.org/2000/svg","g");
23034
+ output_groupEl.setAttribute("x",0);
23035
+ output_groupEl.setAttribute("y",0);
23036
+ node[0][0].__outputLabelGroup__ = output_groupEl;
23037
+
23038
+ var output_output = document.createElementNS("http://www.w3.org/2000/svg","text");
23039
+ output_output.setAttribute("class","red-ui-flow-port-label");
23040
+ output_output.style["font-size"] = "10px";
23041
+ output_output.textContent = "output";
23042
+ output_groupEl.appendChild(output_output);
23043
+ node[0][0].__outputOutput__ = output_output;
23044
+
23045
+ var output_number = document.createElementNS("http://www.w3.org/2000/svg","text");
23046
+ output_number.setAttribute("class","red-ui-flow-port-label red-ui-flow-port-index");
23047
+ output_number.setAttribute("x",0);
23048
+ output_number.setAttribute("y",0);
23049
+ output_number.textContent = d.i+1;
23050
+ output_groupEl.appendChild(output_number);
23051
+ node[0][0].__outputNumber__ = output_number;
23052
+
23053
+ var output_border = document.createElementNS("http://www.w3.org/2000/svg","path");
23054
+ output_border.setAttribute("d","M 40 1 l 0 38")
23055
+ output_border.setAttribute("class", "red-ui-flow-node-icon-shade-border")
23056
+ output_groupEl.appendChild(output_border);
23057
+ node[0][0].__outputBorder__ = output_border;
23058
+
23059
+ nodeContents.appendChild(output_groupEl);
23060
+
23061
+ var text = document.createElementNS("http://www.w3.org/2000/svg","g");
23062
+ text.setAttribute("class","red-ui-flow-port-label");
23063
+ text.setAttribute("transform","translate(38,0)");
23064
+ text.setAttribute('style', 'fill : #888'); // hard coded here!
23065
+ node[0][0].__textGroup__ = text;
23066
+ nodeContents.append(text);
23067
+
23068
+ var portEl = document.createElementNS("http://www.w3.org/2000/svg","g");
23069
+ portEl.setAttribute('transform','translate(-5,15)')
23070
+
23071
+ var port = document.createElementNS("http://www.w3.org/2000/svg","rect");
23072
+ port.setAttribute("class","red-ui-flow-port");
23073
+ port.setAttribute("rx",3);
23074
+ port.setAttribute("ry",3);
23075
+ port.setAttribute("width",10);
23076
+ port.setAttribute("height",10);
23077
+ portEl.appendChild(port);
23078
+ port.__data__ = d;
23079
+
23080
+ d3.select(port)
23081
+ .on("mousedown", function(d,i){portMouseDown(d,PORT_TYPE_INPUT,0);} )
23082
+ .on("touchstart", function(d,i){portMouseDown(d,PORT_TYPE_INPUT,0);d3.event.preventDefault();} )
23083
+ .on("mouseup", function(d,i){portMouseUp(d,PORT_TYPE_INPUT,0);})
23084
+ .on("touchend",function(d,i){portMouseUp(d,PORT_TYPE_INPUT,0);d3.event.preventDefault();} )
23085
+ .on("mouseover",function(d){portMouseOver(d3.select(this),d,PORT_TYPE_INPUT,0);})
23086
+ .on("mouseout",function(d){portMouseOut(d3.select(this),d,PORT_TYPE_INPUT,0);});
23087
+
23088
+ node[0][0].__port__ = portEl
23089
+ nodeContents.appendChild(portEl);
23090
+ node[0][0].appendChild(nodeContents);
22545
23091
  });
22546
- outGroup.append("rect").attr("class","red-ui-flow-subflow-port").attr("rx",8).attr("ry",8).attr("width",40).attr("height",40)
22547
- // TODO: This is exactly the same set of handlers used for regular nodes - DRY
22548
- .on("mouseup",nodeMouseUp)
22549
- .on("mousedown",nodeMouseDown)
22550
- .on("touchstart",nodeTouchStart)
22551
- .on("touchend",nodeTouchEnd)
22552
-
22553
- 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)
22554
- .on("mousedown", function(d,i){portMouseDown(d,PORT_TYPE_INPUT,0);} )
22555
- .on("touchstart", function(d,i){portMouseDown(d,PORT_TYPE_INPUT,0);d3.event.preventDefault();} )
22556
- .on("mouseup", function(d,i){portMouseUp(d,PORT_TYPE_INPUT,0);})
22557
- .on("touchend",function(d,i){portMouseUp(d,PORT_TYPE_INPUT,0);d3.event.preventDefault();} )
22558
- .on("mouseover",function(d){portMouseOver(d3.select(this),d,PORT_TYPE_INPUT,0);})
22559
- .on("mouseout",function(d){portMouseOut(d3.select(this),d,PORT_TYPE_INPUT,0);});
22560
-
22561
- outGroup.append("svg:text").attr("class","red-ui-flow-port-label").attr("x",20).attr("y",12).style("font-size","10px").text("output");
22562
- 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});
22563
23092
 
22564
23093
  var subflowInputs = nodeLayer.selectAll(".red-ui-flow-subflow-port-input").data(activeSubflow.in,function(d,i){ return d.id;});
22565
23094
  subflowInputs.exit().remove();
@@ -22612,11 +23141,89 @@ RED.view = (function() {
22612
23141
 
22613
23142
  subflowOutputs.each(function(d,i) {
22614
23143
  if (d.dirty) {
22615
- var output = d3.select(this);
22616
- output.classed("red-ui-flow-node-selected",function(d) { return d.selected; })
22617
- output.selectAll(".red-ui-flow-port-index").text(function(d){ return d.i+1});
22618
- output.attr("transform", function(d) { return "translate(" + (d.x-d.w/2) + "," + (d.y-d.h/2) + ")"; });
23144
+
23145
+ var port_height = 40;
23146
+
23147
+ var self = this;
23148
+ var thisNode = d3.select(this);
23149
+
22619
23150
  dirtyNodes[d.id] = d;
23151
+
23152
+ var label = getPortLabel(activeSubflow, PORT_TYPE_OUTPUT, d.i) || "";
23153
+ var hideLabel = (label.length < 1)
23154
+
23155
+ var labelParts;
23156
+ if (d.resize || this.__hideLabel__ !== hideLabel || this.__label__ !== label) {
23157
+ labelParts = getLabelParts(label, "red-ui-flow-node-label");
23158
+ if (labelParts.lines.length !== this.__labelLineCount__ || this.__label__ !== label) {
23159
+ d.resize = true;
23160
+ }
23161
+ this.__label__ = label;
23162
+ this.__labelLineCount__ = labelParts.lines.length;
23163
+
23164
+ if (hideLabel) {
23165
+ d.h = Math.max(port_height,(d.outputs || 0) * 15);
23166
+ } else {
23167
+ d.h = Math.max(6+24*labelParts.lines.length,(d.outputs || 0) * 15, port_height);
23168
+ }
23169
+ this.__hideLabel__ = hideLabel;
23170
+ }
23171
+
23172
+ if (d.resize) {
23173
+ var ow = d.w;
23174
+ if (hideLabel) {
23175
+ d.w = port_height;
23176
+ } else {
23177
+ d.w = Math.max(port_height,20*(Math.ceil((labelParts.width+50+7)/20)) );
23178
+ }
23179
+ if (ow !== undefined) {
23180
+ d.x += (d.w-ow)/2;
23181
+ }
23182
+ d.resize = false;
23183
+ }
23184
+
23185
+ this.setAttribute("transform", "translate(" + (d.x-d.w/2) + "," + (d.y-d.h/2) + ")");
23186
+ // This might be the first redraw after a node has been click-dragged to start a move.
23187
+ // So its selected state might have changed since the last redraw.
23188
+ this.classList.toggle("red-ui-flow-node-selected", !!d.selected )
23189
+ if (mouse_mode != RED.state.MOVING_ACTIVE) {
23190
+ this.classList.toggle("red-ui-flow-node-disabled", d.d === true);
23191
+ this.__mainRect__.setAttribute("width", d.w)
23192
+ this.__mainRect__.setAttribute("height", d.h)
23193
+ this.__mainRect__.classList.toggle("red-ui-flow-node-highlighted",!!d.highlighted );
23194
+
23195
+ if (labelParts) {
23196
+ // The label has changed
23197
+ var sa = labelParts.lines;
23198
+ var sn = labelParts.lines.length;
23199
+ var textLines = this.__textGroup__.childNodes;
23200
+ while(textLines.length > sn) {
23201
+ textLines[textLines.length-1].remove();
23202
+ }
23203
+ for (var i=0; i<sn; i++) {
23204
+ if (i===textLines.length) {
23205
+ var line = document.createElementNS("http://www.w3.org/2000/svg","text");
23206
+ line.setAttribute("class","red-ui-flow-node-label-text");
23207
+ line.setAttribute("x",0);
23208
+ line.setAttribute("y",i*24);
23209
+ this.__textGroup__.appendChild(line);
23210
+ }
23211
+ textLines[i].textContent = sa[i];
23212
+ }
23213
+ }
23214
+
23215
+ var textClass = "red-ui-flow-node-label"+(hideLabel?" hide":"");
23216
+ this.__textGroup__.setAttribute("class", textClass);
23217
+ var yp = d.h / 2 - (this.__labelLineCount__ / 2) * 24 + 13;
23218
+
23219
+ // this.__textGroup__.classList.remove("red-ui-flow-node-label-right");
23220
+ this.__textGroup__.setAttribute("transform", "translate(48,"+yp+")");
23221
+
23222
+ this.__outputBorder__.setAttribute("d","M 40 1 l 0 "+(hideLabel?0:(d.h - 2)));
23223
+ this.__port__.setAttribute("transform","translate(-5,"+((d.h/2)-5)+")");
23224
+ this.__outputOutput__.setAttribute("transform","translate(20,"+((d.h/2)-8)+")");
23225
+ this.__outputNumber__.setAttribute("transform","translate(20,"+((d.h/2)+7)+")");
23226
+ }
22620
23227
  d.dirty = false;
22621
23228
  }
22622
23229
  });
@@ -23142,6 +23749,13 @@ RED.view = (function() {
23142
23749
  d3.select(pathBack)
23143
23750
  .on("mousedown",linkMouseDown)
23144
23751
  .on("touchstart",linkTouchStart)
23752
+ .on("mousemove", function(d) {
23753
+ if (mouse_mode === RED.state.SLICING) {
23754
+ selectedLinks.add(d)
23755
+ l.classed("red-ui-flow-link-splice",true)
23756
+ redraw()
23757
+ }
23758
+ })
23145
23759
 
23146
23760
  var pathOutline = document.createElementNS("http://www.w3.org/2000/svg","path");
23147
23761
  pathOutline.__data__ = d;
@@ -23162,7 +23776,7 @@ RED.view = (function() {
23162
23776
  link.exit().remove();
23163
23777
  link.each(function(d) {
23164
23778
  var link = d3.select(this);
23165
- if (d.added || d===selected_link || d.selected || dirtyNodes[d.source.id] || dirtyNodes[d.target.id]) {
23779
+ if (d.added || d.selected || dirtyNodes[d.source.id] || dirtyNodes[d.target.id]) {
23166
23780
  var numOutputs = d.source.outputs || 1;
23167
23781
  var sourcePort = d.sourcePort || 0;
23168
23782
  var y = -((numOutputs-1)/2)*13 +13*sourcePort;
@@ -23185,7 +23799,8 @@ RED.view = (function() {
23185
23799
  this.__pathLine__.classList.toggle("red-ui-flow-node-disabled",!!(d.source.d || d.target.d));
23186
23800
  this.__pathLine__.classList.toggle("red-ui-flow-subflow-link", !d.link && activeSubflow);
23187
23801
  }
23188
- this.classList.toggle("red-ui-flow-link-selected", !!(d===selected_link||d.selected));
23802
+
23803
+ this.classList.toggle("red-ui-flow-link-selected", !!d.selected);
23189
23804
 
23190
23805
  var connectedToUnknown = !!(d.target.type == "unknown" || d.source.type == "unknown");
23191
23806
  this.classList.toggle("red-ui-flow-link-unknown",!!(d.target.type == "unknown" || d.source.type == "unknown"))
@@ -23893,8 +24508,9 @@ RED.view = (function() {
23893
24508
  if (allNodes.size > 0) {
23894
24509
  selection.nodes = Array.from(allNodes);
23895
24510
  }
23896
- if (selected_link != null) {
23897
- selection.link = selected_link;
24511
+ if (selectedLinks.length() > 0) {
24512
+ selection.links = selectedLinks.toArray();
24513
+ selection.link = selection.links[0];
23898
24514
  }
23899
24515
  return selection;
23900
24516
  }
@@ -25195,6 +25811,90 @@ RED.view.tools = (function() {
25195
25811
  }
25196
25812
  }
25197
25813
 
25814
+
25815
+ function wireSeriesOfNodes() {
25816
+ var selection = RED.view.selection();
25817
+ if (selection.nodes) {
25818
+ if (selection.nodes.length > 1) {
25819
+ var i = 0;
25820
+ var newLinks = [];
25821
+ while (i < selection.nodes.length - 1) {
25822
+ var nodeA = selection.nodes[i];
25823
+ var nodeB = selection.nodes[i+1];
25824
+ if (nodeA.outputs > 0 && nodeB.inputs > 0) {
25825
+ var existingLinks = RED.nodes.filterLinks({
25826
+ source: nodeA,
25827
+ target: nodeB,
25828
+ sourcePort: 0
25829
+ })
25830
+ if (existingLinks.length === 0) {
25831
+ var newLink = {
25832
+ source: nodeA,
25833
+ target: nodeB,
25834
+ sourcePort: 0
25835
+ }
25836
+ RED.nodes.addLink(newLink);
25837
+ newLinks.push(newLink);
25838
+ }
25839
+ }
25840
+ i++;
25841
+ }
25842
+ if (newLinks.length > 0) {
25843
+ RED.history.push({
25844
+ t: 'add',
25845
+ links: newLinks,
25846
+ dirty: RED.nodes.dirty()
25847
+ })
25848
+ RED.nodes.dirty(true);
25849
+ RED.view.redraw(true);
25850
+ }
25851
+ }
25852
+ }
25853
+ }
25854
+
25855
+ function wireNodeToMultiple() {
25856
+ var selection = RED.view.selection();
25857
+ if (selection.nodes) {
25858
+ if (selection.nodes.length > 1) {
25859
+ var sourceNode = selection.nodes[0];
25860
+ if (sourceNode.outputs === 0) {
25861
+ return;
25862
+ }
25863
+ var i = 1;
25864
+ var newLinks = [];
25865
+ while (i < selection.nodes.length) {
25866
+ var targetNode = selection.nodes[i];
25867
+ if (targetNode.inputs > 0) {
25868
+ var existingLinks = RED.nodes.filterLinks({
25869
+ source: sourceNode,
25870
+ target: targetNode,
25871
+ sourcePort: Math.min(sourceNode.outputs-1,i-1)
25872
+ })
25873
+ if (existingLinks.length === 0) {
25874
+ var newLink = {
25875
+ source: sourceNode,
25876
+ target: targetNode,
25877
+ sourcePort: Math.min(sourceNode.outputs-1,i-1)
25878
+ }
25879
+ RED.nodes.addLink(newLink);
25880
+ newLinks.push(newLink);
25881
+ }
25882
+ }
25883
+ i++;
25884
+ }
25885
+ if (newLinks.length > 0) {
25886
+ RED.history.push({
25887
+ t: 'add',
25888
+ links: newLinks,
25889
+ dirty: RED.nodes.dirty()
25890
+ })
25891
+ RED.nodes.dirty(true);
25892
+ RED.view.redraw(true);
25893
+ }
25894
+ }
25895
+ }
25896
+ }
25897
+
25198
25898
  return {
25199
25899
  init: function() {
25200
25900
  RED.actions.add("core:show-selected-node-labels", function() { setSelectedNodeLabelState(true); })
@@ -25253,7 +25953,8 @@ RED.view.tools = (function() {
25253
25953
  RED.actions.add("core:distribute-selection-horizontally", function() { distributeSelection('h') })
25254
25954
  RED.actions.add("core:distribute-selection-vertically", function() { distributeSelection('v') })
25255
25955
 
25256
-
25956
+ RED.actions.add("core:wire-series-of-nodes", function() { wireSeriesOfNodes() })
25957
+ RED.actions.add("core:wire-node-to-multiple", function() { wireNodeToMultiple() })
25257
25958
 
25258
25959
  // RED.actions.add("core:add-node", function() { addNode() })
25259
25960
  },
@@ -25291,6 +25992,15 @@ RED.sidebar = (function() {
25291
25992
  var sidebar_tabs;
25292
25993
  var knownTabs = {};
25293
25994
 
25995
+ // We store the current sidebar tab id in localStorage as 'last-sidebar-tab'
25996
+ // This is restored when the editor is reloaded.
25997
+ // We use sidebar_tabs.onchange to update localStorage. However that will
25998
+ // also get triggered when the first tab gets added to the tabs - typically
25999
+ // the 'info' tab. So we use the following variable to store the retrieved
26000
+ // value from localStorage before we start adding the actual tabs
26001
+ var lastSessionSelectedTab = null;
26002
+
26003
+
25294
26004
  function addTab(title,content,closeable,visible) {
25295
26005
  var options;
25296
26006
  if (typeof title === "string") {
@@ -25466,16 +26176,16 @@ RED.sidebar = (function() {
25466
26176
  RED.events.emit("sidebar:resize");
25467
26177
  }
25468
26178
 
25469
- function showSidebar(id) {
26179
+ function showSidebar(id, skipShowSidebar) {
25470
26180
  if (id === ":first") {
25471
- id = RED.settings.get("editor.sidebar.order",["info", "help", "version-control", "debug"])[0]
26181
+ id = lastSessionSelectedTab || RED.settings.get("editor.sidebar.order",["info", "help", "version-control", "debug"])[0]
25472
26182
  }
25473
26183
  if (id) {
25474
26184
  if (!containsTab(id) && knownTabs[id]) {
25475
26185
  sidebar_tabs.addTab(knownTabs[id]);
25476
26186
  }
25477
26187
  sidebar_tabs.activateTab(id);
25478
- if (!RED.menu.isSelected("menu-item-sidebar")) {
26188
+ if (!skipShowSidebar && !RED.menu.isSelected("menu-item-sidebar")) {
25479
26189
  RED.menu.setSelected("menu-item-sidebar",true);
25480
26190
  }
25481
26191
  }
@@ -25499,6 +26209,7 @@ RED.sidebar = (function() {
25499
26209
  if (tab.toolbar) {
25500
26210
  $(tab.toolbar).show();
25501
26211
  }
26212
+ RED.settings.setLocal("last-sidebar-tab", tab.id)
25502
26213
  },
25503
26214
  onremove: function(tab) {
25504
26215
  $(tab.wrapper).hide();
@@ -25527,7 +26238,9 @@ RED.sidebar = (function() {
25527
26238
  }
25528
26239
  });
25529
26240
  RED.popover.tooltip($("#red-ui-sidebar-separator").find(".red-ui-sidebar-control-right"),RED._("keyboard.toggleSidebar"),"core:toggle-sidebar");
25530
- showSidebar();
26241
+
26242
+ lastSessionSelectedTab = RED.settings.getLocal("last-sidebar-tab")
26243
+
25531
26244
  RED.sidebar.info.init();
25532
26245
  RED.sidebar.help.init();
25533
26246
  RED.sidebar.config.init();
@@ -27586,15 +28299,17 @@ RED.sidebar.help = (function() {
27586
28299
  style: "compact",
27587
28300
  delay: 100,
27588
28301
  change: function() {
27589
- var val = $(this).val().toLowerCase();
27590
- if (val) {
28302
+ const searchFor = $(this).val().toLowerCase();
28303
+ if (searchFor) {
27591
28304
  showTOC();
27592
- var c = treeList.treeList('filter',function(item) {
28305
+ treeList.treeList('filter',function(item) {
27593
28306
  if (item.depth === 0) {
27594
28307
  return true;
27595
28308
  }
27596
- return (item.nodeType && item.nodeType.indexOf(val) > -1) ||
27597
- (item.subflowLabel && item.subflowLabel.indexOf(val) > -1)
28309
+ let found = item.nodeType && item.nodeType.toLowerCase().indexOf(searchFor) > -1;
28310
+ found = found || item.subflowLabel && item.subflowLabel.toLowerCase().indexOf(searchFor) > -1;
28311
+ found = found || item.palleteLabel && item.palleteLabel.toLowerCase().indexOf(searchFor) > -1;
28312
+ return found;
27598
28313
  },true)
27599
28314
  } else {
27600
28315
  treeList.treeList('filter',null);
@@ -27746,17 +28461,21 @@ RED.sidebar.help = (function() {
27746
28461
 
27747
28462
 
27748
28463
  moduleNames.forEach(function(moduleName) {
27749
- var module = modules[moduleName];
27750
- var nodeTypes = [];
27751
-
27752
- var setNames = Object.keys(module.sets);
28464
+ const module = modules[moduleName];
28465
+ const nodeTypes = [];
28466
+ const moduleSets = module.sets;
28467
+ const setNames = Object.keys(moduleSets);
27753
28468
  setNames.forEach(function(setName) {
27754
- module.sets[setName].types.forEach(function(nodeType) {
28469
+ const moduleSet = moduleSets[setName];
28470
+ moduleSet.types.forEach(function(nodeType) {
27755
28471
  if ($("script[data-help-name='"+nodeType+"']").length) {
28472
+ const n = {_def:RED.nodes.getType(nodeType),type:nodeType}
28473
+ n.name = getNodePaletteLabel(n);
27756
28474
  nodeTypes.push({
27757
28475
  id: "node-type:"+nodeType,
27758
28476
  nodeType: nodeType,
27759
- element:getNodeLabel({_def:RED.nodes.getType(nodeType),type:nodeType})
28477
+ palleteLabel: n.name,
28478
+ element: getNodeLabel(n)
27760
28479
  })
27761
28480
  }
27762
28481
  })
@@ -27776,18 +28495,21 @@ RED.sidebar.help = (function() {
27776
28495
  treeList.treeList("data",helpData);
27777
28496
  }
27778
28497
 
27779
- function getNodeLabel(n) {
27780
- var div = $('<div>',{class:"red-ui-node-list-item"});
27781
- var icon = RED.utils.createNodeIcon(n).appendTo(div);
27782
- var label = n.name;
28498
+ function getNodePaletteLabel(n) {
28499
+ let label = n.name;
27783
28500
  if (!label && n._def && n._def.paletteLabel) {
27784
28501
  try {
27785
28502
  label = (typeof n._def.paletteLabel === "function" ? n._def.paletteLabel.call(n._def) : n._def.paletteLabel)||"";
27786
28503
  } catch (err) {
27787
28504
  }
27788
28505
  }
27789
- label = label || n.type;
27790
- $('<div>',{class:"red-ui-node-label"}).text(n.name||n.type).appendTo(icon);
28506
+ return label || n.type;
28507
+ }
28508
+
28509
+ function getNodeLabel(n) {
28510
+ const div = $('<div>',{class:"red-ui-node-list-item"});
28511
+ const icon = RED.utils.createNodeIcon(n).appendTo(div);
28512
+ $('<div>',{class:"red-ui-node-label"}).text(getNodePaletteLabel(n)).appendTo(icon);
27791
28513
  return div;
27792
28514
  }
27793
28515
 
@@ -31922,9 +32644,10 @@ RED.editor = (function() {
31922
32644
  editState.changed = true;
31923
32645
  }
31924
32646
  if (!node._def.defaults || !node._def.defaults.hasOwnProperty("icon")) {
31925
- var icon = $("#red-ui-editor-node-icon").val()||""
32647
+ var icon = $("#red-ui-editor-node-icon").val()||"";
31926
32648
  if (!this.isDefaultIcon) {
31927
- if (icon !== node.icon) {
32649
+ if ((icon !== node.icon) &&
32650
+ (icon !== "")) {
31928
32651
  editState.changes.icon = node.icon;
31929
32652
  node.icon = icon;
31930
32653
  editState.changed = true;
@@ -31988,14 +32711,14 @@ RED.editor = (function() {
31988
32711
  if (showLabel) {
31989
32712
  // Default to show label
31990
32713
  if (node.l !== false) {
31991
- editState.changes.l = node.l
32714
+ editState.changes.l = node.l;
31992
32715
  editState.changed = true;
31993
32716
  }
31994
32717
  node.l = false;
31995
32718
  } else {
31996
32719
  // Node has showLabel:false (eg link nodes)
31997
32720
  if (node.hasOwnProperty('l') && node.l) {
31998
- editState.changes.l = node.l
32721
+ editState.changes.l = node.l;
31999
32722
  editState.changed = true;
32000
32723
  }
32001
32724
  delete node.l;
@@ -32005,20 +32728,20 @@ RED.editor = (function() {
32005
32728
  if (showLabel) {
32006
32729
  // Default to show label
32007
32730
  if (node.hasOwnProperty('l') && !node.l) {
32008
- editState.changes.l = node.l
32731
+ editState.changes.l = node.l;
32009
32732
  editState.changed = true;
32010
32733
  }
32011
32734
  delete node.l;
32012
32735
  } else {
32013
32736
  if (!node.l) {
32014
- editState.changes.l = node.l
32737
+ editState.changes.l = node.l;
32015
32738
  editState.changed = true;
32016
32739
  }
32017
32740
  node.l = true;
32018
32741
  }
32019
32742
  }
32020
32743
  }
32021
- }
32744
+ };
32022
32745
  });
32023
32746
 
32024
32747
  function buildAppearanceForm(container,node) {
@@ -32051,10 +32774,10 @@ RED.editor = (function() {
32051
32774
  var categories = RED.palette.getCategories();
32052
32775
  categories.sort(function(A,B) {
32053
32776
  return A.label.localeCompare(B.label);
32054
- })
32777
+ });
32055
32778
  categories.forEach(function(cat) {
32056
32779
  categorySelector.append($("<option/>").val(cat.id).text(cat.label));
32057
- })
32780
+ });
32058
32781
  categorySelector.append($("<option/>").attr('disabled',true).text("---"));
32059
32782
  categorySelector.append($("<option/>").val("_custom_").text(RED._("palette.addCategory")));
32060
32783
 
@@ -32067,7 +32790,7 @@ RED.editor = (function() {
32067
32790
  $("#subflow-appearance-input-category").width(250);
32068
32791
  $("#subflow-appearance-input-custom-category").hide();
32069
32792
  }
32070
- })
32793
+ });
32071
32794
 
32072
32795
  $("#subflow-appearance-input-category").val(node.category||"subflows");
32073
32796
  var userCount = 0;
@@ -32091,7 +32814,7 @@ RED.editor = (function() {
32091
32814
  $("#node-input-show-label").toggleButton({
32092
32815
  enabledLabel: RED._("editor.show"),
32093
32816
  disabledLabel: RED._("editor.hide")
32094
- })
32817
+ });
32095
32818
 
32096
32819
  if (!node.hasOwnProperty("l")) {
32097
32820
  // Show label unless def.showLabel set to false
@@ -32117,7 +32840,7 @@ RED.editor = (function() {
32117
32840
  "#E9967A", "#F3B567", "#FDD0A2",
32118
32841
  "#FDF0C2", "#FFAAAA", "#FFCC66",
32119
32842
  "#FFF0F0", "#FFFFFF"
32120
- ]
32843
+ ];
32121
32844
 
32122
32845
  RED.editor.colorPicker.create({
32123
32846
  id: "red-ui-editor-node-color",
@@ -32132,9 +32855,9 @@ RED.editor = (function() {
32132
32855
  nodeDiv.css('backgroundColor',colour);
32133
32856
  var borderColor = RED.utils.getDarkerColor(colour);
32134
32857
  if (borderColor !== colour) {
32135
- nodeDiv.css('border-color',borderColor)
32858
+ nodeDiv.css('border-color',borderColor);
32136
32859
  }
32137
- })
32860
+ });
32138
32861
  }
32139
32862
 
32140
32863
 
@@ -32151,7 +32874,7 @@ RED.editor = (function() {
32151
32874
  nodeDiv.css('backgroundColor',colour);
32152
32875
  var borderColor = RED.utils.getDarkerColor(colour);
32153
32876
  if (borderColor !== colour) {
32154
- nodeDiv.css('border-color',borderColor)
32877
+ nodeDiv.css('border-color',borderColor);
32155
32878
  }
32156
32879
 
32157
32880
  var iconContainer = $('<div/>',{class:"red-ui-palette-icon-container"}).appendTo(nodeDiv);
@@ -32179,7 +32902,7 @@ RED.editor = (function() {
32179
32902
 
32180
32903
  RED.popover.tooltip(iconButton, function() {
32181
32904
  return $("#red-ui-editor-node-icon").val() || RED._("editor.default");
32182
- })
32905
+ });
32183
32906
  $('<input type="hidden" id="red-ui-editor-node-icon">').val(node.icon).appendTo(iconRow);
32184
32907
  }
32185
32908
 
@@ -32304,11 +33027,11 @@ RED.editor = (function() {
32304
33027
  });
32305
33028
  rows.sort(function(A,B) {
32306
33029
  return A.i-B.i;
32307
- })
33030
+ });
32308
33031
  rows.forEach(function(r,i) {
32309
33032
  r.r.find("label").text((i+1)+".");
32310
33033
  r.r.appendTo(outputsDiv);
32311
- })
33034
+ });
32312
33035
  if (rows.length === 0) {
32313
33036
  buildLabelRow("output",i,"").appendTo(outputsDiv);
32314
33037
  } else {
@@ -32354,7 +33077,7 @@ RED.editor = (function() {
32354
33077
  clear.on("click", function(evt) {
32355
33078
  evt.preventDefault();
32356
33079
  input.val("");
32357
- })
33080
+ });
32358
33081
  }
32359
33082
  return result;
32360
33083
  }
@@ -32388,6 +33111,12 @@ RED.editor = (function() {
32388
33111
  }
32389
33112
  var v = $(this).val();
32390
33113
  hasNonBlankLabel = hasNonBlankLabel || v!== "";
33114
+
33115
+ // mark changed output port labels as dirty
33116
+ if (node.type === "subflow" && (!node.outputLabels || node.outputLabels[index] !== v)) {
33117
+ node.out[index].dirty = true;
33118
+ }
33119
+
32391
33120
  newValue[index] = v;
32392
33121
  });
32393
33122
 
@@ -32396,6 +33125,12 @@ RED.editor = (function() {
32396
33125
  changes.outputLabels = node.outputLabels;
32397
33126
  node.outputLabels = newValue;
32398
33127
  changed = true;
33128
+
33129
+ // trigger redraw of dirty port labels
33130
+ if (node.type === "subflow") {
33131
+ RED.view.redraw();
33132
+ }
33133
+
32399
33134
  }
32400
33135
  return changed;
32401
33136
  }
@@ -32529,7 +33264,9 @@ RED.editor = (function() {
32529
33264
  }
32530
33265
  });
32531
33266
  }
32532
- if (!isSameObj(old_env, new_env)) {
33267
+ if (!old_env && new_env.length === 0) {
33268
+ delete node.env;
33269
+ } else if (!isSameObj(old_env, new_env)) {
32533
33270
  editState.changes.env = node.env;
32534
33271
  if (new_env.length === 0) {
32535
33272
  delete node.env;
@@ -34354,7 +35091,7 @@ RED.editor = (function() {
34354
35091
  var currentExpression = expressionEditor.getValue();
34355
35092
  var expr;
34356
35093
  var usesContext = false;
34357
- var legacyMode = /(^|[^a-zA-Z0-9_'"])msg([^a-zA-Z0-9_'"]|$)/.test(currentExpression);
35094
+ var legacyMode = /(^|[^a-zA-Z0-9_'".])msg([^a-zA-Z0-9_'"]|$)/.test(currentExpression);
34358
35095
  $(".red-ui-editor-type-expression-legacy").toggle(legacyMode);
34359
35096
  try {
34360
35097
  expr = jsonata(currentExpression);
@@ -34643,7 +35380,8 @@ RED.editor = (function() {
34643
35380
  clearTimeout: true,
34644
35381
  setInterval: true,
34645
35382
  clearInterval: true
34646
- }
35383
+ },
35384
+ extraLibs: options.extraLibs
34647
35385
  });
34648
35386
  if (options.cursor) {
34649
35387
  expressionEditor.gotoLine(options.cursor.row+1,options.cursor.column,false);
@@ -37662,6 +38400,7 @@ RED.clipboard = (function() {
37662
38400
  text: RED._("common.label.cancel"),
37663
38401
  click: function() {
37664
38402
  $( this ).dialog( "close" );
38403
+ RED.view.focus();
37665
38404
  }
37666
38405
  },
37667
38406
  { // red-ui-clipboard-dialog-download
@@ -37672,6 +38411,7 @@ RED.clipboard = (function() {
37672
38411
  var data = $("#red-ui-clipboard-dialog-export-text").val();
37673
38412
  downloadData("flows.json", data);
37674
38413
  $( this ).dialog( "close" );
38414
+ RED.view.focus();
37675
38415
  }
37676
38416
  },
37677
38417
  { // red-ui-clipboard-dialog-export
@@ -37686,6 +38426,7 @@ RED.clipboard = (function() {
37686
38426
  $( this ).dialog( "close" );
37687
38427
  copyText(flowData);
37688
38428
  RED.notify(RED._("clipboard.nodesExported"),{id:"clipboard"});
38429
+ RED.view.focus();
37689
38430
  } else {
37690
38431
  var flowToExport = $("#red-ui-clipboard-dialog-export-text").val();
37691
38432
  var selectedPath = activeLibraries[activeTab].getSelected();
@@ -37701,6 +38442,7 @@ RED.clipboard = (function() {
37701
38442
  contentType: "application/json; charset=utf-8"
37702
38443
  }).done(function() {
37703
38444
  $(dialog).dialog( "close" );
38445
+ RED.view.focus();
37704
38446
  RED.notify(RED._("library.exportedToLibrary"),"success");
37705
38447
  }).fail(function(xhr,textStatus,err) {
37706
38448
  if (xhr.status === 401) {
@@ -37762,6 +38504,7 @@ RED.clipboard = (function() {
37762
38504
  }
37763
38505
  }
37764
38506
  $( this ).dialog( "close" );
38507
+ RED.view.focus();
37765
38508
  }
37766
38509
  },
37767
38510
  { // red-ui-clipboard-dialog-import-conflict
@@ -37794,6 +38537,7 @@ RED.clipboard = (function() {
37794
38537
  // console.table(pendingImportConfig.importNodes.map(function(n) { return {id:n.id,type:n.type,result:importMap[n.id]}}))
37795
38538
  RED.view.importNodes(newNodes, pendingImportConfig.importOptions);
37796
38539
  $( this ).dialog( "close" );
38540
+ RED.view.focus();
37797
38541
  }
37798
38542
  }
37799
38543
  ],
@@ -38531,7 +39275,8 @@ RED.clipboard = (function() {
38531
39275
  if (truncated) {
38532
39276
  msg += "_truncated";
38533
39277
  }
38534
- $("#red-ui-clipboard-hidden").val(value).focus().select();
39278
+ var clipboardHidden = $('<textarea type="text" id="red-ui-clipboard-hidden" tabIndex="-1">').appendTo(document.body);
39279
+ clipboardHidden.val(value).focus().select();
38535
39280
  var result = document.execCommand("copy");
38536
39281
  if (result && element) {
38537
39282
  var popover = RED.popover.create({
@@ -38545,14 +39290,13 @@ RED.clipboard = (function() {
38545
39290
  },1000);
38546
39291
  popover.open();
38547
39292
  }
38548
- $("#red-ui-clipboard-hidden").val("");
39293
+ clipboardHidden.remove();
38549
39294
  if (currentFocus) {
38550
39295
  $(currentFocus).focus();
38551
39296
  }
38552
39297
  return result;
38553
39298
  }
38554
39299
 
38555
-
38556
39300
  function importNodes(nodesStr,addFlow) {
38557
39301
  var newNodes = nodesStr;
38558
39302
  if (typeof nodesStr === 'string') {
@@ -38827,8 +39571,6 @@ RED.clipboard = (function() {
38827
39571
  init: function() {
38828
39572
  setupDialogs();
38829
39573
 
38830
- $('<textarea type="text" id="red-ui-clipboard-hidden" tabIndex="-1">').appendTo("#red-ui-editor");
38831
-
38832
39574
  RED.actions.add("core:show-export-dialog",showExportNodes);
38833
39575
  RED.actions.add("core:show-import-dialog",showImportNodes);
38834
39576
 
@@ -40119,6 +40861,7 @@ RED.search = (function() {
40119
40861
  var selected = -1;
40120
40862
  var visible = false;
40121
40863
 
40864
+ var searchHistory = [];
40122
40865
  var index = {};
40123
40866
  var currentResults = [];
40124
40867
  var previousActiveElement;
@@ -40149,10 +40892,22 @@ RED.search = (function() {
40149
40892
  }
40150
40893
  l = l||n.label||n.name||n.id||"";
40151
40894
 
40152
-
40153
40895
  var properties = ['id','type','name','label','info'];
40154
- if (n._def && n._def.defaults) {
40155
- properties = properties.concat(Object.keys(n._def.defaults));
40896
+ const node_def = n && n._def;
40897
+ if (node_def) {
40898
+ if (node_def.defaults) {
40899
+ properties = properties.concat(Object.keys(node_def.defaults));
40900
+ }
40901
+ if (n.type !== "group" && node_def.paletteLabel && node_def.paletteLabel !== node_def.type) {
40902
+ try {
40903
+ const label = ("" + (typeof node_def.paletteLabel === "function" ? node_def.paletteLabel.call(node_def) : node_def.paletteLabel)).toLowerCase();
40904
+ if(label && label !== (""+node_def.type).toLowerCase()) {
40905
+ indexProperty(n, l, label);
40906
+ }
40907
+ } catch(err) {
40908
+ console.warn(`error indexing ${l}`, err);
40909
+ }
40910
+ }
40156
40911
  }
40157
40912
  for (var i=0;i<properties.length;i++) {
40158
40913
  if (n.hasOwnProperty(properties[i])) {
@@ -40302,6 +41057,20 @@ RED.search = (function() {
40302
41057
  }
40303
41058
  }
40304
41059
 
41060
+ function populateSearchHistory() {
41061
+ if (searchHistory.length > 0) {
41062
+ searchResults.editableList('addItem',{
41063
+ historyHeader: true
41064
+ });
41065
+ searchHistory.forEach(function(entry) {
41066
+ searchResults.editableList('addItem',{
41067
+ history: true,
41068
+ value: entry
41069
+ });
41070
+ })
41071
+ }
41072
+
41073
+ }
40305
41074
  function createDialog() {
40306
41075
  dialog = $("<div>",{id:"red-ui-search",class:"red-ui-search"}).appendTo("#red-ui-main-container");
40307
41076
  var searchDiv = $("<div>",{class:"red-ui-search-container"}).appendTo(dialog);
@@ -40310,7 +41079,12 @@ RED.search = (function() {
40310
41079
  change: function() {
40311
41080
  searchResults.editableList('empty');
40312
41081
  selected = -1;
40313
- currentResults = search($(this).val());
41082
+ var value = $(this).val();
41083
+ if (value === "") {
41084
+ populateSearchHistory();
41085
+ return;
41086
+ }
41087
+ currentResults = search(value);
40314
41088
  if (currentResults.length > 0) {
40315
41089
  for (i=0;i<Math.min(currentResults.length,25);i++) {
40316
41090
  searchResults.editableList('addItem',currentResults[i])
@@ -40382,7 +41156,12 @@ RED.search = (function() {
40382
41156
  })
40383
41157
  }
40384
41158
  }
40385
- } else {
41159
+ } if ($(children[selected]).hasClass("red-ui-search-history")) {
41160
+ var object = $(children[selected]).find(".red-ui-editableList-item-content").data('data');
41161
+ if (object) {
41162
+ searchInput.searchBox('value',object.value)
41163
+ }
41164
+ } else if (!$(children[selected]).hasClass("red-ui-search-historyHeader")) {
40386
41165
  if (currentResults.length > 0) {
40387
41166
  reveal(currentResults[Math.max(0,selected)].node);
40388
41167
  }
@@ -40398,7 +41177,32 @@ RED.search = (function() {
40398
41177
  addItem: function(container,i,object) {
40399
41178
  var node = object.node;
40400
41179
  var div;
40401
- if (object.more) {
41180
+ if (object.historyHeader) {
41181
+ container.parent().addClass("red-ui-search-historyHeader")
41182
+ $('<div>',{class:"red-ui-search-empty"}).text(RED._("search.history")).appendTo(container);
41183
+ $('<button type="button" class="red-ui-button red-ui-button-small"></button>').text(RED._("search.clear")).appendTo(container).on("click", function(evt) {
41184
+ evt.preventDefault();
41185
+ searchHistory = [];
41186
+ searchResults.editableList('empty');
41187
+ });
41188
+ } else if (object.history) {
41189
+ container.parent().addClass("red-ui-search-history")
41190
+ div = $('<a>',{href:'#',class:"red-ui-search-result"}).appendTo(container);
41191
+ div.text(object.value);
41192
+ div.on("click", function(evt) {
41193
+ evt.preventDefault();
41194
+ searchInput.searchBox('value',object.value)
41195
+ searchInput.focus();
41196
+ })
41197
+ $('<button type="button" class="red-ui-button red-ui-button-small"><i class="fa fa-remove"></i></button>').appendTo(container).on("click", function(evt) {
41198
+ evt.preventDefault();
41199
+ var index = searchHistory.indexOf(object.value);
41200
+ searchHistory.splice(index,1);
41201
+ searchResults.editableList('removeItem', object);
41202
+ });
41203
+
41204
+
41205
+ } else if (object.more) {
40402
41206
  container.parent().addClass("red-ui-search-more")
40403
41207
  div = $('<a>',{href:'#',class:"red-ui-search-result red-ui-search-empty"}).appendTo(container);
40404
41208
  div.text(RED._("palette.editor.more",{count:object.more.results.length-object.more.start}));
@@ -40453,6 +41257,12 @@ RED.search = (function() {
40453
41257
  }
40454
41258
 
40455
41259
  function reveal(node) {
41260
+ var searchVal = searchInput.val();
41261
+ var existingIndex = searchHistory.indexOf(searchVal);
41262
+ if (existingIndex > -1) {
41263
+ searchHistory.splice(existingIndex,1);
41264
+ }
41265
+ searchHistory.unshift(searchInput.val());
40456
41266
  hide();
40457
41267
  RED.view.reveal(node.id);
40458
41268
  }
@@ -40471,9 +41281,14 @@ RED.search = (function() {
40471
41281
 
40472
41282
  if (dialog === null) {
40473
41283
  createDialog();
41284
+ } else {
41285
+ searchResults.editableList('empty');
40474
41286
  }
40475
41287
  dialog.slideDown(300);
40476
41288
  searchInput.searchBox('value',v)
41289
+ if (!v || v === "") {
41290
+ populateSearchHistory();
41291
+ }
40477
41292
  RED.events.emit("search:open");
40478
41293
  visible = true;
40479
41294
  }
@@ -40731,18 +41546,19 @@ RED.actionList = (function() {
40731
41546
  createDialog();
40732
41547
  }
40733
41548
  dialog.slideDown(300);
40734
- searchInput.searchBox('value',v)
41549
+ searchInput.searchBox('value',v);
40735
41550
  searchResults.editableList('empty');
40736
41551
  results = [];
40737
41552
  var actions = RED.actions.list();
40738
41553
  actions.sort(function(A,B) {
40739
- return A.id.localeCompare(B.id);
41554
+ var Akey = A.label;
41555
+ var Bkey = B.label;
41556
+ return Akey.localeCompare(Bkey);
40740
41557
  });
40741
41558
  actions.forEach(function(action) {
40742
- action.label = action.id.replace(/:/,": ").replace(/-/g," ").replace(/(^| )./g,function() { return arguments[0].toUpperCase()});
40743
41559
  action._label = action.label.toLowerCase();
40744
- searchResults.editableList('addItem',action)
40745
- })
41560
+ searchResults.editableList('addItem',action);
41561
+ });
40746
41562
  RED.events.emit("actionList:open");
40747
41563
  visible = true;
40748
41564
  }
@@ -43104,12 +43920,14 @@ RED.group = (function() {
43104
43920
  markDirty(group);
43105
43921
  }
43106
43922
 
43107
- function getNodes(group,recursive) {
43923
+ function getNodes(group,recursive,excludeGroup) {
43108
43924
  var nodes = [];
43109
43925
  group.nodes.forEach(function(n) {
43110
- nodes.push(n);
43926
+ if (n.type !== 'group' || !excludeGroup) {
43927
+ nodes.push(n);
43928
+ }
43111
43929
  if (recursive && n.type === 'group') {
43112
- nodes = nodes.concat(getNodes(n,recursive))
43930
+ nodes = nodes.concat(getNodes(n,recursive,excludeGroup))
43113
43931
  }
43114
43932
  })
43115
43933
  return nodes;
@@ -43343,6 +44161,13 @@ RED.userSettings = (function() {
43343
44161
  // {setting:"theme", label:"Theme",options:function(done){ done([{val:'',text:'default'}].concat(RED.settings.theme("themes"))) }},
43344
44162
  // ]
43345
44163
  // },
44164
+ {
44165
+ title: "menu.label.view.view",
44166
+ options: [
44167
+ {setting:"view-store-zoom",label:"menu.label.view.storeZoom", default: false, toggle:true, onchange: function(val) { if (!val) { RED.settings.removeLocal("zoom-level")}}},
44168
+ {setting:"view-store-position",label:"menu.label.view.storePosition", default: false, toggle:true, onchange: function(val) { if (!val) { RED.settings.removeLocal("scroll-positions")}}},
44169
+ ]
44170
+ },
43346
44171
  {
43347
44172
  title: "menu.label.view.grid",
43348
44173
  options: [
@@ -44730,6 +45555,9 @@ RED.projects = (function() {
44730
45555
  }
44731
45556
  }).appendTo(row);
44732
45557
 
45558
+ row = $('<div class="form-row red-ui-projects-dialog-screen-create-row red-ui-projects-dialog-screen-create-row-open"></div>').hide().appendTo(container);
45559
+ $('<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();
45560
+
44733
45561
  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);
44734
45562
  $('<label for="red-ui-projects-dialog-screen-create-project-name">'+RED._("projects.create.project-name")+'</label>').appendTo(row);
44735
45563
 
@@ -45019,7 +45847,8 @@ RED.projects = (function() {
45019
45847
  };
45020
45848
  }
45021
45849
  } else if (projectType === 'open') {
45022
- return switchProject(selectedProject.name,function(err,data) {
45850
+ var clearContext = $("#red-ui-projects-dialog-screen-clear-context").prop("checked")
45851
+ return switchProject(selectedProject.name, clearContext, function(err,data) {
45023
45852
  if (err) {
45024
45853
  if (err.code !== 'credentials_load_failed') {
45025
45854
  console.log(RED._("projects.create.unexpected_error"),err)
@@ -45113,7 +45942,7 @@ RED.projects = (function() {
45113
45942
  }
45114
45943
  }
45115
45944
 
45116
- function switchProject(name,done) {
45945
+ function switchProject(name,clearContext,done) {
45117
45946
  RED.deploy.setDeployInflight(true);
45118
45947
  RED.projects.settings.switchProject(name);
45119
45948
  sendRequest({
@@ -45132,7 +45961,7 @@ RED.projects = (function() {
45132
45961
  '*': done
45133
45962
  },
45134
45963
  }
45135
- },{active:true}).then(function() {
45964
+ },{active:true, clearContext:clearContext}).then(function() {
45136
45965
  dialog.dialog( "close" );
45137
45966
  RED.events.emit("project:change", {name:name});
45138
45967
  }).always(function() {
@@ -45205,7 +46034,7 @@ RED.projects = (function() {
45205
46034
  dialogHeight = 590 - (750 - winHeight);
45206
46035
  }
45207
46036
  $(".red-ui-projects-dialog-box").height(dialogHeight);
45208
- $(".red-ui-projects-dialog-project-list-inner-container").height(Math.max(500,dialogHeight) - 180);
46037
+ $(".red-ui-projects-dialog-project-list-inner-container").height(Math.max(500,dialogHeight) - 210);
45209
46038
  dialog.dialog('option','title',screen.title||"");
45210
46039
  dialog.dialog("open");
45211
46040
  }
@@ -49912,6 +50741,10 @@ RED.touch.radialMenu = (function() {
49912
50741
  }
49913
50742
  $('<div>').css("text-align","left").html(getLocaleText(step.description)).appendTo(stepDescription);
49914
50743
 
50744
+ if (step.image) {
50745
+ $(`<img src="red/tours/${step.image}" />`).appendTo(stepDescription)
50746
+ }
50747
+
49915
50748
  var stepToolbar = $('<div>',{class:"red-ui-tourGuide-toolbar"}).appendTo(stepContent);
49916
50749
 
49917
50750
  // var breadcrumbs = $('<div>',{class:"red-ui-tourGuide-breadcrumbs"}).appendTo(stepToolbar);