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

[groups] add basic group functionality to editor

This commit is contained in:
Nick O'Leary 2020-03-03 19:04:32 +00:00
parent c9ad5bea93
commit 86ce5c591b
No known key found for this signature in database
GPG Key ID: 4F2157149161A6C9
7 changed files with 857 additions and 41 deletions

View File

@ -177,6 +177,7 @@ module.exports = function(grunt) {
"packages/node_modules/@node-red/editor-client/src/js/ui/actionList.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/group.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/userSettings.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/projects/projects.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectSettings.js",

View File

@ -61,6 +61,7 @@
"shift-down": "core:step-selection-down",
"shift-left": "core:step-selection-left",
"ctrl-shift-j": "core:show-previous-tab",
"ctrl-shift-k": "core:show-next-tab"
"ctrl-shift-k": "core:show-next-tab",
"ctrl-shift-g": "core:group-selection"
}
}

View File

@ -27,6 +27,9 @@ RED.nodes = (function() {
var subflows = {};
var loadedFlowVersion = null;
var groups = {};
var groupsByZ = {};
var initialLoad;
var dirty = false;
@ -1444,6 +1447,14 @@ RED.nodes = (function() {
// var loadedFlowVersion = null;
}
function addGroup(group) {
groupsByZ[group.z] = groupsByZ[group.z] || [];
groupsByZ[group.z].push(group);
groups[group.id] = group;
}
return {
init: function() {
RED.events.on("registry:node-type-added",function(type) {
@ -1539,6 +1550,10 @@ RED.nodes = (function() {
subflow: getSubflow,
subflowContains: subflowContains,
addGroup: addGroup,
group: function(id) { return groups[id] },
groups: function(z) { return groupsByZ[z] },
eachNode: function(cb) {
for (var n=0;n<nodes.length;n++) {
if (cb(nodes[n]) === false) {

View File

@ -524,6 +524,7 @@ var RED = (function() {
}
RED.subflow.init();
RED.group.init();
RED.clipboard.init();
RED.search.init();
RED.actionList.init();

View File

@ -514,7 +514,9 @@ RED.editor = (function() {
for (var i=editStack.length-1;i<editStack.length;i++) {
var node = editStack[i];
label = node.type;
if (node.type === '_expression') {
if (node.type === 'group') {
label = RED._("subflow.editGroup",{name:RED.utils.sanitize(node.name||node.id)});
} else if (node.type === '_expression') {
label = RED._("expressionEditor.title");
} else if (node.type === '_js') {
label = RED._("jsEditor.title");
@ -2453,6 +2455,249 @@ RED.editor = (function() {
RED.tray.show(trayOptions);
}
function showEditGroupDialog(group) {
var editing_node = group;
editStack.push(group);
RED.view.state(RED.state.EDITING);
var nodeInfoEditor;
var finishedBuilding = false;
var trayOptions = {
title: getEditStackTitle(),
buttons: [
{
id: "node-dialog-cancel",
text: RED._("common.label.cancel"),
click: function() {
RED.tray.close();
}
},
{
id: "node-dialog-ok",
class: "primary",
text: RED._("common.label.done"),
click: function() {
var changes = {};
var changed = false;
var wasDirty = RED.nodes.dirty();
var d;
var outputMap;
if (editing_node._def.oneditsave) {
var oldValues = {};
for (d in editing_node._def.defaults) {
if (editing_node._def.defaults.hasOwnProperty(d)) {
if (typeof editing_node[d] === "string" || typeof editing_node[d] === "number") {
oldValues[d] = editing_node[d];
} else {
oldValues[d] = $.extend(true,{},{v:editing_node[d]}).v;
}
}
}
try {
var rc = editing_node._def.oneditsave.call(editing_node);
if (rc === true) {
changed = true;
}
} catch(err) {
console.log("oneditsave",editing_node.id,editing_node.type,err.toString());
}
for (d in editing_node._def.defaults) {
if (editing_node._def.defaults.hasOwnProperty(d)) {
if (oldValues[d] === null || typeof oldValues[d] === "string" || typeof oldValues[d] === "number") {
if (oldValues[d] !== editing_node[d]) {
changes[d] = oldValues[d];
changed = true;
}
} else {
if (JSON.stringify(oldValues[d]) !== JSON.stringify(editing_node[d])) {
changes[d] = oldValues[d];
changed = true;
}
}
}
}
}
var newValue;
if (editing_node._def.defaults) {
for (d in editing_node._def.defaults) {
if (editing_node._def.defaults.hasOwnProperty(d)) {
var input = $("#node-input-"+d);
if (input.attr('type') === "checkbox") {
newValue = input.prop('checked');
} else if (input.prop("nodeName") === "select" && input.attr("multiple") === "multiple") {
// An empty select-multiple box returns null.
// Need to treat that as an empty array.
newValue = input.val();
if (newValue == null) {
newValue = [];
}
} else if ("format" in editing_node._def.defaults[d] && editing_node._def.defaults[d].format !== "" && input[0].nodeName === "DIV") {
newValue = input.text();
} else {
newValue = input.val();
}
if (newValue != null) {
if (editing_node._def.defaults[d].type) {
if (newValue == "_ADD_") {
newValue = "";
}
}
if (editing_node[d] != newValue) {
if (editing_node._def.defaults[d].type) {
// Change to a related config node
var configNode = RED.nodes.node(editing_node[d]);
if (configNode) {
var users = configNode.users;
users.splice(users.indexOf(editing_node),1);
}
configNode = RED.nodes.node(newValue);
if (configNode) {
configNode.users.push(editing_node);
}
}
changes[d] = editing_node[d];
editing_node[d] = newValue;
changed = true;
}
}
}
}
}
var oldInfo = editing_node.info;
if (nodeInfoEditor) {
var newInfo = nodeInfoEditor.getValue();
if (!!oldInfo) {
// Has existing info property
if (newInfo.trim() === "") {
// New value is blank - remove the property
changed = true;
changes.info = oldInfo;
delete editing_node.info;
} else if (newInfo !== oldInfo) {
// New value is different
changed = true;
changes.info = oldInfo;
editing_node.info = newInfo;
}
} else {
// No existing info
if (newInfo.trim() !== "") {
// New value is not blank
changed = true;
changes.info = undefined;
editing_node.info = newInfo;
}
}
}
if (changed) {
var wasChanged = editing_node.changed;
editing_node.changed = true;
RED.nodes.dirty(true);
var historyEvent = {
t:'edit',
node:editing_node,
changes:changes,
dirty:wasDirty,
changed:wasChanged
};
RED.history.push(historyEvent);
}
editing_node.dirty = true;
RED.tray.close();
RED.view.redraw(true);
}
}
],
resize: function(size) {
editTrayWidthCache['group'] = size.width;
$(".red-ui-tray-content").height(size.height - 50);
// var form = $(".red-ui-tray-content form").height(dimensions.height - 50 - 40);
// if (editing_node && editing_node._def.oneditresize) {
// try {
// editing_node._def.oneditresize.call(editing_node,{width:form.width(),height:form.height()});
// } catch(err) {
// console.log("oneditresize",editing_node.id,editing_node.type,err.toString());
// }
// }
},
open: function(tray, done) {
var trayFooter = tray.find(".red-ui-tray-footer");
var trayFooterLeft = $("<div/>", {
class: "red-ui-tray-footer-left"
}).appendTo(trayFooter)
var trayBody = tray.find('.red-ui-tray-body');
trayBody.parent().css('overflow','hidden');
var editorTabEl = $('<ul></ul>').appendTo(trayBody);
var editorContent = $('<div></div>').appendTo(trayBody);
var editorTabs = RED.tabs.create({
element:editorTabEl,
onchange:function(tab) {
editorContent.children().hide();
if (tab.onchange) {
tab.onchange.call(tab);
}
tab.content.show();
if (finishedBuilding) {
RED.tray.resize();
}
},
collapsible: true,
menu: false
});
var nodePropertiesTab = {
id: "editor-tab-properties",
label: RED._("editor-tab.properties"),
name: RED._("editor-tab.properties"),
content: $('<div>', {class:"red-ui-tray-content"}).appendTo(editorContent).hide(),
iconClass: "fa fa-cog"
};
buildEditForm(nodePropertiesTab.content,"dialog-form","group","node-red",group);
editorTabs.addTab(nodePropertiesTab);
var descriptionTab = {
id: "editor-tab-description",
label: RED._("editor-tab.description"),
name: RED._("editor-tab.description"),
content: $('<div>', {class:"red-ui-tray-content"}).appendTo(editorContent).hide(),
iconClass: "fa fa-file-text-o",
onchange: function() {
nodeInfoEditor.focus();
}
};
editorTabs.addTab(descriptionTab);
nodeInfoEditor = buildDescriptionForm(descriptionTab.content,editing_node);
prepareEditDialog(group,group._def,"node-input", function() {
trayBody.i18n();
finishedBuilding = true;
done();
});
},
close: function() {
if (RED.view.state() != RED.state.IMPORT_DRAGGING) {
RED.view.state(RED.state.DEFAULT);
}
nodeInfoEditor.destroy();
nodeInfoEditor = null;
editStack.pop();
editing_node = null;
},
show: function() {
}
}
if (editTrayWidthCache.hasOwnProperty('group')) {
trayOptions.width = editTrayWidthCache['group'];
}
RED.tray.show(trayOptions);
}
function showTypeEditor(type, options) {
if (customEditTypes.hasOwnProperty(type)) {
if (editStack.length > 0) {
@ -2576,6 +2821,7 @@ RED.editor = (function() {
edit: showEditDialog,
editConfig: showEditConfigNodeDialog,
editSubflow: showEditSubflowDialog,
editGroup: showEditGroupDialog,
editJavaScript: function(options) { showTypeEditor("_js",options) },
editExpression: function(options) { showTypeEditor("_expression", options) },
editJSON: function(options) { showTypeEditor("_json", options) },

View File

@ -25,5 +25,7 @@ RED.state = {
IMPORT_DRAGGING: 8,
QUICK_JOINING: 9,
PANNING: 10,
SELECTING_NODE: 11
SELECTING_NODE: 11,
GROUP_DRAGGING: 12,
GROUP_RESIZE: 13
}

View File

@ -21,12 +21,15 @@
* \- <g>.red-ui-workspace-chart-event-layer "eventLayer"
* |- <rect>.red-ui-workspace-chart-background
* |- <g>.red-ui-workspace-chart-grid "gridLayer"
* |- <g> "groupLayer"
* |- <g> "linkLayer"
* |- <g> "dragGroupLayer"
* \- <g> "nodeLayer"
*/
RED.view = (function() {
var DEBUG_EVENTS = false;
2
var space_width = 5000,
space_height = 5000,
lineCurveScale = 0.75,
@ -48,22 +51,29 @@ RED.view = (function() {
var activeSpliceLink;
var spliceActive = false;
var spliceTimer;
var groupHoverTimer;
var activeSubflow = null;
var activeNodes = [];
var activeLinks = [];
var activeFlowLinks = [];
var activeLinkNodes = {};
var activeGroup = null;
var activeHoverGroup = null;
var activeGroups = [];
var dirtyGroups = {};
var selected_link = null,
mousedown_link = null,
mousedown_node = null,
mousedown_group = null,
mousedown_port_type = null,
mousedown_port_index = 0,
mouseup_node = null,
mouse_offset = [0,0],
mouse_position = null,
mouse_mode = 0,
mousedown_group_handle = null;
moving_set = [],
lasso = null,
ghostNode = null,
@ -75,7 +85,8 @@ RED.view = (function() {
scroll_position = [],
quickAddActive = false,
quickAddLink = null,
showAllLinkPorts = -1;
showAllLinkPorts = -1,
groupNodeSelectPrimed = false;
var selectNodesOptions;
@ -101,6 +112,7 @@ RED.view = (function() {
var linkLayer;
var dragGroupLayer;
var nodeLayer;
var groupLayer;
var drag_lines;
function init() {
@ -253,6 +265,7 @@ RED.view = (function() {
gridLayer = eventLayer.append("g").attr("class","red-ui-workspace-chart-grid");
updateGrid();
groupLayer = eventLayer.append("g");
linkLayer = eventLayer.append("g");
dragGroupLayer = eventLayer.append("g");
nodeLayer = eventLayer.append("g");
@ -517,6 +530,8 @@ RED.view = (function() {
source:{z:activeWorkspace},
target:{z:activeWorkspace}
});
activeGroups = RED.nodes.groups(activeWorkspace)||[];
}
function generateLinkPath(origX,origY, destX, destY, sc) {
@ -671,6 +686,7 @@ RED.view = (function() {
}
function canvasMouseDown() {
if (DEBUG_EVENTS) { console.warn("canvasMouseDown", mouse_mode); }
var point;
if (mouse_mode === RED.state.SELECTING_NODE) {
d3.event.stopPropagation();
@ -684,7 +700,7 @@ RED.view = (function() {
scroll_position = [chart.scrollLeft(),chart.scrollTop()];
return;
}
if (!mousedown_node && !mousedown_link) {
if (!mousedown_node && !mousedown_link && !mousedown_group) {
selected_link = null;
updateSelection();
}
@ -697,7 +713,13 @@ RED.view = (function() {
if (mouse_mode === 0 || mouse_mode === RED.state.QUICK_JOINING) {
if (d3.event.metaKey || d3.event.ctrlKey) {
d3.event.stopPropagation();
showQuickAddDialog(d3.mouse(this));
clearSelection();
point = d3.mouse(this);
var clickedGroup = getGroupAt(point[0],point[1]);
if (drag_lines.length > 0) {
clickedGroup = clickedGroup || RED.nodes.group(drag_lines[0].node.g)
}
showQuickAddDialog(point, null, clickedGroup);
}
}
if (mouse_mode === 0 && !(d3.event.metaKey || d3.event.ctrlKey)) {
@ -718,7 +740,15 @@ RED.view = (function() {
}
}
function showQuickAddDialog(point,spliceLink) {
function showQuickAddDialog(point, spliceLink, targetGroup) {
if (targetGroup && !targetGroup.active) {
targetGroup.active = true;
targetGroup.dirty = true;
selectGroup(targetGroup,false);
activeGroup = targetGroup;
RED.view.redraw();
}
var ox = point[0];
var oy = point[1];
@ -946,6 +976,11 @@ RED.view = (function() {
}
}
}
if (targetGroup) {
RED.group.addToGroup(targetGroup, nn);
}
if (spliceLink) {
resetMouseVars();
// TODO: DRY - droppable/nodeMouseDown/canvasMouseUp/showQuickAddDialog
@ -972,6 +1007,12 @@ RED.view = (function() {
// auto select dropped node - so info shows (if visible)
clearSelection();
nn.selected = true;
if (targetGroup) {
selectGroup(targetGroup,false);
targetGroup.active = true
targetGroup.dirty = true;
activeGroup = targetGroup;
}
moving_set.push({n:nn});
updateActiveNodes();
updateSelection();
@ -1076,12 +1117,23 @@ RED.view = (function() {
return;
}
if (mouse_mode != RED.state.QUICK_JOINING && mouse_mode != RED.state.IMPORT_DRAGGING && !mousedown_node && selected_link == null) {
if (mouse_mode != RED.state.QUICK_JOINING && mouse_mode != RED.state.IMPORT_DRAGGING && !mousedown_node && !mousedown_group && selected_link == null) {
return;
}
var mousePos;
if (mouse_mode == RED.state.JOINING || mouse_mode === RED.state.QUICK_JOINING) {
if (mouse_mode === RED.state.GROUP_RESIZE) {
mousePos = mouse_position;
var nx = mousePos[0] + mousedown_group.dx;
var ny = mousePos[1] + mousedown_group.dy;
switch(mousedown_group.activeHandle) {
case 0: mousedown_group.pos.x0 = nx; mousedown_group.pos.y0 = ny; break;
case 1: mousedown_group.pos.x1 = nx; mousedown_group.pos.y0 = ny; break;
case 2: mousedown_group.pos.x1 = nx; mousedown_group.pos.y1 = ny; break;
case 3: mousedown_group.pos.x0 = nx; mousedown_group.pos.y1 = ny; break;
}
mousedown_group.dirty = true;
} else if (mouse_mode == RED.state.JOINING || mouse_mode === RED.state.QUICK_JOINING) {
// update drag line
if (drag_lines.length === 0 && mousedown_port_type !== null) {
if (d3.event.shiftKey) {
@ -1201,6 +1253,13 @@ RED.view = (function() {
node.n.y -= (maxY - space_height);
}
}
if (mousedown_group) {
mousedown_group.pos.x0 = mousePos[0] + mousedown_group.dx0;
mousedown_group.pos.y0 = mousePos[1] + mousedown_group.dy0;
mousedown_group.pos.x1 = mousePos[0] + mousedown_group.dx1;
mousedown_group.pos.y1 = mousePos[1] + mousedown_group.dy1;
mousedown_group.dirty = true;
}
if (snapGrid != d3.event.shiftKey && moving_set.length > 0) {
var gridOffset = [0,0];
node = moving_set[0];
@ -1265,6 +1324,29 @@ RED.view = (function() {
},100);
}
}
if (!node.n.g && activeGroups) {
if (!groupHoverTimer) {
groupHoverTimer = setTimeout(function() {
activeHoverGroup = null;
for (var i=0;i<activeGroups.length;i++) {
var g = activeGroups[i];
if ( !activeHoverGroup &&
node.n.x >= g.pos.x0 && node.n.x <= g.pos.x1 &&
node.n.y >= g.pos.y0 && node.n.y <= g.pos.y1
) {
g.dirty = !g.hovered;
g.hovered = true;
activeHoverGroup = g;
} else {
// Mark dirty if it is selected
g.dirty = g.hovered;
g.hovered = false;
}
}
groupHoverTimer = null;
},50);
}
}
}
@ -1275,6 +1357,7 @@ RED.view = (function() {
}
function canvasMouseUp() {
if (DEBUG_EVENTS) { console.warn("canvasMouseUp", mouse_mode); }
var i;
var historyEvent;
if (mouse_mode === RED.state.PANNING) {
@ -1311,15 +1394,22 @@ RED.view = (function() {
var y = parseInt(lasso.attr("y"));
var x2 = x+parseInt(lasso.attr("width"));
var y2 = y+parseInt(lasso.attr("height"));
if (!d3.event.ctrlKey) {
if (!d3.event.shiftKey) {
clearSelection();
}
RED.nodes.eachNode(function(n) {
activeNodes.forEach(function(n) {
if (n.z == RED.workspaces.active() && !n.selected) {
n.selected = (n.x > x && n.x < x2 && n.y > y && n.y < y2);
if (n.selected) {
n.dirty = true;
moving_set.push({n:n});
if (n.x > x && n.x < x2 && n.y > y && n.y < y2) {
if (n.g) {
var group = RED.nodes.group(n.g);
if (!group.selected) {
selectGroup(RED.nodes.group(n.g),true);
}
} else {
n.selected = true;
n.dirty = true;
moving_set.push({n:n});
}
}
}
});
@ -1355,6 +1445,20 @@ RED.view = (function() {
}
if (mouse_mode == RED.state.MOVING_ACTIVE) {
if (moving_set.length > 0) {
if (activeHoverGroup) {
for (var j=0;j<moving_set.length;j++) {
var n = moving_set[j];
RED.group.addToGroup(activeHoverGroup,n.n);
}
activeHoverGroup.hovered = false;
activeHoverGroup.dirty = true;
activeHoverGroup.active = true;
activeGroup = activeHoverGroup;
activeGroup.selected = true;
activeHoverGroup = null;
}
var ns = [];
for (var j=0;j<moving_set.length;j++) {
var n = moving_set[j];
@ -1391,7 +1495,23 @@ RED.view = (function() {
}
}
}
// if (mouse_mode === RED.state.MOVING && mousedown_node && mousedown_node.g) {
// if (mousedown_node.gSelected) {
// delete mousedown_node.gSelected
// } else {
// if (!d3.event.ctrlKey && !d3.event.metaKey) {
// clearSelection();
// }
// RED.nodes.group(mousedown_node.g).selected = true;
// mousedown_node.selected = true;
// mousedown_node.dirty = true;
// moving_set.push({n:mousedown_node});
// }
// }
if (mouse_mode == RED.state.MOVING || mouse_mode == RED.state.MOVING_ACTIVE) {
// if (mousedown_node) {
// delete mousedown_node.gSelected;
// }
for (i=0;i<moving_set.length;i++) {
delete moving_set[i].ox;
delete moving_set[i].oy;
@ -1437,20 +1557,28 @@ RED.view = (function() {
if (mouse_mode === RED.state.SELECTING_NODE && selectNodesOptions.single) {
return;
}
RED.nodes.eachNode(function(n) {
if (n.z == RED.workspaces.active()) {
if (mouse_mode === RED.state.SELECTING_NODE) {
if (selectNodesOptions.filter && !selectNodesOptions.filter(n)) {
return;
}
}
if (!n.selected) {
n.selected = true;
n.dirty = true;
moving_set.push({n:n});
activeGroups.forEach(function(g) {
selectGroup(g, true);
if (!g.selected) {
g.selected = true;
g.dirty = true;
}
})
activeNodes.forEach(function(n) {
if (mouse_mode === RED.state.SELECTING_NODE) {
if (selectNodesOptions.filter && !selectNodesOptions.filter(n)) {
return;
}
}
if (!n.g && !n.selected) {
n.selected = true;
n.dirty = true;
moving_set.push({n:n});
}
});
if (mouse_mode !== RED.state.SELECTING_NODE && activeSubflow) {
activeSubflow.in.forEach(function(n) {
if (!n.selected) {
@ -1483,6 +1611,7 @@ RED.view = (function() {
}
function clearSelection() {
if (DEBUG_EVENTS) { console.warn("clearSelection", mouse_mode); }
for (var i=0;i<moving_set.length;i++) {
var n = moving_set[i];
n.n.dirty = true;
@ -1490,6 +1619,15 @@ RED.view = (function() {
}
moving_set = [];
selected_link = null;
if (activeGroup) {
activeGroup.active = false
activeGroup.dirty = true;
activeGroup = null;
}
activeGroups.forEach(function(g) {
g.selected = false;
g.dirty = true;
})
}
var lastSelection = null;
@ -1505,6 +1643,7 @@ RED.view = (function() {
if (selected_link != null) {
selection.link = selected_link;
}
selection.groups = activeGroups.filter(function(g) { return g.selected })
activeLinks = RED.nodes.filterLinks({
source:{z:activeWorkspace},
target:{z:activeWorkspace}
@ -1588,7 +1727,7 @@ RED.view = (function() {
selection.flows = workspaceSelection;
}
var selectionJSON = activeWorkspace+":"+JSON.stringify(selection,function(key,value) {
if (key === 'nodes' || key === 'flows') {
if (key === 'nodes' || key === 'flows' || key === 'groups') {
return value.map(function(n) { return n.id })
} else if (key === 'link') {
return value.source.id+":"+value.sourcePort+":"+value.target.id;
@ -1895,6 +2034,8 @@ RED.view = (function() {
function resetMouseVars() {
mousedown_node = null;
mousedown_group = null;
mousedown_group_handle = null;
mouseup_node = null;
mousedown_link = null;
mouse_mode = 0;
@ -1906,6 +2047,10 @@ RED.view = (function() {
clearTimeout(spliceTimer);
spliceTimer = null;
}
if (groupHoverTimer) {
clearTimeout(groupHoverTimer);
groupHoverTimer = null;
}
}
function disableQuickJoinEventHandler(evt) {
@ -1919,6 +2064,7 @@ RED.view = (function() {
}
function portMouseDown(d,portType,portIndex) {
if (DEBUG_EVENTS) { console.warn("portMouseDown", mouse_mode,d); }
//console.log(d,portType,portIndex);
// disable zoom
//eventLayer.call(d3.behavior.zoom().on("zoom"), null);
@ -1946,6 +2092,7 @@ RED.view = (function() {
}
function portMouseUp(d,portType,portIndex) {
if (DEBUG_EVENTS) { console.warn("portMouseUp", mouse_mode,d); }
if (mouse_mode === RED.state.SELECTING_NODE) {
d3.event.stopPropagation();
return;
@ -2280,6 +2427,7 @@ RED.view = (function() {
}
function nodeMouseUp(d) {
if (DEBUG_EVENTS) { console.warn("nodeMouseUp", mouse_mode,d); }
if (mouse_mode === RED.state.SELECTING_NODE) {
d3.event.stopPropagation();
return;
@ -2295,6 +2443,27 @@ RED.view = (function() {
d3.event.stopPropagation();
return;
}
if (mouse_mode === RED.state.MOVING) {
// Moving primed, but not active.
if (!groupNodeSelectPrimed && !d.selected && d.g && RED.nodes.group(d.g).selected) {
clearSelection();
activeGroup = RED.nodes.group(d.g);
activeGroup.active = true;
activeGroup.dirty = true;
selectGroup(RED.nodes.group(d.g), false);
mousedown_node.selected = true;
moving_set.push({n:mousedown_node});
var mouse = d3.touches(this)[0]||d3.mouse(this);
mouse[0] += d.x-d.w/2;
mouse[1] += d.y-d.h/2;
prepareDrag(mouse);
return;
}
}
groupNodeSelectPrimed = false;
var direction = d._def? (d.inputs > 0 ? 1: 0) : (d.direction == "in" ? 0: 1)
var wasJoining = false;
if (mouse_mode === RED.state.JOINING || mouse_mode === RED.state.QUICK_JOINING) {
@ -2316,7 +2485,23 @@ RED.view = (function() {
}
}
function prepareDrag(mouse) {
mouse_mode = RED.state.MOVING;
// Called when moving_set should be prepared to be dragged
for (i=0;i<moving_set.length;i++) {
moving_set[i].ox = moving_set[i].n.x;
moving_set[i].oy = moving_set[i].n.y;
moving_set[i].dx = moving_set[i].n.x-mouse[0];
moving_set[i].dy = moving_set[i].n.y-mouse[1];
}
mouse_offset = d3.mouse(document.body);
if (isNaN(mouse_offset[0])) {
mouse_offset = d3.touches(document.body)[0];
}
}
function nodeMouseDown(d) {
if (DEBUG_EVENTS) { console.warn("nodeMouseDown", mouse_mode,d); }
focusView();
if (d3.event.button === 1) {
return;
@ -2385,7 +2570,9 @@ RED.view = (function() {
// }
return;
}
mousedown_node = d;
var now = Date.now();
clickElapsed = now-clickTime;
clickTime = now;
@ -2393,11 +2580,76 @@ RED.view = (function() {
dblClickPrimed = (lastClickNode == mousedown_node &&
d3.event.button === 0 &&
!d3.event.shiftKey && !d3.event.metaKey && !d3.event.altKey && !d3.event.ctrlKey);
lastClickNode = mousedown_node;
lastClickNode = mousedown_node;
var i;
if (d.selected && (d3.event.ctrlKey||d3.event.metaKey)) {
if (!d.selected && d.g /*&& !RED.nodes.group(d.g).selected*/) {
var nodeGroup = RED.nodes.group(d.g);
if (nodeGroup !== activeGroup && (d3.event.ctrlKey || d3.event.metaKey)) {
// Clicked on a node in a non-active group with ctrl pressed
// - exit active group
// - toggle the select state of the group
exitActiveGroup();
groupNodeSelectPrimed = true;
if (nodeGroup.selected) {
deselectGroup(nodeGroup);
} else {
selectGroup(nodeGroup,true);
}
} else if (nodeGroup === activeGroup ) {
// Clicked on a node in the active group
if (!d3.event.ctrlKey && !d3.event.metaKey) {
// Ctrl not pressed so clear selection
deselectGroup(nodeGroup);
selectGroup(nodeGroup,false);
}
// Select this node
mousedown_node.selected = true;
moving_set.push({n:mousedown_node});
} else {
// Clicked on a node in a group
// - if this group is not selected, clear current selection
// and select this group
// - if this group is not the active group, exit the active group
// and select the group
// - if this group is the active group, keep it active and
// change node selection
// Set groupNodeSelectPrimed to true as this is a (de)select of the
// group and NOT meant to trigger going into the group - see nodeMouseUp
groupNodeSelectPrimed = !nodeGroup.selected;
var ag = activeGroup;
if (!nodeGroup.selected) {
clearSelection();
}
if (ag) {
if (ag !== nodeGroup) {
ag.active = false;
ag.dirty = true;
} else {
activeGroup = nodeGroup;
activeGroup.active = true;
}
} else {
dblClickPrimed = false;
}
selectGroup(nodeGroup, !activeGroup);
if (activeGroup) {
mousedown_node.selected = true;
moving_set.push({n:mousedown_node});
}
}
if (d3.event.button != 2) {
var mouse = d3.touches(this)[0]||d3.mouse(this);
mouse[0] += d.x-d.w/2;
mouse[1] += d.y-d.h/2;
prepareDrag(mouse);
}
} else if (d.selected && (d3.event.ctrlKey||d3.event.metaKey)) {
mousedown_node.selected = false;
for (i=0;i<moving_set.length;i+=1) {
if (moving_set[i].n === mousedown_node) {
@ -2406,6 +2658,21 @@ RED.view = (function() {
}
}
} else {
// if (d.g && !RED.nodes.group(d.g).selected) {
// selectGroup(RED.nodes.group(d.g), false);
// }
// if (!d.selected && d.g) {
// if (!RED.nodes.group(d.g).selected) {// && !RED.nodes.group(d.g).selected) {
// clearSelection();
// selectGroup(RED.nodes.group(d.g));
// d.selected = true;
// console.log(d.id,"Setting selected")
// d.gSelected = true;
// }
// } else
if (d3.event.shiftKey) {
clearSelection();
var cnodes = RED.nodes.getAllFlowNodes(mousedown_node);
@ -2417,6 +2684,8 @@ RED.view = (function() {
} else if (!d.selected) {
if (!d3.event.ctrlKey && !d3.event.metaKey) {
clearSelection();
} else {
exitActiveGroup();
}
mousedown_node.selected = true;
moving_set.push({n:mousedown_node});
@ -2427,16 +2696,7 @@ RED.view = (function() {
var mouse = d3.touches(this)[0]||d3.mouse(this);
mouse[0] += d.x-d.w/2;
mouse[1] += d.y-d.h/2;
for (i=0;i<moving_set.length;i++) {
moving_set[i].ox = moving_set[i].n.x;
moving_set[i].oy = moving_set[i].n.y;
moving_set[i].dx = moving_set[i].n.x-mouse[0];
moving_set[i].dy = moving_set[i].n.y-mouse[1];
}
mouse_offset = d3.mouse(document.body);
if (isNaN(mouse_offset[0])) {
mouse_offset = d3.touches(document.body)[0];
}
prepareDrag(mouse);
}
}
d.dirty = true;
@ -2445,6 +2705,164 @@ RED.view = (function() {
d3.event.stopPropagation();
}
function groupMouseUp(g) {
if (dblClickPrimed && mousedown_group == g && clickElapsed > 0 && clickElapsed < 750) {
mouse_mode = RED.state.DEFAULT;
RED.editor.editGroup(g);
d3.event.stopPropagation();
return;
}
}
function groupMouseDown(g) {
var mouse = d3.touches(this.parentNode)[0]||d3.mouse(this.parentNode);
if (! (mouse[0] < g.pos.x0+10 || mouse[0] > g.pos.x1-10 || mouse[1] < g.pos.y0+10 || mouse[1] > g.pos.y1-10) ) {
return
}
focusView();
if (d3.event.button === 1) {
return;
}
if (mouse_mode == RED.state.IMPORT_DRAGGING) {
RED.keyboard.remove("escape");
console.log("Dragged a node into the group")
} else if (mouse_mode == RED.state.QUICK_JOINING) {
d3.event.stopPropagation();
return;
} else if (mouse_mode === RED.state.SELECTING_NODE) {
d3.event.stopPropagation();
return;
}
mousedown_group = g;
var now = Date.now();
clickElapsed = now-clickTime;
clickTime = now;
dblClickPrimed = (
lastClickNode == g &&
d3.event.button === 0 &&
!d3.event.shiftKey && !d3.event.metaKey && !d3.event.altKey && !d3.event.ctrlKey
);
lastClickNode = g;
if (g.selected && (d3.event.ctrlKey||d3.event.metaKey)) {
if (g === activeGroup) {
exitActiveGroup();
}
deselectGroup(g);
d3.event.stopPropagation();
} else {
if (!g.selected) {
if (!d3.event.ctrlKey && !d3.event.metaKey) {
clearSelection();
}
selectGroup(g,true);//!wasSelected);
} else {
exitActiveGroup();
}
if (d3.event.button != 2) {
var d = g.nodes[0];
prepareDrag(mouse);
mousedown_group.dx0 = mousedown_group.pos.x0 - mouse[0];
mousedown_group.dy0 = mousedown_group.pos.y0 - mouse[1];
mousedown_group.dx1 = mousedown_group.pos.x1 - mouse[0];
mousedown_group.dy1 = mousedown_group.pos.y1 - mouse[1];
}
}
updateSelection();
redraw();
d3.event.stopPropagation();
}
function selectGroup(g, includeNodes) {
if (!g.selected) {
g.selected = true;
g.dirty = true;
}
if (includeNodes) {
var currentSet = new Set(moving_set.map(function(n) { return n.n }));
g.nodes.forEach(function(n) {
if (!currentSet.has(n)) {
moving_set.push({n:n})
// n.selected = true;
}
n.dirty = true;
})
}
}
function exitActiveGroup() {
if (activeGroup) {
activeGroup.active = false;
activeGroup.dirty = true;
deselectGroup(activeGroup);
selectGroup(activeGroup,true);
activeGroup = null;
}
}
function deselectGroup(g) {
if (g.selected) {
g.selected = false;
g.dirty = true;
}
var nodeSet = new Set(g.nodes);
for (var i = moving_set.length-1; i >= 0; i -= 1) {
if (nodeSet.has(moving_set[i].n)) {
moving_set[i].n.selected = false;
moving_set[i].n.dirty = true;
moving_set.splice(i,1);
}
}
}
function getGroupAt(x,y) {
for (var i=0;i<activeGroups.length;i++) {
var g = activeGroups[i];
if (x >= g.pos.x0 && x <= g.pos.x1 && y >= g.pos.y0 && y <= g.pos.y1) {
return g;
}
}
return null;
}
function groupHandleMouseDown(group, groupEl, handle,handleIndex) {
d3.event.stopPropagation();
console.log("GHANDLE MD");
if (d3.event.button != 2) {
mousedown_group = group;
group.activeHandle = handleIndex;
var mouse = d3.touches(handle.parentNode.parentNode)[0]||d3.mouse(handle.parentNode.parentNode);
switch(handleIndex) {
case 0: group.ox = group.pos.x0; group.oy = group.pos.y0; break;
case 1: group.ox = group.pos.x1; group.oy = group.pos.y0; break;
case 2: group.ox = group.pos.x1; group.oy = group.pos.y1; break;
case 3: group.ox = group.pos.x0; group.oy = group.pos.y1; break;
}
group.dx = group.ox - mouse[0];
group.dy = group.oy - mouse[1];
console.log("START",group.ox, group.oy);
mouse_offset = d3.mouse(document.body);
if (isNaN(mouse_offset[0])) {
mouse_offset = d3.touches(document.body)[0];
}
mouse_mode = RED.state.GROUP_RESIZE;
}
}
function groupHandleMouseUp(group,groupEl,handle,handleIndex) {
console.log("GHANDLE MU");
d3.event.stopPropagation();
delete group.ox;
delete group.oy;
delete group.dx;
delete group.dy;
resetMouseVars();
mouse_mode = RED.state.DEFAULT;
}
function isButtonEnabled(d) {
var buttonEnabled = true;
var ws = RED.nodes.workspace(RED.workspaces.active());
@ -3294,6 +3712,19 @@ RED.view = (function() {
}
d.dirty = false;
if (d.g) {
if (!dirtyGroups[d.g]) {
dirtyGroups[d.g] = RED.nodes.group(d.g);
}
var group = dirtyGroups[d.g];
group.pos = {
x0: Math.min(group.pos.x0,d.x-d.w/2-25-((d._def.button && d._def.align!=="right")?20:0)),
y0: Math.min(group.pos.y0,d.y-d.h/2-25),
x1: Math.max(group.pos.x1,d.x+d.w/2+25+((d._def.button && d._def.align=="right")?20:0)),
y1: Math.max(group.pos.y1,d.y+d.h/2+25)
}
}
}
});
@ -3324,7 +3755,9 @@ RED.view = (function() {
d3.event.stopPropagation();
if (d3.event.metaKey || d3.event.ctrlKey) {
l.classed("red-ui-flow-link-splice",true);
showQuickAddDialog(d3.mouse(this), selected_link);
var point = d3.mouse(this);
var clickedGroup = getGroupAt(point[0],point[1]);
showQuickAddDialog(point, selected_link, clickedGroup);
}
})
.on("touchstart",function(d) {
@ -3509,6 +3942,112 @@ RED.view = (function() {
})
var group = groupLayer.selectAll(".red-ui-flow-group").data(activeGroups,function(d) { return d.id });
group.exit().remove();
var groupEnter = group.enter().insert("svg:g")
.attr("class", "red-ui-flow-group")
groupEnter.each(function(d,i) {
var g = d3.select(this);
// g.append('path').classed("red-ui-flow-group-handle red-ui-flow-group-handle-0",true).style({
// "fill":"white",
// "stroke": "#ff7f0e",
// "stroke-width": 2
// }).attr("d","m -5 6 v -2 q 0 -9 9 -9 h 2 v 11 z")
//
// g.append('path').classed("red-ui-flow-group-handle red-ui-flow-group-handle-1",true).style({
// "fill": "white",
// "stroke": "#ff7f0e",
// "stroke-width": 2
// }).attr("d","m -6 -5 h 2 q 9 0 9 9 v 2 h -11 z")
//
// g.append('path').classed("red-ui-flow-group-handle red-ui-flow-group-handle-2",true).style({
// "fill": "white",
// "stroke": "#ff7f0e",
// "stroke-width": 2
// }).attr("d","m 5 -6 v 2 q 0 9 -9 9 h -2 v -11 z")
//
// g.append('path').classed("red-ui-flow-group-handle red-ui-flow-group-handle-3",true).style({
// "fill": "white",
// "stroke": "#ff7f0e",
// "stroke-width": 2
// }).attr("d","m 6 5 h -2 q -9 0 -9 -9 v -2 h 11 z")
g.append('rect').classed("red-ui-flow-group-outline",true)
.attr('rx',1).attr('ry',1).style({
"fill":"none",
"stroke": "#ff7f0e",
"stroke-opacity": 0,
"stroke-width": 15
})
g.append('rect').classed("red-ui-flow-group-body",true)
.attr('rx',1).attr('ry',1).style({
"fill":d.fill||"none",
"stroke": d.stroke||"none",
"stroke-width": 2
})
d.dirty = true;
g.on("mousedown",groupMouseDown).on("mouseup",groupMouseUp)
});
group.each(function(d,i) {
if (d.dirty || dirtyGroups[d.id]) {
var g = d3.select(this);
var minX = Number.POSITIVE_INFINITY;
var minY = Number.POSITIVE_INFINITY;
var maxX = 0;
var maxY = 0;
d.nodes.forEach(function(n) {
minX = Math.min(minX,n.x-n.w/2-25-((n._def.button && n._def.align!=="right")?20:0));
minY = Math.min(minY,n.y-n.h/2-25);
maxX = Math.max(maxX,n.x+n.w/2+25+((n._def.button && n._def.align=="right")?20:0));
maxY = Math.max(maxY,n.y+n.h/2+25);
});
d.pos = {
x0: minX, y0: minY,
x1: maxX, y1: maxY
}
g.attr("transform","translate("+d.pos.x0+","+d.pos.y0+")");
g.selectAll(".red-ui-flow-group-outline")
.attr("width",d.pos.x1-d.pos.x0)
.attr("height",d.pos.y1-d.pos.y0)
.style("stroke-opacity",function(d) { if (d.selected) { return 0.3 } return 0});
g.selectAll(".red-ui-flow-group-body")
.attr("width",d.pos.x1-d.pos.x0)
.attr("height",d.pos.y1-d.pos.y0)
.style("stroke",function(d) { /*if (d.selected) { return "#ff7f0e" } */return d.style.stroke || "none"})
.style("stroke-dasharray", function(d) { return (d.active||d.hovered)?"10 4":"none"})
.style("fill", function(d) { return d.style.fill || "none"})
.style("fill-opacity", 0.1)
// g.selectAll(".red-ui-flow-group-handle-0")
// .style("opacity",function(d) { return d.selected?1:0})
// g.selectAll(".red-ui-flow-group-handle-1")
// .attr("transform","translate("+(maxX-minX)+",0)")
// .style("opacity",function(d) { return d.selected?1:0})
// g.selectAll(".red-ui-flow-group-handle-2")
// .attr("transform","translate("+(maxX-minX)+","+(maxY-minY)+")")
// .style("opacity",function(d) { return d.selected?1:0})
// g.selectAll(".red-ui-flow-group-handle-3")
// .attr("transform","translate(0,"+(maxY-minY)+")")
// .style("opacity",function(d) { return d.selected?1:0})
delete dirtyGroups[d.id];
delete d.dirty;
}
})
} else {
// JOINING - unselect any selected links
linkLayer.selectAll(".red-ui-flow-link-selected").data(
@ -3785,10 +4324,17 @@ RED.view = (function() {
selectedNode.dirty = true;
moving_set = [{n:selectedNode}];
}
} else if (selection) {
if (selection.groups) {
updateActiveNodes();
selection.groups.forEach(function(g) {
selectGroup(g,true);
})
}
}
}
updateSelection();
redraw();
redraw(true);
},
selection: function() {
var selection = {};
@ -3798,6 +4344,10 @@ RED.view = (function() {
if (selected_link != null) {
selection.link = selected_link;
}
selection.groups = activeGroups.filter(function(g) { return g.selected })
if (selection.groups.length === 0) {
delete selection.groups;
}
return selection;
},
scale: function() {