mirror of
https://github.com/node-red/node-red.git
synced 2023-10-10 13:36:53 +02:00
Merge pull request #3399 from Steve-Mcl/split-wire-to-links
Add feature: split-wire-to-links
This commit is contained in:
commit
0e92f68b4a
@ -497,7 +497,8 @@
|
||||
"redoChange": "Redo",
|
||||
"searchBox": "Open search box",
|
||||
"managePalette": "Manage palette",
|
||||
"actionList":"Action list"
|
||||
"actionList": "Action list",
|
||||
"splitWiresWithLinks": "Split selection with Link nodes"
|
||||
},
|
||||
"library": {
|
||||
"library": "Library",
|
||||
|
@ -13,6 +13,11 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
/**
|
||||
* An API for undo / redo history buffer
|
||||
* @namespace RED.history
|
||||
*/
|
||||
RED.history = (function() {
|
||||
var undoHistory = [];
|
||||
var redoHistory = [];
|
||||
|
@ -90,6 +90,7 @@
|
||||
"alt-a m": "core:align-selection-to-middle",
|
||||
"alt-a c": "core:align-selection-to-center",
|
||||
"alt-a h": "core:distribute-selection-horizontally",
|
||||
"alt-a v": "core:distribute-selection-vertically"
|
||||
"alt-a v": "core:distribute-selection-vertically",
|
||||
"alt-l l": "core:split-wire-with-link-nodes"
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,11 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
/**
|
||||
* An Interface to nodes and utility functions for creating/adding/deleting nodes and links
|
||||
* @namespace RED.nodes
|
||||
*/
|
||||
RED.nodes = (function() {
|
||||
|
||||
var PORT_TYPE_INPUT = 1;
|
||||
@ -2684,7 +2689,6 @@ RED.nodes = (function() {
|
||||
getType: registry.getNodeType,
|
||||
getNodeHelp: getNodeHelp,
|
||||
convertNode: convertNode,
|
||||
|
||||
add: addNode,
|
||||
remove: removeNode,
|
||||
clear: clear,
|
||||
|
@ -602,7 +602,10 @@ var RED = (function() {
|
||||
null,
|
||||
{id: "menu-item-edit-select-all", label:RED._("keyboard.selectAll"), onselect: "core:select-all-nodes"},
|
||||
{id: "menu-item-edit-select-connected", label:RED._("keyboard.selectAllConnected"), onselect: "core:select-connected-nodes"},
|
||||
{id: "menu-item-edit-select-none", label:RED._("keyboard.selectNone"), onselect: "core:select-none"}
|
||||
{id: "menu-item-edit-select-none", label:RED._("keyboard.selectNone"), onselect: "core:select-none"},
|
||||
null,
|
||||
{id: "menu-item-edit-split-wire-with-links", label:RED._("keyboard.splitWireWithLinks"), onselect: "core:split-wire-with-link-nodes"},
|
||||
|
||||
]});
|
||||
|
||||
menuOptions.push({id:"menu-item-view-menu",label:RED._("menu.label.view.view"),options:[
|
||||
|
@ -809,6 +809,167 @@ RED.view.tools = (function() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits selected wires and re-joins them with link-out+link-in
|
||||
* @param {Object || Object[]} wires The wire(s) to split and replace with link-out, link-in nodes.
|
||||
*/
|
||||
function splitWiresWithLinkNodes(wires) {
|
||||
let wiresToSplit = wires || RED.view.selection().links;
|
||||
if (!Array.isArray(wiresToSplit)) {
|
||||
wiresToSplit = [wiresToSplit];
|
||||
}
|
||||
if (wiresToSplit.length < 1) {
|
||||
return; //nothing selected
|
||||
}
|
||||
|
||||
const history = {
|
||||
t: 'multi',
|
||||
events: [],
|
||||
dirty: RED.nodes.dirty()
|
||||
}
|
||||
const nodeSrcMap = {};
|
||||
const nodeTrgMap = {};
|
||||
const _gridSize = RED.view.gridSize();
|
||||
|
||||
for (let wireIdx = 0; wireIdx < wiresToSplit.length; wireIdx++) {
|
||||
const wire = wiresToSplit[wireIdx];
|
||||
|
||||
//get source and target nodes of this wire link
|
||||
const nSrc = wire.source;
|
||||
const nTrg = wire.target;
|
||||
|
||||
var updateNewNodePosXY = function (origNode, newNode, alignLeft, snap, yOffset) {
|
||||
const nnSize = RED.view.calculateNodeDimensions(newNode);
|
||||
newNode.w = nnSize[0];
|
||||
newNode.h = nnSize[1];
|
||||
const coords = { x: origNode.x || 0, y: origNode.y || 0, w: origNode.w || RED.view.node_width, h: origNode.h || RED.view.node_height };
|
||||
const x = coords.x - (coords.w/2.0);
|
||||
if (alignLeft) {
|
||||
coords.x = x - _gridSize - (newNode.w/2.0);
|
||||
} else {
|
||||
coords.x = x + coords.w + _gridSize + (newNode.w/2.0);
|
||||
}
|
||||
newNode.x = coords.x;
|
||||
newNode.y = coords.y;
|
||||
if (snap !== false) {
|
||||
const offsets = RED.view.tools.calculateGridSnapOffsets(newNode);
|
||||
newNode.x -= offsets.x;
|
||||
newNode.y -= offsets.y;
|
||||
}
|
||||
newNode.y += (yOffset || 0);
|
||||
}
|
||||
const srcPort = (wire.sourcePort || 0);
|
||||
let linkOutMapId = nSrc.id + ':' + srcPort;
|
||||
let nnLinkOut = nodeSrcMap[linkOutMapId];
|
||||
//Create a Link Out if one is not already present
|
||||
if(!nnLinkOut) {
|
||||
const nLinkOut = RED.view.createNode("link out"); //create link node
|
||||
nnLinkOut = nLinkOut.node;
|
||||
nodeSrcMap[linkOutMapId] = nnLinkOut;
|
||||
let yOffset = 0;
|
||||
if(nSrc.outputs > 1) {
|
||||
|
||||
const CENTER_PORT = (((nSrc.outputs-1) / 2) + 1);
|
||||
const offsetCount = Math.abs(CENTER_PORT - (srcPort + 1));
|
||||
yOffset = (_gridSize * 2 * offsetCount);
|
||||
if((srcPort + 1) < CENTER_PORT) {
|
||||
yOffset = -yOffset;
|
||||
}
|
||||
updateNewNodePosXY(nSrc, nnLinkOut, false, false, yOffset);
|
||||
} else {
|
||||
updateNewNodePosXY(nSrc, nnLinkOut, false, RED.view.snapGrid, yOffset);
|
||||
}
|
||||
//add created node
|
||||
RED.nodes.add(nnLinkOut);
|
||||
RED.editor.validateNode(nnLinkOut);
|
||||
history.events.push(nLinkOut.historyEvent);
|
||||
//connect node to link node
|
||||
const link = {
|
||||
source: nSrc,
|
||||
sourcePort: wire.sourcePort || 0,
|
||||
target: nnLinkOut
|
||||
};
|
||||
RED.nodes.addLink(link);
|
||||
history.events.push({
|
||||
t: 'add',
|
||||
links: [link],
|
||||
});
|
||||
}
|
||||
|
||||
let nnLinkIn = nodeTrgMap[nTrg.id];
|
||||
//Create a Link In if one is not already present
|
||||
if(!nnLinkIn) {
|
||||
const nLinkIn = RED.view.createNode("link in"); //create link node
|
||||
nnLinkIn = nLinkIn.node;
|
||||
nodeTrgMap[nTrg.id] = nnLinkIn;
|
||||
updateNewNodePosXY(nTrg, nnLinkIn, true, RED.view.snapGrid, 0);
|
||||
//add created node
|
||||
RED.nodes.add(nnLinkIn);
|
||||
RED.editor.validateNode(nnLinkIn);
|
||||
history.events.push(nLinkIn.historyEvent);
|
||||
//connect node to link node
|
||||
const link = {
|
||||
source: nnLinkIn,
|
||||
sourcePort: 0,
|
||||
target: nTrg
|
||||
};
|
||||
RED.nodes.addLink(link);
|
||||
history.events.push({
|
||||
t: 'add',
|
||||
links: [link],
|
||||
});
|
||||
}
|
||||
|
||||
//connect the link out/link in virtual wires
|
||||
if(nnLinkIn.links.indexOf(nnLinkOut.id) == -1) {
|
||||
nnLinkIn.links.push(nnLinkOut.id);
|
||||
}
|
||||
if(nnLinkOut.links.indexOf(nnLinkIn.id) == -1) {
|
||||
nnLinkOut.links.push(nnLinkIn.id);
|
||||
}
|
||||
|
||||
//delete the original wire
|
||||
RED.nodes.removeLink(wire);
|
||||
history.events.push({
|
||||
t: "delete",
|
||||
links: [wire]
|
||||
});
|
||||
}
|
||||
//add all history events to stack
|
||||
RED.history.push(history);
|
||||
|
||||
//select all downstream of new link-in nodes so user can drag to new location
|
||||
RED.view.clearSelection();
|
||||
RED.view.select({nodes: Object.values(nodeTrgMap) });
|
||||
selectConnected("down");
|
||||
|
||||
//update the view
|
||||
RED.nodes.dirty(true);
|
||||
RED.view.redraw(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the required offsets to snap a node
|
||||
* @param {Object} node The node to calculate grid snap offsets for
|
||||
* @param {Object} [options] Options: `align` can be "nearest", "left" or "right"
|
||||
* @returns `{x:number, y:number}` as the offsets to deduct from `x` and `y`
|
||||
*/
|
||||
function calculateGridSnapOffsets(node, options) {
|
||||
options = options || { align: "nearest" };
|
||||
const gridOffset = { x: 0, y: 0 };
|
||||
const gridSize = RED.view.gridSize();
|
||||
const offsetLeft = node.x - (gridSize * Math.round((node.x - node.w / 2) / gridSize) + node.w / 2);
|
||||
const offsetRight = node.x - (gridSize * Math.round((node.x + node.w / 2) / gridSize) - node.w / 2);
|
||||
gridOffset.x = offsetRight;
|
||||
if (options.align === "right") {
|
||||
//skip - already set to right
|
||||
} else if (options.align === "left" || Math.abs(offsetLeft) < Math.abs(offsetRight)) {
|
||||
gridOffset.x = offsetLeft;
|
||||
}
|
||||
gridOffset.y = node.y - (gridSize * Math.round(node.y / gridSize));
|
||||
return gridOffset;
|
||||
}
|
||||
|
||||
return {
|
||||
init: function() {
|
||||
RED.actions.add("core:show-selected-node-labels", function() { setSelectedNodeLabelState(true); })
|
||||
@ -870,6 +1031,8 @@ RED.view.tools = (function() {
|
||||
RED.actions.add("core:wire-series-of-nodes", function() { wireSeriesOfNodes() })
|
||||
RED.actions.add("core:wire-node-to-multiple", function() { wireNodeToMultiple() })
|
||||
|
||||
RED.actions.add("core:split-wire-with-link-nodes", function () { splitWiresWithLinkNodes() });
|
||||
|
||||
// RED.actions.add("core:add-node", function() { addNode() })
|
||||
},
|
||||
/**
|
||||
@ -881,7 +1044,8 @@ RED.view.tools = (function() {
|
||||
* @param {Number} dx
|
||||
* @param {Number} dy
|
||||
*/
|
||||
moveSelection: moveSelection
|
||||
moveSelection: moveSelection,
|
||||
calculateGridSnapOffsets: calculateGridSnapOffsets
|
||||
}
|
||||
|
||||
})();
|
||||
|
@ -449,7 +449,7 @@ RED.view = (function() {
|
||||
drop: function( event, ui ) {
|
||||
d3.event = event;
|
||||
var selected_tool = $(ui.draggable[0]).attr("data-palette-type");
|
||||
var result = addNode(selected_tool);
|
||||
var result = createNode(selected_tool);
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
@ -493,15 +493,7 @@ RED.view = (function() {
|
||||
nn.y = mousePos[1];
|
||||
|
||||
if (snapGrid) {
|
||||
var gridOffset = [0,0];
|
||||
var offsetLeft = nn.x-(gridSize*Math.round((nn.x-nn.w/2)/gridSize)+nn.w/2);
|
||||
var offsetRight = nn.x-(gridSize*Math.round((nn.x+nn.w/2)/gridSize)-nn.w/2);
|
||||
if (Math.abs(offsetLeft) < Math.abs(offsetRight)) {
|
||||
gridOffset[0] = offsetLeft
|
||||
} else {
|
||||
gridOffset[0] = offsetRight
|
||||
}
|
||||
gridOffset[1] = nn.y-(gridSize*Math.round(nn.y/gridSize));
|
||||
var gridOffset = calculateGridSnapOffsets(nn);
|
||||
nn.x -= gridOffset[0];
|
||||
nn.y -= gridOffset[1];
|
||||
}
|
||||
@ -927,81 +919,6 @@ RED.view = (function() {
|
||||
}
|
||||
}
|
||||
|
||||
function addNode(type,x,y) {
|
||||
var m = /^subflow:(.+)$/.exec(type);
|
||||
|
||||
if (activeSubflow && m) {
|
||||
var subflowId = m[1];
|
||||
if (subflowId === activeSubflow.id) {
|
||||
RED.notify(RED._("notification.error",{message: RED._("notification.errors.cannotAddSubflowToItself")}),"error");
|
||||
return;
|
||||
}
|
||||
if (RED.nodes.subflowContains(m[1],activeSubflow.id)) {
|
||||
RED.notify(RED._("notification.error",{message: RED._("notification.errors.cannotAddCircularReference")}),"error");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var nn = { id:RED.nodes.id(),z:RED.workspaces.active()};
|
||||
|
||||
nn.type = type;
|
||||
nn._def = RED.nodes.getType(nn.type);
|
||||
|
||||
if (!m) {
|
||||
nn.inputs = nn._def.inputs || 0;
|
||||
nn.outputs = nn._def.outputs;
|
||||
|
||||
for (var d in nn._def.defaults) {
|
||||
if (nn._def.defaults.hasOwnProperty(d)) {
|
||||
if (nn._def.defaults[d].value !== undefined) {
|
||||
nn[d] = JSON.parse(JSON.stringify(nn._def.defaults[d].value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (nn._def.onadd) {
|
||||
try {
|
||||
nn._def.onadd.call(nn);
|
||||
} catch(err) {
|
||||
console.log("Definition error: "+nn.type+".onadd:",err);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var subflow = RED.nodes.subflow(m[1]);
|
||||
nn.name = "";
|
||||
nn.inputs = subflow.in.length;
|
||||
nn.outputs = subflow.out.length;
|
||||
}
|
||||
|
||||
nn.changed = true;
|
||||
nn.moved = true;
|
||||
|
||||
nn.w = node_width;
|
||||
nn.h = Math.max(node_height,(nn.outputs||0) * 15);
|
||||
nn.resize = true;
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
node: nn,
|
||||
historyEvent: historyEvent
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function canvasMouseDown() {
|
||||
if (RED.view.DEBUG) { console.warn("canvasMouseDown", mouse_mode); }
|
||||
var point;
|
||||
@ -1190,7 +1107,7 @@ RED.view = (function() {
|
||||
keepAdding = false;
|
||||
resetMouseVars();
|
||||
}
|
||||
var result = addNode(type);
|
||||
var result = createNode(type);
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
@ -1643,16 +1560,9 @@ RED.view = (function() {
|
||||
gridOffset[0] = node.n.x-(gridSize*Math.floor(node.n.x/gridSize))-gridSize/2;
|
||||
gridOffset[1] = node.n.y-(gridSize*Math.floor(node.n.y/gridSize))-gridSize/2;
|
||||
} else {
|
||||
var offsetLeft = node.n.x-(gridSize*Math.round((node.n.x-node.n.w/2)/gridSize)+node.n.w/2);
|
||||
var offsetRight = node.n.x-(gridSize*Math.round((node.n.x+node.n.w/2)/gridSize)-node.n.w/2);
|
||||
// gridOffset[0] = node.n.x-(gridSize*Math.floor((node.n.x-node.n.w/2)/gridSize)+node.n.w/2);
|
||||
if (Math.abs(offsetLeft) < Math.abs(offsetRight)) {
|
||||
gridOffset[0] = offsetLeft
|
||||
} else {
|
||||
gridOffset[0] = offsetRight
|
||||
}
|
||||
gridOffset[1] = node.n.y-(gridSize*Math.round(node.n.y/gridSize));
|
||||
// console.log(offsetLeft, offsetRight);
|
||||
const snapOffsets = RED.view.tools.calculateGridSnapOffsets(node.n);
|
||||
gridOffset[0] = snapOffsets.x;
|
||||
gridOffset[1] = snapOffsets.y;
|
||||
}
|
||||
if (gridOffset[0] !== 0 || gridOffset[1] !== 0) {
|
||||
for (i = 0; i<movingSet.length(); i++) {
|
||||
@ -5508,6 +5418,93 @@ RED.view = (function() {
|
||||
return selection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a node from a type string.
|
||||
* **NOTE:** Can throw on error - use `try` `catch` block when calling
|
||||
* @param {string} type The node type to create
|
||||
* @param {number} [x] (optional) The horizontal position on the workspace
|
||||
* @param {number} [y] (optional)The vertical on the workspace
|
||||
* @param {string} [z] (optional) The flow tab this node will belong to. Defaults to active workspace.
|
||||
* @returns An object containing the `node` and a `historyEvent`
|
||||
* @private
|
||||
*/
|
||||
function createNode(type, x, y, z) {
|
||||
var m = /^subflow:(.+)$/.exec(type);
|
||||
var activeSubflow = z ? RED.nodes.subflow(z) : null;
|
||||
if (activeSubflow && m) {
|
||||
var subflowId = m[1];
|
||||
if (subflowId === activeSubflow.id) {
|
||||
throw new Error(RED._("notification.error", { message: RED._("notification.errors.cannotAddSubflowToItself") }))
|
||||
}
|
||||
if (RED.nodes.subflowContains(m[1], activeSubflow.id)) {
|
||||
throw new Error(RED._("notification.error", { message: RED._("notification.errors.cannotAddCircularReference") }))
|
||||
}
|
||||
}
|
||||
|
||||
var nn = { id: RED.nodes.id(), z: z || RED.workspaces.active() };
|
||||
|
||||
nn.type = type;
|
||||
nn._def = RED.nodes.getType(nn.type);
|
||||
|
||||
if (!m) {
|
||||
nn.inputs = nn._def.inputs || 0;
|
||||
nn.outputs = nn._def.outputs;
|
||||
|
||||
for (var d in nn._def.defaults) {
|
||||
if (nn._def.defaults.hasOwnProperty(d)) {
|
||||
if (nn._def.defaults[d].value !== undefined) {
|
||||
nn[d] = JSON.parse(JSON.stringify(nn._def.defaults[d].value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (nn._def.onadd) {
|
||||
try {
|
||||
nn._def.onadd.call(nn);
|
||||
} catch (err) {
|
||||
console.log("Definition error: " + nn.type + ".onadd:", err);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var subflow = RED.nodes.subflow(m[1]);
|
||||
nn.name = "";
|
||||
nn.inputs = subflow.in.length;
|
||||
nn.outputs = subflow.out.length;
|
||||
}
|
||||
|
||||
nn.changed = true;
|
||||
nn.moved = true;
|
||||
|
||||
nn.w = RED.view.node_width;
|
||||
nn.h = Math.max(RED.view.node_height, (nn.outputs || 0) * 15);
|
||||
nn.resize = true;
|
||||
if (x != null && typeof x == "number" && x >= 0) {
|
||||
nn.x = x;
|
||||
}
|
||||
if (y != null && typeof y == "number" && y >= 0) {
|
||||
nn.y = y;
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
node: nn,
|
||||
historyEvent: historyEvent
|
||||
}
|
||||
}
|
||||
|
||||
function calculateNodeDimensions(node) {
|
||||
var result = [node_width,node_height];
|
||||
try {
|
||||
@ -5591,7 +5588,21 @@ RED.view = (function() {
|
||||
redraw(true);
|
||||
},
|
||||
selection: getSelection,
|
||||
|
||||
clearSelection: clearSelection,
|
||||
createNode: createNode,
|
||||
/** default node width */
|
||||
get node_width() {
|
||||
return node_width;
|
||||
},
|
||||
/** default node height */
|
||||
get node_height() {
|
||||
return node_height;
|
||||
},
|
||||
/** snap to grid option state */
|
||||
get snapGrid() {
|
||||
return snapGrid;
|
||||
},
|
||||
/** gets the current scale factor */
|
||||
scale: function() {
|
||||
return scaleFactor;
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user