function importNodes(newNodesObj,options) {
        options = options || {
            addFlow: false,
            touchImport: false,
            generateIds: false,
            generateDefaultNames: false
        }
        var addNewFlow = options.addFlow
        var touchImport = options.touchImport;

        if (mouse_mode === RED.state.SELECTING_NODE) {
            return;
        }
        const wasDirty = RED.nodes.dirty()
        var nodesToImport;
        if (typeof newNodesObj === "string") {
            if (newNodesObj === "") {
                return;
            }
            try {
                nodesToImport = JSON.parse(newNodesObj);
            } catch(err) {
                var e = new Error(RED._("clipboard.invalidFlow",{message:err.message}));
                e.code = "NODE_RED";
                throw e;
            }
        } else {
            nodesToImport = newNodesObj;
        }

        if (!Array.isArray(nodesToImport)) {
            nodesToImport = [nodesToImport];
        }
        if (options.generateDefaultNames) {
            RED.actions.invoke("core:generate-node-names", nodesToImport, {
                renameBlank: false,
                renameClash: true,
                generateHistory: false
            })
        }

        try {
            var activeSubflowChanged;
            if (activeSubflow) {
                activeSubflowChanged = activeSubflow.changed;
            }
            var filteredNodesToImport = nodesToImport;
            var globalConfig = null;
            var gconf = null;

            RED.nodes.eachConfig(function (conf) {
                if (conf.type === "global-config") {
                    gconf = conf;
                }
            });
            if (gconf) {
                filteredNodesToImport = nodesToImport.filter(function (n) {
                    return (n.type !== "global-config");
                });
                globalConfig = nodesToImport.find(function (n) {
                    return (n.type === "global-config");
                });
            }
            var result = RED.nodes.import(filteredNodesToImport,{
                generateIds: options.generateIds,
                addFlow: addNewFlow,
                importMap: options.importMap,
                markChanged: true
            });
            if (result) {
                var new_nodes = result.nodes;
                var new_links = result.links;
                var new_groups = result.groups;
                var new_junctions = result.junctions;
                var new_workspaces = result.workspaces;
                var new_subflows = result.subflows;
                var removedNodes = result.removedNodes;
                var new_default_workspace = result.missingWorkspace;
                if (addNewFlow && new_default_workspace) {
                    RED.workspaces.show(new_default_workspace.id);
                }
                var new_ms = new_nodes.filter(function(n) { return n.hasOwnProperty("x") && n.hasOwnProperty("y") && n.z == RED.workspaces.active() });
                new_ms = new_ms.concat(new_groups.filter(function(g) { return g.z === RED.workspaces.active()}))
                new_ms = new_ms.concat(new_junctions.filter(function(j) { return j.z === RED.workspaces.active()}))
                var new_node_ids = new_nodes.map(function(n){ return n.id; });

                clearSelection();
                movingSet.clear();
                movingSet.add(new_ms);


                // TODO: pick a more sensible root node
                if (movingSet.length() > 0) {
                    if (mouse_position == null) {
                        mouse_position = [0,0];
                    }

                    var dx = mouse_position[0];
                    var dy = mouse_position[1];
                    if (movingSet.length() > 0) {
                        var root_node = movingSet.get(0).n;
                        dx = root_node.x;
                        dy = root_node.y;
                    }

                    var minX = 0;
                    var minY = 0;
                    var i;
                    var node,group;
                    var l =movingSet.length();
                    for (i=0;i<l;i++) {
                        node = movingSet.get(i);
                        node.n.selected = true;
                        node.n.changed = true;
                        node.n.moved = true;
                        node.n.x -= dx - mouse_position[0];
                        node.n.y -= dy - mouse_position[1];
                        if (node.n.type !== 'junction') {
                            node.n.w = node_width;
                            node.n.h = node_height;
                            node.n.resize = true;
                        }
                        node.dx = node.n.x - mouse_position[0];
                        node.dy = node.n.y - mouse_position[1];
                        if (node.n.type === "group") {
                            node.n.groupMoved = false;
                            minX = Math.min(node.n.x-5,minX);
                            minY = Math.min(node.n.y-5,minY);
                        } else {
                            minX = Math.min(node.n.x-node_width/2-5,minX);
                            minY = Math.min(node.n.y-node_height/2-5,minY);
                        }
                    }
                    for (i=0;i<l;i++) {
                        node = movingSet.get(i);
                        node.n.x -= minX;
                        node.n.y -= minY;
                        node.dx -= minX;
                        node.dy -= minY;
                        if (node.n._def.onadd) {
                            try {
                                node.n._def.onadd.call(node.n);
                            } catch(err) {
                                console.log("Definition error: "+node.n.type+".onadd:",err);
                            }
                        }

                    }
                    if (!touchImport) {
                        mouse_mode = RED.state.IMPORT_DRAGGING;
                        startSelectionMove()  
                    }
                }

                var historyEvent = {
                    t: "add",
                    nodes: new_node_ids,
                    links: new_links,
                    groups: new_groups,
                    junctions: new_junctions,
                    workspaces: new_workspaces,
                    subflows: new_subflows,
                    dirty: wasDirty
                };
                if (movingSet.length() === 0) {
                    RED.nodes.dirty(true);
                }
                if (activeSubflow) {
                    var subflowRefresh = RED.subflow.refresh(true);
                    if (subflowRefresh) {
                        historyEvent.subflow = {
                            id: activeSubflow.id,
                            changed: activeSubflowChanged,
                            instances: subflowRefresh.instances
                        }
                    }
                }
                if (removedNodes) {
                    var replaceEvent = {
                        t: "replace",
                        config: removedNodes
                    }
                    historyEvent = {
                        t:"multi",
                        events: [
                            replaceEvent,
                            historyEvent
                        ]
                    }
                }

                if (globalConfig) {
                    // merge global env to existing global-config
                    var env0 = gconf.env;
                    var env1 = globalConfig.env;
                    var newEnv = Array.from(env0);
                    var changed = false;

                    env1.forEach(function (item1) {
                        var index = newEnv.findIndex(function (item0) {
                            return (item0.name === item1.name);
                        });
                        if (index >= 0) {
                            var item0 = newEnv[index];
                            if ((item0.type !== item1.type) ||
                                (item0.value !== item1.value)) {
                                newEnv[index] = item1;
                                changed = true;
                            }
                        }
                        else {
                            newEnv.push(item1);
                            changed = true;
                        }
                    });
                    if(changed) {
                        gconf.env = newEnv;
                        var replaceEvent = {
                            t: "edit",
                            node: gconf,
                            changed: true,
                            changes: {
                                env: env0
                            }
                        };
                        historyEvent = {
                            t:"multi",
                            events: [
                                replaceEvent,
                                historyEvent,
                            ]
                        };
                    }
                }

                RED.history.push(historyEvent);

                updateActiveNodes();
                redraw();

                var counts = [];
                var newNodeCount = 0;
                var newConfigNodeCount = 0;
                new_nodes.forEach(function(n) {
                    if (n.hasOwnProperty("x") && n.hasOwnProperty("y")) {
                        newNodeCount++;
                    } else {
                        newConfigNodeCount++;
                    }
                })
                var newGroupCount = new_groups.length;
                var newJunctionCount = new_junctions.length;
                if (new_workspaces.length > 0) {
                    counts.push(RED._("clipboard.flow",{count:new_workspaces.length}));
                }
                if (newNodeCount > 0) {
                    counts.push(RED._("clipboard.node",{count:newNodeCount}));
                }
                if (newGroupCount > 0) {
                    counts.push(RED._("clipboard.group",{count:newGroupCount}));
                }
                if (newConfigNodeCount > 0) {
                    counts.push(RED._("clipboard.configNode",{count:newConfigNodeCount}));
                }
                if (new_subflows.length > 0) {
                    counts.push(RED._("clipboard.subflow",{count:new_subflows.length}));
                }
                if (removedNodes && removedNodes.length > 0) {
                    counts.push(RED._("clipboard.replacedNodes",{count:removedNodes.length}));
                }
                if (counts.length > 0) {
                    var countList = "<ul><li>"+counts.join("</li><li>")+"</li></ul>";
                    RED.notify("<p>"+RED._("clipboard.nodesImported")+"</p>"+countList,{id:"clipboard"});
                }

            }
        } catch(error) {
            if (error.code === "import_conflict") {
                // Pass this up for the called to resolve
                throw error;
            } else if (error.code != "NODE_RED") {
                console.log(error.stack);
                RED.notify(RED._("notification.error",{message:error.toString()}),"error");
            } else {
                RED.notify(RED._("notification.error",{message:error.message}),"error");
            }
        }
    }
Imports a new collection of nodes from a JSON String.

 - all get new IDs assigned
 - all "selected"
 - attached to mouse for placing - "IMPORT_DRAGGING"
@param  {String/Array} newNodesObj nodes to import
@param  {Object} options options object

Options:
 - addFlow - whether to import nodes to a new tab
 - touchImport - whether this is a touch import. If not, imported nodes are
                 attachedto mouse for placing - "IMPORT_DRAGGING" state
 - generateIds - whether to automatically generate new ids for all imported nodes
 - generateDefaultNames - whether to automatically update any nodes with clashing
                          default names