Doc the (new) functions and add the onNodeTypeAdded function

This commit is contained in:
GogoVega 2024-06-20 12:15:12 +02:00
parent 5d78c546bc
commit 6a2a763288
No known key found for this signature in database
GPG Key ID: E1E048B63AC5AC2B

View File

@ -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,