@node-red/editor-client 2.1.5 → 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
@@ -3539,7 +3539,9 @@ RED.state = {
3539
3539
  PANNING: 10,
3540
3540
  SELECTING_NODE: 11,
3541
3541
  GROUP_DRAGGING: 12,
3542
- GROUP_RESIZE: 13
3542
+ GROUP_RESIZE: 13,
3543
+ DETACHED_DRAGGING: 14,
3544
+ SLICING: 15
3543
3545
  }
3544
3546
  ;RED.plugins = (function() {
3545
3547
  var plugins = {};
@@ -3604,6 +3606,9 @@ RED.state = {
3604
3606
  **/
3605
3607
  RED.nodes = (function() {
3606
3608
 
3609
+ var PORT_TYPE_INPUT = 1;
3610
+ var PORT_TYPE_OUTPUT = 0;
3611
+
3607
3612
  var node_defs = {};
3608
3613
  var linkTabMap = {};
3609
3614
 
@@ -6047,6 +6052,144 @@ RED.nodes = (function() {
6047
6052
  return helpContent;
6048
6053
  }
6049
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
+
6050
6193
  return {
6051
6194
  init: function() {
6052
6195
  RED.events.on("registry:node-type-added",function(type) {
@@ -6128,7 +6271,7 @@ RED.nodes = (function() {
6128
6271
  add: addNode,
6129
6272
  remove: removeNode,
6130
6273
  clear: clear,
6131
-
6274
+ detachNodes: detachNodes,
6132
6275
  moveNodesForwards: moveNodesForwards,
6133
6276
  moveNodesBackwards: moveNodesBackwards,
6134
6277
  moveNodesToFront: moveNodesToFront,
@@ -6140,7 +6283,20 @@ RED.nodes = (function() {
6140
6283
 
6141
6284
  addLink: addLink,
6142
6285
  removeLink: removeLink,
6143
-
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
+ },
6144
6300
  addWorkspace: addWorkspace,
6145
6301
  removeWorkspace: removeWorkspace,
6146
6302
  getWorkspaceOrder: function() { return workspacesOrder },
@@ -6214,6 +6370,7 @@ RED.nodes = (function() {
6214
6370
  getAllFlowNodes: getAllFlowNodes,
6215
6371
  getAllUpstreamNodes: getAllUpstreamNodes,
6216
6372
  getAllDownstreamNodes: getAllDownstreamNodes,
6373
+ getNodeIslands: getNodeIslands,
6217
6374
  createExportableNodeSet: createExportableNodeSet,
6218
6375
  createCompleteNodeSet: createCompleteNodeSet,
6219
6376
  updateConfigNodeUsers: updateConfigNodeUsers,
@@ -7734,6 +7891,13 @@ RED.history = (function() {
7734
7891
  peek: function() {
7735
7892
  return undoHistory[undoHistory.length-1];
7736
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
+ },
7737
7901
  clear: function() {
7738
7902
  undoHistory = [];
7739
7903
  redoHistory = [];
@@ -15047,6 +15211,16 @@ RED.deploy = (function() {
15047
15211
  var unknownNodes = [];
15048
15212
  var invalidNodes = [];
15049
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
+ });
15050
15224
  RED.nodes.eachNode(function(node) {
15051
15225
  if (!node.valid && !node.d) {
15052
15226
  invalidNodes.push(getNodeInfo(node));
@@ -18880,7 +19054,6 @@ RED.view = (function() {
18880
19054
  var activeGroups = [];
18881
19055
  var dirtyGroups = {};
18882
19056
 
18883
- var selected_link = null;
18884
19057
  var mousedown_link = null;
18885
19058
  var mousedown_node = null;
18886
19059
  var mousedown_group = null;
@@ -18892,6 +19065,8 @@ RED.view = (function() {
18892
19065
  var mouse_mode = 0;
18893
19066
  var mousedown_group_handle = null;
18894
19067
  var lasso = null;
19068
+ var slicePath = null;
19069
+ var slicePathLast = null;
18895
19070
  var ghostNode = null;
18896
19071
  var showStatus = false;
18897
19072
  var lastClickNode = null;
@@ -18946,6 +19121,14 @@ RED.view = (function() {
18946
19121
  if (!setIds.has(node.id)) {
18947
19122
  set.push({n:node});
18948
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
+ }
18949
19132
  }
18950
19133
  }
18951
19134
  },
@@ -18962,6 +19145,10 @@ RED.view = (function() {
18962
19145
  }
18963
19146
  }
18964
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
+ }
18965
19152
  }
18966
19153
  },
18967
19154
  clear: function() {
@@ -18976,6 +19163,31 @@ RED.view = (function() {
18976
19163
  return api;
18977
19164
  })();
18978
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
+
18979
19191
  function init() {
18980
19192
 
18981
19193
  chart = $("#red-ui-workspace-chart");
@@ -19010,6 +19222,12 @@ RED.view = (function() {
19010
19222
  }
19011
19223
  } else if (mouse_mode === RED.state.PANNING && d3.event.buttons !== 4) {
19012
19224
  resetMouseVars();
19225
+ } else if (slicePath) {
19226
+ if (d3.event.buttons !== 2) {
19227
+ slicePath.remove();
19228
+ slicePath = null;
19229
+ resetMouseVars()
19230
+ }
19013
19231
  }
19014
19232
  })
19015
19233
  .on("touchend", function() {
@@ -19229,26 +19447,56 @@ RED.view = (function() {
19229
19447
  var historyEvent = result.historyEvent;
19230
19448
  var nn = result.node;
19231
19449
 
19450
+ RED.nodes.add(nn);
19451
+
19232
19452
  var showLabel = RED.utils.getMessageProperty(RED.settings.get('editor'),"view.view-node-show-label");
19233
19453
  if (showLabel !== undefined && (nn._def.hasOwnProperty("showLabel")?nn._def.showLabel:true) && !nn._def.defaults.hasOwnProperty("l")) {
19234
19454
  nn.l = showLabel;
19235
19455
  }
19236
19456
 
19237
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();
19238
19460
  var mousePos = d3.touches(this)[0]||d3.mouse(this);
19239
19461
 
19240
- mousePos[1] += this.scrollTop + ((nn.h/2)-helperOffset[1]);
19241
- 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]);
19242
19480
  mousePos[1] /= scaleFactor;
19243
19481
  mousePos[0] /= scaleFactor;
19244
19482
 
19245
- if (snapGrid) {
19246
- mousePos[0] = gridSize*(Math.ceil(mousePos[0]/gridSize));
19247
- mousePos[1] = gridSize*(Math.ceil(mousePos[1]/gridSize));
19248
- }
19249
19483
  nn.x = mousePos[0];
19250
19484
  nn.y = mousePos[1];
19251
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
+
19252
19500
  var spliceLink = $(ui.helper).data("splice");
19253
19501
  if (spliceLink) {
19254
19502
  // TODO: DRY - droppable/nodeMouseDown/canvasMouseUp/showQuickAddDialog
@@ -19269,7 +19517,6 @@ RED.view = (function() {
19269
19517
  historyEvent.removedLinks = [spliceLink];
19270
19518
  }
19271
19519
 
19272
- RED.nodes.add(nn);
19273
19520
 
19274
19521
  var group = $(ui.helper).data("group");
19275
19522
  if (group) {
@@ -19319,6 +19566,8 @@ RED.view = (function() {
19319
19566
  RED.actions.add("core:cut-selection-to-internal-clipboard",function(){copySelection();deleteSelection();});
19320
19567
  RED.actions.add("core:paste-from-internal-clipboard",function(){importNodes(clipboard,{generateIds: true});});
19321
19568
 
19569
+ RED.actions.add("core:detach-selected-nodes", function() { detachSelectedNodes() })
19570
+
19322
19571
  RED.events.on("view:selection-changed", function(selection) {
19323
19572
  var hasSelection = (selection.nodes && selection.nodes.length > 0);
19324
19573
  var hasMultipleSelection = hasSelection && selection.nodes.length > 1;
@@ -19341,6 +19590,7 @@ RED.view = (function() {
19341
19590
  })
19342
19591
 
19343
19592
  RED.actions.add("core:delete-selection",deleteSelection);
19593
+ RED.actions.add("core:delete-selection-and-reconnect",function() { deleteSelection(true) });
19344
19594
  RED.actions.add("core:edit-selected-node",editSelection);
19345
19595
  RED.actions.add("core:go-to-selection",function() {
19346
19596
  if (movingSet.length() > 0) {
@@ -19726,7 +19976,7 @@ RED.view = (function() {
19726
19976
  return;
19727
19977
  }
19728
19978
  if (!mousedown_node && !mousedown_link && !mousedown_group) {
19729
- selected_link = null;
19979
+ selectedLinks.clear();
19730
19980
  updateSelection();
19731
19981
  }
19732
19982
  if (mouse_mode === 0) {
@@ -19735,19 +19985,18 @@ RED.view = (function() {
19735
19985
  lasso = null;
19736
19986
  }
19737
19987
  }
19738
- if (mouse_mode === 0 || mouse_mode === RED.state.QUICK_JOINING) {
19739
- if (d3.event.metaKey || d3.event.ctrlKey) {
19740
- d3.event.stopPropagation();
19741
- clearSelection();
19742
- point = d3.mouse(this);
19743
- var clickedGroup = getGroupAt(point[0],point[1]);
19744
- if (drag_lines.length > 0) {
19745
- clickedGroup = clickedGroup || RED.nodes.group(drag_lines[0].node.g)
19746
- }
19747
- showQuickAddDialog({position:point, group:clickedGroup});
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)
19748
19996
  }
19749
- }
19750
- 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
19751
20000
  if (!touchStartTime) {
19752
20001
  point = d3.mouse(this);
19753
20002
  lasso = eventLayer.append("rect")
@@ -19762,6 +20011,13 @@ RED.view = (function() {
19762
20011
  .attr("class","nr-ui-view-lasso");
19763
20012
  d3.event.preventDefault();
19764
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();
19765
20021
  }
19766
20022
  }
19767
20023
 
@@ -20158,6 +20414,17 @@ RED.view = (function() {
20158
20414
  .attr("height",h)
20159
20415
  ;
20160
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
20161
20428
  }
20162
20429
 
20163
20430
  if (mouse_mode === RED.state.SELECTING_NODE) {
@@ -20165,7 +20432,7 @@ RED.view = (function() {
20165
20432
  return;
20166
20433
  }
20167
20434
 
20168
- 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) {
20169
20436
  return;
20170
20437
  }
20171
20438
 
@@ -20189,16 +20456,18 @@ RED.view = (function() {
20189
20456
  // Get all the wires we need to detach.
20190
20457
  var links = [];
20191
20458
  var existingLinks = [];
20192
- if (selected_link &&
20193
- ((mousedown_port_type === PORT_TYPE_OUTPUT &&
20194
- selected_link.source === mousedown_node &&
20195
- selected_link.sourcePort === mousedown_port_index
20196
- ) ||
20197
- (mousedown_port_type === PORT_TYPE_INPUT &&
20198
- selected_link.target === mousedown_node
20199
- ))
20200
- ) {
20201
- existingLinks = [selected_link];
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
+ })
20202
20471
  } else {
20203
20472
  var filter;
20204
20473
  if (mousedown_port_type === PORT_TYPE_OUTPUT) {
@@ -20236,7 +20505,7 @@ RED.view = (function() {
20236
20505
  } else if (mousedown_node && !quickAddLink) {
20237
20506
  showDragLines([{node:mousedown_node,port:mousedown_port_index,portType:mousedown_port_type}]);
20238
20507
  }
20239
- selected_link = null;
20508
+ selectedLinks.clear();
20240
20509
  }
20241
20510
  mousePos = mouse_position;
20242
20511
  for (i=0;i<drag_lines.length;i++) {
@@ -20268,7 +20537,7 @@ RED.view = (function() {
20268
20537
  RED.nodes.filterLinks({ target: node.n }).length === 0;
20269
20538
  }
20270
20539
  }
20271
- } 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) {
20272
20541
  mousePos = mouse_position;
20273
20542
  var minX = 0;
20274
20543
  var minY = 0;
@@ -20332,8 +20601,16 @@ RED.view = (function() {
20332
20601
  gridOffset[0] = node.n.x-(gridSize*Math.floor(node.n.x/gridSize))-gridSize/2;
20333
20602
  gridOffset[1] = node.n.y-(gridSize*Math.floor(node.n.y/gridSize))-gridSize/2;
20334
20603
  } else {
20335
- gridOffset[0] = node.n.x-(gridSize*Math.floor((node.n.x-node.n.w/2)/gridSize)+node.n.w/2);
20336
- gridOffset[1] = node.n.y-(gridSize*Math.floor(node.n.y/gridSize));
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);
20337
20614
  }
20338
20615
  if (gridOffset[0] !== 0 || gridOffset[1] !== 0) {
20339
20616
  for (i = 0; i<movingSet.length(); i++) {
@@ -20553,6 +20830,11 @@ RED.view = (function() {
20553
20830
  } else if (mouse_mode == RED.state.DEFAULT && mousedown_link == null && !d3.event.ctrlKey && !d3.event.metaKey ) {
20554
20831
  clearSelection();
20555
20832
  updateSelection();
20833
+ } else if (slicePath) {
20834
+ deleteSelection();
20835
+ slicePath.remove();
20836
+ slicePath = null;
20837
+ RED.view.redraw(true);
20556
20838
  }
20557
20839
  if (mouse_mode == RED.state.MOVING_ACTIVE) {
20558
20840
  if (movingSet.length() > 0) {
@@ -20624,10 +20906,30 @@ RED.view = (function() {
20624
20906
  // movingSet.add(mousedown_node);
20625
20907
  // }
20626
20908
  // }
20627
- 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) {
20628
20910
  // if (mousedown_node) {
20629
20911
  // delete mousedown_node.gSelected;
20630
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
+ }
20631
20933
  for (i=0;i<movingSet.length();i++) {
20632
20934
  var node = movingSet.get(i);
20633
20935
  delete node.ox;
@@ -20672,10 +20974,29 @@ RED.view = (function() {
20672
20974
  if (mouse_mode === RED.state.MOVING || mouse_mode === RED.state.MOVING_ACTIVE) {
20673
20975
  return;
20674
20976
  }
20675
- 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
+ }
20676
20983
  clearSelection();
20677
20984
  RED.history.pop();
20678
20985
  mouse_mode = 0;
20986
+ } else if (mouse_mode === RED.state.IMPORT_DRAGGING) {
20987
+ clearSelection();
20988
+ RED.history.pop();
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;
20679
21000
  } else if (activeGroup) {
20680
21001
  exitActiveGroup()
20681
21002
  } else {
@@ -20687,6 +21008,7 @@ RED.view = (function() {
20687
21008
  if (mouse_mode === RED.state.SELECTING_NODE && selectNodesOptions.single) {
20688
21009
  return;
20689
21010
  }
21011
+ selectedLinks.clear();
20690
21012
 
20691
21013
  if (activeGroup) {
20692
21014
  var ag = activeGroup;
@@ -20758,7 +21080,6 @@ RED.view = (function() {
20758
21080
  }
20759
21081
  }
20760
21082
  }
20761
- selected_link = null;
20762
21083
  if (mouse_mode !== RED.state.SELECTING_NODE) {
20763
21084
  updateSelection();
20764
21085
  }
@@ -20773,7 +21094,7 @@ RED.view = (function() {
20773
21094
  n.n.selected = false;
20774
21095
  }
20775
21096
  movingSet.clear();
20776
- selected_link = null;
21097
+ selectedLinks.clear();
20777
21098
  if (activeGroup) {
20778
21099
  activeGroup.active = false
20779
21100
  activeGroup.dirty = true;
@@ -20867,12 +21188,16 @@ RED.view = (function() {
20867
21188
  }
20868
21189
  }
20869
21190
  }
20870
- if (activeFlowLinks.length === 0 && selected_link !== null && selected_link.link) {
20871
- activeLinks.push(selected_link);
20872
- activeLinkNodes[selected_link.source.id] = selected_link.source;
20873
- selected_link.source.dirty = true;
20874
- activeLinkNodes[selected_link.target.id] = selected_link.target;
20875
- selected_link.target.dirty = true;
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
+ })
20876
21201
  }
20877
21202
  } else {
20878
21203
  selection.flows = workspaceSelection;
@@ -20883,6 +21208,10 @@ RED.view = (function() {
20883
21208
  return value.map(function(n) { return n.id })
20884
21209
  } else if (key === 'link') {
20885
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
+ });
20886
21215
  }
20887
21216
  return value;
20888
21217
  });
@@ -20904,7 +21233,7 @@ RED.view = (function() {
20904
21233
  }
20905
21234
  }
20906
21235
  }
20907
- function deleteSelection() {
21236
+ function deleteSelection(reconnectWires) {
20908
21237
  if (mouse_mode === RED.state.SELECTING_NODE) {
20909
21238
  return;
20910
21239
  }
@@ -20952,7 +21281,7 @@ RED.view = (function() {
20952
21281
  updateActiveNodes();
20953
21282
  updateSelection();
20954
21283
  redraw();
20955
- } else if (movingSet.length() > 0 || selected_link != null) {
21284
+ } else if (movingSet.length() > 0 || selectedLinks.length() > 0) {
20956
21285
  var result;
20957
21286
  var node;
20958
21287
  var removedNodes = [];
@@ -20962,6 +21291,16 @@ RED.view = (function() {
20962
21291
  var removedSubflowInputs = [];
20963
21292
  var removedSubflowStatus;
20964
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
+ }
20965
21304
 
20966
21305
  var startDirty = RED.nodes.dirty();
20967
21306
  var startChanged = false;
@@ -21051,71 +21390,71 @@ RED.view = (function() {
21051
21390
  RED.nodes.dirty(true);
21052
21391
  }
21053
21392
  }
21054
- var historyEvent;
21055
21393
 
21056
- if (selected_link && selected_link.link) {
21057
- var sourceId = selected_link.source.id;
21058
- var targetId = selected_link.target.id;
21059
- var sourceIdIndex = selected_link.target.links.indexOf(sourceId);
21060
- var targetIdIndex = selected_link.source.links.indexOf(targetId);
21061
-
21062
- historyEvent = {
21063
- t:"multi",
21064
- events: [
21065
- {
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({
21066
21402
  t: "edit",
21067
- node: selected_link.source,
21068
- changed: selected_link.source.changed,
21403
+ node: link.source,
21404
+ changed: link.source.changed,
21069
21405
  changes: {
21070
- links: $.extend(true,{},{v:selected_link.source.links}).v
21406
+ links: $.extend(true,{},{v:link.source.links}).v
21071
21407
  }
21072
- },
21073
- {
21408
+ })
21409
+ historyEvents.push({
21074
21410
  t: "edit",
21075
- node: selected_link.target,
21076
- changed: selected_link.target.changed,
21411
+ node: link.target,
21412
+ changed: link.target.changed,
21077
21413
  changes: {
21078
- links: $.extend(true,{},{v:selected_link.target.links}).v
21414
+ links: $.extend(true,{},{v:link.target.links}).v
21079
21415
  }
21080
- }
21081
-
21082
- ],
21083
- dirty:RED.nodes.dirty()
21084
- }
21085
- RED.nodes.dirty(true);
21086
- selected_link.source.changed = true;
21087
- selected_link.target.changed = true;
21088
- selected_link.target.links.splice(sourceIdIndex,1);
21089
- selected_link.source.links.splice(targetIdIndex,1);
21090
- selected_link.source.dirty = true;
21091
- selected_link.target.dirty = true;
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;
21092
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
+ })
21093
21453
  } else {
21094
- if (selected_link) {
21095
- RED.nodes.removeLink(selected_link);
21096
- removedLinks.push(selected_link);
21097
- }
21098
- RED.nodes.dirty(true);
21099
- historyEvent = {
21100
- t:"delete",
21101
- nodes:removedNodes,
21102
- links:removedLinks,
21103
- groups: removedGroups,
21104
- subflowOutputs:removedSubflowOutputs,
21105
- subflowInputs:removedSubflowInputs,
21106
- subflow: {
21107
- id: activeSubflow?activeSubflow.id:undefined,
21108
- instances: subflowInstances
21109
- },
21110
- dirty:startDirty
21111
- };
21112
- if (removedSubflowStatus) {
21113
- historyEvent.subflow.status = removedSubflowStatus;
21114
- }
21454
+ RED.history.push(historyEvent);
21115
21455
  }
21116
- RED.history.push(historyEvent);
21117
21456
 
21118
- selected_link = null;
21457
+ selectedLinks.clear();
21119
21458
  updateActiveNodes();
21120
21459
  updateSelection();
21121
21460
  redraw();
@@ -21192,6 +21531,28 @@ RED.view = (function() {
21192
21531
  }
21193
21532
  }
21194
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
+
21195
21556
  function calculateTextWidth(str, className) {
21196
21557
  var result = convertLineBreakCharacter(str);
21197
21558
  var width = 0;
@@ -21278,7 +21639,7 @@ RED.view = (function() {
21278
21639
  activeHoverGroup.hovered = false;
21279
21640
  activeHoverGroup = null;
21280
21641
  }
21281
- 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);
21282
21643
  if (spliceTimer) {
21283
21644
  clearTimeout(spliceTimer);
21284
21645
  spliceTimer = null;
@@ -21526,10 +21887,13 @@ RED.view = (function() {
21526
21887
  } else {
21527
21888
  resetMouseVars();
21528
21889
  }
21529
- selected_link = select_link;
21530
21890
  mousedown_link = select_link;
21531
21891
  if (select_link) {
21892
+ selectedLinks.clear();
21893
+ selectedLinks.add(select_link);
21532
21894
  updateSelection();
21895
+ } else {
21896
+ selectedLinks.clear();
21533
21897
  }
21534
21898
  }
21535
21899
  redraw();
@@ -21538,7 +21902,10 @@ RED.view = (function() {
21538
21902
 
21539
21903
  resetMouseVars();
21540
21904
  hideDragLines();
21541
- selected_link = select_link;
21905
+ if (select_link) {
21906
+ selectedLinks.clear();
21907
+ selectedLinks.add(select_link);
21908
+ }
21542
21909
  mousedown_link = select_link;
21543
21910
  if (select_link) {
21544
21911
  updateSelection();
@@ -21711,10 +22078,13 @@ RED.view = (function() {
21711
22078
  msn.dx = msn.n.x-mouse[0];
21712
22079
  msn.dy = msn.n.y-mouse[1];
21713
22080
  }
21714
-
21715
- mouse_offset = d3.mouse(document.body);
21716
- if (isNaN(mouse_offset[0])) {
21717
- 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]
21718
22088
  }
21719
22089
  }
21720
22090
 
@@ -21795,7 +22165,7 @@ RED.view = (function() {
21795
22165
  //var touch0 = d3.event;
21796
22166
  //var pos = [touch0.pageX,touch0.pageY];
21797
22167
  //RED.touch.radialMenu.show(d3.select(this),pos);
21798
- if (mouse_mode == RED.state.IMPORT_DRAGGING) {
22168
+ if (mouse_mode == RED.state.IMPORT_DRAGGING || mouse_mode == RED.state.DETACHED_DRAGGING) {
21799
22169
  var historyEvent = RED.history.peek();
21800
22170
  if (activeSpliceLink) {
21801
22171
  // TODO: DRY - droppable/nodeMouseDown/canvasMouseUp
@@ -21833,6 +22203,18 @@ RED.view = (function() {
21833
22203
  activeHoverGroup = null;
21834
22204
  }
21835
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
+ }
21836
22218
 
21837
22219
  updateSelection();
21838
22220
  RED.nodes.dirty(true);
@@ -22001,7 +22383,9 @@ RED.view = (function() {
22001
22383
  // }
22002
22384
  // } else
22003
22385
  if (d3.event.shiftKey) {
22004
- clearSelection();
22386
+ if (!(d3.event.ctrlKey||d3.event.metaKey)) {
22387
+ clearSelection();
22388
+ }
22005
22389
  var clickPosition = (d3.event.offsetX/scaleFactor - mousedown_node.x)
22006
22390
  var edgeDelta = (mousedown_node.w/2) - Math.abs(clickPosition);
22007
22391
  var cnodes;
@@ -22029,7 +22413,7 @@ RED.view = (function() {
22029
22413
  mousedown_node.selected = true;
22030
22414
  movingSet.add(mousedown_node);
22031
22415
  }
22032
- selected_link = null;
22416
+ // selectedLinks.clear();
22033
22417
  if (d3.event.button != 2) {
22034
22418
  mouse_mode = RED.state.MOVING;
22035
22419
  var mouse = d3.touches(this)[0]||d3.mouse(this);
@@ -22155,19 +22539,35 @@ RED.view = (function() {
22155
22539
  d3.event.stopPropagation();
22156
22540
  return;
22157
22541
  }
22158
- mousedown_link = d;
22159
- clearSelection();
22160
- selected_link = mousedown_link;
22161
- updateSelection();
22162
- redraw();
22163
- focusView();
22164
- d3.event.stopPropagation();
22165
- if (d3.event.metaKey || d3.event.ctrlKey) {
22166
- d3.select(this).classed("red-ui-flow-link-splice",true);
22167
- var point = d3.mouse(this);
22168
- var clickedGroup = getGroupAt(point[0],point[1]);
22169
- showQuickAddDialog({position:point, splice:selected_link, group:clickedGroup});
22170
- }
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
+ }
22171
22571
  }
22172
22572
  function linkTouchStart(d) {
22173
22573
  if (mouse_mode === RED.state.SELECTING_NODE) {
@@ -22176,7 +22576,8 @@ RED.view = (function() {
22176
22576
  }
22177
22577
  mousedown_link = d;
22178
22578
  clearSelection();
22179
- selected_link = mousedown_link;
22579
+ selectedLinks.clear();
22580
+ selectedLinks.add(mousedown_link);
22180
22581
  updateSelection();
22181
22582
  redraw();
22182
22583
  focusView();
@@ -22412,7 +22813,7 @@ RED.view = (function() {
22412
22813
  function showTouchMenu(obj,pos) {
22413
22814
  var mdn = mousedown_node;
22414
22815
  var options = [];
22415
- 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();}});
22416
22817
  options.push({name:"cut",disabled:(movingSet.length()===0),onselect:function() {copySelection();deleteSelection();}});
22417
22818
  options.push({name:"copy",disabled:(movingSet.length()===0),onselect:function() {copySelection();}});
22418
22819
  options.push({name:"paste",disabled:(clipboard.length===0),onselect:function() {importNodes(clipboard, {generateIds: true, touchImport: true});}});
@@ -23149,6 +23550,13 @@ RED.view = (function() {
23149
23550
  d3.select(pathBack)
23150
23551
  .on("mousedown",linkMouseDown)
23151
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
+ })
23152
23560
 
23153
23561
  var pathOutline = document.createElementNS("http://www.w3.org/2000/svg","path");
23154
23562
  pathOutline.__data__ = d;
@@ -23169,7 +23577,7 @@ RED.view = (function() {
23169
23577
  link.exit().remove();
23170
23578
  link.each(function(d) {
23171
23579
  var link = d3.select(this);
23172
- 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]) {
23173
23581
  var numOutputs = d.source.outputs || 1;
23174
23582
  var sourcePort = d.sourcePort || 0;
23175
23583
  var y = -((numOutputs-1)/2)*13 +13*sourcePort;
@@ -23192,7 +23600,8 @@ RED.view = (function() {
23192
23600
  this.__pathLine__.classList.toggle("red-ui-flow-node-disabled",!!(d.source.d || d.target.d));
23193
23601
  this.__pathLine__.classList.toggle("red-ui-flow-subflow-link", !d.link && activeSubflow);
23194
23602
  }
23195
- 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);
23196
23605
 
23197
23606
  var connectedToUnknown = !!(d.target.type == "unknown" || d.source.type == "unknown");
23198
23607
  this.classList.toggle("red-ui-flow-link-unknown",!!(d.target.type == "unknown" || d.source.type == "unknown"))
@@ -23900,8 +24309,9 @@ RED.view = (function() {
23900
24309
  if (allNodes.size > 0) {
23901
24310
  selection.nodes = Array.from(allNodes);
23902
24311
  }
23903
- if (selected_link != null) {
23904
- selection.link = selected_link;
24312
+ if (selectedLinks.length() > 0) {
24313
+ selection.links = selectedLinks.toArray();
24314
+ selection.link = selection.links[0];
23905
24315
  }
23906
24316
  return selection;
23907
24317
  }
@@ -25202,6 +25612,90 @@ RED.view.tools = (function() {
25202
25612
  }
25203
25613
  }
25204
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
+
25205
25699
  return {
25206
25700
  init: function() {
25207
25701
  RED.actions.add("core:show-selected-node-labels", function() { setSelectedNodeLabelState(true); })
@@ -25260,7 +25754,8 @@ RED.view.tools = (function() {
25260
25754
  RED.actions.add("core:distribute-selection-horizontally", function() { distributeSelection('h') })
25261
25755
  RED.actions.add("core:distribute-selection-vertically", function() { distributeSelection('v') })
25262
25756
 
25263
-
25757
+ RED.actions.add("core:wire-series-of-nodes", function() { wireSeriesOfNodes() })
25758
+ RED.actions.add("core:wire-node-to-multiple", function() { wireNodeToMultiple() })
25264
25759
 
25265
25760
  // RED.actions.add("core:add-node", function() { addNode() })
25266
25761
  },
@@ -40130,6 +40625,7 @@ RED.search = (function() {
40130
40625
  var selected = -1;
40131
40626
  var visible = false;
40132
40627
 
40628
+ var searchHistory = [];
40133
40629
  var index = {};
40134
40630
  var currentResults = [];
40135
40631
  var previousActiveElement;
@@ -40313,6 +40809,20 @@ RED.search = (function() {
40313
40809
  }
40314
40810
  }
40315
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
+ }
40316
40826
  function createDialog() {
40317
40827
  dialog = $("<div>",{id:"red-ui-search",class:"red-ui-search"}).appendTo("#red-ui-main-container");
40318
40828
  var searchDiv = $("<div>",{class:"red-ui-search-container"}).appendTo(dialog);
@@ -40321,7 +40831,12 @@ RED.search = (function() {
40321
40831
  change: function() {
40322
40832
  searchResults.editableList('empty');
40323
40833
  selected = -1;
40324
- currentResults = search($(this).val());
40834
+ var value = $(this).val();
40835
+ if (value === "") {
40836
+ populateSearchHistory();
40837
+ return;
40838
+ }
40839
+ currentResults = search(value);
40325
40840
  if (currentResults.length > 0) {
40326
40841
  for (i=0;i<Math.min(currentResults.length,25);i++) {
40327
40842
  searchResults.editableList('addItem',currentResults[i])
@@ -40393,7 +40908,12 @@ RED.search = (function() {
40393
40908
  })
40394
40909
  }
40395
40910
  }
40396
- } 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")) {
40397
40917
  if (currentResults.length > 0) {
40398
40918
  reveal(currentResults[Math.max(0,selected)].node);
40399
40919
  }
@@ -40409,7 +40929,32 @@ RED.search = (function() {
40409
40929
  addItem: function(container,i,object) {
40410
40930
  var node = object.node;
40411
40931
  var div;
40412
- 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) {
40413
40958
  container.parent().addClass("red-ui-search-more")
40414
40959
  div = $('<a>',{href:'#',class:"red-ui-search-result red-ui-search-empty"}).appendTo(container);
40415
40960
  div.text(RED._("palette.editor.more",{count:object.more.results.length-object.more.start}));
@@ -40464,6 +41009,12 @@ RED.search = (function() {
40464
41009
  }
40465
41010
 
40466
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());
40467
41018
  hide();
40468
41019
  RED.view.reveal(node.id);
40469
41020
  }
@@ -40482,9 +41033,14 @@ RED.search = (function() {
40482
41033
 
40483
41034
  if (dialog === null) {
40484
41035
  createDialog();
41036
+ } else {
41037
+ searchResults.editableList('empty');
40485
41038
  }
40486
41039
  dialog.slideDown(300);
40487
41040
  searchInput.searchBox('value',v)
41041
+ if (!v || v === "") {
41042
+ populateSearchHistory();
41043
+ }
40488
41044
  RED.events.emit("search:open");
40489
41045
  visible = true;
40490
41046
  }
@@ -43115,12 +43671,14 @@ RED.group = (function() {
43115
43671
  markDirty(group);
43116
43672
  }
43117
43673
 
43118
- function getNodes(group,recursive) {
43674
+ function getNodes(group,recursive,excludeGroup) {
43119
43675
  var nodes = [];
43120
43676
  group.nodes.forEach(function(n) {
43121
- nodes.push(n);
43677
+ if (n.type !== 'group' || !excludeGroup) {
43678
+ nodes.push(n);
43679
+ }
43122
43680
  if (recursive && n.type === 'group') {
43123
- nodes = nodes.concat(getNodes(n,recursive))
43681
+ nodes = nodes.concat(getNodes(n,recursive,excludeGroup))
43124
43682
  }
43125
43683
  })
43126
43684
  return nodes;
@@ -49923,6 +50481,10 @@ RED.touch.radialMenu = (function() {
49923
50481
  }
49924
50482
  $('<div>').css("text-align","left").html(getLocaleText(step.description)).appendTo(stepDescription);
49925
50483
 
50484
+ if (step.image) {
50485
+ $(`<img src="red/tours/${step.image}" />`).appendTo(stepDescription)
50486
+ }
50487
+
49926
50488
  var stepToolbar = $('<div>',{class:"red-ui-tourGuide-toolbar"}).appendTo(stepContent);
49927
50489
 
49928
50490
  // var breadcrumbs = $('<div>',{class:"red-ui-tourGuide-breadcrumbs"}).appendTo(stepToolbar);