@applicaster/zapp-react-native-utils
Version:
Applicaster Zapp React Native utilities package
259 lines (222 loc) • 6.65 kB
JavaScript
import { focusManagerLogger } from "../../logger";
export class Tree {
constructor(treeLoaded) {
this.root = {
id: "root",
component: null,
parent: null,
children: [],
lastFocusedItem: null,
};
this.loadingCounter = 0;
this.treeLoaded = treeLoaded;
}
/**
* Find node by id and removes it from tree
* @public
* @param {String} id of node to remove from tree
*/
findAndRemoveNode(id) {
const node = this.findInTree(id);
if (node) {
this.removeNode(node);
}
}
/**
* Remove node from tree
* @private
* @param {Object} node to remove.
*/
removeNode(node) {
const index = node.parent.children.indexOf(node);
if (index > -1) {
node.parent.children.splice(index, 1);
}
}
/**
* Adds given node to given parent. If parent childtren is undefined or null it
* initialize the array.
* @private
* @param {Object} node to add
* @param {Object} parentNode which node should be added to
*/
addNodeToParentNode(node, parentNode) {
if (!parentNode.children) {
parentNode.children = [];
}
parentNode.children.push(node);
node.parent = parentNode;
}
/**
* Update node with provided data from newNode
* @private
* @param {Object} node to update
* @param {Object} newNode node to take data from
*/
updateNode(currentNode, newNode) {
if (!currentNode.component && newNode.component) {
this.loadingCounter--;
if (this.loadingCounter === 0) {
currentNode.component = newNode.component;
this.treeLoaded(true, currentNode);
}
} else if (currentNode.component && !newNode.component) {
this.loadingCounter++;
if (this.loadingCounter > 0) {
currentNode.component = newNode.component;
this.treeLoaded(false, currentNode);
}
}
currentNode.component = newNode.component;
}
/**
* Creates temporary parent node for given node. add node as a child of parent node.
* @private
* @param {Object} node to add to temporarly created parentNode
* @returns (Object) temporarly created parentNode
*/
createTemporaryParentNodeforNode(node) {
const parentNode = {
id: node.component.props.groupId,
component: null,
parent: this.root,
children: [node],
lastFocusedItem: null,
};
node.parent = parentNode;
this.root.children.push(parentNode);
this.loadingCounter++;
if (this.loadingCounter > 0) {
this.treeLoaded(false, parentNode);
}
return parentNode;
}
/**
* Adds node to tree. If node does not have parent adds this node to root parent.
* If node has parent but parent does not exists in tree structure it create
* temporary parent and add it to root. If node has parent and parent exists in tree,
* it will be added under prefered parent node. If node exist in the tree it will be
* updated with given input node data (parent, component).
* @public
* @param {Object} node to add
*/
addNode(node) {
const existingNode = this.findInTree(node.id);
if (!existingNode) {
if (this.hasGroupID(node)) {
const existingParentNode = this.findInTree(
node.component.props.groupId
);
if (existingParentNode) {
this.addNodeToParentNode(node, existingParentNode);
} else {
this.createTemporaryParentNodeforNode(node);
}
} else {
this.addNodeToParentNode(node, this.root);
}
} else {
focusManagerLogger.debug({
message: `Focusable node with ${
node.id
} id already exist. Replacing with the new one. ${
this.hasGroupID(node)
? "Make sure that there are no id duplicates inside the " +
existingNode.parent.id +
" group."
: ""
}`,
});
if (this.hasGroupID(node)) {
if (existingNode.parent.id !== node.component.props.groupId) {
this.removeNode(existingNode);
this.updateNode(existingNode, node);
const existingParentNode = this.findInTree(
node.component.props.groupId
);
if (existingParentNode) {
this.addNodeToParentNode(existingNode, existingParentNode);
} else {
this.createTemporaryParentNodeforNode(existingNode);
}
} else {
this.updateNode(existingNode, node);
}
} else {
this.updateNode(existingNode, node);
}
}
}
/**
* Check if node has definced component and component has groupId
* @private
* @param {Object} node to check
* @returns True if node has defined groupID
* @returns False if node has NOT defined groupID
*/
hasGroupID(node) {
return node.component && node.component.props.groupId;
}
/**
* Add given node to root node
* @private
* @param {Object} node to add
*/
pushNodeToRoot(node) {
if (node.component) {
const findChildNode = this.findInTree(node.component.props.groupId);
if (findChildNode) {
this.root.children.pop(findChildNode);
node.children.push(findChildNode);
}
}
this.root.children.push(node);
}
/**
* Finds item by given id in tree.
* @public
* @param {String} id of node to search
* @returns founded node or null
*/
findInTree(id) {
const retVal = null;
return this.findInArray(id, this.root.children, retVal);
}
findInArray(id, children, retVal) {
if (!retVal && children) {
retVal = children.find((obj) => obj.id === id);
if (!retVal) {
children.forEach((child) => {
if (child.children) {
retVal = this.findInArray(id, child.children, retVal);
if (retVal) {
return retVal;
}
}
});
} else {
return retVal;
}
}
return retVal;
}
/**
* Gets the IDs of all parent groups for a given node id.
* @public
* @param {String} nodeId - The id of the node to get parent IDs for.
* @returns {Array} An array of parent group IDs or an empty array if the node is not found.
*/
getParentGroupIds(nodeId) {
const parentIds = [];
let currentNode = this.findInTree(nodeId);
if (!currentNode) {
return []; // Node not found, return an empty array
}
// Traverse up the tree, collecting parent IDs
while (currentNode.parent && currentNode.parent.id !== "root") {
parentIds.push(currentNode.parent.id);
currentNode = currentNode.parent;
}
return parentIds;
}
}