diff --git a/packages/node_modules/@node-red/editor-client/src/js/nodes.js b/packages/node_modules/@node-red/editor-client/src/js/nodes.js index e1086884e..e6053b887 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/nodes.js +++ b/packages/node_modules/@node-red/editor-client/src/js/nodes.js @@ -1764,6 +1764,16 @@ RED.nodes = (function() { } + /** + * Analyzes the array of nodes passed as an argument to find unknown node types. + * + * Options: + * - `emitNotification` (boolean) - Emit an notification with unknown types. + * + * @param {Array} nodes An array of nodes to analyse + * @param {object} options An options object + * @returns {Array} An array with unknown types + */ function identifyUnknowType(nodes, options = {}) { const unknownTypes = []; for (let node of nodes) { @@ -1791,6 +1801,191 @@ RED.nodes = (function() { return unknownTypes; } + function copyConfigNode(configNode, def, options = {}) { + const newNode = { + id: configNode.id, + z: configNode.z, + type: configNode.type, + icon: configNode.icon, + info: configNode.info, + label: def.label, + users: [], + _config: {}, + _def: def + }; + + if (!configNode.z) { + delete newNode.z; + } + + if (options.markChanged) { + newNode.changed = true; + } + + if (configNode.hasOwnProperty("d")) { + newNode.d = configNode.d; + } + + // Copy node user properties + for (const d in def.defaults) { + if (def.defaults.hasOwnProperty(d)) { + newNode._config[d] = JSON.stringify(configNode[d]); + newNode[d] = configNode[d]; + } + } + + // Copy credentials - ONLY if the node contains it to avoid erase it + if (def.hasOwnProperty("credentials") && configNode.hasOwnProperty("credentials")) { + newNode.credentials = {}; + for (const c in def.credentials) { + if (def.credentials.hasOwnProperty(c) && configNode.credentials.hasOwnProperty(c)) { + newNode.credentials[c] = configNode.credentials[c]; + } + } + } + + return newNode; + } + + function copyNode(node, def, options = {}) { + const newNode = { + id: node.id, + x: parseFloat(node.x || 0), + y: parseFloat(node.y || 0), + z: node.z, + type: node.type, + info: node.info, + changed: false, + _config: {}, + _def: def + }; + + if (node.type !== "group" && node.type !== "junction") { + newNode.wires = node.wires || []; + newNode.inputLabels = node.inputLabels; + newNode.outputLabels = node.outputLabels; + newNode.icon = node.icon; + } + if (node.type === "junction") { + newNode.wires = node.wires || []; + } + if (node.hasOwnProperty("l")) { + newNode.l = node.l; + } + if (node.hasOwnProperty("d")) { + newNode.d = node.d; + } + if (node.hasOwnProperty("g")) { // Group + newNode.g = node.g; + } + if (options.markChanged) { + newNode.changed = true + } + + if (node.type === "group") { + newNode._def = RED.group.def; + for (const d in newNode._def.defaults) { + if (newNode._def.defaults.hasOwnProperty(d) && d !== "inputs" && d !== "outputs") { + newNode[d] = node[d]; + newNode._config[d] = JSON.stringify(node[d]); + } + } + newNode._config.x = node.x; + newNode._config.y = node.y; + if (node.hasOwnProperty("w")) { // Weight + newNode.w = node.w + } + if (node.hasOwnProperty("h")) { // Height + newNode.h = node.h + } + } else if (node.type === "junction") { + newNode._def = { defaults: {} }; + newNode._config.x = node.x + newNode._config.y = node.y + newNode.inputs = 1 + newNode.outputs = 1 + newNode.w = 0; + newNode.h = 0; + } else if (node.type.substring(0, 7) === "subflow") { + const parentId = node.type.split(":")[1]; + /* + const subflow = subflow_denylist[parentId]||subflow_map[parentId]||getSubflow(parentId); + // TODO: Pourquoi ne pas l'avoir mis dans les tabs ? + if (!subflow){ + node._def = { + color:"#fee", + defaults: {}, + label: "unknown: "+n.type, + labelStyle: "red-ui-flow-node-label-italic", + outputs: n.outputs|| (n.wires && n.wires.length) || 0, + set: registry.getNodeSet("node-red/unknown") + } + } else { + // TODO: Normal que la copie utilise le Subflow parent ? + if (subflow_denylist[parentId] || createNewIds || options.importMap[n.id] === "copy") { + parentId = subflow.id; + node.type = "subflow:"+parentId; + node._def = registry.getNodeType(node.type); + delete node.i; + } + node.name = n.name; + node.outputs = subflow.out.length; + node.inputs = subflow.in.length; + node.env = n.env; + }*/ + } else { + newNode._config.x = node.x; + newNode._config.y = node.y; + + if (node.hasOwnProperty("inputs")) { + newNode._config.inputs = JSON.stringify(node.inputs); + newNode.inputs = node.inputs; + } else { + newNode.inputs = def.inputs; + } + + if (node.hasOwnProperty("outputs")) { + newNode._config.outputs = JSON.stringify(node.outputs); + newNode.outputs = node.outputs; + } else { + newNode.outputs = def.outputs; + } + + if (newNode.wires.length > newNode.outputs) { + if (!def.defaults.hasOwnProperty("outputs") || !isNaN(parseInt(newNode.outputs))) { + // The node declares outputs in its defaults, but has not got a valid value + // Defer to the length of the wires array + newNode.outputs = newNode.wires.length; + } else { + // 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); + // TODO: Pas dans l'autre sens ? + newNode.wires = newNode.wires.slice(0, newNode.outputs); + } + } + + // Copy node user properties + for (const d in def.defaults) { + if (newNode._def.defaults.hasOwnProperty(d) && d !== "inputs" && d !== "outputs") { + newNode._config[d] = JSON.stringify(node[d]); + newNode[d] = node[d]; + } + } + + // Copy credentials - ONLY if the node contains it to avoid erase it + if (def.hasOwnProperty("credentials") && node.hasOwnProperty("credentials")) { + newNode.credentials = {}; + for (const c in def.credentials) { + if (newNode._def.credentials.hasOwnProperty(c) && node.credentials.hasOwnProperty(c)) { + newNode.credentials[c] = node.credentials[c]; + } + } + } + } + + return newNode; + } + /** * Options: * - generateIds - whether to replace all node ids @@ -1803,7 +1998,7 @@ RED.nodes = (function() { * - id:copy - import with new id * - id:replace - import over the top of existing */ - function importNodes(newNodes, options) { + function importNodes(originalNodes, options) { const defaultOptions = { generateIds: false, addFlow: false, markChanged: false, reimport: false, importMap: {} }; options = Object.assign({}, defaultOptions, options); @@ -1811,11 +2006,11 @@ RED.nodes = (function() { const createMissingWorkspace = options.addFlow; const reimport = (!createNewIds && !!options.reimport); - // Checks and converts the flow into an Array if necessary - if (typeof newNodes === "string") { - if (newNodes === "") { return; } + // Checks and converts nodes into an Array if necessary + if (typeof originalNodes === "string") { + if (originalNodes === "") { return; } try { - newNodes = JSON.parse(newNodes); + originalNodes = JSON.parse(originalNodes); } catch(err) { const error = new Error(RED._("clipboard.invalidFlow", { message: err.message })); error.code = "NODE_RED"; @@ -1823,15 +2018,15 @@ RED.nodes = (function() { } } - if (!Array.isArray(newNodes)) { - newNodes = [newNodes]; + if (!Array.isArray(originalNodes)) { + originalNodes = [originalNodes]; } const seenIds = new Set(); const existingNodes = []; const nodesToReplace = []; - // Checks if the imported flow contains duplicates or existing nodes. - newNodes = newNodes.filter(function(node) { + // Checks if the imported nodes contains duplicates or existing nodes. + originalNodes = originalNodes.filter(function(node) { const id = node.id; // This is a temporary fix to help resolve corrupted flows caused by 0.20.0 where multiple @@ -1840,10 +2035,11 @@ RED.nodes = (function() { if (seenIds.has(id)) { return false; } seenIds.add(id); - // Checks if the node already exists - the user will choose between copying the node or replacing it + // Checks if the node already exists - the user will choose between copying the node, replacing it or not import it if (!createNewIds) { if (!options.importMap[id]) { // No conflict resolution for this node + // TODO: Sure? const existingNode = allNodes.getNode(id) || configNodes[id] || workspaces[id] || subflows[id] || groups[id] || junctions[id]; if (existingNode) { existingNodes.push({ existing: existingNode, imported: node }); @@ -1857,9 +2053,10 @@ RED.nodes = (function() { return true; }); - // If some nodes already exists, ask the user for each node to choose between copying it or replacing it + // If some nodes already exists, ask the user for each node to choose + // between copying it, replacing it or not importing it. if (existingNodes.length) { - const errorMessage = RED._("clipboard.importDuplicate",{ count: existingNodes.length }); + const errorMessage = RED._("clipboard.importDuplicate", { count: existingNodes.length }); const maxItemCount = 5; // Max 5 items in the list const nodeList = $("