mirror of
https://github.com/node-red/node-red.git
synced 2023-10-10 13:36:53 +02:00
Clean up subflow editor
- new appearance of subflow tabs - input/output buttons now counters - allow multiple input wires to the same node when converting to subflow - ensure edit history is propagated properly to instance nodes
This commit is contained in:
parent
d1a5395727
commit
fdbf079896
@ -15,7 +15,7 @@
|
||||
**/
|
||||
RED.history = (function() {
|
||||
var undo_history = [];
|
||||
|
||||
|
||||
return {
|
||||
//TODO: this function is a placeholder until there is a 'save' event that can be listened to
|
||||
markAllDirty: function() {
|
||||
@ -62,6 +62,23 @@ RED.history = (function() {
|
||||
RED.workspaces.remove(ev.subflows[i]);
|
||||
}
|
||||
}
|
||||
if (ev.subflow) {
|
||||
if (ev.subflow.instances) {
|
||||
ev.subflow.instances.forEach(function(n) {
|
||||
var node = RED.nodes.node(n.id);
|
||||
if (node) {
|
||||
node.changed = n.changed;
|
||||
node.dirty = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (ev.subflow.hasOwnProperty('changed')) {
|
||||
var subflow = RED.nodes.subflow(ev.subflow.id);
|
||||
if (subflow) {
|
||||
subflow.changed = ev.subflow.changed;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (ev.t == "delete") {
|
||||
if (ev.workspaces) {
|
||||
for (i=0;i<ev.workspaces.length;i++) {
|
||||
@ -69,8 +86,8 @@ RED.history = (function() {
|
||||
RED.workspaces.add(ev.workspaces[i]);
|
||||
}
|
||||
}
|
||||
if (ev.subflow) {
|
||||
RED.nodes.addSubflow(ev.subflow);
|
||||
if (ev.subflow && ev.subflow.subflow) {
|
||||
RED.nodes.addSubflow(ev.subflow.subflow);
|
||||
}
|
||||
var subflow;
|
||||
if (ev.subflowInputs && ev.subflowInputs.length > 0) {
|
||||
@ -97,9 +114,17 @@ RED.history = (function() {
|
||||
});
|
||||
}
|
||||
}
|
||||
if (ev.subflow && ev.subflow.hasOwnProperty('instances')) {
|
||||
ev.subflow.instances.forEach(function(n) {
|
||||
var node = RED.nodes.node(n.id);
|
||||
if (node) {
|
||||
node.changed = n.changed;
|
||||
node.dirty = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (subflow) {
|
||||
RED.nodes.filterNodes({type:"subflow:"+subflow.id}).forEach(function(n) {
|
||||
n.changed = true;
|
||||
n.inputs = subflow.in.length;
|
||||
n.outputs = subflow.out.length;
|
||||
while (n.outputs > n.ports.length) {
|
||||
@ -148,13 +173,21 @@ RED.history = (function() {
|
||||
ev.node.out = ev.node.out.concat(ev.subflow.outputs);
|
||||
}
|
||||
}
|
||||
if (ev.subflow.hasOwnProperty('instances')) {
|
||||
ev.subflow.instances.forEach(function(n) {
|
||||
var node = RED.nodes.node(n.id);
|
||||
if (node) {
|
||||
node.changed = n.changed;
|
||||
node.dirty = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
RED.nodes.filterNodes({type:"subflow:"+ev.node.id}).forEach(function(n) {
|
||||
n.changed = ev.changed;
|
||||
n.inputs = ev.node.in.length;
|
||||
n.outputs = ev.node.out.length;
|
||||
RED.editor.updateNodeProperties(n);
|
||||
});
|
||||
|
||||
|
||||
RED.palette.refresh();
|
||||
} else {
|
||||
RED.editor.updateNodeProperties(ev.node);
|
||||
@ -182,10 +215,10 @@ RED.history = (function() {
|
||||
RED.nodes.removeLink(ev.links[i]);
|
||||
}
|
||||
}
|
||||
|
||||
RED.nodes.removeSubflow(ev.subflow);
|
||||
RED.workspaces.remove(ev.subflow);
|
||||
|
||||
|
||||
RED.nodes.removeSubflow(ev.subflow.subflow);
|
||||
RED.workspaces.remove(ev.subflow.subflow);
|
||||
|
||||
if (ev.removedLinks) {
|
||||
for (i=0;i<ev.removedLinks.length;i++) {
|
||||
RED.nodes.addLink(ev.removedLinks[i]);
|
||||
@ -199,7 +232,6 @@ RED.history = (function() {
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
RED.nodes.dirty(ev.dirty);
|
||||
RED.view.redraw(true);
|
||||
RED.palette.refresh();
|
||||
|
@ -33,7 +33,6 @@ RED.editor = (function() {
|
||||
var subflow;
|
||||
var isValid;
|
||||
var hasChanged;
|
||||
|
||||
if (node.type.indexOf("subflow:")===0) {
|
||||
subflow = RED.nodes.subflow(node.type.substring(8));
|
||||
isValid = subflow.valid;
|
||||
@ -43,7 +42,7 @@ RED.editor = (function() {
|
||||
hasChanged = subflow.changed;
|
||||
}
|
||||
node.valid = isValid;
|
||||
node.changed = hasChanged;
|
||||
node.changed = node.changed || hasChanged;
|
||||
} else if (node._def) {
|
||||
node.valid = validateNodeProperties(node, node._def.defaults, node);
|
||||
if (node._def._creds) {
|
||||
@ -65,7 +64,7 @@ RED.editor = (function() {
|
||||
var modifiedTabs = {};
|
||||
for (i=0;i<subflowInstances.length;i++) {
|
||||
subflowInstances[i].valid = node.valid;
|
||||
subflowInstances[i].changed = node.changed;
|
||||
subflowInstances[i].changed = subflowInstances[i].changed || node.changed;
|
||||
subflowInstances[i].dirty = true;
|
||||
modifiedTabs[subflowInstances[i].z] = true;
|
||||
}
|
||||
@ -270,7 +269,36 @@ RED.editor = (function() {
|
||||
var wasChanged = editing_node.changed;
|
||||
editing_node.changed = true;
|
||||
RED.nodes.dirty(true);
|
||||
RED.history.push({t:'edit',node:editing_node,changes:changes,links:removedLinks,dirty:wasDirty,changed:wasChanged});
|
||||
|
||||
var activeSubflow = RED.nodes.subflow(RED.workspaces.active());
|
||||
if (activeSubflow) {
|
||||
var subflowInstances = [];
|
||||
RED.nodes.eachNode(function(n) {
|
||||
if (n.type == "subflow:"+RED.workspaces.active()) {
|
||||
subflowInstances.push({
|
||||
id:n.id,
|
||||
changed:n.changed
|
||||
});
|
||||
n.changed = true;
|
||||
n.dirty = true;
|
||||
updateNodeProperties(n);
|
||||
}
|
||||
});
|
||||
}
|
||||
var historyEvent = {
|
||||
t:'edit',
|
||||
node:editing_node,
|
||||
changes:changes,
|
||||
links:removedLinks,
|
||||
dirty:wasDirty,
|
||||
changed:wasChanged
|
||||
};
|
||||
if (subflowInstances) {
|
||||
historyEvent.subflow = {
|
||||
instances:subflowInstances
|
||||
}
|
||||
}
|
||||
RED.history.push(historyEvent);
|
||||
}
|
||||
editing_node.dirty = true;
|
||||
validateNode(editing_node);
|
||||
@ -900,15 +928,21 @@ RED.editor = (function() {
|
||||
changes['name'] = editing_node.name;
|
||||
editing_node.name = newName;
|
||||
changed = true;
|
||||
$("#menu-item-workspace-menu-"+editing_node.id.replace(".","-")).text(RED._("subflow.tabLabel",{name:newName}));
|
||||
$("#menu-item-workspace-menu-"+editing_node.id.replace(".","-")).text(newName);
|
||||
}
|
||||
|
||||
RED.palette.refresh();
|
||||
|
||||
if (changed) {
|
||||
var subflowInstances = [];
|
||||
RED.nodes.eachNode(function(n) {
|
||||
if (n.type == "subflow:"+editing_node.id) {
|
||||
subflowInstances.push({
|
||||
id:n.id,
|
||||
changed:n.changed
|
||||
})
|
||||
n.changed = true;
|
||||
n.dirty = true;
|
||||
updateNodeProperties(n);
|
||||
}
|
||||
});
|
||||
@ -920,7 +954,10 @@ RED.editor = (function() {
|
||||
node:editing_node,
|
||||
changes:changes,
|
||||
dirty:wasDirty,
|
||||
changed:wasChanged
|
||||
changed:wasChanged,
|
||||
subflow: {
|
||||
instances:subflowInstances
|
||||
}
|
||||
};
|
||||
|
||||
RED.history.push(historyEvent);
|
||||
|
@ -21,8 +21,11 @@ RED.subflow = (function() {
|
||||
return RED.nodes.subflow(RED.workspaces.active());
|
||||
}
|
||||
|
||||
function findAvailableSubflowIOPosition(subflow) {
|
||||
var pos = {x:70,y:70};
|
||||
function findAvailableSubflowIOPosition(subflow,isInput) {
|
||||
var pos = {x:50,y:30};
|
||||
if (!isInput) {
|
||||
pos.x += 110;
|
||||
}
|
||||
for (var i=0;i<subflow.out.length+subflow.in.length;i++) {
|
||||
var port;
|
||||
if (i < subflow.out.length) {
|
||||
@ -40,7 +43,10 @@ RED.subflow = (function() {
|
||||
|
||||
function addSubflowInput() {
|
||||
var subflow = RED.nodes.subflow(RED.workspaces.active());
|
||||
var position = findAvailableSubflowIOPosition(subflow);
|
||||
if (subflow.in.length === 1) {
|
||||
return;
|
||||
}
|
||||
var position = findAvailableSubflowIOPosition(subflow,true);
|
||||
var newInput = {
|
||||
type:"subflow",
|
||||
direction:"in",
|
||||
@ -56,31 +62,50 @@ RED.subflow = (function() {
|
||||
var wasDirty = RED.nodes.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 result = refresh(true);
|
||||
var historyEvent = {
|
||||
t:'edit',
|
||||
node:subflow,
|
||||
dirty:wasDirty,
|
||||
changed:wasChanged,
|
||||
subflow: {
|
||||
inputCount: oldInCount
|
||||
inputCount: oldInCount,
|
||||
instances: result.instances
|
||||
}
|
||||
};
|
||||
RED.history.push(historyEvent);
|
||||
$("#workspace-subflow-add-input").toggleClass("disabled",true);
|
||||
RED.view.select();
|
||||
RED.nodes.dirty(true);
|
||||
RED.view.redraw();
|
||||
$("#workspace-subflow-input-add").addClass("active");
|
||||
$("#workspace-subflow-input-remove").removeClass("active");
|
||||
}
|
||||
|
||||
function removeSubflowInput() {
|
||||
var activeSubflow = RED.nodes.subflow(RED.workspaces.active());
|
||||
if (activeSubflow.in.length === 0) {
|
||||
return;
|
||||
}
|
||||
var removedInput = activeSubflow.in[0];
|
||||
var removedInputLinks = [];
|
||||
RED.nodes.eachLink(function(l) {
|
||||
if (l.source.type == "subflow" && l.source.z == activeSubflow.id && l.source.i == removedInput.i) {
|
||||
removedInputLinks.push(l);
|
||||
} else if (l.target.type == "subflow:"+activeSubflow.id) {
|
||||
removedInputLinks.push(l);
|
||||
}
|
||||
});
|
||||
removedInputLinks.forEach(function(l) { RED.nodes.removeLink(l)});
|
||||
activeSubflow.in = [];
|
||||
$("#workspace-subflow-input-add").removeClass("active");
|
||||
$("#workspace-subflow-input-remove").addClass("active");
|
||||
activeSubflow.changed = true;
|
||||
return {subflowInputs: [ removedInput ], links:removedInputLinks};
|
||||
}
|
||||
|
||||
function addSubflowOutput(id) {
|
||||
var subflow = RED.nodes.subflow(RED.workspaces.active());
|
||||
var position = findAvailableSubflowIOPosition(subflow);
|
||||
var position = findAvailableSubflowIOPosition(subflow,false);
|
||||
|
||||
var newOutput = {
|
||||
type:"subflow",
|
||||
@ -98,45 +123,186 @@ RED.subflow = (function() {
|
||||
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 result = refresh(true);
|
||||
|
||||
var historyEvent = {
|
||||
t:'edit',
|
||||
node:subflow,
|
||||
dirty:wasDirty,
|
||||
changed:wasChanged,
|
||||
subflow: {
|
||||
outputCount: oldOutCount
|
||||
outputCount: oldOutCount,
|
||||
instances: result.instances
|
||||
}
|
||||
};
|
||||
RED.history.push(historyEvent);
|
||||
RED.view.select();
|
||||
RED.nodes.dirty(true);
|
||||
RED.view.redraw();
|
||||
$("#workspace-subflow-output .spinner-value").html(subflow.out.length);
|
||||
}
|
||||
|
||||
function init() {
|
||||
function removeSubflowOutput(removedSubflowOutputs) {
|
||||
var activeSubflow = RED.nodes.subflow(RED.workspaces.active());
|
||||
if (activeSubflow.out.length === 0) {
|
||||
return;
|
||||
}
|
||||
if (typeof removedSubflowOutputs === "undefined") {
|
||||
removedSubflowOutputs = [activeSubflow.out[activeSubflow.out.length-1]];
|
||||
}
|
||||
var removedLinks = [];
|
||||
removedSubflowOutputs.sort(function(a,b) { return b.i-a.i});
|
||||
for (i=0;i<removedSubflowOutputs.length;i++) {
|
||||
var output = removedSubflowOutputs[i];
|
||||
activeSubflow.out.splice(output.i,1);
|
||||
var subflowRemovedLinks = [];
|
||||
var subflowMovedLinks = [];
|
||||
RED.nodes.eachLink(function(l) {
|
||||
if (l.target.type == "subflow" && l.target.z == activeSubflow.id && l.target.i == output.i) {
|
||||
subflowRemovedLinks.push(l);
|
||||
}
|
||||
if (l.source.type == "subflow:"+activeSubflow.id) {
|
||||
if (l.sourcePort == output.i) {
|
||||
subflowRemovedLinks.push(l);
|
||||
} else if (l.sourcePort > output.i) {
|
||||
subflowMovedLinks.push(l);
|
||||
}
|
||||
}
|
||||
});
|
||||
subflowRemovedLinks.forEach(function(l) { RED.nodes.removeLink(l)});
|
||||
subflowMovedLinks.forEach(function(l) { l.sourcePort--; });
|
||||
|
||||
removedLinks = removedLinks.concat(subflowRemovedLinks);
|
||||
for (var j=output.i;j<activeSubflow.out.length;j++) {
|
||||
activeSubflow.out[j].i--;
|
||||
activeSubflow.out[j].dirty = true;
|
||||
}
|
||||
}
|
||||
activeSubflow.changed = true;
|
||||
|
||||
return {subflowOutputs: removedSubflowOutputs, links: removedLinks}
|
||||
}
|
||||
|
||||
function refresh(markChange) {
|
||||
var activeSubflow = RED.nodes.subflow(RED.workspaces.active());
|
||||
refreshToolbar(activeSubflow);
|
||||
var subflowInstances = [];
|
||||
if (activeSubflow) {
|
||||
RED.nodes.filterNodes({type:"subflow:"+activeSubflow.id}).forEach(function(n) {
|
||||
subflowInstances.push({
|
||||
id: n.id,
|
||||
changed: n.changed
|
||||
});
|
||||
if (markChange) {
|
||||
n.changed = true;
|
||||
}
|
||||
n.inputs = activeSubflow.in.length;
|
||||
n.outputs = activeSubflow.out.length;
|
||||
while (n.outputs < n.ports.length) {
|
||||
n.ports.pop();
|
||||
}
|
||||
n.resize = true;
|
||||
n.dirty = true;
|
||||
RED.editor.updateNodeProperties(n);
|
||||
});
|
||||
RED.editor.validateNode(activeSubflow);
|
||||
return {
|
||||
instances: subflowInstances
|
||||
}
|
||||
}
|
||||
}
|
||||
function refreshToolbar(activeSubflow) {
|
||||
if (activeSubflow) {
|
||||
$("#workspace-subflow-input-add").toggleClass("active", activeSubflow.in.length !== 0);
|
||||
$("#workspace-subflow-input-remove").toggleClass("active",activeSubflow.in.length === 0);
|
||||
|
||||
$("#workspace-subflow-output .spinner-value").html(activeSubflow.out.length);
|
||||
}
|
||||
}
|
||||
|
||||
function showWorkspaceToolbar(activeSubflow) {
|
||||
var toolbar = $("#workspace-toolbar");
|
||||
toolbar.empty();
|
||||
|
||||
$('<a class="button" id="workspace-subflow-edit" href="#" data-i18n="[append]subflow.editSubflowName"><i class="fa fa-pencil"></i></a>').appendTo(toolbar);
|
||||
$('<span style="margin-left: 5px;" data-i18n="subflow.input"></span> '+
|
||||
'<div style="display: inline-block;" class="button-group">'+
|
||||
'<a id="workspace-subflow-input-remove" class="button active" href="#">0</a>'+
|
||||
'<a id="workspace-subflow-input-add" class="button" href="#">1</a>'+
|
||||
'</div>').appendTo(toolbar);
|
||||
|
||||
$('<span style="margin-left: 5px;" data-i18n="subflow.output"></span> <div id="workspace-subflow-output" style="display: inline-block;" class="button-group spinner-group">'+
|
||||
'<a id="workspace-subflow-output-remove" class="button" href="#"><i class="fa fa-minus"></i></a>'+
|
||||
'<div class="spinner-value">3</div>'+
|
||||
'<a id="workspace-subflow-output-add" class="button" href="#"><i class="fa fa-plus"></i></a>'+
|
||||
'</div>').appendTo(toolbar);
|
||||
|
||||
// $('<a class="button disabled" id="workspace-subflow-add-input" href="#" data-i18n="[append]subflow.input"><i class="fa fa-plus"></i> </a>').appendTo(toolbar);
|
||||
// $('<a class="button" id="workspace-subflow-add-output" href="#" data-i18n="[append]subflow.output"><i class="fa fa-plus"></i> </a>').appendTo(toolbar);
|
||||
$('<a class="button" id="workspace-subflow-delete" href="#" data-i18n="[append]subflow.deleteSubflow"><i class="fa fa-trash"></i> </a>').appendTo(toolbar);
|
||||
toolbar.i18n();
|
||||
|
||||
|
||||
$("#workspace-subflow-output-remove").click(function(event) {
|
||||
event.preventDefault();
|
||||
var wasDirty = RED.nodes.dirty();
|
||||
var wasChanged = activeSubflow.changed;
|
||||
var result = removeSubflowOutput();
|
||||
if (result) {
|
||||
var inst = refresh(true);
|
||||
RED.history.push({
|
||||
t:'delete',
|
||||
links:result.links,
|
||||
subflowOutputs: result.subflowOutputs,
|
||||
changed: wasChanged,
|
||||
dirty:wasDirty,
|
||||
subflow: {
|
||||
instances: inst.instances
|
||||
}
|
||||
});
|
||||
|
||||
RED.view.select();
|
||||
RED.nodes.dirty(true);
|
||||
RED.view.redraw(true);
|
||||
}
|
||||
});
|
||||
$("#workspace-subflow-output-add").click(function(event) {
|
||||
event.preventDefault();
|
||||
addSubflowOutput();
|
||||
});
|
||||
|
||||
$("#workspace-subflow-input-add").click(function(event) {
|
||||
event.preventDefault();
|
||||
addSubflowInput();
|
||||
});
|
||||
$("#workspace-subflow-input-remove").click(function(event) {
|
||||
event.preventDefault();
|
||||
var wasDirty = RED.nodes.dirty();
|
||||
var wasChanged = activeSubflow.changed;
|
||||
activeSubflow.changed = true;
|
||||
var result = removeSubflowInput();
|
||||
if (result) {
|
||||
var inst = refresh(true);
|
||||
RED.history.push({
|
||||
t:'delete',
|
||||
links:result.links,
|
||||
changed: wasChanged,
|
||||
subflowInputs: result.subflowInputs,
|
||||
dirty:wasDirty,
|
||||
subflow: {
|
||||
instances: inst.instances
|
||||
}
|
||||
});
|
||||
RED.view.select();
|
||||
RED.nodes.dirty(true);
|
||||
RED.view.redraw(true);
|
||||
}
|
||||
});
|
||||
|
||||
$("#workspace-subflow-edit").click(function(event) {
|
||||
RED.editor.editSubflow(RED.nodes.subflow(RED.workspaces.active()));
|
||||
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();
|
||||
@ -170,7 +336,9 @@ RED.subflow = (function() {
|
||||
t:'delete',
|
||||
nodes:removedNodes,
|
||||
links:removedLinks,
|
||||
subflow: activeSubflow,
|
||||
subflow: {
|
||||
subflow: activeSubflow
|
||||
},
|
||||
dirty:startDirty
|
||||
});
|
||||
|
||||
@ -179,6 +347,26 @@ RED.subflow = (function() {
|
||||
RED.view.redraw();
|
||||
});
|
||||
|
||||
refreshToolbar(activeSubflow);
|
||||
|
||||
$("#chart").css({"margin-top": "40px"});
|
||||
$("#workspace-toolbar").show();
|
||||
}
|
||||
function hideWorkspaceToolbar() {
|
||||
$("#workspace-toolbar").hide().empty();
|
||||
$("#chart").css({"margin-top": "0"});
|
||||
}
|
||||
|
||||
|
||||
function init() {
|
||||
RED.events.on("workspace:change",function(event) {
|
||||
var activeSubflow = RED.nodes.subflow(event.workspace);
|
||||
if (activeSubflow) {
|
||||
showWorkspaceToolbar(activeSubflow);
|
||||
} else {
|
||||
hideWorkspaceToolbar();
|
||||
}
|
||||
});
|
||||
RED.events.on("view:selection-changed",function(selection) {
|
||||
if (!selection.nodes) {
|
||||
RED.menu.setDisabled("menu-item-subflow-convert",true);
|
||||
@ -211,7 +399,9 @@ RED.subflow = (function() {
|
||||
RED.nodes.addSubflow(subflow);
|
||||
RED.history.push({
|
||||
t:'createSubflow',
|
||||
subflow: subflow,
|
||||
subflow: {
|
||||
subflow:subflow
|
||||
},
|
||||
dirty:RED.nodes.dirty()
|
||||
});
|
||||
RED.workspaces.show(subflowId);
|
||||
@ -230,6 +420,8 @@ RED.subflow = (function() {
|
||||
|
||||
var candidateInputs = [];
|
||||
var candidateOutputs = [];
|
||||
var candidateInputNodes = {};
|
||||
|
||||
|
||||
var boundingBox = [selection.nodes[0].x,
|
||||
selection.nodes[0].y,
|
||||
@ -262,6 +454,7 @@ RED.subflow = (function() {
|
||||
if (!nodes[link.source.id] && nodes[link.target.id]) {
|
||||
// An inbound link
|
||||
candidateInputs.push(link);
|
||||
candidateInputNodes[link.target.id] = link.target;
|
||||
removedLinks.push(link);
|
||||
}
|
||||
});
|
||||
@ -279,15 +472,10 @@ RED.subflow = (function() {
|
||||
});
|
||||
candidateOutputs.sort(function(a,b) { return a.source.y-b.source.y});
|
||||
|
||||
if (candidateInputs.length > 1) {
|
||||
if (Object.keys(candidateInputNodes).length > 1) {
|
||||
RED.notify(RED._("subflow.errors.multipleInputsToSelection"),"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) {
|
||||
@ -304,15 +492,15 @@ RED.subflow = (function() {
|
||||
type:"subflow",
|
||||
id:subflowId,
|
||||
name:name,
|
||||
in: candidateInputs.map(function(v,i) { var index = i; return {
|
||||
in: Object.keys(candidateInputNodes).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,
|
||||
x:candidateInputNodes[v].x-(candidateInputNodes[v].w/2)-80,
|
||||
y:candidateInputNodes[v].y,
|
||||
z:subflowId,
|
||||
i:index,
|
||||
id:RED.nodes.id(),
|
||||
wires:[{id:v.target.id}]
|
||||
wires:[{id:candidateInputNodes[v].id}]
|
||||
}}),
|
||||
out: candidateOutputs.map(function(v,i) { var index = i; return {
|
||||
type:"subflow",
|
||||
@ -325,6 +513,7 @@ RED.subflow = (function() {
|
||||
wires:[{id:v.source.id,port:v.sourcePort}]
|
||||
}})
|
||||
};
|
||||
|
||||
RED.nodes.addSubflow(subflow);
|
||||
|
||||
var subflowInstance = {
|
||||
@ -383,7 +572,9 @@ RED.subflow = (function() {
|
||||
t:'createSubflow',
|
||||
nodes:[subflowInstance.id],
|
||||
links:new_links,
|
||||
subflow: subflow,
|
||||
subflow: {
|
||||
subflow: subflow
|
||||
},
|
||||
|
||||
activeWorkspace: RED.workspaces.active(),
|
||||
removedLinks: removedLinks,
|
||||
@ -401,6 +592,9 @@ RED.subflow = (function() {
|
||||
return {
|
||||
init: init,
|
||||
createSubflow: createSubflow,
|
||||
convertToSubflow: convertToSubflow
|
||||
convertToSubflow: convertToSubflow,
|
||||
refresh: refresh,
|
||||
removeInput: removeSubflowInput,
|
||||
removeOutput: removeSubflowOutput
|
||||
}
|
||||
})();
|
||||
|
@ -80,12 +80,15 @@ RED.tabs = (function() {
|
||||
tabs.css({width:currentTabWidth+"%"});
|
||||
if (tabWidth < 50) {
|
||||
ul.find(".red-ui-tab-close").hide();
|
||||
ul.find(".red-ui-tab-icon").hide();
|
||||
} else {
|
||||
ul.find(".red-ui-tab-close").show();
|
||||
ul.find(".red-ui-tab-icon").show();
|
||||
}
|
||||
if (currentActiveTabWidth !== 0) {
|
||||
ul.find("li.red-ui-tab.active").css({"width":options.minimumActiveTabWidth});
|
||||
ul.find("li.red-ui-tab.active .red-ui-tab-close").show();
|
||||
ul.find("li.red-ui-tab.active .red-ui-tab-icon").show();
|
||||
}
|
||||
|
||||
}
|
||||
@ -116,13 +119,16 @@ RED.tabs = (function() {
|
||||
tabs[tab.id] = tab;
|
||||
var li = $("<li/>",{class:"red-ui-tab"}).appendTo(ul);
|
||||
var link = $("<a/>",{href:"#"+tab.id, class:"red-ui-tab-label"}).appendTo(li);
|
||||
link.html(tab.label);
|
||||
if (tab.icon) {
|
||||
$('<img src="'+tab.icon+'" class="red-ui-tab-icon"/>').appendTo(link);
|
||||
}
|
||||
$('<span/>').text(tab.label).appendTo(link);
|
||||
|
||||
link.on("click",onTabClick);
|
||||
link.on("dblclick",onTabDblClick);
|
||||
if (tab.closeable) {
|
||||
var closeLink = $("<a/>",{href:"#",class:"red-ui-tab-close"}).appendTo(li);
|
||||
closeLink.html('<i class="fa fa-times" />');
|
||||
closeLink.append('<i class="fa fa-times" />');
|
||||
|
||||
closeLink.on("click",function(event) {
|
||||
removeTab(tab.id);
|
||||
@ -150,7 +156,7 @@ RED.tabs = (function() {
|
||||
tabs[id].label = label;
|
||||
var tab = ul.find("a[href='#"+id+"']");
|
||||
tab.attr("title",label);
|
||||
tab.text(label);
|
||||
tab.find("span").text(label);
|
||||
updateTabWidths();
|
||||
}
|
||||
|
||||
|
@ -255,9 +255,6 @@ RED.view = (function() {
|
||||
var scrollStartTop = chart.scrollTop();
|
||||
|
||||
activeSubflow = RED.nodes.subflow(event.workspace);
|
||||
if (activeSubflow) {
|
||||
$("#workspace-subflow-add-input").toggleClass("disabled",activeSubflow.in.length > 0);
|
||||
}
|
||||
|
||||
RED.menu.setDisabled("menu-item-workspace-edit", activeSubflow);
|
||||
RED.menu.setDisabled("menu-item-workspace-delete",RED.workspaces.count() == 1 || activeSubflow);
|
||||
@ -351,7 +348,23 @@ RED.view = (function() {
|
||||
|
||||
nn.changed = true;
|
||||
nn.h = Math.max(node_height,(nn.outputs||0) * 15);
|
||||
RED.history.push({t:'add',nodes:[nn.id],dirty:RED.nodes.dirty()});
|
||||
var historyEvent = {
|
||||
t:'add',
|
||||
nodes:[nn.id],
|
||||
dirty:RED.nodes.dirty()
|
||||
}
|
||||
if (activeSubflow) {
|
||||
var subflowRefresh = RED.subflow.refresh(true);
|
||||
if (subflowRefresh) {
|
||||
historyEvent.subflow = {
|
||||
id:activeSubflow.id,
|
||||
changed: activeSubflow.changed,
|
||||
instances: subflowRefresh.instances
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RED.history.push(historyEvent);
|
||||
RED.nodes.add(nn);
|
||||
RED.editor.validateNode(nn);
|
||||
RED.nodes.dirty(true);
|
||||
@ -745,8 +758,10 @@ RED.view = (function() {
|
||||
var removedLinks = [];
|
||||
var removedSubflowOutputs = [];
|
||||
var removedSubflowInputs = [];
|
||||
var subflowInstances = [];
|
||||
|
||||
var startDirty = RED.nodes.dirty();
|
||||
var startChanged = false;
|
||||
if (moving_set.length > 0) {
|
||||
for (var i=0;i<moving_set.length;i++) {
|
||||
var node = moving_set[i].n;
|
||||
@ -769,65 +784,22 @@ RED.view = (function() {
|
||||
}
|
||||
}
|
||||
if (removedSubflowOutputs.length > 0) {
|
||||
removedSubflowOutputs.sort(function(a,b) { return b.i-a.i});
|
||||
for (i=0;i<removedSubflowOutputs.length;i++) {
|
||||
var output = removedSubflowOutputs[i];
|
||||
activeSubflow.out.splice(output.i,1);
|
||||
var subflowRemovedLinks = [];
|
||||
var subflowMovedLinks = [];
|
||||
RED.nodes.eachLink(function(l) {
|
||||
if (l.target.type == "subflow" && l.target.z == activeSubflow.id && l.target.i == output.i) {
|
||||
subflowRemovedLinks.push(l);
|
||||
}
|
||||
if (l.source.type == "subflow:"+activeSubflow.id) {
|
||||
if (l.sourcePort == output.i) {
|
||||
subflowRemovedLinks.push(l);
|
||||
} else if (l.sourcePort > output.i) {
|
||||
subflowMovedLinks.push(l);
|
||||
}
|
||||
}
|
||||
});
|
||||
subflowRemovedLinks.forEach(function(l) { RED.nodes.removeLink(l)});
|
||||
subflowMovedLinks.forEach(function(l) { l.sourcePort--; });
|
||||
|
||||
removedLinks = removedLinks.concat(subflowRemovedLinks);
|
||||
for (var j=output.i;j<activeSubflow.out.length;j++) {
|
||||
activeSubflow.out[j].i--;
|
||||
activeSubflow.out[j].dirty = true;
|
||||
}
|
||||
var result = RED.subflow.removeOutput(removedSubflowOutputs);
|
||||
if (result) {
|
||||
removedLinks = removedLinks.concat(result.links);
|
||||
}
|
||||
}
|
||||
// Assume 0/1 inputs
|
||||
if (removedSubflowInputs.length == 1) {
|
||||
var input = removedSubflowInputs[0];
|
||||
var subflowRemovedInputLinks = [];
|
||||
RED.nodes.eachLink(function(l) {
|
||||
if (l.source.type == "subflow" && l.source.z == activeSubflow.id && l.source.i == input.i) {
|
||||
subflowRemovedInputLinks.push(l);
|
||||
} else if (l.target.type == "subflow:"+activeSubflow.id) {
|
||||
subflowRemovedInputLinks.push(l);
|
||||
}
|
||||
});
|
||||
subflowRemovedInputLinks.forEach(function(l) { RED.nodes.removeLink(l)});
|
||||
removedLinks = removedLinks.concat(subflowRemovedInputLinks);
|
||||
activeSubflow.in = [];
|
||||
$("#workspace-subflow-add-input").toggleClass("disabled",false);
|
||||
var result = RED.subflow.removeInput();
|
||||
if (result) {
|
||||
removedLinks = removedLinks.concat(result.links);
|
||||
}
|
||||
}
|
||||
|
||||
if (activeSubflow) {
|
||||
RED.nodes.filterNodes({type:"subflow:"+activeSubflow.id}).forEach(function(n) {
|
||||
n.changed = true;
|
||||
n.inputs = activeSubflow.in.length;
|
||||
n.outputs = activeSubflow.out.length;
|
||||
while (n.outputs < n.ports.length) {
|
||||
n.ports.pop();
|
||||
}
|
||||
n.resize = true;
|
||||
n.dirty = true;
|
||||
});
|
||||
RED.editor.validateNode(activeSubflow);
|
||||
var instances = RED.subflow.refresh(true);
|
||||
if (instances) {
|
||||
subflowInstances = instances.instances;
|
||||
}
|
||||
|
||||
moving_set = [];
|
||||
if (removedNodes.length > 0 || removedSubflowOutputs.length > 0 || removedSubflowInputs.length > 0) {
|
||||
RED.nodes.dirty(true);
|
||||
@ -838,7 +810,18 @@ RED.view = (function() {
|
||||
removedLinks.push(selected_link);
|
||||
RED.nodes.dirty(true);
|
||||
}
|
||||
RED.history.push({t:'delete',nodes:removedNodes,links:removedLinks,subflowOutputs:removedSubflowOutputs,subflowInputs:removedSubflowInputs,dirty:startDirty});
|
||||
var historyEvent = {
|
||||
t:'delete',
|
||||
nodes:removedNodes,
|
||||
links:removedLinks,
|
||||
subflowOutputs:removedSubflowOutputs,
|
||||
subflowInputs:removedSubflowInputs,
|
||||
subflow: {
|
||||
instances: subflowInstances
|
||||
},
|
||||
dirty:startDirty
|
||||
};
|
||||
RED.history.push(historyEvent);
|
||||
|
||||
selected_link = null;
|
||||
updateActiveNodes();
|
||||
@ -945,7 +928,22 @@ RED.view = (function() {
|
||||
if (!existingLink) {
|
||||
var link = {source: src, sourcePort:src_port, target: dst};
|
||||
RED.nodes.addLink(link);
|
||||
RED.history.push({t:'add',links:[link],dirty:RED.nodes.dirty()});
|
||||
var historyEvent = {
|
||||
t:'add',
|
||||
links:[link],
|
||||
dirty:RED.nodes.dirty()
|
||||
};
|
||||
if (activeSubflow) {
|
||||
var subflowRefresh = RED.subflow.refresh(true);
|
||||
if (subflowRefresh) {
|
||||
historyEvent.subflow = {
|
||||
id:activeSubflow.id,
|
||||
changed: activeSubflow.changed,
|
||||
instances: subflowRefresh.instances
|
||||
}
|
||||
}
|
||||
}
|
||||
RED.history.push(historyEvent);
|
||||
updateActiveNodes();
|
||||
RED.nodes.dirty(true);
|
||||
} else {
|
||||
@ -1695,6 +1693,10 @@ RED.view = (function() {
|
||||
*/
|
||||
function importNodes(newNodesStr,touchImport) {
|
||||
try {
|
||||
var activeSubflowChanged;
|
||||
if (activeSubflow) {
|
||||
activeSubflowChanged = activeSubflow.changed;
|
||||
}
|
||||
var result = RED.nodes.import(newNodesStr,true);
|
||||
if (result) {
|
||||
var new_nodes = result[0];
|
||||
@ -1752,14 +1754,25 @@ RED.view = (function() {
|
||||
moving_set = new_ms;
|
||||
}
|
||||
|
||||
RED.history.push({
|
||||
var historyEvent = {
|
||||
t:'add',
|
||||
nodes:new_node_ids,
|
||||
links:new_links,
|
||||
workspaces:new_workspaces,
|
||||
subflows:new_subflows,
|
||||
dirty:RED.nodes.dirty()
|
||||
});
|
||||
};
|
||||
if (activeSubflow) {
|
||||
var subflowRefresh = RED.subflow.refresh(true);
|
||||
if (subflowRefresh) {
|
||||
historyEvent.subflow = {
|
||||
id:activeSubflow.id,
|
||||
changed: activeSubflowChanged,
|
||||
instances: subflowRefresh.instances
|
||||
}
|
||||
}
|
||||
}
|
||||
RED.history.push(historyEvent);
|
||||
|
||||
updateActiveNodes();
|
||||
redraw();
|
||||
|
@ -84,13 +84,6 @@ RED.workspaces = (function() {
|
||||
workspace_tabs = RED.tabs.create({
|
||||
id: "workspace-tabs",
|
||||
onchange: function(tab) {
|
||||
if (tab.type == "subflow") {
|
||||
$("#chart").css({"margin-top": "40px"});
|
||||
$("#workspace-toolbar").show();
|
||||
} else {
|
||||
$("#workspace-toolbar").hide();
|
||||
$("#chart").css({"margin-top": "0"});
|
||||
}
|
||||
var event = {
|
||||
old: activeWorkspace
|
||||
}
|
||||
@ -242,7 +235,7 @@ RED.workspaces = (function() {
|
||||
if (!workspace_tabs.contains(id)) {
|
||||
var sf = RED.nodes.subflow(id);
|
||||
if (sf) {
|
||||
addWorkspace({type:"subflow",id:id,label:RED._("subflow.tabLabel",{name:sf.name}), closeable: true});
|
||||
addWorkspace({type:"subflow",id:id,icon:"red/images/subflow_tab.png",label:sf.name, closeable: true});
|
||||
}
|
||||
}
|
||||
workspace_tabs.activateTab(id);
|
||||
@ -250,7 +243,7 @@ RED.workspaces = (function() {
|
||||
refresh: function() {
|
||||
RED.nodes.eachSubflow(function(sf) {
|
||||
if (workspace_tabs.contains(sf.id)) {
|
||||
workspace_tabs.renameTab(sf.id,RED._("subflow.tabLabel",{name:sf.name}));
|
||||
workspace_tabs.renameTab(sf.id,sf.name);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
@ -47,7 +47,7 @@ span.logo {
|
||||
font-size: 30px;
|
||||
line-height: 30px;
|
||||
text-decoration: none;
|
||||
|
||||
|
||||
span {
|
||||
vertical-align: middle;
|
||||
font-size: 16px !important;
|
||||
@ -55,14 +55,14 @@ span.logo {
|
||||
img {
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
.header-toolbar {
|
||||
@ -70,13 +70,13 @@ span.logo {
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
float: right;
|
||||
|
||||
|
||||
> li {
|
||||
display: inline-block;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
position: relative;
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -97,19 +97,19 @@ span.logo {
|
||||
vertical-align: middle;
|
||||
border-left: 2px solid #000;
|
||||
border-right: 2px solid #000;
|
||||
|
||||
|
||||
&:hover {
|
||||
border-color: $headerMenuItemHover;
|
||||
}
|
||||
}
|
||||
|
||||
.button-group {
|
||||
#header .button-group {
|
||||
display: inline-block;
|
||||
margin: auto 15px;
|
||||
vertical-align: middle;
|
||||
clear: both;
|
||||
}
|
||||
.button-group > a {
|
||||
#header .button-group > a {
|
||||
display: inline-block;
|
||||
float: left;
|
||||
line-height: 22px;
|
||||
@ -122,11 +122,11 @@ span.logo {
|
||||
.deploy-button {
|
||||
background: $deployButton;
|
||||
color: #eee !important;
|
||||
|
||||
|
||||
&:hover {
|
||||
background: $deployButtonHover;
|
||||
}
|
||||
|
||||
|
||||
&:active {
|
||||
background: $deployButtonActive;
|
||||
color: #ccc !important;
|
||||
@ -134,18 +134,18 @@ span.logo {
|
||||
}
|
||||
|
||||
#btn-deploy {
|
||||
|
||||
|
||||
padding: 4px 12px;
|
||||
|
||||
|
||||
&.disabled {
|
||||
cursor: default;
|
||||
background: $deployDisabledButton;
|
||||
color: #999 !important;
|
||||
|
||||
|
||||
img {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
|
||||
&+ #btn-deploy-options {
|
||||
background: $deployDisabledButton;
|
||||
color: #ddd;
|
||||
@ -157,7 +157,7 @@ span.logo {
|
||||
background: $deployDisabledButton;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
img {
|
||||
margin-right: 8px;
|
||||
}
|
||||
@ -264,4 +264,3 @@ span.logo {
|
||||
font-size: 16px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
|
@ -21,13 +21,10 @@ ul.red-ui-tabs {
|
||||
display: block;
|
||||
height: 35px;
|
||||
box-sizing:border-box;
|
||||
white-space: nowrap;
|
||||
border-bottom: 1px solid $primary-border-color;
|
||||
background: #fff;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
@include disable-selection;
|
||||
}
|
||||
|
||||
ul.red-ui-tabs li {
|
||||
@ -110,3 +107,16 @@ ul.red-ui-tabs li.active {
|
||||
ul.red-ui-tabs li.active a {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
ul.red-ui-tabs li img {
|
||||
margin-left: -8px;
|
||||
margin-right: 3px;
|
||||
margin-top: -2px;
|
||||
opacity: 0.1;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
ul.red-ui-tabs li.active img {
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
@ -17,6 +17,9 @@
|
||||
|
||||
#workspace-toolbar {
|
||||
display: none;
|
||||
color: $workspace-button-color;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
position: absolute;
|
||||
top: 35px;
|
||||
left:0;
|
||||
@ -26,12 +29,46 @@
|
||||
box-sizing: border-box;
|
||||
background: #fff;
|
||||
border-bottom: 1px solid $secondary-border-color;
|
||||
}
|
||||
white-space: nowrap;
|
||||
|
||||
.button {
|
||||
@include workspace-button;
|
||||
margin-right: 10px;
|
||||
padding: 2px 8px;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
@include disable-selection;
|
||||
|
||||
.button:first-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
&:not(.spinner-group) {
|
||||
.button:not(:first-child) {
|
||||
border-left: none;
|
||||
}
|
||||
}
|
||||
.button.active {
|
||||
background: $workspace-button-background-active;
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
|
||||
.spinner-value {
|
||||
width: 25px;
|
||||
color: $workspace-button-color;
|
||||
padding: 0 5px;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
border-top: 1px solid $secondary-border-color;
|
||||
border-bottom: 1px solid $secondary-border-color;
|
||||
margin: 0;
|
||||
height: 24px;
|
||||
font-size: 12px;
|
||||
background: #f9f9f9;
|
||||
line-height: 22px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#workspace-toolbar .button {
|
||||
@include workspace-button;
|
||||
line-height: 18px;
|
||||
font-size: 12px;
|
||||
margin-right: 5px;
|
||||
padding: 2px 8px;
|
||||
}
|
||||
|
@ -58,12 +58,7 @@
|
||||
<ul id="workspace-tabs"></ul>
|
||||
<div id="workspace-add-tab"><a id="btn-workspace-add-tab" href="#"><i class="fa fa-plus"></i></a></div>
|
||||
<div id="chart"></div>
|
||||
<div id="workspace-toolbar">
|
||||
<a class="button" id="workspace-subflow-edit" href="#" data-i18n="[append]subflow.editSubflowName"><i class="fa fa-pencil"></i> </a>
|
||||
<a class="button disabled" id="workspace-subflow-add-input" href="#" data-i18n="[append]subflow.input"><i class="fa fa-plus"></i> </a>
|
||||
<a class="button" id="workspace-subflow-add-output" href="#" data-i18n="[append]subflow.output"><i class="fa fa-plus"></i> </a>
|
||||
<a class="button" id="workspace-subflow-delete" href="#" data-i18n="[append]subflow.deleteSubflow"><i class="fa fa-trash"></i> </a>
|
||||
</div>
|
||||
<div id="workspace-toolbar"></div>
|
||||
<div id="workspace-footer">
|
||||
<a class="workspace-footer-button" id="btn-zoom-out" href="#"><i class="fa fa-minus"></i></a>
|
||||
<a class="workspace-footer-button" id="btn-zoom-zero" href="#"><i class="fa fa-circle-o"></i></a>
|
||||
|
@ -94,14 +94,13 @@
|
||||
}
|
||||
},
|
||||
"subflow": {
|
||||
"tabLabel": "Subflow: __name__",
|
||||
"editSubflow": "Edit flow __name__",
|
||||
"edit": "Edit flow",
|
||||
"subflowInstances": "There is __count__ instance of this subflow",
|
||||
"subflowInstances_plural": "There are __count__ instances of this subflow",
|
||||
"editSubflowName": "edit name",
|
||||
"input": "input",
|
||||
"output": "output",
|
||||
"input": "inputs:",
|
||||
"output": "outputs:",
|
||||
"deleteSubflow": "delete subflow",
|
||||
"errors": {
|
||||
"noNodesSelected": "<strong>Cannot create subflow</strong>: no nodes selected",
|
||||
|
Loading…
Reference in New Issue
Block a user