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

Support ctrl-click selection of flow tabs

This commit is contained in:
Nick O'Leary 2018-10-30 22:18:16 +00:00
parent 8dba0dac9e
commit c0d8f904b3
No known key found for this signature in database
GPG Key ID: 4F2157149161A6C9
9 changed files with 343 additions and 99 deletions

View File

@ -91,12 +91,15 @@ RED.history = (function() {
} else if (ev.t == "delete") { } else if (ev.t == "delete") {
if (ev.workspaces) { if (ev.workspaces) {
for (i=0;i<ev.workspaces.length;i++) { for (i=0;i<ev.workspaces.length;i++) {
RED.nodes.addWorkspace(ev.workspaces[i]); RED.nodes.addWorkspace(ev.workspaces[i],ev.workspaces[i]._index);
RED.workspaces.add(ev.workspaces[i]); RED.workspaces.add(ev.workspaces[i],undefined,ev.workspaces[i]._index);
delete ev.workspaces[i]._index;
} }
} }
if (ev.subflow && ev.subflow.subflow) { if (ev.subflows) {
RED.nodes.addSubflow(ev.subflow.subflow); for (i=0;i<ev.subflows.length;i++) {
RED.nodes.addSubflow(ev.subflows[i]);
}
} }
if (ev.subflowInputs && ev.subflowInputs.length > 0) { if (ev.subflowInputs && ev.subflowInputs.length > 0) {
subflow = RED.nodes.subflow(ev.subflowInputs[0].z); subflow = RED.nodes.subflow(ev.subflowInputs[0].z);

View File

@ -291,9 +291,18 @@ RED.clipboard = (function() {
var flow = ""; var flow = "";
var nodes = null; var nodes = null;
if (type === 'export-range-selected') { if (type === 'export-range-selected') {
var selection = RED.view.selection(); var selection = RED.workspaces.selection();
if (selection.length > 0) {
nodes = [];
selection.forEach(function(n) {
nodes.push(n);
nodes = nodes.concat(RED.nodes.filterNodes({z:n.id}));
});
} else {
nodes = RED.view.selection().nodes||[];
}
// Don't include the subflow meta-port nodes in the exported selection // Don't include the subflow meta-port nodes in the exported selection
nodes = RED.nodes.createExportableNodeSet(selection.nodes.filter(function(n) { return n.type !== 'subflow'})); nodes = RED.nodes.createExportableNodeSet(nodes.filter(function(n) { return n.type !== 'subflow'}));
} else if (type === 'export-range-flow') { } else if (type === 'export-range-flow') {
var activeWorkspace = RED.workspaces.active(); var activeWorkspace = RED.workspaces.active();
nodes = RED.nodes.filterNodes({z:activeWorkspace}); nodes = RED.nodes.filterNodes({z:activeWorkspace});
@ -323,13 +332,18 @@ RED.clipboard = (function() {
$("#clipboard-dialog-cancel").hide(); $("#clipboard-dialog-cancel").hide();
$("#clipboard-dialog-copy").hide(); $("#clipboard-dialog-copy").hide();
$("#clipboard-dialog-close").hide(); $("#clipboard-dialog-close").hide();
var selection = RED.view.selection(); var selection = RED.workspaces.selection();
if (selection.length > 0) {
$("#export-range-selected").click();
} else {
selection = RED.view.selection();
if (selection.nodes) { if (selection.nodes) {
$("#export-range-selected").click(); $("#export-range-selected").click();
} else { } else {
$("#export-range-selected").addClass('disabled').removeClass('selected'); $("#export-range-selected").addClass('disabled').removeClass('selected');
$("#export-range-flow").click(); $("#export-range-flow").click();
} }
}
if (format === "export-format-full") { if (format === "export-format-full") {
$("#export-format-full").click(); $("#export-format-full").click();
} else { } else {

View File

@ -161,11 +161,86 @@ RED.tabs = (function() {
ul.children().first().addClass("active"); ul.children().first().addClass("active");
ul.children().addClass("red-ui-tab"); ul.children().addClass("red-ui-tab");
function onTabClick() { function getSelection() {
if (options.onclick) { var selection = ul.find("li.red-ui-tab.selected");
options.onclick(tabs[$(this).attr('href').slice(1)]); var selectedTabs = [];
selection.each(function() {
selectedTabs.push(tabs[$(this).find('a').attr('href').slice(1)])
})
return selectedTabs;
}
function selectionChanged() {
options.onselect(getSelection());
}
function onTabClick(evt) {
evt.preventDefault();
var currentTab = ul.find("li.red-ui-tab.active");
var thisTab = $(this).parent();
var fireSelectionChanged = false;
if (options.onselect) {
if (evt.metaKey) {
if (thisTab.hasClass("selected")) {
thisTab.removeClass("selected");
if (thisTab[0] !== currentTab[0]) {
// Deselect background tab
// - don't switch to it
selectionChanged();
return;
} else {
// Deselect current tab
// - if nothing remains selected, do nothing
// - otherwise switch to first selected tab
var selection = ul.find("li.red-ui-tab.selected");
if (selection.length === 0) {
selectionChanged();
return;
}
thisTab = selection.first();
}
} else {
if (!currentTab.hasClass("selected")) {
var currentTabObj = tabs[currentTab.find('a').attr('href').slice(1)];
// Auto select current tab
currentTab.addClass("selected");
}
thisTab.addClass("selected");
}
fireSelectionChanged = true;
} else if (evt.shiftKey) {
if (currentTab[0] !== thisTab[0]) {
var firstTab,lastTab;
if (currentTab.index() < thisTab.index()) {
firstTab = currentTab;
lastTab = thisTab;
} else {
firstTab = thisTab;
lastTab = currentTab;
}
ul.find("li.red-ui-tab").removeClass("selected");
firstTab.addClass("selected");
lastTab.addClass("selected");
firstTab.nextUntil(lastTab).addClass("selected");
}
fireSelectionChanged = true;
} else {
var selection = ul.find("li.red-ui-tab.selected");
if (selection.length > 0) {
selection.removeClass("selected");
fireSelectionChanged = true;
}
}
}
var thisTabA = thisTab.find("a");
if (options.onclick) {
options.onclick(tabs[thisTabA.attr('href').slice(1)]);
}
activateTab(thisTabA);
if (fireSelectionChanged) {
selectionChanged();
} }
activateTab($(this));
return false; return false;
} }
@ -186,7 +261,12 @@ RED.tabs = (function() {
} }
} }
} }
function onTabDblClick() { function onTabDblClick(evt) {
evt.preventDefault();
evt.stopPropagation();
if (evt.metaKey || evt.shiftKey) {
return;
}
if (options.ondblclick) { if (options.ondblclick) {
options.ondblclick(tabs[$(this).attr('href').slice(1)]); options.ondblclick(tabs[$(this).attr('href').slice(1)]);
} }
@ -288,23 +368,23 @@ RED.tabs = (function() {
currentActiveTabWidth = 0; currentActiveTabWidth = 0;
} }
} }
if (options.collapsible) { // if (options.collapsible) {
console.log(currentTabWidth); // console.log(currentTabWidth);
} // }
tabs.css({width:currentTabWidth}); tabs.css({width:currentTabWidth});
if (tabWidth < 50) { if (tabWidth < 50) {
ul.find(".red-ui-tab-close").hide(); // ul.find(".red-ui-tab-close").hide();
ul.find(".red-ui-tab-icon").hide(); ul.find(".red-ui-tab-icon").hide();
ul.find(".red-ui-tab-label").css({paddingLeft:Math.min(12,Math.max(0,tabWidth-38))+"px"}) ul.find(".red-ui-tab-label").css({paddingLeft:Math.min(12,Math.max(0,tabWidth-38))+"px"})
} else { } else {
ul.find(".red-ui-tab-close").show(); // ul.find(".red-ui-tab-close").show();
ul.find(".red-ui-tab-icon").show(); ul.find(".red-ui-tab-icon").show();
ul.find(".red-ui-tab-label").css({paddingLeft:""}) ul.find(".red-ui-tab-label").css({paddingLeft:""})
} }
if (currentActiveTabWidth !== 0) { if (currentActiveTabWidth !== 0) {
ul.find("li.red-ui-tab.active").css({"width":options.minimumActiveTabWidth}); 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-close").show();
ul.find("li.red-ui-tab.active .red-ui-tab-icon").show(); ul.find("li.red-ui-tab.active .red-ui-tab-icon").show();
ul.find("li.red-ui-tab.active .red-ui-tab-label").css({paddingLeft:""}) ul.find("li.red-ui-tab.active .red-ui-tab-label").css({paddingLeft:""})
} }
@ -319,6 +399,13 @@ RED.tabs = (function() {
function removeTab(id) { function removeTab(id) {
if (options.onselect) {
var selection = ul.find("li.red-ui-tab.selected");
if (selection.length > 0) {
selection.removeClass("selected");
selectionChanged();
}
}
var li = ul.find("a[href='#"+id+"']").parent(); var li = ul.find("a[href='#"+id+"']").parent();
if (li.hasClass("active")) { if (li.hasClass("active")) {
var tab = li.prev(); var tab = li.prev();
@ -341,14 +428,24 @@ RED.tabs = (function() {
return { return {
addTab: function(tab,targetIndex) { addTab: function(tab,targetIndex) {
if (options.onselect) {
var selection = ul.find("li.red-ui-tab.selected");
if (selection.length > 0) {
selection.removeClass("selected");
selectionChanged();
}
}
tabs[tab.id] = tab; tabs[tab.id] = tab;
var li = $("<li/>",{class:"red-ui-tab"}); var li = $("<li/>",{class:"red-ui-tab"});
if (targetIndex === undefined) { if (ul.children().length === 0) {
li.appendTo(ul); targetIndex = undefined;
} else if (targetIndex === 0) { }
if (targetIndex === 0) {
li.prependTo(ul); li.prependTo(ul);
} else { } else if (targetIndex > 0) {
li.insertAfter(ul.find("li:nth-child("+(targetIndex)+")")); li.insertAfter(ul.find("li:nth-child("+(targetIndex)+")"));
} else {
li.appendTo(ul);
} }
li.attr('id',"red-ui-tab-"+(tab.id.replace(".","-"))); li.attr('id',"red-ui-tab-"+(tab.id.replace(".","-")));
li.data("tabId",tab.id); li.data("tabId",tab.id);
@ -400,15 +497,23 @@ RED.tabs = (function() {
} }
link.on("click",onTabClick); link.on("click",onTabClick);
link.on("dblclick",onTabDblClick); link.on("dblclick",onTabDblClick);
if (tab.closeable) { if (tab.closeable) {
li.addClass("red-ui-tabs-closeable")
var closeLink = $("<a/>",{href:"#",class:"red-ui-tab-close"}).appendTo(li); var closeLink = $("<a/>",{href:"#",class:"red-ui-tab-close"}).appendTo(li);
closeLink.append('<i class="fa fa-times" />'); closeLink.append('<i class="fa fa-times" />');
closeLink.on("click",function(event) { closeLink.on("click",function(event) {
event.preventDefault(); event.preventDefault();
removeTab(tab.id); removeTab(tab.id);
}); });
} }
var badges = $('<span class="red-ui-tabs-badges"></span>').appendTo(li);
if (options.onselect) {
$('<i class="red-ui-tabs-badge-changed fa fa-circle"></i>').appendTo(badges);
$('<i class="red-ui-tabs-badge-selected fa fa-check-circle"></i>').appendTo(badges);
}
if (options.onadd) { if (options.onadd) {
options.onadd(tab); options.onadd(tab);
} }
@ -516,6 +621,7 @@ RED.tabs = (function() {
tab.find("span.bidiAware").text(label).attr('dir', RED.text.bidi.resolveBaseTextDir(label)); tab.find("span.bidiAware").text(label).attr('dir', RED.text.bidi.resolveBaseTextDir(label));
updateTabWidths(); updateTabWidths();
}, },
selection: getSelection,
order: function(order) { order: function(order) {
var existingTabOrder = $.makeArray(ul.children().map(function() { return $(this).data('tabId');})); var existingTabOrder = $.makeArray(ul.children().map(function() { return $(this).data('tabId');}));
if (existingTabOrder.length !== order.length) { if (existingTabOrder.length !== order.length) {

View File

@ -370,9 +370,7 @@ RED.subflow = (function() {
return { return {
nodes:removedNodes, nodes:removedNodes,
links:removedLinks, links:removedLinks,
subflow: { subflows: [activeSubflow]
subflow: activeSubflow
}
} }
} }
function init() { function init() {

View File

@ -158,11 +158,37 @@ RED.sidebar.info = (function() {
} else if (Array.isArray(node)) { } else if (Array.isArray(node)) {
// Multiple things selected // Multiple things selected
// - hide help and info sections // - hide help and info sections
var types = {
nodes:0,
flows:0,
subflows:0
}
node.forEach(function(n) {
if (n.type === 'tab') {
types.flows++;
types.nodes += RED.nodes.filterNodes({z:n.id}).length;
} else if (n.type === 'subflow') {
types.subflows++;
} else {
types.nodes++;
}
});
helpSection.container.hide(); helpSection.container.hide();
infoSection.container.hide(); infoSection.container.hide();
// - show the count of selected nodes // - show the count of selected nodes
propRow = $('<tr class="node-info-node-row"><td>'+RED._("sidebar.info.selection")+"</td><td></td></tr>").appendTo(tableBody); propRow = $('<tr class="node-info-node-row"><td>'+RED._("sidebar.info.selection")+"</td><td></td></tr>").appendTo(tableBody);
$(propRow.children()[1]).text(RED._("sidebar.info.nodes",{count:node.length}))
var counts = $('<div>').appendTo($(propRow.children()[1]));
if (types.flows > 0) {
$('<div>').text(RED._("clipboard.flow",{count:types.flows})).appendTo(counts);
}
if (types.subflows > 0) {
$('<div>').text(RED._("clipboard.subflow",{count:types.subflows})).appendTo(counts);
}
if (types.nodes > 0) {
$('<div>').text(RED._("clipboard.node",{count:types.nodes})).appendTo(counts);
}
} else { } else {
// A single 'thing' selected. // A single 'thing' selected.
@ -458,6 +484,8 @@ RED.sidebar.info = (function() {
} else { } else {
refresh(selection.nodes); refresh(selection.nodes);
} }
} else if (selection.flows || selection.subflows) {
refresh(selection.flows);
} else { } else {
var activeWS = RED.workspaces.active(); var activeWS = RED.workspaces.active();

View File

@ -325,7 +325,9 @@ RED.view = (function() {
mouse_position[0] += scrollDeltaLeft; mouse_position[0] += scrollDeltaLeft;
mouse_position[1] += scrollDeltaTop; mouse_position[1] += scrollDeltaTop;
} }
if (RED.workspaces.selection().length === 0) {
clearSelection(); clearSelection();
}
RED.nodes.eachNode(function(n) { RED.nodes.eachNode(function(n) {
n.dirty = true; n.dirty = true;
}); });
@ -1197,6 +1199,8 @@ RED.view = (function() {
function updateSelection() { function updateSelection() {
var selection = {}; var selection = {};
var workspaceSelection = RED.workspaces.selection();
if (workspaceSelection.length === 0) {
if (moving_set.length > 0) { if (moving_set.length > 0) {
selection.nodes = moving_set.map(function(n) { return n.n;}); selection.nodes = moving_set.map(function(n) { return n.n;});
} }
@ -1265,8 +1269,11 @@ RED.view = (function() {
} }
} }
} }
} else {
selection.flows = workspaceSelection;
}
var selectionJSON = activeWorkspace+":"+JSON.stringify(selection,function(key,value) { var selectionJSON = activeWorkspace+":"+JSON.stringify(selection,function(key,value) {
if (key === 'nodes') { if (key === 'nodes' || key === 'flows') {
return value.map(function(n) { return n.id }) return value.map(function(n) { return n.id })
} else if (key === 'link') { } else if (key === 'link') {
return value.source.id+":"+value.sourcePort+":"+value.target.id; return value.source.id+":"+value.sourcePort+":"+value.target.id;
@ -1347,7 +1354,45 @@ RED.view = (function() {
portLabelHover.remove(); portLabelHover.remove();
portLabelHover = null; portLabelHover = null;
} }
if (moving_set.length > 0 || selected_link != null) { var workspaceSelection = RED.workspaces.selection();
if (workspaceSelection.length > 0) {
var workspaceCount = 0;
workspaceSelection.forEach(function(ws) { if (ws.type === 'tab') workspaceCount++ });
if (workspaceCount === RED.workspaces.count()) {
// Cannot delete all workspaces
return;
}
var historyEvent = {
t: 'delete',
dirty: RED.nodes.dirty(),
nodes: [],
links: [],
workspaces: [],
subflows: []
}
var workspaceOrder = RED.nodes.getWorkspaceOrder().slice(0);
for (var i=0;i<workspaceSelection.length;i++) {
var ws = workspaceSelection[i];
ws._index = workspaceOrder.indexOf(ws.id);
RED.workspaces.remove(ws);
var subEvent;
if (ws.type === 'tab') {
historyEvent.workspaces.push(ws);
subEvent = RED.nodes.removeWorkspace(ws.id);
} else {
subEvent = RED.subflow.removeSubflow(ws.id);
historyEvent.subflows = historyEvent.subflows.concat(subEvent.subflows);
}
historyEvent.nodes = historyEvent.nodes.concat(subEvent.nodes);
historyEvent.links = historyEvent.links.concat(subEvent.links);
}
RED.history.push(historyEvent);
RED.nodes.dirty(true);
updateActiveNodes();
updateSelection();
redraw();
} else if (moving_set.length > 0 || selected_link != null) {
var result; var result;
var removedNodes = []; var removedNodes = [];
var removedLinks = []; var removedLinks = [];

View File

@ -22,7 +22,7 @@ RED.workspaces = (function() {
function addWorkspace(ws,skipHistoryEntry,targetIndex) { function addWorkspace(ws,skipHistoryEntry,targetIndex) {
if (ws) { if (ws) {
workspace_tabs.addTab(ws); workspace_tabs.addTab(ws,targetIndex);
workspace_tabs.resize(); workspace_tabs.resize();
} else { } else {
var tabId = RED.nodes.id(); var tabId = RED.nodes.id();
@ -46,6 +46,8 @@ RED.workspaces = (function() {
if (workspaceTabCount === 1) { if (workspaceTabCount === 1) {
return; return;
} }
var workspaceOrder = RED.nodes.getWorkspaceOrder();
ws._index = workspaceOrder.indexOf(ws.id);
removeWorkspace(ws); removeWorkspace(ws);
var historyEvent = RED.nodes.removeWorkspace(ws.id); var historyEvent = RED.nodes.removeWorkspace(ws.id);
historyEvent.t = 'delete'; historyEvent.t = 'delete';
@ -287,6 +289,20 @@ RED.workspaces = (function() {
RED.nodes.dirty(true); RED.nodes.dirty(true);
setWorkspaceOrder(newOrder); setWorkspaceOrder(newOrder);
}, },
onselect: function(selectedTabs) {
RED.view.select(false)
if (selectedTabs.length === 0) {
// TODO: workspace-toolbar
$("#chart svg").css({"pointer-events":"auto",filter:"none"})
$("#palette-container").css({"pointer-events":"auto",filter:"none"})
$(".sidebar-shade").hide();
} else {
RED.view.select(false)
$("#chart svg").css({"pointer-events":"none",filter:"opacity(60%)"})
$("#palette-container").css({"pointer-events":"none",filter:"opacity(60%)"})
$(".sidebar-shade").show();
}
},
minimumActiveTabWidth: 150, minimumActiveTabWidth: 150,
scrollable: true, scrollable: true,
addButton: "core:add-flow", addButton: "core:add-flow",
@ -366,6 +382,9 @@ RED.workspaces = (function() {
active: function() { active: function() {
return activeWorkspace return activeWorkspace
}, },
selection: function() {
return workspace_tabs.selection();
},
show: function(id) { show: function(id) {
if (!workspace_tabs.contains(id)) { if (!workspace_tabs.contains(id)) {
var sf = RED.nodes.subflow(id); var sf = RED.nodes.subflow(id);

View File

@ -35,6 +35,7 @@ $primary-border-color: #bbbbbb;
$secondary-border-color: #dddddd; $secondary-border-color: #dddddd;
$tab-background-active: #fff; $tab-background-active: #fff;
$tab-background-selected: #f9f9f9;
$tab-background-inactive: #f0f0f0; $tab-background-inactive: #f0f0f0;
$tab-background-hover: #ddd; $tab-background-hover: #ddd;

View File

@ -57,7 +57,14 @@
overflow: hidden; overflow: hidden;
white-space: nowrap; white-space: nowrap;
position: relative; position: relative;
&.red-ui-tabs-closeable:hover {
.red-ui-tabs-badges {
display: none;
}
.red-ui-tab-close {
display: block;
}
}
a.red-ui-tab-label { a.red-ui-tab-label {
display: block; display: block;
font-size: 14px; font-size: 14px;
@ -97,6 +104,19 @@
opacity: 0.2; opacity: 0.2;
} }
} }
&.selected {
&:not(.active) {
background: $tab-background-selected;
}
font-weight: bold;
.red-ui-tabs-badge-selected {
display: inline;
}
.red-ui-tabs-badge-changed {
display: none;
}
}
&:not(.active) a:hover { &:not(.active) a:hover {
color: $workspace-button-color-hover; color: $workspace-button-color-hover;
background: $tab-background-hover; background: $tab-background-hover;
@ -282,11 +302,20 @@ i.red-ui-tab-icon {
.red-ui-tabs-badges { .red-ui-tabs-badges {
position: absolute; position: absolute;
top:2px; top:0px;
right:2px; right:0px;
width: 20px;
pointer-events: none;
display: block;
height: 30px;
line-height: 28px;
text-align: center;
padding:0px;
color: #aaa;
} }
.red-ui-tab-closeable .red-ui-tabs-badges {
right: 22px; .red-ui-tabs-badges i {
display: none;
} }
.red-ui-tab.node_changed img.node_changed { .red-ui-tab.node_changed img.node_changed {
@ -303,13 +332,14 @@ i.red-ui-tab-icon {
vertical-align: top; vertical-align: top;
} }
.red-ui-tab-close { .red-ui-tab-close {
display: none;
background: $tab-background-inactive; background: $tab-background-inactive;
opacity: 0.8; opacity: 0.8;
position: absolute; position: absolute;
right: 0px; right: 0px;
top: 0px; top: 0px;
display: block;
width: 20px; width: 20px;
height: 30px; height: 30px;
line-height: 28px; line-height: 28px;