/** * Copyright JS Foundation and other contributors, http://js.foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. **/ RED.nodes = (function() { var node_defs = {}; var nodes = []; var nodeTabMap = {}; var configNodes = {}; var links = []; var defaultWorkspace; var workspaces = {}; var workspacesOrder =[]; var subflows = {}; var loadedFlowVersion = null; var groups = {}; var groupsByZ = {}; var initialLoad; var dirty = false; function setDirty(d) { dirty = d; RED.events.emit("nodes:change",{dirty:dirty}); } var registry = (function() { var moduleList = {}; var nodeList = []; var nodeSets = {}; var typeToId = {}; var nodeDefinitions = {}; var iconSets = {}; nodeDefinitions['tab'] = { defaults: { label: {value:""}, disabled: {value: false}, info: {value: ""} } }; var exports = { setModulePendingUpdated: function(module,version) { moduleList[module].pending_version = version; RED.events.emit("registry:module-updated",{module:module,version:version}); }, getModule: function(module) { return moduleList[module]; }, getNodeSetForType: function(nodeType) { return exports.getNodeSet(typeToId[nodeType]); }, getModuleList: function() { return moduleList; }, getNodeList: function() { return nodeList; }, getNodeTypes: function() { return Object.keys(nodeDefinitions); }, setNodeList: function(list) { nodeList = []; for(var i=0;i n.outputs)) { n.outputs = n.wires.length; } if (n.outputs) { for (var i=0;idiv:not(.node-input-env-container-row)"); var height = size.height; // for (var i=0; idiv.node-input-env-container-row"); // height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom"))); $("ol.red-ui-editor-subflow-env-list").editableList('height',height); }, set:{ module: "node-red" } }); sf._def = RED.nodes.getType("subflow:"+sf.id); } function getSubflow(id) { return subflows[id]; } function removeSubflow(sf) { delete subflows[sf.id]; delete nodeTabMap[sf.id]; registry.removeNodeType("subflow:"+sf.id); } function subflowContains(sfid,nodeid) { for (var i=0;i 0) { node.credentials = credentialSet; } } } if (n.type === "group") { node.x = n.x; node.y = n.y; node.w = n.w; node.h = n.h; node.nodes = node.nodes.map(function(n) { return n.id }); } if (n._def.category != "config") { node.x = n.x; node.y = n.y; node.wires = []; for(var i=0;i 0 && n.inputLabels && !/^\s*$/.test(n.inputLabels.join(""))) { node.inputLabels = n.inputLabels.slice(); } if (n.outputs > 0 && n.outputLabels && !/^\s*$/.test(n.outputLabels.join(""))) { node.outputLabels = n.outputLabels.slice(); } if ((!n._def.defaults || !n._def.defaults.hasOwnProperty("icon")) && n.icon) { var defIcon = RED.utils.getDefaultNodeIcon(n._def, n); if (n.icon !== defIcon.module+"/"+defIcon.file) { node.icon = n.icon; } } if ((!n._def.defaults || !n._def.defaults.hasOwnProperty("l")) && n.hasOwnProperty('l')) { var isLink = /^link (in|out)$/.test(node.type); if (isLink == n.l) { node.l = n.l; } } } if (n.info) { node.info = n.info; } return node; } function convertSubflow(n, exportCreds) { var node = {}; node.id = n.id; node.type = n.type; node.name = n.name; node.info = n.info; node.category = n.category; node.in = []; node.out = []; node.env = n.env; if (exportCreds) { var credentialSet = {}; // A subflow node can have arbitrary creds for (var sfCred in n.credentials) { if (n.credentials.hasOwnProperty(sfCred)) { if (!n.credentials._ || n.credentials["has_"+sfCred] != n.credentials._["has_"+sfCred] || (n.credentials["has_"+sfCred] && n.credentials[sfCred])) { credentialSet[sfCred] = n.credentials[sfCred]; } } } if (Object.keys(credentialSet).length > 0) { node.credentials = credentialSet; } } node.color = n.color; n.in.forEach(function(p) { var nIn = {x:p.x,y:p.y,wires:[]}; var wires = links.filter(function(d) { return d.source === p }); for (var i=0;i 0 && n.inputLabels && !/^\s*$/.test(n.inputLabels.join(""))) { node.inputLabels = n.inputLabels.slice(); } if (node.out.length > 0 && n.outputLabels && !/^\s*$/.test(n.outputLabels.join(""))) { node.outputLabels = n.outputLabels.slice(); } if (n.icon) { if (n.icon !== "node-red/subflow.svg") { node.icon = n.icon; } } if (n.status) { node.status = {x: n.status.x, y: n.status.y, wires:[]}; links.forEach(function(d) { if (d.target === n.status) { if (d.source.type != "subflow") { node.status.wires.push({id:d.source.id, port:d.sourcePort}) } else { node.status.wires.push({id:n.id, port:0}) } } }); } return node; } /** * Converts the current node selection to an exportable JSON Object **/ function createExportableNodeSet(set, exportedIds, exportedSubflows, exportedConfigNodes) { var nns = []; exportedIds = exportedIds || {}; set = set.filter(function(n) { if (exportedIds[n.id]) { return false; } exportedIds[n.id] = true; return true; }) exportedConfigNodes = exportedConfigNodes || {}; exportedSubflows = exportedSubflows || {}; for (var n=0;n 0) { var typeList = "
  • "+unknownTypes.join("
  • ")+"
"; RED.notify("

"+RED._("clipboard.importUnrecognised",{count:unknownTypes.length})+"

"+typeList,"error",false,10000); } var activeWorkspace = RED.workspaces.active(); //TODO: check the z of the subflow instance and check _that_ if it exists var activeSubflow = getSubflow(activeWorkspace); for (i=0;i node.outputs) { if (!node._def.defaults.hasOwnProperty("outputs") || !isNaN(parseInt(n.outputs))) { // If 'wires' is longer than outputs, clip wires console.log("Warning: node.wires longer than node.outputs - trimming wires:",node.id," wires:",node.wires.length," outputs:",node.outputs); node.wires = node.wires.slice(0,node.outputs); } else { // The node declares outputs in its defaults, but has not got a valid value // Defer to the length of the wires array node.outputs = node.wires.length; } } for (d in node._def.defaults) { if (node._def.defaults.hasOwnProperty(d) && d !== 'inputs' && d !== 'outputs') { node[d] = n[d]; node._config[d] = JSON.stringify(n[d]); } } node._config.x = node.x; node._config.y = node.y; if (node._def.hasOwnProperty('credentials') && n.hasOwnProperty('credentials')) { node.credentials = {}; for (d in node._def.credentials) { if (node._def.credentials.hasOwnProperty(d) && n.credentials.hasOwnProperty(d)) { node.credentials[d] = n.credentials[d]; } } } } } if (node.type !== "group") { addNode(node); RED.editor.validateNode(node); } else { addGroup(node); } node_map[n.id] = node; // If an 'unknown' config node, it will not have been caught by the // proper config node handling, so needs adding to new_nodes here if (node.type === "unknown" || node._def.category !== "config") { new_nodes.push(node); } else if (node.type === "group") { new_groups.push(node); } } } } // TODO: make this a part of the node definition so it doesn't have to // be hardcoded here var nodeTypeArrayReferences = { "catch":"scope", "status":"scope", "complete": "scope", "link in":"links", "link out":"links" } // Remap all wires and config node references for (i=0;i",node_map[wires[w2]].id); } } } } delete n.wires; } if (n.g && node_map[n.g]) { n.g = node_map[n.g].id; } else { delete n.g } for (var d3 in n._def.defaults) { if (n._def.defaults.hasOwnProperty(d3)) { if (n._def.defaults[d3].type && node_map[n[d3]]) { n[d3] = node_map[n[d3]].id; configNode = RED.nodes.node(n[d3]); if (configNode && configNode.users.indexOf(n) === -1) { configNode.users.push(n); } } else if (nodeTypeArrayReferences.hasOwnProperty(n.type) && nodeTypeArrayReferences[n.type] === d3 && n[d3] !== undefined && n[d3] !== null) { for (var j = 0;j 0) { var reimportList = []; replaceNodeIds.forEach(function(id) { var n = replaceNodes[id]; if (configNodes.hasOwnProperty(n.id)) { delete configNodes[n.id]; } else { nodes.splice(nodes.indexOf(n),1); if (nodeTabMap[n.z]) { delete nodeTabMap[n.z][n.id]; } } reimportList.push(convertNode(n)); }); // Remove any links between nodes that are going to be reimported. // This prevents a duplicate link from being added. var removeLinks = []; RED.nodes.eachLink(function(l) { if (replaceNodes.hasOwnProperty(l.source.id) && replaceNodes.hasOwnProperty(l.target.id)) { removeLinks.push(l); } }); removeLinks.forEach(removeLink); // Force the redraw to be synchronous so the view updates // *now* and removes the unknown node RED.view.redraw(true, true); var result = importNodes(reimportList,false); var newNodeMap = {}; result[0].forEach(function(n) { newNodeMap[n.id] = n; }); RED.nodes.eachLink(function(l) { if (newNodeMap.hasOwnProperty(l.source.id)) { l.source = newNodeMap[l.source.id]; } if (newNodeMap.hasOwnProperty(l.target.id)) { l.target = newNodeMap[l.target.id]; } }); RED.view.redraw(true); } }); }, registry:registry, setNodeList: registry.setNodeList, getNodeSet: registry.getNodeSet, addNodeSet: registry.addNodeSet, removeNodeSet: registry.removeNodeSet, enableNodeSet: registry.enableNodeSet, disableNodeSet: registry.disableNodeSet, setIconSets: registry.setIconSets, getIconSets: registry.getIconSets, registerType: registry.registerNodeType, getType: registry.getNodeType, convertNode: convertNode, add: addNode, remove: removeNode, clear: clear, moveNodeToTab: moveNodeToTab, addLink: addLink, removeLink: removeLink, addWorkspace: addWorkspace, removeWorkspace: removeWorkspace, getWorkspaceOrder: function() { return workspacesOrder }, setWorkspaceOrder: function(order) { workspacesOrder = order; }, workspace: getWorkspace, addSubflow: addSubflow, removeSubflow: removeSubflow, subflow: getSubflow, subflowContains: subflowContains, addGroup: addGroup, removeGroup: removeGroup, group: function(id) { return groups[id] }, groups: function(z) { return groupsByZ[z]||[] }, eachNode: function(cb) { for (var n=0;n