node-red/packages/node_modules/@node-red/editor-client/src/js/ui/editors/envVarList.js

626 lines
30 KiB
JavaScript

RED.editor.envVarList = (function() {
var currentLocale = 'en-US';
var DEFAULT_ENV_TYPE_LIST = ['str','num','bool','json','bin','env'];
var DEFAULT_ENV_TYPE_LIST_INC_CRED = ['str','num','bool','json','bin','env','cred','jsonata'];
/**
* Create env var edit interface
* @param container - container
* @param node - subflow node
*/
function buildPropertiesList(envContainer, node) {
var isTemplateNode = (node.type === "subflow");
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 this is an instance node, these are properties unique to
// this instance - ie opt.parent will not be defined.
if (isTemplateNode) {
container.addClass("red-ui-editor-subflow-env-editable")
}
var envRow = $('<div/>').appendTo(container);
var nameField = null;
var valueField = null;
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);
var types = (opt.ui && opt.ui.opts && opt.ui.opts.types);
if (!types) {
types = isTemplateNode ? DEFAULT_ENV_TYPE_LIST : DEFAULT_ENV_TYPE_LIST_INC_CRED;
}
valueField.typedInput({default:'str',types:types});
valueField.typedInput('type', opt.type);
if (opt.type === "cred") {
if (opt.value) {
valueField.typedInput('value', opt.value);
} else if (node.credentials && node.credentials[opt.name]) {
valueField.typedInput('value', node.credentials[opt.name]);
} else if (node.credentials && node.credentials['has_'+opt.name]) {
valueField.typedInput('value', "__PWRD__");
} else {
valueField.typedInput('value', "");
}
} else {
valueField.typedInput('value', opt.value);
}
opt.nameField = nameField;
opt.valueField = valueField;
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.
if (opt.type === 'cred') {
opt.ui = opt.ui || {
icon: "",
type: "cred"
}
opt.ui.type = "cred";
} else {
opt.ui = opt.ui || {
icon: "",
type: "input",
opts: {types:DEFAULT_ENV_TYPE_LIST}
}
}
opt.ui.label = opt.ui.label || {};
opt.ui.type = opt.ui.type || "input";
if ((opt.ui.type === "cred") &&
opt.ui.opts &&
opt.ui.opts.types) {
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:DEFAULT_ENV_TYPE_LIST},
'select': {opts:[]},
'spinner': {},
'cred': {}
};
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) : {});
RED.editor.iconPicker.show(iconButton, null, iconPath, true, function (newIcon) {
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:"120px"}).text(RED._("languages."+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: [
{
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"},
{value: "cred",label: RED._("editor.types.cred"),icon: "fa fa-lock"}
],
default: DEFAULT_ENV_TYPE_LIST,
valueLabel: function(container,value) {
container.css("padding",0);
var innerContainer = $('<div class="red-ui-editor-subflow-env-input-type"></div>').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) {
if (!/^fa /.test(v.icon)) {
$('<img>',{src:v.icon,style:"max-width:14px; padding: 0 3px; margin-top:-4px; margin-left: 1px"}).appendTo(input);
} else {
var s = $('<span>',{style:"max-width:14px; padding: 0 3px; margin-top:-4px; margin-left: 1px"}).appendTo(input);
$("<i>",{class: v.icon}).appendTo(s);
}
})
} else {
$('<span class="red-ui-editor-subflow-env-input-type-placeholder"></span>').text(RED._("editor.selectType")).appendTo(input);
}
}
},
{
value: "cred",
label: RED._("typedInput.type.cred"), icon:"fa fa-lock", showLabel: false,
valueLabel: function(container,value) {
container.css("padding",0);
var innerContainer = $('<div class="red-ui-editor-subflow-env-input-type">').css({
"border-top-right-radius": "4px",
"border-bottom-right-radius": "4px"
}).appendTo(container);
$('<div class="placeholder-input">').html("&bull;&bull;&bull;&bull;&bull;&bull;&bull;&bull;").appendTo(innerContainer);
}
},
{
value:"select",
label:RED._("editor.inputs.select"), icon:"fa fa-tasks",showLabel:false,
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({
header:$("<div><div>"+RED._("editor.select.label")+"</div><div>"+RED._("editor.select.value")+"</div></div>"),
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:DEFAULT_ENV_TYPE_LIST});
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())
}
}
}
}
},
{
value:"checkbox",
label:RED._("editor.inputs.checkbox"), icon:"fa fa-check-square-o",showLabel:false,
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");
}
},
{
value:"spinner",
label:RED._("editor.inputs.spinner"), icon:"fa fa-sort-numeric-asc", showLabel:false,
valueLabel: function(container,value) {
container.css("padding",0);
var innerContainer = $('<div class="red-ui-editor-subflow-env-input-type"></div>').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);
$('<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);
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())
}
}
}
}
},
{
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
}
],
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;
case 'cred':
valueField.typedInput('types',['cred']);
break;
default:
valueField.typedInput('types',DEFAULT_ENV_TYPE_LIST)
}
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') {
var types = inputCellInput.typedInput('value');
ui.opts.types = (types === "") ? ["str"] : types.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 setLocale(l, list) {
currentLocale = l;
if (list) {
var items = list.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);
});
}
}
/**
* 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;
}
return {
create: buildPropertiesList,
setLocale: setLocale,
lookupLabel: lookupLabel,
DEFAULT_ENV_TYPE_LIST: DEFAULT_ENV_TYPE_LIST,
DEFAULT_ENV_TYPE_LIST_INC_CRED: DEFAULT_ENV_TYPE_LIST_INC_CRED
}
})();