From aeff72ac7be0a0f372ab10beb4aaa193ade2b27e Mon Sep 17 00:00:00 2001 From: GogoVega <92022724+GogoVega@users.noreply.github.com> Date: Tue, 24 Dec 2024 13:29:39 +0100 Subject: [PATCH 1/2] Move some logic to `identifyUnknownTypes`, `copyConfigNode` and `copyNode` --- .../@node-red/editor-client/src/js/nodes.js | 574 +++++++++++------- 1 file changed, 340 insertions(+), 234 deletions(-) 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 cab3547c2..38c142005 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 @@ -1715,6 +1715,30 @@ RED.nodes = (function() { } + /** + * Analyzes the array of nodes passed as an argument to find unknown node types. + * + * @param {Array} nodes An array of nodes to analyse + * @returns {Array} An array with unknown types + */ + function identifyUnknowTypes(nodes) { + const unknownTypes = []; + + for (const node of nodes) { + // TODO: remove workspace + const knowTypes = ["workspace", "tab", "subflow", "group", "junction"]; + + if (!knowTypes.includes(node.type) && + node.type.substring(0, 8) != "subflow:" && + !registry.getNodeType(node.type) && + !unknownTypes.includes(node.type)) { + unknownTypes.push(node.type); + } + } + + return unknownTypes; + } + /** * Replace the provided nodes. * This must contain complete Subflow defs or complete Flow Tabs. @@ -1824,6 +1848,205 @@ RED.nodes = (function() { } + /** + * Makes a copy of the Config Node received as parameter. + * + * @remarks The id is not modified + * + * @param {object} configNode The Config Node to copy. + * @param {object} def The Config Node definition. + * @param {object} [options] + * @param {boolean} [options.markChanged = false] Whether the Config Node + * should have changed. Default `false`. + * @returns {object} The new Config Node copied. + */ + function copyConfigNode(configNode, def, options = {}) { + const newNode = { + _config: {}, + _configNodeReferences: new Set(), + _def: def, + id: configNode.id, + type: configNode.type, + changed: false, + icon: configNode.icon, + info: configNode.info, + label: def.label, + users: [], + }; + + if (configNode.z) { + newNode.z = configNode.z; + } + + if (options.markChanged) { + newNode.changed = true; + } + + // Whether the config node is disabled + if (configNode.hasOwnProperty("d")) { + newNode.d = configNode.d; + } + + // Copy editable properties + for (const d in def.defaults) { + if (def.defaults.hasOwnProperty(d)) { + newNode._config[d] = JSON.stringify(configNode[d]); + newNode[d] = configNode[d]; + + if (def.defaults[d].type) { + // Add the config node used by this config node to the list + newNode._configNodeReferences.add(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; + } + + /** + * Makes a copy of the Node received as parameter. + * + * @remarks The id is not modified + * + * @param {object} node The Node to copy. + * @param {object} def The Node definition. + * @param {object} [options] + * @param {boolean} [options.markChanged = false] Whether the Node + * should have changed. Default `false`. + * @returns {object} The new Config Node copied. + */ + function copyNode(node, def, options = {}) { + const newNode = { + _config: {}, + _def: def, + id: node.id, + type: node.type, + changed: false, + dirty: true, + info: node.info, + // TODO: parseFloat(node.x) || 0 + x: parseFloat(node.x || 0), + y: parseFloat(node.y || 0), + z: node.z, + }; + + // Whether the node shown its label + if (node.hasOwnProperty("l")) { + newNode.l = node.l; + } + + // Whether the node is disabled + if (node.hasOwnProperty("d")) { + newNode.d = node.d; + } + + // Whether the node is into a group + if (node.hasOwnProperty("g")) { + newNode.g = node.g; + } + + if (options.markChanged) { + newNode.changed = true; + } + + 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 === "group") { + 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._config.x = node.x; + newNode._config.y = node.y; + newNode.wires = node.wires || []; + newNode.inputs = 1; + newNode.outputs = 1; + newNode.w = 0; + newNode.h = 0; + } else if (node.type.substring(0, 7) === "subflow") { + newNode.name = node.name; + newNode.inputs = node.inputs ?? 0; + newNode.outputs = node.outputs ?? 0; + newNode.env = node.env; + } else { + newNode._config.x = node.x; + newNode._config.y = node.y; + + if (node.hasOwnProperty("inputs") && def.defaults.hasOwnProperty("inputs")) { + newNode._config.inputs = JSON.stringify(node.inputs); + newNode.inputs = parseInt(node.inputs, 10); + } else { + newNode.inputs = def.inputs; + } + + if (node.hasOwnProperty("outputs") && def.defaults.hasOwnProperty("outputs")) { + newNode._config.outputs = JSON.stringify(node.outputs); + newNode.outputs = parseInt(node.outputs, 10); + } else { + newNode.outputs = def.outputs; + } + + // The node declares outputs in its defaults, but has not got a valid value + // Defer to the length of the wires array + if (node.hasOwnProperty('wires')) { + if (isNaN(newNode.outputs)) { + newNode.outputs = newNode.wires.length; + } else if (newNode.wires.length > newNode.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); + // TODO: Pas dans l'autre sens ? + newNode.wires = newNode.wires.slice(0, newNode.outputs); + } + } + + // Copy editable properties + for (const d in def.defaults) { + if (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 (def.credentials.hasOwnProperty(c) && node.credentials.hasOwnProperty(c)) { + newNode.credentials[c] = node.credentials[c]; + } + } + } + } + + return newNode; + } + /** * Options: * - generateIds - whether to replace all node ids @@ -1930,7 +2153,23 @@ RED.nodes = (function() { isInitialLoad = true; initialLoad = JSON.parse(JSON.stringify(newNodes)); } - var unknownTypes = []; + + const unknownTypes = identifyUnknowTypes(newNodes); + if (!isInitialLoad && unknownTypes.length) { + const typeList = $("