@node-red/editor-client 4.1.1 → 4.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@node-red/editor-client",
3
- "version": "4.1.1",
3
+ "version": "4.1.2",
4
4
  "license": "Apache-2.0",
5
5
  "repository": {
6
6
  "type": "git",
package/public/red/about CHANGED
@@ -1,3 +1,24 @@
1
+ #### 4.1.2: Maintenance Release
2
+
3
+
4
+ Editor
5
+
6
+ - Fix invalid `dirty` state during redo after deployment (#5352) @GogoVega
7
+ - Fix up port event cancelling on node-select (#5338) @knolleary
8
+ - Add selection-to-subflow context menu item (#5337) @knolleary
9
+ - Show subflow input label on virtual port (#5325) @knolleary
10
+ - Clear suggestions on node/port mouse down (#5323) @knolleary
11
+ - Fix lock icon for read-only user (#5336) @knolleary
12
+ - Fix `RED.comms.subscribe` callback on error (#5313) @GogoVega
13
+
14
+ Runtime
15
+
16
+ - ci: add files generated by npm test to .gitignore (#5230) @bryopsida
17
+ - Handle plugin name in `plugins.getConfig` (#5276) @GogoVega
18
+ - Update express version to 4.22.1 (#5365) @hardillb
19
+ - Improved readme (#5340) @dimitrieh
20
+ - Fix race condition in projects initialization by returning gitTools.init() promise (#5315) @stoprocent
21
+
1
22
  #### 4.1.1: Maintenance Release
2
23
 
3
24
  Editor
package/public/red/red.js CHANGED
@@ -2139,8 +2139,8 @@ RED.comms = (function() {
2139
2139
  subscribers[i](msg.topic,msg.data);
2140
2140
  } catch (error) {
2141
2141
  // need to decide what to do with this uncaught error
2142
- console.warn('Uncaught error from RED.comms.subscribe: ' + err.toString())
2143
- console.warn(err)
2142
+ console.warn('Uncaught error from RED.comms.subscribe: ' + error.toString())
2143
+ console.warn(error)
2144
2144
  }
2145
2145
  }
2146
2146
  }
@@ -9321,7 +9321,15 @@ RED.history = (function() {
9321
9321
  }
9322
9322
  }
9323
9323
  }
9324
-
9324
+ if (ev.node.type === 'subflow') {
9325
+ // Ensure ports get a refresh in case of a label change
9326
+ if (ev.changes.inputLabels) {
9327
+ ev.node.in.forEach(function(input) { input.dirty = true; });
9328
+ }
9329
+ if (ev.changes.outputLabels) {
9330
+ ev.node.out.forEach(function(output) { output.dirty = true; });
9331
+ }
9332
+ }
9325
9333
  ev.node.dirty = true;
9326
9334
  ev.node.changed = ev.changed;
9327
9335
 
@@ -9632,8 +9640,8 @@ RED.history = (function() {
9632
9640
  return {
9633
9641
  //TODO: this function is a placeholder until there is a 'save' event that can be listened to
9634
9642
  markAllDirty: function() {
9635
- for (var i=0;i<undoHistory.length;i++) {
9636
- undoHistory[i].dirty = true;
9643
+ for (const event of [...undoHistory, ...redoHistory]) {
9644
+ event.dirty = true;
9637
9645
  }
9638
9646
  },
9639
9647
  list: function() {
@@ -17791,7 +17799,7 @@ RED.deploy = (function() {
17791
17799
  }
17792
17800
 
17793
17801
  function updateLockedState() {
17794
- if (RED.settings.user?.permissions === 'read') {
17802
+ if (!RED.user.hasPermission('flows.write')) {
17795
17803
  $(".red-ui-deploy-button-group").addClass("readOnly");
17796
17804
  $("#red-ui-header-button-deploy").addClass("disabled");
17797
17805
  } else {
@@ -22742,6 +22750,7 @@ RED.view = (function() {
22742
22750
  let suggestedLinks = [];
22743
22751
  let suggestedJunctions = [];
22744
22752
 
22753
+ let forceFullRedraw = false
22745
22754
  // Note: these are the permitted status colour aliases. The actual RGB values
22746
22755
  // are set in the CSS - flow.scss/colors.scss
22747
22756
  const status_colours = {
@@ -25852,6 +25861,7 @@ RED.view = (function() {
25852
25861
 
25853
25862
  function portMouseDown(d,portType,portIndex, evt) {
25854
25863
  if (RED.view.DEBUG) { console.warn("portMouseDown", mouse_mode,d,portType,portIndex); }
25864
+ clearSuggestedFlow();
25855
25865
  RED.contextMenu.hide();
25856
25866
  evt = evt || d3.event;
25857
25867
  if (evt === 1) {
@@ -26275,9 +26285,9 @@ RED.view = (function() {
26275
26285
  return tooltip;
26276
26286
  }
26277
26287
 
26278
- function portMouseOver(port,d,portType,portIndex) {
26288
+ function portMouseOver(port,d,portType,portIndex, event) {
26279
26289
  if (mouse_mode === RED.state.SELECTING_NODE) {
26280
- d3.event.stopPropagation();
26290
+ (d3.event || event).stopPropagation();
26281
26291
  return;
26282
26292
  }
26283
26293
  clearTimeout(portLabelHoverTimeout);
@@ -26316,9 +26326,9 @@ RED.view = (function() {
26316
26326
  }
26317
26327
  port.classed("red-ui-flow-port-hovered",active);
26318
26328
  }
26319
- function portMouseOut(port,d,portType,portIndex) {
26329
+ function portMouseOut(port,d,portType,portIndex, event) {
26320
26330
  if (mouse_mode === RED.state.SELECTING_NODE) {
26321
- d3.event.stopPropagation();
26331
+ (d3.event || event).stopPropagation();
26322
26332
  return;
26323
26333
  }
26324
26334
  clearTimeout(portLabelHoverTimeout);
@@ -26436,6 +26446,7 @@ RED.view = (function() {
26436
26446
  }
26437
26447
  function nodeMouseDown(d) {
26438
26448
  if (RED.view.DEBUG) { console.warn("nodeMouseDown", mouse_mode,d); }
26449
+ clearSuggestedFlow()
26439
26450
  focusView();
26440
26451
  RED.contextMenu.hide();
26441
26452
  if (d3.event.button === 1) {
@@ -27061,130 +27072,211 @@ RED.view = (function() {
27061
27072
  }
27062
27073
  }
27063
27074
 
27075
+ function buildSubflowPort (d) {
27076
+ const NODE_TYPE = d.direction === "in" ? PORT_TYPE_INPUT : PORT_TYPE_OUTPUT;
27077
+ // PORT_TYPE is the 'opposite' of NODE_TYPE
27078
+ const PORT_TYPE = NODE_TYPE === PORT_TYPE_INPUT ? PORT_TYPE_OUTPUT : PORT_TYPE_INPUT;
27079
+ var node = d3.select(this);
27080
+ var nodeContents = document.createDocumentFragment();
27081
+
27082
+ d.h = 40;
27083
+ d.resize = true;
27084
+ d.dirty = true;
27085
+
27086
+ var mainRect = document.createElementNS("http://www.w3.org/2000/svg","rect");
27087
+ mainRect.__data__ = d;
27088
+ mainRect.setAttribute("class", "red-ui-flow-subflow-port");
27089
+ mainRect.setAttribute("rx", 8);
27090
+ mainRect.setAttribute("ry", 8);
27091
+ mainRect.setAttribute("width", 40);
27092
+ mainRect.setAttribute("height", 40);
27093
+ node[0][0].__mainRect__ = mainRect;
27094
+ d3.select(mainRect)
27095
+ .on("mouseup",nodeMouseUp)
27096
+ .on("mousedown",nodeMouseDown)
27097
+ .on("touchstart",nodeTouchStart)
27098
+ .on("touchend",nodeTouchEnd)
27099
+ nodeContents.appendChild(mainRect);
27100
+
27101
+ const port_label_group = document.createElementNS("http://www.w3.org/2000/svg","g");
27102
+ port_label_group.setAttribute("x",0);
27103
+ port_label_group.setAttribute("y",0);
27104
+ node[0][0].__portLabelGroup__ = port_label_group;
27105
+
27106
+ const port_label = document.createElementNS("http://www.w3.org/2000/svg","text");
27107
+ port_label.setAttribute("class","red-ui-flow-port-label");
27108
+ port_label.style["font-size"] = "10px";
27109
+ port_label.textContent = NODE_TYPE === PORT_TYPE_INPUT? "input" : "output";
27110
+ port_label_group.appendChild(port_label);
27111
+ node[0][0].__portLabel__ = port_label;
27112
+
27113
+ if (NODE_TYPE === PORT_TYPE_OUTPUT) {
27114
+ const port_number = document.createElementNS("http://www.w3.org/2000/svg","text");
27115
+ port_number.setAttribute("class","red-ui-flow-port-label red-ui-flow-port-index");
27116
+ port_number.setAttribute("x",0);
27117
+ port_number.setAttribute("y",0);
27118
+ port_number.textContent = d.i+1;
27119
+ port_label_group.appendChild(port_number);
27120
+ node[0][0].__portNumber__ = port_number;
27121
+ }
27122
+
27123
+ const port_border = document.createElementNS("http://www.w3.org/2000/svg","path");
27124
+ port_border.setAttribute("d","M 40 1 l 0 38")
27125
+ port_border.setAttribute("class", "red-ui-flow-node-icon-shade-border")
27126
+ port_label_group.appendChild(port_border);
27127
+ node[0][0].__portBorder__ = port_border;
27128
+
27129
+ nodeContents.appendChild(port_label_group);
27130
+
27131
+ var text = document.createElementNS("http://www.w3.org/2000/svg","g");
27132
+ text.setAttribute("class","red-ui-flow-port-label");
27133
+ text.setAttribute("transform","translate(38,0)");
27134
+ text.setAttribute('style', 'fill : #888'); // hard coded here!
27135
+ node[0][0].__textGroup__ = text;
27136
+ nodeContents.append(text);
27137
+
27138
+ var portEl = document.createElementNS("http://www.w3.org/2000/svg","g");
27139
+ portEl.setAttribute('transform','translate(-5,15)')
27140
+
27141
+ var port = document.createElementNS("http://www.w3.org/2000/svg","rect");
27142
+ port.setAttribute("class","red-ui-flow-port");
27143
+ port.setAttribute("rx",3);
27144
+ port.setAttribute("ry",3);
27145
+ port.setAttribute("width",10);
27146
+ port.setAttribute("height",10);
27147
+ portEl.appendChild(port);
27148
+ port.__data__ = d;
27149
+
27150
+ d3.select(port)
27151
+ .on("mousedown", function(d,i){portMouseDown(d,PORT_TYPE,0);} )
27152
+ .on("touchstart", function(d,i){portMouseDown(d,PORT_TYPE,0);d3.event.preventDefault();} )
27153
+ .on("mouseup", function(d,i){portMouseUp(d,PORT_TYPE,0);})
27154
+ .on("touchend",function(d,i){portMouseUp(d,PORT_TYPE,0);d3.event.preventDefault();} )
27155
+ .on("mouseover",function(d){portMouseOver(d3.select(this),d,PORT_TYPE,0);})
27156
+ .on("mouseout",function(d){portMouseOut(d3.select(this),d,PORT_TYPE,0);});
27157
+
27158
+ node[0][0].__port__ = portEl
27159
+ nodeContents.appendChild(portEl);
27160
+ node[0][0].appendChild(nodeContents);
27161
+ }
27162
+ function updateSubflowPort (d) {
27163
+ if (d.dirty) {
27164
+ const port_height = 40;
27165
+ const NODE_TYPE = d.direction === "in" ? PORT_TYPE_INPUT : PORT_TYPE_OUTPUT;
27166
+ // PORT_TYPE is the 'opposite' of NODE_TYPE
27167
+ const PORT_TYPE = NODE_TYPE === PORT_TYPE_INPUT ? PORT_TYPE_OUTPUT : PORT_TYPE_INPUT;
27168
+
27169
+ var label = getPortLabel(activeSubflow, NODE_TYPE, d.i) || "";
27170
+ var hideLabel = (label.length < 1)
27171
+ var labelParts;
27172
+ if (d.resize || this.__hideLabel__ !== hideLabel || this.__label__ !== label) {
27173
+ labelParts = getLabelParts(label, "red-ui-flow-node-label");
27174
+ if (labelParts.lines.length !== this.__labelLineCount__ || this.__label__ !== label) {
27175
+ d.resize = true;
27176
+ }
27177
+ this.__label__ = label;
27178
+ this.__labelLineCount__ = labelParts.lines.length;
27179
+
27180
+ if (hideLabel) {
27181
+ d.h = Math.max(port_height,(d.outputs || 0) * 15);
27182
+ } else {
27183
+ d.h = Math.max(6+24*labelParts.lines.length,(d.outputs || 0) * 15, port_height);
27184
+ }
27185
+ this.__hideLabel__ = hideLabel;
27186
+ }
27187
+
27188
+ if (d.resize) {
27189
+ var ow = d.w;
27190
+ if (hideLabel) {
27191
+ d.w = port_height;
27192
+ } else {
27193
+ d.w = Math.max(port_height,20*(Math.ceil((labelParts.width+50+7)/20)) );
27194
+ }
27195
+ if (ow !== undefined) {
27196
+ d.x += (d.w-ow)/2;
27197
+ }
27198
+ d.resize = false;
27199
+ }
27200
+
27201
+ this.setAttribute("transform", "translate(" + (d.x-d.w/2) + "," + (d.y-d.h/2) + ")");
27202
+ // This might be the first redraw after a node has been click-dragged to start a move.
27203
+ // So its selected state might have changed since the last redraw.
27204
+ this.classList.toggle("red-ui-flow-node-selected", !!d.selected )
27205
+ if (mouse_mode != RED.state.MOVING_ACTIVE) {
27206
+ this.classList.toggle("red-ui-flow-node-disabled", d.d === true);
27207
+ this.__mainRect__.setAttribute("width", d.w)
27208
+ this.__mainRect__.setAttribute("height", d.h)
27209
+ this.__mainRect__.classList.toggle("red-ui-flow-node-highlighted",!!d.highlighted );
27210
+
27211
+ if (labelParts) {
27212
+ // The label has changed
27213
+ var sa = labelParts.lines;
27214
+ var sn = labelParts.lines.length;
27215
+ var textLines = this.__textGroup__.childNodes;
27216
+ while(textLines.length > sn) {
27217
+ textLines[textLines.length-1].remove();
27218
+ }
27219
+ for (var i=0; i<sn; i++) {
27220
+ if (i===textLines.length) {
27221
+ var line = document.createElementNS("http://www.w3.org/2000/svg","text");
27222
+ line.setAttribute("class","red-ui-flow-node-label-text");
27223
+ line.setAttribute("x",0);
27224
+ line.setAttribute("y",i*24);
27225
+ this.__textGroup__.appendChild(line);
27226
+ }
27227
+ textLines[i].textContent = sa[i];
27228
+ }
27229
+ }
27230
+
27231
+ var textClass = "red-ui-flow-node-label"+(hideLabel?" hide":"");
27232
+ this.__textGroup__.setAttribute("class", textClass);
27233
+ var yp = d.h / 2 - (this.__labelLineCount__ / 2) * 24 + 13;
27234
+
27235
+ // this.__textGroup__.classList.remove("red-ui-flow-node-label-right");
27236
+ this.__textGroup__.setAttribute("transform", "translate(48,"+yp+")");
27237
+
27238
+ this.__portBorder__.setAttribute("d","M 40 1 l 0 "+(hideLabel?0:(d.h - 2)));
27239
+ const portX = PORT_TYPE === PORT_TYPE_OUTPUT ? d.w - 5 : -5
27240
+ this.__port__.setAttribute("transform","translate("+portX+","+((d.h/2)-5)+")");
27241
+ if (NODE_TYPE === PORT_TYPE_OUTPUT) {
27242
+ this.__portLabel__.setAttribute("transform","translate(20,"+((d.h/2)-8)+")");
27243
+ this.__portNumber__.setAttribute("transform","translate(20,"+((d.h/2)+7)+")");
27244
+ this.__portNumber__.textContent = d.i+1;
27245
+ } else {
27246
+ this.__portLabel__.setAttribute("transform","translate(20,"+(d.h/2)+")");
27247
+ }
27248
+ }
27249
+ d.dirty = false;
27250
+ return true
27251
+ }
27252
+ return false
27253
+ }
27254
+
27064
27255
  function _redraw() {
27065
27256
  eventLayer.attr("transform","scale("+scaleFactor+")");
27066
27257
  outer.attr("width", space_width*scaleFactor).attr("height", space_height*scaleFactor);
27067
27258
 
27068
27259
  // Don't bother redrawing nodes if we're drawing links
27069
- if (showAllLinkPorts !== -1 || mouse_mode != RED.state.JOINING) {
27070
-
27260
+ if (forceFullRedraw || showAllLinkPorts !== -1 || mouse_mode != RED.state.JOINING) {
27261
+ forceFullRedraw = false
27071
27262
  var dirtyNodes = {};
27072
27263
 
27073
27264
  if (activeSubflow) {
27074
27265
  var subflowOutputs = nodeLayer.selectAll(".red-ui-flow-subflow-port-output").data(activeSubflow.out,function(d,i){ return d.id;});
27075
27266
  subflowOutputs.exit().remove();
27076
27267
  var outGroup = subflowOutputs.enter().insert("svg:g").attr("class","red-ui-flow-node red-ui-flow-subflow-port-output")
27077
- outGroup.each(function(d,i) {
27078
- var node = d3.select(this);
27079
- var nodeContents = document.createDocumentFragment();
27080
-
27081
- d.h = 40;
27082
- d.resize = true;
27083
- d.dirty = true;
27084
-
27085
- var mainRect = document.createElementNS("http://www.w3.org/2000/svg","rect");
27086
- mainRect.__data__ = d;
27087
- mainRect.setAttribute("class", "red-ui-flow-subflow-port");
27088
- mainRect.setAttribute("rx", 8);
27089
- mainRect.setAttribute("ry", 8);
27090
- mainRect.setAttribute("width", 40);
27091
- mainRect.setAttribute("height", 40);
27092
- node[0][0].__mainRect__ = mainRect;
27093
- d3.select(mainRect)
27094
- .on("mouseup",nodeMouseUp)
27095
- .on("mousedown",nodeMouseDown)
27096
- .on("touchstart",nodeTouchStart)
27097
- .on("touchend",nodeTouchEnd)
27098
- nodeContents.appendChild(mainRect);
27099
-
27100
- var output_groupEl = document.createElementNS("http://www.w3.org/2000/svg","g");
27101
- output_groupEl.setAttribute("x",0);
27102
- output_groupEl.setAttribute("y",0);
27103
- node[0][0].__outputLabelGroup__ = output_groupEl;
27104
-
27105
- var output_output = document.createElementNS("http://www.w3.org/2000/svg","text");
27106
- output_output.setAttribute("class","red-ui-flow-port-label");
27107
- output_output.style["font-size"] = "10px";
27108
- output_output.textContent = "output";
27109
- output_groupEl.appendChild(output_output);
27110
- node[0][0].__outputOutput__ = output_output;
27111
-
27112
- var output_number = document.createElementNS("http://www.w3.org/2000/svg","text");
27113
- output_number.setAttribute("class","red-ui-flow-port-label red-ui-flow-port-index");
27114
- output_number.setAttribute("x",0);
27115
- output_number.setAttribute("y",0);
27116
- output_number.textContent = d.i+1;
27117
- output_groupEl.appendChild(output_number);
27118
- node[0][0].__outputNumber__ = output_number;
27119
-
27120
- var output_border = document.createElementNS("http://www.w3.org/2000/svg","path");
27121
- output_border.setAttribute("d","M 40 1 l 0 38")
27122
- output_border.setAttribute("class", "red-ui-flow-node-icon-shade-border")
27123
- output_groupEl.appendChild(output_border);
27124
- node[0][0].__outputBorder__ = output_border;
27125
-
27126
- nodeContents.appendChild(output_groupEl);
27127
-
27128
- var text = document.createElementNS("http://www.w3.org/2000/svg","g");
27129
- text.setAttribute("class","red-ui-flow-port-label");
27130
- text.setAttribute("transform","translate(38,0)");
27131
- text.setAttribute('style', 'fill : #888'); // hard coded here!
27132
- node[0][0].__textGroup__ = text;
27133
- nodeContents.append(text);
27134
-
27135
- var portEl = document.createElementNS("http://www.w3.org/2000/svg","g");
27136
- portEl.setAttribute('transform','translate(-5,15)')
27137
-
27138
- var port = document.createElementNS("http://www.w3.org/2000/svg","rect");
27139
- port.setAttribute("class","red-ui-flow-port");
27140
- port.setAttribute("rx",3);
27141
- port.setAttribute("ry",3);
27142
- port.setAttribute("width",10);
27143
- port.setAttribute("height",10);
27144
- portEl.appendChild(port);
27145
- port.__data__ = d;
27146
-
27147
- d3.select(port)
27148
- .on("mousedown", function(d,i){portMouseDown(d,PORT_TYPE_INPUT,0);} )
27149
- .on("touchstart", function(d,i){portMouseDown(d,PORT_TYPE_INPUT,0);d3.event.preventDefault();} )
27150
- .on("mouseup", function(d,i){portMouseUp(d,PORT_TYPE_INPUT,0);})
27151
- .on("touchend",function(d,i){portMouseUp(d,PORT_TYPE_INPUT,0);d3.event.preventDefault();} )
27152
- .on("mouseover",function(d){portMouseOver(d3.select(this),d,PORT_TYPE_INPUT,0);})
27153
- .on("mouseout",function(d){portMouseOut(d3.select(this),d,PORT_TYPE_INPUT,0);});
27154
-
27155
- node[0][0].__port__ = portEl
27156
- nodeContents.appendChild(portEl);
27157
- node[0][0].appendChild(nodeContents);
27158
- });
27268
+ outGroup.each(buildSubflowPort);
27159
27269
 
27160
27270
  var subflowInputs = nodeLayer.selectAll(".red-ui-flow-subflow-port-input").data(activeSubflow.in,function(d,i){ return d.id;});
27161
27271
  subflowInputs.exit().remove();
27162
27272
  var inGroup = subflowInputs.enter().insert("svg:g").attr("class","red-ui-flow-node red-ui-flow-subflow-port-input").attr("transform",function(d) { return "translate("+(d.x-20)+","+(d.y-20)+")"});
27163
- inGroup.each(function(d,i) {
27164
- d.w=40;
27165
- d.h=40;
27166
- });
27167
- inGroup.append("rect").attr("class","red-ui-flow-subflow-port").attr("rx",8).attr("ry",8).attr("width",40).attr("height",40)
27168
- // TODO: This is exactly the same set of handlers used for regular nodes - DRY
27169
- .on("mouseup",nodeMouseUp)
27170
- .on("mousedown",nodeMouseDown)
27171
- .on("touchstart",nodeTouchStart)
27172
- .on("touchend", nodeTouchEnd);
27173
-
27174
- inGroup.append("g").attr('transform','translate(35,15)').append("rect").attr("class","red-ui-flow-port").attr("rx",3).attr("ry",3).attr("width",10).attr("height",10)
27175
- .on("mousedown", function(d,i){portMouseDown(d,PORT_TYPE_OUTPUT,i);} )
27176
- .on("touchstart", function(d,i){portMouseDown(d,PORT_TYPE_OUTPUT,i);d3.event.preventDefault();} )
27177
- .on("mouseup", function(d,i){portMouseUp(d,PORT_TYPE_OUTPUT,i);})
27178
- .on("touchend",function(d,i){portMouseUp(d,PORT_TYPE_OUTPUT,i);d3.event.preventDefault();} )
27179
- .on("mouseover",function(d){portMouseOver(d3.select(this),d,PORT_TYPE_OUTPUT,0);})
27180
- .on("mouseout",function(d) {portMouseOut(d3.select(this),d,PORT_TYPE_OUTPUT,0);});
27181
-
27182
- inGroup.append("svg:text").attr("class","red-ui-flow-port-label").attr("x",18).attr("y",20).style("font-size","10px").text("input");
27273
+ inGroup.each(buildSubflowPort);
27183
27274
 
27184
27275
  var subflowStatus = nodeLayer.selectAll(".red-ui-flow-subflow-port-status").data(activeSubflow.status?[activeSubflow.status]:[],function(d,i){ return d.id;});
27185
27276
  subflowStatus.exit().remove();
27186
27277
 
27187
27278
  var statusGroup = subflowStatus.enter().insert("svg:g").attr("class","red-ui-flow-node red-ui-flow-subflow-port-status").attr("transform",function(d) { return "translate("+(d.x-20)+","+(d.y-20)+")"});
27279
+ // TODO: use buildSubflowPort/updateSubflowPort for status port
27188
27280
  statusGroup.each(function(d,i) {
27189
27281
  d.w=40;
27190
27282
  d.h=40;
@@ -27206,104 +27298,16 @@ RED.view = (function() {
27206
27298
 
27207
27299
  statusGroup.append("svg:text").attr("class","red-ui-flow-port-label").attr("x",22).attr("y",20).style("font-size","10px").text("status");
27208
27300
 
27209
- subflowOutputs.each(function(d,i) {
27210
- if (d.dirty) {
27211
-
27212
- var port_height = 40;
27213
-
27214
- var self = this;
27215
- var thisNode = d3.select(this);
27216
-
27301
+ subflowOutputs.each(function (d,i) {
27302
+ if (updateSubflowPort.call(this, d)) {
27217
27303
  dirtyNodes[d.id] = d;
27218
-
27219
- var label = getPortLabel(activeSubflow, PORT_TYPE_OUTPUT, d.i) || "";
27220
- var hideLabel = (label.length < 1)
27221
-
27222
- var labelParts;
27223
- if (d.resize || this.__hideLabel__ !== hideLabel || this.__label__ !== label) {
27224
- labelParts = getLabelParts(label, "red-ui-flow-node-label");
27225
- if (labelParts.lines.length !== this.__labelLineCount__ || this.__label__ !== label) {
27226
- d.resize = true;
27227
- }
27228
- this.__label__ = label;
27229
- this.__labelLineCount__ = labelParts.lines.length;
27230
-
27231
- if (hideLabel) {
27232
- d.h = Math.max(port_height,(d.outputs || 0) * 15);
27233
- } else {
27234
- d.h = Math.max(6+24*labelParts.lines.length,(d.outputs || 0) * 15, port_height);
27235
- }
27236
- this.__hideLabel__ = hideLabel;
27237
- }
27238
-
27239
- if (d.resize) {
27240
- var ow = d.w;
27241
- if (hideLabel) {
27242
- d.w = port_height;
27243
- } else {
27244
- d.w = Math.max(port_height,20*(Math.ceil((labelParts.width+50+7)/20)) );
27245
- }
27246
- if (ow !== undefined) {
27247
- d.x += (d.w-ow)/2;
27248
- }
27249
- d.resize = false;
27250
- }
27251
-
27252
- this.setAttribute("transform", "translate(" + (d.x-d.w/2) + "," + (d.y-d.h/2) + ")");
27253
- // This might be the first redraw after a node has been click-dragged to start a move.
27254
- // So its selected state might have changed since the last redraw.
27255
- this.classList.toggle("red-ui-flow-node-selected", !!d.selected )
27256
- if (mouse_mode != RED.state.MOVING_ACTIVE) {
27257
- this.classList.toggle("red-ui-flow-node-disabled", d.d === true);
27258
- this.__mainRect__.setAttribute("width", d.w)
27259
- this.__mainRect__.setAttribute("height", d.h)
27260
- this.__mainRect__.classList.toggle("red-ui-flow-node-highlighted",!!d.highlighted );
27261
-
27262
- if (labelParts) {
27263
- // The label has changed
27264
- var sa = labelParts.lines;
27265
- var sn = labelParts.lines.length;
27266
- var textLines = this.__textGroup__.childNodes;
27267
- while(textLines.length > sn) {
27268
- textLines[textLines.length-1].remove();
27269
- }
27270
- for (var i=0; i<sn; i++) {
27271
- if (i===textLines.length) {
27272
- var line = document.createElementNS("http://www.w3.org/2000/svg","text");
27273
- line.setAttribute("class","red-ui-flow-node-label-text");
27274
- line.setAttribute("x",0);
27275
- line.setAttribute("y",i*24);
27276
- this.__textGroup__.appendChild(line);
27277
- }
27278
- textLines[i].textContent = sa[i];
27279
- }
27280
- }
27281
-
27282
- var textClass = "red-ui-flow-node-label"+(hideLabel?" hide":"");
27283
- this.__textGroup__.setAttribute("class", textClass);
27284
- var yp = d.h / 2 - (this.__labelLineCount__ / 2) * 24 + 13;
27285
-
27286
- // this.__textGroup__.classList.remove("red-ui-flow-node-label-right");
27287
- this.__textGroup__.setAttribute("transform", "translate(48,"+yp+")");
27288
-
27289
- this.__outputBorder__.setAttribute("d","M 40 1 l 0 "+(hideLabel?0:(d.h - 2)));
27290
- this.__port__.setAttribute("transform","translate(-5,"+((d.h/2)-5)+")");
27291
- this.__outputOutput__.setAttribute("transform","translate(20,"+((d.h/2)-8)+")");
27292
- this.__outputNumber__.setAttribute("transform","translate(20,"+((d.h/2)+7)+")");
27293
- this.__outputNumber__.textContent = d.i+1;
27294
- }
27295
- d.dirty = false;
27296
27304
  }
27297
- });
27298
- subflowInputs.each(function(d,i) {
27299
- if (d.dirty) {
27300
- var input = d3.select(this);
27301
- input.classed("red-ui-flow-node-selected",function(d) { return d.selected; })
27302
- input.attr("transform", function(d) { return "translate(" + (d.x-d.w/2) + "," + (d.y-d.h/2) + ")"; });
27305
+ })
27306
+ subflowInputs.each(function (d,i) {
27307
+ if (updateSubflowPort.call(this, d)) {
27303
27308
  dirtyNodes[d.id] = d;
27304
- d.dirty = false;
27305
27309
  }
27306
- });
27310
+ })
27307
27311
  subflowStatus.each(function(d,i) {
27308
27312
  if (d.dirty) {
27309
27313
  var output = d3.select(this);
@@ -29357,7 +29361,6 @@ RED.view = (function() {
29357
29361
  refreshSuggestedFlow();
29358
29362
  } else { // Anything else; clear the suggestion
29359
29363
  clearSuggestedFlow();
29360
- RED.view.redraw(true);
29361
29364
  // manually push the event to the keyboard handler
29362
29365
  RED.keyboard.handle(evt)
29363
29366
  }
@@ -29366,7 +29369,6 @@ RED.view = (function() {
29366
29369
  if (suggestion.clickToApply) {
29367
29370
  $(window).on('mousedown.suggestedFlow', function (evnt) {
29368
29371
  clearSuggestedFlow();
29369
- RED.view.redraw(true);
29370
29372
  })
29371
29373
  }
29372
29374
  }
@@ -29387,12 +29389,16 @@ RED.view = (function() {
29387
29389
  }
29388
29390
 
29389
29391
  function clearSuggestedFlow () {
29390
- $(window).off('mousedown.suggestedFlow');
29391
- $(window).off('keydown.suggestedFlow')
29392
- RED.keyboard.enable()
29393
- currentSuggestion = null
29394
- suggestedNodes = []
29395
- suggestedLinks = []
29392
+ if (currentSuggestion) {
29393
+ $(window).off('mousedown.suggestedFlow');
29394
+ $(window).off('keydown.suggestedFlow')
29395
+ RED.keyboard.enable()
29396
+ currentSuggestion = null
29397
+ suggestedNodes = []
29398
+ suggestedLinks = []
29399
+ forceFullRedraw = true
29400
+ RED.view.redraw(true);
29401
+ }
29396
29402
  }
29397
29403
 
29398
29404
  function applySuggestedFlow () {
@@ -40019,6 +40025,9 @@ RED.editor = (function() {
40019
40025
  changes.inputLabels = node.inputLabels;
40020
40026
  node.inputLabels = newValue;
40021
40027
  changed = true;
40028
+ if (node.type === "subflow") {
40029
+ node.in[0].dirty = true
40030
+ }
40022
40031
  }
40023
40032
  hasNonBlankLabel = false;
40024
40033
  newValue = new Array(node.outputs);
@@ -49295,6 +49304,11 @@ RED.search = (function() {
49295
49304
  { onselect: 'core:show-export-dialog', label: RED._("menu.label.export") }
49296
49305
  )
49297
49306
  }
49307
+ if (hasSelection && canEdit) {
49308
+ menuItems.push(
49309
+ { onselect: 'core:convert-to-subflow', label: RED._("menu.label.selectionToSubflow") }
49310
+ )
49311
+ }
49298
49312
  menuItems.push(
49299
49313
  { onselect: 'core:select-all-nodes', label: RED._("keyboard.selectAll") }
49300
49314
  )