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") {
if (ev.workspaces) {
for (i=0;i<ev.workspaces.length;i++) {
RED.nodes.addWorkspace(ev.workspaces[i]);
RED.workspaces.add(ev.workspaces[i]);
RED.nodes.addWorkspace(ev.workspaces[i],ev.workspaces[i]._index);
RED.workspaces.add(ev.workspaces[i],undefined,ev.workspaces[i]._index);
delete ev.workspaces[i]._index;
}
}
if (ev.subflow && ev.subflow.subflow) {
RED.nodes.addSubflow(ev.subflow.subflow);
if (ev.subflows) {
for (i=0;i<ev.subflows.length;i++) {
RED.nodes.addSubflow(ev.subflows[i]);
}
}
if (ev.subflowInputs && ev.subflowInputs.length > 0) {
subflow = RED.nodes.subflow(ev.subflowInputs[0].z);

View File

@ -291,9 +291,18 @@ RED.clipboard = (function() {
var flow = "";
var nodes = null;
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
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') {
var activeWorkspace = RED.workspaces.active();
nodes = RED.nodes.filterNodes({z:activeWorkspace});
@ -323,12 +332,17 @@ RED.clipboard = (function() {
$("#clipboard-dialog-cancel").hide();
$("#clipboard-dialog-copy").hide();
$("#clipboard-dialog-close").hide();
var selection = RED.view.selection();
if (selection.nodes) {
var selection = RED.workspaces.selection();
if (selection.length > 0) {
$("#export-range-selected").click();
} else {
$("#export-range-selected").addClass('disabled').removeClass('selected');
$("#export-range-flow").click();
selection = RED.view.selection();
if (selection.nodes) {
$("#export-range-selected").click();
} else {
$("#export-range-selected").addClass('disabled').removeClass('selected');
$("#export-range-flow").click();
}
}
if (format === "export-format-full") {
$("#export-format-full").click();

View File

@ -161,11 +161,86 @@ RED.tabs = (function() {
ul.children().first().addClass("active");
ul.children().addClass("red-ui-tab");
function onTabClick() {
if (options.onclick) {
options.onclick(tabs[$(this).attr('href').slice(1)]);
function getSelection() {
var selection = ul.find("li.red-ui-tab.selected");
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;
}
@ -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) {
options.ondblclick(tabs[$(this).attr('href').slice(1)]);
}
@ -288,23 +368,23 @@ RED.tabs = (function() {
currentActiveTabWidth = 0;
}
}
if (options.collapsible) {
console.log(currentTabWidth);
}
// if (options.collapsible) {
// console.log(currentTabWidth);
// }
tabs.css({width:currentTabWidth});
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-label").css({paddingLeft:Math.min(12,Math.max(0,tabWidth-38))+"px"})
} 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-label").css({paddingLeft:""})
}
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-close").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:""})
}
@ -319,6 +399,13 @@ RED.tabs = (function() {
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();
if (li.hasClass("active")) {
var tab = li.prev();
@ -341,14 +428,24 @@ RED.tabs = (function() {
return {
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;
var li = $("<li/>",{class:"red-ui-tab"});
if (targetIndex === undefined) {
li.appendTo(ul);
} else if (targetIndex === 0) {
if (ul.children().length === 0) {
targetIndex = undefined;
}
if (targetIndex === 0) {
li.prependTo(ul);
} else {
} else if (targetIndex > 0) {
li.insertAfter(ul.find("li:nth-child("+(targetIndex)+")"));
} else {
li.appendTo(ul);
}
li.attr('id',"red-ui-tab-"+(tab.id.replace(".","-")));
li.data("tabId",tab.id);
@ -400,15 +497,23 @@ RED.tabs = (function() {
}
link.on("click",onTabClick);
link.on("dblclick",onTabDblClick);
if (tab.closeable) {
li.addClass("red-ui-tabs-closeable")
var closeLink = $("<a/>",{href:"#",class:"red-ui-tab-close"}).appendTo(li);
closeLink.append('<i class="fa fa-times" />');
closeLink.on("click",function(event) {
event.preventDefault();
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) {
options.onadd(tab);
}
@ -516,6 +621,7 @@ RED.tabs = (function() {
tab.find("span.bidiAware").text(label).attr('dir', RED.text.bidi.resolveBaseTextDir(label));
updateTabWidths();
},
selection: getSelection,
order: function(order) {
var existingTabOrder = $.makeArray(ul.children().map(function() { return $(this).data('tabId');}));
if (existingTabOrder.length !== order.length) {

View File

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

View File

@ -158,11 +158,37 @@ RED.sidebar.info = (function() {
} else if (Array.isArray(node)) {
// Multiple things selected
// - 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();
infoSection.container.hide();
// - 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.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 {
// A single 'thing' selected.
@ -458,6 +484,8 @@ RED.sidebar.info = (function() {
} else {
refresh(selection.nodes);
}
} else if (selection.flows || selection.subflows) {
refresh(selection.flows);
} else {
var activeWS = RED.workspaces.active();

View File

@ -325,7 +325,9 @@ RED.view = (function() {
mouse_position[0] += scrollDeltaLeft;
mouse_position[1] += scrollDeltaTop;
}
clearSelection();
if (RED.workspaces.selection().length === 0) {
clearSelection();
}
RED.nodes.eachNode(function(n) {
n.dirty = true;
});
@ -1197,76 +1199,81 @@ RED.view = (function() {
function updateSelection() {
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;
}
var activeWorkspace = RED.workspaces.active();
activeLinks = RED.nodes.filterLinks({
source:{z:activeWorkspace},
target:{z:activeWorkspace}
});
var tabOrder = RED.nodes.getWorkspaceOrder();
var currentLinks = activeLinks;
var addedLinkLinks = {};
activeFlowLinks = [];
for (var i=0;i<moving_set.length;i++) {
if (moving_set[i].n.type === "link out" || moving_set[i].n.type === "link in") {
var linkNode = moving_set[i].n;
var offFlowLinks = {};
linkNode.links.forEach(function(id) {
var target = RED.nodes.node(id);
if (target) {
if (linkNode.type === "link out") {
if (target.z === linkNode.z) {
if (!addedLinkLinks[linkNode.id+":"+target.id]) {
activeLinks.push({
source:linkNode,
sourcePort:0,
target: target,
link: true
});
addedLinkLinks[linkNode.id+":"+target.id] = true;
var workspaceSelection = RED.workspaces.selection();
if (workspaceSelection.length === 0) {
if (moving_set.length > 0) {
selection.nodes = moving_set.map(function(n) { return n.n;});
}
if (selected_link != null) {
selection.link = selected_link;
}
var activeWorkspace = RED.workspaces.active();
activeLinks = RED.nodes.filterLinks({
source:{z:activeWorkspace},
target:{z:activeWorkspace}
});
var tabOrder = RED.nodes.getWorkspaceOrder();
var currentLinks = activeLinks;
var addedLinkLinks = {};
activeFlowLinks = [];
for (var i=0;i<moving_set.length;i++) {
if (moving_set[i].n.type === "link out" || moving_set[i].n.type === "link in") {
var linkNode = moving_set[i].n;
var offFlowLinks = {};
linkNode.links.forEach(function(id) {
var target = RED.nodes.node(id);
if (target) {
if (linkNode.type === "link out") {
if (target.z === linkNode.z) {
if (!addedLinkLinks[linkNode.id+":"+target.id]) {
activeLinks.push({
source:linkNode,
sourcePort:0,
target: target,
link: true
});
addedLinkLinks[linkNode.id+":"+target.id] = true;
}
} else {
offFlowLinks[target.z] = offFlowLinks[target.z]||[];
offFlowLinks[target.z].push(target);
}
} else {
offFlowLinks[target.z] = offFlowLinks[target.z]||[];
offFlowLinks[target.z].push(target);
}
} else {
if (target.z === linkNode.z) {
if (!addedLinkLinks[target.id+":"+linkNode.id]) {
activeLinks.push({
source:target,
sourcePort:0,
target: linkNode,
link: true
});
addedLinkLinks[target.id+":"+linkNode.id] = true;
if (target.z === linkNode.z) {
if (!addedLinkLinks[target.id+":"+linkNode.id]) {
activeLinks.push({
source:target,
sourcePort:0,
target: linkNode,
link: true
});
addedLinkLinks[target.id+":"+linkNode.id] = true;
}
} else {
offFlowLinks[target.z] = offFlowLinks[target.z]||[];
offFlowLinks[target.z].push(target);
}
} else {
offFlowLinks[target.z] = offFlowLinks[target.z]||[];
offFlowLinks[target.z].push(target);
}
}
}
});
var offFlows = Object.keys(offFlowLinks);
// offFlows.sort(function(A,B) {
// return tabOrder.indexOf(A) - tabOrder.indexOf(B);
// });
if (offFlows.length > 0) {
activeFlowLinks.push({
refresh: Math.floor(Math.random()*10000),
node: linkNode,
links: offFlowLinks//offFlows.map(function(i) { return {id:i,links:offFlowLinks[i]};})
});
var offFlows = Object.keys(offFlowLinks);
// offFlows.sort(function(A,B) {
// return tabOrder.indexOf(A) - tabOrder.indexOf(B);
// });
if (offFlows.length > 0) {
activeFlowLinks.push({
refresh: Math.floor(Math.random()*10000),
node: linkNode,
links: offFlowLinks//offFlows.map(function(i) { return {id:i,links:offFlowLinks[i]};})
});
}
}
}
} else {
selection.flows = workspaceSelection;
}
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 })
} else if (key === 'link') {
return value.source.id+":"+value.sourcePort+":"+value.target.id;
@ -1347,7 +1354,45 @@ RED.view = (function() {
portLabelHover.remove();
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 removedNodes = [];
var removedLinks = [];

View File

@ -22,7 +22,7 @@ RED.workspaces = (function() {
function addWorkspace(ws,skipHistoryEntry,targetIndex) {
if (ws) {
workspace_tabs.addTab(ws);
workspace_tabs.addTab(ws,targetIndex);
workspace_tabs.resize();
} else {
var tabId = RED.nodes.id();
@ -46,6 +46,8 @@ RED.workspaces = (function() {
if (workspaceTabCount === 1) {
return;
}
var workspaceOrder = RED.nodes.getWorkspaceOrder();
ws._index = workspaceOrder.indexOf(ws.id);
removeWorkspace(ws);
var historyEvent = RED.nodes.removeWorkspace(ws.id);
historyEvent.t = 'delete';
@ -287,6 +289,20 @@ RED.workspaces = (function() {
RED.nodes.dirty(true);
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,
scrollable: true,
addButton: "core:add-flow",
@ -366,6 +382,9 @@ RED.workspaces = (function() {
active: function() {
return activeWorkspace
},
selection: function() {
return workspace_tabs.selection();
},
show: function(id) {
if (!workspace_tabs.contains(id)) {
var sf = RED.nodes.subflow(id);

View File

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

View File

@ -57,7 +57,14 @@
overflow: hidden;
white-space: nowrap;
position: relative;
&.red-ui-tabs-closeable:hover {
.red-ui-tabs-badges {
display: none;
}
.red-ui-tab-close {
display: block;
}
}
a.red-ui-tab-label {
display: block;
font-size: 14px;
@ -97,6 +104,19 @@
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 {
color: $workspace-button-color-hover;
background: $tab-background-hover;
@ -282,11 +302,20 @@ i.red-ui-tab-icon {
.red-ui-tabs-badges {
position: absolute;
top:2px;
right:2px;
top:0px;
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 {
@ -303,13 +332,14 @@ i.red-ui-tab-icon {
vertical-align: top;
}
.red-ui-tab-close {
display: none;
background: $tab-background-inactive;
opacity: 0.8;
position: absolute;
right: 0px;
top: 0px;
display: block;
width: 20px;
height: 30px;
line-height: 28px;