mirror of
				https://github.com/node-red/node-red.git
				synced 2025-03-01 10:36:34 +00:00 
			
		
		
		
	Merge 15c2faffde into 2feb290ae3
				
					
				
			This commit is contained in:
		@@ -1715,6 +1715,30 @@ RED.nodes = (function() {
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Analyzes the array of nodes passed as an argument to find unknown node types.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {Array<object>} nodes An array of nodes to analyse
 | 
			
		||||
     * @returns {Array<string>} 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,21 +2153,26 @@ RED.nodes = (function() {
 | 
			
		||||
            isInitialLoad = true;
 | 
			
		||||
            initialLoad = JSON.parse(JSON.stringify(newNodes));
 | 
			
		||||
        }
 | 
			
		||||
        var unknownTypes = [];
 | 
			
		||||
 | 
			
		||||
        const unknownTypes = identifyUnknowTypes(newNodes);
 | 
			
		||||
        if (!isInitialLoad && unknownTypes.length) {
 | 
			
		||||
            const typeList = $("<ul>");
 | 
			
		||||
 | 
			
		||||
            unknownTypes.forEach(function (type) {
 | 
			
		||||
                $("<li>").text(type).appendTo(typeList);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            const title = "<p>" + RED._("clipboard.importUnrecognised", { count: unknownTypes.length }) + "</p>";
 | 
			
		||||
            RED.notify(title + typeList[0].outerHTML, {
 | 
			
		||||
                type: "error",
 | 
			
		||||
                fixed: false,
 | 
			
		||||
                timeout: 10000
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for (i=0;i<newNodes.length;i++) {
 | 
			
		||||
            n = newNodes[i];
 | 
			
		||||
            var id = n.id;
 | 
			
		||||
            // TODO: remove workspace in next release+1
 | 
			
		||||
            if (n.type != "workspace" &&
 | 
			
		||||
                n.type != "tab" &&
 | 
			
		||||
                n.type != "subflow" &&
 | 
			
		||||
                n.type != "group" &&
 | 
			
		||||
                n.type != 'junction' &&
 | 
			
		||||
                !registry.getNodeType(n.type) &&
 | 
			
		||||
                n.type.substring(0,8) != "subflow:" &&
 | 
			
		||||
                unknownTypes.indexOf(n.type)==-1) {
 | 
			
		||||
                    unknownTypes.push(n.type);
 | 
			
		||||
            }
 | 
			
		||||
            if (n.z) {
 | 
			
		||||
                nodeZmap[n.z] = nodeZmap[n.z] || [];
 | 
			
		||||
                nodeZmap[n.z].push(n);
 | 
			
		||||
@@ -1967,15 +2195,6 @@ RED.nodes = (function() {
 | 
			
		||||
                n.z = recoveryWorkspace.id;
 | 
			
		||||
                nodeZmap[recoveryWorkspace.id].push(n);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
        if (!isInitialLoad && unknownTypes.length > 0) {
 | 
			
		||||
            var typeList = $("<ul>");
 | 
			
		||||
            unknownTypes.forEach(function(t) {
 | 
			
		||||
                $("<li>").text(t).appendTo(typeList);
 | 
			
		||||
            })
 | 
			
		||||
            typeList = typeList[0].outerHTML;
 | 
			
		||||
            RED.notify("<p>"+RED._("clipboard.importUnrecognised",{count:unknownTypes.length})+"</p>"+typeList,"error",false,10000);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var activeWorkspace = RED.workspaces.active();
 | 
			
		||||
@@ -2154,43 +2373,7 @@ RED.nodes = (function() {
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (!existingConfigNode || existingConfigNode._def.exclusive) { //} || !compareNodes(existingConfigNode,n,true) || existingConfigNode.z !== n.z) {
 | 
			
		||||
                    configNode = {
 | 
			
		||||
                        id:n.id,
 | 
			
		||||
                        z:n.z,
 | 
			
		||||
                        type:n.type,
 | 
			
		||||
                        info: n.info,
 | 
			
		||||
                        users:[],
 | 
			
		||||
                        _config:{},
 | 
			
		||||
                        _configNodeReferences: new Set()
 | 
			
		||||
                    };
 | 
			
		||||
                    if (!n.z) {
 | 
			
		||||
                        delete configNode.z;
 | 
			
		||||
                    }
 | 
			
		||||
                    if (options.markChanged) {
 | 
			
		||||
                        configNode.changed = true
 | 
			
		||||
                    }
 | 
			
		||||
                    if (n.hasOwnProperty('d')) {
 | 
			
		||||
                        configNode.d = n.d;
 | 
			
		||||
                    }
 | 
			
		||||
                    for (d in def.defaults) {
 | 
			
		||||
                        if (def.defaults.hasOwnProperty(d)) {
 | 
			
		||||
                            configNode[d] = n[d];
 | 
			
		||||
                            configNode._config[d] = JSON.stringify(n[d]);
 | 
			
		||||
                            if (def.defaults[d].type) {
 | 
			
		||||
                                configNode._configNodeReferences.add(n[d])
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    if (def.hasOwnProperty('credentials') && n.hasOwnProperty('credentials')) {
 | 
			
		||||
                        configNode.credentials = {};
 | 
			
		||||
                        for (d in def.credentials) {
 | 
			
		||||
                            if (def.credentials.hasOwnProperty(d) && n.credentials.hasOwnProperty(d)) {
 | 
			
		||||
                                configNode.credentials[d] = n.credentials[d];
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    configNode.label = def.label;
 | 
			
		||||
                    configNode._def = def;
 | 
			
		||||
                    configNode = copyConfigNode(n, def, options);
 | 
			
		||||
                    if (createNewIds || options.importMap[n.id] === "copy") {
 | 
			
		||||
                        configNode.id = getID();
 | 
			
		||||
                    }
 | 
			
		||||
@@ -2251,222 +2434,133 @@ RED.nodes = (function() {
 | 
			
		||||
            if (n.type !== "workspace" && n.type !== "tab" && n.type !== "subflow") {
 | 
			
		||||
                def = registry.getNodeType(n.type);
 | 
			
		||||
                if (!def || def.category != "config") {
 | 
			
		||||
                    var node = {
 | 
			
		||||
                        x:parseFloat(n.x || 0),
 | 
			
		||||
                        y:parseFloat(n.y || 0),
 | 
			
		||||
                        z:n.z,
 | 
			
		||||
                        type: n.type,
 | 
			
		||||
                        info: n.info,
 | 
			
		||||
                        changed:false,
 | 
			
		||||
                        _config:{}
 | 
			
		||||
                    }
 | 
			
		||||
                    if (n.type !== "group" && n.type !== 'junction') {
 | 
			
		||||
                        node.wires = n.wires||[];
 | 
			
		||||
                        node.inputLabels = n.inputLabels;
 | 
			
		||||
                        node.outputLabels = n.outputLabels;
 | 
			
		||||
                        node.icon = n.icon;
 | 
			
		||||
                    }
 | 
			
		||||
                    if (n.type === 'junction') {
 | 
			
		||||
                        node.wires = n.wires||[];
 | 
			
		||||
                    }
 | 
			
		||||
                    if (n.hasOwnProperty('l')) {
 | 
			
		||||
                        node.l = n.l;
 | 
			
		||||
                    }
 | 
			
		||||
                    if (n.hasOwnProperty('d')) {
 | 
			
		||||
                        node.d = n.d;
 | 
			
		||||
                    }
 | 
			
		||||
                    if (n.hasOwnProperty('g')) {
 | 
			
		||||
                        node.g = n.g;
 | 
			
		||||
                    }
 | 
			
		||||
                    if (options.markChanged) {
 | 
			
		||||
                        node.changed = true
 | 
			
		||||
                    }
 | 
			
		||||
                    if (createNewIds || options.importMap[n.id] === "copy") {
 | 
			
		||||
                        if (subflow_denylist[n.z]) {
 | 
			
		||||
                            continue;
 | 
			
		||||
                        } else if (subflow_map[node.z]) {
 | 
			
		||||
                            node.z = subflow_map[node.z].id;
 | 
			
		||||
                        } else if (subflow_map[n.z]) {
 | 
			
		||||
                            n.z = subflow_map[n.z].id;
 | 
			
		||||
                        } else {
 | 
			
		||||
                            node.z = workspace_map[node.z];
 | 
			
		||||
                            if (!workspaces[node.z]) {
 | 
			
		||||
                            n.z = workspace_map[n.z];
 | 
			
		||||
                            if (!workspaces[n.z]) {
 | 
			
		||||
                                if (createMissingWorkspace) {
 | 
			
		||||
                                    if (missingWorkspace === null) {
 | 
			
		||||
                                        missingWorkspace = RED.workspaces.add(null,true);
 | 
			
		||||
                                        new_workspaces.push(missingWorkspace);
 | 
			
		||||
                                    }
 | 
			
		||||
                                    node.z = missingWorkspace.id;
 | 
			
		||||
                                    n.z = missingWorkspace.id;
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    node.z = activeWorkspace;
 | 
			
		||||
                                    n.z = activeWorkspace;
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        node.id = getID();
 | 
			
		||||
                        // TODO: If updated here, conflict with importMap in L2634
 | 
			
		||||
                        //n.id = getID();
 | 
			
		||||
                    } else {
 | 
			
		||||
                        node.id = n.id;
 | 
			
		||||
                        const keepNodesCurrentZ = reimport && node.z && (RED.workspaces.contains(node.z) || RED.nodes.subflow(node.z))
 | 
			
		||||
                        if (!keepNodesCurrentZ && (node.z == null || (!workspace_map[node.z] && !subflow_map[node.z]))) {
 | 
			
		||||
                        const keepNodesCurrentZ = reimport && n.z && (RED.workspaces.contains(n.z) || RED.nodes.subflow(n.z))
 | 
			
		||||
                        if (!keepNodesCurrentZ && (n.z == null || (!workspace_map[n.z] && !subflow_map[n.z]))) {
 | 
			
		||||
                            if (createMissingWorkspace) {
 | 
			
		||||
                                if (missingWorkspace === null) {
 | 
			
		||||
                                    missingWorkspace = RED.workspaces.add(null,true);
 | 
			
		||||
                                    new_workspaces.push(missingWorkspace);
 | 
			
		||||
                                }
 | 
			
		||||
                                node.z = missingWorkspace.id;
 | 
			
		||||
                                n.z = missingWorkspace.id;
 | 
			
		||||
                            } else {
 | 
			
		||||
                                node.z = activeWorkspace;
 | 
			
		||||
                                n.z = activeWorkspace;
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    node._def = def;
 | 
			
		||||
                    if (node.type === "group") {
 | 
			
		||||
                        node._def = RED.group.def;
 | 
			
		||||
                        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 (n.hasOwnProperty('w')) {
 | 
			
		||||
                            node.w = n.w
 | 
			
		||||
                        }
 | 
			
		||||
                        if (n.hasOwnProperty('h')) {
 | 
			
		||||
                            node.h = n.h
 | 
			
		||||
                        }
 | 
			
		||||
                    } else if (n.type.substring(0,7) === "subflow") {
 | 
			
		||||
                        var parentId = n.type.split(":")[1];
 | 
			
		||||
                        var subflow = subflow_denylist[parentId]||subflow_map[parentId]||getSubflow(parentId);
 | 
			
		||||
                        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")
 | 
			
		||||
                            }
 | 
			
		||||
                            var orig = {};
 | 
			
		||||
                            for (var p in n) {
 | 
			
		||||
                                if (n.hasOwnProperty(p) && p!="x" && p!="y" && p!="z" && p!="id" && p!="wires") {
 | 
			
		||||
                                    orig[p] = n[p];
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                            node._orig = orig;
 | 
			
		||||
                            node.name = n.type;
 | 
			
		||||
                            node.type = "unknown";
 | 
			
		||||
                        } else {
 | 
			
		||||
 | 
			
		||||
                    // Update Subflow instance properties from Subflow definition
 | 
			
		||||
                    if (n.type.substring(0, 7) === "subflow") {
 | 
			
		||||
                        const parentId = n.type.split(":")[1];
 | 
			
		||||
                        const subflow = subflow_denylist[parentId] || subflow_map[parentId] || getSubflow(parentId);
 | 
			
		||||
 | 
			
		||||
                        if (subflow) {
 | 
			
		||||
                            // If the parent Subflow is found, update Subflow instance properties
 | 
			
		||||
                            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 if (n.type === 'junction') {
 | 
			
		||||
                         node._def = {defaults:{}}
 | 
			
		||||
                         node._config.x = node.x
 | 
			
		||||
                         node._config.y = node.y
 | 
			
		||||
                         node.inputs = 1
 | 
			
		||||
                         node.outputs = 1
 | 
			
		||||
                         node.w = 0;
 | 
			
		||||
                         node.h = 0;
 | 
			
		||||
 | 
			
		||||
                    } else {
 | 
			
		||||
                        if (!node._def) {
 | 
			
		||||
                            if (node.x && node.y) {
 | 
			
		||||
                                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 {
 | 
			
		||||
                                node._def = {
 | 
			
		||||
                                    category:"config",
 | 
			
		||||
                                    set: registry.getNodeSet("node-red/unknown")
 | 
			
		||||
                                };
 | 
			
		||||
                                node.users = [];
 | 
			
		||||
                                // This is a config node, so delete the default
 | 
			
		||||
                                // non-config node properties
 | 
			
		||||
                                delete node.x;
 | 
			
		||||
                                delete node.y;
 | 
			
		||||
                                delete node.wires;
 | 
			
		||||
                                delete node.inputLabels;
 | 
			
		||||
                                delete node.outputLabels;
 | 
			
		||||
                                if (!n.z) {
 | 
			
		||||
                                    delete node.z;
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                            var orig = {};
 | 
			
		||||
                            for (var p in n) {
 | 
			
		||||
                                if (n.hasOwnProperty(p) && p!="x" && p!="y" && p!="z" && p!="id" && p!="wires") {
 | 
			
		||||
                                    orig[p] = n[p];
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                            node._orig = orig;
 | 
			
		||||
                            node.name = n.type;
 | 
			
		||||
                            node.type = "unknown";
 | 
			
		||||
                        }
 | 
			
		||||
                        if (node._def.category != "config") {
 | 
			
		||||
                            if (n.hasOwnProperty('inputs') && node._def.defaults.hasOwnProperty("inputs")) {
 | 
			
		||||
                                node.inputs = parseInt(n.inputs, 10);
 | 
			
		||||
                                node._config.inputs = JSON.stringify(n.inputs);
 | 
			
		||||
                            } else {
 | 
			
		||||
                                node.inputs = node._def.inputs;
 | 
			
		||||
                            }
 | 
			
		||||
                            if (n.hasOwnProperty('outputs') && node._def.defaults.hasOwnProperty("outputs")) {
 | 
			
		||||
                                node.outputs = parseInt(n.outputs, 10);
 | 
			
		||||
                                node._config.outputs = JSON.stringify(n.outputs);
 | 
			
		||||
                            } else {
 | 
			
		||||
                                node.outputs = node._def.outputs;
 | 
			
		||||
                                n.type = "subflow:" + subflow.id;
 | 
			
		||||
                                def = registry.getNodeType(n.type);
 | 
			
		||||
                                // TODO: Why delete `node.i`
 | 
			
		||||
                                delete n.i;
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                            // 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(node.outputs)) {
 | 
			
		||||
                                    node.outputs = node.wires.length;
 | 
			
		||||
                                } else if (node.wires.length > node.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);
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                            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];
 | 
			
		||||
                                    }
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                            // Get inputs/outputs count from parent
 | 
			
		||||
                            n.inputs = subflow.in.length;
 | 
			
		||||
                            n.outputs = subflow.out.length;
 | 
			
		||||
                        } else {
 | 
			
		||||
                            // If the parent Subflow is not found, this Subflow instance will be marked as unknown
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    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 === 'junction') {
 | 
			
		||||
                        new_junctions.push(node)
 | 
			
		||||
                    } else if (node.type === "unknown" || node._def.category !== "config") {
 | 
			
		||||
                        new_nodes.push(node);
 | 
			
		||||
                    } else if (node.type === "group") {
 | 
			
		||||
                        new_groups.push(node);
 | 
			
		||||
                        new_group_set.add(node.id);
 | 
			
		||||
 | 
			
		||||
                    // Try to fix the node definition
 | 
			
		||||
                    let isUnknownNode = false;
 | 
			
		||||
                    if (!def) {
 | 
			
		||||
                        if (n.type === "group") {
 | 
			
		||||
                            // Group Node Definition
 | 
			
		||||
                            def = RED.group.def;
 | 
			
		||||
                        } else if (n.type === "junction") {
 | 
			
		||||
                            // Junction Node Definition
 | 
			
		||||
                            def = { defaults: {} };
 | 
			
		||||
                        } else if (!n.hasOwnProperty("x") && !n.hasOwnProperty("y")) {
 | 
			
		||||
                            // Assumes its an unknown Config Node
 | 
			
		||||
                            isUnknownNode = true;
 | 
			
		||||
                            def = {
 | 
			
		||||
                                category: "config",
 | 
			
		||||
                                defaults: {},
 | 
			
		||||
                                set: registry.getNodeSet("node-red/unknown")
 | 
			
		||||
                            };
 | 
			
		||||
                        } else {
 | 
			
		||||
                            // Unknown Node
 | 
			
		||||
                            isUnknownNode = true;
 | 
			
		||||
                            def = {
 | 
			
		||||
                                color: "#fee",
 | 
			
		||||
                                defaults: {},
 | 
			
		||||
                                label: "unknown: " + n.type,
 | 
			
		||||
                                labelStyle: "red-ui-flow-node-label-italic",
 | 
			
		||||
                                // TODO: ?? instead of ||
 | 
			
		||||
                                outputs: n.outputs || (n.wires && n.wires.length) || 0,
 | 
			
		||||
                                set: registry.getNodeSet("node-red/unknown")
 | 
			
		||||
                            };
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    // Now all properties have been fixed, copy the node
 | 
			
		||||
                    // NOTE: If def is unknown node, editable properties are not copied
 | 
			
		||||
                    // TODO: Group Node has config as category - why?
 | 
			
		||||
                    const isConfigNode = def?.category === "config" && n.type !== "group";
 | 
			
		||||
                    if (isConfigNode) {
 | 
			
		||||
                        node_map[n.id] = copyConfigNode(n, def, options);
 | 
			
		||||
                    } else {
 | 
			
		||||
                        // Node, Group, Junction or Subflow
 | 
			
		||||
                        node_map[n.id] = copyNode(n, def, options);
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    // Unknown Node - Copy editable properties so that the node is always exportable
 | 
			
		||||
                    if (isUnknownNode) {
 | 
			
		||||
                        const propertiesNotCopyable = ["x", "y", "z", "id", "wires"];
 | 
			
		||||
                        node_map[n.id]._orig = Object.entries(n).reduce(function (orig, [prop, value]) {
 | 
			
		||||
                            if (n.hasOwnProperty(prop) && !propertiesNotCopyable.includes(prop)) {
 | 
			
		||||
                                orig[prop] = value;
 | 
			
		||||
                            }
 | 
			
		||||
                            return orig;
 | 
			
		||||
                        }, {});
 | 
			
		||||
 | 
			
		||||
                        node_map[n.id].name = n.type;
 | 
			
		||||
                        node_map[n.id].type = "unknown";
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    // Now the node has been copied, change the `id` if it's a copy
 | 
			
		||||
                    if (createNewIds || options.importMap[n.id] === "copy") {
 | 
			
		||||
                        node_map[n.id].id = getID();
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if (n.type === 'junction') {
 | 
			
		||||
                        new_junctions.push(node_map[n.id])
 | 
			
		||||
                    } else if (n.type === "group") {
 | 
			
		||||
                        new_groups.push(node_map[n.id]);
 | 
			
		||||
                        new_group_set.add(node_map[n.id].id);
 | 
			
		||||
                    } else {
 | 
			
		||||
                        new_nodes.push(node_map[n.id]);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user