2015-03-12 01:08:47 +01:00
|
|
|
/**
|
2017-01-11 16:24:33 +01:00
|
|
|
* Copyright JS Foundation and other contributors, http://js.foundation
|
2015-03-12 01:08:47 +01:00
|
|
|
*
|
|
|
|
* 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.subflow = (function() {
|
2015-07-01 00:42:03 +02:00
|
|
|
|
2019-08-12 16:01:54 +02:00
|
|
|
var currentLocale = "en-US";
|
|
|
|
|
2019-01-26 15:15:20 +01:00
|
|
|
var _subflowEditTemplate = '<script type="text/x-red" data-template-name="subflow">'+
|
|
|
|
'<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"></div>'+
|
2019-08-12 16:01:54 +02:00
|
|
|
'<div id="subflow-input-ui"></div>'+
|
2019-01-26 15:15:20 +01:00
|
|
|
'</script>';
|
2019-02-04 15:39:00 +01:00
|
|
|
|
2018-08-23 22:00:19 +02:00
|
|
|
var _subflowTemplateEditTemplate = '<script type="text/x-red" data-template-name="subflow-template">'+
|
2019-08-12 16:01:54 +02:00
|
|
|
'<div class="form-row"><label for="subflow-input-name" data-i18n="[append]common.label.name"><i class="fa fa-tag"></i> </label><input type="text" id="subflow-input-name"></div>'+
|
|
|
|
'<div class="form-row">'+
|
|
|
|
'<ul style="margin-bottom: 20px;" id="subflow-env-tabs"></ul>'+
|
|
|
|
'</div>'+
|
|
|
|
'<div id="subflow-env-tabs-content">'+
|
|
|
|
'<div id="subflow-env-tab-edit">'+
|
|
|
|
'<div class="form-row node-input-env-container-row" id="subflow-input-edit-ui">'+
|
|
|
|
'<ol class="red-ui-editor-subflow-env-list" id="node-input-env-container"></ol>'+
|
|
|
|
'<div class="node-input-env-locales-row"><i class="fa fa-language"></i> <select id="subflow-input-env-locale"></select></div>'+
|
|
|
|
'</div>'+
|
|
|
|
'</div>'+
|
|
|
|
'<div id="subflow-env-tab-preview">'+
|
|
|
|
'<div id="subflow-input-ui"/>'+
|
|
|
|
'</div>'+
|
|
|
|
'</div>'+
|
2018-08-23 22:00:19 +02:00
|
|
|
'</script>';
|
2018-04-26 18:53:45 +02:00
|
|
|
|
2015-07-30 12:03:37 +02:00
|
|
|
function findAvailableSubflowIOPosition(subflow,isInput) {
|
|
|
|
var pos = {x:50,y:30};
|
|
|
|
if (!isInput) {
|
|
|
|
pos.x += 110;
|
|
|
|
}
|
2019-02-02 00:44:50 +01:00
|
|
|
var ports = [].concat(subflow.out).concat(subflow.in);
|
|
|
|
if (subflow.status) {
|
|
|
|
ports.push(subflow.status);
|
|
|
|
}
|
|
|
|
ports.sort(function(A,B) {
|
|
|
|
return A.x-B.x;
|
|
|
|
});
|
|
|
|
for (var i=0; i<ports.length; i++) {
|
|
|
|
var port = ports[i];
|
2015-03-12 01:08:47 +01:00
|
|
|
if (port.x == pos.x && port.y == pos.y) {
|
|
|
|
pos.x += 55;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return pos;
|
|
|
|
}
|
2015-07-01 00:42:03 +02:00
|
|
|
|
2015-03-12 01:08:47 +01:00
|
|
|
function addSubflowInput() {
|
2015-03-13 00:38:37 +01:00
|
|
|
var subflow = RED.nodes.subflow(RED.workspaces.active());
|
2015-07-30 12:03:37 +02:00
|
|
|
if (subflow.in.length === 1) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
var position = findAvailableSubflowIOPosition(subflow,true);
|
2015-03-12 01:08:47 +01:00
|
|
|
var newInput = {
|
|
|
|
type:"subflow",
|
|
|
|
direction:"in",
|
|
|
|
z:subflow.id,
|
|
|
|
i:subflow.in.length,
|
|
|
|
x:position.x,
|
|
|
|
y:position.y,
|
|
|
|
id:RED.nodes.id()
|
|
|
|
};
|
|
|
|
var oldInCount = subflow.in.length;
|
|
|
|
subflow.in.push(newInput);
|
|
|
|
subflow.dirty = true;
|
2015-03-15 22:54:36 +01:00
|
|
|
var wasDirty = RED.nodes.dirty();
|
2015-03-12 01:08:47 +01:00
|
|
|
var wasChanged = subflow.changed;
|
|
|
|
subflow.changed = true;
|
2015-07-30 12:03:37 +02:00
|
|
|
var result = refresh(true);
|
2015-03-12 01:08:47 +01:00
|
|
|
var historyEvent = {
|
|
|
|
t:'edit',
|
|
|
|
node:subflow,
|
|
|
|
dirty:wasDirty,
|
|
|
|
changed:wasChanged,
|
|
|
|
subflow: {
|
2015-07-30 12:03:37 +02:00
|
|
|
inputCount: oldInCount,
|
|
|
|
instances: result.instances
|
2015-03-12 01:08:47 +01:00
|
|
|
}
|
|
|
|
};
|
|
|
|
RED.history.push(historyEvent);
|
|
|
|
RED.view.select();
|
2015-07-30 12:03:37 +02:00
|
|
|
RED.nodes.dirty(true);
|
|
|
|
RED.view.redraw();
|
2019-05-16 23:32:28 +02:00
|
|
|
$("#red-ui-subflow-input-add").addClass("active");
|
|
|
|
$("#red-ui-subflow-input-remove").removeClass("active");
|
2019-03-05 15:37:07 +01:00
|
|
|
RED.palette.refresh();
|
2015-07-30 12:03:37 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function removeSubflowInput() {
|
|
|
|
var activeSubflow = RED.nodes.subflow(RED.workspaces.active());
|
|
|
|
if (activeSubflow.in.length === 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
var removedInput = activeSubflow.in[0];
|
|
|
|
var removedInputLinks = [];
|
|
|
|
RED.nodes.eachLink(function(l) {
|
|
|
|
if (l.source.type == "subflow" && l.source.z == activeSubflow.id && l.source.i == removedInput.i) {
|
|
|
|
removedInputLinks.push(l);
|
|
|
|
} else if (l.target.type == "subflow:"+activeSubflow.id) {
|
|
|
|
removedInputLinks.push(l);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
removedInputLinks.forEach(function(l) { RED.nodes.removeLink(l)});
|
|
|
|
activeSubflow.in = [];
|
2019-05-16 23:32:28 +02:00
|
|
|
$("#red-ui-subflow-input-add").removeClass("active");
|
|
|
|
$("#red-ui-subflow-input-remove").addClass("active");
|
2015-07-30 12:03:37 +02:00
|
|
|
activeSubflow.changed = true;
|
2019-03-05 15:37:07 +01:00
|
|
|
RED.palette.refresh();
|
2015-07-30 12:03:37 +02:00
|
|
|
return {subflowInputs: [ removedInput ], links:removedInputLinks};
|
2015-03-12 01:08:47 +01:00
|
|
|
}
|
2015-07-01 00:42:03 +02:00
|
|
|
|
2015-03-12 01:08:47 +01:00
|
|
|
function addSubflowOutput(id) {
|
2015-03-13 00:38:37 +01:00
|
|
|
var subflow = RED.nodes.subflow(RED.workspaces.active());
|
2015-07-30 12:03:37 +02:00
|
|
|
var position = findAvailableSubflowIOPosition(subflow,false);
|
2015-07-01 00:42:03 +02:00
|
|
|
|
2015-03-12 01:08:47 +01:00
|
|
|
var newOutput = {
|
|
|
|
type:"subflow",
|
|
|
|
direction:"out",
|
|
|
|
z:subflow.id,
|
|
|
|
i:subflow.out.length,
|
|
|
|
x:position.x,
|
|
|
|
y:position.y,
|
|
|
|
id:RED.nodes.id()
|
|
|
|
};
|
|
|
|
var oldOutCount = subflow.out.length;
|
|
|
|
subflow.out.push(newOutput);
|
|
|
|
subflow.dirty = true;
|
2015-03-15 22:54:36 +01:00
|
|
|
var wasDirty = RED.nodes.dirty();
|
2015-03-12 01:08:47 +01:00
|
|
|
var wasChanged = subflow.changed;
|
|
|
|
subflow.changed = true;
|
2015-07-01 00:42:03 +02:00
|
|
|
|
2015-07-30 12:03:37 +02:00
|
|
|
var result = refresh(true);
|
|
|
|
|
2015-03-12 01:08:47 +01:00
|
|
|
var historyEvent = {
|
|
|
|
t:'edit',
|
|
|
|
node:subflow,
|
|
|
|
dirty:wasDirty,
|
|
|
|
changed:wasChanged,
|
|
|
|
subflow: {
|
2015-07-30 12:03:37 +02:00
|
|
|
outputCount: oldOutCount,
|
|
|
|
instances: result.instances
|
2015-03-12 01:08:47 +01:00
|
|
|
}
|
|
|
|
};
|
|
|
|
RED.history.push(historyEvent);
|
|
|
|
RED.view.select();
|
2015-07-30 12:03:37 +02:00
|
|
|
RED.nodes.dirty(true);
|
|
|
|
RED.view.redraw();
|
2019-05-16 23:32:28 +02:00
|
|
|
$("#red-ui-subflow-output .spinner-value").text(subflow.out.length);
|
2019-03-05 15:37:07 +01:00
|
|
|
RED.palette.refresh();
|
2015-03-12 01:08:47 +01:00
|
|
|
}
|
2015-07-01 00:42:03 +02:00
|
|
|
|
2015-07-30 12:03:37 +02:00
|
|
|
function removeSubflowOutput(removedSubflowOutputs) {
|
|
|
|
var activeSubflow = RED.nodes.subflow(RED.workspaces.active());
|
|
|
|
if (activeSubflow.out.length === 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (typeof removedSubflowOutputs === "undefined") {
|
|
|
|
removedSubflowOutputs = [activeSubflow.out[activeSubflow.out.length-1]];
|
|
|
|
}
|
|
|
|
var removedLinks = [];
|
|
|
|
removedSubflowOutputs.sort(function(a,b) { return b.i-a.i});
|
|
|
|
for (i=0;i<removedSubflowOutputs.length;i++) {
|
|
|
|
var output = removedSubflowOutputs[i];
|
|
|
|
activeSubflow.out.splice(output.i,1);
|
|
|
|
var subflowRemovedLinks = [];
|
|
|
|
var subflowMovedLinks = [];
|
|
|
|
RED.nodes.eachLink(function(l) {
|
|
|
|
if (l.target.type == "subflow" && l.target.z == activeSubflow.id && l.target.i == output.i) {
|
|
|
|
subflowRemovedLinks.push(l);
|
|
|
|
}
|
|
|
|
if (l.source.type == "subflow:"+activeSubflow.id) {
|
|
|
|
if (l.sourcePort == output.i) {
|
|
|
|
subflowRemovedLinks.push(l);
|
|
|
|
} else if (l.sourcePort > output.i) {
|
|
|
|
subflowMovedLinks.push(l);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
subflowRemovedLinks.forEach(function(l) { RED.nodes.removeLink(l)});
|
|
|
|
subflowMovedLinks.forEach(function(l) { l.sourcePort--; });
|
|
|
|
|
|
|
|
removedLinks = removedLinks.concat(subflowRemovedLinks);
|
|
|
|
for (var j=output.i;j<activeSubflow.out.length;j++) {
|
|
|
|
activeSubflow.out[j].i--;
|
|
|
|
activeSubflow.out[j].dirty = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
activeSubflow.changed = true;
|
2019-03-05 15:37:07 +01:00
|
|
|
RED.palette.refresh();
|
2015-07-30 12:03:37 +02:00
|
|
|
return {subflowOutputs: removedSubflowOutputs, links: removedLinks}
|
|
|
|
}
|
|
|
|
|
2019-02-02 00:44:50 +01:00
|
|
|
function addSubflowStatus() {
|
|
|
|
var subflow = RED.nodes.subflow(RED.workspaces.active());
|
|
|
|
if (subflow.status) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
var position = findAvailableSubflowIOPosition(subflow,false);
|
|
|
|
var statusNode = {
|
|
|
|
type:"subflow",
|
|
|
|
direction:"status",
|
|
|
|
z:subflow.id,
|
|
|
|
x:position.x,
|
|
|
|
y:position.y,
|
|
|
|
id:RED.nodes.id()
|
|
|
|
};
|
|
|
|
subflow.status = statusNode;
|
|
|
|
subflow.dirty = true;
|
|
|
|
var wasDirty = RED.nodes.dirty();
|
|
|
|
var wasChanged = subflow.changed;
|
|
|
|
subflow.changed = true;
|
|
|
|
var result = refresh(true);
|
|
|
|
var historyEvent = {
|
|
|
|
t:'edit',
|
|
|
|
node:subflow,
|
|
|
|
dirty:wasDirty,
|
|
|
|
changed:wasChanged,
|
|
|
|
subflow: { status: true }
|
|
|
|
};
|
|
|
|
RED.history.push(historyEvent);
|
|
|
|
RED.view.select();
|
|
|
|
RED.nodes.dirty(true);
|
|
|
|
RED.view.redraw();
|
2019-05-16 23:32:28 +02:00
|
|
|
$("#red-ui-subflow-status").prop("checked",!!subflow.status);
|
|
|
|
$("#red-ui-subflow-status").parent().parent().toggleClass("active",!!subflow.status);
|
2019-02-02 00:44:50 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
function removeSubflowStatus() {
|
|
|
|
var subflow = RED.nodes.subflow(RED.workspaces.active());
|
|
|
|
if (!subflow.status) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
var subflowRemovedLinks = [];
|
|
|
|
RED.nodes.eachLink(function(l) {
|
|
|
|
if (l.target.type == "subflow" && l.target.z == subflow.id && l.target.direction == "status") {
|
|
|
|
subflowRemovedLinks.push(l);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
subflowRemovedLinks.forEach(function(l) { RED.nodes.removeLink(l)});
|
|
|
|
delete subflow.status;
|
|
|
|
|
2019-05-16 23:32:28 +02:00
|
|
|
$("#red-ui-subflow-status").prop("checked",!!subflow.status);
|
|
|
|
$("#red-ui-subflow-status").parent().parent().toggleClass("active",!!subflow.status);
|
2019-02-02 00:44:50 +01:00
|
|
|
|
|
|
|
return { links: subflowRemovedLinks }
|
|
|
|
}
|
|
|
|
|
2015-07-30 12:03:37 +02:00
|
|
|
function refresh(markChange) {
|
|
|
|
var activeSubflow = RED.nodes.subflow(RED.workspaces.active());
|
|
|
|
refreshToolbar(activeSubflow);
|
|
|
|
var subflowInstances = [];
|
|
|
|
if (activeSubflow) {
|
|
|
|
RED.nodes.filterNodes({type:"subflow:"+activeSubflow.id}).forEach(function(n) {
|
|
|
|
subflowInstances.push({
|
|
|
|
id: n.id,
|
|
|
|
changed: n.changed
|
|
|
|
});
|
|
|
|
if (markChange) {
|
|
|
|
n.changed = true;
|
|
|
|
}
|
|
|
|
n.inputs = activeSubflow.in.length;
|
|
|
|
n.outputs = activeSubflow.out.length;
|
|
|
|
while (n.outputs < n.ports.length) {
|
|
|
|
n.ports.pop();
|
|
|
|
}
|
|
|
|
n.resize = true;
|
|
|
|
n.dirty = true;
|
|
|
|
RED.editor.updateNodeProperties(n);
|
|
|
|
});
|
|
|
|
RED.editor.validateNode(activeSubflow);
|
|
|
|
return {
|
|
|
|
instances: subflowInstances
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-02-02 00:44:50 +01:00
|
|
|
|
2015-07-30 12:03:37 +02:00
|
|
|
function refreshToolbar(activeSubflow) {
|
|
|
|
if (activeSubflow) {
|
2019-05-16 23:32:28 +02:00
|
|
|
$("#red-ui-subflow-input-add").toggleClass("active", activeSubflow.in.length !== 0);
|
|
|
|
$("#red-ui-subflow-input-remove").toggleClass("active",activeSubflow.in.length === 0);
|
2015-07-30 12:03:37 +02:00
|
|
|
|
2019-05-16 23:32:28 +02:00
|
|
|
$("#red-ui-subflow-output .spinner-value").text(activeSubflow.out.length);
|
2019-02-02 00:44:50 +01:00
|
|
|
|
2019-05-16 23:32:28 +02:00
|
|
|
$("#red-ui-subflow-status").prop("checked",!!activeSubflow.status);
|
|
|
|
$("#red-ui-subflow-status").parent().parent().toggleClass("active",!!activeSubflow.status);
|
2019-02-02 00:44:50 +01:00
|
|
|
|
2015-07-30 12:03:37 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function showWorkspaceToolbar(activeSubflow) {
|
2019-05-01 23:41:20 +02:00
|
|
|
var toolbar = $("#red-ui-workspace-toolbar");
|
2015-07-30 12:03:37 +02:00
|
|
|
toolbar.empty();
|
|
|
|
|
2019-02-02 00:44:50 +01:00
|
|
|
// Edit properties
|
2019-05-16 23:32:28 +02:00
|
|
|
$('<a class="button" id="red-ui-subflow-edit" href="#" data-i18n="[append]subflow.editSubflowProperties"><i class="fa fa-pencil"></i> </a>').appendTo(toolbar);
|
2019-02-02 00:44:50 +01:00
|
|
|
|
|
|
|
// Inputs
|
2015-07-30 12:03:37 +02:00
|
|
|
$('<span style="margin-left: 5px;" data-i18n="subflow.input"></span> '+
|
|
|
|
'<div style="display: inline-block;" class="button-group">'+
|
2019-05-16 23:32:28 +02:00
|
|
|
'<a id="red-ui-subflow-input-remove" class="button active" href="#">0</a>'+
|
|
|
|
'<a id="red-ui-subflow-input-add" class="button" href="#">1</a>'+
|
2015-07-30 12:03:37 +02:00
|
|
|
'</div>').appendTo(toolbar);
|
|
|
|
|
2019-02-02 00:44:50 +01:00
|
|
|
// Outputs
|
2019-05-16 23:32:28 +02:00
|
|
|
$('<span style="margin-left: 5px;" data-i18n="subflow.output"></span> <div id="red-ui-subflow-output" style="display: inline-block;" class="button-group spinner-group">'+
|
|
|
|
'<a id="red-ui-subflow-output-remove" class="button" href="#"><i class="fa fa-minus"></i></a>'+
|
2015-07-30 12:03:37 +02:00
|
|
|
'<div class="spinner-value">3</div>'+
|
2019-05-16 23:32:28 +02:00
|
|
|
'<a id="red-ui-subflow-output-add" class="button" href="#"><i class="fa fa-plus"></i></a>'+
|
2015-07-30 12:03:37 +02:00
|
|
|
'</div>').appendTo(toolbar);
|
|
|
|
|
2019-02-02 00:44:50 +01:00
|
|
|
// Status
|
2019-05-16 23:32:28 +02:00
|
|
|
$('<span class="button-group"><span class="button" style="padding:0"><label for="red-ui-subflow-status"><input id="red-ui-subflow-status" type="checkbox"> <span data-i18n="subflow.status"></span></label></span></span>').appendTo(toolbar);
|
2019-02-02 00:44:50 +01:00
|
|
|
|
2019-05-16 23:32:28 +02:00
|
|
|
// $('<a class="button disabled" id="red-ui-subflow-add-input" href="#" data-i18n="[append]subflow.input"><i class="fa fa-plus"></i> </a>').appendTo(toolbar);
|
|
|
|
// $('<a class="button" id="red-ui-subflow-add-output" href="#" data-i18n="[append]subflow.output"><i class="fa fa-plus"></i> </a>').appendTo(toolbar);
|
2019-02-02 00:44:50 +01:00
|
|
|
|
|
|
|
// Delete
|
2019-05-16 23:32:28 +02:00
|
|
|
$('<a class="button" id="red-ui-subflow-delete" href="#" data-i18n="[append]subflow.deleteSubflow"><i class="fa fa-trash"></i> </a>').appendTo(toolbar);
|
2019-02-02 00:44:50 +01:00
|
|
|
|
2015-07-30 12:03:37 +02:00
|
|
|
toolbar.i18n();
|
|
|
|
|
|
|
|
|
2019-05-16 23:32:28 +02:00
|
|
|
$("#red-ui-subflow-output-remove").on("click", function(event) {
|
2015-03-12 01:08:47 +01:00
|
|
|
event.preventDefault();
|
2015-07-30 12:03:37 +02:00
|
|
|
var wasDirty = RED.nodes.dirty();
|
|
|
|
var wasChanged = activeSubflow.changed;
|
|
|
|
var result = removeSubflowOutput();
|
|
|
|
if (result) {
|
|
|
|
var inst = refresh(true);
|
|
|
|
RED.history.push({
|
|
|
|
t:'delete',
|
|
|
|
links:result.links,
|
|
|
|
subflowOutputs: result.subflowOutputs,
|
|
|
|
changed: wasChanged,
|
|
|
|
dirty:wasDirty,
|
|
|
|
subflow: {
|
|
|
|
instances: inst.instances
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
RED.view.select();
|
|
|
|
RED.nodes.dirty(true);
|
|
|
|
RED.view.redraw(true);
|
|
|
|
}
|
2015-03-12 01:08:47 +01:00
|
|
|
});
|
2019-02-02 00:44:50 +01:00
|
|
|
|
2019-05-16 23:32:28 +02:00
|
|
|
$("#red-ui-subflow-output-add").on("click", function(event) {
|
2015-07-30 12:03:37 +02:00
|
|
|
event.preventDefault();
|
|
|
|
addSubflowOutput();
|
|
|
|
});
|
|
|
|
|
2019-05-16 23:32:28 +02:00
|
|
|
$("#red-ui-subflow-input-add").on("click", function(event) {
|
2015-03-12 01:08:47 +01:00
|
|
|
event.preventDefault();
|
|
|
|
addSubflowInput();
|
|
|
|
});
|
2019-02-02 00:44:50 +01:00
|
|
|
|
2019-05-16 23:32:28 +02:00
|
|
|
$("#red-ui-subflow-input-remove").on("click", function(event) {
|
2015-03-12 01:08:47 +01:00
|
|
|
event.preventDefault();
|
2015-07-30 12:03:37 +02:00
|
|
|
var wasDirty = RED.nodes.dirty();
|
|
|
|
var wasChanged = activeSubflow.changed;
|
|
|
|
activeSubflow.changed = true;
|
|
|
|
var result = removeSubflowInput();
|
|
|
|
if (result) {
|
|
|
|
var inst = refresh(true);
|
|
|
|
RED.history.push({
|
|
|
|
t:'delete',
|
|
|
|
links:result.links,
|
|
|
|
changed: wasChanged,
|
|
|
|
subflowInputs: result.subflowInputs,
|
|
|
|
dirty:wasDirty,
|
|
|
|
subflow: {
|
|
|
|
instances: inst.instances
|
|
|
|
}
|
|
|
|
});
|
|
|
|
RED.view.select();
|
|
|
|
RED.nodes.dirty(true);
|
|
|
|
RED.view.redraw(true);
|
2015-03-12 01:08:47 +01:00
|
|
|
}
|
2015-07-30 12:03:37 +02:00
|
|
|
});
|
|
|
|
|
2019-05-16 23:32:28 +02:00
|
|
|
$("#red-ui-subflow-status").on("change", function(evt) {
|
2019-02-02 00:44:50 +01:00
|
|
|
if (this.checked) {
|
|
|
|
addSubflowStatus();
|
|
|
|
} else {
|
|
|
|
var currentStatus = activeSubflow.status;
|
|
|
|
var wasChanged = activeSubflow.changed;
|
|
|
|
var result = removeSubflowStatus();
|
|
|
|
if (result) {
|
|
|
|
activeSubflow.changed = true;
|
|
|
|
var wasDirty = RED.nodes.dirty();
|
|
|
|
RED.history.push({
|
|
|
|
t:'delete',
|
|
|
|
links:result.links,
|
|
|
|
changed: wasChanged,
|
|
|
|
dirty:wasDirty,
|
|
|
|
subflow: {
|
|
|
|
id: activeSubflow.id,
|
|
|
|
status: currentStatus
|
|
|
|
}
|
|
|
|
});
|
|
|
|
RED.view.select();
|
|
|
|
RED.nodes.dirty(true);
|
|
|
|
RED.view.redraw();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2019-05-16 23:32:28 +02:00
|
|
|
$("#red-ui-subflow-edit").on("click", function(event) {
|
2015-07-30 12:03:37 +02:00
|
|
|
RED.editor.editSubflow(RED.nodes.subflow(RED.workspaces.active()));
|
|
|
|
event.preventDefault();
|
2015-03-12 01:08:47 +01:00
|
|
|
});
|
2015-07-01 00:42:03 +02:00
|
|
|
|
2019-05-16 23:32:28 +02:00
|
|
|
$("#red-ui-subflow-delete").on("click", function(event) {
|
2015-03-12 01:08:47 +01:00
|
|
|
event.preventDefault();
|
2015-03-15 22:54:36 +01:00
|
|
|
var startDirty = RED.nodes.dirty();
|
2016-12-20 20:42:38 +01:00
|
|
|
var historyEvent = removeSubflow(RED.workspaces.active());
|
|
|
|
historyEvent.t = 'delete';
|
|
|
|
historyEvent.dirty = startDirty;
|
2015-07-01 00:42:03 +02:00
|
|
|
|
2016-12-20 20:42:38 +01:00
|
|
|
RED.history.push(historyEvent);
|
2015-09-25 14:29:55 +02:00
|
|
|
|
2015-03-12 01:08:47 +01:00
|
|
|
});
|
2015-07-01 00:42:03 +02:00
|
|
|
|
2015-07-30 12:03:37 +02:00
|
|
|
refreshToolbar(activeSubflow);
|
|
|
|
|
2019-05-01 23:41:20 +02:00
|
|
|
$("#red-ui-workspace-chart").css({"margin-top": "40px"});
|
|
|
|
$("#red-ui-workspace-toolbar").show();
|
2015-07-30 12:03:37 +02:00
|
|
|
}
|
2019-02-02 00:44:50 +01:00
|
|
|
|
2015-07-30 12:03:37 +02:00
|
|
|
function hideWorkspaceToolbar() {
|
2019-05-01 23:41:20 +02:00
|
|
|
$("#red-ui-workspace-toolbar").hide().empty();
|
|
|
|
$("#red-ui-workspace-chart").css({"margin-top": "0"});
|
2015-07-30 12:03:37 +02:00
|
|
|
}
|
|
|
|
|
2016-12-20 20:42:38 +01:00
|
|
|
function removeSubflow(id) {
|
|
|
|
var removedNodes = [];
|
|
|
|
var removedLinks = [];
|
|
|
|
|
|
|
|
var activeSubflow = RED.nodes.subflow(id);
|
2015-07-30 12:03:37 +02:00
|
|
|
|
2016-12-20 20:42:38 +01:00
|
|
|
RED.nodes.eachNode(function(n) {
|
|
|
|
if (n.type == "subflow:"+activeSubflow.id) {
|
|
|
|
removedNodes.push(n);
|
|
|
|
}
|
|
|
|
if (n.z == activeSubflow.id) {
|
|
|
|
removedNodes.push(n);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
RED.nodes.eachConfig(function(n) {
|
|
|
|
if (n.z == activeSubflow.id) {
|
|
|
|
removedNodes.push(n);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
var removedConfigNodes = [];
|
|
|
|
for (var i=0;i<removedNodes.length;i++) {
|
|
|
|
var removedEntities = RED.nodes.remove(removedNodes[i].id);
|
|
|
|
removedLinks = removedLinks.concat(removedEntities.links);
|
|
|
|
removedConfigNodes = removedConfigNodes.concat(removedEntities.nodes);
|
|
|
|
}
|
|
|
|
// TODO: this whole delete logic should be in RED.nodes.removeSubflow..
|
|
|
|
removedNodes = removedNodes.concat(removedConfigNodes);
|
|
|
|
|
|
|
|
RED.nodes.removeSubflow(activeSubflow);
|
|
|
|
RED.workspaces.remove(activeSubflow);
|
|
|
|
RED.nodes.dirty(true);
|
|
|
|
RED.view.redraw();
|
|
|
|
|
|
|
|
return {
|
|
|
|
nodes:removedNodes,
|
|
|
|
links:removedLinks,
|
2018-10-30 23:18:16 +01:00
|
|
|
subflows: [activeSubflow]
|
2016-12-20 20:42:38 +01:00
|
|
|
}
|
|
|
|
}
|
2019-02-02 00:44:50 +01:00
|
|
|
|
2015-07-30 12:03:37 +02:00
|
|
|
function init() {
|
|
|
|
RED.events.on("workspace:change",function(event) {
|
|
|
|
var activeSubflow = RED.nodes.subflow(event.workspace);
|
|
|
|
if (activeSubflow) {
|
|
|
|
showWorkspaceToolbar(activeSubflow);
|
|
|
|
} else {
|
|
|
|
hideWorkspaceToolbar();
|
|
|
|
}
|
|
|
|
});
|
2015-07-10 20:49:31 +02:00
|
|
|
RED.events.on("view:selection-changed",function(selection) {
|
2015-03-12 12:21:05 +01:00
|
|
|
if (!selection.nodes) {
|
2015-04-13 17:48:38 +02:00
|
|
|
RED.menu.setDisabled("menu-item-subflow-convert",true);
|
2015-03-12 12:21:05 +01:00
|
|
|
} else {
|
2015-04-13 17:48:38 +02:00
|
|
|
RED.menu.setDisabled("menu-item-subflow-convert",false);
|
2015-03-12 12:21:05 +01:00
|
|
|
}
|
|
|
|
});
|
2015-07-01 00:42:03 +02:00
|
|
|
|
2016-12-04 23:59:43 +01:00
|
|
|
RED.actions.add("core:create-subflow",createSubflow);
|
|
|
|
RED.actions.add("core:convert-to-subflow",convertToSubflow);
|
2018-04-26 18:53:45 +02:00
|
|
|
|
2019-05-03 22:32:12 +02:00
|
|
|
$(_subflowEditTemplate).appendTo("#red-ui-editor-node-configs");
|
|
|
|
$(_subflowTemplateEditTemplate).appendTo("#red-ui-editor-node-configs");
|
2018-04-26 18:53:45 +02:00
|
|
|
|
2015-03-12 01:08:47 +01:00
|
|
|
}
|
2015-07-01 00:42:03 +02:00
|
|
|
|
2015-03-12 01:08:47 +01:00
|
|
|
function createSubflow() {
|
|
|
|
var lastIndex = 0;
|
|
|
|
RED.nodes.eachSubflow(function(sf) {
|
|
|
|
var m = (new RegExp("^Subflow (\\d+)$")).exec(sf.name);
|
|
|
|
if (m) {
|
|
|
|
lastIndex = Math.max(lastIndex,m[1]);
|
|
|
|
}
|
|
|
|
});
|
2015-07-01 00:42:03 +02:00
|
|
|
|
2015-03-12 01:08:47 +01:00
|
|
|
var name = "Subflow "+(lastIndex+1);
|
2015-07-01 00:42:03 +02:00
|
|
|
|
2015-03-12 01:08:47 +01:00
|
|
|
var subflowId = RED.nodes.id();
|
|
|
|
var subflow = {
|
|
|
|
type:"subflow",
|
|
|
|
id:subflowId,
|
|
|
|
name:name,
|
2015-10-23 23:14:21 +02:00
|
|
|
info:"",
|
2015-03-12 01:08:47 +01:00
|
|
|
in: [],
|
|
|
|
out: []
|
|
|
|
};
|
|
|
|
RED.nodes.addSubflow(subflow);
|
|
|
|
RED.history.push({
|
|
|
|
t:'createSubflow',
|
2015-07-30 12:03:37 +02:00
|
|
|
subflow: {
|
|
|
|
subflow:subflow
|
|
|
|
},
|
2015-03-15 22:54:36 +01:00
|
|
|
dirty:RED.nodes.dirty()
|
2015-03-12 01:08:47 +01:00
|
|
|
});
|
2015-03-13 00:38:37 +01:00
|
|
|
RED.workspaces.show(subflowId);
|
2015-10-23 23:14:21 +02:00
|
|
|
RED.nodes.dirty(true);
|
2015-03-12 01:08:47 +01:00
|
|
|
}
|
2015-07-01 00:42:03 +02:00
|
|
|
|
2019-01-26 21:49:22 +01:00
|
|
|
function snapToGrid(x) {
|
|
|
|
if (RED.settings.get("editor").view['view-snap-grid']) {
|
|
|
|
x = Math.round(x / RED.view.gridSize()) * RED.view.gridSize();
|
|
|
|
}
|
|
|
|
return x;
|
|
|
|
}
|
2019-02-02 00:44:50 +01:00
|
|
|
|
2015-03-12 01:08:47 +01:00
|
|
|
function convertToSubflow() {
|
|
|
|
var selection = RED.view.selection();
|
|
|
|
if (!selection.nodes) {
|
2015-07-01 00:42:03 +02:00
|
|
|
RED.notify(RED._("subflow.errors.noNodesSelected"),"error");
|
2015-03-12 01:08:47 +01:00
|
|
|
return;
|
|
|
|
}
|
2016-06-30 00:51:08 +02:00
|
|
|
var i,n;
|
2015-03-12 01:08:47 +01:00
|
|
|
var nodes = {};
|
|
|
|
var new_links = [];
|
|
|
|
var removedLinks = [];
|
2015-07-01 00:42:03 +02:00
|
|
|
|
2015-03-12 01:08:47 +01:00
|
|
|
var candidateInputs = [];
|
|
|
|
var candidateOutputs = [];
|
2015-07-30 12:03:37 +02:00
|
|
|
var candidateInputNodes = {};
|
|
|
|
|
2015-03-12 01:08:47 +01:00
|
|
|
var boundingBox = [selection.nodes[0].x,
|
|
|
|
selection.nodes[0].y,
|
|
|
|
selection.nodes[0].x,
|
|
|
|
selection.nodes[0].y];
|
2015-07-01 00:42:03 +02:00
|
|
|
|
2015-03-12 01:08:47 +01:00
|
|
|
for (i=0;i<selection.nodes.length;i++) {
|
2016-06-30 00:51:08 +02:00
|
|
|
n = selection.nodes[i];
|
2015-03-12 01:08:47 +01:00
|
|
|
nodes[n.id] = {n:n,outputs:{}};
|
|
|
|
boundingBox = [
|
|
|
|
Math.min(boundingBox[0],n.x),
|
|
|
|
Math.min(boundingBox[1],n.y),
|
|
|
|
Math.max(boundingBox[2],n.x),
|
|
|
|
Math.max(boundingBox[3],n.y)
|
|
|
|
]
|
|
|
|
}
|
2019-02-02 00:44:50 +01:00
|
|
|
var offsetX = snapToGrid(boundingBox[0] - 200);
|
|
|
|
var offsetY = snapToGrid(boundingBox[1] - 80);
|
2019-01-26 21:49:22 +01:00
|
|
|
|
2015-07-01 00:42:03 +02:00
|
|
|
|
2019-01-26 21:49:22 +01:00
|
|
|
var center = [
|
|
|
|
snapToGrid((boundingBox[2]+boundingBox[0]) / 2),
|
|
|
|
snapToGrid((boundingBox[3]+boundingBox[1]) / 2)
|
|
|
|
];
|
2015-07-01 00:42:03 +02:00
|
|
|
|
2015-03-12 01:08:47 +01:00
|
|
|
RED.nodes.eachLink(function(link) {
|
|
|
|
if (nodes[link.source.id] && nodes[link.target.id]) {
|
|
|
|
// A link wholely within the selection
|
|
|
|
}
|
2015-07-01 00:42:03 +02:00
|
|
|
|
2015-03-12 01:08:47 +01:00
|
|
|
if (nodes[link.source.id] && !nodes[link.target.id]) {
|
|
|
|
// An outbound link from the selection
|
|
|
|
candidateOutputs.push(link);
|
|
|
|
removedLinks.push(link);
|
|
|
|
}
|
|
|
|
if (!nodes[link.source.id] && nodes[link.target.id]) {
|
|
|
|
// An inbound link
|
|
|
|
candidateInputs.push(link);
|
2015-07-30 12:03:37 +02:00
|
|
|
candidateInputNodes[link.target.id] = link.target;
|
2015-03-12 01:08:47 +01:00
|
|
|
removedLinks.push(link);
|
|
|
|
}
|
|
|
|
});
|
2015-07-01 00:42:03 +02:00
|
|
|
|
2015-03-12 01:08:47 +01:00
|
|
|
var outputs = {};
|
|
|
|
candidateOutputs = candidateOutputs.filter(function(v) {
|
|
|
|
if (outputs[v.source.id+":"+v.sourcePort]) {
|
|
|
|
outputs[v.source.id+":"+v.sourcePort].targets.push(v.target);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
v.targets = [];
|
|
|
|
v.targets.push(v.target);
|
|
|
|
outputs[v.source.id+":"+v.sourcePort] = v;
|
|
|
|
return true;
|
|
|
|
});
|
|
|
|
candidateOutputs.sort(function(a,b) { return a.source.y-b.source.y});
|
2015-07-01 00:42:03 +02:00
|
|
|
|
2015-07-30 12:03:37 +02:00
|
|
|
if (Object.keys(candidateInputNodes).length > 1) {
|
2015-07-01 00:42:03 +02:00
|
|
|
RED.notify(RED._("subflow.errors.multipleInputsToSelection"),"error");
|
2015-03-12 01:08:47 +01:00
|
|
|
return;
|
|
|
|
}
|
2015-07-01 00:42:03 +02:00
|
|
|
|
2015-03-12 01:08:47 +01:00
|
|
|
var lastIndex = 0;
|
|
|
|
RED.nodes.eachSubflow(function(sf) {
|
|
|
|
var m = (new RegExp("^Subflow (\\d+)$")).exec(sf.name);
|
|
|
|
if (m) {
|
|
|
|
lastIndex = Math.max(lastIndex,m[1]);
|
|
|
|
}
|
|
|
|
});
|
2015-07-01 00:42:03 +02:00
|
|
|
|
2015-03-12 01:08:47 +01:00
|
|
|
var name = "Subflow "+(lastIndex+1);
|
2015-07-01 00:42:03 +02:00
|
|
|
|
2015-03-12 01:08:47 +01:00
|
|
|
var subflowId = RED.nodes.id();
|
|
|
|
var subflow = {
|
|
|
|
type:"subflow",
|
|
|
|
id:subflowId,
|
|
|
|
name:name,
|
2015-10-23 23:14:21 +02:00
|
|
|
info:"",
|
2015-07-30 12:03:37 +02:00
|
|
|
in: Object.keys(candidateInputNodes).map(function(v,i) { var index = i; return {
|
2015-03-12 01:08:47 +01:00
|
|
|
type:"subflow",
|
|
|
|
direction:"in",
|
2019-01-26 21:49:22 +01:00
|
|
|
x:snapToGrid(candidateInputNodes[v].x-(candidateInputNodes[v].w/2)-80 - offsetX),
|
|
|
|
y:snapToGrid(candidateInputNodes[v].y - offsetY),
|
2015-03-12 01:08:47 +01:00
|
|
|
z:subflowId,
|
|
|
|
i:index,
|
|
|
|
id:RED.nodes.id(),
|
2015-07-30 12:03:37 +02:00
|
|
|
wires:[{id:candidateInputNodes[v].id}]
|
2015-03-12 01:08:47 +01:00
|
|
|
}}),
|
|
|
|
out: candidateOutputs.map(function(v,i) { var index = i; return {
|
|
|
|
type:"subflow",
|
2019-02-05 08:27:02 +01:00
|
|
|
direction:"out",
|
2019-01-26 21:49:22 +01:00
|
|
|
x:snapToGrid(v.source.x+(v.source.w/2)+80 - offsetX),
|
|
|
|
y:snapToGrid(v.source.y - offsetY),
|
2015-03-12 01:08:47 +01:00
|
|
|
z:subflowId,
|
|
|
|
i:index,
|
|
|
|
id:RED.nodes.id(),
|
|
|
|
wires:[{id:v.source.id,port:v.sourcePort}]
|
|
|
|
}})
|
|
|
|
};
|
2015-07-30 12:03:37 +02:00
|
|
|
|
2015-03-12 01:08:47 +01:00
|
|
|
RED.nodes.addSubflow(subflow);
|
2015-07-01 00:42:03 +02:00
|
|
|
|
2015-03-12 01:08:47 +01:00
|
|
|
var subflowInstance = {
|
|
|
|
id:RED.nodes.id(),
|
|
|
|
type:"subflow:"+subflow.id,
|
|
|
|
x: center[0],
|
|
|
|
y: center[1],
|
2015-03-13 00:38:37 +01:00
|
|
|
z: RED.workspaces.active(),
|
2015-03-12 01:08:47 +01:00
|
|
|
inputs: subflow.in.length,
|
|
|
|
outputs: subflow.out.length,
|
|
|
|
h: Math.max(30/*node_height*/,(subflow.out.length||0) * 15),
|
|
|
|
changed:true
|
|
|
|
}
|
|
|
|
subflowInstance._def = RED.nodes.getType(subflowInstance.type);
|
|
|
|
RED.editor.validateNode(subflowInstance);
|
|
|
|
RED.nodes.add(subflowInstance);
|
2015-07-01 00:42:03 +02:00
|
|
|
|
2015-03-12 01:08:47 +01:00
|
|
|
candidateInputs.forEach(function(l) {
|
|
|
|
var link = {source:l.source, sourcePort:l.sourcePort, target: subflowInstance};
|
|
|
|
new_links.push(link);
|
|
|
|
RED.nodes.addLink(link);
|
|
|
|
});
|
2015-07-01 00:42:03 +02:00
|
|
|
|
2015-03-12 01:08:47 +01:00
|
|
|
candidateOutputs.forEach(function(output,i) {
|
|
|
|
output.targets.forEach(function(target) {
|
|
|
|
var link = {source:subflowInstance, sourcePort:i, target: target};
|
|
|
|
new_links.push(link);
|
|
|
|
RED.nodes.addLink(link);
|
|
|
|
});
|
|
|
|
});
|
2015-07-01 00:42:03 +02:00
|
|
|
|
2015-03-12 01:08:47 +01:00
|
|
|
subflow.in.forEach(function(input) {
|
|
|
|
input.wires.forEach(function(wire) {
|
|
|
|
var link = {source: input, sourcePort: 0, target: RED.nodes.node(wire.id) }
|
|
|
|
new_links.push(link);
|
|
|
|
RED.nodes.addLink(link);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
subflow.out.forEach(function(output,i) {
|
|
|
|
output.wires.forEach(function(wire) {
|
|
|
|
var link = {source: RED.nodes.node(wire.id), sourcePort: wire.port , target: output }
|
|
|
|
new_links.push(link);
|
|
|
|
RED.nodes.addLink(link);
|
|
|
|
});
|
|
|
|
});
|
2015-07-01 00:42:03 +02:00
|
|
|
|
2015-03-12 01:08:47 +01:00
|
|
|
for (i=0;i<removedLinks.length;i++) {
|
|
|
|
RED.nodes.removeLink(removedLinks[i]);
|
|
|
|
}
|
2015-07-01 00:42:03 +02:00
|
|
|
|
2015-03-12 01:08:47 +01:00
|
|
|
for (i=0;i<selection.nodes.length;i++) {
|
2016-06-30 00:51:08 +02:00
|
|
|
n = selection.nodes[i];
|
|
|
|
if (/^link /.test(n.type)) {
|
|
|
|
n.links = n.links.filter(function(id) {
|
|
|
|
var isLocalLink = nodes.hasOwnProperty(id);
|
|
|
|
if (!isLocalLink) {
|
|
|
|
var otherNode = RED.nodes.node(id);
|
|
|
|
if (otherNode && otherNode.links) {
|
|
|
|
var i = otherNode.links.indexOf(n.id);
|
|
|
|
if (i > -1) {
|
|
|
|
otherNode.links.splice(i,1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return isLocalLink;
|
|
|
|
});
|
|
|
|
}
|
2019-01-26 21:49:22 +01:00
|
|
|
n.x -= offsetX;
|
|
|
|
n.y -= offsetY;
|
2019-07-26 16:06:56 +02:00
|
|
|
RED.nodes.moveNodeToTab(n, subflow.id);
|
2015-03-12 01:08:47 +01:00
|
|
|
}
|
2015-07-01 00:42:03 +02:00
|
|
|
|
2015-03-12 01:08:47 +01:00
|
|
|
RED.history.push({
|
|
|
|
t:'createSubflow',
|
|
|
|
nodes:[subflowInstance.id],
|
|
|
|
links:new_links,
|
2015-07-30 12:03:37 +02:00
|
|
|
subflow: {
|
2019-01-26 21:49:22 +01:00
|
|
|
subflow: subflow,
|
|
|
|
offsetX: offsetX,
|
|
|
|
offsetY: offsetY
|
2015-07-30 12:03:37 +02:00
|
|
|
},
|
2015-07-01 00:42:03 +02:00
|
|
|
|
2015-03-13 00:38:37 +01:00
|
|
|
activeWorkspace: RED.workspaces.active(),
|
2015-03-12 01:08:47 +01:00
|
|
|
removedLinks: removedLinks,
|
2015-07-01 00:42:03 +02:00
|
|
|
|
2015-03-15 22:54:36 +01:00
|
|
|
dirty:RED.nodes.dirty()
|
2015-03-12 01:08:47 +01:00
|
|
|
});
|
2016-06-30 00:51:08 +02:00
|
|
|
RED.view.select(null);
|
2015-03-20 00:11:55 +01:00
|
|
|
RED.editor.validateNode(subflow);
|
2015-03-15 22:54:36 +01:00
|
|
|
RED.nodes.dirty(true);
|
2015-03-12 12:21:05 +01:00
|
|
|
RED.view.redraw(true);
|
2015-03-12 01:08:47 +01:00
|
|
|
}
|
2015-07-01 00:42:03 +02:00
|
|
|
|
2019-08-12 16:01:54 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Create interface for controlling env var UI definition
|
|
|
|
*/
|
|
|
|
function buildEnvControl(envList) {
|
|
|
|
|
|
|
|
var tabs = RED.tabs.create({
|
|
|
|
id: "subflow-env-tabs",
|
|
|
|
onchange: function(tab) {
|
|
|
|
if (tab.id === "subflow-env-tab-preview") {
|
|
|
|
var inputContainer = $("#subflow-input-ui");
|
|
|
|
var list = envList.editableList("items");
|
|
|
|
var exportedEnv = exportEnvList(list, true);
|
|
|
|
buildEnvUI(inputContainer, exportedEnv);
|
|
|
|
}
|
|
|
|
$("#subflow-env-tabs-content").children().hide();
|
|
|
|
$("#" + tab.id).show();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
tabs.addTab({
|
|
|
|
id: "subflow-env-tab-edit",
|
|
|
|
label: RED._("editor-tab.envProperties")
|
|
|
|
});
|
|
|
|
tabs.addTab({
|
|
|
|
id: "subflow-env-tab-preview",
|
|
|
|
label: RED._("editor-tab.preview")
|
|
|
|
});
|
|
|
|
|
|
|
|
var localesList = RED.settings.theme("languages")
|
|
|
|
.map(function(lc) { var name = RED._("languages."+lc); return {text: (name ? name : lc), val: lc}; })
|
|
|
|
.sort(function(a, b) { return a.text.localeCompare(b.text) });
|
|
|
|
RED.popover.tooltip($(".node-input-env-locales-row i"),RED._("editor.locale"))
|
|
|
|
var locales = $("#subflow-input-env-locale")
|
|
|
|
localesList.forEach(function(item) {
|
|
|
|
var opt = {
|
|
|
|
value: item.val
|
|
|
|
};
|
|
|
|
if (item.val === "en-US") { // make en-US default selected
|
|
|
|
opt.selected = "";
|
|
|
|
}
|
|
|
|
$("<option/>", opt).text(item.text).appendTo(locales);
|
|
|
|
});
|
|
|
|
currentLocale = RED.i18n.lang();
|
|
|
|
locales.val(currentLocale);
|
|
|
|
|
|
|
|
locales.on("change", function() {
|
|
|
|
currentLocale = $(this).val();
|
|
|
|
var items = $("#node-input-env-container").editableList("items");
|
|
|
|
items.each(function (i, item) {
|
|
|
|
var entry = $(this).data('data');
|
|
|
|
var labelField = entry.ui.labelField;
|
|
|
|
labelField.val(lookupLabel(entry.ui.label, "", currentLocale));
|
|
|
|
if (labelField.timeout) {
|
|
|
|
clearTimeout(labelField.timeout);
|
|
|
|
delete labelField.timeout;
|
|
|
|
}
|
|
|
|
labelField.addClass("input-updated");
|
|
|
|
labelField.timeout = setTimeout(function() {
|
|
|
|
delete labelField.timeout
|
|
|
|
labelField.removeClass("input-updated");
|
|
|
|
},3000);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create env var edit interface
|
|
|
|
* @param container - container
|
|
|
|
* @param node - subflow node
|
|
|
|
*/
|
|
|
|
function buildPropertiesList(envContainer, node) {
|
|
|
|
|
|
|
|
var isTemplateNode = (node.type === "subflow");
|
|
|
|
|
|
|
|
if (isTemplateNode) {
|
|
|
|
buildEnvControl(envContainer);
|
|
|
|
}
|
|
|
|
envContainer
|
|
|
|
.css({
|
|
|
|
'min-height':'150px',
|
|
|
|
'min-width':'450px'
|
|
|
|
})
|
|
|
|
.editableList({
|
|
|
|
header: isTemplateNode?$('<div><div><div></div><div data-i18n="common.label.name"></div><div data-i18n="editor-tab.defaultValue"></div><div></div></div></div>'):undefined,
|
|
|
|
addItem: function(container, i, opt) {
|
|
|
|
if (isTemplateNode) {
|
|
|
|
container.addClass("red-ui-editor-subflow-env-editable")
|
|
|
|
}
|
|
|
|
|
|
|
|
var envRow = $('<div/>').appendTo(container);
|
|
|
|
var nameField = null;
|
|
|
|
var valueField = null;
|
|
|
|
|
|
|
|
// if (opt.parent) {
|
|
|
|
// buildEnvUIRow(envRow,opt,opt.parent.ui||{})
|
|
|
|
// } else {
|
|
|
|
nameField = $('<input/>', {
|
|
|
|
class: "node-input-env-name",
|
|
|
|
type: "text",
|
|
|
|
placeholder: RED._("common.label.name")
|
|
|
|
}).attr("autocomplete","disable").appendTo(envRow).val(opt.name);
|
|
|
|
valueField = $('<input/>',{
|
|
|
|
style: "width:100%",
|
|
|
|
class: "node-input-env-value",
|
|
|
|
type: "text",
|
|
|
|
}).attr("autocomplete","disable").appendTo(envRow)
|
|
|
|
valueField.typedInput({default:'str',types:['str','num','bool','json','bin','env']});
|
|
|
|
valueField.typedInput('type', opt.parent?(opt.type||opt.parent.type):opt.type);
|
|
|
|
valueField.typedInput('value', opt.parent?((opt.value !== undefined)?opt.value:opt.parent.value):opt.value);
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
opt.nameField = nameField;
|
|
|
|
opt.valueField = valueField;
|
|
|
|
|
|
|
|
if (!opt.parent) {
|
|
|
|
var actionButton = $('<a/>',{href:"#",class:"red-ui-editableList-item-remove red-ui-button red-ui-button-small"}).appendTo(envRow);
|
|
|
|
$('<i/>',{class:"fa "+(opt.parent?"fa-reply":"fa-remove")}).appendTo(actionButton);
|
|
|
|
var removeTip = RED.popover.tooltip(actionButton,RED._("subflow.env.remove"));
|
|
|
|
actionButton.on("click", function(evt) {
|
|
|
|
evt.preventDefault();
|
|
|
|
removeTip.close();
|
|
|
|
container.parent().addClass("red-ui-editableList-item-deleting")
|
|
|
|
container.fadeOut(300, function() {
|
|
|
|
envContainer.editableList('removeItem',opt);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isTemplateNode) {
|
|
|
|
// Add the UI customisation row
|
|
|
|
// if `opt.ui` does not exist, then apply defaults. If these
|
|
|
|
// defaults do not change then they will get stripped off
|
|
|
|
// before saving.
|
|
|
|
opt.ui = opt.ui || {
|
|
|
|
icon: "",
|
|
|
|
label: {},
|
|
|
|
type: "input",
|
|
|
|
opts: {types:['str','num','bool','json','bin','env']}
|
|
|
|
}
|
|
|
|
opt.ui.label = opt.ui.label || {};
|
|
|
|
opt.ui.type = opt.ui.type || "input";
|
|
|
|
|
|
|
|
var uiRow = $('<div/>').appendTo(container).hide();
|
|
|
|
// save current info for reverting on cancel
|
|
|
|
// var copy = $.extend(true, {}, ui);
|
|
|
|
|
|
|
|
$('<a href="#"><i class="fa fa-angle-right"></a>').prependTo(envRow).on("click", function (evt) {
|
|
|
|
evt.preventDefault();
|
|
|
|
if ($(this).hasClass('expanded')) {
|
|
|
|
uiRow.slideUp();
|
|
|
|
$(this).removeClass('expanded');
|
|
|
|
} else {
|
|
|
|
uiRow.slideDown();
|
|
|
|
$(this).addClass('expanded');
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
buildEnvEditRow(uiRow, opt.ui, nameField, valueField);
|
|
|
|
nameField.trigger('change');
|
|
|
|
}
|
|
|
|
},
|
|
|
|
sortable: ".red-ui-editableList-item-handle",
|
|
|
|
removable: false
|
|
|
|
});
|
|
|
|
var parentEnv = {};
|
|
|
|
var envList = [];
|
|
|
|
if (/^subflow:/.test(node.type)) {
|
|
|
|
var subflowDef = RED.nodes.subflow(node.type.substring(8));
|
|
|
|
if (subflowDef.env) {
|
|
|
|
subflowDef.env.forEach(function(env) {
|
|
|
|
var item = {
|
|
|
|
name:env.name,
|
|
|
|
parent: {
|
|
|
|
type: env.type,
|
|
|
|
value: env.value,
|
|
|
|
ui: env.ui
|
|
|
|
}
|
|
|
|
}
|
|
|
|
envList.push(item);
|
|
|
|
parentEnv[env.name] = item;
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (node.env) {
|
|
|
|
for (var i = 0; i < node.env.length; i++) {
|
|
|
|
var env = node.env[i];
|
|
|
|
if (parentEnv.hasOwnProperty(env.name)) {
|
|
|
|
parentEnv[env.name].type = env.type;
|
|
|
|
parentEnv[env.name].value = env.value;
|
|
|
|
} else {
|
|
|
|
envList.push({
|
|
|
|
name: env.name,
|
|
|
|
type: env.type,
|
|
|
|
value: env.value,
|
|
|
|
ui: env.ui
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
envList.forEach(function(env) {
|
|
|
|
if (env.parent && env.parent.ui && env.parent.ui.type === 'hide') {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (!isTemplateNode && env.parent) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
envContainer.editableList('addItem', JSON.parse(JSON.stringify(env)));
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create UI edit interface for environment variable
|
|
|
|
* @param container - container
|
|
|
|
* @param env - env var definition
|
|
|
|
* @param nameField - name field of env var
|
|
|
|
* @param valueField - value field of env var
|
|
|
|
*/
|
|
|
|
function buildEnvEditRow(container, ui, nameField, valueField) {
|
|
|
|
container.addClass("red-ui-editor-subflow-env-ui-row")
|
|
|
|
var topRow = $('<div></div>').appendTo(container);
|
|
|
|
$('<div></div>').appendTo(topRow);
|
|
|
|
$('<div>').text(RED._("editor.icon")).appendTo(topRow);
|
|
|
|
$('<div>').text(RED._("editor.label")).appendTo(topRow);
|
|
|
|
$('<div>').text(RED._("editor.inputType")).appendTo(topRow);
|
|
|
|
|
|
|
|
var row = $('<div></div>').appendTo(container);
|
|
|
|
$('<div><i class="red-ui-editableList-item-handle fa fa-bars"></i></div>').appendTo(row);
|
|
|
|
|
|
|
|
var typeOptions = {
|
|
|
|
'input': {types:['str','num','bool','json','bin','env']},
|
|
|
|
'select': {opts:[]},
|
|
|
|
'spinner': {}
|
|
|
|
};
|
|
|
|
if (ui.opts) {
|
|
|
|
typeOptions[ui.type] = ui.opts;
|
|
|
|
} else {
|
|
|
|
// Pick up the default values if not otherwise provided
|
|
|
|
ui.opts = typeOptions[ui.type];
|
|
|
|
}
|
|
|
|
var iconCell = $('<div></div>').appendTo(row);
|
|
|
|
|
|
|
|
var iconButton = $('<a href="#"></a>').appendTo(iconCell);
|
|
|
|
iconButton.on("click", function(evt) {
|
|
|
|
evt.preventDefault();
|
|
|
|
var icon = ui.icon || "";
|
|
|
|
var iconPath = (icon ? RED.utils.separateIconPath(icon) : {});
|
2019-08-12 23:07:55 +02:00
|
|
|
RED.editor.showIconPicker(iconButton, null, iconPath, true, function (newIcon) {
|
2019-08-12 16:01:54 +02:00
|
|
|
iconButton.empty();
|
|
|
|
var path = newIcon || "";
|
|
|
|
var newPath = RED.utils.separateIconPath(path);
|
|
|
|
if (newPath) {
|
|
|
|
$('<i class="fa"></i>').addClass(newPath.file).appendTo(iconButton);
|
|
|
|
}
|
|
|
|
ui.icon = path;
|
|
|
|
});
|
|
|
|
})
|
|
|
|
|
|
|
|
if (ui.icon) {
|
|
|
|
var newPath = RED.utils.separateIconPath(ui.icon);
|
|
|
|
$('<i class="fa '+newPath.file+'"></i>').appendTo(iconButton);
|
|
|
|
}
|
|
|
|
|
|
|
|
var labelCell = $('<div></div>').appendTo(row);
|
|
|
|
|
|
|
|
var label = ui.label && ui.label[currentLocale] || "";
|
|
|
|
var labelInput = $('<input type="text">').val(label).appendTo(labelCell);
|
|
|
|
ui.labelField = labelInput;
|
|
|
|
labelInput.on('change', function(evt) {
|
|
|
|
ui.label = ui.label || {};
|
|
|
|
var val = $(this).val().trim();
|
|
|
|
if (val === "") {
|
|
|
|
delete ui.label[currentLocale];
|
|
|
|
} else {
|
|
|
|
ui.label[currentLocale] = val;
|
|
|
|
}
|
|
|
|
})
|
|
|
|
var labelIcon = $('<span class="red-ui-editor-subflow-env-lang-icon"><i class="fa fa-language"></i></span>').appendTo(labelCell);
|
|
|
|
RED.popover.tooltip(labelIcon,function() {
|
|
|
|
var langs = Object.keys(ui.label);
|
|
|
|
var content = $("<div>");
|
|
|
|
if (langs.indexOf(currentLocale) === -1) {
|
|
|
|
langs.push(currentLocale);
|
|
|
|
langs.sort();
|
|
|
|
}
|
|
|
|
langs.forEach(function(l) {
|
|
|
|
var row = $('<div>').appendTo(content);
|
|
|
|
$('<span>').css({display:"inline-block",width:"50px"}).text(l+(l===currentLocale?"*":"")).appendTo(row);
|
|
|
|
$('<span>').text(ui.label[l]||"").appendTo(row);
|
|
|
|
});
|
|
|
|
return content;
|
|
|
|
})
|
|
|
|
|
|
|
|
nameField.on('change',function(evt) {
|
|
|
|
labelInput.attr("placeholder",$(this).val())
|
|
|
|
});
|
|
|
|
|
|
|
|
var inputCell = $('<div></div>').appendTo(row);
|
|
|
|
var inputCellInput = $('<input type="text">').css("width","100%").appendTo(inputCell);
|
|
|
|
if (ui.type === "input") {
|
|
|
|
inputCellInput.val(ui.opts.types.join(","));
|
|
|
|
}
|
|
|
|
var checkbox;
|
|
|
|
var selectBox;
|
|
|
|
|
|
|
|
inputCellInput.typedInput({
|
|
|
|
types: [
|
2019-08-13 14:17:31 +02:00
|
|
|
{
|
|
|
|
value:"input",
|
|
|
|
label:RED._("editor.inputs.input"), icon:"fa fa-i-cursor",showLabel:false,multiple:true,options:[
|
|
|
|
{value:"str",label:RED._("editor.types.str"),icon:"red/images/typedInput/az.svg"},
|
|
|
|
{value:"num",label:RED._("editor.types.num"),icon:"red/images/typedInput/09.svg"},
|
|
|
|
{value:"bool",label:RED._("editor.types.bool"),icon:"red/images/typedInput/bool.svg"},
|
|
|
|
{value:"json",label:RED._("editor.types.json"),icon:"red/images/typedInput/json.svg"},
|
|
|
|
{value: "bin",label: RED._("editor.types.bin"),icon: "red/images/typedInput/bin.svg"},
|
|
|
|
{value: "env",label: RED._("editor.types.env"),icon: "red/images/typedInput/env.svg"}
|
2019-08-12 16:01:54 +02:00
|
|
|
],
|
|
|
|
default: ['str','num','bool','json','bin','env'],
|
|
|
|
valueLabel: function(container,value) {
|
|
|
|
container.css("padding",0);
|
|
|
|
var innerContainer = $('<div>').css({
|
|
|
|
"background":"white",
|
|
|
|
"height":"100%",
|
|
|
|
"box-sizing": "border-box"
|
|
|
|
}).appendTo(container);
|
|
|
|
|
|
|
|
var input = $('<div class="placeholder-input">').appendTo(innerContainer);
|
|
|
|
$('<span><i class="fa fa-i-cursor"></i></span>').appendTo(input);
|
|
|
|
if (value.length) {
|
|
|
|
value.forEach(function(v) {
|
|
|
|
$('<img>',{src:v.icon,style:"max-width:14px; padding: 0 3px; margin-top:-4px; margin-left: 3px"}).appendTo(input);
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
$("<span>").css({
|
|
|
|
"color":"#aaa",
|
|
|
|
"padding-left": "4px"
|
|
|
|
}).text("select types...").appendTo(input);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
2019-08-13 14:17:31 +02:00
|
|
|
{
|
|
|
|
value:"select",
|
|
|
|
label:RED._("editor.inputs.select"), icon:"fa fa-tasks",showLabel:false,
|
2019-08-12 16:01:54 +02:00
|
|
|
valueLabel: function(container,value) {
|
|
|
|
container.css("padding","0");
|
|
|
|
|
|
|
|
selectBox = $('<select></select>').appendTo(container);
|
|
|
|
if (ui.opts && Array.isArray(ui.opts.opts)) {
|
|
|
|
ui.opts.opts.forEach(function(o) {
|
|
|
|
var label = lookupLabel(o.l, o.l["en-US"]||o.v, currentLocale);
|
|
|
|
// $('<option>').val((o.t||'str')+":"+o.v).text(label).appendTo(selectBox);
|
|
|
|
$('<option>').val(o.v).text(label).appendTo(selectBox);
|
|
|
|
})
|
|
|
|
}
|
|
|
|
selectBox.on('change', function(evt) {
|
|
|
|
var v = selectBox.val();
|
|
|
|
// var parts = v.split(":");
|
|
|
|
// var t = parts.shift();
|
|
|
|
// v = parts.join(":");
|
|
|
|
//
|
|
|
|
// valueField.typedInput("type",'str')
|
|
|
|
valueField.typedInput("value",v)
|
|
|
|
});
|
|
|
|
selectBox.val(valueField.typedInput("value"));
|
|
|
|
// selectBox.val(valueField.typedInput('type')+":"+valueField.typedInput("value"));
|
|
|
|
},
|
|
|
|
expand: {
|
|
|
|
icon: "fa-caret-down",
|
|
|
|
minWidth: 400,
|
|
|
|
content: function(container) {
|
|
|
|
var content = $('<div class="red-ui-editor-subflow-ui-edit-panel">').appendTo(container);
|
|
|
|
var optList = $('<ol>').appendTo(content).editableList({
|
2019-08-13 14:17:31 +02:00
|
|
|
header:$("<div><div>"+RED._("editor.select.label")+"</div><div>"+RED._("editor.select.value")+"</div></div>"),
|
2019-08-12 16:01:54 +02:00
|
|
|
addItem: function(row,index,itemData) {
|
|
|
|
var labelDiv = $('<div>').appendTo(row);
|
|
|
|
var label = lookupLabel(itemData.l, "", currentLocale);
|
|
|
|
itemData.label = $('<input type="text">').val(label).appendTo(labelDiv);
|
|
|
|
itemData.label.on('keydown', function(evt) {
|
|
|
|
if (evt.keyCode === 13) {
|
|
|
|
itemData.input.focus();
|
|
|
|
evt.preventDefault();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
var labelIcon = $('<span class="red-ui-editor-subflow-env-lang-icon"><i class="fa fa-language"></i></span>').appendTo(labelDiv);
|
|
|
|
RED.popover.tooltip(labelIcon,function() {
|
|
|
|
return currentLocale;
|
|
|
|
})
|
|
|
|
itemData.input = $('<input type="text">').val(itemData.v).appendTo(row);
|
|
|
|
|
|
|
|
// Problem using a TI here:
|
|
|
|
// - this is in a popout panel
|
|
|
|
// - clicking the expand button in the TI will close the parent edit tray
|
|
|
|
// and open the type editor.
|
|
|
|
// - but it leaves the popout panel over the top.
|
|
|
|
// - there is no way to get back to the popout panel after closing the type editor
|
|
|
|
//.typedInput({default:itemData.t||'str', types:['str','num','bool','json','bin','env']});
|
|
|
|
itemData.input.on('keydown', function(evt) {
|
|
|
|
if (evt.keyCode === 13) {
|
|
|
|
// Enter or Tab
|
|
|
|
var index = optList.editableList('indexOf',itemData);
|
|
|
|
var length = optList.editableList('length');
|
|
|
|
if (index + 1 === length) {
|
|
|
|
var newItem = {};
|
|
|
|
optList.editableList('addItem',newItem);
|
|
|
|
setTimeout(function() {
|
|
|
|
if (newItem.label) {
|
|
|
|
newItem.label.focus();
|
|
|
|
}
|
|
|
|
},100)
|
|
|
|
} else {
|
|
|
|
var nextItem = optList.editableList('getItemAt',index+1);
|
|
|
|
if (nextItem.label) {
|
|
|
|
nextItem.label.focus()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
evt.preventDefault();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
},
|
|
|
|
sortable: true,
|
|
|
|
removable: true,
|
|
|
|
height: 160
|
|
|
|
})
|
|
|
|
if (ui.opts.opts.length > 0) {
|
|
|
|
ui.opts.opts.forEach(function(o) {
|
|
|
|
optList.editableList('addItem',$.extend(true,{},o))
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
optList.editableList('addItem',{})
|
|
|
|
}
|
|
|
|
return {
|
|
|
|
onclose: function() {
|
|
|
|
var items = optList.editableList('items');
|
|
|
|
var vals = [];
|
|
|
|
items.each(function (i,el) {
|
|
|
|
var data = el.data('data');
|
|
|
|
var l = data.label.val().trim();
|
|
|
|
var v = data.input.val();
|
|
|
|
// var t = data.input.typedInput('type');
|
|
|
|
// var v = data.input.typedInput('value');
|
|
|
|
if (l.length > 0) {
|
|
|
|
data.l = data.l || {};
|
|
|
|
data.l[currentLocale] = l;
|
|
|
|
}
|
|
|
|
data.v = v;
|
|
|
|
|
|
|
|
if (l.length > 0 || v.length > 0) {
|
|
|
|
var val = {l:data.l,v:data.v};
|
|
|
|
// if (t !== 'str') {
|
|
|
|
// val.t = t;
|
|
|
|
// }
|
|
|
|
vals.push(val);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
ui.opts.opts = vals;
|
|
|
|
inputCellInput.typedInput('value',Date.now())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
2019-08-13 14:17:31 +02:00
|
|
|
{
|
|
|
|
value:"checkbox",
|
|
|
|
label:RED._("editor.inputs.checkbox"), icon:"fa fa-check-square-o",showLabel:false,
|
2019-08-12 16:01:54 +02:00
|
|
|
valueLabel: function(container,value) {
|
|
|
|
container.css("padding",0);
|
|
|
|
checkbox = $('<input type="checkbox">').appendTo(container);
|
|
|
|
checkbox.on('change', function(evt) {
|
|
|
|
valueField.typedInput('value',$(this).prop('checked')?"true":"false");
|
|
|
|
})
|
|
|
|
checkbox.prop('checked',valueField.typedInput('value')==="true");
|
|
|
|
}
|
|
|
|
},
|
2019-08-13 14:17:31 +02:00
|
|
|
{
|
|
|
|
value:"spinner",
|
|
|
|
label:RED._("editor.inputs.spinner"), icon:"fa fa-sort-numeric-asc", showLabel:false,
|
2019-08-12 16:01:54 +02:00
|
|
|
valueLabel: function(container,value) {
|
|
|
|
container.css("padding",0);
|
|
|
|
var innerContainer = $('<div>').css({
|
|
|
|
"background":"white",
|
|
|
|
"height":"100%",
|
|
|
|
"box-sizing": "border-box"
|
|
|
|
}).appendTo(container);
|
|
|
|
|
|
|
|
var input = $('<div class="placeholder-input">').appendTo(innerContainer);
|
|
|
|
$('<span><i class="fa fa-sort-numeric-asc"></i></span>').appendTo(input);
|
|
|
|
|
|
|
|
var min = ui.opts && ui.opts.min;
|
|
|
|
var max = ui.opts && ui.opts.max;
|
|
|
|
var label = "";
|
|
|
|
if (min !== undefined && max !== undefined) {
|
|
|
|
label = Math.min(min,max)+" - "+Math.max(min,max);
|
|
|
|
} else if (min !== undefined) {
|
|
|
|
label = "> "+min;
|
|
|
|
} else if (max !== undefined) {
|
|
|
|
label = "< "+max;
|
|
|
|
}
|
|
|
|
$('<span>').css("margin-left","15px").text(label).appendTo(input);
|
|
|
|
},
|
|
|
|
expand: {
|
|
|
|
icon: "fa-caret-down",
|
|
|
|
content: function(container) {
|
|
|
|
var content = $('<div class="red-ui-editor-subflow-ui-edit-panel">').appendTo(container);
|
|
|
|
content.css("padding","8px 5px")
|
|
|
|
var min = ui.opts.min;
|
|
|
|
var max = ui.opts.max;
|
|
|
|
var minInput = $('<input type="number" style="margin-bottom:0; width:60px">');
|
|
|
|
minInput.val(min);
|
|
|
|
var maxInput = $('<input type="number" style="margin-bottom:0; width:60px">');
|
|
|
|
maxInput.val(max);
|
2019-08-13 14:17:31 +02:00
|
|
|
$('<div class="form-row" style="margin-bottom:3px"><label>'+RED._("editor.spinner.min")+'</label></div>').append(minInput).appendTo(content);
|
|
|
|
$('<div class="form-row" style="margin-bottom:0"><label>'+RED._("editor.spinner.max")+'</label></div>').append(maxInput).appendTo(content);
|
2019-08-12 16:01:54 +02:00
|
|
|
return {
|
|
|
|
onclose: function() {
|
|
|
|
var min = minInput.val().trim();
|
|
|
|
var max = maxInput.val().trim();
|
|
|
|
if (min !== "") {
|
|
|
|
ui.opts.min = parseInt(min);
|
|
|
|
} else {
|
|
|
|
delete ui.opts.min;
|
|
|
|
}
|
|
|
|
if (max !== "") {
|
|
|
|
ui.opts.max = parseInt(max);
|
|
|
|
} else {
|
|
|
|
delete ui.opts.max;
|
|
|
|
}
|
|
|
|
inputCellInput.typedInput('value',Date.now())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
2019-08-13 14:17:31 +02:00
|
|
|
{
|
|
|
|
value:"none",
|
|
|
|
label:RED._("editor.inputs.none"), icon:"fa fa-times",hasValue:false
|
|
|
|
},
|
|
|
|
{
|
|
|
|
value:"hide",
|
|
|
|
label:RED._("editor.inputs.hidden"), icon:"fa fa-ban",hasValue:false
|
|
|
|
}
|
2019-08-12 16:01:54 +02:00
|
|
|
],
|
|
|
|
default: 'none'
|
|
|
|
}).on("typedinputtypechange", function(evt,type) {
|
|
|
|
ui.type = $(this).typedInput("type");
|
|
|
|
ui.opts = typeOptions[ui.type];
|
|
|
|
if (ui.type === 'input') {
|
|
|
|
// In the case of 'input' type, the typedInput uses the multiple-option
|
|
|
|
// mode. Its value needs to be set to a comma-separately list of the
|
|
|
|
// selected options.
|
|
|
|
inputCellInput.typedInput('value',ui.opts.types.join(","))
|
|
|
|
} else {
|
|
|
|
// No other type cares about `value`, but doing this will
|
|
|
|
// force a refresh of the label now that `ui.opts` has
|
|
|
|
// been updated.
|
|
|
|
inputCellInput.typedInput('value',Date.now())
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (ui.type) {
|
|
|
|
case 'input':
|
|
|
|
valueField.typedInput('types',ui.opts.types);
|
|
|
|
break;
|
|
|
|
case 'select':
|
|
|
|
valueField.typedInput('types',['str']);
|
|
|
|
break;
|
|
|
|
case 'checkbox':
|
|
|
|
valueField.typedInput('types',['bool']);
|
|
|
|
break;
|
|
|
|
case 'spinner':
|
|
|
|
valueField.typedInput('types',['num'])
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
valueField.typedInput('types',['str','num','bool','json','bin','env'])
|
|
|
|
}
|
|
|
|
if (ui.type === 'checkbox') {
|
|
|
|
valueField.typedInput('type','bool');
|
|
|
|
} else if (ui.type === 'spinner') {
|
|
|
|
valueField.typedInput('type','num');
|
|
|
|
}
|
|
|
|
if (ui.type !== 'checkbox') {
|
|
|
|
checkbox = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
}).on("change", function(evt,type) {
|
|
|
|
if (ui.type === 'input') {
|
|
|
|
ui.opts.types = inputCellInput.typedInput('value').split(",");
|
|
|
|
valueField.typedInput('types',ui.opts.types);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
valueField.on("change", function(evt) {
|
|
|
|
if (checkbox) {
|
|
|
|
checkbox.prop('checked',$(this).typedInput('value')==="true")
|
|
|
|
}
|
|
|
|
})
|
|
|
|
// Set the input to the right type. This will trigger the 'typedinputtypechange'
|
|
|
|
// event handler (just above ^^) to update the value if needed
|
|
|
|
inputCellInput.typedInput('type',ui.type)
|
|
|
|
}
|
|
|
|
|
|
|
|
function buildEnvUIRow(row, tenv, ui) {
|
|
|
|
|
|
|
|
ui.label = ui.label||{};
|
|
|
|
ui.opts = ui.opts||{};
|
|
|
|
if (!ui.type) {
|
|
|
|
ui.type = "input";
|
|
|
|
ui.opts = {types:['str','num','bool','json','bin','env']}
|
|
|
|
} else {
|
|
|
|
ui.opts = ui.opts || {};
|
|
|
|
}
|
|
|
|
|
|
|
|
var labels = ui.label || {};
|
|
|
|
var labelText = lookupLabel(labels, labels["en-US"]||tenv.name, currentLocale);
|
|
|
|
var label = $('<label>').appendTo(row);
|
|
|
|
var labelContainer = $('<span></span>').appendTo(label);
|
|
|
|
if (ui.icon) {
|
|
|
|
var newPath = RED.utils.separateIconPath(ui.icon);
|
|
|
|
if (newPath) {
|
|
|
|
$("<i class='fa "+newPath.file +"'/>").appendTo(labelContainer);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (ui.type !== "checkbox") {
|
|
|
|
$('<span>').css({"padding-left":"5px"}).text(labelText).appendTo(label);
|
|
|
|
if (ui.type === 'none') {
|
|
|
|
label.width('100%');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
var input;
|
|
|
|
var val = {
|
|
|
|
value: "",
|
|
|
|
type: "str"
|
|
|
|
};
|
|
|
|
if (tenv.parent) {
|
|
|
|
val.value = tenv.parent.value;
|
|
|
|
val.type = tenv.parent.type;
|
|
|
|
}
|
|
|
|
if (tenv.hasOwnProperty('value')) {
|
|
|
|
val.value = tenv.value;
|
|
|
|
}
|
|
|
|
if (tenv.hasOwnProperty('type')) {
|
|
|
|
val.type = tenv.type;
|
|
|
|
}
|
|
|
|
switch(ui.type) {
|
|
|
|
case "input":
|
|
|
|
input = $('<input type="text">').css('width','70%').appendTo(row);
|
|
|
|
if (ui.opts.types && ui.opts.types.length > 0) {
|
|
|
|
var inputType = val.type;
|
|
|
|
if (ui.opts.types.indexOf(inputType) === -1) {
|
|
|
|
inputType = ui.opts.types[0]
|
|
|
|
}
|
|
|
|
input.typedInput({
|
|
|
|
types: ui.opts.types,
|
|
|
|
default: inputType
|
|
|
|
})
|
|
|
|
input.typedInput('value',val.value)
|
|
|
|
} else {
|
|
|
|
input.val(val.value)
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case "select":
|
|
|
|
input = $('<select>').css('width','70%').appendTo(row);
|
|
|
|
if (ui.opts.opts) {
|
|
|
|
ui.opts.opts.forEach(function(o) {
|
|
|
|
$('<option>').val(o.v).text(lookupLabel(o.l, o.l['en-US']||o.v, currentLocale)).appendTo(input);
|
|
|
|
})
|
|
|
|
}
|
|
|
|
input.val(val.value);
|
|
|
|
break;
|
|
|
|
case "checkbox":
|
|
|
|
label.css("cursor","default");
|
|
|
|
var cblabel = $('<label>').css('width','70%').appendTo(row);
|
|
|
|
input = $('<input type="checkbox">').css({
|
|
|
|
marginTop: 0,
|
|
|
|
width: 'auto',
|
|
|
|
height: '34px'
|
|
|
|
}).appendTo(cblabel);
|
|
|
|
labelContainer.css({"padding-left":"5px"}).appendTo(cblabel);
|
|
|
|
$('<span>').css({"padding-left":"5px"}).text(labelText).appendTo(cblabel);
|
|
|
|
var boolVal = false;
|
|
|
|
if (val.type === 'bool') {
|
|
|
|
boolVal = val.value === 'true'
|
|
|
|
} else if (val.type === 'num') {
|
|
|
|
boolVal = val.value !== "0"
|
|
|
|
} else {
|
|
|
|
boolVal = val.value !== ""
|
|
|
|
}
|
|
|
|
input.prop("checked",boolVal);
|
|
|
|
break;
|
|
|
|
case "spinner":
|
|
|
|
input = $('<input>').css('width','70%').appendTo(row);
|
|
|
|
var spinnerOpts = {};
|
|
|
|
if (ui.opts.hasOwnProperty('min')) {
|
|
|
|
spinnerOpts.min = ui.opts.min;
|
|
|
|
}
|
|
|
|
if (ui.opts.hasOwnProperty('max')) {
|
|
|
|
spinnerOpts.max = ui.opts.max;
|
|
|
|
}
|
|
|
|
input.spinner(spinnerOpts).parent().width('70%');
|
|
|
|
input.val(val.value);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (input) {
|
|
|
|
input.attr('id',getSubflowEnvPropertyName(tenv.name))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create environment variable input UI
|
|
|
|
* @param uiContainer - container for UI
|
|
|
|
* @param envList - env var definitions of template
|
|
|
|
*/
|
|
|
|
function buildEnvUI(uiContainer, envList) {
|
|
|
|
uiContainer.empty();
|
|
|
|
var elementID = 0;
|
|
|
|
for (var i = 0; i < envList.length; i++) {
|
|
|
|
var tenv = envList[i];
|
|
|
|
if (tenv.ui && tenv.ui.type === 'hide') {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
var row = $("<div/>", { class: "form-row" }).appendTo(uiContainer);
|
|
|
|
buildEnvUIRow(row,tenv, tenv.ui || {});
|
|
|
|
|
|
|
|
// console.log(ui);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// buildEnvUI
|
|
|
|
|
|
|
|
function exportEnvList(list, all) {
|
|
|
|
if (list) {
|
|
|
|
var env = [];
|
|
|
|
list.each(function(i) {
|
|
|
|
var entry = $(this);
|
|
|
|
var item = entry.data('data');
|
|
|
|
var name = (item.parent?item.name:item.nameField.val()).trim();
|
|
|
|
if (name !== "") {
|
|
|
|
var valueInput = item.valueField;
|
|
|
|
var value = valueInput.typedInput("value");
|
|
|
|
var type = valueInput.typedInput("type");
|
|
|
|
if (all || !item.parent || (item.parent.value !== value || item.parent.type !== type)) {
|
|
|
|
var envItem = {
|
|
|
|
name: name,
|
|
|
|
type: type,
|
|
|
|
value: value,
|
|
|
|
};
|
|
|
|
if (item.ui) {
|
|
|
|
var ui = {
|
|
|
|
icon: item.ui.icon,
|
|
|
|
label: $.extend(true,{},item.ui.label),
|
|
|
|
type: item.ui.type,
|
|
|
|
opts: $.extend(true,{},item.ui.opts)
|
|
|
|
}
|
|
|
|
// Check to see if this is the default ui definition.
|
|
|
|
// Delete any defaults to keep it compact
|
|
|
|
// {
|
|
|
|
// icon: "",
|
|
|
|
// label: {},
|
|
|
|
// type: "input",
|
|
|
|
// opts: {types:['str','num','bool','json','bin','env']}
|
|
|
|
// }
|
|
|
|
if (!ui.icon) {
|
|
|
|
delete ui.icon;
|
|
|
|
}
|
|
|
|
if ($.isEmptyObject(ui.label)) {
|
|
|
|
delete ui.label;
|
|
|
|
}
|
|
|
|
switch (ui.type) {
|
|
|
|
case "input":
|
|
|
|
if (JSON.stringify(ui.opts) === JSON.stringify({types:['str','num','bool','json','bin','env']})) {
|
|
|
|
// This is the default input config. Delete it as it will
|
|
|
|
// be applied automatically
|
|
|
|
delete ui.type;
|
|
|
|
delete ui.opts;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case "select":
|
|
|
|
if (ui.opts && $.isEmptyObject(ui.opts.opts)) {
|
|
|
|
// This is the default select config.
|
|
|
|
// Delete it as it will be applied automatically
|
|
|
|
delete ui.opts;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case "spinner":
|
|
|
|
if ($.isEmptyObject(ui.opts)) {
|
|
|
|
// This is the default spinner config.
|
|
|
|
// Delete as it will be applied automatically
|
|
|
|
delete ui.opts
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
delete ui.opts;
|
|
|
|
}
|
|
|
|
if (!$.isEmptyObject(ui)) {
|
|
|
|
envItem.ui = ui;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
env.push(envItem);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return env;
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
function getSubflowInstanceParentEnv(node) {
|
|
|
|
var parentEnv = {};
|
|
|
|
var envList = [];
|
|
|
|
if (/^subflow:/.test(node.type)) {
|
|
|
|
var subflowDef = RED.nodes.subflow(node.type.substring(8));
|
|
|
|
if (subflowDef.env) {
|
|
|
|
subflowDef.env.forEach(function(env) {
|
|
|
|
var item = {
|
|
|
|
name:env.name,
|
|
|
|
parent: {
|
|
|
|
type: env.type,
|
|
|
|
value: env.value
|
|
|
|
},
|
|
|
|
ui: env.ui
|
|
|
|
}
|
|
|
|
envList.push(item);
|
|
|
|
parentEnv[env.name] = item;
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (node.env) {
|
|
|
|
for (var i = 0; i < node.env.length; i++) {
|
|
|
|
var env = node.env[i];
|
|
|
|
if (parentEnv.hasOwnProperty(env.name)) {
|
|
|
|
parentEnv[env.name].type = env.type;
|
|
|
|
parentEnv[env.name].value = env.value;
|
|
|
|
} else {
|
|
|
|
// envList.push({
|
|
|
|
// name: env.name,
|
|
|
|
// type: env.type,
|
|
|
|
// value: env.value,
|
|
|
|
// });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return envList;
|
|
|
|
}
|
|
|
|
|
|
|
|
function exportSubflowInstanceEnv(node) {
|
|
|
|
var env = [];
|
|
|
|
|
|
|
|
// First, get the values for the SubflowTemplate defined properties
|
|
|
|
// - these are the ones with custom UI elements
|
|
|
|
var parentEnv = getSubflowInstanceParentEnv(node);
|
|
|
|
parentEnv.forEach(function(data) {
|
|
|
|
var item;
|
|
|
|
var ui = data.ui || {};
|
|
|
|
if (!ui.type) {
|
|
|
|
ui.type = "input";
|
|
|
|
ui.opts = {types:['str','num','bool','json','bin','env']}
|
|
|
|
} else {
|
|
|
|
ui.opts = ui.opts || {};
|
|
|
|
}
|
|
|
|
var input = $("#"+getSubflowEnvPropertyName(data.name));
|
|
|
|
if (input.length) {
|
|
|
|
item = { name: data.name };
|
|
|
|
switch(ui.type) {
|
|
|
|
case "input":
|
|
|
|
if (ui.opts.types && ui.opts.types.length > 0) {
|
|
|
|
item.value = input.typedInput('value');
|
|
|
|
item.type = input.typedInput('type');
|
|
|
|
} else {
|
|
|
|
item.value = input.val();
|
|
|
|
item.type = 'str';
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case "spinner":
|
|
|
|
item.value = input.val();
|
|
|
|
item.type = 'num';
|
|
|
|
break;
|
|
|
|
case "select":
|
|
|
|
item.value = input.val();
|
|
|
|
item.type = 'str';
|
|
|
|
break;
|
|
|
|
case "checkbox":
|
|
|
|
item.type = 'bool';
|
|
|
|
item.value = ""+input.prop("checked");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (item.type !== data.parent.type || item.value !== data.parent.value) {
|
|
|
|
env.push(item);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
// Second, get the values from the Properties table tab
|
|
|
|
var items = $('#red-ui-editor-subflow-env-list').editableList('items');
|
|
|
|
items.each(function (i,el) {
|
|
|
|
var data = el.data('data');
|
|
|
|
var item;
|
|
|
|
if (data.nameField && data.valueField) {
|
|
|
|
item = {
|
|
|
|
name: data.nameField.val(),
|
|
|
|
value: data.valueField.typedInput("value"),
|
|
|
|
type: data.valueField.typedInput("type")
|
|
|
|
}
|
|
|
|
if (item.name.trim() !== "") {
|
|
|
|
env.push(item);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return env;
|
|
|
|
}
|
|
|
|
|
|
|
|
function getSubflowEnvPropertyName(name) {
|
2019-08-14 12:43:04 +02:00
|
|
|
return 'node-input-subflow-env-'+name.replace(/[^a-z0-9-_]/ig,"_");
|
2019-08-12 16:01:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Lookup text for specific locale
|
|
|
|
* @param labels - dict of labels
|
|
|
|
* @param defaultLabel - fallback label if not found
|
|
|
|
* @param locale - target locale
|
|
|
|
* @returns {string} text for specified locale
|
|
|
|
*/
|
|
|
|
function lookupLabel(labels, defaultLabel, locale) {
|
|
|
|
if (labels) {
|
|
|
|
if (labels[locale]) {
|
|
|
|
return labels[locale];
|
|
|
|
}
|
|
|
|
if (locale) {
|
|
|
|
var lang = locale.substring(0, 2);
|
|
|
|
if (labels[lang]) {
|
|
|
|
return labels[lang];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return defaultLabel;
|
|
|
|
}
|
|
|
|
|
|
|
|
function buildEditForm(container,type,node) {
|
|
|
|
if (type === "subflow-template") {
|
|
|
|
buildPropertiesList($('#node-input-env-container'), node);
|
|
|
|
} else if (type === "subflow") {
|
|
|
|
buildEnvUI($("#subflow-input-ui"), getSubflowInstanceParentEnv(node));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
function buildPropertiesForm(container, node) {
|
|
|
|
var form = $('<form class="dialog-form form-horizontal"></form>').appendTo(container);
|
|
|
|
var listContainer = $('<div class="form-row node-input-env-container-row"></div>').appendTo(form);
|
|
|
|
var list = $('<ol id="red-ui-editor-subflow-env-list" class="red-ui-editor-subflow-env-list"></ol>').appendTo(listContainer);
|
|
|
|
buildPropertiesList(list, node);
|
|
|
|
}
|
|
|
|
|
2015-03-12 01:08:47 +01:00
|
|
|
return {
|
|
|
|
init: init,
|
|
|
|
createSubflow: createSubflow,
|
2015-07-30 12:03:37 +02:00
|
|
|
convertToSubflow: convertToSubflow,
|
2016-12-20 20:42:38 +01:00
|
|
|
removeSubflow: removeSubflow,
|
2015-07-30 12:03:37 +02:00
|
|
|
refresh: refresh,
|
|
|
|
removeInput: removeSubflowInput,
|
2019-02-02 00:44:50 +01:00
|
|
|
removeOutput: removeSubflowOutput,
|
2019-08-12 16:01:54 +02:00
|
|
|
removeStatus: removeSubflowStatus,
|
|
|
|
|
|
|
|
|
|
|
|
buildEditForm: buildEditForm,
|
|
|
|
buildPropertiesForm: buildPropertiesForm,
|
|
|
|
|
|
|
|
exportSubflowTemplateEnv: exportEnvList,
|
|
|
|
exportSubflowInstanceEnv: exportSubflowInstanceEnv
|
|
|
|
|
2015-03-12 01:08:47 +01:00
|
|
|
}
|
|
|
|
})();
|