mirror of
https://github.com/node-red/node-red.git
synced 2025-03-01 10:36:34 +00:00
Doc the (new) functions and add the onNodeTypeAdded
function
This commit is contained in:
parent
5d78c546bc
commit
6a2a763288
@ -564,6 +564,11 @@ RED.nodes = (function() {
|
||||
return api;
|
||||
})()
|
||||
|
||||
/**
|
||||
* Generates a random ID consisting of 8 bytes.
|
||||
*
|
||||
* @returns {string} The generated ID.
|
||||
*/
|
||||
function generateId() {
|
||||
var bytes = [];
|
||||
for (var i=0;i<8;i++) {
|
||||
@ -1693,7 +1698,6 @@ RED.nodes = (function() {
|
||||
var newConfigNodes = {};
|
||||
var removedNodes = [];
|
||||
// Figure out what we're being asked to replace - subflows/configNodes
|
||||
// TODO: config nodes
|
||||
newNodes.forEach(function(n) {
|
||||
if (n.type === "subflow") {
|
||||
newSubflows[n.id] = n;
|
||||
@ -1795,10 +1799,11 @@ 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.
|
||||
* - `emitNotification` - Emit an notification with unknown types. Default false.
|
||||
*
|
||||
* @remarks Throws an error to be handled.
|
||||
* @param {Array<object>} nodes An array of nodes to analyse
|
||||
* @param {object} options An options object
|
||||
* @param {{ emitNotification?: boolean; }} options An options object
|
||||
* @returns {Array<string>} An array with unknown types
|
||||
*/
|
||||
function identifyUnknowType(nodes, options = {}) {
|
||||
@ -1828,6 +1833,13 @@ RED.nodes = (function() {
|
||||
return unknownTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Warns the user that the import contains existing nodes that the user need to resolve.
|
||||
*
|
||||
* @remarks Throws an error to be handled.
|
||||
* @param {Array<object>} existingNodes An array containing conflicting nodes.
|
||||
* @param {Array<object>} importedNodes An array containing all imported nodes.
|
||||
*/
|
||||
function emitExistingNodesNotification(existingNodes, importedNodes) {
|
||||
const errorMessage = RED._("clipboard.importDuplicate", { count: existingNodes.length });
|
||||
const maxItemCount = 5; // Max 5 items in the list
|
||||
@ -1852,6 +1864,15 @@ RED.nodes = (function() {
|
||||
throw existingNodesError;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a copy of the Config Node received as a parameter.
|
||||
*
|
||||
* @remarks Don't change the ID.
|
||||
* @param {object} configNode The Config Node to copy.
|
||||
* @param {object} def The Config Node definition.
|
||||
* @param {object} options Same options as import
|
||||
* @returns The new Config Node copied.
|
||||
*/
|
||||
function copyConfigNode(configNode, def, options = {}) {
|
||||
const newNode = {
|
||||
id: configNode.id,
|
||||
@ -1899,6 +1920,15 @@ RED.nodes = (function() {
|
||||
return newNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a copy of the Node received as a parameter.
|
||||
*
|
||||
* @remarks Don't change the ID.
|
||||
* @param {object} node The Node to copy.
|
||||
* @param {object} def The Node definition.
|
||||
* @param {object} options Same options as import
|
||||
* @returns The new Node copied.
|
||||
*/
|
||||
function copyNode(node, def, options = {}) {
|
||||
const newNode = {
|
||||
id: node.id,
|
||||
@ -2012,18 +2042,30 @@ RED.nodes = (function() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the import of nodes - these nodes can be copied, replaced or just imported.
|
||||
*
|
||||
* Options:
|
||||
* - generateIds - whether to replace all node ids
|
||||
* - addFlow - whether to import nodes to a new tab
|
||||
* - markChanged - whether to set changed=true on all newly imported objects
|
||||
* - reimport - if node has a .z property, dont overwrite it
|
||||
* Only applicible when `generateIds` is false
|
||||
* - importMap - how to resolve any conflicts.
|
||||
* - id:import - import as-is
|
||||
* - id:copy - import with new id
|
||||
* - id:replace - import over the top of existing
|
||||
* - `addFlow` - whether to import nodes to a new tab. Default false.
|
||||
* - `generateIds` - whether to replace all node ids. Default false.
|
||||
* - `markChanged` - whether to set `changed` = true on all newly imported nodes.
|
||||
* - `reimport` - if node has a `z` property, dont overwrite it
|
||||
* Only applicible when `generateIds` is false. Default false.
|
||||
* - `importMap` - how to resolve any conflicts.
|
||||
* - nodeId: `import` - import as-is
|
||||
* - nodeId: `copy` - import with new id
|
||||
* - nodeId: `replace` - repace the existing
|
||||
*
|
||||
* @remarks Only Subflow definition (tab) and Config Node are replaceable!
|
||||
*
|
||||
* @typedef {string} NodeId
|
||||
* @typedef {Record<NodeId, "copy" | "import" | "replace" | "skip">} ImportMap
|
||||
*
|
||||
* @param {string | object | Array<object>} originalNodes A node or array of nodes to import
|
||||
* @param {{ addFlow?: boolean; generateIds?: boolean; importMap?: ImportMap; markChanged?: boolean;
|
||||
* reimport?: boolean; }} options Options to involve on import.
|
||||
* @returns An object containing all the elements created.
|
||||
*/
|
||||
function importNodes(originalNodes, options) {
|
||||
function importNodes(originalNodes, options = {}) {
|
||||
const defaultOptions = { generateIds: false, addFlow: false, markChanged: false, reimport: false, importMap: {} };
|
||||
options = Object.assign({}, defaultOptions, options);
|
||||
|
||||
@ -2075,6 +2117,11 @@ RED.nodes = (function() {
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure ignored nodes are not imported
|
||||
if (options.importMap[id] === "skip") {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
@ -2653,30 +2700,40 @@ RED.nodes = (function() {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Update any config nodes referenced by the provided node to ensure their 'users' list is correct
|
||||
function updateConfigNodeUsers(n, options) {
|
||||
/**
|
||||
* Update any config nodes referenced by the provided node to ensure
|
||||
* their 'users' list is correct.
|
||||
*
|
||||
* Options:
|
||||
* - `action` - Add or remove the node from the Config Node users list. Default `add`.
|
||||
* - `emitEvent` - Emit the `nodes:changes` event. Default true.
|
||||
*
|
||||
* @param {object} node The node in which to check if it contains references
|
||||
* @param {{ action?: "add" | "remove"; emitEvent?: boolean; }} options Options to apply.
|
||||
*/
|
||||
function updateConfigNodeUsers(node, options = {}) {
|
||||
const defaultOptions = { action: "add", emitEvent: true };
|
||||
options = Object.assign({}, defaultOptions, options);
|
||||
|
||||
for (var d in n._def.defaults) {
|
||||
if (n._def.defaults.hasOwnProperty(d)) {
|
||||
var property = n._def.defaults[d];
|
||||
for (var d in node._def.defaults) {
|
||||
if (node._def.defaults.hasOwnProperty(d)) {
|
||||
var property = node._def.defaults[d];
|
||||
if (property.type) {
|
||||
var type = registry.getNodeType(property.type);
|
||||
if (type && type.category == "config") {
|
||||
var configNode = configNodes[n[d]];
|
||||
var configNode = configNodes[node[d]];
|
||||
if (configNode) {
|
||||
if (options.action === "add") {
|
||||
if (configNode.users.indexOf(n) === -1) {
|
||||
configNode.users.push(n);
|
||||
if (configNode.users.indexOf(node) === -1) {
|
||||
configNode.users.push(node);
|
||||
if (options.emitEvent) {
|
||||
RED.events.emit('nodes:change', configNode);
|
||||
}
|
||||
}
|
||||
} else if (options.action === "remove") {
|
||||
if (configNode.users.indexOf(n) !== -1) {
|
||||
if (configNode.users.indexOf(node) !== -1) {
|
||||
const users = configNode.users;
|
||||
users.splice(users.indexOf(n), 1);
|
||||
users.splice(users.indexOf(node), 1);
|
||||
if (options.emitEvent) {
|
||||
RED.events.emit('nodes:change', configNode);
|
||||
}
|
||||
@ -2952,88 +3009,110 @@ RED.nodes = (function() {
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
init: function() {
|
||||
RED.events.on("registry:node-type-added",function(type) {
|
||||
var def = registry.getNodeType(type);
|
||||
var replaced = false;
|
||||
var replaceNodes = {};
|
||||
RED.nodes.eachNode(function(n) {
|
||||
if (n.type === "unknown" && n.name === type) {
|
||||
replaceNodes[n.id] = n;
|
||||
/**
|
||||
* Finds unknown nodes of the same type and replaces them to update their
|
||||
* definition.
|
||||
*
|
||||
* An imported node that has no definition is marked as unknown.
|
||||
* When adding a new node type, this function searches for existing nodes
|
||||
* of that type to update their definition.
|
||||
*
|
||||
* @param {string} type The type of the Node added
|
||||
*/
|
||||
function onNodeTypeAdded(type) {
|
||||
const nodesToReplace = [];
|
||||
RED.nodes.eachConfig(function (node) {
|
||||
if (node.type === "unknown" && node.name === type) {
|
||||
nodesToReplace.push(node);
|
||||
}
|
||||
});
|
||||
|
||||
RED.nodes.eachNode(function (node) {
|
||||
if (node.type === "unknown" && node.name === type) {
|
||||
nodesToReplace.push(node);
|
||||
}
|
||||
});
|
||||
|
||||
// Skip if there is nothing to replace
|
||||
if (!nodesToReplace.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nodeGroupMap = {};
|
||||
const removedNodes = [];
|
||||
nodesToReplace.forEach(function (node) {
|
||||
// Create a snapshot of the Node
|
||||
removedNodes.push(convertNode(node));
|
||||
|
||||
// Remove the Node
|
||||
removeNode(node);
|
||||
|
||||
// Reimporting a node *without* including its group object will cause
|
||||
// the g property to be cleared. Cache it here so we can restore it.
|
||||
if (node.g) {
|
||||
nodeGroupMap[node.id] = node.g
|
||||
}
|
||||
});
|
||||
|
||||
const removedLinks = [];
|
||||
const nodeMap = nodesToReplace.reduce(function (map, node) {
|
||||
map[node.id] = node;
|
||||
return map;
|
||||
}, {});
|
||||
// Remove any links between nodes that are going to be reimported.
|
||||
// This prevents a duplicate link from being added.
|
||||
RED.nodes.eachLink(function (link) {
|
||||
if (nodeMap.hasOwnProperty(link.source.id) && nodeMap.hasOwnProperty(link.target.id)) {
|
||||
removedLinks.push(link);
|
||||
}
|
||||
});
|
||||
|
||||
removedLinks.forEach(removeLink);
|
||||
|
||||
// Force the redraw to be synchronous so the view updates
|
||||
// *now* and removes the unknown node
|
||||
RED.view.redraw(true, true);
|
||||
|
||||
// Re-import removed nodes - now nodes have their definition
|
||||
const result = importNodes(removedNodes, { generateIds: false, reimport: true });
|
||||
|
||||
const newNodeMap = {};
|
||||
// Rattach the nodes to their group
|
||||
result?.nodes.forEach(function (node) {
|
||||
newNodeMap[node.id] = node;
|
||||
if (nodeGroupMap[node.id]) {
|
||||
// This node is in a group - need to substitute the
|
||||
// node reference inside the group
|
||||
node.g = nodeGroupMap[node.id];
|
||||
const group = RED.nodes.group(node.g);
|
||||
if (group) {
|
||||
const index = group.nodes.findIndex((g) => g.id === node.id);
|
||||
if (index > -1) {
|
||||
group.nodes[index] = node;
|
||||
}
|
||||
});
|
||||
RED.nodes.eachConfig(function(n) {
|
||||
if (n.type === "unknown" && n.name === type) {
|
||||
replaceNodes[n.id] = n;
|
||||
}
|
||||
});
|
||||
|
||||
const nodeGroupMap = {}
|
||||
var replaceNodeIds = Object.keys(replaceNodes);
|
||||
if (replaceNodeIds.length > 0) {
|
||||
var reimportList = [];
|
||||
replaceNodeIds.forEach(function(id) {
|
||||
var n = replaceNodes[id];
|
||||
if (configNodes.hasOwnProperty(n.id)) {
|
||||
delete configNodes[n.id];
|
||||
} else {
|
||||
allNodes.removeNode(n);
|
||||
}
|
||||
if (n.g) {
|
||||
// reimporting a node *without* including its group object
|
||||
// will cause the g property to be cleared. Cache it
|
||||
// here so we can restore it
|
||||
nodeGroupMap[n.id] = n.g
|
||||
}
|
||||
reimportList.push(convertNode(n));
|
||||
RED.events.emit('nodes:remove',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,{generateIds:false, reimport: true});
|
||||
var newNodeMap = {};
|
||||
result.nodes.forEach(function(n) {
|
||||
newNodeMap[n.id] = n;
|
||||
if (nodeGroupMap[n.id]) {
|
||||
// This node is in a group - need to substitute the
|
||||
// node reference inside the group
|
||||
n.g = nodeGroupMap[n.id]
|
||||
const group = RED.nodes.group(n.g)
|
||||
if (group) {
|
||||
var index = group.nodes.findIndex(gn => gn.id === n.id)
|
||||
if (index > -1) {
|
||||
group.nodes[index] = 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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Relink nodes
|
||||
RED.nodes.eachLink(function (link) {
|
||||
if (newNodeMap.hasOwnProperty(link.source.id)) {
|
||||
link.source = newNodeMap[link.source.id];
|
||||
}
|
||||
if (newNodeMap.hasOwnProperty(link.target.id)) {
|
||||
link.target = newNodeMap[link.target.id];
|
||||
}
|
||||
});
|
||||
|
||||
RED.view.redraw(true);
|
||||
}
|
||||
|
||||
return {
|
||||
init: function () {
|
||||
RED.events.on("registry:node-type-added", onNodeTypeAdded);
|
||||
RED.events.on("deploy", function () {
|
||||
allNodes.clearState();
|
||||
});
|
||||
RED.events.on('deploy', function () {
|
||||
allNodes.clearState()
|
||||
})
|
||||
},
|
||||
registry:registry,
|
||||
setNodeList: registry.setNodeList,
|
||||
|
Loading…
x
Reference in New Issue
Block a user