@node-red/editor-client 2.1.2 → 2.2.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/public/red/red.js CHANGED
@@ -566,31 +566,33 @@ var RED = (function() {
566
566
  var typeList;
567
567
  var info;
568
568
  if (topic == "notification/node/added") {
569
- var addedTypes = [];
570
- msg.forEach(function(m) {
571
- var id = m.id;
572
- RED.nodes.addNodeSet(m);
573
- addedTypes = addedTypes.concat(m.types);
574
- RED.i18n.loadNodeCatalog(id, function() {
575
- var lang = localStorage.getItem("editor-language")||RED.i18n.detectLanguage();
576
- $.ajax({
577
- headers: {
578
- "Accept":"text/html",
579
- "Accept-Language": lang
580
- },
581
- cache: false,
582
- url: 'nodes/'+id,
583
- success: function(data) {
584
- appendNodeConfig(data);
585
- }
569
+ RED.settings.refreshSettings(function(err, data) {
570
+ var addedTypes = [];
571
+ msg.forEach(function(m) {
572
+ var id = m.id;
573
+ RED.nodes.addNodeSet(m);
574
+ addedTypes = addedTypes.concat(m.types);
575
+ RED.i18n.loadNodeCatalog(id, function() {
576
+ var lang = localStorage.getItem("editor-language")||RED.i18n.detectLanguage();
577
+ $.ajax({
578
+ headers: {
579
+ "Accept":"text/html",
580
+ "Accept-Language": lang
581
+ },
582
+ cache: false,
583
+ url: 'nodes/'+id,
584
+ success: function(data) {
585
+ appendNodeConfig(data);
586
+ }
587
+ });
586
588
  });
587
589
  });
588
- });
589
- if (addedTypes.length) {
590
- typeList = "<ul><li>"+addedTypes.map(RED.utils.sanitize).join("</li><li>")+"</li></ul>";
591
- RED.notify(RED._("palette.event.nodeAdded", {count:addedTypes.length})+typeList,"success");
592
- }
593
- loadIconList();
590
+ if (addedTypes.length) {
591
+ typeList = "<ul><li>"+addedTypes.map(RED.utils.sanitize).join("</li><li>")+"</li></ul>";
592
+ RED.notify(RED._("palette.event.nodeAdded", {count:addedTypes.length})+typeList,"success");
593
+ }
594
+ loadIconList();
595
+ })
594
596
  } else if (topic == "notification/node/removed") {
595
597
  for (i=0;i<msg.length;i++) {
596
598
  m = msg[i];
@@ -603,27 +605,29 @@ var RED = (function() {
603
605
  loadIconList();
604
606
  } else if (topic == "notification/node/enabled") {
605
607
  if (msg.types) {
606
- info = RED.nodes.getNodeSet(msg.id);
607
- if (info.added) {
608
- RED.nodes.enableNodeSet(msg.id);
609
- typeList = "<ul><li>"+msg.types.map(RED.utils.sanitize).join("</li><li>")+"</li></ul>";
610
- RED.notify(RED._("palette.event.nodeEnabled", {count:msg.types.length})+typeList,"success");
611
- } else {
612
- var lang = localStorage.getItem("editor-language")||RED.i18n.detectLanguage();
613
- $.ajax({
614
- headers: {
615
- "Accept":"text/html",
616
- "Accept-Language": lang
617
- },
618
- cache: false,
619
- url: 'nodes/'+msg.id,
620
- success: function(data) {
621
- appendNodeConfig(data);
622
- typeList = "<ul><li>"+msg.types.map(RED.utils.sanitize).join("</li><li>")+"</li></ul>";
623
- RED.notify(RED._("palette.event.nodeAdded", {count:msg.types.length})+typeList,"success");
624
- }
625
- });
626
- }
608
+ RED.settings.refreshSettings(function(err, data) {
609
+ info = RED.nodes.getNodeSet(msg.id);
610
+ if (info.added) {
611
+ RED.nodes.enableNodeSet(msg.id);
612
+ typeList = "<ul><li>"+msg.types.map(RED.utils.sanitize).join("</li><li>")+"</li></ul>";
613
+ RED.notify(RED._("palette.event.nodeEnabled", {count:msg.types.length})+typeList,"success");
614
+ } else {
615
+ var lang = localStorage.getItem("editor-language")||RED.i18n.detectLanguage();
616
+ $.ajax({
617
+ headers: {
618
+ "Accept":"text/html",
619
+ "Accept-Language": lang
620
+ },
621
+ cache: false,
622
+ url: 'nodes/'+msg.id,
623
+ success: function(data) {
624
+ appendNodeConfig(data);
625
+ typeList = "<ul><li>"+msg.types.map(RED.utils.sanitize).join("</li><li>")+"</li></ul>";
626
+ RED.notify(RED._("palette.event.nodeAdded", {count:msg.types.length})+typeList,"success");
627
+ }
628
+ });
629
+ }
630
+ });
627
631
  }
628
632
  } else if (topic == "notification/node/disabled") {
629
633
  if (msg.types) {
@@ -677,7 +681,7 @@ var RED = (function() {
677
681
  {id:"menu-item-projects-settings",label:RED._("menu.label.projects-settings"),disabled:false,onselect:"core:show-project-settings"}
678
682
  ]});
679
683
  }
680
- menuOptions.push({id:"menu-item-edit-menu", label:"Edit", options: [
684
+ menuOptions.push({id:"menu-item-edit-menu", label:RED._("menu.label.edit"), options: [
681
685
  {id: "menu-item-edit-undo", label:RED._("keyboard.undoChange"), disabled: true, onselect: "core:undo"},
682
686
  {id: "menu-item-edit-redo", label:RED._("keyboard.redoChange"), disabled: true, onselect: "core:redo"},
683
687
  null,
@@ -1168,6 +1172,8 @@ RED.i18n = (function() {
1168
1172
  defaultNS: "editor",
1169
1173
  fallbackLng: ['en-US'],
1170
1174
  returnObjects: true,
1175
+ keySeparator: ".",
1176
+ nsSeparator: ":",
1171
1177
  interpolation: {
1172
1178
  unescapeSuffix: 'HTML',
1173
1179
  escapeValue: false,
@@ -1408,7 +1414,7 @@ RED.settings = (function () {
1408
1414
  load(done);
1409
1415
  }
1410
1416
 
1411
- var load = function(done) {
1417
+ var refreshSettings = function(done) {
1412
1418
  $.ajax({
1413
1419
  headers: {
1414
1420
  "Accept": "application/json"
@@ -1418,6 +1424,23 @@ RED.settings = (function () {
1418
1424
  url: 'settings',
1419
1425
  success: function (data) {
1420
1426
  setProperties(data);
1427
+ done(null, data);
1428
+ },
1429
+ error: function(jqXHR,textStatus,errorThrown) {
1430
+ if (jqXHR.status === 401) {
1431
+ if (/[?&]access_token=(.*?)(?:$|&)/.test(window.location.search)) {
1432
+ window.location.search = "";
1433
+ }
1434
+ RED.user.login(function() { refreshSettings(done); });
1435
+ } else {
1436
+ console.log("Unexpected error loading settings:",jqXHR.status,textStatus);
1437
+ }
1438
+ }
1439
+ });
1440
+ }
1441
+ var load = function(done) {
1442
+ refreshSettings(function(err, data) {
1443
+ if (!err) {
1421
1444
  if (!RED.settings.user || RED.settings.user.anonymous) {
1422
1445
  RED.settings.remove("auth-tokens");
1423
1446
  }
@@ -1430,18 +1453,8 @@ RED.settings = (function () {
1430
1453
  console.log("D3",d3.version);
1431
1454
  console.groupEnd();
1432
1455
  loadUserSettings(done);
1433
- },
1434
- error: function(jqXHR,textStatus,errorThrown) {
1435
- if (jqXHR.status === 401) {
1436
- if (/[?&]access_token=(.*?)(?:$|&)/.test(window.location.search)) {
1437
- window.location.search = "";
1438
- }
1439
- RED.user.login(function() { load(done); });
1440
- } else {
1441
- console.log("Unexpected error loading settings:",jqXHR.status,textStatus);
1442
- }
1443
1456
  }
1444
- });
1457
+ })
1445
1458
  };
1446
1459
 
1447
1460
  function loadUserSettings(done) {
@@ -1517,6 +1530,7 @@ RED.settings = (function () {
1517
1530
  init: init,
1518
1531
  load: load,
1519
1532
  loadUserSettings: loadUserSettings,
1533
+ refreshSettings: refreshSettings,
1520
1534
  set: set,
1521
1535
  get: get,
1522
1536
  remove: remove,
@@ -3525,7 +3539,9 @@ RED.state = {
3525
3539
  PANNING: 10,
3526
3540
  SELECTING_NODE: 11,
3527
3541
  GROUP_DRAGGING: 12,
3528
- GROUP_RESIZE: 13
3542
+ GROUP_RESIZE: 13,
3543
+ DETACHED_DRAGGING: 14,
3544
+ SLICING: 15
3529
3545
  }
3530
3546
  ;RED.plugins = (function() {
3531
3547
  var plugins = {};
@@ -3590,6 +3606,9 @@ RED.state = {
3590
3606
  **/
3591
3607
  RED.nodes = (function() {
3592
3608
 
3609
+ var PORT_TYPE_INPUT = 1;
3610
+ var PORT_TYPE_OUTPUT = 0;
3611
+
3593
3612
  var node_defs = {};
3594
3613
  var linkTabMap = {};
3595
3614
 
@@ -4380,7 +4399,6 @@ RED.nodes = (function() {
4380
4399
  var removedGroups = [];
4381
4400
  if (ws) {
4382
4401
  delete workspaces[id];
4383
- allNodes.removeTab(id);
4384
4402
  delete linkTabMap[id];
4385
4403
  workspacesOrder.splice(workspacesOrder.indexOf(id),1);
4386
4404
  var i;
@@ -4418,6 +4436,7 @@ RED.nodes = (function() {
4418
4436
  for (i=removedGroups.length-1; i>=0; i--) {
4419
4437
  removeGroup(removedGroups[i]);
4420
4438
  }
4439
+ allNodes.removeTab(id);
4421
4440
  RED.events.emit('flows:remove',ws);
4422
4441
  }
4423
4442
  return {nodes:removedNodes,links:removedLinks, groups: removedGroups};
@@ -4672,6 +4691,11 @@ RED.nodes = (function() {
4672
4691
  // Until we know how that can happen, add a filter here to remove them
4673
4692
  node.nodes = node.nodes.filter(function(n) { return !!n }).map(function(n) { return n.id });
4674
4693
  }
4694
+ if (n.type === "tab" || n.type === "group") {
4695
+ if (node.env && node.env.length === 0) {
4696
+ delete node.env;
4697
+ }
4698
+ }
4675
4699
  if (n._def.category != "config") {
4676
4700
  node.x = n.x;
4677
4701
  node.y = n.y;
@@ -6028,6 +6052,144 @@ RED.nodes = (function() {
6028
6052
  return helpContent;
6029
6053
  }
6030
6054
 
6055
+ function getNodeIslands(nodes) {
6056
+ var selectedNodes = new Set(nodes);
6057
+ // Maps node => island index
6058
+ var nodeToIslandIndex = new Map();
6059
+ // Maps island index => [nodes in island]
6060
+ var islandIndexToNodes = new Map();
6061
+ var internalLinks = new Set();
6062
+ nodes.forEach((node, index) => {
6063
+ nodeToIslandIndex.set(node,index);
6064
+ islandIndexToNodes.set(index, [node]);
6065
+ var inboundLinks = RED.nodes.getNodeLinks(node, PORT_TYPE_INPUT);
6066
+ var outboundLinks = RED.nodes.getNodeLinks(node, PORT_TYPE_OUTPUT);
6067
+ inboundLinks.forEach(l => {
6068
+ if (selectedNodes.has(l.source)) {
6069
+ internalLinks.add(l)
6070
+ }
6071
+ })
6072
+ outboundLinks.forEach(l => {
6073
+ if (selectedNodes.has(l.target)) {
6074
+ internalLinks.add(l)
6075
+ }
6076
+ })
6077
+ })
6078
+
6079
+ internalLinks.forEach(l => {
6080
+ let source = l.source;
6081
+ let target = l.target;
6082
+ if (nodeToIslandIndex.get(source) !== nodeToIslandIndex.get(target)) {
6083
+ let sourceIsland = nodeToIslandIndex.get(source);
6084
+ let islandToMove = nodeToIslandIndex.get(target);
6085
+ let nodesToMove = islandIndexToNodes.get(islandToMove);
6086
+ nodesToMove.forEach(n => {
6087
+ nodeToIslandIndex.set(n,sourceIsland);
6088
+ islandIndexToNodes.get(sourceIsland).push(n);
6089
+ })
6090
+ islandIndexToNodes.delete(islandToMove);
6091
+ }
6092
+ })
6093
+ const result = [];
6094
+ islandIndexToNodes.forEach((nodes,index) => {
6095
+ result.push(nodes);
6096
+ })
6097
+ return result;
6098
+ }
6099
+
6100
+ function detachNodes(nodes) {
6101
+ let allSelectedNodes = [];
6102
+ nodes.forEach(node => {
6103
+ if (node.type === 'group') {
6104
+ let groupNodes = RED.group.getNodes(node,true,true);
6105
+ allSelectedNodes = allSelectedNodes.concat(groupNodes);
6106
+ } else {
6107
+ allSelectedNodes.push(node);
6108
+ }
6109
+ })
6110
+ if (allSelectedNodes.length > 0 ) {
6111
+ const nodeIslands = RED.nodes.getNodeIslands(allSelectedNodes);
6112
+ let removedLinks = [];
6113
+ let newLinks = [];
6114
+ let createdLinkIds = new Set();
6115
+
6116
+ nodeIslands.forEach(nodes => {
6117
+ let selectedNodes = new Set(nodes);
6118
+ let allInboundLinks = [];
6119
+ let allOutboundLinks = [];
6120
+ // Identify links that enter or exit this island of nodes
6121
+ nodes.forEach(node => {
6122
+ var inboundLinks = RED.nodes.getNodeLinks(node, PORT_TYPE_INPUT);
6123
+ var outboundLinks = RED.nodes.getNodeLinks(node, PORT_TYPE_OUTPUT);
6124
+ inboundLinks.forEach(l => {
6125
+ if (!selectedNodes.has(l.source)) {
6126
+ allInboundLinks.push(l)
6127
+ }
6128
+ })
6129
+ outboundLinks.forEach(l => {
6130
+ if (!selectedNodes.has(l.target)) {
6131
+ allOutboundLinks.push(l)
6132
+ }
6133
+ })
6134
+ });
6135
+
6136
+
6137
+ // Identify the links to restore
6138
+ allInboundLinks.forEach(inLink => {
6139
+ // For Each inbound link,
6140
+ // - get source node.
6141
+ // - trace through to all outbound links
6142
+ let sourceNode = inLink.source;
6143
+ let targetNodes = new Set();
6144
+ let visited = new Set();
6145
+ let stack = [inLink.target];
6146
+ while (stack.length > 0) {
6147
+ let node = stack.pop(stack);
6148
+ visited.add(node)
6149
+ let links = RED.nodes.getNodeLinks(node, PORT_TYPE_OUTPUT);
6150
+ links.forEach(l => {
6151
+ if (visited.has(l.target)) {
6152
+ return
6153
+ }
6154
+ visited.add(l.target);
6155
+ if (selectedNodes.has(l.target)) {
6156
+ // internal link
6157
+ stack.push(l.target)
6158
+ } else {
6159
+ targetNodes.add(l.target)
6160
+ }
6161
+ })
6162
+ }
6163
+ targetNodes.forEach(target => {
6164
+ let linkId = `${sourceNode.id}[${inLink.sourcePort}] -> ${target.id}`
6165
+ if (!createdLinkIds.has(linkId)) {
6166
+ createdLinkIds.add(linkId);
6167
+ let link = {
6168
+ source: sourceNode,
6169
+ sourcePort: inLink.sourcePort,
6170
+ target: target
6171
+ }
6172
+ let existingLinks = RED.nodes.filterLinks(link)
6173
+ if (existingLinks.length === 0) {
6174
+ newLinks.push(link);
6175
+ }
6176
+ }
6177
+ })
6178
+ })
6179
+
6180
+ // 2. delete all those links
6181
+ allInboundLinks.forEach(l => { RED.nodes.removeLink(l); removedLinks.push(l)})
6182
+ allOutboundLinks.forEach(l => { RED.nodes.removeLink(l); removedLinks.push(l)})
6183
+ })
6184
+
6185
+ newLinks.forEach(l => RED.nodes.addLink(l));
6186
+ return {
6187
+ newLinks,
6188
+ removedLinks
6189
+ }
6190
+ }
6191
+ }
6192
+
6031
6193
  return {
6032
6194
  init: function() {
6033
6195
  RED.events.on("registry:node-type-added",function(type) {
@@ -6109,7 +6271,7 @@ RED.nodes = (function() {
6109
6271
  add: addNode,
6110
6272
  remove: removeNode,
6111
6273
  clear: clear,
6112
-
6274
+ detachNodes: detachNodes,
6113
6275
  moveNodesForwards: moveNodesForwards,
6114
6276
  moveNodesBackwards: moveNodesBackwards,
6115
6277
  moveNodesToFront: moveNodesToFront,
@@ -6121,7 +6283,20 @@ RED.nodes = (function() {
6121
6283
 
6122
6284
  addLink: addLink,
6123
6285
  removeLink: removeLink,
6124
-
6286
+ getNodeLinks: function(id, portType) {
6287
+ if (typeof id !== 'string') {
6288
+ id = id.id;
6289
+ }
6290
+ if (nodeLinks[id]) {
6291
+ if (portType === 1) {
6292
+ // Return cloned arrays so they can be safely modified by caller
6293
+ return [].concat(nodeLinks[id].in)
6294
+ } else {
6295
+ return [].concat(nodeLinks[id].out)
6296
+ }
6297
+ }
6298
+ return [];
6299
+ },
6125
6300
  addWorkspace: addWorkspace,
6126
6301
  removeWorkspace: removeWorkspace,
6127
6302
  getWorkspaceOrder: function() { return workspacesOrder },
@@ -6195,6 +6370,7 @@ RED.nodes = (function() {
6195
6370
  getAllFlowNodes: getAllFlowNodes,
6196
6371
  getAllUpstreamNodes: getAllUpstreamNodes,
6197
6372
  getAllDownstreamNodes: getAllDownstreamNodes,
6373
+ getNodeIslands: getNodeIslands,
6198
6374
  createExportableNodeSet: createExportableNodeSet,
6199
6375
  createCompleteNodeSet: createCompleteNodeSet,
6200
6376
  updateConfigNodeUsers: updateConfigNodeUsers,
@@ -7715,6 +7891,13 @@ RED.history = (function() {
7715
7891
  peek: function() {
7716
7892
  return undoHistory[undoHistory.length-1];
7717
7893
  },
7894
+ replace: function(ev) {
7895
+ if (undoHistory.length === 0) {
7896
+ RED.history.push(ev);
7897
+ } else {
7898
+ undoHistory[undoHistory.length-1] = ev;
7899
+ }
7900
+ },
7718
7901
  clear: function() {
7719
7902
  undoHistory = [];
7720
7903
  redoHistory = [];
@@ -7914,6 +8097,8 @@ RED.utils = (function() {
7914
8097
  result = $('<span class="red-ui-debug-msg-object-value red-ui-debug-msg-type-meta"></span>').text('function');
7915
8098
  } else if (value.hasOwnProperty('type') && (value.type === 'number' || value.type === 'bigint')) {
7916
8099
  result = $('<span class="red-ui-debug-msg-object-value red-ui-debug-msg-type-number"></span>').text(value.data);
8100
+ } else if (value.hasOwnProperty('type') && value.type === 'regexp') {
8101
+ result = $('<span class="red-ui-debug-msg-object-value red-ui-debug-msg-type-string"></span>').text(value.data);
7917
8102
  } else {
7918
8103
  result = $('<span class="red-ui-debug-msg-object-value red-ui-debug-msg-type-meta">object</span>');
7919
8104
  }
@@ -8212,6 +8397,8 @@ RED.utils = (function() {
8212
8397
  $('<span class="red-ui-debug-msg-type-null">undefined</span>').appendTo(entryObj);
8213
8398
  } else if (obj.__enc__ && (obj.type === 'number' || obj.type === 'bigint')) {
8214
8399
  e = $('<span class="red-ui-debug-msg-type-number red-ui-debug-msg-object-header"></span>').text(obj.data).appendTo(entryObj);
8400
+ } else if (typeHint === "regexp" || (obj.__enc__ && obj.type === 'regexp')) {
8401
+ e = $('<span class="red-ui-debug-msg-type-string red-ui-debug-msg-object-header"></span>').text((typeof obj === "string")?obj:obj.data).appendTo(entryObj);
8215
8402
  } else if (typeHint === "function" || (obj.__enc__ && obj.type === 'function')) {
8216
8403
  e = $('<span class="red-ui-debug-msg-type-meta red-ui-debug-msg-object-header"></span>').text("function").appendTo(entryObj);
8217
8404
  } else if (typeHint === "internal" || (obj.__enc__ && obj.type === 'internal')) {
@@ -9925,7 +10112,7 @@ RED.utils = (function() {
9925
10112
  if (child.depth !== parent.depth+1) {
9926
10113
  child.depth = parent.depth+1;
9927
10114
  // var labelPaddingWidth = ((child.gutter ? child.gutter[0].offsetWidth + 2 : 0) + (child.depth * 20));
9928
- var labelPaddingWidth = ((child.gutter?child.gutter.width()+2:0)+(child.depth*20));
10115
+ var labelPaddingWidth = (((child.gutter&&!child.gutter.hasClass("red-ui-treeList-gutter-float"))?child.gutter.width()+2:0)+(child.depth*20));
9929
10116
  child.treeList.labelPadding.width(labelPaddingWidth+'px');
9930
10117
  if (child.element) {
9931
10118
  $(child.element).css({
@@ -10147,8 +10334,9 @@ RED.utils = (function() {
10147
10334
  }).appendTo(label)
10148
10335
 
10149
10336
  }
10150
- // var labelPaddingWidth = (item.gutter?item.gutter.width()+2:0)+(depth*20);
10151
- var labelPaddingWidth = (item.gutter ? item.gutter[0].offsetWidth + 2 : 0) + (depth * 20)
10337
+
10338
+ var labelPaddingWidth = ((item.gutter&&!item.gutter.hasClass("red-ui-treeList-gutter-float"))?item.gutter.width()+2:0)+(depth*20);
10339
+
10152
10340
  item.treeList.labelPadding = $('<span>').css({
10153
10341
  display: "inline-block",
10154
10342
  "flex-shrink": 0,
@@ -12072,6 +12260,8 @@ RED.tabs = (function() {
12072
12260
  menuOptions = options.menu()
12073
12261
  } else if (Array.isArray(options.menu)) {
12074
12262
  menuOptions = options.menu;
12263
+ } else if (typeof options.menu === 'function') {
12264
+ menuOptions = options.menu();
12075
12265
  }
12076
12266
  menu = RED.menu.init({options: menuOptions});
12077
12267
  menu.attr("id",options.id+"-menu");
@@ -12531,7 +12721,7 @@ RED.tabs = (function() {
12531
12721
 
12532
12722
  function findPreviousVisibleTab(li) {
12533
12723
  if (!li) {
12534
- li = ul.find("li.active").parent();
12724
+ li = ul.find("li.active");
12535
12725
  }
12536
12726
  var previous = li.prev();
12537
12727
  while(previous.length > 0 && previous.hasClass("hide-tab")) {
@@ -12541,9 +12731,9 @@ RED.tabs = (function() {
12541
12731
  }
12542
12732
  function findNextVisibleTab(li) {
12543
12733
  if (!li) {
12544
- li = ul.find("li.active").parent();
12734
+ li = ul.find("li.active");
12545
12735
  }
12546
- var next = ul.find("li.active").next();
12736
+ var next = li.next();
12547
12737
  while(next.length > 0 && next.hasClass("hide-tab")) {
12548
12738
  next = next.next();
12549
12739
  }
@@ -12764,15 +12954,18 @@ RED.tabs = (function() {
12764
12954
  event.preventDefault();
12765
12955
  removeTab(tab.id);
12766
12956
  });
12957
+ RED.popover.tooltip(closeLink,RED._("workspace.hideFlow"));
12767
12958
  }
12768
12959
  if (tab.hideable) {
12769
12960
  li.addClass("red-ui-tabs-closeable")
12770
- var closeLink = $("<a/>",{href:"#",class:"red-ui-tab-close"}).appendTo(li);
12771
- closeLink.append('<i class="fa fa-times" />');
12961
+ var closeLink = $("<a/>",{href:"#",class:"red-ui-tab-close red-ui-tab-hide"}).appendTo(li);
12962
+ closeLink.append('<i class="fa fa-eye" />');
12963
+ closeLink.append('<i class="fa fa-eye-slash" />');
12772
12964
  closeLink.on("click",function(event) {
12773
12965
  event.preventDefault();
12774
12966
  hideTab(tab.id);
12775
12967
  });
12968
+ RED.popover.tooltip(closeLink,RED._("workspace.hideFlow"));
12776
12969
  }
12777
12970
 
12778
12971
  var badges = $('<span class="red-ui-tabs-badges"></span>').appendTo(li);
@@ -12781,7 +12974,8 @@ RED.tabs = (function() {
12781
12974
  $('<i class="red-ui-tabs-badge-selected fa fa-check-circle"></i>').appendTo(badges);
12782
12975
  }
12783
12976
 
12784
- link.attr("title",tab.label);
12977
+ // link.attr("title",tab.label);
12978
+ RED.popover.tooltip(link,function() { return tab.label})
12785
12979
 
12786
12980
  if (options.onadd) {
12787
12981
  options.onadd(tab);
@@ -12900,7 +13094,6 @@ RED.tabs = (function() {
12900
13094
  renameTab: function(id,label) {
12901
13095
  tabs[id].label = label;
12902
13096
  var tab = ul.find("a[href='#"+id+"']");
12903
- tab.attr("title",label);
12904
13097
  tab.find("span.red-ui-text-bidi-aware").text(label).attr('dir', RED.text.bidi.resolveBaseTextDir(label));
12905
13098
  updateTabWidths();
12906
13099
  },
@@ -13704,7 +13897,7 @@ RED.stack = (function() {
13704
13897
  this.optionExpandButton = $('<button tabindex="0" class="red-ui-typedInput-option-expand" style="display:inline-block"></button>').appendTo(this.uiSelect);
13705
13898
  this.optionExpandButtonIcon = $('<i class="red-ui-typedInput-icon fa fa-ellipsis-h"></i>').appendTo(this.optionExpandButton);
13706
13899
 
13707
- this.type(this.options.default||this.typeList[0].value);
13900
+ this.type(this.typeField.val() || this.options.default||this.typeList[0].value);
13708
13901
  this.typeChanged = !!this.options.default;
13709
13902
  }catch(err) {
13710
13903
  console.log(err.stack);
@@ -13953,6 +14146,7 @@ RED.stack = (function() {
13953
14146
  var that = this;
13954
14147
  var currentType = this.type();
13955
14148
  this.typeMap = {};
14149
+ var firstCall = (this.typeList === undefined);
13956
14150
  this.typeList = types.map(function(opt) {
13957
14151
  var result;
13958
14152
  if (typeof opt === 'string') {
@@ -13977,10 +14171,14 @@ RED.stack = (function() {
13977
14171
  }
13978
14172
  this.menu = this._createMenu(this.typeList,{},function(v) { that.type(v) });
13979
14173
  if (currentType && !this.typeMap.hasOwnProperty(currentType)) {
13980
- this.type(this.typeList[0].value);
14174
+ if (!firstCall) {
14175
+ this.type(this.typeList[0].value);
14176
+ }
13981
14177
  } else {
13982
14178
  this.propertyType = null;
13983
- this.type(currentType);
14179
+ if (!firstCall) {
14180
+ this.type(currentType);
14181
+ }
13984
14182
  }
13985
14183
  if (this.typeList.length === 1 && !this.typeList[0].icon && (!this.typeList[0].label || this.typeList[0].showLabel === false)) {
13986
14184
  this.selectTrigger.hide()
@@ -14071,7 +14269,7 @@ RED.stack = (function() {
14071
14269
  var previousType = this.typeMap[this.propertyType];
14072
14270
  previousValue = this.input.val();
14073
14271
 
14074
- if (this.typeChanged) {
14272
+ if (previousType && this.typeChanged) {
14075
14273
  if (this.options.debug) { console.log(this.identifier,"typeChanged",{previousType,previousValue}) }
14076
14274
  if (previousType.options && opt.hasValue !== true) {
14077
14275
  this.oldValues[previousType.value] = previousValue;
@@ -14532,7 +14730,7 @@ RED.stack = (function() {
14532
14730
  * value: String : the value to insert if selected
14533
14731
  * label: String|DOM Element : the label to display in the dropdown.
14534
14732
  * }
14535
- *
14733
+ *
14536
14734
  */
14537
14735
 
14538
14736
  $.widget( "nodered.autoComplete", {
@@ -14573,7 +14771,7 @@ RED.stack = (function() {
14573
14771
  maxHeight: 200,
14574
14772
  class: "red-ui-autoComplete-container",
14575
14773
  options: completions,
14576
- onselect: (opt) => { this.element.val(opt.value); this.element.focus() },
14774
+ onselect: (opt) => { this.element.val(opt.value); this.element.focus(); this.element.trigger("change") },
14577
14775
  onclose: () => { this.completionMenuShown = false; delete this.menu; this.element.focus()}
14578
14776
  });
14579
14777
  this.menu.show({
@@ -15013,6 +15211,16 @@ RED.deploy = (function() {
15013
15211
  var unknownNodes = [];
15014
15212
  var invalidNodes = [];
15015
15213
 
15214
+ RED.nodes.eachConfig(function(node) {
15215
+ if (!node.valid && !node.d) {
15216
+ invalidNodes.push(getNodeInfo(node));
15217
+ }
15218
+ if (node.type === "unknown") {
15219
+ if (unknownNodes.indexOf(node.name) == -1) {
15220
+ unknownNodes.push(node.name);
15221
+ }
15222
+ }
15223
+ });
15016
15224
  RED.nodes.eachNode(function(node) {
15017
15225
  if (!node.valid && !node.d) {
15018
15226
  invalidNodes.push(getNodeInfo(node));
@@ -15750,6 +15958,8 @@ RED.deploy = (function() {
15750
15958
  color: "#DDAA99",
15751
15959
  defaults:{name:{value:""}}
15752
15960
  }
15961
+ } else if (node.type === "group") {
15962
+ def = RED.group.def;
15753
15963
  } else {
15754
15964
  def = {};
15755
15965
  }
@@ -15959,16 +16169,15 @@ RED.deploy = (function() {
15959
16169
  }
15960
16170
  }
15961
16171
 
15962
-
15963
16172
  if (node.hasOwnProperty('x')) {
15964
16173
  if (localNode) {
15965
- if (localNode.x !== node.x || localNode.y !== node.y) {
16174
+ if (localNode.x !== node.x || localNode.y !== node.y || localNode.w !== node.w || localNode.h !== node.h ) {
15966
16175
  localChanged = true;
15967
16176
  localChanges++;
15968
16177
  }
15969
16178
  }
15970
16179
  if (remoteNode) {
15971
- if (remoteNode.x !== node.x || remoteNode.y !== node.y) {
16180
+ if (remoteNode.x !== node.x || remoteNode.y !== node.y|| remoteNode.w !== node.w || remoteNode.h !== node.h) {
15972
16181
  remoteChanged = true;
15973
16182
  remoteChanges++;
15974
16183
  }
@@ -15986,7 +16195,12 @@ RED.deploy = (function() {
15986
16195
  localCell.addClass("red-ui-diff-status-"+(localChanged?"changed":"unchanged"));
15987
16196
  $('<span class="red-ui-diff-status">'+(localChanged?'<i class="fa fa-square"></i>':'')+'</span>').appendTo(localCell);
15988
16197
  element = $('<span class="red-ui-diff-list-element"></span>').appendTo(localCell);
15989
- propertyElements['local.position'] = RED.utils.createObjectElement({x:localNode.x,y:localNode.y},
16198
+ var localPosition = {x:localNode.x,y:localNode.y};
16199
+ if (localNode.hasOwnProperty('w')) {
16200
+ localPosition.w = localNode.w;
16201
+ localPosition.h = localNode.h;
16202
+ }
16203
+ propertyElements['local.position'] = RED.utils.createObjectElement(localPosition,
15990
16204
  {
15991
16205
  path: "position",
15992
16206
  exposeApi: true,
@@ -16007,7 +16221,12 @@ RED.deploy = (function() {
16007
16221
  if (remoteNode) {
16008
16222
  $('<span class="red-ui-diff-status">'+(remoteChanged?'<i class="fa fa-square"></i>':'')+'</span>').appendTo(remoteCell);
16009
16223
  element = $('<span class="red-ui-diff-list-element"></span>').appendTo(remoteCell);
16010
- propertyElements['remote.position'] = RED.utils.createObjectElement({x:remoteNode.x,y:remoteNode.y},
16224
+ var remotePosition = {x:remoteNode.x,y:remoteNode.y};
16225
+ if (remoteNode.hasOwnProperty('w')) {
16226
+ remotePosition.w = remoteNode.w;
16227
+ remotePosition.h = remoteNode.h;
16228
+ }
16229
+ propertyElements['remote.position'] = RED.utils.createObjectElement(remotePosition,
16011
16230
  {
16012
16231
  path: "position",
16013
16232
  exposeApi: true,
@@ -16079,11 +16298,11 @@ RED.deploy = (function() {
16079
16298
  }
16080
16299
  }
16081
16300
  }
16082
- var properties = Object.keys(node).filter(function(p) { return p!='inputLabels'&&p!='outputLabels'&&p!='z'&&p!='wires'&&p!=='x'&&p!=='y'&&p!=='id'&&p!=='type'&&(!def.defaults||!def.defaults.hasOwnProperty(p))});
16301
+ var properties = Object.keys(node).filter(function(p) { return p!='inputLabels'&&p!='outputLabels'&&p!='z'&&p!='wires'&&p!=='x'&&p!=='y'&&p!=='w'&&p!=='h'&&p!=='id'&&p!=='type'&&(!def.defaults||!def.defaults.hasOwnProperty(p))});
16083
16302
  if (def.defaults) {
16084
16303
  properties = properties.concat(Object.keys(def.defaults));
16085
16304
  }
16086
- if (node.type !== 'tab') {
16305
+ if (node.type !== 'tab' && node.type !== "group") {
16087
16306
  properties = properties.concat(['inputLabels','outputLabels']);
16088
16307
  }
16089
16308
  if ( ((localNode && localNode.hasOwnProperty('icon')) || (remoteNode && remoteNode.hasOwnProperty('icon'))) &&
@@ -18184,7 +18403,7 @@ RED.workspaces = (function() {
18184
18403
  var tabId = RED.nodes.id();
18185
18404
  do {
18186
18405
  workspaceIndex += 1;
18187
- } while ($("#red-ui-workspace-tabs a[title='"+RED._('workspace.defaultName',{number:workspaceIndex})+"']").size() !== 0);
18406
+ } while ($("#red-ui-workspace-tabs li[flowname='"+RED._('workspace.defaultName',{number:workspaceIndex})+"']").size() !== 0);
18188
18407
 
18189
18408
  ws = {
18190
18409
  type: "tab",
@@ -18197,12 +18416,15 @@ RED.workspaces = (function() {
18197
18416
  };
18198
18417
  RED.nodes.addWorkspace(ws,targetIndex);
18199
18418
  workspace_tabs.addTab(ws,targetIndex);
18419
+
18200
18420
  workspace_tabs.activateTab(tabId);
18201
18421
  if (!skipHistoryEntry) {
18202
18422
  RED.history.push({t:'add',workspaces:[ws],dirty:RED.nodes.dirty()});
18203
18423
  RED.nodes.dirty(true);
18204
18424
  }
18205
18425
  }
18426
+ $("#red-ui-tab-"+(ws.id.replace(".","-"))).attr("flowname",ws.label)
18427
+
18206
18428
  RED.view.focus();
18207
18429
  return ws;
18208
18430
  }
@@ -18326,65 +18548,84 @@ RED.workspaces = (function() {
18326
18548
  },
18327
18549
  onhide: function(tab) {
18328
18550
  hideStack.push(tab.id);
18551
+
18552
+ var hiddenTabs = JSON.parse(RED.settings.getLocal("hiddenTabs")||"{}");
18553
+ hiddenTabs[tab.id] = true;
18554
+ RED.settings.setLocal("hiddenTabs",JSON.stringify(hiddenTabs));
18555
+
18329
18556
  RED.events.emit("workspace:hide",{workspace: tab.id})
18330
18557
  },
18331
18558
  onshow: function(tab) {
18332
18559
  removeFromHideStack(tab.id);
18560
+
18561
+ var hiddenTabs = JSON.parse(RED.settings.getLocal("hiddenTabs")||"{}");
18562
+ delete hiddenTabs[tab.id];
18563
+ RED.settings.setLocal("hiddenTabs",JSON.stringify(hiddenTabs));
18564
+
18333
18565
  RED.events.emit("workspace:show",{workspace: tab.id})
18334
18566
  },
18335
18567
  minimumActiveTabWidth: 150,
18336
18568
  scrollable: true,
18337
18569
  addButton: "core:add-flow",
18338
18570
  addButtonCaption: RED._("workspace.addFlow"),
18339
- menu: [
18340
- {
18341
- id:"red-ui-tabs-menu-option-search-flows",
18342
- label: RED._("workspace.listFlows"),
18343
- onselect: "core:list-flows"
18344
- },
18345
- {
18346
- id:"red-ui-tabs-menu-option-search-subflows",
18347
- label: RED._("workspace.listSubflows"),
18348
- onselect: "core:list-subflows"
18349
- },
18350
- null,
18351
- {
18352
- id:"red-ui-tabs-menu-option-add-flow",
18353
- label: RED._("workspace.addFlow"),
18354
- onselect: "core:add-flow"
18355
- },
18356
- {
18357
- id:"red-ui-tabs-menu-option-add-flow-right",
18358
- label: RED._("workspace.addFlowToRight"),
18359
- onselect: "core:add-flow-to-right"
18360
- },
18361
- null,
18362
- {
18363
- id:"red-ui-tabs-menu-option-add-hide-flows",
18364
- label: RED._("workspace.hideFlow"),
18365
- onselect: "core:hide-flow"
18366
- },
18367
- {
18368
- id:"red-ui-tabs-menu-option-add-hide-other-flows",
18369
- label: RED._("workspace.hideOtherFlows"),
18370
- onselect: "core:hide-other-flows"
18371
- },
18372
- {
18373
- id:"red-ui-tabs-menu-option-add-show-all-flows",
18374
- label: RED._("workspace.showAllFlows"),
18375
- onselect: "core:show-all-flows"
18376
- },
18377
- {
18378
- id:"red-ui-tabs-menu-option-add-hide-all-flows",
18379
- label: RED._("workspace.hideAllFlows"),
18380
- onselect: "core:hide-all-flows"
18381
- },
18382
- {
18383
- id:"red-ui-tabs-menu-option-add-show-last-flow",
18384
- label: RED._("workspace.showLastHiddenFlow"),
18385
- onselect: "core:show-last-hidden-flow"
18571
+ menu: function() {
18572
+ var menuItems = [
18573
+ {
18574
+ id:"red-ui-tabs-menu-option-search-flows",
18575
+ label: RED._("workspace.listFlows"),
18576
+ onselect: "core:list-flows"
18577
+ },
18578
+ {
18579
+ id:"red-ui-tabs-menu-option-search-subflows",
18580
+ label: RED._("workspace.listSubflows"),
18581
+ onselect: "core:list-subflows"
18582
+ },
18583
+ null,
18584
+ {
18585
+ id:"red-ui-tabs-menu-option-add-flow",
18586
+ label: RED._("workspace.addFlow"),
18587
+ onselect: "core:add-flow"
18588
+ },
18589
+ {
18590
+ id:"red-ui-tabs-menu-option-add-flow-right",
18591
+ label: RED._("workspace.addFlowToRight"),
18592
+ onselect: "core:add-flow-to-right"
18593
+ },
18594
+ null,
18595
+ {
18596
+ id:"red-ui-tabs-menu-option-add-hide-flows",
18597
+ label: RED._("workspace.hideFlow"),
18598
+ onselect: "core:hide-flow"
18599
+ },
18600
+ {
18601
+ id:"red-ui-tabs-menu-option-add-hide-other-flows",
18602
+ label: RED._("workspace.hideOtherFlows"),
18603
+ onselect: "core:hide-other-flows"
18604
+ },
18605
+ {
18606
+ id:"red-ui-tabs-menu-option-add-show-all-flows",
18607
+ label: RED._("workspace.showAllFlows"),
18608
+ onselect: "core:show-all-flows"
18609
+ },
18610
+ {
18611
+ id:"red-ui-tabs-menu-option-add-hide-all-flows",
18612
+ label: RED._("workspace.hideAllFlows"),
18613
+ onselect: "core:hide-all-flows"
18614
+ },
18615
+ {
18616
+ id:"red-ui-tabs-menu-option-add-show-last-flow",
18617
+ label: RED._("workspace.showLastHiddenFlow"),
18618
+ onselect: "core:show-last-hidden-flow"
18619
+ }
18620
+ ]
18621
+ if (hideStack.length > 0) {
18622
+ menuItems.unshift({
18623
+ label: RED._("workspace.hiddenFlows",{count: hideStack.length}),
18624
+ onselect: "core:list-hidden-flows"
18625
+ })
18386
18626
  }
18387
- ]
18627
+ return menuItems;
18628
+ }
18388
18629
  });
18389
18630
  workspaceTabCount = 0;
18390
18631
  }
@@ -18524,7 +18765,9 @@ RED.workspaces = (function() {
18524
18765
  }
18525
18766
  }
18526
18767
  })
18527
-
18768
+ RED.actions.add("core:list-hidden-flows",function() {
18769
+ RED.actions.invoke("core:search","is:hidden ");
18770
+ })
18528
18771
  RED.actions.add("core:list-flows",function() {
18529
18772
  RED.actions.invoke("core:search","type:tab ");
18530
18773
  })
@@ -18568,7 +18811,7 @@ RED.workspaces = (function() {
18568
18811
  var changes = { disabled: workspace.disabled };
18569
18812
  workspace.disabled = disabled;
18570
18813
  $("#red-ui-tab-"+(workspace.id.replace(".","-"))).toggleClass('red-ui-workspace-disabled',!!workspace.disabled);
18571
- if (id || activeWorkspace) {
18814
+ if (!id || (id === activeWorkspace)) {
18572
18815
  $("#red-ui-workspace").toggleClass("red-ui-workspace-disabled",!!workspace.disabled);
18573
18816
  }
18574
18817
  var historyEvent = {
@@ -18649,11 +18892,11 @@ RED.workspaces = (function() {
18649
18892
  }
18650
18893
  if (workspace_tabs.contains(id)) {
18651
18894
  workspace_tabs.hideTab(id);
18652
- var hiddenTabs = JSON.parse(RED.settings.getLocal("hiddenTabs")||"{}");
18653
- hiddenTabs[id] = true;
18654
- RED.settings.setLocal("hiddenTabs",JSON.stringify(hiddenTabs));
18655
18895
  }
18656
18896
  },
18897
+ isHidden: function(id) {
18898
+ return hideStack.includes(id)
18899
+ },
18657
18900
  show: function(id,skipStack,unhideOnly) {
18658
18901
  if (!workspace_tabs.contains(id)) {
18659
18902
  var sf = RED.nodes.subflow(id);
@@ -18676,14 +18919,11 @@ RED.workspaces = (function() {
18676
18919
  }
18677
18920
  workspace_tabs.activateTab(id);
18678
18921
  }
18679
- var hiddenTabs = JSON.parse(RED.settings.getLocal("hiddenTabs")||"{}");
18680
- delete hiddenTabs[id];
18681
- RED.settings.setLocal("hiddenTabs",JSON.stringify(hiddenTabs));
18682
18922
  },
18683
18923
  refresh: function() {
18684
18924
  RED.nodes.eachWorkspace(function(ws) {
18685
18925
  workspace_tabs.renameTab(ws.id,ws.label);
18686
-
18926
+ $("#red-ui-tab-"+(ws.id.replace(".","-"))).attr("flowname",ws.label)
18687
18927
  })
18688
18928
  RED.nodes.eachSubflow(function(sf) {
18689
18929
  if (workspace_tabs.contains(sf.id)) {
@@ -18814,7 +19054,6 @@ RED.view = (function() {
18814
19054
  var activeGroups = [];
18815
19055
  var dirtyGroups = {};
18816
19056
 
18817
- var selected_link = null;
18818
19057
  var mousedown_link = null;
18819
19058
  var mousedown_node = null;
18820
19059
  var mousedown_group = null;
@@ -18826,6 +19065,8 @@ RED.view = (function() {
18826
19065
  var mouse_mode = 0;
18827
19066
  var mousedown_group_handle = null;
18828
19067
  var lasso = null;
19068
+ var slicePath = null;
19069
+ var slicePathLast = null;
18829
19070
  var ghostNode = null;
18830
19071
  var showStatus = false;
18831
19072
  var lastClickNode = null;
@@ -18880,6 +19121,14 @@ RED.view = (function() {
18880
19121
  if (!setIds.has(node.id)) {
18881
19122
  set.push({n:node});
18882
19123
  setIds.add(node.id);
19124
+ var links = RED.nodes.getNodeLinks(node.id,PORT_TYPE_INPUT).concat(RED.nodes.getNodeLinks(node.id,PORT_TYPE_OUTPUT))
19125
+ for (var i=0,l=links.length;i<l;i++) {
19126
+ var link = links[i]
19127
+ if (link.source === node && setIds.has(link.target.id) ||
19128
+ link.target === node && setIds.has(link.source.id)) {
19129
+ selectedLinks.add(link)
19130
+ }
19131
+ }
18883
19132
  }
18884
19133
  }
18885
19134
  },
@@ -18896,6 +19145,10 @@ RED.view = (function() {
18896
19145
  }
18897
19146
  }
18898
19147
  }
19148
+ var links = RED.nodes.getNodeLinks(node.id,PORT_TYPE_INPUT).concat(RED.nodes.getNodeLinks(node.id,PORT_TYPE_OUTPUT))
19149
+ for (var i=0,l=links.length;i<l;i++) {
19150
+ selectedLinks.remove(links[i]);
19151
+ }
18899
19152
  }
18900
19153
  },
18901
19154
  clear: function() {
@@ -18910,6 +19163,31 @@ RED.view = (function() {
18910
19163
  return api;
18911
19164
  })();
18912
19165
 
19166
+ var selectedLinks = (function() {
19167
+ var links = new Set();
19168
+ return {
19169
+ add: function(link) {
19170
+ links.add(link);
19171
+ link.selected = true;
19172
+ },
19173
+ remove: function(link) {
19174
+ links.delete(link);
19175
+ link.selected = false;
19176
+ },
19177
+ clear: function() {
19178
+ links.forEach(function(link) { link.selected = false })
19179
+ links.clear();
19180
+ },
19181
+ length: function() {
19182
+ return links.size;
19183
+ },
19184
+ forEach: function(func) { links.forEach(func) },
19185
+ has: function(link) { return links.has(link) },
19186
+ toArray: function() { return Array.from(links) }
19187
+ }
19188
+ })();
19189
+
19190
+
18913
19191
  function init() {
18914
19192
 
18915
19193
  chart = $("#red-ui-workspace-chart");
@@ -18944,6 +19222,12 @@ RED.view = (function() {
18944
19222
  }
18945
19223
  } else if (mouse_mode === RED.state.PANNING && d3.event.buttons !== 4) {
18946
19224
  resetMouseVars();
19225
+ } else if (slicePath) {
19226
+ if (d3.event.buttons !== 2) {
19227
+ slicePath.remove();
19228
+ slicePath = null;
19229
+ resetMouseVars()
19230
+ }
18947
19231
  }
18948
19232
  })
18949
19233
  .on("touchend", function() {
@@ -19163,26 +19447,56 @@ RED.view = (function() {
19163
19447
  var historyEvent = result.historyEvent;
19164
19448
  var nn = result.node;
19165
19449
 
19450
+ RED.nodes.add(nn);
19451
+
19166
19452
  var showLabel = RED.utils.getMessageProperty(RED.settings.get('editor'),"view.view-node-show-label");
19167
19453
  if (showLabel !== undefined && (nn._def.hasOwnProperty("showLabel")?nn._def.showLabel:true) && !nn._def.defaults.hasOwnProperty("l")) {
19168
19454
  nn.l = showLabel;
19169
19455
  }
19170
19456
 
19171
19457
  var helperOffset = d3.touches(ui.helper.get(0))[0]||d3.mouse(ui.helper.get(0));
19458
+ var helperWidth = ui.helper.width();
19459
+ var helperHeight = ui.helper.height();
19172
19460
  var mousePos = d3.touches(this)[0]||d3.mouse(this);
19173
19461
 
19174
- mousePos[1] += this.scrollTop + ((nn.h/2)-helperOffset[1]);
19175
- mousePos[0] += this.scrollLeft + ((nn.w/2)-helperOffset[0]);
19462
+ try {
19463
+ var isLink = (nn.type === "link in" || nn.type === "link out")
19464
+ var hideLabel = nn.hasOwnProperty('l')?!nn.l : isLink;
19465
+
19466
+ var label = RED.utils.getNodeLabel(nn, nn.type);
19467
+ var labelParts = getLabelParts(label, "red-ui-flow-node-label");
19468
+ if (hideLabel) {
19469
+ nn.w = node_height;
19470
+ nn.h = Math.max(node_height,(nn.outputs || 0) * 15);
19471
+ } else {
19472
+ nn.w = Math.max(node_width,20*(Math.ceil((labelParts.width+50+(nn._def.inputs>0?7:0))/20)) );
19473
+ nn.h = Math.max(6+24*labelParts.lines.length,(nn.outputs || 0) * 15, 30);
19474
+ }
19475
+ } catch(err) {
19476
+ }
19477
+
19478
+ mousePos[1] += this.scrollTop + ((helperHeight/2)-helperOffset[1]);
19479
+ mousePos[0] += this.scrollLeft + ((helperWidth/2)-helperOffset[0]);
19176
19480
  mousePos[1] /= scaleFactor;
19177
19481
  mousePos[0] /= scaleFactor;
19178
19482
 
19179
- if (snapGrid) {
19180
- mousePos[0] = gridSize*(Math.ceil(mousePos[0]/gridSize));
19181
- mousePos[1] = gridSize*(Math.ceil(mousePos[1]/gridSize));
19182
- }
19183
19483
  nn.x = mousePos[0];
19184
19484
  nn.y = mousePos[1];
19185
19485
 
19486
+ if (snapGrid) {
19487
+ var gridOffset = [0,0];
19488
+ var offsetLeft = nn.x-(gridSize*Math.round((nn.x-nn.w/2)/gridSize)+nn.w/2);
19489
+ var offsetRight = nn.x-(gridSize*Math.round((nn.x+nn.w/2)/gridSize)-nn.w/2);
19490
+ if (Math.abs(offsetLeft) < Math.abs(offsetRight)) {
19491
+ gridOffset[0] = offsetLeft
19492
+ } else {
19493
+ gridOffset[0] = offsetRight
19494
+ }
19495
+ gridOffset[1] = nn.y-(gridSize*Math.round(nn.y/gridSize));
19496
+ nn.x -= gridOffset[0];
19497
+ nn.y -= gridOffset[1];
19498
+ }
19499
+
19186
19500
  var spliceLink = $(ui.helper).data("splice");
19187
19501
  if (spliceLink) {
19188
19502
  // TODO: DRY - droppable/nodeMouseDown/canvasMouseUp/showQuickAddDialog
@@ -19203,7 +19517,6 @@ RED.view = (function() {
19203
19517
  historyEvent.removedLinks = [spliceLink];
19204
19518
  }
19205
19519
 
19206
- RED.nodes.add(nn);
19207
19520
 
19208
19521
  var group = $(ui.helper).data("group");
19209
19522
  if (group) {
@@ -19253,6 +19566,8 @@ RED.view = (function() {
19253
19566
  RED.actions.add("core:cut-selection-to-internal-clipboard",function(){copySelection();deleteSelection();});
19254
19567
  RED.actions.add("core:paste-from-internal-clipboard",function(){importNodes(clipboard,{generateIds: true});});
19255
19568
 
19569
+ RED.actions.add("core:detach-selected-nodes", function() { detachSelectedNodes() })
19570
+
19256
19571
  RED.events.on("view:selection-changed", function(selection) {
19257
19572
  var hasSelection = (selection.nodes && selection.nodes.length > 0);
19258
19573
  var hasMultipleSelection = hasSelection && selection.nodes.length > 1;
@@ -19275,6 +19590,7 @@ RED.view = (function() {
19275
19590
  })
19276
19591
 
19277
19592
  RED.actions.add("core:delete-selection",deleteSelection);
19593
+ RED.actions.add("core:delete-selection-and-reconnect",function() { deleteSelection(true) });
19278
19594
  RED.actions.add("core:edit-selected-node",editSelection);
19279
19595
  RED.actions.add("core:go-to-selection",function() {
19280
19596
  if (movingSet.length() > 0) {
@@ -19660,7 +19976,7 @@ RED.view = (function() {
19660
19976
  return;
19661
19977
  }
19662
19978
  if (!mousedown_node && !mousedown_link && !mousedown_group) {
19663
- selected_link = null;
19979
+ selectedLinks.clear();
19664
19980
  updateSelection();
19665
19981
  }
19666
19982
  if (mouse_mode === 0) {
@@ -19669,19 +19985,18 @@ RED.view = (function() {
19669
19985
  lasso = null;
19670
19986
  }
19671
19987
  }
19672
- if (mouse_mode === 0 || mouse_mode === RED.state.QUICK_JOINING) {
19673
- if (d3.event.metaKey || d3.event.ctrlKey) {
19674
- d3.event.stopPropagation();
19675
- clearSelection();
19676
- point = d3.mouse(this);
19677
- var clickedGroup = getGroupAt(point[0],point[1]);
19678
- if (drag_lines.length > 0) {
19679
- clickedGroup = clickedGroup || RED.nodes.group(drag_lines[0].node.g)
19680
- }
19681
- showQuickAddDialog({position:point, group:clickedGroup});
19988
+ if ((mouse_mode === 0 || mouse_mode === RED.state.QUICK_JOINING) && (d3.event.touches || d3.event.button === 0) && (d3.event.metaKey || d3.event.ctrlKey)) {
19989
+ // Trigger quick add dialog
19990
+ d3.event.stopPropagation();
19991
+ clearSelection();
19992
+ point = d3.mouse(this);
19993
+ var clickedGroup = getGroupAt(point[0],point[1]);
19994
+ if (drag_lines.length > 0) {
19995
+ clickedGroup = clickedGroup || RED.nodes.group(drag_lines[0].node.g)
19682
19996
  }
19683
- }
19684
- if (mouse_mode === 0 && !(d3.event.metaKey || d3.event.ctrlKey)) {
19997
+ showQuickAddDialog({position:point, group:clickedGroup});
19998
+ } else if (mouse_mode === 0 && (d3.event.touches || d3.event.button === 0) && !(d3.event.metaKey || d3.event.ctrlKey)) {
19999
+ // Tigger lasso
19685
20000
  if (!touchStartTime) {
19686
20001
  point = d3.mouse(this);
19687
20002
  lasso = eventLayer.append("rect")
@@ -19696,6 +20011,13 @@ RED.view = (function() {
19696
20011
  .attr("class","nr-ui-view-lasso");
19697
20012
  d3.event.preventDefault();
19698
20013
  }
20014
+ } else if (mouse_mode === 0 && d3.event.button === 2 && (d3.event.metaKey || d3.event.ctrlKey)) {
20015
+ clearSelection();
20016
+ mouse_mode = RED.state.SLICING;
20017
+ point = d3.mouse(this);
20018
+ slicePath = eventLayer.append("path").attr("class","nr-ui-view-slice").attr("d",`M${point[0]} ${point[1]}`)
20019
+ slicePathLast = point;
20020
+ RED.view.redraw();
19699
20021
  }
19700
20022
  }
19701
20023
 
@@ -20092,6 +20414,17 @@ RED.view = (function() {
20092
20414
  .attr("height",h)
20093
20415
  ;
20094
20416
  return;
20417
+ } else if (mouse_mode === RED.state.SLICING) {
20418
+ if (slicePath) {
20419
+ var delta = Math.max(1,Math.abs(slicePathLast[0]-mouse_position[0]))*Math.max(1,Math.abs(slicePathLast[1]-mouse_position[1]))
20420
+ if (delta > 20) {
20421
+ var currentPath = slicePath.attr("d")
20422
+ currentPath += " L"+mouse_position[0]+" "+mouse_position[1]
20423
+ slicePath.attr("d",currentPath);
20424
+ slicePathLast = mouse_position
20425
+ }
20426
+ }
20427
+ return
20095
20428
  }
20096
20429
 
20097
20430
  if (mouse_mode === RED.state.SELECTING_NODE) {
@@ -20099,7 +20432,7 @@ RED.view = (function() {
20099
20432
  return;
20100
20433
  }
20101
20434
 
20102
- if (mouse_mode != RED.state.QUICK_JOINING && mouse_mode != RED.state.IMPORT_DRAGGING && !mousedown_node && !mousedown_group && selected_link == null) {
20435
+ 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) {
20103
20436
  return;
20104
20437
  }
20105
20438
 
@@ -20123,16 +20456,18 @@ RED.view = (function() {
20123
20456
  // Get all the wires we need to detach.
20124
20457
  var links = [];
20125
20458
  var existingLinks = [];
20126
- if (selected_link &&
20127
- ((mousedown_port_type === PORT_TYPE_OUTPUT &&
20128
- selected_link.source === mousedown_node &&
20129
- selected_link.sourcePort === mousedown_port_index
20130
- ) ||
20131
- (mousedown_port_type === PORT_TYPE_INPUT &&
20132
- selected_link.target === mousedown_node
20133
- ))
20134
- ) {
20135
- existingLinks = [selected_link];
20459
+ if (selectedLinks.length() > 0) {
20460
+ selectedLinks.forEach(function(link) {
20461
+ if (((mousedown_port_type === PORT_TYPE_OUTPUT &&
20462
+ link.source === mousedown_node &&
20463
+ link.sourcePort === mousedown_port_index
20464
+ ) ||
20465
+ (mousedown_port_type === PORT_TYPE_INPUT &&
20466
+ link.target === mousedown_node
20467
+ ))) {
20468
+ existingLinks.push(link);
20469
+ }
20470
+ })
20136
20471
  } else {
20137
20472
  var filter;
20138
20473
  if (mousedown_port_type === PORT_TYPE_OUTPUT) {
@@ -20170,7 +20505,7 @@ RED.view = (function() {
20170
20505
  } else if (mousedown_node && !quickAddLink) {
20171
20506
  showDragLines([{node:mousedown_node,port:mousedown_port_index,portType:mousedown_port_type}]);
20172
20507
  }
20173
- selected_link = null;
20508
+ selectedLinks.clear();
20174
20509
  }
20175
20510
  mousePos = mouse_position;
20176
20511
  for (i=0;i<drag_lines.length;i++) {
@@ -20202,7 +20537,7 @@ RED.view = (function() {
20202
20537
  RED.nodes.filterLinks({ target: node.n }).length === 0;
20203
20538
  }
20204
20539
  }
20205
- } else if (mouse_mode == RED.state.MOVING_ACTIVE || mouse_mode == RED.state.IMPORT_DRAGGING) {
20540
+ } else if (mouse_mode == RED.state.MOVING_ACTIVE || mouse_mode == RED.state.IMPORT_DRAGGING || mouse_mode == RED.state.DETACHED_DRAGGING) {
20206
20541
  mousePos = mouse_position;
20207
20542
  var minX = 0;
20208
20543
  var minY = 0;
@@ -20266,8 +20601,16 @@ RED.view = (function() {
20266
20601
  gridOffset[0] = node.n.x-(gridSize*Math.floor(node.n.x/gridSize))-gridSize/2;
20267
20602
  gridOffset[1] = node.n.y-(gridSize*Math.floor(node.n.y/gridSize))-gridSize/2;
20268
20603
  } else {
20269
- gridOffset[0] = node.n.x-(gridSize*Math.floor((node.n.x-node.n.w/2)/gridSize)+node.n.w/2);
20270
- gridOffset[1] = node.n.y-(gridSize*Math.floor(node.n.y/gridSize));
20604
+ var offsetLeft = node.n.x-(gridSize*Math.round((node.n.x-node.n.w/2)/gridSize)+node.n.w/2);
20605
+ var offsetRight = node.n.x-(gridSize*Math.round((node.n.x+node.n.w/2)/gridSize)-node.n.w/2);
20606
+ // gridOffset[0] = node.n.x-(gridSize*Math.floor((node.n.x-node.n.w/2)/gridSize)+node.n.w/2);
20607
+ if (Math.abs(offsetLeft) < Math.abs(offsetRight)) {
20608
+ gridOffset[0] = offsetLeft
20609
+ } else {
20610
+ gridOffset[0] = offsetRight
20611
+ }
20612
+ gridOffset[1] = node.n.y-(gridSize*Math.round(node.n.y/gridSize));
20613
+ // console.log(offsetLeft, offsetRight);
20271
20614
  }
20272
20615
  if (gridOffset[0] !== 0 || gridOffset[1] !== 0) {
20273
20616
  for (i = 0; i<movingSet.length(); i++) {
@@ -20487,6 +20830,11 @@ RED.view = (function() {
20487
20830
  } else if (mouse_mode == RED.state.DEFAULT && mousedown_link == null && !d3.event.ctrlKey && !d3.event.metaKey ) {
20488
20831
  clearSelection();
20489
20832
  updateSelection();
20833
+ } else if (slicePath) {
20834
+ deleteSelection();
20835
+ slicePath.remove();
20836
+ slicePath = null;
20837
+ RED.view.redraw(true);
20490
20838
  }
20491
20839
  if (mouse_mode == RED.state.MOVING_ACTIVE) {
20492
20840
  if (movingSet.length() > 0) {
@@ -20558,10 +20906,30 @@ RED.view = (function() {
20558
20906
  // movingSet.add(mousedown_node);
20559
20907
  // }
20560
20908
  // }
20561
- if (mouse_mode == RED.state.MOVING || mouse_mode == RED.state.MOVING_ACTIVE) {
20909
+ if (mouse_mode == RED.state.MOVING || mouse_mode == RED.state.MOVING_ACTIVE || mouse_mode == RED.state.DETACHED_DRAGGING) {
20562
20910
  // if (mousedown_node) {
20563
20911
  // delete mousedown_node.gSelected;
20564
20912
  // }
20913
+ if (mouse_mode === RED.state.DETACHED_DRAGGING) {
20914
+ var ns = [];
20915
+ for (var j=0;j<movingSet.length();j++) {
20916
+ var n = movingSet.get(j);
20917
+ if (n.ox !== n.n.x || n.oy !== n.n.y) {
20918
+ ns.push({n:n.n,ox:n.ox,oy:n.oy,moved:n.n.moved});
20919
+ n.n.dirty = true;
20920
+ n.n.moved = true;
20921
+ }
20922
+ }
20923
+ var detachEvent = RED.history.peek();
20924
+ // The last event in the stack *should* be a multi-event from
20925
+ // where the links were added/removed
20926
+ var historyEvent = {t:"move",nodes:ns,dirty:RED.nodes.dirty()}
20927
+ if (detachEvent.t === "multi") {
20928
+ detachEvent.events.push(historyEvent)
20929
+ } else {
20930
+ RED.history.push(historyEvent)
20931
+ }
20932
+ }
20565
20933
  for (i=0;i<movingSet.length();i++) {
20566
20934
  var node = movingSet.get(i);
20567
20935
  delete node.ox;
@@ -20606,10 +20974,29 @@ RED.view = (function() {
20606
20974
  if (mouse_mode === RED.state.MOVING || mouse_mode === RED.state.MOVING_ACTIVE) {
20607
20975
  return;
20608
20976
  }
20609
- if (mouse_mode === RED.state.IMPORT_DRAGGING) {
20977
+ if (mouse_mode === RED.state.DETACHED_DRAGGING) {
20978
+ for (var j=0;j<movingSet.length();j++) {
20979
+ var n = movingSet.get(j);
20980
+ n.n.x = n.ox;
20981
+ n.n.y = n.oy;
20982
+ }
20983
+ clearSelection();
20984
+ RED.history.pop();
20985
+ mouse_mode = 0;
20986
+ } else if (mouse_mode === RED.state.IMPORT_DRAGGING) {
20610
20987
  clearSelection();
20611
20988
  RED.history.pop();
20612
20989
  mouse_mode = 0;
20990
+ } else if (mouse_mode === RED.state.SLICING) {
20991
+ if (slicePath) {
20992
+ slicePath.remove();
20993
+ slicePath = null;
20994
+ resetMouseVars()
20995
+ }
20996
+ clearSelection();
20997
+ } else if (lasso) {
20998
+ lasso.remove();
20999
+ lasso = null;
20613
21000
  } else if (activeGroup) {
20614
21001
  exitActiveGroup()
20615
21002
  } else {
@@ -20621,6 +21008,7 @@ RED.view = (function() {
20621
21008
  if (mouse_mode === RED.state.SELECTING_NODE && selectNodesOptions.single) {
20622
21009
  return;
20623
21010
  }
21011
+ selectedLinks.clear();
20624
21012
 
20625
21013
  if (activeGroup) {
20626
21014
  var ag = activeGroup;
@@ -20692,7 +21080,6 @@ RED.view = (function() {
20692
21080
  }
20693
21081
  }
20694
21082
  }
20695
- selected_link = null;
20696
21083
  if (mouse_mode !== RED.state.SELECTING_NODE) {
20697
21084
  updateSelection();
20698
21085
  }
@@ -20707,7 +21094,7 @@ RED.view = (function() {
20707
21094
  n.n.selected = false;
20708
21095
  }
20709
21096
  movingSet.clear();
20710
- selected_link = null;
21097
+ selectedLinks.clear();
20711
21098
  if (activeGroup) {
20712
21099
  activeGroup.active = false
20713
21100
  activeGroup.dirty = true;
@@ -20801,12 +21188,16 @@ RED.view = (function() {
20801
21188
  }
20802
21189
  }
20803
21190
  }
20804
- if (activeFlowLinks.length === 0 && selected_link !== null && selected_link.link) {
20805
- activeLinks.push(selected_link);
20806
- activeLinkNodes[selected_link.source.id] = selected_link.source;
20807
- selected_link.source.dirty = true;
20808
- activeLinkNodes[selected_link.target.id] = selected_link.target;
20809
- selected_link.target.dirty = true;
21191
+ if (activeFlowLinks.length === 0 && selectedLinks.length() > 0) {
21192
+ selectedLinks.forEach(function(link) {
21193
+ if (link.link) {
21194
+ activeLinks.push(link);
21195
+ activeLinkNodes[link.source.id] = link.source;
21196
+ link.source.dirty = true;
21197
+ activeLinkNodes[link.target.id] = link.target;
21198
+ link.target.dirty = true;
21199
+ }
21200
+ })
20810
21201
  }
20811
21202
  } else {
20812
21203
  selection.flows = workspaceSelection;
@@ -20817,6 +21208,10 @@ RED.view = (function() {
20817
21208
  return value.map(function(n) { return n.id })
20818
21209
  } else if (key === 'link') {
20819
21210
  return value.source.id+":"+value.sourcePort+":"+value.target.id;
21211
+ } else if (key === 'links') {
21212
+ return value.map(function(link) {
21213
+ return link.source.id+":"+link.sourcePort+":"+link.target.id;
21214
+ });
20820
21215
  }
20821
21216
  return value;
20822
21217
  });
@@ -20838,7 +21233,7 @@ RED.view = (function() {
20838
21233
  }
20839
21234
  }
20840
21235
  }
20841
- function deleteSelection() {
21236
+ function deleteSelection(reconnectWires) {
20842
21237
  if (mouse_mode === RED.state.SELECTING_NODE) {
20843
21238
  return;
20844
21239
  }
@@ -20886,7 +21281,7 @@ RED.view = (function() {
20886
21281
  updateActiveNodes();
20887
21282
  updateSelection();
20888
21283
  redraw();
20889
- } else if (movingSet.length() > 0 || selected_link != null) {
21284
+ } else if (movingSet.length() > 0 || selectedLinks.length() > 0) {
20890
21285
  var result;
20891
21286
  var node;
20892
21287
  var removedNodes = [];
@@ -20896,6 +21291,16 @@ RED.view = (function() {
20896
21291
  var removedSubflowInputs = [];
20897
21292
  var removedSubflowStatus;
20898
21293
  var subflowInstances = [];
21294
+ var historyEvents = [];
21295
+
21296
+ if (reconnectWires) {
21297
+ var reconnectResult = RED.nodes.detachNodes(movingSet.nodes())
21298
+ var addedLinks = reconnectResult.newLinks;
21299
+ if (addedLinks.length > 0) {
21300
+ historyEvents.push({ t:'add', links: addedLinks })
21301
+ }
21302
+ removedLinks = removedLinks.concat(reconnectResult.removedLinks)
21303
+ }
20899
21304
 
20900
21305
  var startDirty = RED.nodes.dirty();
20901
21306
  var startChanged = false;
@@ -20985,71 +21390,71 @@ RED.view = (function() {
20985
21390
  RED.nodes.dirty(true);
20986
21391
  }
20987
21392
  }
20988
- var historyEvent;
20989
-
20990
- if (selected_link && selected_link.link) {
20991
- var sourceId = selected_link.source.id;
20992
- var targetId = selected_link.target.id;
20993
- var sourceIdIndex = selected_link.target.links.indexOf(sourceId);
20994
- var targetIdIndex = selected_link.source.links.indexOf(targetId);
20995
21393
 
20996
- historyEvent = {
20997
- t:"multi",
20998
- events: [
20999
- {
21394
+ if (selectedLinks.length() > 0) {
21395
+ selectedLinks.forEach(function(link) {
21396
+ if (link.link) {
21397
+ var sourceId = link.source.id;
21398
+ var targetId = link.target.id;
21399
+ var sourceIdIndex = link.target.links.indexOf(sourceId);
21400
+ var targetIdIndex = link.source.links.indexOf(targetId);
21401
+ historyEvents.push({
21000
21402
  t: "edit",
21001
- node: selected_link.source,
21002
- changed: selected_link.source.changed,
21403
+ node: link.source,
21404
+ changed: link.source.changed,
21003
21405
  changes: {
21004
- links: $.extend(true,{},{v:selected_link.source.links}).v
21406
+ links: $.extend(true,{},{v:link.source.links}).v
21005
21407
  }
21006
- },
21007
- {
21408
+ })
21409
+ historyEvents.push({
21008
21410
  t: "edit",
21009
- node: selected_link.target,
21010
- changed: selected_link.target.changed,
21411
+ node: link.target,
21412
+ changed: link.target.changed,
21011
21413
  changes: {
21012
- links: $.extend(true,{},{v:selected_link.target.links}).v
21414
+ links: $.extend(true,{},{v:link.target.links}).v
21013
21415
  }
21014
- }
21015
-
21016
- ],
21017
- dirty:RED.nodes.dirty()
21018
- }
21019
- RED.nodes.dirty(true);
21020
- selected_link.source.changed = true;
21021
- selected_link.target.changed = true;
21022
- selected_link.target.links.splice(sourceIdIndex,1);
21023
- selected_link.source.links.splice(targetIdIndex,1);
21024
- selected_link.source.dirty = true;
21025
- selected_link.target.dirty = true;
21416
+ })
21417
+ link.source.changed = true;
21418
+ link.target.changed = true;
21419
+ link.target.links.splice(sourceIdIndex,1);
21420
+ link.source.links.splice(targetIdIndex,1);
21421
+ link.source.dirty = true;
21422
+ link.target.dirty = true;
21026
21423
 
21424
+ } else {
21425
+ RED.nodes.removeLink(link);
21426
+ removedLinks.push(link);
21427
+ }
21428
+ })
21429
+ }
21430
+ RED.nodes.dirty(true);
21431
+ var historyEvent = {
21432
+ t:"delete",
21433
+ nodes:removedNodes,
21434
+ links:removedLinks,
21435
+ groups: removedGroups,
21436
+ subflowOutputs:removedSubflowOutputs,
21437
+ subflowInputs:removedSubflowInputs,
21438
+ subflow: {
21439
+ id: activeSubflow?activeSubflow.id:undefined,
21440
+ instances: subflowInstances
21441
+ },
21442
+ dirty:startDirty
21443
+ };
21444
+ if (removedSubflowStatus) {
21445
+ historyEvent.subflow.status = removedSubflowStatus;
21446
+ }
21447
+ if (historyEvents.length > 0) {
21448
+ historyEvents.unshift(historyEvent);
21449
+ RED.history.push({
21450
+ t:"multi",
21451
+ events: historyEvents
21452
+ })
21027
21453
  } else {
21028
- if (selected_link) {
21029
- RED.nodes.removeLink(selected_link);
21030
- removedLinks.push(selected_link);
21031
- }
21032
- RED.nodes.dirty(true);
21033
- historyEvent = {
21034
- t:"delete",
21035
- nodes:removedNodes,
21036
- links:removedLinks,
21037
- groups: removedGroups,
21038
- subflowOutputs:removedSubflowOutputs,
21039
- subflowInputs:removedSubflowInputs,
21040
- subflow: {
21041
- id: activeSubflow?activeSubflow.id:undefined,
21042
- instances: subflowInstances
21043
- },
21044
- dirty:startDirty
21045
- };
21046
- if (removedSubflowStatus) {
21047
- historyEvent.subflow.status = removedSubflowStatus;
21048
- }
21454
+ RED.history.push(historyEvent);
21049
21455
  }
21050
- RED.history.push(historyEvent);
21051
21456
 
21052
- selected_link = null;
21457
+ selectedLinks.clear();
21053
21458
  updateActiveNodes();
21054
21459
  updateSelection();
21055
21460
  redraw();
@@ -21126,6 +21531,28 @@ RED.view = (function() {
21126
21531
  }
21127
21532
  }
21128
21533
 
21534
+
21535
+ function detachSelectedNodes() {
21536
+ var selection = RED.view.selection();
21537
+ if (selection.nodes) {
21538
+ const {newLinks, removedLinks} = RED.nodes.detachNodes(selection.nodes);
21539
+ if (removedLinks.length || newLinks.length) {
21540
+ RED.history.push({
21541
+ t: "multi",
21542
+ events: [
21543
+ { t:'delete', links: removedLinks },
21544
+ { t:'add', links: newLinks }
21545
+ ],
21546
+ dirty: RED.nodes.dirty()
21547
+ })
21548
+ RED.nodes.dirty(true)
21549
+ }
21550
+ prepareDrag([selection.nodes[0].x,selection.nodes[0].y]);
21551
+ mouse_mode = RED.state.DETACHED_DRAGGING;
21552
+ RED.view.redraw(true);
21553
+ }
21554
+ }
21555
+
21129
21556
  function calculateTextWidth(str, className) {
21130
21557
  var result = convertLineBreakCharacter(str);
21131
21558
  var width = 0;
@@ -21212,7 +21639,7 @@ RED.view = (function() {
21212
21639
  activeHoverGroup.hovered = false;
21213
21640
  activeHoverGroup = null;
21214
21641
  }
21215
- d3.select(".red-ui-flow-link-splice").classed("red-ui-flow-link-splice",false);
21642
+ d3.selectAll(".red-ui-flow-link-splice").classed("red-ui-flow-link-splice",false);
21216
21643
  if (spliceTimer) {
21217
21644
  clearTimeout(spliceTimer);
21218
21645
  spliceTimer = null;
@@ -21460,10 +21887,13 @@ RED.view = (function() {
21460
21887
  } else {
21461
21888
  resetMouseVars();
21462
21889
  }
21463
- selected_link = select_link;
21464
21890
  mousedown_link = select_link;
21465
21891
  if (select_link) {
21892
+ selectedLinks.clear();
21893
+ selectedLinks.add(select_link);
21466
21894
  updateSelection();
21895
+ } else {
21896
+ selectedLinks.clear();
21467
21897
  }
21468
21898
  }
21469
21899
  redraw();
@@ -21472,7 +21902,10 @@ RED.view = (function() {
21472
21902
 
21473
21903
  resetMouseVars();
21474
21904
  hideDragLines();
21475
- selected_link = select_link;
21905
+ if (select_link) {
21906
+ selectedLinks.clear();
21907
+ selectedLinks.add(select_link);
21908
+ }
21476
21909
  mousedown_link = select_link;
21477
21910
  if (select_link) {
21478
21911
  updateSelection();
@@ -21645,10 +22078,13 @@ RED.view = (function() {
21645
22078
  msn.dx = msn.n.x-mouse[0];
21646
22079
  msn.dy = msn.n.y-mouse[1];
21647
22080
  }
21648
-
21649
- mouse_offset = d3.mouse(document.body);
21650
- if (isNaN(mouse_offset[0])) {
21651
- mouse_offset = d3.touches(document.body)[0];
22081
+ try {
22082
+ mouse_offset = d3.mouse(document.body);
22083
+ if (isNaN(mouse_offset[0])) {
22084
+ mouse_offset = d3.touches(document.body)[0];
22085
+ }
22086
+ } catch(err) {
22087
+ mouse_offset = [0,0]
21652
22088
  }
21653
22089
  }
21654
22090
 
@@ -21729,7 +22165,7 @@ RED.view = (function() {
21729
22165
  //var touch0 = d3.event;
21730
22166
  //var pos = [touch0.pageX,touch0.pageY];
21731
22167
  //RED.touch.radialMenu.show(d3.select(this),pos);
21732
- if (mouse_mode == RED.state.IMPORT_DRAGGING) {
22168
+ if (mouse_mode == RED.state.IMPORT_DRAGGING || mouse_mode == RED.state.DETACHED_DRAGGING) {
21733
22169
  var historyEvent = RED.history.peek();
21734
22170
  if (activeSpliceLink) {
21735
22171
  // TODO: DRY - droppable/nodeMouseDown/canvasMouseUp
@@ -21767,6 +22203,18 @@ RED.view = (function() {
21767
22203
  activeHoverGroup = null;
21768
22204
  }
21769
22205
 
22206
+ if (mouse_mode == RED.state.DETACHED_DRAGGING) {
22207
+ var ns = [];
22208
+ for (var j=0;j<movingSet.length();j++) {
22209
+ var n = movingSet.get(j);
22210
+ if (n.ox !== n.n.x || n.oy !== n.n.y) {
22211
+ ns.push({n:n.n,ox:n.ox,oy:n.oy,moved:n.n.moved});
22212
+ n.n.dirty = true;
22213
+ n.n.moved = true;
22214
+ }
22215
+ }
22216
+ RED.history.replace({t:"multi",events:[historyEvent,{t:"move",nodes:ns}],dirty: historyEvent.dirty})
22217
+ }
21770
22218
 
21771
22219
  updateSelection();
21772
22220
  RED.nodes.dirty(true);
@@ -21935,7 +22383,9 @@ RED.view = (function() {
21935
22383
  // }
21936
22384
  // } else
21937
22385
  if (d3.event.shiftKey) {
21938
- clearSelection();
22386
+ if (!(d3.event.ctrlKey||d3.event.metaKey)) {
22387
+ clearSelection();
22388
+ }
21939
22389
  var clickPosition = (d3.event.offsetX/scaleFactor - mousedown_node.x)
21940
22390
  var edgeDelta = (mousedown_node.w/2) - Math.abs(clickPosition);
21941
22391
  var cnodes;
@@ -21963,7 +22413,7 @@ RED.view = (function() {
21963
22413
  mousedown_node.selected = true;
21964
22414
  movingSet.add(mousedown_node);
21965
22415
  }
21966
- selected_link = null;
22416
+ // selectedLinks.clear();
21967
22417
  if (d3.event.button != 2) {
21968
22418
  mouse_mode = RED.state.MOVING;
21969
22419
  var mouse = d3.touches(this)[0]||d3.mouse(this);
@@ -22089,19 +22539,35 @@ RED.view = (function() {
22089
22539
  d3.event.stopPropagation();
22090
22540
  return;
22091
22541
  }
22092
- mousedown_link = d;
22093
- clearSelection();
22094
- selected_link = mousedown_link;
22095
- updateSelection();
22096
- redraw();
22097
- focusView();
22098
- d3.event.stopPropagation();
22099
- if (d3.event.metaKey || d3.event.ctrlKey) {
22100
- d3.select(this).classed("red-ui-flow-link-splice",true);
22101
- var point = d3.mouse(this);
22102
- var clickedGroup = getGroupAt(point[0],point[1]);
22103
- showQuickAddDialog({position:point, splice:selected_link, group:clickedGroup});
22104
- }
22542
+ if (d3.event.button === 2) {
22543
+ return
22544
+ }
22545
+ mousedown_link = d;
22546
+
22547
+ if (!(d3.event.metaKey || d3.event.ctrlKey)) {
22548
+ clearSelection();
22549
+ }
22550
+ if (d3.event.metaKey || d3.event.ctrlKey) {
22551
+ if (!selectedLinks.has(mousedown_link)) {
22552
+ selectedLinks.add(mousedown_link);
22553
+ } else {
22554
+ if (selectedLinks.length() !== 1) {
22555
+ selectedLinks.remove(mousedown_link);
22556
+ }
22557
+ }
22558
+ } else {
22559
+ selectedLinks.add(mousedown_link);
22560
+ }
22561
+ updateSelection();
22562
+ redraw();
22563
+ focusView();
22564
+ d3.event.stopPropagation();
22565
+ 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)) {
22566
+ d3.select(this).classed("red-ui-flow-link-splice",true);
22567
+ var point = d3.mouse(this);
22568
+ var clickedGroup = getGroupAt(point[0],point[1]);
22569
+ showQuickAddDialog({position:point, splice:mousedown_link, group:clickedGroup});
22570
+ }
22105
22571
  }
22106
22572
  function linkTouchStart(d) {
22107
22573
  if (mouse_mode === RED.state.SELECTING_NODE) {
@@ -22110,7 +22576,8 @@ RED.view = (function() {
22110
22576
  }
22111
22577
  mousedown_link = d;
22112
22578
  clearSelection();
22113
- selected_link = mousedown_link;
22579
+ selectedLinks.clear();
22580
+ selectedLinks.add(mousedown_link);
22114
22581
  updateSelection();
22115
22582
  redraw();
22116
22583
  focusView();
@@ -22346,7 +22813,7 @@ RED.view = (function() {
22346
22813
  function showTouchMenu(obj,pos) {
22347
22814
  var mdn = mousedown_node;
22348
22815
  var options = [];
22349
- options.push({name:"delete",disabled:(movingSet.length()===0 && selected_link === null),onselect:function() {deleteSelection();}});
22816
+ options.push({name:"delete",disabled:(movingSet.length()===0 && selectedLinks.length() === 0),onselect:function() {deleteSelection();}});
22350
22817
  options.push({name:"cut",disabled:(movingSet.length()===0),onselect:function() {copySelection();deleteSelection();}});
22351
22818
  options.push({name:"copy",disabled:(movingSet.length()===0),onselect:function() {copySelection();}});
22352
22819
  options.push({name:"paste",disabled:(clipboard.length===0),onselect:function() {importNodes(clipboard, {generateIds: true, touchImport: true});}});
@@ -22426,7 +22893,11 @@ RED.view = (function() {
22426
22893
  nodeEl = document.getElementById(d.id);
22427
22894
  }
22428
22895
  if (nodeEl) {
22429
- if (!showStatus || !d.status) {
22896
+ // Do not show node status if:
22897
+ // - global flag set
22898
+ // - node has no status
22899
+ // - node is disabled
22900
+ if (!showStatus || !d.status || d.d === true) {
22430
22901
  nodeEl.__statusGroup__.style.display = "none";
22431
22902
  } else {
22432
22903
  nodeEl.__statusGroup__.style.display = "inline";
@@ -23079,6 +23550,13 @@ RED.view = (function() {
23079
23550
  d3.select(pathBack)
23080
23551
  .on("mousedown",linkMouseDown)
23081
23552
  .on("touchstart",linkTouchStart)
23553
+ .on("mousemove", function(d) {
23554
+ if (mouse_mode === RED.state.SLICING) {
23555
+ selectedLinks.add(d)
23556
+ l.classed("red-ui-flow-link-splice",true)
23557
+ redraw()
23558
+ }
23559
+ })
23082
23560
 
23083
23561
  var pathOutline = document.createElementNS("http://www.w3.org/2000/svg","path");
23084
23562
  pathOutline.__data__ = d;
@@ -23099,7 +23577,7 @@ RED.view = (function() {
23099
23577
  link.exit().remove();
23100
23578
  link.each(function(d) {
23101
23579
  var link = d3.select(this);
23102
- if (d.added || d===selected_link || d.selected || dirtyNodes[d.source.id] || dirtyNodes[d.target.id]) {
23580
+ if (d.added || d.selected || dirtyNodes[d.source.id] || dirtyNodes[d.target.id]) {
23103
23581
  var numOutputs = d.source.outputs || 1;
23104
23582
  var sourcePort = d.sourcePort || 0;
23105
23583
  var y = -((numOutputs-1)/2)*13 +13*sourcePort;
@@ -23122,7 +23600,8 @@ RED.view = (function() {
23122
23600
  this.__pathLine__.classList.toggle("red-ui-flow-node-disabled",!!(d.source.d || d.target.d));
23123
23601
  this.__pathLine__.classList.toggle("red-ui-flow-subflow-link", !d.link && activeSubflow);
23124
23602
  }
23125
- this.classList.toggle("red-ui-flow-link-selected", !!(d===selected_link||d.selected));
23603
+
23604
+ this.classList.toggle("red-ui-flow-link-selected", !!d.selected);
23126
23605
 
23127
23606
  var connectedToUnknown = !!(d.target.type == "unknown" || d.source.type == "unknown");
23128
23607
  this.classList.toggle("red-ui-flow-link-unknown",!!(d.target.type == "unknown" || d.source.type == "unknown"))
@@ -23172,6 +23651,9 @@ RED.view = (function() {
23172
23651
  n.selected = true;
23173
23652
  n.dirty = true;
23174
23653
  movingSet.add(n);
23654
+ if (targets.length === 1) {
23655
+ RED.view.reveal(n.id);
23656
+ }
23175
23657
  });
23176
23658
  updateSelection();
23177
23659
  redraw();
@@ -23708,7 +24190,7 @@ RED.view = (function() {
23708
24190
  counts.push(RED._("clipboard.group",{count:newGroupCount}));
23709
24191
  }
23710
24192
  if (newConfigNodeCount > 0) {
23711
- counts.push(RED._("clipboard.configNode",{count:newNodeCount}));
24193
+ counts.push(RED._("clipboard.configNode",{count:newConfigNodeCount}));
23712
24194
  }
23713
24195
  if (new_subflows.length > 0) {
23714
24196
  counts.push(RED._("clipboard.subflow",{count:new_subflows.length}));
@@ -23780,6 +24262,7 @@ RED.view = (function() {
23780
24262
  delete node.d;
23781
24263
  }
23782
24264
  node.dirty = true;
24265
+ node.dirtyStatus = true;
23783
24266
  node.changed = true;
23784
24267
  RED.events.emit("nodes:change",node);
23785
24268
  }
@@ -23826,8 +24309,9 @@ RED.view = (function() {
23826
24309
  if (allNodes.size > 0) {
23827
24310
  selection.nodes = Array.from(allNodes);
23828
24311
  }
23829
- if (selected_link != null) {
23830
- selection.link = selected_link;
24312
+ if (selectedLinks.length() > 0) {
24313
+ selection.links = selectedLinks.toArray();
24314
+ selection.link = selection.links[0];
23831
24315
  }
23832
24316
  return selection;
23833
24317
  }
@@ -25128,6 +25612,90 @@ RED.view.tools = (function() {
25128
25612
  }
25129
25613
  }
25130
25614
 
25615
+
25616
+ function wireSeriesOfNodes() {
25617
+ var selection = RED.view.selection();
25618
+ if (selection.nodes) {
25619
+ if (selection.nodes.length > 1) {
25620
+ var i = 0;
25621
+ var newLinks = [];
25622
+ while (i < selection.nodes.length - 1) {
25623
+ var nodeA = selection.nodes[i];
25624
+ var nodeB = selection.nodes[i+1];
25625
+ if (nodeA.outputs > 0 && nodeB.inputs > 0) {
25626
+ var existingLinks = RED.nodes.filterLinks({
25627
+ source: nodeA,
25628
+ target: nodeB,
25629
+ sourcePort: 0
25630
+ })
25631
+ if (existingLinks.length === 0) {
25632
+ var newLink = {
25633
+ source: nodeA,
25634
+ target: nodeB,
25635
+ sourcePort: 0
25636
+ }
25637
+ RED.nodes.addLink(newLink);
25638
+ newLinks.push(newLink);
25639
+ }
25640
+ }
25641
+ i++;
25642
+ }
25643
+ if (newLinks.length > 0) {
25644
+ RED.history.push({
25645
+ t: 'add',
25646
+ links: newLinks,
25647
+ dirty: RED.nodes.dirty()
25648
+ })
25649
+ RED.nodes.dirty(true);
25650
+ RED.view.redraw(true);
25651
+ }
25652
+ }
25653
+ }
25654
+ }
25655
+
25656
+ function wireNodeToMultiple() {
25657
+ var selection = RED.view.selection();
25658
+ if (selection.nodes) {
25659
+ if (selection.nodes.length > 1) {
25660
+ var sourceNode = selection.nodes[0];
25661
+ if (sourceNode.outputs === 0) {
25662
+ return;
25663
+ }
25664
+ var i = 1;
25665
+ var newLinks = [];
25666
+ while (i < selection.nodes.length) {
25667
+ var targetNode = selection.nodes[i];
25668
+ if (targetNode.inputs > 0) {
25669
+ var existingLinks = RED.nodes.filterLinks({
25670
+ source: sourceNode,
25671
+ target: targetNode,
25672
+ sourcePort: Math.min(sourceNode.outputs-1,i-1)
25673
+ })
25674
+ if (existingLinks.length === 0) {
25675
+ var newLink = {
25676
+ source: sourceNode,
25677
+ target: targetNode,
25678
+ sourcePort: Math.min(sourceNode.outputs-1,i-1)
25679
+ }
25680
+ RED.nodes.addLink(newLink);
25681
+ newLinks.push(newLink);
25682
+ }
25683
+ }
25684
+ i++;
25685
+ }
25686
+ if (newLinks.length > 0) {
25687
+ RED.history.push({
25688
+ t: 'add',
25689
+ links: newLinks,
25690
+ dirty: RED.nodes.dirty()
25691
+ })
25692
+ RED.nodes.dirty(true);
25693
+ RED.view.redraw(true);
25694
+ }
25695
+ }
25696
+ }
25697
+ }
25698
+
25131
25699
  return {
25132
25700
  init: function() {
25133
25701
  RED.actions.add("core:show-selected-node-labels", function() { setSelectedNodeLabelState(true); })
@@ -25186,7 +25754,8 @@ RED.view.tools = (function() {
25186
25754
  RED.actions.add("core:distribute-selection-horizontally", function() { distributeSelection('h') })
25187
25755
  RED.actions.add("core:distribute-selection-vertically", function() { distributeSelection('v') })
25188
25756
 
25189
-
25757
+ RED.actions.add("core:wire-series-of-nodes", function() { wireSeriesOfNodes() })
25758
+ RED.actions.add("core:wire-node-to-multiple", function() { wireNodeToMultiple() })
25190
25759
 
25191
25760
  // RED.actions.add("core:add-node", function() { addNode() })
25192
25761
  },
@@ -26942,6 +27511,7 @@ RED.sidebar.info = (function() {
26942
27511
  n.d = true;
26943
27512
  }
26944
27513
  n.dirty = true;
27514
+ n.dirtyStatus = true;
26945
27515
  n.changed = true;
26946
27516
  RED.events.emit("nodes:change",n);
26947
27517
  groupHistoryEvent.events.push(historyEvent);
@@ -26970,6 +27540,7 @@ RED.sidebar.info = (function() {
26970
27540
  n.d = true;
26971
27541
  }
26972
27542
  n.dirty = true;
27543
+ n.dirtyStatus = true;
26973
27544
  n.changed = true;
26974
27545
  RED.events.emit("nodes:change",n);
26975
27546
  RED.history.push(historyEvent);
@@ -27039,6 +27610,7 @@ RED.sidebar.info = (function() {
27039
27610
  {label:RED._("sidebar.info.search.invalidNodes"), value: "is:invalid"},
27040
27611
  {label:RED._("sidebar.info.search.uknownNodes"), value: "type:unknown"},
27041
27612
  {label:RED._("sidebar.info.search.unusedSubflows"), value:"is:subflow is:unused"},
27613
+ {label:RED._("sidebar.info.search.hiddenFlows"), value:"is:hidden"},
27042
27614
  ]
27043
27615
  });
27044
27616
 
@@ -27330,7 +27902,7 @@ RED.sidebar.info = (function() {
27330
27902
  }
27331
27903
  }
27332
27904
  function getGutter(n) {
27333
- var span = $("<span>",{class:"red-ui-info-outline-gutter"});
27905
+ var span = $("<span>",{class:"red-ui-info-outline-gutter red-ui-treeList-gutter-float"});
27334
27906
  var revealButton = $('<button type="button" class="red-ui-info-outline-item-control-reveal red-ui-button red-ui-button-small"><i class="fa fa-search"></i></button>').appendTo(span).on("click",function(evt) {
27335
27907
  evt.preventDefault();
27336
27908
  evt.stopPropagation();
@@ -27831,7 +28403,7 @@ RED.sidebar.help = (function() {
27831
28403
  var currentVersionParts = RED.settings.version.split(".");
27832
28404
  var tourVersionParts = tour.version.split(".");
27833
28405
  if (tourVersionParts[0] === currentVersionParts[0] && tourVersionParts[1] === currentVersionParts[1]) {
27834
- tourHeader = '<div><button type="button" onclick="RED.actions.invoke(\'core:show-welcome-tour\')" class="red-ui-button">Take a tour</button></div>'
28406
+ tourHeader = '<div><button type="button" onclick="RED.actions.invoke(\'core:show-welcome-tour\')" class="red-ui-button">' + RED._("tourGuide.takeATour") + '</button></div>';
27835
28407
  }
27836
28408
  }
27837
28409
  var aboutHeader = '<div style="text-align:center;">'+tourHeader+'</div>'
@@ -30612,7 +31184,16 @@ RED.editor = (function() {
30612
31184
  delete cn.__label__;
30613
31185
  });
30614
31186
 
30615
- select.append('<option value="_ADD_"'+(value===""?" selected":"")+'>'+RED._("editor.addNewType", {type:type})+'</option>');
31187
+ var label = type;
31188
+ if (typeof node_def.paletteLabel !== "undefined") {
31189
+ try {
31190
+ label = RED.utils.sanitize((typeof node_def.paletteLabel === "function" ? node_def.paletteLabel.call(node_def) : node_def.paletteLabel)||type);
31191
+ } catch(err) {
31192
+ console.log("Definition error: "+type+".paletteLabel",err);
31193
+ }
31194
+ }
31195
+
31196
+ select.append('<option value="_ADD_"'+(value===""?" selected":"")+'>'+RED._("editor.addNewType", {type:label})+'</option>');
30616
31197
  window.setTimeout(function() { select.trigger("change");},50);
30617
31198
  }
30618
31199
  }
@@ -32450,9 +33031,15 @@ RED.editor = (function() {
32450
33031
  }
32451
33032
  });
32452
33033
  }
32453
- if (!isSameObj(old_env, new_env)) {
32454
- node.env = new_env;
33034
+ if (!old_env && new_env.length === 0) {
33035
+ delete node.env;
33036
+ } else if (!isSameObj(old_env, new_env)) {
32455
33037
  editState.changes.env = node.env;
33038
+ if (new_env.length === 0) {
33039
+ delete node.env;
33040
+ } else {
33041
+ node.env = new_env;
33042
+ }
32456
33043
  editState.changed = true;
32457
33044
  }
32458
33045
  }
@@ -34271,7 +34858,7 @@ RED.editor = (function() {
34271
34858
  var currentExpression = expressionEditor.getValue();
34272
34859
  var expr;
34273
34860
  var usesContext = false;
34274
- var legacyMode = /(^|[^a-zA-Z0-9_'"])msg([^a-zA-Z0-9_'"]|$)/.test(currentExpression);
34861
+ var legacyMode = /(^|[^a-zA-Z0-9_'".])msg([^a-zA-Z0-9_'"]|$)/.test(currentExpression);
34275
34862
  $(".red-ui-editor-type-expression-legacy").toggle(legacyMode);
34276
34863
  try {
34277
34864
  expr = jsonata(currentExpression);
@@ -34560,7 +35147,8 @@ RED.editor = (function() {
34560
35147
  clearTimeout: true,
34561
35148
  setInterval: true,
34562
35149
  clearInterval: true
34563
- }
35150
+ },
35151
+ extraLibs: options.extraLibs
34564
35152
  });
34565
35153
  if (options.cursor) {
34566
35154
  expressionEditor.gotoLine(options.cursor.row+1,options.cursor.column,false);
@@ -37579,6 +38167,7 @@ RED.clipboard = (function() {
37579
38167
  text: RED._("common.label.cancel"),
37580
38168
  click: function() {
37581
38169
  $( this ).dialog( "close" );
38170
+ RED.view.focus();
37582
38171
  }
37583
38172
  },
37584
38173
  { // red-ui-clipboard-dialog-download
@@ -37589,6 +38178,7 @@ RED.clipboard = (function() {
37589
38178
  var data = $("#red-ui-clipboard-dialog-export-text").val();
37590
38179
  downloadData("flows.json", data);
37591
38180
  $( this ).dialog( "close" );
38181
+ RED.view.focus();
37592
38182
  }
37593
38183
  },
37594
38184
  { // red-ui-clipboard-dialog-export
@@ -37603,6 +38193,7 @@ RED.clipboard = (function() {
37603
38193
  $( this ).dialog( "close" );
37604
38194
  copyText(flowData);
37605
38195
  RED.notify(RED._("clipboard.nodesExported"),{id:"clipboard"});
38196
+ RED.view.focus();
37606
38197
  } else {
37607
38198
  var flowToExport = $("#red-ui-clipboard-dialog-export-text").val();
37608
38199
  var selectedPath = activeLibraries[activeTab].getSelected();
@@ -37618,6 +38209,7 @@ RED.clipboard = (function() {
37618
38209
  contentType: "application/json; charset=utf-8"
37619
38210
  }).done(function() {
37620
38211
  $(dialog).dialog( "close" );
38212
+ RED.view.focus();
37621
38213
  RED.notify(RED._("library.exportedToLibrary"),"success");
37622
38214
  }).fail(function(xhr,textStatus,err) {
37623
38215
  if (xhr.status === 401) {
@@ -37679,6 +38271,7 @@ RED.clipboard = (function() {
37679
38271
  }
37680
38272
  }
37681
38273
  $( this ).dialog( "close" );
38274
+ RED.view.focus();
37682
38275
  }
37683
38276
  },
37684
38277
  { // red-ui-clipboard-dialog-import-conflict
@@ -37711,6 +38304,7 @@ RED.clipboard = (function() {
37711
38304
  // console.table(pendingImportConfig.importNodes.map(function(n) { return {id:n.id,type:n.type,result:importMap[n.id]}}))
37712
38305
  RED.view.importNodes(newNodes, pendingImportConfig.importOptions);
37713
38306
  $( this ).dialog( "close" );
38307
+ RED.view.focus();
37714
38308
  }
37715
38309
  }
37716
38310
  ],
@@ -38448,28 +39042,25 @@ RED.clipboard = (function() {
38448
39042
  if (truncated) {
38449
39043
  msg += "_truncated";
38450
39044
  }
38451
- $("#red-ui-clipboard-hidden").val(value).focus().select();
38452
- var result = document.execCommand("copy");
38453
- if (result && element) {
38454
- var popover = RED.popover.create({
38455
- target: element,
38456
- direction: 'left',
38457
- size: 'small',
38458
- content: RED._(msg)
38459
- });
38460
- setTimeout(function() {
38461
- popover.close();
38462
- },1000);
38463
- popover.open();
38464
- }
38465
- $("#red-ui-clipboard-hidden").val("");
38466
- if (currentFocus) {
38467
- $(currentFocus).focus();
38468
- }
38469
- return result;
39045
+ navigator.clipboard.writeText(value).then(function () {
39046
+ if (element) {
39047
+ var popover = RED.popover.create({
39048
+ target: element,
39049
+ direction: 'left',
39050
+ size: 'small',
39051
+ content: RED._(msg)
39052
+ });
39053
+ setTimeout(function() {
39054
+ popover.close();
39055
+ },1000);
39056
+ popover.open();
39057
+ }
39058
+ if (currentFocus) {
39059
+ $(currentFocus).focus();
39060
+ }
39061
+ }).catch(err => { console.error("Failed to copy:",err) });
38470
39062
  }
38471
39063
 
38472
-
38473
39064
  function importNodes(nodesStr,addFlow) {
38474
39065
  var newNodes = nodesStr;
38475
39066
  if (typeof nodesStr === 'string') {
@@ -38744,8 +39335,6 @@ RED.clipboard = (function() {
38744
39335
  init: function() {
38745
39336
  setupDialogs();
38746
39337
 
38747
- $('<textarea type="text" id="red-ui-clipboard-hidden" tabIndex="-1">').appendTo("#red-ui-editor");
38748
-
38749
39338
  RED.actions.add("core:show-export-dialog",showExportNodes);
38750
39339
  RED.actions.add("core:show-import-dialog",showImportNodes);
38751
39340
 
@@ -40036,6 +40625,7 @@ RED.search = (function() {
40036
40625
  var selected = -1;
40037
40626
  var visible = false;
40038
40627
 
40628
+ var searchHistory = [];
40039
40629
  var index = {};
40040
40630
  var currentResults = [];
40041
40631
  var previousActiveElement;
@@ -40119,6 +40709,7 @@ RED.search = (function() {
40119
40709
  val = extractFlag(val,"unused",flags);
40120
40710
  val = extractFlag(val,"config",flags);
40121
40711
  val = extractFlag(val,"subflow",flags);
40712
+ val = extractFlag(val,"hidden",flags);
40122
40713
  // uses:<node-id>
40123
40714
  val = extractValue(val,"uses",flags);
40124
40715
 
@@ -40164,7 +40755,15 @@ RED.search = (function() {
40164
40755
  continue;
40165
40756
  }
40166
40757
  }
40167
-
40758
+ if (flags.hasOwnProperty("hidden")) {
40759
+ // Only tabs can be hidden
40760
+ if (node.node.type !== 'tab') {
40761
+ continue
40762
+ }
40763
+ if (!RED.workspaces.isHidden(node.node.id)) {
40764
+ continue
40765
+ }
40766
+ }
40168
40767
  if (flags.hasOwnProperty("unused")) {
40169
40768
  var isUnused = (node.node.type === 'subflow' && node.node.instances.length === 0) ||
40170
40769
  (isConfigNode && node.node.users.length === 0)
@@ -40210,6 +40809,20 @@ RED.search = (function() {
40210
40809
  }
40211
40810
  }
40212
40811
 
40812
+ function populateSearchHistory() {
40813
+ if (searchHistory.length > 0) {
40814
+ searchResults.editableList('addItem',{
40815
+ historyHeader: true
40816
+ });
40817
+ searchHistory.forEach(function(entry) {
40818
+ searchResults.editableList('addItem',{
40819
+ history: true,
40820
+ value: entry
40821
+ });
40822
+ })
40823
+ }
40824
+
40825
+ }
40213
40826
  function createDialog() {
40214
40827
  dialog = $("<div>",{id:"red-ui-search",class:"red-ui-search"}).appendTo("#red-ui-main-container");
40215
40828
  var searchDiv = $("<div>",{class:"red-ui-search-container"}).appendTo(dialog);
@@ -40218,7 +40831,12 @@ RED.search = (function() {
40218
40831
  change: function() {
40219
40832
  searchResults.editableList('empty');
40220
40833
  selected = -1;
40221
- currentResults = search($(this).val());
40834
+ var value = $(this).val();
40835
+ if (value === "") {
40836
+ populateSearchHistory();
40837
+ return;
40838
+ }
40839
+ currentResults = search(value);
40222
40840
  if (currentResults.length > 0) {
40223
40841
  for (i=0;i<Math.min(currentResults.length,25);i++) {
40224
40842
  searchResults.editableList('addItem',currentResults[i])
@@ -40290,7 +40908,12 @@ RED.search = (function() {
40290
40908
  })
40291
40909
  }
40292
40910
  }
40293
- } else {
40911
+ } if ($(children[selected]).hasClass("red-ui-search-history")) {
40912
+ var object = $(children[selected]).find(".red-ui-editableList-item-content").data('data');
40913
+ if (object) {
40914
+ searchInput.searchBox('value',object.value)
40915
+ }
40916
+ } else if (!$(children[selected]).hasClass("red-ui-search-historyHeader")) {
40294
40917
  if (currentResults.length > 0) {
40295
40918
  reveal(currentResults[Math.max(0,selected)].node);
40296
40919
  }
@@ -40306,7 +40929,32 @@ RED.search = (function() {
40306
40929
  addItem: function(container,i,object) {
40307
40930
  var node = object.node;
40308
40931
  var div;
40309
- if (object.more) {
40932
+ if (object.historyHeader) {
40933
+ container.parent().addClass("red-ui-search-historyHeader")
40934
+ $('<div>',{class:"red-ui-search-empty"}).text(RED._("search.history")).appendTo(container);
40935
+ $('<button type="button" class="red-ui-button red-ui-button-small"></button>').text(RED._("search.clear")).appendTo(container).on("click", function(evt) {
40936
+ evt.preventDefault();
40937
+ searchHistory = [];
40938
+ searchResults.editableList('empty');
40939
+ });
40940
+ } else if (object.history) {
40941
+ container.parent().addClass("red-ui-search-history")
40942
+ div = $('<a>',{href:'#',class:"red-ui-search-result"}).appendTo(container);
40943
+ div.text(object.value);
40944
+ div.on("click", function(evt) {
40945
+ evt.preventDefault();
40946
+ searchInput.searchBox('value',object.value)
40947
+ searchInput.focus();
40948
+ })
40949
+ $('<button type="button" class="red-ui-button red-ui-button-small"><i class="fa fa-remove"></i></button>').appendTo(container).on("click", function(evt) {
40950
+ evt.preventDefault();
40951
+ var index = searchHistory.indexOf(object.value);
40952
+ searchHistory.splice(index,1);
40953
+ searchResults.editableList('removeItem', object);
40954
+ });
40955
+
40956
+
40957
+ } else if (object.more) {
40310
40958
  container.parent().addClass("red-ui-search-more")
40311
40959
  div = $('<a>',{href:'#',class:"red-ui-search-result red-ui-search-empty"}).appendTo(container);
40312
40960
  div.text(RED._("palette.editor.more",{count:object.more.results.length-object.more.start}));
@@ -40361,6 +41009,12 @@ RED.search = (function() {
40361
41009
  }
40362
41010
 
40363
41011
  function reveal(node) {
41012
+ var searchVal = searchInput.val();
41013
+ var existingIndex = searchHistory.indexOf(searchVal);
41014
+ if (existingIndex > -1) {
41015
+ searchHistory.splice(existingIndex,1);
41016
+ }
41017
+ searchHistory.unshift(searchInput.val());
40364
41018
  hide();
40365
41019
  RED.view.reveal(node.id);
40366
41020
  }
@@ -40379,9 +41033,14 @@ RED.search = (function() {
40379
41033
 
40380
41034
  if (dialog === null) {
40381
41035
  createDialog();
41036
+ } else {
41037
+ searchResults.editableList('empty');
40382
41038
  }
40383
41039
  dialog.slideDown(300);
40384
41040
  searchInput.searchBox('value',v)
41041
+ if (!v || v === "") {
41042
+ populateSearchHistory();
41043
+ }
40385
41044
  RED.events.emit("search:open");
40386
41045
  visible = true;
40387
41046
  }
@@ -43012,12 +43671,14 @@ RED.group = (function() {
43012
43671
  markDirty(group);
43013
43672
  }
43014
43673
 
43015
- function getNodes(group,recursive) {
43674
+ function getNodes(group,recursive,excludeGroup) {
43016
43675
  var nodes = [];
43017
43676
  group.nodes.forEach(function(n) {
43018
- nodes.push(n);
43677
+ if (n.type !== 'group' || !excludeGroup) {
43678
+ nodes.push(n);
43679
+ }
43019
43680
  if (recursive && n.type === 'group') {
43020
- nodes = nodes.concat(getNodes(n,recursive))
43681
+ nodes = nodes.concat(getNodes(n,recursive,excludeGroup))
43021
43682
  }
43022
43683
  })
43023
43684
  return nodes;
@@ -49820,6 +50481,10 @@ RED.touch.radialMenu = (function() {
49820
50481
  }
49821
50482
  $('<div>').css("text-align","left").html(getLocaleText(step.description)).appendTo(stepDescription);
49822
50483
 
50484
+ if (step.image) {
50485
+ $(`<img src="red/tours/${step.image}" />`).appendTo(stepDescription)
50486
+ }
50487
+
49823
50488
  var stepToolbar = $('<div>',{class:"red-ui-tourGuide-toolbar"}).appendTo(stepContent);
49824
50489
 
49825
50490
  // var breadcrumbs = $('<div>',{class:"red-ui-tourGuide-breadcrumbs"}).appendTo(stepToolbar);
@@ -49928,6 +50593,8 @@ RED.touch.radialMenu = (function() {
49928
50593
  if (step.fallback) {
49929
50594
  focus.one("mouseenter", function(evt) {
49930
50595
  setTimeout(function() {
50596
+ var pos = targetElement[0].getBoundingClientRect();
50597
+ var dimension = Math.max(50, Math.max(pos.width,pos.height)*1.5);
49931
50598
  focus.css({
49932
50599
  width: (4*dimension)+"px",
49933
50600
  height: (4*dimension)+"px"