mirror of
https://github.com/node-red/node-red.git
synced 2023-10-10 13:36:53 +02:00
e5511ea86d
Closes #1026,#983,#982,#978 PR #1026 contains the full history of the changes made herein. Due to the volume of commits with no meaningful comment, they have been squashed down to this one. Adding bidi Files Tuning code style Adding rest of bidi files and its dependencies Tuning code style editing numeric shaping value Tuning Code style adding bidi support Adding Bidi Support for the rest of files Adding Bidi support Editing Bidi menu adding mirroring enablement handeling code style Addinng Bidi Support Adding Bidi Support Adding locale settings to national calendar support Adding Numeric Shaping Support scss files after adding rtl direction part 1 adding rtl direction at scss files part2 adding right directionality at comman typeInput correcting some comments editing spaces applying code style editing code style editing code style editing code style editing code style adding right directionality adding right directionality adding style in case of right directionality adding style in case of right directionality adding style in case of right directionality create a global variable for ui direction to call it once need Update main.js Update typedInput.js Update 10-switch.html manage palette mirroring adding RTL directionality to Tabs and Manage palette Style Editing Adding mirroring to subflow workspace handle mirroring defects at manage palette Handle Mirroring defects at sidebar seperator Numeric Shaping Updates Editing code style Handling mirroring defects Handling mirroring defects Fixing mirroring defects editing code style fixing mirroring defects at deploy dialog editing code style Updating Bidi Support handling some reviewing comments handling chicks Update base-text-dir.js Update bidi-util.js Update bidi-util.js Handling reviewing comment Fixing Popover mirroring defect Handling namespace structure for bidi features reflecting new namespace structure for bidi features at editorfiles Handling comments that related to css moving bidi.js under js/bidi folder
1406 lines
61 KiB
JavaScript
1406 lines
61 KiB
JavaScript
/**
|
|
* Copyright 2013, 2016 IBM Corp.
|
|
*
|
|
* 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.editor = (function() {
|
|
|
|
|
|
var editStack = [];
|
|
var editing_node = null;
|
|
var editing_config_node = null;
|
|
var subflowEditor;
|
|
|
|
var editTrayWidthCache = {};
|
|
|
|
function getCredentialsURL(nodeType, nodeID) {
|
|
var dashedType = nodeType.replace(/\s+/g, '-');
|
|
return 'credentials/' + dashedType + "/" + nodeID;
|
|
}
|
|
|
|
/**
|
|
* Validate a node
|
|
* @param node - the node being validated
|
|
* @returns {boolean} whether the node is valid. Sets node.dirty if needed
|
|
*/
|
|
function validateNode(node) {
|
|
var oldValue = node.valid;
|
|
var oldChanged = node.changed;
|
|
node.valid = true;
|
|
var subflow;
|
|
var isValid;
|
|
var hasChanged;
|
|
if (node.type.indexOf("subflow:")===0) {
|
|
subflow = RED.nodes.subflow(node.type.substring(8));
|
|
isValid = subflow.valid;
|
|
hasChanged = subflow.changed;
|
|
if (isValid === undefined) {
|
|
isValid = validateNode(subflow);
|
|
hasChanged = subflow.changed;
|
|
}
|
|
node.valid = isValid;
|
|
node.changed = node.changed || hasChanged;
|
|
} else if (node._def) {
|
|
node.valid = validateNodeProperties(node, node._def.defaults, node);
|
|
if (node._def._creds) {
|
|
node.valid = node.valid && validateNodeProperties(node, node._def.credentials, node._def._creds);
|
|
}
|
|
} else if (node.type == "subflow") {
|
|
var subflowNodes = RED.nodes.filterNodes({z:node.id});
|
|
for (var i=0;i<subflowNodes.length;i++) {
|
|
isValid = subflowNodes[i].valid;
|
|
hasChanged = subflowNodes[i].changed;
|
|
if (isValid === undefined) {
|
|
isValid = validateNode(subflowNodes[i]);
|
|
hasChanged = subflowNodes[i].changed;
|
|
}
|
|
node.valid = node.valid && isValid;
|
|
node.changed = node.changed || hasChanged;
|
|
}
|
|
var subflowInstances = RED.nodes.filterNodes({type:"subflow:"+node.id});
|
|
var modifiedTabs = {};
|
|
for (i=0;i<subflowInstances.length;i++) {
|
|
subflowInstances[i].valid = node.valid;
|
|
subflowInstances[i].changed = subflowInstances[i].changed || node.changed;
|
|
subflowInstances[i].dirty = true;
|
|
modifiedTabs[subflowInstances[i].z] = true;
|
|
}
|
|
Object.keys(modifiedTabs).forEach(function(id) {
|
|
var subflow = RED.nodes.subflow(id);
|
|
if (subflow) {
|
|
validateNode(subflow);
|
|
}
|
|
});
|
|
}
|
|
if (oldValue !== node.valid || oldChanged !== node.changed) {
|
|
node.dirty = true;
|
|
subflow = RED.nodes.subflow(node.z);
|
|
if (subflow) {
|
|
validateNode(subflow);
|
|
}
|
|
}
|
|
return node.valid;
|
|
}
|
|
|
|
/**
|
|
* Validate a node's properties for the given set of property definitions
|
|
* @param node - the node being validated
|
|
* @param definition - the node property definitions (either def.defaults or def.creds)
|
|
* @param properties - the node property values to validate
|
|
* @returns {boolean} whether the node's properties are valid
|
|
*/
|
|
function validateNodeProperties(node, definition, properties) {
|
|
var isValid = true;
|
|
for (var prop in definition) {
|
|
if (definition.hasOwnProperty(prop)) {
|
|
if (!validateNodeProperty(node, definition, prop, properties[prop])) {
|
|
isValid = false;
|
|
}
|
|
}
|
|
}
|
|
return isValid;
|
|
}
|
|
|
|
/**
|
|
* Validate a individual node property
|
|
* @param node - the node being validated
|
|
* @param definition - the node property definitions (either def.defaults or def.creds)
|
|
* @param property - the property name being validated
|
|
* @param value - the property value being validated
|
|
* @returns {boolean} whether the node proprty is valid
|
|
*/
|
|
function validateNodeProperty(node,definition,property,value) {
|
|
var valid = true;
|
|
if (/^\$\([a-zA-Z_][a-zA-Z0-9_]*\)$/.test(value)) {
|
|
return true;
|
|
}
|
|
if ("required" in definition[property] && definition[property].required) {
|
|
valid = value !== "";
|
|
}
|
|
if (valid && "validate" in definition[property]) {
|
|
try {
|
|
valid = definition[property].validate.call(node,value);
|
|
} catch(err) {
|
|
console.log("Validation error:",node.type,node.id,"property: "+property,"value:",value,err);
|
|
}
|
|
}
|
|
if (valid && definition[property].type && RED.nodes.getType(definition[property].type) && !("validate" in definition[property])) {
|
|
if (!value || value == "_ADD_") {
|
|
valid = definition[property].hasOwnProperty("required") && !definition[property].required;
|
|
} else {
|
|
var configNode = RED.nodes.node(value);
|
|
valid = (configNode !== null && (configNode.valid == null || configNode.valid));
|
|
}
|
|
}
|
|
return valid;
|
|
}
|
|
|
|
|
|
function validateNodeEditor(node,prefix) {
|
|
for (var prop in node._def.defaults) {
|
|
if (node._def.defaults.hasOwnProperty(prop)) {
|
|
validateNodeEditorProperty(node,node._def.defaults,prop,prefix);
|
|
}
|
|
}
|
|
if (node._def.credentials) {
|
|
for (prop in node._def.credentials) {
|
|
if (node._def.credentials.hasOwnProperty(prop)) {
|
|
validateNodeEditorProperty(node,node._def.credentials,prop,prefix);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
function validateNodeEditorProperty(node,defaults,property,prefix) {
|
|
var input = $("#"+prefix+"-"+property);
|
|
if (input.length > 0) {
|
|
var value = input.val();
|
|
if (defaults[property].hasOwnProperty("format") && defaults[property].format !== "" && input[0].nodeName === "DIV") {
|
|
value = input.text();
|
|
}
|
|
if (!validateNodeProperty(node, defaults, property,value)) {
|
|
input.addClass("input-error");
|
|
} else {
|
|
input.removeClass("input-error");
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called when the node's properties have changed.
|
|
* Marks the node as dirty and needing a size check.
|
|
* Removes any links to non-existant outputs.
|
|
* @param node - the node that has been updated
|
|
* @returns {array} the links that were removed due to this update
|
|
*/
|
|
function updateNodeProperties(node) {
|
|
node.resize = true;
|
|
node.dirty = true;
|
|
var removedLinks = [];
|
|
if (node.ports) {
|
|
if (node.outputs < node.ports.length) {
|
|
while (node.outputs < node.ports.length) {
|
|
node.ports.pop();
|
|
}
|
|
RED.nodes.eachLink(function(l) {
|
|
if (l.source === node && l.sourcePort >= node.outputs) {
|
|
removedLinks.push(l);
|
|
}
|
|
});
|
|
} else if (node.outputs > node.ports.length) {
|
|
while (node.outputs > node.ports.length) {
|
|
node.ports.push(node.ports.length);
|
|
}
|
|
}
|
|
}
|
|
if (node.inputs === 0) {
|
|
removedLinks.concat(RED.nodes.filterLinks({target:node}));
|
|
}
|
|
for (var l=0;l<removedLinks.length;l++) {
|
|
RED.nodes.removeLink(removedLinks[l]);
|
|
}
|
|
return removedLinks;
|
|
}
|
|
|
|
/**
|
|
* Create a config-node select box for this property
|
|
* @param node - the node being edited
|
|
* @param property - the name of the field
|
|
* @param type - the type of the config-node
|
|
*/
|
|
function prepareConfigNodeSelect(node,property,type,prefix) {
|
|
var input = $("#"+prefix+"-"+property);
|
|
if (input.length === 0 ) {
|
|
return;
|
|
}
|
|
var newWidth = input.width();
|
|
var attrStyle = input.attr('style');
|
|
var m;
|
|
if ((m = /width\s*:\s*(\d+(%|[a-z]+))/i.exec(attrStyle)) !== null) {
|
|
newWidth = m[1];
|
|
} else {
|
|
newWidth = "70%";
|
|
}
|
|
var outerWrap = $("<div></div>").css({display:'inline-block',position:'relative'});
|
|
var selectWrap = $("<div></div>").css({position:'absolute',left:0,right:'40px'}).appendTo(outerWrap);
|
|
var select = $('<select id="'+prefix+'-'+property+'"></select>').appendTo(selectWrap);
|
|
|
|
outerWrap.width(newWidth).height(input.height());
|
|
if (outerWrap.width() === 0) {
|
|
outerWrap.width("70%");
|
|
}
|
|
input.replaceWith(outerWrap);
|
|
// set the style attr directly - using width() on FF causes a value of 114%...
|
|
select.attr('style',"width:100%");
|
|
updateConfigNodeSelect(property,type,node[property],prefix);
|
|
$('<a id="'+prefix+'-lookup-'+property+'" class="editor-button"><i class="fa fa-pencil"></i></a>')
|
|
.css({position:'absolute',right:0,top:0})
|
|
.appendTo(outerWrap);
|
|
$('#'+prefix+'-lookup-'+property).click(function(e) {
|
|
showEditConfigNodeDialog(property,type,select.find(":selected").val(),prefix);
|
|
e.preventDefault();
|
|
});
|
|
var label = "";
|
|
var configNode = RED.nodes.node(node[property]);
|
|
var node_def = RED.nodes.getType(type);
|
|
|
|
if (configNode && node_def.label) {
|
|
if (typeof node_def.label == "function") {
|
|
try {
|
|
label = node_def.label.call(configNode);
|
|
} catch(err) {
|
|
console.log("Definition error: "+node_def.type+".label",err);
|
|
label = node_def.type;
|
|
}
|
|
} else {
|
|
label = node_def.label;
|
|
}
|
|
}
|
|
input.val(label);
|
|
}
|
|
|
|
/**
|
|
* Create a config-node button for this property
|
|
* @param node - the node being edited
|
|
* @param property - the name of the field
|
|
* @param type - the type of the config-node
|
|
*/
|
|
function prepareConfigNodeButton(node,property,type,prefix) {
|
|
var input = $("#"+prefix+"-"+property);
|
|
input.val(node[property]);
|
|
input.attr("type","hidden");
|
|
|
|
var button = $("<a>",{id:prefix+"-edit-"+property, class:"editor-button"});
|
|
input.after(button);
|
|
|
|
if (node[property]) {
|
|
button.text(RED._("editor.configEdit"));
|
|
} else {
|
|
button.text(RED._("editor.configAdd"));
|
|
}
|
|
|
|
button.click(function(e) {
|
|
showEditConfigNodeDialog(property,type,input.val()||"_ADD_",prefix);
|
|
e.preventDefault();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Populate the editor dialog input field for this property
|
|
* @param node - the node being edited
|
|
* @param property - the name of the field
|
|
* @param prefix - the prefix to use in the input element ids (node-input|node-config-input)
|
|
* @param definition - the definition of the field
|
|
*/
|
|
function preparePropertyEditor(node,property,prefix,definition) {
|
|
var input = $("#"+prefix+"-"+property);
|
|
if (input.length === 0) {
|
|
return;
|
|
}
|
|
if (input.attr('type') === "checkbox") {
|
|
input.prop('checked',node[property]);
|
|
}
|
|
else {
|
|
var val = node[property];
|
|
if (val == null) {
|
|
val = "";
|
|
}
|
|
if (definition !== undefined && definition[property].hasOwnProperty("format") && definition[property].format !== "" && input[0].nodeName === "DIV") {
|
|
input.html(RED.bidi.applyBidiSupport(val, RED.bidi.flags.STT_GETHTML, definition[property].format, {}));
|
|
RED.bidi.applyBidiSupport(input[0],RED.bidi.flags.STT_ATTACH, definition[property].format, {});
|
|
} else {
|
|
input.val(val);
|
|
if (input[0].nodeName === 'INPUT' || input[0].nodeName === 'TEXTAREA') {
|
|
RED.bidi.prepareInput(input);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add an on-change handler to revalidate a node field
|
|
* @param node - the node being edited
|
|
* @param definition - the definition of the node
|
|
* @param property - the name of the field
|
|
* @param prefix - the prefix to use in the input element ids (node-input|node-config-input)
|
|
*/
|
|
function attachPropertyChangeHandler(node,definition,property,prefix) {
|
|
var input = $("#"+prefix+"-"+property);
|
|
if (definition !== undefined && "format" in definition[property] && definition[property].format !== "" && input[0].nodeName === "DIV") {
|
|
$("#"+prefix+"-"+property).on('change keyup', function(event,skipValidation) {
|
|
if (!skipValidation) {
|
|
validateNodeEditor(node,prefix);
|
|
}
|
|
});
|
|
} else {
|
|
$("#"+prefix+"-"+property).change(function(event,skipValidation) {
|
|
if (!skipValidation) {
|
|
validateNodeEditor(node,prefix);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Assign the value to each credential field
|
|
* @param node
|
|
* @param credDef
|
|
* @param credData
|
|
* @param prefix
|
|
*/
|
|
function populateCredentialsInputs(node, credDef, credData, prefix) {
|
|
var cred;
|
|
for (cred in credDef) {
|
|
if (credDef.hasOwnProperty(cred)) {
|
|
if (credDef[cred].type == 'password') {
|
|
if (credData[cred]) {
|
|
$('#' + prefix + '-' + cred).val(credData[cred]);
|
|
} else if (credData['has_' + cred]) {
|
|
$('#' + prefix + '-' + cred).val('__PWRD__');
|
|
}
|
|
else {
|
|
$('#' + prefix + '-' + cred).val('');
|
|
}
|
|
} else {
|
|
preparePropertyEditor(credData, cred, prefix, credDef);
|
|
}
|
|
attachPropertyChangeHandler(node, credDef, cred, prefix);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update the node credentials from the edit form
|
|
* @param node - the node containing the credentials
|
|
* @param credDefinition - definition of the credentials
|
|
* @param prefix - prefix of the input fields
|
|
* @return {boolean} whether anything has changed
|
|
*/
|
|
function updateNodeCredentials(node, credDefinition, prefix) {
|
|
var changed = false;
|
|
if(!node.credentials) {
|
|
node.credentials = {_:{}};
|
|
}
|
|
|
|
for (var cred in credDefinition) {
|
|
if (credDefinition.hasOwnProperty(cred)) {
|
|
var input = $("#" + prefix + '-' + cred);
|
|
var value = input.val();
|
|
if (credDefinition[cred].type == 'password') {
|
|
node.credentials['has_' + cred] = (value !== "");
|
|
if (value == '__PWRD__') {
|
|
continue;
|
|
}
|
|
changed = true;
|
|
|
|
}
|
|
node.credentials[cred] = value;
|
|
if (value != node.credentials._[cred]) {
|
|
changed = true;
|
|
}
|
|
}
|
|
}
|
|
return changed;
|
|
}
|
|
|
|
/**
|
|
* Prepare all of the editor dialog fields
|
|
* @param node - the node being edited
|
|
* @param definition - the node definition
|
|
* @param prefix - the prefix to use in the input element ids (node-input|node-config-input)
|
|
*/
|
|
function prepareEditDialog(node,definition,prefix) {
|
|
for (var d in definition.defaults) {
|
|
if (definition.defaults.hasOwnProperty(d)) {
|
|
if (definition.defaults[d].type) {
|
|
var configTypeDef = RED.nodes.getType(definition.defaults[d].type);
|
|
if (configTypeDef) {
|
|
if (configTypeDef.exclusive) {
|
|
prepareConfigNodeButton(node,d,definition.defaults[d].type,prefix);
|
|
} else {
|
|
prepareConfigNodeSelect(node,d,definition.defaults[d].type,prefix);
|
|
}
|
|
} else {
|
|
console.log("Unknown type:", definition.defaults[d].type);
|
|
preparePropertyEditor(node,d,prefix,definition.defaults);
|
|
}
|
|
} else {
|
|
preparePropertyEditor(node,d,prefix,definition.defaults);
|
|
}
|
|
attachPropertyChangeHandler(node,definition.defaults,d,prefix);
|
|
}
|
|
}
|
|
var completePrepare = function() {
|
|
if (definition.oneditprepare) {
|
|
try {
|
|
definition.oneditprepare.call(node);
|
|
} catch(err) {
|
|
console.log("oneditprepare",node.id,node.type,err.toString());
|
|
}
|
|
}
|
|
// Now invoke any change handlers added to the fields - passing true
|
|
// to prevent full node validation from being triggered each time
|
|
for (var d in definition.defaults) {
|
|
if (definition.defaults.hasOwnProperty(d)) {
|
|
$("#"+prefix+"-"+d).trigger("change",[true]);
|
|
}
|
|
}
|
|
if (definition.credentials) {
|
|
for (d in definition.credentials) {
|
|
if (definition.credentials.hasOwnProperty(d)) {
|
|
$("#"+prefix+"-"+d).trigger("change",[true]);
|
|
}
|
|
}
|
|
}
|
|
validateNodeEditor(node,prefix);
|
|
}
|
|
|
|
if (definition.credentials) {
|
|
if (node.credentials) {
|
|
populateCredentialsInputs(node, definition.credentials, node.credentials, prefix);
|
|
completePrepare();
|
|
} else {
|
|
$.getJSON(getCredentialsURL(node.type, node.id), function (data) {
|
|
node.credentials = data;
|
|
node.credentials._ = $.extend(true,{},data);
|
|
populateCredentialsInputs(node, definition.credentials, node.credentials, prefix);
|
|
completePrepare();
|
|
});
|
|
}
|
|
} else {
|
|
completePrepare();
|
|
}
|
|
}
|
|
|
|
function getEditStackTitle() {
|
|
|
|
var title = '<ul class="editor-tray-breadcrumbs">';
|
|
for (var i=0;i<editStack.length;i++) {
|
|
var node = editStack[i];
|
|
var label = node.type;
|
|
if (node.type === 'subflow') {
|
|
label = RED._("subflow.editSubflow",{name:node.name})
|
|
} else if (node.type.indexOf("subflow:")===0) {
|
|
var subflow = RED.nodes.subflow(node.type.substring(8));
|
|
label = RED._("subflow.editSubflow",{name:subflow.name})
|
|
} else {
|
|
if (typeof node._def.paletteLabel !== "undefined") {
|
|
try {
|
|
label = (typeof node._def.paletteLabel === "function" ? node._def.paletteLabel.call(node._def) : node._def.paletteLabel)||"";
|
|
} catch(err) {
|
|
console.log("Definition error: "+node.type+".paletteLabel",err);
|
|
}
|
|
}
|
|
if (i === editStack.length-1) {
|
|
if (RED.nodes.node(node.id)) {
|
|
label = RED._("editor.editNode",{type:label});
|
|
} else {
|
|
label = RED._("editor.addNewConfig",{type:label});
|
|
}
|
|
}
|
|
}
|
|
title += '<li>'+label+'</li>';
|
|
}
|
|
title += '</ul>';
|
|
return title;
|
|
}
|
|
|
|
function showEditDialog(node) {
|
|
var editing_node = node;
|
|
editStack.push(node);
|
|
RED.view.state(RED.state.EDITING);
|
|
var type = node.type;
|
|
if (node.type.substring(0,8) == "subflow:") {
|
|
type = "subflow";
|
|
}
|
|
var trayOptions = {
|
|
title: getEditStackTitle(),
|
|
buttons: [
|
|
{
|
|
id: "node-dialog-cancel",
|
|
text: RED._("common.label.cancel"),
|
|
click: function() {
|
|
if (editing_node._def) {
|
|
if (editing_node._def.oneditcancel) {
|
|
try {
|
|
editing_node._def.oneditcancel.call(editing_node);
|
|
} catch(err) {
|
|
console.log("oneditcancel",editing_node.id,editing_node.type,err.toString());
|
|
}
|
|
}
|
|
|
|
for (var d in editing_node._def.defaults) {
|
|
if (editing_node._def.defaults.hasOwnProperty(d)) {
|
|
var def = editing_node._def.defaults[d];
|
|
if (def.type) {
|
|
var configTypeDef = RED.nodes.getType(def.type);
|
|
if (configTypeDef && configTypeDef.exclusive) {
|
|
var input = $("#node-input-"+d).val()||"";
|
|
if (input !== "" && !editing_node[d]) {
|
|
// This node has an exclusive config node that
|
|
// has just been added. As the user is cancelling
|
|
// the edit, need to delete the just-added config
|
|
// node so that it doesn't get orphaned.
|
|
RED.nodes.remove(input);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
RED.tray.close();
|
|
}
|
|
},
|
|
{
|
|
id: "node-dialog-ok",
|
|
text: RED._("common.label.done"),
|
|
class: "primary",
|
|
click: function() {
|
|
var changes = {};
|
|
var changed = false;
|
|
var wasDirty = RED.nodes.dirty();
|
|
var d;
|
|
|
|
if (editing_node._def.oneditsave) {
|
|
var oldValues = {};
|
|
for (d in editing_node._def.defaults) {
|
|
if (editing_node._def.defaults.hasOwnProperty(d)) {
|
|
if (typeof editing_node[d] === "string" || typeof editing_node[d] === "number") {
|
|
oldValues[d] = editing_node[d];
|
|
} else {
|
|
oldValues[d] = $.extend(true,{},{v:editing_node[d]}).v;
|
|
}
|
|
}
|
|
}
|
|
try {
|
|
var rc = editing_node._def.oneditsave.call(editing_node);
|
|
if (rc === true) {
|
|
changed = true;
|
|
}
|
|
} catch(err) {
|
|
console.log("oneditsave",editing_node.id,editing_node.type,err.toString());
|
|
}
|
|
|
|
for (d in editing_node._def.defaults) {
|
|
if (editing_node._def.defaults.hasOwnProperty(d)) {
|
|
if (oldValues[d] === null || typeof oldValues[d] === "string" || typeof oldValues[d] === "number") {
|
|
if (oldValues[d] !== editing_node[d]) {
|
|
changes[d] = oldValues[d];
|
|
changed = true;
|
|
}
|
|
} else {
|
|
if (JSON.stringify(oldValues[d]) !== JSON.stringify(editing_node[d])) {
|
|
changes[d] = oldValues[d];
|
|
changed = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (editing_node._def.defaults) {
|
|
for (d in editing_node._def.defaults) {
|
|
if (editing_node._def.defaults.hasOwnProperty(d)) {
|
|
var input = $("#node-input-"+d);
|
|
var newValue;
|
|
if (input.attr('type') === "checkbox") {
|
|
newValue = input.prop('checked');
|
|
} else if ("format" in editing_node._def.defaults[d] && editing_node._def.defaults[d].format !== "" && input[0].nodeName === "DIV") {
|
|
newValue = input.text();
|
|
} else {
|
|
newValue = input.val();
|
|
}
|
|
if (newValue != null) {
|
|
if (d === "outputs" && (newValue.trim() === "" || isNaN(newValue))) {
|
|
continue;
|
|
}
|
|
if (editing_node[d] != newValue) {
|
|
if (editing_node._def.defaults[d].type) {
|
|
if (newValue == "_ADD_") {
|
|
newValue = "";
|
|
}
|
|
// Change to a related config node
|
|
var configNode = RED.nodes.node(editing_node[d]);
|
|
if (configNode) {
|
|
var users = configNode.users;
|
|
users.splice(users.indexOf(editing_node),1);
|
|
}
|
|
configNode = RED.nodes.node(newValue);
|
|
if (configNode) {
|
|
configNode.users.push(editing_node);
|
|
}
|
|
}
|
|
changes[d] = editing_node[d];
|
|
editing_node[d] = newValue;
|
|
changed = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (editing_node._def.credentials) {
|
|
var prefix = 'node-input';
|
|
var credDefinition = editing_node._def.credentials;
|
|
var credsChanged = updateNodeCredentials(editing_node,credDefinition,prefix);
|
|
changed = changed || credsChanged;
|
|
}
|
|
|
|
var removedLinks = updateNodeProperties(editing_node);
|
|
if (changed) {
|
|
var wasChanged = editing_node.changed;
|
|
editing_node.changed = true;
|
|
RED.nodes.dirty(true);
|
|
|
|
var activeSubflow = RED.nodes.subflow(RED.workspaces.active());
|
|
var subflowInstances = null;
|
|
if (activeSubflow) {
|
|
subflowInstances = [];
|
|
RED.nodes.eachNode(function(n) {
|
|
if (n.type == "subflow:"+RED.workspaces.active()) {
|
|
subflowInstances.push({
|
|
id:n.id,
|
|
changed:n.changed
|
|
});
|
|
n.changed = true;
|
|
n.dirty = true;
|
|
updateNodeProperties(n);
|
|
}
|
|
});
|
|
}
|
|
var historyEvent = {
|
|
t:'edit',
|
|
node:editing_node,
|
|
changes:changes,
|
|
links:removedLinks,
|
|
dirty:wasDirty,
|
|
changed:wasChanged
|
|
};
|
|
if (subflowInstances) {
|
|
historyEvent.subflow = {
|
|
instances:subflowInstances
|
|
}
|
|
}
|
|
RED.history.push(historyEvent);
|
|
}
|
|
editing_node.dirty = true;
|
|
validateNode(editing_node);
|
|
RED.events.emit("editor:save",editing_node);
|
|
RED.tray.close();
|
|
}
|
|
}
|
|
],
|
|
resize: function(dimensions) {
|
|
editTrayWidthCache[type] = dimensions.width;
|
|
if (editing_node && editing_node._def.oneditresize) {
|
|
var form = $("#dialog-form");
|
|
try {
|
|
editing_node._def.oneditresize.call(editing_node,{width:form.width(),height:form.height()});
|
|
} catch(err) {
|
|
console.log("oneditresize",editing_node.id,editing_node.type,err.toString());
|
|
}
|
|
}
|
|
},
|
|
open: function(tray) {
|
|
if (editing_node) {
|
|
RED.sidebar.info.refresh(editing_node);
|
|
}
|
|
var trayBody = tray.find('.editor-tray-body');
|
|
var dialogForm = $('<form id="dialog-form" class="form-horizontal"></form>').appendTo(trayBody);
|
|
dialogForm.html($("script[data-template-name='"+type+"']").html());
|
|
var ns;
|
|
if (node._def.set.module === "node-red") {
|
|
ns = "node-red";
|
|
} else {
|
|
ns = node._def.set.id;
|
|
}
|
|
dialogForm.find('[data-i18n]').each(function() {
|
|
var current = $(this).attr("data-i18n");
|
|
var keys = current.split(";");
|
|
for (var i=0;i<keys.length;i++) {
|
|
var key = keys[i];
|
|
if (key.indexOf(":") === -1) {
|
|
var prefix = "";
|
|
if (key.indexOf("[")===0) {
|
|
var parts = key.split("]");
|
|
prefix = parts[0]+"]";
|
|
key = parts[1];
|
|
}
|
|
keys[i] = prefix+ns+":"+key;
|
|
}
|
|
}
|
|
$(this).attr("data-i18n",keys.join(";"));
|
|
});
|
|
$('<input type="text" style="display: none;" />').prependTo(dialogForm);
|
|
prepareEditDialog(node,node._def,"node-input");
|
|
dialogForm.i18n();
|
|
},
|
|
close: function() {
|
|
if (RED.view.state() != RED.state.IMPORT_DRAGGING) {
|
|
RED.view.state(RED.state.DEFAULT);
|
|
}
|
|
if (editing_node) {
|
|
RED.sidebar.info.refresh(editing_node);
|
|
}
|
|
RED.workspaces.refresh();
|
|
RED.view.redraw(true);
|
|
editStack.pop();
|
|
},
|
|
show: function() {
|
|
if (editing_node) {
|
|
RED.sidebar.info.refresh(editing_node);
|
|
}
|
|
}
|
|
}
|
|
if (editTrayWidthCache.hasOwnProperty(type)) {
|
|
trayOptions.width = editTrayWidthCache[type];
|
|
}
|
|
|
|
if (type === 'subflow') {
|
|
var id = editing_node.type.substring(8);
|
|
trayOptions.buttons.unshift({
|
|
class: 'leftButton',
|
|
text: RED._("subflow.edit"),
|
|
click: function() {
|
|
RED.workspaces.show(id);
|
|
$("#node-dialog-ok").click();
|
|
}
|
|
});
|
|
}
|
|
|
|
RED.tray.show(trayOptions);
|
|
}
|
|
/**
|
|
* name - name of the property that holds this config node
|
|
* type - type of config node
|
|
* id - id of config node to edit. _ADD_ for a new one
|
|
* prefix - the input prefix of the parent property
|
|
*/
|
|
function showEditConfigNodeDialog(name,type,id,prefix) {
|
|
var adding = (id == "_ADD_");
|
|
var node_def = RED.nodes.getType(type);
|
|
var editing_config_node = RED.nodes.node(id);
|
|
|
|
var ns;
|
|
if (node_def.set.module === "node-red") {
|
|
ns = "node-red";
|
|
} else {
|
|
ns = node_def.set.id;
|
|
}
|
|
var configNodeScope = ""; // default to global
|
|
var activeSubflow = RED.nodes.subflow(RED.workspaces.active());
|
|
if (activeSubflow) {
|
|
configNodeScope = activeSubflow.id;
|
|
}
|
|
if (editing_config_node == null) {
|
|
editing_config_node = {
|
|
id: RED.nodes.id(),
|
|
_def: node_def,
|
|
type: type,
|
|
z: configNodeScope,
|
|
users: []
|
|
}
|
|
for (var d in node_def.defaults) {
|
|
if (node_def.defaults[d].value) {
|
|
editing_config_node[d] = JSON.parse(JSON.stringify(node_def.defaults[d].value));
|
|
}
|
|
}
|
|
editing_config_node["_"] = node_def._;
|
|
}
|
|
editStack.push(editing_config_node);
|
|
|
|
RED.view.state(RED.state.EDITING);
|
|
var trayOptions = {
|
|
title: getEditStackTitle(), //(adding?RED._("editor.addNewConfig", {type:type}):RED._("editor.editConfig", {type:type})),
|
|
resize: function() {
|
|
if (editing_config_node && editing_config_node._def.oneditresize) {
|
|
var form = $("#node-config-dialog-edit-form");
|
|
try {
|
|
editing_config_node._def.oneditresize.call(editing_config_node,{width:form.width(),height:form.height()});
|
|
} catch(err) {
|
|
console.log("oneditresize",editing_node.id,editing_node.type,err.toString());
|
|
}
|
|
}
|
|
},
|
|
open: function(tray) {
|
|
var trayHeader = tray.find(".editor-tray-header");
|
|
var trayBody = tray.find(".editor-tray-body");
|
|
var trayFooter = tray.find(".editor-tray-footer");
|
|
|
|
if (node_def.hasUsers !== false) {
|
|
trayFooter.prepend('<div id="node-config-dialog-user-count"><i class="fa fa-info-circle"></i> <span></span></div>');
|
|
}
|
|
trayFooter.append('<span id="node-config-dialog-scope-container"><span id="node-config-dialog-scope-warning" data-i18n="[title]editor.errors.scopeChange"><i class="fa fa-warning"></i></span><select id="node-config-dialog-scope"></select></span>');
|
|
|
|
var dialogForm = $('<form id="node-config-dialog-edit-form" class="form-horizontal"></form>').appendTo(trayBody);
|
|
dialogForm.html($("script[data-template-name='"+type+"']").html());
|
|
dialogForm.find('[data-i18n]').each(function() {
|
|
var current = $(this).attr("data-i18n");
|
|
if (current.indexOf(":") === -1) {
|
|
var prefix = "";
|
|
if (current.indexOf("[")===0) {
|
|
var parts = current.split("]");
|
|
prefix = parts[0]+"]";
|
|
current = parts[1];
|
|
}
|
|
$(this).attr("data-i18n",prefix+ns+":"+current);
|
|
}
|
|
});
|
|
$('<input type="text" style="display: none;" />').prependTo(dialogForm);
|
|
prepareEditDialog(editing_config_node,node_def,"node-config-input");
|
|
if (editing_config_node._def.exclusive) {
|
|
$("#node-config-dialog-scope").hide();
|
|
} else {
|
|
$("#node-config-dialog-scope").show();
|
|
}
|
|
$("#node-config-dialog-scope-warning").hide();
|
|
|
|
var nodeUserFlows = {};
|
|
editing_config_node.users.forEach(function(n) {
|
|
nodeUserFlows[n.z] = true;
|
|
});
|
|
var flowCount = Object.keys(nodeUserFlows).length;
|
|
var tabSelect = $("#node-config-dialog-scope").empty();
|
|
tabSelect.off("change");
|
|
tabSelect.append('<option value=""'+(!editing_config_node.z?" selected":"")+' data-i18n="sidebar.config.global"></option>');
|
|
tabSelect.append('<option disabled data-i18n="sidebar.config.flows"></option>');
|
|
RED.nodes.eachWorkspace(function(ws) {
|
|
var workspaceLabel = ws.label;
|
|
if (nodeUserFlows[ws.id]) {
|
|
workspaceLabel = "* "+workspaceLabel;
|
|
}
|
|
tabSelect.append('<option value="'+ws.id+'"'+(ws.id==editing_config_node.z?" selected":"")+'>'+workspaceLabel+'</option>');
|
|
});
|
|
tabSelect.append('<option disabled data-i18n="sidebar.config.subflows"></option>');
|
|
RED.nodes.eachSubflow(function(ws) {
|
|
var workspaceLabel = ws.name;
|
|
if (nodeUserFlows[ws.id]) {
|
|
workspaceLabel = "* "+workspaceLabel;
|
|
}
|
|
tabSelect.append('<option value="'+ws.id+'"'+(ws.id==editing_config_node.z?" selected":"")+'>'+workspaceLabel+'</option>');
|
|
});
|
|
if (flowCount > 0) {
|
|
tabSelect.on('change',function() {
|
|
var newScope = $(this).val();
|
|
if (newScope === '') {
|
|
// global scope - everyone can use it
|
|
$("#node-config-dialog-scope-warning").hide();
|
|
} else if (!nodeUserFlows[newScope] || flowCount > 1) {
|
|
// a user will loose access to it
|
|
$("#node-config-dialog-scope-warning").show();
|
|
} else {
|
|
$("#node-config-dialog-scope-warning").hide();
|
|
}
|
|
});
|
|
}
|
|
tabSelect.i18n();
|
|
|
|
dialogForm.i18n();
|
|
if (node_def.hasUsers !== false) {
|
|
$("#node-config-dialog-user-count").find("span").html(RED._("editor.nodesUse", {count:editing_config_node.users.length})).parent().show();
|
|
}
|
|
},
|
|
close: function() {
|
|
RED.workspaces.refresh();
|
|
editStack.pop();
|
|
},
|
|
show: function() {
|
|
if (editing_config_node) {
|
|
RED.sidebar.info.refresh(editing_config_node);
|
|
}
|
|
}
|
|
}
|
|
trayOptions.buttons = [
|
|
{
|
|
id: "node-config-dialog-cancel",
|
|
text: RED._("common.label.cancel"),
|
|
click: function() {
|
|
var configType = type;
|
|
var configId = editing_config_node.id;
|
|
var configAdding = adding;
|
|
var configTypeDef = RED.nodes.getType(configType);
|
|
|
|
if (configTypeDef.oneditcancel) {
|
|
// TODO: what to pass as this to call
|
|
if (configTypeDef.oneditcancel) {
|
|
var cn = RED.nodes.node(configId);
|
|
if (cn) {
|
|
try {
|
|
configTypeDef.oneditcancel.call(cn,false);
|
|
} catch(err) {
|
|
console.log("oneditcancel",cn.id,cn.type,err.toString());
|
|
}
|
|
} else {
|
|
try {
|
|
configTypeDef.oneditcancel.call({id:configId},true);
|
|
} catch(err) {
|
|
console.log("oneditcancel",configId,configType,err.toString());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
RED.tray.close();
|
|
}
|
|
},
|
|
{
|
|
id: "node-config-dialog-ok",
|
|
text: adding?RED._("editor.configAdd"):RED._("editor.configUpdate"),
|
|
class: "primary",
|
|
click: function() {
|
|
var configProperty = name;
|
|
var configId = editing_config_node.id;
|
|
var configType = type;
|
|
var configAdding = adding;
|
|
var configTypeDef = RED.nodes.getType(configType);
|
|
var d;
|
|
var input;
|
|
var scope = $("#node-config-dialog-scope").val();
|
|
|
|
if (configTypeDef.oneditsave) {
|
|
try {
|
|
configTypeDef.oneditsave.call(editing_config_node);
|
|
} catch(err) {
|
|
console.log("oneditsave",editing_config_node.id,editing_config_node.type,err.toString());
|
|
}
|
|
}
|
|
|
|
for (d in configTypeDef.defaults) {
|
|
if (configTypeDef.defaults.hasOwnProperty(d)) {
|
|
var newValue;
|
|
input = $("#node-config-input-"+d);
|
|
if (input.attr('type') === "checkbox") {
|
|
newValue = input.prop('checked');
|
|
} else if ("format" in configTypeDef.defaults[d] && configTypeDef.defaults[d].format !== "" && input[0].nodeName === "DIV") {
|
|
newValue = input.text();
|
|
} else {
|
|
newValue = input.val();
|
|
}
|
|
if (newValue != null && newValue !== editing_config_node[d]) {
|
|
if (editing_config_node._def.defaults[d].type) {
|
|
if (newValue == "_ADD_") {
|
|
newValue = "";
|
|
}
|
|
// Change to a related config node
|
|
var configNode = RED.nodes.node(editing_config_node[d]);
|
|
if (configNode) {
|
|
var users = configNode.users;
|
|
users.splice(users.indexOf(editing_config_node),1);
|
|
}
|
|
configNode = RED.nodes.node(newValue);
|
|
if (configNode) {
|
|
configNode.users.push(editing_config_node);
|
|
}
|
|
}
|
|
editing_config_node[d] = newValue;
|
|
}
|
|
}
|
|
}
|
|
editing_config_node.label = configTypeDef.label;
|
|
editing_config_node.z = scope;
|
|
|
|
if (scope) {
|
|
// Search for nodes that use this one that are no longer
|
|
// in scope, so must be removed
|
|
editing_config_node.users = editing_config_node.users.filter(function(n) {
|
|
var keep = true;
|
|
for (var d in n._def.defaults) {
|
|
if (n._def.defaults.hasOwnProperty(d)) {
|
|
if (n._def.defaults[d].type === editing_config_node.type &&
|
|
n[d] === editing_config_node.id &&
|
|
n.z !== scope) {
|
|
keep = false;
|
|
// Remove the reference to this node
|
|
// and revalidate
|
|
n[d] = null;
|
|
n.dirty = true;
|
|
n.changed = true;
|
|
validateNode(n);
|
|
}
|
|
}
|
|
}
|
|
return keep;
|
|
});
|
|
}
|
|
|
|
if (configAdding) {
|
|
RED.nodes.add(editing_config_node);
|
|
}
|
|
|
|
if (configTypeDef.credentials) {
|
|
updateNodeCredentials(editing_config_node,configTypeDef.credentials,"node-config-input");
|
|
}
|
|
validateNode(editing_config_node);
|
|
var validatedNodes = {};
|
|
validatedNodes[editing_config_node.id] = true;
|
|
|
|
var userStack = editing_config_node.users.slice();
|
|
while(userStack.length > 0) {
|
|
var user = userStack.pop();
|
|
if (!validatedNodes[user.id]) {
|
|
validatedNodes[user.id] = true;
|
|
if (user.users) {
|
|
userStack = userStack.concat(user.users);
|
|
}
|
|
validateNode(user);
|
|
}
|
|
}
|
|
RED.nodes.dirty(true);
|
|
RED.view.redraw(true);
|
|
if (!configAdding) {
|
|
RED.events.emit("editor:save",editing_config_node);
|
|
}
|
|
RED.tray.close(function() {
|
|
updateConfigNodeSelect(configProperty,configType,editing_config_node.id,prefix);
|
|
});
|
|
}
|
|
}
|
|
];
|
|
|
|
if (!adding) {
|
|
trayOptions.buttons.unshift({
|
|
class: 'leftButton',
|
|
text: RED._("editor.configDelete"), //'<i class="fa fa-trash"></i>',
|
|
click: function() {
|
|
var configProperty = name;
|
|
var configId = editing_config_node.id;
|
|
var configType = type;
|
|
var configTypeDef = RED.nodes.getType(configType);
|
|
|
|
try {
|
|
|
|
if (configTypeDef.ondelete) {
|
|
// Deprecated: never documented but used by some early nodes
|
|
console.log("Deprecated API warning: config node type ",configType," has an ondelete function - should be oneditdelete");
|
|
configTypeDef.ondelete.call(editing_config_node);
|
|
}
|
|
if (configTypeDef.oneditdelete) {
|
|
configTypeDef.oneditdelete.call(editing_config_node);
|
|
}
|
|
} catch(err) {
|
|
console.log("oneditdelete",editing_config_node.id,editing_config_node.type,err.toString());
|
|
}
|
|
|
|
var historyEvent = {
|
|
t:'delete',
|
|
nodes:[editing_config_node],
|
|
changes: {},
|
|
dirty: RED.nodes.dirty()
|
|
}
|
|
for (var i=0;i<editing_config_node.users.length;i++) {
|
|
var user = editing_config_node.users[i];
|
|
historyEvent.changes[user.id] = {
|
|
changed: user.changed,
|
|
valid: user.valid
|
|
};
|
|
for (var d in user._def.defaults) {
|
|
if (user._def.defaults.hasOwnProperty(d) && user[d] == configId) {
|
|
historyEvent.changes[user.id][d] = configId
|
|
user[d] = "";
|
|
user.changed = true;
|
|
user.dirty = true;
|
|
}
|
|
}
|
|
validateNode(user);
|
|
}
|
|
RED.nodes.remove(configId);
|
|
RED.nodes.dirty(true);
|
|
RED.view.redraw(true);
|
|
RED.history.push(historyEvent);
|
|
RED.tray.close(function() {
|
|
updateConfigNodeSelect(configProperty,configType,"",prefix);
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
RED.tray.show(trayOptions);
|
|
}
|
|
|
|
function defaultConfigNodeSort(A,B) {
|
|
if (A.__label__ < B.__label__) {
|
|
return -1;
|
|
} else if (A.__label__ > B.__label__) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
function updateConfigNodeSelect(name,type,value,prefix) {
|
|
// if prefix is null, there is no config select to update
|
|
if (prefix) {
|
|
var button = $("#"+prefix+"-edit-"+name);
|
|
if (button.length) {
|
|
if (value) {
|
|
button.text(RED._("editor.configEdit"));
|
|
} else {
|
|
button.text(RED._("editor.configAdd"));
|
|
}
|
|
$("#"+prefix+"-"+name).val(value);
|
|
} else {
|
|
|
|
var select = $("#"+prefix+"-"+name);
|
|
var node_def = RED.nodes.getType(type);
|
|
select.children().remove();
|
|
|
|
var activeWorkspace = RED.nodes.workspace(RED.workspaces.active());
|
|
if (!activeWorkspace) {
|
|
activeWorkspace = RED.nodes.subflow(RED.workspaces.active());
|
|
}
|
|
|
|
var configNodes = [];
|
|
|
|
RED.nodes.eachConfig(function(config) {
|
|
if (config.type == type && (!config.z || config.z === activeWorkspace.id)) {
|
|
var label = "";
|
|
if (typeof node_def.label == "function") {
|
|
try {
|
|
label = node_def.label.call(config);
|
|
} catch(err) {
|
|
console.log("Definition error: "+node_def.type+".label",err);
|
|
label = node_def.type;
|
|
}
|
|
} else {
|
|
label = node_def.label;
|
|
}
|
|
config.__label__ = label;
|
|
configNodes.push(config);
|
|
}
|
|
});
|
|
var configSortFn = defaultConfigNodeSort;
|
|
if (typeof node_def.sort == "function") {
|
|
configSortFn = node_def.sort;
|
|
}
|
|
try {
|
|
configNodes.sort(configSortFn);
|
|
} catch(err) {
|
|
console.log("Definition error: "+node_def.type+".sort",err);
|
|
}
|
|
|
|
configNodes.forEach(function(cn) {
|
|
select.append('<option value="'+cn.id+'"'+(value==cn.id?" selected":"")+'>'+RED.bidi.applyBidiSupport(cn.__label__,RED.bidi.flags.BTD & RED.bidi.flags.NS)+'</option>');
|
|
delete cn.__label__;
|
|
});
|
|
|
|
select.append('<option value="_ADD_"'+(value===""?" selected":"")+'>'+RED._("editor.addNewType", {type:type})+'</option>');
|
|
window.setTimeout(function() { select.change();},50);
|
|
}
|
|
}
|
|
}
|
|
|
|
function showEditSubflowDialog(subflow) {
|
|
var editing_node = subflow;
|
|
editStack.push(subflow);
|
|
RED.view.state(RED.state.EDITING);
|
|
var subflowEditor;
|
|
|
|
var trayOptions = {
|
|
title: getEditStackTitle(),
|
|
buttons: [
|
|
{
|
|
id: "node-dialog-cancel",
|
|
text: RED._("common.label.cancel"),
|
|
click: function() {
|
|
RED.tray.close();
|
|
}
|
|
},
|
|
{
|
|
id: "node-dialog-ok",
|
|
class: "primary",
|
|
text: RED._("common.label.done"),
|
|
click: function() {
|
|
var i;
|
|
var changes = {};
|
|
var changed = false;
|
|
var wasDirty = RED.nodes.dirty();
|
|
|
|
var newName = $("#subflow-input-name").val();
|
|
|
|
if (newName != editing_node.name) {
|
|
changes['name'] = editing_node.name;
|
|
editing_node.name = newName;
|
|
changed = true;
|
|
}
|
|
|
|
var newDescription = subflowEditor.getValue();
|
|
|
|
if (newDescription != editing_node.info) {
|
|
changes['info'] = editing_node.info;
|
|
editing_node.info = newDescription;
|
|
changed = true;
|
|
}
|
|
|
|
RED.palette.refresh();
|
|
|
|
if (changed) {
|
|
var subflowInstances = [];
|
|
RED.nodes.eachNode(function(n) {
|
|
if (n.type == "subflow:"+editing_node.id) {
|
|
subflowInstances.push({
|
|
id:n.id,
|
|
changed:n.changed
|
|
})
|
|
n.changed = true;
|
|
n.dirty = true;
|
|
updateNodeProperties(n);
|
|
}
|
|
});
|
|
var wasChanged = editing_node.changed;
|
|
editing_node.changed = true;
|
|
RED.nodes.dirty(true);
|
|
var historyEvent = {
|
|
t:'edit',
|
|
node:editing_node,
|
|
changes:changes,
|
|
dirty:wasDirty,
|
|
changed:wasChanged,
|
|
subflow: {
|
|
instances:subflowInstances
|
|
}
|
|
};
|
|
|
|
RED.history.push(historyEvent);
|
|
}
|
|
editing_node.dirty = true;
|
|
RED.tray.close();
|
|
}
|
|
}
|
|
],
|
|
resize: function() {
|
|
var rows = $("#dialog-form>div:not(.node-text-editor-row)");
|
|
var editorRow = $("#dialog-form>div.node-text-editor-row");
|
|
var height = $("#dialog-form").height();
|
|
for (var i=0;i<rows.size();i++) {
|
|
height -= $(rows[i]).outerHeight(true);
|
|
}
|
|
height -= (parseInt($("#dialog-form").css("marginTop"))+parseInt($("#dialog-form").css("marginBottom")));
|
|
$(".node-text-editor").css("height",height+"px");
|
|
subflowEditor.resize();
|
|
},
|
|
open: function(tray) {
|
|
if (editing_node) {
|
|
RED.sidebar.info.refresh(editing_node);
|
|
}
|
|
var trayBody = tray.find('.editor-tray-body');
|
|
var dialogForm = $('<form id="dialog-form" class="form-horizontal"></form>').appendTo(trayBody);
|
|
dialogForm.html($("script[data-template-name='subflow-template']").html());
|
|
var ns = "node-red";
|
|
dialogForm.find('[data-i18n]').each(function() {
|
|
var current = $(this).attr("data-i18n");
|
|
var keys = current.split(";");
|
|
for (var i=0;i<keys.length;i++) {
|
|
var key = keys[i];
|
|
if (key.indexOf(":") === -1) {
|
|
var prefix = "";
|
|
if (key.indexOf("[")===0) {
|
|
var parts = key.split("]");
|
|
prefix = parts[0]+"]";
|
|
key = parts[1];
|
|
}
|
|
keys[i] = prefix+ns+":"+key;
|
|
}
|
|
}
|
|
$(this).attr("data-i18n",keys.join(";"));
|
|
});
|
|
$('<input type="text" style="display: none;" />').prependTo(dialogForm);
|
|
|
|
dialogForm.submit(function(e) { e.preventDefault();});
|
|
subflowEditor = RED.editor.createEditor({
|
|
id: 'subflow-input-info-editor',
|
|
mode: 'ace/mode/markdown',
|
|
value: ""
|
|
});
|
|
|
|
$("#subflow-input-name").val(subflow.name);
|
|
RED.bidi.prepareInput($("#subflow-input-name"));
|
|
subflowEditor.getSession().setValue(subflow.info||"",-1);
|
|
var userCount = 0;
|
|
var subflowType = "subflow:"+editing_node.id;
|
|
|
|
RED.nodes.eachNode(function(n) {
|
|
if (n.type === subflowType) {
|
|
userCount++;
|
|
}
|
|
});
|
|
$("#subflow-dialog-user-count").html(RED._("subflow.subflowInstances", {count:userCount})).show();
|
|
dialogForm.i18n();
|
|
},
|
|
close: function() {
|
|
if (RED.view.state() != RED.state.IMPORT_DRAGGING) {
|
|
RED.view.state(RED.state.DEFAULT);
|
|
}
|
|
RED.sidebar.info.refresh(editing_node);
|
|
RED.workspaces.refresh();
|
|
editStack.pop();
|
|
editing_node = null;
|
|
},
|
|
show: function() {
|
|
}
|
|
}
|
|
RED.tray.show(trayOptions);
|
|
}
|
|
|
|
return {
|
|
init: function() {
|
|
RED.tray.init();
|
|
$(window).on('keydown', function(evt) {
|
|
if (evt.keyCode === $.ui.keyCode.ESCAPE && (evt.metaKey || evt.ctrlKey)) {
|
|
$("#node-dialog-cancel").click();
|
|
$("#node-config-dialog-cancel").click();
|
|
} else if (evt.keyCode === $.ui.keyCode.ENTER && (evt.metaKey || evt.ctrlKey)) {
|
|
$("#node-dialog-ok").click();
|
|
$("#node-config-dialog-ok").click();
|
|
}
|
|
});
|
|
},
|
|
edit: showEditDialog,
|
|
editConfig: showEditConfigNodeDialog,
|
|
editSubflow: showEditSubflowDialog,
|
|
validateNode: validateNode,
|
|
updateNodeProperties: updateNodeProperties, // TODO: only exposed for edit-undo
|
|
|
|
createEditor: function(options) {
|
|
var editor = ace.edit(options.id);
|
|
editor.setTheme("ace/theme/tomorrow");
|
|
var session = editor.getSession();
|
|
if (options.mode) {
|
|
session.setMode(options.mode);
|
|
}
|
|
if (options.foldStyle) {
|
|
session.setFoldStyle(options.foldStyle);
|
|
} else {
|
|
session.setFoldStyle('markbeginend');
|
|
}
|
|
if (options.options) {
|
|
editor.setOptions(options.options);
|
|
} else {
|
|
editor.setOptions({
|
|
enableBasicAutocompletion:true,
|
|
enableSnippets:true
|
|
});
|
|
}
|
|
editor.$blockScrolling = Infinity;
|
|
if (options.value) {
|
|
session.setValue(options.value,-1);
|
|
}
|
|
if (options.globals) {
|
|
setTimeout(function() {
|
|
if (!!session.$worker) {
|
|
session.$worker.send("setOptions", [{globals: options.globals, esversion:6}]);
|
|
}
|
|
},100);
|
|
}
|
|
|
|
return editor;
|
|
}
|
|
}
|
|
})();
|