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:
parent
e26ea14104
commit
ab3e64271b
@ -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>
|
||||||
|
@ -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.
|
||||||
|
@ -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();
|
||||||
|
@ -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
392
public/red/ui/subflow.js
Normal 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
|
||||||
|
}
|
||||||
|
})();
|
@ -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("");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
@ -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,18 +2000,30 @@ RED.view = (function() {
|
|||||||
},
|
},
|
||||||
calculateTextWidth: calculateTextWidth,
|
calculateTextWidth: calculateTextWidth,
|
||||||
select: function(selection) {
|
select: function(selection) {
|
||||||
clearSelection();
|
if (typeof selection !== "undefined") {
|
||||||
if (typeof selection == "string") {
|
clearSelection();
|
||||||
var selectedNode = RED.nodes.node(selection);
|
if (typeof selection == "string") {
|
||||||
if (selectedNode) {
|
var selectedNode = RED.nodes.node(selection);
|
||||||
selectedNode.selected = true;
|
if (selectedNode) {
|
||||||
selectedNode.dirty = true;
|
selectedNode.selected = true;
|
||||||
moving_set = [{n:selectedNode}];
|
selectedNode.dirty = true;
|
||||||
|
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();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
Loading…
Reference in New Issue
Block a user