/** * 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.group = (function() { var _groupEditTemplate = ''; var groupDef = { defaults:{ name:{value:""}, style:{value:{}}, nodes:{value:[]} }, category: "config", oneditprepare: function() { var style = this.style || {}; $("#node-input-style-stroke").val(style.stroke || "#eeeeee") $("#node-input-style-fill").val(style.fill || "none") }, oneditresize: function(size) { }, oneditsave: function() { this.style.stroke = $("#node-input-style-stroke").val(); this.style.fill = $("#node-input-style-fill").val(); }, set:{ module: "node-red" } } function init() { RED.events.on("view:selection-changed",function(selection) { RED.menu.setDisabled("menu-item-group-group",!!!selection.nodes); RED.menu.setDisabled("menu-item-group-ungroup",!!!selection.nodes || selection.nodes.filter(function(n) { return n.type==='group'}).length === 0); RED.menu.setDisabled("menu-item-group-merge",!!!selection.nodes); RED.menu.setDisabled("menu-item-group-remove",!!!selection.nodes || selection.nodes.filter(function(n) { return !!n.g }).length === 0); }); RED.actions.add("core:group-selection", function() { groupSelection() }) RED.actions.add("core:ungroup-selection", function() { ungroupSelection() }) RED.actions.add("core:merge-selection-to-group", function() { mergeSelection() }) RED.actions.add("core:remove-selection-from-group", function() { removeSelection() }) $(_groupEditTemplate).appendTo("#red-ui-editor-node-configs"); } function groupSelection() { var selection = RED.view.selection(); if (selection.nodes) { var group = createGroup(selection.nodes); if (group) { var historyEvent = { t:"createGroup", groups: [ group ], dirty: RED.nodes.dirty() } RED.history.push(historyEvent); RED.view.select({nodes:[group]}); RED.nodes.dirty(true); } } } function ungroupSelection() { var selection = RED.view.selection(); if (selection.nodes) { var newSelection = []; groups = selection.nodes.filter(function(n) { return n.type === "group" }); var historyEvent = { t:"ungroup", groups: [ ], dirty: RED.nodes.dirty() } RED.history.push(historyEvent); groups.forEach(function(g) { newSelection = newSelection.concat(ungroup(g)) historyEvent.groups.push(g); }) RED.history.push(historyEvent); RED.view.select({nodes:newSelection}) RED.nodes.dirty(true); } } function ungroup(g) { var nodes = []; var parentGroup = RED.nodes.group(g.g); g.nodes.forEach(function(n) { nodes.push(n); if (parentGroup) { // Move nodes to parent group n.g = parentGroup.id; parentGroup.nodes.push(n); parentGroup.dirty = true; n.dirty = true; } else { delete n.g; } }) RED.nodes.removeGroup(g); return nodes; } function mergeSelection() { // TODO: this currently creates an entirely new group. Need to merge properties // of any existing group var selection = RED.view.selection(); if (selection.nodes) { var nodes = []; var historyEvent = { t: "multi", events: [] } var ungroupHistoryEvent = { t: "ungroup", groups: [] } var n; var parentGroup; // First pass, check they are all in the same parent // TODO: DRY mergeSelection,removeSelection,... for (var i=0; i 0) { historyEvent.events.push(ungroupHistoryEvent); } // Finally, create the new group var group = createGroup(nodes); if (group) { RED.view.select({nodes:[group]}) } historyEvent.events.push({ t:"createGroup", groups: [ group ], dirty: RED.nodes.dirty() }); RED.history.push(historyEvent); RED.nodes.dirty(true); } } function removeSelection() { var selection = RED.view.selection(); if (selection.nodes) { var nodes = []; var n; var parentGroup = RED.nodes.group(selection.nodes[0].g); if (parentGroup) { try { removeFromGroup(parentGroup,selection.nodes,true); var historyEvent = { t: "removeFromGroup", dirty: RED.nodes.dirty(), group: parentGroup, nodes: selection.nodes } RED.history.push(historyEvent); RED.nodes.dirty(true); } catch(err) { RED.notify(err,"error"); return; } } RED.view.select({nodes:selection.nodes}) } } function createGroup(nodes) { if (nodes.length === 0) { return; } // nodes is an array // each node must be on the same tab (z) var group = { id: RED.nodes.id(), type: 'group', nodes: [], style: { stroke: "#999", fill: "none" }, x: Number.POSITIVE_INFINITY, y: Number.POSITIVE_INFINITY, w: 0, h: 0, _def: RED.group.def } try { addToGroup(group,nodes); } catch(err) { RED.notify(err,"error"); return; } group.z = nodes[0].z; RED.nodes.addGroup(group); return group; } function addToGroup(group,nodes) { if (!Array.isArray(nodes)) { nodes = [nodes]; } var i,n,z; var g; // First pass - validate we can safely add these nodes to the group for (i=0;i -1) { g.nodes.splice(ni,1) } } n.g = group.id; n.dirty = true; group.nodes.push(n); group.x = Math.min(group.x,n.x-n.w/2-25-((n._def.button && n._def.align!=="right")?20:0)); group.y = Math.min(group.y,n.y-n.h/2-25); group.w = Math.max(group.w,n.x+n.w/2+25+((n._def.button && n._def.align=="right")?20:0) - group.x); group.h = Math.max(group.h,n.y+n.h/2+25-group.y); } } function removeFromGroup(group, nodes, reparent) { if (!Array.isArray(nodes)) { nodes = [nodes]; } var n; // First pass, check they are all in the same parent // TODO: DRY mergeSelection,removeSelection,... for (var i=0; i