2020-03-04 22:48:38 +01:00
|
|
|
/**
|
|
|
|
* 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 = '<script type="text/x-red" data-template-name="group">'+
|
|
|
|
'<div class="form-row">'+
|
|
|
|
'<label for="node-input-name" data-i18n="[append]editor:common.label.name"><i class="fa fa-tag"></i> </label>'+
|
|
|
|
'<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">'+
|
|
|
|
'</div>'+
|
|
|
|
|
2020-03-26 22:00:22 +01:00
|
|
|
// '<div class="node-input-group-style-tools"><span class="button-group"><button class="red-ui-button red-ui-button-small">Use default style</button><button class="red-ui-button red-ui-button-small">Set as default style</button></span></div>'+
|
2020-03-20 21:00:03 +01:00
|
|
|
|
2020-03-19 14:41:54 +01:00
|
|
|
'<div class="form-row" id="node-input-row-style-stroke">'+
|
2020-05-08 08:01:39 +02:00
|
|
|
'<label data-i18n="editor:common.label.style"></label>'+
|
2020-03-20 21:00:03 +01:00
|
|
|
'<label style="width: 70px;margin-right:10px" for="node-input-style-stroke" data-i18n="editor:common.label.line"></label>'+
|
|
|
|
'</div>'+
|
|
|
|
'<div class="form-row" style="padding-left: 100px;" id="node-input-row-style-fill">'+
|
|
|
|
'<label style="width: 70px;margin-right: 10px " for="node-input-style-fill" data-i18n="editor:common.label.fill"></label>'+
|
2020-03-04 22:48:38 +01:00
|
|
|
'</div>'+
|
2020-03-20 21:00:03 +01:00
|
|
|
'<div class="form-row">'+
|
2020-05-08 08:01:39 +02:00
|
|
|
'<label for="node-input-style-label" data-i18n="editor:common.label.label"></label>'+
|
2020-03-20 21:00:03 +01:00
|
|
|
'<input type="checkbox" id="node-input-style-label"/>'+
|
|
|
|
'</div>'+
|
|
|
|
'<div class="form-row" id="node-input-row-style-label-options">'+
|
|
|
|
'<div style="margin-left: 100px; display: inline-block">'+
|
|
|
|
'<div class="form-row">'+
|
|
|
|
'<span style="display: inline-block; min-width: 140px" id="node-input-row-style-label-color">'+
|
2020-03-26 23:50:46 +01:00
|
|
|
'<label style="width: 70px;margin-right: 10px" for="node-input-style-fill" data-i18n="editor:common.label.color"></label>'+
|
2020-03-20 21:00:03 +01:00
|
|
|
'</span>'+
|
|
|
|
'</div>'+
|
|
|
|
'<div class="form-row">'+
|
|
|
|
'<span style="display: inline-block; min-width: 140px;" id="node-input-row-style-label-position">'+
|
2020-03-26 23:50:46 +01:00
|
|
|
'<label style="width: 70px;margin-right: 10px " for="node-input-style-label-position" data-i18n="editor:common.label.position"></label>'+
|
2020-03-20 21:00:03 +01:00
|
|
|
'</span>'+
|
|
|
|
'</div>'+
|
|
|
|
'</div>'+
|
2020-03-04 22:48:38 +01:00
|
|
|
'</div>'+
|
|
|
|
|
|
|
|
'</script>';
|
|
|
|
|
2020-03-19 14:41:54 +01:00
|
|
|
var colorPalette = [
|
|
|
|
"#ff0000",
|
|
|
|
"#ffC000",
|
|
|
|
"#ffff00",
|
|
|
|
"#92d04f",
|
|
|
|
"#0070c0",
|
|
|
|
"#001f60",
|
|
|
|
"#6f2fa0",
|
|
|
|
"#000000",
|
|
|
|
"#777777"
|
|
|
|
]
|
|
|
|
var colorSteps = 3;
|
|
|
|
var colorCount = colorPalette.length;
|
|
|
|
for (var i=0,len=colorPalette.length*colorSteps;i<len;i++) {
|
|
|
|
var ci = i%colorCount;
|
|
|
|
var j = Math.floor(i/colorCount)+1;
|
|
|
|
var c = colorPalette[ci];
|
|
|
|
var r = parseInt(c.substring(1, 3), 16);
|
|
|
|
var g = parseInt(c.substring(3, 5), 16);
|
|
|
|
var b = parseInt(c.substring(5, 7), 16);
|
|
|
|
var dr = (255-r)/(colorSteps+((ci===colorCount-1) ?0:1));
|
|
|
|
var dg = (255-g)/(colorSteps+((ci===colorCount-1) ?0:1));
|
|
|
|
var db = (255-b)/(colorSteps+((ci===colorCount-1) ?0:1));
|
|
|
|
r = Math.min(255,Math.floor(r+j*dr));
|
|
|
|
g = Math.min(255,Math.floor(g+j*dg));
|
|
|
|
b = Math.min(255,Math.floor(b+j*db));
|
2020-04-03 00:23:41 +02:00
|
|
|
var s = ((r<<16) + (g<<8) + b).toString(16);
|
|
|
|
colorPalette.push('#'+'000000'.slice(0, 6-s.length)+s);
|
2020-03-19 14:41:54 +01:00
|
|
|
}
|
|
|
|
|
2020-06-18 23:24:44 +02:00
|
|
|
var defaultGroupStyle = {
|
2020-07-07 12:01:05 +02:00
|
|
|
label: true,
|
|
|
|
"label-position": "nw"
|
2020-06-18 23:24:44 +02:00
|
|
|
};
|
2020-03-19 14:41:54 +01:00
|
|
|
|
2020-03-04 22:48:38 +01:00
|
|
|
var groupDef = {
|
|
|
|
defaults:{
|
|
|
|
name:{value:""},
|
2020-06-18 23:24:44 +02:00
|
|
|
style:{value:{label:true}},
|
2020-03-09 12:14:18 +01:00
|
|
|
nodes:{value:[]}
|
2020-03-04 22:48:38 +01:00
|
|
|
},
|
|
|
|
category: "config",
|
|
|
|
oneditprepare: function() {
|
|
|
|
var style = this.style || {};
|
2020-03-19 14:41:54 +01:00
|
|
|
RED.colorPicker.create({
|
|
|
|
id:"node-input-style-stroke",
|
2020-07-07 12:01:05 +02:00
|
|
|
value: style.stroke || defaultGroupStyle.stroke || "#a4a4a4",
|
2020-03-19 14:41:54 +01:00
|
|
|
palette: colorPalette,
|
|
|
|
cellPerRow: colorCount,
|
|
|
|
cellWidth: 16,
|
|
|
|
cellHeight: 16,
|
|
|
|
cellMargin: 3,
|
|
|
|
none: true,
|
2020-07-13 21:44:53 +02:00
|
|
|
opacity: style.hasOwnProperty('stroke-opacity')?style['stroke-opacity']:(defaultGroupStyle.hasOwnProperty('stroke-opacity')?defaultGroupStyle['stroke-opacity']:1.0)
|
2020-03-19 14:41:54 +01:00
|
|
|
}).appendTo("#node-input-row-style-stroke");
|
|
|
|
RED.colorPicker.create({
|
|
|
|
id:"node-input-style-fill",
|
2020-07-13 21:44:53 +02:00
|
|
|
value: style.fill || defaultGroupStyle.fill ||"none",
|
2020-03-19 14:41:54 +01:00
|
|
|
palette: colorPalette,
|
|
|
|
cellPerRow: colorCount,
|
|
|
|
cellWidth: 16,
|
|
|
|
cellHeight: 16,
|
|
|
|
cellMargin: 3,
|
|
|
|
none: true,
|
2020-07-13 21:44:53 +02:00
|
|
|
opacity: style.hasOwnProperty('fill-opacity')?style['fill-opacity']:(defaultGroupStyle.hasOwnProperty('fill-opacity')?defaultGroupStyle['fill-opacity']:1.0)
|
2020-03-19 14:41:54 +01:00
|
|
|
}).appendTo("#node-input-row-style-fill");
|
2020-03-20 21:00:03 +01:00
|
|
|
|
|
|
|
createLayoutPicker({
|
|
|
|
id:"node-input-style-label-position",
|
|
|
|
value:style["label-position"] || "nw"
|
|
|
|
}).appendTo("#node-input-row-style-label-position");
|
|
|
|
|
|
|
|
RED.colorPicker.create({
|
|
|
|
id:"node-input-style-color",
|
2020-07-07 12:01:05 +02:00
|
|
|
value: style.color || defaultGroupStyle.color ||"#a4a4a4",
|
2020-03-20 21:00:03 +01:00
|
|
|
palette: colorPalette,
|
|
|
|
cellPerRow: colorCount,
|
|
|
|
cellWidth: 16,
|
|
|
|
cellHeight: 16,
|
|
|
|
cellMargin: 3
|
|
|
|
}).appendTo("#node-input-row-style-label-color");
|
|
|
|
|
|
|
|
$("#node-input-style-label").toggleButton({
|
|
|
|
enabledLabel: RED._("editor.show"),
|
2020-06-18 23:24:44 +02:00
|
|
|
disabledLabel: RED._("editor.show"),
|
2020-03-20 21:00:03 +01:00
|
|
|
})
|
|
|
|
|
|
|
|
$("#node-input-style-label").on("change", function(evt) {
|
|
|
|
$("#node-input-row-style-label-options").toggle($(this).prop("checked"));
|
|
|
|
})
|
|
|
|
$("#node-input-style-label").prop("checked", this.style.label)
|
|
|
|
$("#node-input-style-label").trigger("change");
|
|
|
|
|
2020-03-04 22:48:38 +01:00
|
|
|
},
|
|
|
|
oneditresize: function(size) {
|
|
|
|
},
|
|
|
|
oneditsave: function() {
|
|
|
|
this.style.stroke = $("#node-input-style-stroke").val();
|
|
|
|
this.style.fill = $("#node-input-style-fill").val();
|
2020-03-19 14:41:54 +01:00
|
|
|
this.style["stroke-opacity"] = $("#node-input-style-stroke-opacity").val();
|
|
|
|
this.style["fill-opacity"] = $("#node-input-style-fill-opacity").val();
|
2020-03-20 21:00:03 +01:00
|
|
|
this.style.label = $("#node-input-style-label").prop("checked");
|
|
|
|
if (this.style.label) {
|
|
|
|
this.style["label-position"] = $("#node-input-style-label-position").val();
|
|
|
|
this.style.color = $("#node-input-style-color").val();
|
|
|
|
} else {
|
|
|
|
delete this.style["label-position"];
|
|
|
|
delete this.style.color;
|
|
|
|
}
|
2020-03-19 14:41:54 +01:00
|
|
|
|
2020-07-07 12:01:05 +02:00
|
|
|
var node = this;
|
|
|
|
['stroke','fill','stroke-opacity','fill-opacity','color','label-position'].forEach(function(prop) {
|
|
|
|
if (node.style[prop] === defaultGroupStyle[prop]) {
|
|
|
|
delete node.style[prop]
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2020-03-26 16:24:02 +01:00
|
|
|
this.resize = true;
|
2020-03-04 22:48:38 +01:00
|
|
|
},
|
|
|
|
set:{
|
|
|
|
module: "node-red"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function init() {
|
|
|
|
|
2020-03-16 11:20:48 +01:00
|
|
|
RED.events.on("view:selection-changed",function(selection) {
|
2020-06-12 02:54:11 +02:00
|
|
|
var activateGroup = !!selection.nodes;
|
|
|
|
var activateUngroup = false;
|
|
|
|
var activateMerge = false;
|
|
|
|
var activateRemove = false;
|
|
|
|
if (activateGroup) {
|
|
|
|
selection.nodes.forEach(function (n) {
|
|
|
|
if (n.type === "group") {
|
|
|
|
activateUngroup = true;
|
|
|
|
}
|
|
|
|
if (!!n.g) {
|
|
|
|
activateRemove = true;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
if (activateUngroup) {
|
|
|
|
activateMerge = (selection.nodes.length > 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
RED.menu.setDisabled("menu-item-group-group", !activateGroup);
|
|
|
|
RED.menu.setDisabled("menu-item-group-ungroup", !activateUngroup);
|
|
|
|
RED.menu.setDisabled("menu-item-group-merge", !activateMerge);
|
|
|
|
RED.menu.setDisabled("menu-item-group-remove", !activateRemove);
|
2020-03-16 11:20:48 +01:00
|
|
|
});
|
2020-03-04 22:48:38 +01:00
|
|
|
|
|
|
|
RED.actions.add("core:group-selection", function() { groupSelection() })
|
2020-03-05 11:43:28 +01:00
|
|
|
RED.actions.add("core:ungroup-selection", function() { ungroupSelection() })
|
2020-03-05 16:52:26 +01:00
|
|
|
RED.actions.add("core:merge-selection-to-group", function() { mergeSelection() })
|
|
|
|
RED.actions.add("core:remove-selection-from-group", function() { removeSelection() })
|
2020-03-26 16:24:02 +01:00
|
|
|
RED.actions.add("core:copy-group-style", function() { copyGroupStyle() });
|
|
|
|
RED.actions.add("core:paste-group-style", function() { pasteGroupStyle() });
|
2020-03-04 22:48:38 +01:00
|
|
|
|
|
|
|
$(_groupEditTemplate).appendTo("#red-ui-editor-node-configs");
|
|
|
|
|
2020-03-26 16:24:02 +01:00
|
|
|
var groupStyleDiv = $("<div>",{
|
|
|
|
class:"red-ui-flow-group-body",
|
|
|
|
style: "position: absolute; top: -1000px;"
|
|
|
|
}).appendTo(document.body);
|
|
|
|
var groupStyle = getComputedStyle(groupStyleDiv[0]);
|
|
|
|
defaultGroupStyle = {
|
|
|
|
stroke: convertColorToHex(groupStyle.stroke),
|
|
|
|
"stroke-opacity": groupStyle.strokeOpacity,
|
|
|
|
fill: convertColorToHex(groupStyle.fill),
|
2020-06-18 23:24:44 +02:00
|
|
|
"fill-opacity": groupStyle.fillOpacity,
|
2020-07-07 12:01:05 +02:00
|
|
|
label: true,
|
|
|
|
"label-position": "nw"
|
2020-03-26 16:24:02 +01:00
|
|
|
}
|
|
|
|
groupStyleDiv.remove();
|
2020-07-07 12:01:05 +02:00
|
|
|
groupStyleDiv = $("<div>",{
|
|
|
|
class:"red-ui-flow-group-label",
|
|
|
|
style: "position: absolute; top: -1000px;"
|
|
|
|
}).appendTo(document.body);
|
|
|
|
groupStyle = getComputedStyle(groupStyleDiv[0]);
|
|
|
|
defaultGroupStyle.color = convertColorToHex(groupStyle.fill);
|
|
|
|
groupStyleDiv.remove();
|
2020-03-26 16:24:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
function convertColorToHex(c) {
|
|
|
|
var m = /^rgb\((\d+), (\d+), (\d+)\)$/.exec(c);
|
|
|
|
if (m) {
|
2020-04-03 00:23:41 +02:00
|
|
|
var s = ((parseInt(m[1])<<16) + (parseInt(m[2])<<8) + parseInt(m[3])).toString(16)
|
|
|
|
return '#'+'000000'.slice(0, 6-s.length)+s;
|
2020-03-26 16:24:02 +01:00
|
|
|
}
|
|
|
|
return c;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var groupStyleClipboard;
|
|
|
|
|
|
|
|
function copyGroupStyle() {
|
2020-10-02 17:07:22 +02:00
|
|
|
if (RED.view.state() !== RED.state.DEFAULT) { return }
|
2020-03-26 16:24:02 +01:00
|
|
|
var selection = RED.view.selection();
|
|
|
|
if (selection.nodes && selection.nodes.length === 1 && selection.nodes[0].type === 'group') {
|
|
|
|
groupStyleClipboard = JSON.parse(JSON.stringify(selection.nodes[0].style));
|
|
|
|
RED.notify(RED._("clipboard.groupStyleCopied"),{id:"clipboard"})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
function pasteGroupStyle() {
|
2020-10-02 17:07:22 +02:00
|
|
|
if (RED.view.state() !== RED.state.DEFAULT) { return }
|
2020-03-26 16:24:02 +01:00
|
|
|
if (groupStyleClipboard) {
|
|
|
|
var selection = RED.view.selection();
|
|
|
|
if (selection.nodes) {
|
|
|
|
var historyEvent = {
|
|
|
|
t:'multi',
|
|
|
|
events:[],
|
|
|
|
dirty: RED.nodes.dirty()
|
|
|
|
}
|
|
|
|
selection.nodes.forEach(function(n) {
|
|
|
|
if (n.type === 'group') {
|
|
|
|
historyEvent.events.push({
|
|
|
|
t: "edit",
|
|
|
|
node: n,
|
|
|
|
changes: {
|
|
|
|
style: JSON.parse(JSON.stringify(n.style))
|
|
|
|
},
|
|
|
|
dirty: RED.nodes.dirty()
|
|
|
|
});
|
|
|
|
n.style = JSON.parse(JSON.stringify(groupStyleClipboard));
|
|
|
|
n.dirty = true;
|
|
|
|
|
|
|
|
}
|
|
|
|
})
|
|
|
|
if (historyEvent.events.length > 0) {
|
|
|
|
RED.history.push(historyEvent);
|
|
|
|
RED.nodes.dirty(true);
|
|
|
|
RED.view.redraw();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-03-04 22:48:38 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
function groupSelection() {
|
2020-10-02 17:07:22 +02:00
|
|
|
if (RED.view.state() !== RED.state.DEFAULT) { return }
|
2020-03-04 22:48:38 +01:00
|
|
|
var selection = RED.view.selection();
|
|
|
|
if (selection.nodes) {
|
|
|
|
var group = createGroup(selection.nodes);
|
|
|
|
if (group) {
|
2020-03-14 00:01:01 +01:00
|
|
|
var historyEvent = {
|
|
|
|
t:"createGroup",
|
|
|
|
groups: [ group ],
|
|
|
|
dirty: RED.nodes.dirty()
|
|
|
|
}
|
|
|
|
RED.history.push(historyEvent);
|
|
|
|
RED.view.select({nodes:[group]});
|
|
|
|
RED.nodes.dirty(true);
|
2020-03-04 22:48:38 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-03-05 11:43:28 +01:00
|
|
|
function ungroupSelection() {
|
2020-10-02 17:07:22 +02:00
|
|
|
if (RED.view.state() !== RED.state.DEFAULT) { return }
|
2020-03-05 11:43:28 +01:00
|
|
|
var selection = RED.view.selection();
|
|
|
|
if (selection.nodes) {
|
|
|
|
var newSelection = [];
|
|
|
|
groups = selection.nodes.filter(function(n) { return n.type === "group" });
|
2020-03-14 00:01:01 +01:00
|
|
|
|
|
|
|
var historyEvent = {
|
|
|
|
t:"ungroup",
|
|
|
|
groups: [ ],
|
|
|
|
dirty: RED.nodes.dirty()
|
|
|
|
}
|
|
|
|
RED.history.push(historyEvent);
|
|
|
|
|
|
|
|
|
2020-03-05 11:43:28 +01:00
|
|
|
groups.forEach(function(g) {
|
2020-03-05 16:52:26 +01:00
|
|
|
newSelection = newSelection.concat(ungroup(g))
|
2020-03-14 00:01:01 +01:00
|
|
|
historyEvent.groups.push(g);
|
2020-03-05 16:52:26 +01:00
|
|
|
})
|
2020-03-14 00:01:01 +01:00
|
|
|
RED.history.push(historyEvent);
|
2020-03-05 16:52:26 +01:00
|
|
|
RED.view.select({nodes:newSelection})
|
2020-03-14 00:01:01 +01:00
|
|
|
RED.nodes.dirty(true);
|
2020-03-05 16:52:26 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
2020-04-20 22:30:03 +02:00
|
|
|
if (n.type === 'group') {
|
|
|
|
RED.events.emit("groups:change",n)
|
|
|
|
} else {
|
|
|
|
RED.events.emit("nodes:change",n)
|
|
|
|
}
|
2020-03-05 16:52:26 +01:00
|
|
|
})
|
|
|
|
RED.nodes.removeGroup(g);
|
|
|
|
return nodes;
|
|
|
|
}
|
|
|
|
|
|
|
|
function mergeSelection() {
|
2020-10-02 17:07:22 +02:00
|
|
|
if (RED.view.state() !== RED.state.DEFAULT) { return }
|
2020-03-05 16:52:26 +01:00
|
|
|
var selection = RED.view.selection();
|
|
|
|
if (selection.nodes) {
|
|
|
|
var nodes = [];
|
2020-03-14 00:01:01 +01:00
|
|
|
|
|
|
|
var historyEvent = {
|
|
|
|
t: "multi",
|
|
|
|
events: []
|
|
|
|
}
|
|
|
|
var ungroupHistoryEvent = {
|
|
|
|
t: "ungroup",
|
|
|
|
groups: []
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-03-05 16:52:26 +01:00
|
|
|
var n;
|
|
|
|
var parentGroup;
|
|
|
|
// First pass, check they are all in the same parent
|
|
|
|
// TODO: DRY mergeSelection,removeSelection,...
|
|
|
|
for (var i=0; i<selection.nodes.length; i++) {
|
|
|
|
n = selection.nodes[i];
|
|
|
|
if (i === 0) {
|
|
|
|
parentGroup = n.g;
|
|
|
|
} else if (n.g !== parentGroup) {
|
2020-03-26 23:50:46 +01:00
|
|
|
RED.notify(RED._("group.errors.cannotCreateDiffGroups"),"error");
|
2020-03-05 16:52:26 +01:00
|
|
|
return;
|
2020-03-05 11:43:28 +01:00
|
|
|
}
|
2020-03-05 16:52:26 +01:00
|
|
|
}
|
2020-09-03 14:28:35 +02:00
|
|
|
var existingGroup;
|
|
|
|
|
2020-03-05 16:52:26 +01:00
|
|
|
// Second pass, ungroup any groups in the selection and add their contents
|
|
|
|
// to the selection
|
|
|
|
for (var i=0; i<selection.nodes.length; i++) {
|
|
|
|
n = selection.nodes[i];
|
|
|
|
if (n.type === "group") {
|
2020-09-03 14:28:35 +02:00
|
|
|
if (!existingGroup) {
|
|
|
|
existingGroup = n;
|
|
|
|
}
|
2020-03-14 00:01:01 +01:00
|
|
|
ungroupHistoryEvent.groups.push(n);
|
2020-03-05 16:52:26 +01:00
|
|
|
nodes = nodes.concat(ungroup(n));
|
|
|
|
} else {
|
|
|
|
nodes.push(n);
|
|
|
|
}
|
|
|
|
n.dirty = true;
|
|
|
|
}
|
2020-03-14 00:01:01 +01:00
|
|
|
if (ungroupHistoryEvent.groups.length > 0) {
|
|
|
|
historyEvent.events.push(ungroupHistoryEvent);
|
|
|
|
}
|
2020-03-05 16:52:26 +01:00
|
|
|
// Finally, create the new group
|
|
|
|
var group = createGroup(nodes);
|
|
|
|
if (group) {
|
2020-09-03 14:28:35 +02:00
|
|
|
if (existingGroup) {
|
|
|
|
group.style = existingGroup.style;
|
|
|
|
group.name = existingGroup.name;
|
|
|
|
}
|
2020-03-05 16:52:26 +01:00
|
|
|
RED.view.select({nodes:[group]})
|
|
|
|
}
|
2020-03-14 00:01:01 +01:00
|
|
|
historyEvent.events.push({
|
|
|
|
t:"createGroup",
|
|
|
|
groups: [ group ],
|
|
|
|
dirty: RED.nodes.dirty()
|
|
|
|
});
|
|
|
|
RED.history.push(historyEvent);
|
|
|
|
RED.nodes.dirty(true);
|
2020-03-05 16:52:26 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function removeSelection() {
|
2020-10-02 17:07:22 +02:00
|
|
|
if (RED.view.state() !== RED.state.DEFAULT) { return }
|
2020-03-05 16:52:26 +01:00
|
|
|
var selection = RED.view.selection();
|
|
|
|
if (selection.nodes) {
|
|
|
|
var nodes = [];
|
|
|
|
var n;
|
2020-03-14 00:01:01 +01:00
|
|
|
var parentGroup = RED.nodes.group(selection.nodes[0].g);
|
2020-03-05 16:52:26 +01:00
|
|
|
if (parentGroup) {
|
2020-03-14 00:01:01 +01:00
|
|
|
try {
|
2020-03-16 23:51:54 +01:00
|
|
|
removeFromGroup(parentGroup,selection.nodes,true);
|
2020-03-14 00:01:01 +01:00
|
|
|
var historyEvent = {
|
|
|
|
t: "removeFromGroup",
|
|
|
|
dirty: RED.nodes.dirty(),
|
|
|
|
group: parentGroup,
|
|
|
|
nodes: selection.nodes
|
2020-03-05 11:43:28 +01:00
|
|
|
}
|
2020-03-14 00:01:01 +01:00
|
|
|
RED.history.push(historyEvent);
|
|
|
|
RED.nodes.dirty(true);
|
|
|
|
} catch(err) {
|
|
|
|
RED.notify(err,"error");
|
|
|
|
return;
|
2020-03-05 16:52:26 +01:00
|
|
|
}
|
|
|
|
}
|
2020-03-14 00:01:01 +01:00
|
|
|
RED.view.select({nodes:selection.nodes})
|
2020-03-05 11:43:28 +01:00
|
|
|
}
|
|
|
|
}
|
2020-03-04 22:48:38 +01:00
|
|
|
function createGroup(nodes) {
|
|
|
|
if (nodes.length === 0) {
|
|
|
|
return;
|
|
|
|
}
|
2020-03-24 15:05:35 +01:00
|
|
|
if (nodes.filter(function(n) { return n.type === "subflow" }).length > 0) {
|
2020-03-26 23:50:46 +01:00
|
|
|
RED.notify(RED._("group.errors.cannotAddSubflowPorts"),"error");
|
2020-03-24 15:05:35 +01:00
|
|
|
return;
|
|
|
|
}
|
2020-03-04 22:48:38 +01:00
|
|
|
// nodes is an array
|
|
|
|
// each node must be on the same tab (z)
|
|
|
|
var group = {
|
|
|
|
id: RED.nodes.id(),
|
|
|
|
type: 'group',
|
|
|
|
nodes: [],
|
2020-03-26 16:24:02 +01:00
|
|
|
style: JSON.parse(JSON.stringify(defaultGroupStyle)),
|
2020-03-04 22:48:38 +01:00
|
|
|
x: Number.POSITIVE_INFINITY,
|
|
|
|
y: Number.POSITIVE_INFINITY,
|
|
|
|
w: 0,
|
|
|
|
h: 0,
|
|
|
|
_def: RED.group.def
|
|
|
|
}
|
2020-04-27 12:06:28 +02:00
|
|
|
|
|
|
|
group.z = nodes[0].z;
|
|
|
|
RED.nodes.addGroup(group);
|
|
|
|
|
2020-03-04 22:48:38 +01:00
|
|
|
try {
|
|
|
|
addToGroup(group,nodes);
|
|
|
|
} catch(err) {
|
|
|
|
RED.notify(err,"error");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
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<nodes.length;i++) {
|
|
|
|
n = nodes[i]
|
|
|
|
if (!n.z) {
|
|
|
|
throw new Error("Cannot add node without a z property to a group")
|
|
|
|
}
|
|
|
|
if (!z) {
|
|
|
|
z = n.z;
|
|
|
|
} else if (z !== n.z) {
|
2020-03-26 23:50:46 +01:00
|
|
|
throw new Error("Cannot add nooes with different z properties")
|
2020-03-04 22:48:38 +01:00
|
|
|
}
|
|
|
|
if (n.g) {
|
|
|
|
// This is already in a group.
|
|
|
|
// - check they are all in the same group
|
|
|
|
if (!g) {
|
|
|
|
if (i!==0) {
|
|
|
|
// TODO: this might be ok when merging groups
|
2020-03-26 23:50:46 +01:00
|
|
|
throw new Error(RED._("group.errors.cannotCreateDiffGroups"))
|
2020-03-04 22:48:38 +01:00
|
|
|
}
|
|
|
|
g = n.g
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (g !== n.g) {
|
2020-03-26 23:50:46 +01:00
|
|
|
throw new Error(RED._("group.errors.cannotCreateDiffGroups"))
|
2020-03-04 22:48:38 +01:00
|
|
|
}
|
|
|
|
}
|
2020-03-24 15:05:09 +01:00
|
|
|
// The nodes are already in a group. The assumption is they should be
|
|
|
|
// wrapped in the newly provided group, and that group added to in their
|
|
|
|
// place to the existing containing group.
|
2020-03-04 22:48:38 +01:00
|
|
|
if (g) {
|
|
|
|
g = RED.nodes.group(g);
|
|
|
|
g.nodes.push(group);
|
2020-03-05 16:52:26 +01:00
|
|
|
g.dirty = true;
|
2020-03-04 22:48:38 +01:00
|
|
|
group.g = g.id;
|
|
|
|
}
|
|
|
|
// Second pass - add them to the group
|
|
|
|
for (i=0;i<nodes.length;i++) {
|
|
|
|
n = nodes[i];
|
2020-03-24 15:05:35 +01:00
|
|
|
if (n.type !== "subflow") {
|
|
|
|
if (g && n.g === g.id) {
|
|
|
|
var ni = g.nodes.indexOf(n);
|
|
|
|
if (ni > -1) {
|
|
|
|
g.nodes.splice(ni,1)
|
|
|
|
}
|
2020-03-04 22:48:38 +01:00
|
|
|
}
|
2020-03-24 15:05:35 +01:00
|
|
|
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);
|
2020-04-20 22:30:03 +02:00
|
|
|
if (n.type === 'group') {
|
|
|
|
RED.events.emit("groups:change",n)
|
|
|
|
} else {
|
|
|
|
RED.events.emit("nodes:change",n)
|
|
|
|
}
|
2020-03-04 22:48:38 +01:00
|
|
|
}
|
|
|
|
}
|
2020-05-14 23:08:25 +02:00
|
|
|
if (g) {
|
|
|
|
RED.events.emit("groups:change",group)
|
|
|
|
}
|
2020-03-24 15:05:35 +01:00
|
|
|
markDirty(group);
|
2020-03-04 22:48:38 +01:00
|
|
|
}
|
2020-03-16 23:51:54 +01:00
|
|
|
function removeFromGroup(group, nodes, reparent) {
|
2020-03-14 00:01:01 +01:00
|
|
|
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<nodes.length; i++) {
|
|
|
|
if (nodes[i].g !== group.id) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
var parentGroup = RED.nodes.group(group.g);
|
|
|
|
for (var i=0; i<nodes.length; i++) {
|
|
|
|
n = nodes[i];
|
|
|
|
n.dirty = true;
|
|
|
|
var index = group.nodes.indexOf(n);
|
|
|
|
group.nodes.splice(index,1);
|
2020-03-16 23:51:54 +01:00
|
|
|
if (reparent && group.g) {
|
2020-03-14 00:01:01 +01:00
|
|
|
n.g = group.g
|
|
|
|
parentGroup.nodes.push(n);
|
|
|
|
} else {
|
|
|
|
delete n.g;
|
|
|
|
}
|
2020-04-20 22:30:03 +02:00
|
|
|
if (n.type === 'group') {
|
|
|
|
RED.events.emit("groups:change",n)
|
|
|
|
} else {
|
|
|
|
RED.events.emit("nodes:change",n)
|
|
|
|
}
|
2020-03-14 00:01:01 +01:00
|
|
|
}
|
2020-03-24 15:05:09 +01:00
|
|
|
markDirty(group);
|
2020-03-14 00:01:01 +01:00
|
|
|
}
|
2020-03-04 22:48:38 +01:00
|
|
|
|
|
|
|
function getNodes(group,recursive) {
|
|
|
|
var nodes = [];
|
|
|
|
group.nodes.forEach(function(n) {
|
2020-03-26 16:27:34 +01:00
|
|
|
nodes.push(n);
|
|
|
|
if (recursive && n.type === 'group') {
|
2020-03-04 22:48:38 +01:00
|
|
|
nodes = nodes.concat(getNodes(n,recursive))
|
|
|
|
}
|
|
|
|
})
|
|
|
|
return nodes;
|
|
|
|
}
|
|
|
|
|
|
|
|
function groupContains(group,item) {
|
|
|
|
if (item.g === group.id) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
for (var i=0;i<group.nodes.length;i++) {
|
|
|
|
if (group.nodes[i].type === "group") {
|
|
|
|
if (groupContains(group.nodes[i],item)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
function getRootGroup(group) {
|
|
|
|
if (!group.g) {
|
|
|
|
return group;
|
|
|
|
}
|
|
|
|
return getRootGroup(RED.nodes.group(group.g))
|
|
|
|
}
|
|
|
|
|
2020-03-20 21:00:03 +01:00
|
|
|
function createLayoutPicker(options) {
|
|
|
|
|
|
|
|
var container = $("<div>",{style:"display:inline-block"});
|
|
|
|
var layoutHiddenInput = $("<input/>", { id: options.id, type: "hidden", value: options.value }).appendTo(container);
|
|
|
|
|
|
|
|
var layoutButton = $('<button type="button" class="red-ui-button red-ui-editor-node-appearance-button">').appendTo(container);
|
|
|
|
$('<i class="fa fa-caret-down"></i>').appendTo(layoutButton);
|
|
|
|
|
|
|
|
var layoutDispContainer = $('<div>',{class:"red-ui-search-result-node"}).appendTo(layoutButton);
|
|
|
|
var layoutDisp = $('<div>',{class:"red-ui-group-layout-picker-cell-text red-ui-group-layout-text-pos-"}).appendTo(layoutDispContainer);
|
|
|
|
|
|
|
|
var refreshDisplay = function() {
|
|
|
|
var val = layoutHiddenInput.val();
|
|
|
|
layoutDisp.removeClass().addClass("red-ui-group-layout-picker-cell-text red-ui-group-layout-text-pos-"+val)
|
|
|
|
}
|
|
|
|
layoutButton.on("click", function(e) {
|
|
|
|
var picker = $("<div/>", {
|
|
|
|
class: "red-ui-group-layout-picker"
|
|
|
|
}).css({
|
|
|
|
width: "126px"
|
|
|
|
});
|
|
|
|
|
|
|
|
var row = null;
|
|
|
|
|
|
|
|
row = $("<div/>").appendTo(picker);
|
2020-06-18 23:24:44 +02:00
|
|
|
var currentButton;
|
2020-03-20 21:00:03 +01:00
|
|
|
for (var y=0;y<2;y++) { //red-ui-group-layout-text-pos
|
|
|
|
var yComponent= "ns"[y];
|
|
|
|
row = $("<div/>").appendTo(picker);
|
|
|
|
for (var x=0;x<3;x++) {
|
|
|
|
var xComponent = ["w","","e"][x];
|
|
|
|
var val = yComponent+xComponent;
|
2020-06-18 23:24:44 +02:00
|
|
|
var button = $("<button/>", { class:"red-ui-search-result-node red-ui-button","data-pos":val }).appendTo(row);
|
2020-03-20 21:00:03 +01:00
|
|
|
button.on("click", function (e) {
|
|
|
|
e.preventDefault();
|
|
|
|
layoutHiddenInput.val($(this).data("pos"));
|
|
|
|
layoutPanel.hide()
|
|
|
|
refreshDisplay();
|
|
|
|
});
|
|
|
|
$('<div>',{class:"red-ui-group-layout-picker-cell-text red-ui-group-layout-text-pos-"+val}).appendTo(button);
|
2020-06-18 23:24:44 +02:00
|
|
|
if (val === layoutHiddenInput.val()) {
|
|
|
|
currentButton = button;
|
|
|
|
}
|
2020-03-20 21:00:03 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
refreshDisplay();
|
|
|
|
var layoutPanel = RED.popover.panel(picker);
|
|
|
|
layoutPanel.show({
|
2020-06-18 23:24:44 +02:00
|
|
|
target: layoutButton,
|
|
|
|
onclose: function() {
|
|
|
|
layoutButton.focus();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
if (currentButton) {
|
|
|
|
currentButton.focus();
|
|
|
|
}
|
2020-03-20 21:00:03 +01:00
|
|
|
})
|
|
|
|
|
|
|
|
refreshDisplay();
|
|
|
|
|
|
|
|
return container;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2020-03-24 15:05:09 +01:00
|
|
|
function markDirty(group) {
|
|
|
|
group.dirty = true;
|
|
|
|
while(group) {
|
|
|
|
group.dirty = true;
|
|
|
|
group = RED.nodes.group(group.g);
|
|
|
|
}
|
|
|
|
}
|
2020-03-20 21:00:03 +01:00
|
|
|
|
2020-03-04 22:48:38 +01:00
|
|
|
|
|
|
|
return {
|
|
|
|
def: groupDef,
|
|
|
|
init: init,
|
|
|
|
createGroup: createGroup,
|
2020-03-14 00:01:01 +01:00
|
|
|
ungroup: ungroup,
|
2020-03-04 22:48:38 +01:00
|
|
|
addToGroup: addToGroup,
|
2020-03-14 00:01:01 +01:00
|
|
|
removeFromGroup: removeFromGroup,
|
2020-03-04 22:48:38 +01:00
|
|
|
getNodes: getNodes,
|
2020-03-24 15:05:09 +01:00
|
|
|
contains: groupContains,
|
|
|
|
markDirty: markDirty
|
2020-03-04 22:48:38 +01:00
|
|
|
}
|
|
|
|
})();
|