1
0
mirror of https://github.com/node-red/node-red.git synced 2023-10-10 13:36:53 +02:00

Move subflow handling to own module

This commit is contained in:
Nick O'Leary 2015-03-12 00:08:47 +00:00
parent e26ea14104
commit ab3e64271b
7 changed files with 462 additions and 399 deletions

View File

@ -268,6 +268,7 @@
<script src="red/ui/editor.js"></script> <script src="red/ui/editor.js"></script>
<script src="red/ui/library.js"></script> <script src="red/ui/library.js"></script>
<script src="red/ui/notifications.js"></script> <script src="red/ui/notifications.js"></script>
<script src="red/ui/subflow.js"></script>
<script src="red/ui/touch/radialMenu.js"></script> <script src="red/ui/touch/radialMenu.js"></script>
</body> </body>

View File

@ -1,5 +1,5 @@
/** /**
* Copyright 2013 IBM Corp. * Copyright 2013, 2015 IBM Corp.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@ -286,8 +286,8 @@ var RED = (function() {
{id:"btn-config-nodes",label:"Configuration nodes",onselect:RED.sidebar.config.show}, {id:"btn-config-nodes",label:"Configuration nodes",onselect:RED.sidebar.config.show},
null, null,
{id:"btn-subflow-menu",label:"Subflows", options: [ {id:"btn-subflow-menu",label:"Subflows", options: [
{id:"btn-create-subflow",label:"Create subflow",onselect:RED.view.createSubflow}, {id:"btn-create-subflow",label:"Create subflow",onselect:RED.subflow.createSubflow},
{id:"btn-convert-subflow",label:"Selection to subflow",disabled:true,onselect:RED.view.convertToSubflow}, {id:"btn-convert-subflow",label:"Selection to subflow",disabled:true,onselect:RED.subflow.convertToSubflow},
]}, ]},
null, null,
{id:"btn-workspace-menu",label:"Workspaces",options:[ {id:"btn-workspace-menu",label:"Workspaces",options:[
@ -356,8 +356,24 @@ var RED = (function() {
RED.library.init(); RED.library.init();
RED.palette.init(); RED.palette.init();
RED.sidebar.init(); RED.sidebar.init();
RED.subflow.init();
RED.view.init(); RED.view.init();
RED.view.on("selection-changed",function(selection) {
if (!selection.nodes) {
RED.menu.setDisabled("btn-export-menu",true);
RED.menu.setDisabled("btn-export-clipboard",true);
RED.menu.setDisabled("btn-export-library",true);
RED.menu.setDisabled("btn-convert-subflow",true);
} else {
RED.menu.setDisabled("btn-export-menu",false);
RED.menu.setDisabled("btn-export-clipboard",false);
RED.menu.setDisabled("btn-export-library",false);
RED.menu.setDisabled("btn-convert-subflow",false);
}
});
RED.keyboard.add(/* ? */ 191,{shift:true},function(){showHelp();d3.event.preventDefault();}); RED.keyboard.add(/* ? */ 191,{shift:true},function(){showHelp();d3.event.preventDefault();});
RED.comms.connect(); RED.comms.connect();
loadNodeList(); loadNodeList();

View File

@ -1,5 +1,5 @@
/** /**
* Copyright 2013 IBM Corp. * Copyright 2013, 2015 IBM Corp.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

392
public/red/ui/subflow.js Normal file
View File

@ -0,0 +1,392 @@
/**
* Copyright 2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.subflow = (function() {
function getSubflow() {
return RED.nodes.subflow(RED.view.getWorkspace());
}
function findAvailableSubflowIOPosition(subflow) {
var pos = {x:70,y:70};
for (var i=0;i<subflow.out.length+subflow.in.length;i++) {
var port;
if (i < subflow.out.length) {
port = subflow.out[i];
} else {
port = subflow.in[i-subflow.out.length];
}
if (port.x == pos.x && port.y == pos.y) {
pos.x += 55;
i=0;
}
}
return pos;
}
function addSubflowInput() {
var subflow = RED.nodes.subflow(RED.view.getWorkspace());
var position = findAvailableSubflowIOPosition(subflow);
var newInput = {
type:"subflow",
direction:"in",
z:subflow.id,
i:subflow.in.length,
x:position.x,
y:position.y,
id:RED.nodes.id()
};
var oldInCount = subflow.in.length;
subflow.in.push(newInput);
subflow.dirty = true;
var wasDirty = RED.view.dirty();
var wasChanged = subflow.changed;
subflow.changed = true;
RED.nodes.eachNode(function(n) {
if (n.type == "subflow:"+subflow.id) {
n.changed = true;
n.inputs = subflow.in.length;
RED.editor.updateNodeProperties(n);
}
});
var historyEvent = {
t:'edit',
node:subflow,
dirty:wasDirty,
changed:wasChanged,
subflow: {
inputCount: oldInCount
}
};
RED.history.push(historyEvent);
$("#workspace-subflow-add-input").toggleClass("disabled",true);
RED.view.select();
}
function addSubflowOutput(id) {
var subflow = RED.nodes.subflow(RED.view.getWorkspace());
var position = findAvailableSubflowIOPosition(subflow);
var newOutput = {
type:"subflow",
direction:"out",
z:subflow.id,
i:subflow.out.length,
x:position.x,
y:position.y,
id:RED.nodes.id()
};
var oldOutCount = subflow.out.length;
subflow.out.push(newOutput);
subflow.dirty = true;
var wasDirty = RED.view.dirty();
var wasChanged = subflow.changed;
subflow.changed = true;
RED.nodes.eachNode(function(n) {
if (n.type == "subflow:"+subflow.id) {
n.changed = true;
n.outputs = subflow.out.length;
RED.editor.updateNodeProperties(n);
}
});
var historyEvent = {
t:'edit',
node:subflow,
dirty:wasDirty,
changed:wasChanged,
subflow: {
outputCount: oldOutCount
}
};
RED.history.push(historyEvent);
RED.view.select();
}
function init() {
$("#workspace-subflow-edit").click(function(event) {
RED.editor.editSubflow(RED.nodes.subflow(RED.view.getWorkspace()));
event.preventDefault();
});
$("#workspace-subflow-add-input").click(function(event) {
event.preventDefault();
if ($(this).hasClass("disabled")) {
return;
}
addSubflowInput();
});
$("#workspace-subflow-add-output").click(function(event) {
event.preventDefault();
if ($(this).hasClass("disabled")) {
return;
}
addSubflowOutput();
});
$("#workspace-subflow-delete").click(function(event) {
event.preventDefault();
var removedNodes = [];
var removedLinks = [];
var startDirty = RED.view.dirty();
RED.nodes.eachNode(function(n) {
if (n.type == "subflow:"+getSubflow().id) {
removedNodes.push(n);
}
if (n.z == getSubflow().id) {
removedNodes.push(n);
}
});
for (var i=0;i<removedNodes.length;i++) {
var rmlinks = RED.nodes.remove(removedNodes[i].id);
removedLinks = removedLinks.concat(rmlinks);
}
var activeSubflow = getSubflow();
RED.nodes.removeSubflow(activeSubflow);
RED.history.push({
t:'delete',
nodes:removedNodes,
links:removedLinks,
subflow: activeSubflow,
dirty:startDirty
});
RED.view.removeWorkspace(activeSubflow);
RED.view.dirty(true);
RED.view.redraw();
});
}
function createSubflow() {
var lastIndex = 0;
RED.nodes.eachSubflow(function(sf) {
var m = (new RegExp("^Subflow (\\d+)$")).exec(sf.name);
if (m) {
lastIndex = Math.max(lastIndex,m[1]);
}
});
var name = "Subflow "+(lastIndex+1);
var subflowId = RED.nodes.id();
var subflow = {
type:"subflow",
id:subflowId,
name:name,
in: [],
out: []
};
RED.nodes.addSubflow(subflow);
RED.history.push({
t:'createSubflow',
subflow: subflow,
dirty:RED.view.dirty()
});
RED.view.showSubflow(subflowId);
}
function convertToSubflow() {
var selection = RED.view.selection();
if (!selection.nodes) {
RED.notify("<strong>Cannot create subflow</strong>: no nodes selected","error");
return;
}
var i;
var nodes = {};
var new_links = [];
var removedLinks = [];
var candidateInputs = [];
var candidateOutputs = [];
var boundingBox = [selection.nodes[0].x,
selection.nodes[0].y,
selection.nodes[0].x,
selection.nodes[0].y];
for (i=0;i<selection.nodes.length;i++) {
var n = selection.nodes[i];
nodes[n.id] = {n:n,outputs:{}};
boundingBox = [
Math.min(boundingBox[0],n.x),
Math.min(boundingBox[1],n.y),
Math.max(boundingBox[2],n.x),
Math.max(boundingBox[3],n.y)
]
}
var center = [(boundingBox[2]+boundingBox[0]) / 2,(boundingBox[3]+boundingBox[1]) / 2];
RED.nodes.eachLink(function(link) {
if (nodes[link.source.id] && nodes[link.target.id]) {
// A link wholely within the selection
}
if (nodes[link.source.id] && !nodes[link.target.id]) {
// An outbound link from the selection
candidateOutputs.push(link);
removedLinks.push(link);
}
if (!nodes[link.source.id] && nodes[link.target.id]) {
// An inbound link
candidateInputs.push(link);
removedLinks.push(link);
}
});
var outputs = {};
candidateOutputs = candidateOutputs.filter(function(v) {
if (outputs[v.source.id+":"+v.sourcePort]) {
outputs[v.source.id+":"+v.sourcePort].targets.push(v.target);
return false;
}
v.targets = [];
v.targets.push(v.target);
outputs[v.source.id+":"+v.sourcePort] = v;
return true;
});
candidateOutputs.sort(function(a,b) { return a.source.y-b.source.y});
if (candidateInputs.length > 1) {
RED.notify("<strong>Cannot create subflow</strong>: multiple inputs to selection","error");
return;
}
//if (candidateInputs.length == 0) {
// RED.notify("<strong>Cannot create subflow</strong>: no input to selection","error");
// return;
//}
var lastIndex = 0;
RED.nodes.eachSubflow(function(sf) {
var m = (new RegExp("^Subflow (\\d+)$")).exec(sf.name);
if (m) {
lastIndex = Math.max(lastIndex,m[1]);
}
});
var name = "Subflow "+(lastIndex+1);
var subflowId = RED.nodes.id();
var subflow = {
type:"subflow",
id:subflowId,
name:name,
in: candidateInputs.map(function(v,i) { var index = i; return {
type:"subflow",
direction:"in",
x:v.target.x-(v.target.w/2)-80,
y:v.target.y,
z:subflowId,
i:index,
id:RED.nodes.id(),
wires:[{id:v.target.id}]
}}),
out: candidateOutputs.map(function(v,i) { var index = i; return {
type:"subflow",
direction:"in",
x:v.source.x+(v.source.w/2)+80,
y:v.source.y,
z:subflowId,
i:index,
id:RED.nodes.id(),
wires:[{id:v.source.id,port:v.sourcePort}]
}})
};
RED.nodes.addSubflow(subflow);
var subflowInstance = {
id:RED.nodes.id(),
type:"subflow:"+subflow.id,
x: center[0],
y: center[1],
z: RED.view.getWorkspace(),
inputs: subflow.in.length,
outputs: subflow.out.length,
h: Math.max(30/*node_height*/,(subflow.out.length||0) * 15),
changed:true
}
subflowInstance._def = RED.nodes.getType(subflowInstance.type);
RED.editor.validateNode(subflowInstance);
RED.nodes.add(subflowInstance);
candidateInputs.forEach(function(l) {
var link = {source:l.source, sourcePort:l.sourcePort, target: subflowInstance};
new_links.push(link);
RED.nodes.addLink(link);
});
candidateOutputs.forEach(function(output,i) {
output.targets.forEach(function(target) {
var link = {source:subflowInstance, sourcePort:i, target: target};
new_links.push(link);
RED.nodes.addLink(link);
});
});
subflow.in.forEach(function(input) {
input.wires.forEach(function(wire) {
var link = {source: input, sourcePort: 0, target: RED.nodes.node(wire.id) }
new_links.push(link);
RED.nodes.addLink(link);
});
});
subflow.out.forEach(function(output,i) {
output.wires.forEach(function(wire) {
var link = {source: RED.nodes.node(wire.id), sourcePort: wire.port , target: output }
new_links.push(link);
RED.nodes.addLink(link);
});
});
for (i=0;i<removedLinks.length;i++) {
RED.nodes.removeLink(removedLinks[i]);
}
for (i=0;i<selection.nodes.length;i++) {
selection.nodes[i].z = subflow.id;
}
RED.history.push({
t:'createSubflow',
nodes:[subflowInstance.id],
links:new_links,
subflow: subflow,
activeWorkspace: RED.view.getWorkspace(),
removedLinks: removedLinks,
dirty:RED.view.dirty()
});
RED.view.dirty(true);
RED.view.redraw();
}
return {
init: init,
createSubflow: createSubflow,
convertToSubflow: convertToSubflow
}
})();

View File

@ -133,11 +133,33 @@ RED.sidebar.info = (function() {
$("#tab-info").html(table); $("#tab-info").html(table);
} }
function clear() {
$("#tab-info").html("");
}
RED.view.on("selection-changed",function(selection) {
if (selection.nodes) {
if (selection.nodes.length == 1) {
var node = selection.nodes[0];
if (node.type === "subflow" && node.direction) {
refresh(RED.nodes.subflow(node.z));
} else {
refresh(node);
}
}
} else {
var subflow = RED.nodes.subflow(RED.view.getWorkspace());
if (subflow) {
refresh(subflow);
} else {
clear();
}
}
});
return { return {
show: show, show: show,
refresh:refresh, refresh:refresh,
clear: function() { clear: clear
$("#tab-info").html("");
}
} }
})(); })();

View File

@ -1,5 +1,5 @@
/** /**
* Copyright 2013, 2014 IBM Corp. * Copyright 2013, 2015 IBM Corp.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -231,60 +231,6 @@ RED.view = (function() {
var drag_line = vis.append("svg:path").attr("class", "drag_line"); var drag_line = vis.append("svg:path").attr("class", "drag_line");
$("#workspace-subflow-edit").click(function(event) {
showSubflowDialog(activeSubflow.id);
event.preventDefault();
});
$("#workspace-subflow-add-input").click(function(event) {
event.preventDefault();
if ($(this).hasClass("disabled")) {
return;
}
addSubflowInput(activeSubflow.id);
});
$("#workspace-subflow-add-output").click(function(event) {
event.preventDefault();
if ($(this).hasClass("disabled")) {
return;
}
addSubflowOutput(activeSubflow.id);
});
$("#workspace-subflow-delete").click(function(event) {
event.preventDefault();
var removedNodes = [];
var removedLinks = [];
var startDirty = RED.view.dirty();
RED.nodes.eachNode(function(n) {
if (n.type == "subflow:"+activeSubflow.id) {
removedNodes.push(n);
}
if (n.z == activeSubflow.id) {
removedNodes.push(n);
}
});
for (var i=0;i<removedNodes.length;i++) {
var rmlinks = RED.nodes.remove(removedNodes[i].id);
removedLinks = removedLinks.concat(rmlinks);
}
RED.nodes.removeSubflow(activeSubflow);
RED.history.push({
t:'delete',
nodes:removedNodes,
links:removedLinks,
subflow: activeSubflow,
dirty:startDirty
});
RED.view.removeWorkspace(activeSubflow);
RED.view.dirty(true);
RED.view.redraw();
});
var workspace_tabs = RED.tabs.create({ var workspace_tabs = RED.tabs.create({
id: "workspace-tabs", id: "workspace-tabs",
onchange: function(tab) { onchange: function(tab) {
@ -329,13 +275,14 @@ RED.view = (function() {
RED.nodes.eachNode(function(n) { RED.nodes.eachNode(function(n) {
n.dirty = true; n.dirty = true;
}); });
updateSelection();
redraw(); redraw();
}, },
ondblclick: function(tab) { ondblclick: function(tab) {
if (tab.type != "subflow") { if (tab.type != "subflow") {
showRenameWorkspaceDialog(tab.id); showRenameWorkspaceDialog(tab.id);
} else { } else {
showSubflowDialog(tab.id); RED.editor.editSubflow(RED.nodes.subflow(tab.id));
} }
}, },
onadd: function(tab) { onadd: function(tab) {
@ -766,17 +713,6 @@ RED.view = (function() {
} }
function updateSelection() { function updateSelection() {
if (moving_set.length === 0) {
RED.menu.setDisabled("btn-export-menu",true);
RED.menu.setDisabled("btn-export-clipboard",true);
RED.menu.setDisabled("btn-export-library",true);
RED.menu.setDisabled("btn-convert-subflow",true);
} else {
RED.menu.setDisabled("btn-export-menu",false);
RED.menu.setDisabled("btn-export-clipboard",false);
RED.menu.setDisabled("btn-export-library",false);
RED.menu.setDisabled("btn-convert-subflow",false);
}
if (moving_set.length === 0 && selected_link == null) { if (moving_set.length === 0 && selected_link == null) {
RED.keyboard.remove(/* backspace */ 8); RED.keyboard.remove(/* backspace */ 8);
RED.keyboard.remove(/* delete */ 46); RED.keyboard.remove(/* delete */ 46);
@ -799,6 +735,7 @@ RED.view = (function() {
RED.keyboard.add(/* left */ 37, function() { if(d3.event.shiftKey){moveSelection(-20, 0)}else{moveSelection(-1, 0);}d3.event.preventDefault();},endKeyboardMove); RED.keyboard.add(/* left */ 37, function() { if(d3.event.shiftKey){moveSelection(-20, 0)}else{moveSelection(-1, 0);}d3.event.preventDefault();},endKeyboardMove);
RED.keyboard.add(/* right*/ 39, function() { if(d3.event.shiftKey){moveSelection( 20, 0)}else{moveSelection( 1, 0);}d3.event.preventDefault();},endKeyboardMove); RED.keyboard.add(/* right*/ 39, function() { if(d3.event.shiftKey){moveSelection( 20, 0)}else{moveSelection( 1, 0);}d3.event.preventDefault();},endKeyboardMove);
} }
var selection = {}; var selection = {};
if (moving_set.length > 0) { if (moving_set.length > 0) {
@ -809,21 +746,8 @@ RED.view = (function() {
} }
eventHandler.emit("selection-changed",selection); eventHandler.emit("selection-changed",selection);
if (moving_set.length == 1) {
if (moving_set[0].n.type === "subflow" && moving_set[0].n.direction) {
RED.sidebar.info.refresh(RED.nodes.subflow(moving_set[0].n.z));
} else {
RED.sidebar.info.refresh(moving_set[0].n);
}
} else if (moving_set.length === 0 && activeSubflow) {
RED.sidebar.info.refresh(activeSubflow);
} else {
RED.sidebar.info.clear();
} }
}
function endKeyboardMove() { function endKeyboardMove() {
var ns = []; var ns = [];
for (var i=0;i<moving_set.length;i++) { for (var i=0;i<moving_set.length;i++) {
@ -1895,108 +1819,6 @@ RED.view = (function() {
$( "#node-dialog-rename-workspace" ).dialog("open"); $( "#node-dialog-rename-workspace" ).dialog("open");
} }
function showSubflowDialog(id) {
RED.editor.editSubflow(RED.nodes.subflow(id));
}
function findAvailableSubflowIOPosition(subflow) {
var pos = {x:70,y:70};
for (var i=0;i<subflow.out.length+subflow.in.length;i++) {
var port;
if (i < subflow.out.length) {
port = subflow.out[i];
} else {
port = subflow.in[i-subflow.out.length];
}
if (port.x == pos.x && port.y == pos.y) {
pos.x += 55;
i=0;
}
}
return pos;
}
function addSubflowInput(id) {
var subflow = RED.nodes.subflow(id);
var position = findAvailableSubflowIOPosition(subflow);
var newInput = {
type:"subflow",
direction:"in",
z:subflow.id,
i:subflow.in.length,
x:position.x,
y:position.y,
id:RED.nodes.id()
};
var oldInCount = subflow.in.length;
subflow.in.push(newInput);
subflow.dirty = true;
var wasDirty = RED.view.dirty();
var wasChanged = subflow.changed;
subflow.changed = true;
RED.nodes.eachNode(function(n) {
if (n.type == "subflow:"+subflow.id) {
n.changed = true;
n.inputs = subflow.in.length;
RED.editor.updateNodeProperties(n);
}
});
var historyEvent = {
t:'edit',
node:subflow,
dirty:wasDirty,
changed:wasChanged,
subflow: {
inputCount: oldInCount
}
};
RED.history.push(historyEvent);
$("#workspace-subflow-add-input").toggleClass("disabled",true);
updateSelection();
RED.view.redraw();
}
function addSubflowOutput(id) {
var subflow = RED.nodes.subflow(id);
var position = findAvailableSubflowIOPosition(subflow);
var newOutput = {
type:"subflow",
direction:"out",
z:subflow.id,
i:subflow.out.length,
x:position.x,
y:position.y,
id:RED.nodes.id()
};
var oldOutCount = subflow.out.length;
subflow.out.push(newOutput);
subflow.dirty = true;
var wasDirty = RED.view.dirty();
var wasChanged = subflow.changed;
subflow.changed = true;
RED.nodes.eachNode(function(n) {
if (n.type == "subflow:"+subflow.id) {
n.changed = true;
n.outputs = subflow.out.length;
RED.editor.updateNodeProperties(n);
}
});
var historyEvent = {
t:'edit',
node:subflow,
dirty:wasDirty,
changed:wasChanged,
subflow: {
outputCount: oldOutCount
}
};
RED.history.push(historyEvent);
updateSelection();
RED.view.redraw();
}
$("#node-dialog-rename-workspace form" ).submit(function(e) { e.preventDefault();}); $("#node-dialog-rename-workspace form" ).submit(function(e) { e.preventDefault();});
$( "#node-dialog-rename-workspace" ).dialog({ $( "#node-dialog-rename-workspace" ).dialog({
modal: true, modal: true,
@ -2178,6 +2000,7 @@ RED.view = (function() {
}, },
calculateTextWidth: calculateTextWidth, calculateTextWidth: calculateTextWidth,
select: function(selection) { select: function(selection) {
if (typeof selection !== "undefined") {
clearSelection(); clearSelection();
if (typeof selection == "string") { if (typeof selection == "string") {
var selectedNode = RED.nodes.node(selection); var selectedNode = RED.nodes.node(selection);
@ -2187,9 +2010,20 @@ RED.view = (function() {
moving_set = [{n:selectedNode}]; moving_set = [{n:selectedNode}];
} }
} }
}
updateSelection(); updateSelection();
redraw(); redraw();
}, },
selection: function() {
var selection = {};
if (moving_set.length > 0) {
selection.nodes = moving_set.map(function(n) { return n.n;});
}
if (selected_link != null) {
selection.link = selected_link;
}
return selection;
},
//TODO: should these move to an import/export module? //TODO: should these move to an import/export module?
showImportNodesDialog: showImportNodesDialog, showImportNodesDialog: showImportNodesDialog,
showExportNodesDialog: showExportNodesDialog, showExportNodesDialog: showExportNodesDialog,
@ -2209,208 +2043,6 @@ RED.view = (function() {
workspace_tabs.resize(); workspace_tabs.resize();
} }
workspace_tabs.activateTab(id); workspace_tabs.activateTab(id);
},
createSubflow: function() {
var lastIndex = 0;
RED.nodes.eachSubflow(function(sf) {
var m = (new RegExp("^Subflow (\\d+)$")).exec(sf.name);
if (m) {
lastIndex = Math.max(lastIndex,m[1]);
}
});
var name = "Subflow "+(lastIndex+1);
var subflowId = RED.nodes.id();
var subflow = {
type:"subflow",
id:subflowId,
name:name,
in: [],
out: []
};
RED.nodes.addSubflow(subflow);
RED.history.push({
t:'createSubflow',
subflow: subflow,
dirty:RED.view.dirty()
});
RED.view.showSubflow(subflowId);
},
convertToSubflow: function() {
if (moving_set.length === 0) {
RED.notify("<strong>Cannot create subflow</strong>: no nodes selected","error");
return;
}
var i;
var nodes = {};
var new_links = [];
var removedLinks = [];
var candidateInputs = [];
var candidateOutputs = [];
var boundingBox = [moving_set[0].n.x,moving_set[0].n.y,moving_set[0].n.x,moving_set[0].n.y];
for (i=0;i<moving_set.length;i++) {
var n = moving_set[i];
nodes[n.n.id] = {n:n.n,outputs:{}};
boundingBox = [
Math.min(boundingBox[0],n.n.x),
Math.min(boundingBox[1],n.n.y),
Math.max(boundingBox[2],n.n.x),
Math.max(boundingBox[3],n.n.y)
]
}
var center = [(boundingBox[2]+boundingBox[0]) / 2,(boundingBox[3]+boundingBox[1]) / 2];
RED.nodes.eachLink(function(link) {
if (nodes[link.source.id] && nodes[link.target.id]) {
// A link wholely within the selection
}
if (nodes[link.source.id] && !nodes[link.target.id]) {
// An outbound link from the selection
candidateOutputs.push(link);
removedLinks.push(link);
}
if (!nodes[link.source.id] && nodes[link.target.id]) {
// An inbound link
candidateInputs.push(link);
removedLinks.push(link);
}
});
var outputs = {};
candidateOutputs = candidateOutputs.filter(function(v) {
if (outputs[v.source.id+":"+v.sourcePort]) {
outputs[v.source.id+":"+v.sourcePort].targets.push(v.target);
return false;
}
v.targets = [];
v.targets.push(v.target);
outputs[v.source.id+":"+v.sourcePort] = v;
return true;
});
candidateOutputs.sort(function(a,b) { return a.source.y-b.source.y});
if (candidateInputs.length > 1) {
RED.notify("<strong>Cannot create subflow</strong>: multiple inputs to selection","error");
return;
}
//if (candidateInputs.length == 0) {
// RED.notify("<strong>Cannot create subflow</strong>: no input to selection","error");
// return;
//}
var lastIndex = 0;
RED.nodes.eachSubflow(function(sf) {
var m = (new RegExp("^Subflow (\\d+)$")).exec(sf.name);
if (m) {
lastIndex = Math.max(lastIndex,m[1]);
}
});
var name = "Subflow "+(lastIndex+1);
var subflowId = RED.nodes.id();
var subflow = {
type:"subflow",
id:subflowId,
name:name,
in: candidateInputs.map(function(v,i) { var index = i; return {
type:"subflow",
direction:"in",
x:v.target.x-(v.target.w/2)-80,
y:v.target.y,
z:subflowId,
i:index,
id:RED.nodes.id(),
wires:[{id:v.target.id}]
}}),
out: candidateOutputs.map(function(v,i) { var index = i; return {
type:"subflow",
direction:"in",
x:v.source.x+(v.source.w/2)+80,
y:v.source.y,
z:subflowId,
i:index,
id:RED.nodes.id(),
wires:[{id:v.source.id,port:v.sourcePort}]
}})
};
RED.nodes.addSubflow(subflow);
var subflowInstance = {
id:RED.nodes.id(),
type:"subflow:"+subflow.id,
x: center[0],
y: center[1],
z: activeWorkspace,
inputs: subflow.in.length,
outputs: subflow.out.length,
h: Math.max(node_height,(subflow.out.length||0) * 15),
changed:true
}
subflowInstance._def = RED.nodes.getType(subflowInstance.type);
RED.editor.validateNode(subflowInstance);
RED.nodes.add(subflowInstance);
candidateInputs.forEach(function(l) {
var link = {source:l.source, sourcePort:l.sourcePort, target: subflowInstance};
new_links.push(link);
RED.nodes.addLink(link);
});
candidateOutputs.forEach(function(output,i) {
output.targets.forEach(function(target) {
var link = {source:subflowInstance, sourcePort:i, target: target};
new_links.push(link);
RED.nodes.addLink(link);
});
});
subflow.in.forEach(function(input) {
input.wires.forEach(function(wire) {
var link = {source: input, sourcePort: 0, target: RED.nodes.node(wire.id) }
new_links.push(link);
RED.nodes.addLink(link);
});
});
subflow.out.forEach(function(output,i) {
output.wires.forEach(function(wire) {
var link = {source: RED.nodes.node(wire.id), sourcePort: wire.port , target: output }
new_links.push(link);
RED.nodes.addLink(link);
});
});
for (i=0;i<removedLinks.length;i++) {
RED.nodes.removeLink(removedLinks[i]);
}
for (i=0;i<moving_set.length;i++) {
moving_set[i].n.z = subflow.id;
}
RED.history.push({
t:'createSubflow',
nodes:[subflowInstance.id],
links:new_links,
subflow: subflow,
activeWorkspace: activeWorkspace,
removedLinks: removedLinks,
dirty:RED.view.dirty()
});
setDirty(true);
redraw();
} }
}; };
})(); })();