/** * Copyright JS Foundation and other contributors, http://js.foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. **/ RED.view.tools = (function() { function selectConnected(type) { var selection = RED.view.selection(); var visited = new Set(); if (selection.nodes && selection.nodes.length > 0) { selection.nodes.forEach(function(n) { if (!visited.has(n)) { var connected; if (type === 'all') { connected = RED.nodes.getAllFlowNodes(n); } else if (type === 'up') { connected = [n].concat(RED.nodes.getAllUpstreamNodes(n)); } else if (type === 'down') { connected = [n].concat(RED.nodes.getAllDownstreamNodes(n)); } connected.forEach(function(nn) { visited.add(nn) }) } }); RED.view.select({nodes:Array.from(visited)}); } } function alignToGrid() { var selection = RED.view.selection(); if (selection.nodes) { var changedNodes = []; selection.nodes.forEach(function(n) { var x = n.w/2 + Math.round((n.x-n.w/2)/RED.view.gridSize())*RED.view.gridSize(); var y = Math.round(n.y/RED.view.gridSize())*RED.view.gridSize(); if (n.x !== x || n.y !== y) { changedNodes.push({ n:n, ox: n.x, oy: n.y, moved: n.moved }); n.x = x; n.y = y; n.dirty = true; n.moved = true; } }); if (changedNodes.length > 0) { RED.history.push({t:"move",nodes:changedNodes,dirty:RED.nodes.dirty()}); RED.nodes.dirty(true); RED.view.redraw(true); } } } var moving_set = null; var endMoveSet = false; function endKeyboardMove() { endMoveSet = false; if (moving_set.length > 0) { var ns = []; for (var i=0;i 0) { var n = selection.nodes.shift(); moving_set.push({n:n}); if (n.type === "group") { selection.nodes = selection.nodes.concat(n.nodes); } } } } if (moving_set && moving_set.length > 0) { if (!endMoveSet) { $(document).one('keyup',endKeyboardMove); endMoveSet = true; } var minX = 0; var minY = 0; var node; for (var i=0;i 0) { RED.history.push({ t: "multi", events: historyEvents, dirty: RED.nodes.dirty() }) RED.nodes.dirty(true); } RED.view.redraw(); } function selectFirstNode() { var canidates; var origin = {x:0, y:0}; var activeGroup = RED.view.getActiveGroup(); if (!activeGroup) { candidates = RED.view.getActiveNodes(); } else { candidates = RED.group.getNodes(activeGroup,false); origin = activeGroup; } var distances = []; candidates.forEach(function(node) { var deltaX = node.x - origin.x; var deltaY = node.y - origin.x; var delta = deltaY*deltaY + deltaX*deltaX; distances.push({node: node, delta: delta}) }); if (distances.length > 0) { distances.sort(function(A,B) { return A.delta - B.delta }) var newNode = distances[0].node; if (newNode) { RED.view.select({nodes:[newNode]}); RED.view.reveal(newNode.id,false); } } } function gotoNextNode() { var selection = RED.view.selection(); if (selection.nodes && selection.nodes.length === 1) { var origin = selection.nodes[0]; var links = RED.nodes.filterLinks({source:origin}); if (links.length > 0) { links.sort(function(A,B) { return Math.abs(A.target.y - origin.y) - Math.abs(B.target.y - origin.y) }) var newNode = links[0].target; if (newNode) { RED.view.select({nodes:[newNode]}); RED.view.reveal(newNode.id,false); } } } else if (RED.workspaces.selection().length === 0) { selectFirstNode(); } } function gotoPreviousNode() { var selection = RED.view.selection(); if (selection.nodes && selection.nodes.length === 1) { var origin = selection.nodes[0]; var links = RED.nodes.filterLinks({target:origin}); if (links.length > 0) { links.sort(function(A,B) { return Math.abs(A.source.y - origin.y) - Math.abs(B.source.y - origin.y) }) var newNode = links[0].source; if (newNode) { RED.view.select({nodes:[newNode]}); RED.view.reveal(newNode.id,false); } } } else if (RED.workspaces.selection().length === 0) { selectFirstNode(); } } function getChildren(node) { return RED.nodes.filterLinks({source:node}).map(function(l) { return l.target}) } function getParents(node) { return RED.nodes.filterLinks({target:node}).map(function(l) { return l.source}) } function getSiblings(node) { var siblings = new Set(); var parents = getParents(node); parents.forEach(function(p) { getChildren(p).forEach(function(c) { siblings.add(c) }) }); var children = getChildren(node); children.forEach(function(p) { getParents(p).forEach(function(c) { siblings.add(c) }) }); siblings.delete(node); return Array.from(siblings); } function gotoNextSibling() { // 'next' defined as nearest on the y-axis below this node var selection = RED.view.selection(); if (selection.nodes && selection.nodes.length === 1) { var origin = selection.nodes[0]; var siblings = getSiblings(origin); if (siblings.length > 0) { siblings = siblings.filter(function(n) { return n.y > origin. y}) siblings.sort(function(A,B) { return Math.abs(A.y - origin.y) - Math.abs(B.y - origin.y) }) var newNode = siblings[0]; if (newNode) { RED.view.select({nodes:[newNode]}); RED.view.reveal(newNode.id,false); } } } else if (RED.workspaces.selection().length === 0) { selectFirstNode(); } } function gotoPreviousSibling() { // 'next' defined as nearest on the y-axis above this node var selection = RED.view.selection(); if (selection.nodes && selection.nodes.length === 1) { var origin = selection.nodes[0]; var siblings = getSiblings(origin); if (siblings.length > 0) { siblings = siblings.filter(function(n) { return n.y < origin. y}) siblings.sort(function(A,B) { return Math.abs(A.y - origin.y) - Math.abs(B.y - origin.y) }) var newNode = siblings[0]; if (newNode) { RED.view.select({nodes:[newNode]}); RED.view.reveal(newNode.id,false); } } } else if (RED.workspaces.selection().length === 0) { selectFirstNode(); } } function addNode() { var selection = RED.view.selection(); if (selection.nodes && selection.nodes.length === 1 && selection.nodes[0].outputs > 0) { var selectedNode = selection.nodes[0]; RED.view.showQuickAddDialog([ selectedNode.x + selectedNode.w + 50,selectedNode.y ]) } else { RED.view.showQuickAddDialog(); } } function gotoNearestNode(direction) { var selection = RED.view.selection(); if (selection.nodes && selection.nodes.length === 1) { var origin = selection.nodes[0]; var candidates = RED.nodes.filterNodes({z:origin.z}); candidates = candidates.concat(RED.view.getSubflowPorts()); var distances = []; candidates.forEach(function(node) { if (node === origin) { return; } var deltaX = node.x - origin.x; var deltaY = node.y - origin.y; var delta = deltaY*deltaY + deltaX*deltaX; var angle = (180/Math.PI)*Math.atan2(deltaY,deltaX); if (angle < 0) { angle += 360 } if (angle > 360) { angle -= 360 } var weight; // 0 - right // 270 - above // 90 - below // 180 - left switch(direction) { case 'up': if (angle < 210 || angle > 330) { return } weight = Math.max(Math.abs(270 - angle)/60, 0.2); break; case 'down': if (angle < 30 || angle > 150) { return } weight = Math.max(Math.abs(90 - angle)/60, 0.2); break; case 'left': if (angle < 140 || angle > 220) { return } weight = Math.max(Math.abs(180 - angle)/40, 0.1 ); break; case 'right': if (angle > 40 && angle < 320) { return } weight = Math.max(Math.abs(angle)/40, 0.1); break; } weight = Math.max(weight,0.1); distances.push({ node: node, d: delta, w: weight, delta: delta*weight }) }) if (distances.length > 0) { distances.sort(function(A,B) { return A.delta - B.delta }) var newNode = distances[0].node; if (newNode) { RED.view.select({nodes:[newNode]}); RED.view.reveal(newNode.id,false); } } } else if (RED.workspaces.selection().length === 0) { var candidates = RED.view.getActiveNodes(); var distances = []; candidates.forEach(function(node) { var deltaX = node.x; var deltaY = node.y; var delta = deltaY*deltaY + deltaX*deltaX; distances.push({node: node, delta: delta}) }); if (distances.length > 0) { distances.sort(function(A,B) { return A.delta - B.delta }) var newNode = distances[0].node; if (newNode) { RED.view.select({nodes:[newNode]}); RED.view.reveal(newNode.id,false); } } } } function alignSelectionToEdge(direction) { var selection = RED.view.selection(); if (selection.nodes && selection.nodes.length > 1) { var changedNodes = []; var bounds = { minX: Number.MAX_SAFE_INTEGER, minY: Number.MAX_SAFE_INTEGER, maxX: Number.MIN_SAFE_INTEGER, maxY: Number.MIN_SAFE_INTEGER } selection.nodes.forEach(function(n) { if (n.type === "group") { bounds.minX = Math.min(bounds.minX, n.x); bounds.minY = Math.min(bounds.minY, n.y); bounds.maxX = Math.max(bounds.maxX, n.x + n.w); bounds.maxY = Math.max(bounds.maxY, n.y + n.h); } else { bounds.minX = Math.min(bounds.minX, n.x - n.w/2); bounds.minY = Math.min(bounds.minY, n.y - n.h/2); bounds.maxX = Math.max(bounds.maxX, n.x + n.w/2); bounds.maxY = Math.max(bounds.maxY, n.y + n.h/2); } }); bounds.midX = bounds.minX + (bounds.maxX - bounds.minX)/2; bounds.midY = bounds.minY + (bounds.maxY - bounds.minY)/2; selection.nodes.forEach(function(n) { var targetX; var targetY; var isGroup = n.type==="group"; switch(direction) { case 'top': targetX = n.x; targetY = bounds.minY + (isGroup?0:(n.h/2)); break; case 'bottom': targetX = n.x; targetY = bounds.maxY - (isGroup?n.h:(n.h/2)); break; case 'left': targetX = bounds.minX + (isGroup?0:(n.w/2)); targetY = n.y; break; case 'right': targetX = bounds.maxX - (isGroup?n.w:(n.w/2)); targetY = n.y; break; case 'middle': targetX = n.x; targetY = bounds.midY - (isGroup?n.h/2:0) break; case 'center': targetX = bounds.midX - (isGroup?n.w/2:0) targetY = n.y; break; } if (n.x !== targetX || n.y !== targetY) { if (!isGroup) { changedNodes.push({ n:n, ox: n.x, oy: n.y, moved: n.moved }); n.x = targetX; n.y = targetY; n.dirty = true; n.moved = true; } else { var groupNodes = RED.group.getNodes(n, true); var deltaX = n.x - targetX; var deltaY = n.y - targetY; groupNodes.forEach(function(gn) { if (gn.type !== "group" ) { changedNodes.push({ n:gn, ox: gn.x, oy: gn.y, moved: gn.moved }); gn.x = gn.x - deltaX; gn.y = gn.y - deltaY; gn.dirty = true; gn.moved = true; } }) } } }); if (changedNodes.length > 0) { RED.history.push({t:"move",nodes:changedNodes,dirty:RED.nodes.dirty()}); RED.nodes.dirty(true); RED.view.redraw(true); } } } function distributeSelection(direction) { var selection = RED.view.selection(); if (selection.nodes && selection.nodes.length > 2) { var changedNodes = []; var bounds = { minX: Number.MAX_SAFE_INTEGER, minY: Number.MAX_SAFE_INTEGER, maxX: Number.MIN_SAFE_INTEGER, maxY: Number.MIN_SAFE_INTEGER } var startAnchors = []; var endAnchors = []; selection.nodes.forEach(function(n) { var nx,ny; if (n.type === "group") { nx = n.x + n.w/2; ny = n.y + n.h/2; } else { nx = n.x; ny = n.y; } if (direction === "h") { if (nx < bounds.minX) { startAnchors = []; bounds.minX = nx; } if (nx === bounds.minX) { startAnchors.push(n); } if (nx > bounds.maxX) { endAnchors = []; bounds.maxX = nx; } if (nx === bounds.maxX) { endAnchors.push(n); } } else { if (ny < bounds.minY) { startAnchors = []; bounds.minY = ny; } if (ny === bounds.minY) { startAnchors.push(n); } if (ny > bounds.maxY) { endAnchors = []; bounds.maxY = ny; } if (ny === bounds.maxY) { endAnchors.push(n); } } }); var startAnchor = startAnchors[0]; var endAnchor = endAnchors[0]; var nodeSpace = 0; var nodesToMove = selection.nodes.filter(function(n) { if (n.id !== startAnchor.id && n.id !== endAnchor.id) { nodeSpace += direction === 'h'?n.w:n.h; return true; } return false; }).sort(function(A,B) { if (direction === 'h') { return A.x - B.x } else { return A.y - B.y } }) var saX = startAnchor.x + startAnchor.w/2; var saY = startAnchor.y + startAnchor.h/2; if (startAnchor.type === "group") { saX = startAnchor.x + startAnchor.w; saY = startAnchor.y + startAnchor.h; } var eaX = endAnchor.x; var eaY = endAnchor.y; if (endAnchor.type !== "group") { eaX -= endAnchor.w/2; eaY -= endAnchor.h/2; } var spaceToFill = direction === 'h'?(eaX - saX - nodeSpace): (eaY - saY - nodeSpace); var spaceBetweenNodes = spaceToFill / (nodesToMove.length + 1); var tx = saX; var ty = saY; while(nodesToMove.length > 0) { if (direction === 'h') { tx += spaceBetweenNodes; } else { ty += spaceBetweenNodes; } var nextNode = nodesToMove.shift(); var isGroup = nextNode.type==="group"; var nx = nextNode.x; var ny = nextNode.y; if (!isGroup) { tx += nextNode.w/2; ty += nextNode.h/2; } if ((direction === 'h' && nx !== tx) || (direction === 'v' && ny !== ty)) { if (!isGroup) { changedNodes.push({ n:nextNode, ox: nextNode.x, oy: nextNode.y, moved: nextNode.moved }); if (direction === 'h') { nextNode.x = tx; } else { nextNode.y = ty; } nextNode.dirty = true; nextNode.moved = true; } else { var groupNodes = RED.group.getNodes(nextNode, true); var deltaX = direction === 'h'? nx - tx : 0; var deltaY = direction === 'v'? ny - ty : 0; groupNodes.forEach(function(gn) { if (gn.type !== "group" ) { changedNodes.push({ n:gn, ox: gn.x, oy: gn.y, moved: gn.moved }); gn.x = gn.x - deltaX; gn.y = gn.y - deltaY; gn.dirty = true; gn.moved = true; } }) } } if (isGroup) { tx += nextNode.w; ty += nextNode.h; } else { tx += nextNode.w/2; ty += nextNode.h/2; } } if (changedNodes.length > 0) { RED.history.push({t:"move",nodes:changedNodes,dirty:RED.nodes.dirty()}); RED.nodes.dirty(true); RED.view.redraw(true); } } } function reorderSelection(dir) { var selection = RED.view.selection(); if (selection.nodes) { var nodesToMove = []; selection.nodes.forEach(function(n) { if (n.type === "group") { nodesToMove = nodesToMove.concat(RED.group.getNodes(n, true).filter(function(n) { return n.type !== "group"; })) } else if (n.type !== "subflow"){ nodesToMove.push(n); } }) if (nodesToMove.length > 0) { var z = nodesToMove[0].z; var existingOrder = RED.nodes.getNodeOrder(z); var movedNodes; if (dir === "forwards") { movedNodes = RED.nodes.moveNodesForwards(nodesToMove); } else if (dir === "backwards") { movedNodes = RED.nodes.moveNodesBackwards(nodesToMove); } else if (dir === "front") { movedNodes = RED.nodes.moveNodesToFront(nodesToMove); } else if (dir === "back") { movedNodes = RED.nodes.moveNodesToBack(nodesToMove); } if (movedNodes.length > 0) { var newOrder = RED.nodes.getNodeOrder(z); RED.history.push({t:"reorder",nodes:{z:z,from:existingOrder,to:newOrder},dirty:RED.nodes.dirty()}); RED.nodes.dirty(true); RED.view.redraw(true); } } } } return { init: function() { RED.actions.add("core:show-selected-node-labels", function() { setSelectedNodeLabelState(true); }) RED.actions.add("core:hide-selected-node-labels", function() { setSelectedNodeLabelState(false); }) RED.actions.add("core:scroll-view-up", function() { RED.view.scroll(0,-RED.view.gridSize());}); RED.actions.add("core:scroll-view-right", function() { RED.view.scroll(RED.view.gridSize(),0);}); RED.actions.add("core:scroll-view-down", function() { RED.view.scroll(0,RED.view.gridSize());}); RED.actions.add("core:scroll-view-left", function() { RED.view.scroll(-RED.view.gridSize(),0);}); RED.actions.add("core:step-view-up", function() { RED.view.scroll(0,-5*RED.view.gridSize());}); RED.actions.add("core:step-view-right", function() { RED.view.scroll(5*RED.view.gridSize(),0);}); RED.actions.add("core:step-view-down", function() { RED.view.scroll(0,5*RED.view.gridSize());}); RED.actions.add("core:step-view-left", function() { RED.view.scroll(-5*RED.view.gridSize(),0);}); RED.actions.add("core:move-selection-up", function() { moveSelection(0,-1);}); RED.actions.add("core:move-selection-right", function() { moveSelection(1,0);}); RED.actions.add("core:move-selection-down", function() { moveSelection(0,1);}); RED.actions.add("core:move-selection-left", function() { moveSelection(-1,0);}); RED.actions.add("core:move-selection-forwards", function() { reorderSelection('forwards') }) RED.actions.add("core:move-selection-backwards", function() { reorderSelection('backwards') }) RED.actions.add("core:move-selection-to-front", function() { reorderSelection('front') }) RED.actions.add("core:move-selection-to-back", function() { reorderSelection('back') }) RED.actions.add("core:step-selection-up", function() { moveSelection(0,-RED.view.gridSize());}); RED.actions.add("core:step-selection-right", function() { moveSelection(RED.view.gridSize(),0);}); RED.actions.add("core:step-selection-down", function() { moveSelection(0,RED.view.gridSize());}); RED.actions.add("core:step-selection-left", function() { moveSelection(-RED.view.gridSize(),0);}); RED.actions.add("core:select-connected-nodes", function() { selectConnected("all") }); RED.actions.add("core:select-downstream-nodes", function() { selectConnected("down") }); RED.actions.add("core:select-upstream-nodes", function() { selectConnected("up") }); RED.actions.add("core:go-to-next-node", function() { gotoNextNode() }) RED.actions.add("core:go-to-previous-node", function() { gotoPreviousNode() }) RED.actions.add("core:go-to-next-sibling", function() { gotoNextSibling() }) RED.actions.add("core:go-to-previous-sibling", function() { gotoPreviousSibling() }) RED.actions.add("core:go-to-nearest-node-on-left", function() { gotoNearestNode('left')}) RED.actions.add("core:go-to-nearest-node-on-right", function() { gotoNearestNode('right')}) RED.actions.add("core:go-to-nearest-node-above", function() { gotoNearestNode('up') }) RED.actions.add("core:go-to-nearest-node-below", function() { gotoNearestNode('down') }) RED.actions.add("core:align-selection-to-grid", alignToGrid); RED.actions.add("core:align-selection-to-left", function() { alignSelectionToEdge('left') }) RED.actions.add("core:align-selection-to-right", function() { alignSelectionToEdge('right') }) RED.actions.add("core:align-selection-to-top", function() { alignSelectionToEdge('top') }) RED.actions.add("core:align-selection-to-bottom", function() { alignSelectionToEdge('bottom') }) RED.actions.add("core:align-selection-to-middle", function() { alignSelectionToEdge('middle') }) RED.actions.add("core:align-selection-to-center", function() { alignSelectionToEdge('center') }) RED.actions.add("core:distribute-selection-horizontally", function() { distributeSelection('h') }) RED.actions.add("core:distribute-selection-vertically", function() { distributeSelection('v') }) // RED.actions.add("core:add-node", function() { addNode() }) }, /** * Aligns all selected nodes to the current grid */ alignSelectionToGrid: alignToGrid, /** * Moves all of the selected nodes by the specified amount * @param {Number} dx * @param {Number} dy */ moveSelection: moveSelection } })();