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:
Nick O'Leary 2015-07-30 11:03:37 +01:00
parent d1a5395727
commit fdbf079896
11 changed files with 493 additions and 178 deletions

View File

@ -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();

View File

@ -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);

View File

@ -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
}
})();

View File

@ -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();
}

View File

@ -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();

View File

@ -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);
}
});
},

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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>

View File

@ -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",