Source: node_modules/react-aiot/src/util/index.js

/** @module react-aiot/util */
import { handleSortableTreeInit, handleSortableTree, handleSortableTreeWillUpdate } from './injectSortable';
import { handleSearch, handleSearchBlur, handleSearchClose, handleSearchKeyDown } from './injectSearchable';

export { handleSortableTreeInit, handleSortableTree, handleSortableTreeWillUpdate };
export { handleSearch, handleSearchBlur, handleSearchClose, handleSearchKeyDown };

/**
 * Create a unique id.
 * 
 * @returns {string}
 */
export function uuid() {
    function s4() {
        return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
    }
    return "aiot-" + s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
}

/**
 * Helper function to add a class to a DOMElement.
 * 
 * @param {DOMElement} el The element
 * @param {string} className The class names
 */
export function addClass(el, className) {
    if (el.classList)
        el.classList.add(className);
    else
        el.className += ' ' + className;
}

/**
 * Helper function to remove a class to a DOMElement.
 * 
 * @param {DOMElement} el The element
 * @param {string} className The class names
 */
export function removeClass(el, className) {
    if (el.classList)
        el.classList.remove(className);
    else
        el.className = el.className.replace(new RegExp('(^|\\b)' + className.split(' ').join('|') + '(\\b|$)', 'gi'), ' ');
}

/**
 * Get parent of a element.
 * 
 * @see https://gist.github.com/ziggi/2f15832b57398649ee9b
 */
export function parents(elem, selector) {
	var elements = [];
	var ishaveselector = selector !== undefined;
 
	while ((elem = elem.parentElement) !== null) {
		if (elem.nodeType !== window.Node.ELEMENT_NODE) {
			continue;
		}
 
		if (!ishaveselector || elem.matches(selector)) {
			elements.push(elem);
		}
	}
 
	return elements;
}

/**
 * Create a helper class to store compact JSON data in local storage if supported.
 * 
 * @param {int} id Unique id for the storage key. Prefixed with "AIOT-"
 */
export class Storage {
    constructor(id) {
        this.id = id;
        this.cache = undefined;
    }
    
    /**
     * Set the item for a JSON path for this local storage object.
     * 
     * @param {string} path The path
     * @param {*} value The stringify-able value
     */
    setItem(path, value) {
        let collection = this.getItem();
        this.setObjectPath(collection, path, value);
        window.localStorage.setItem("AIOT-" + this.id, JSON.stringify(collection));
        this.cache = undefined;
    }
    
    /**
     * Get the item for a JSON path for this local storage object.
     * 
     * @param {string} path The path
     * @returns {*}
     */
    getItem(path) {
        if (!this.cache) {
            this.cache = JSON.parse(window.localStorage.getItem('AIOT-' + this.id) || '{}');
        }
        return path ? this.getObjectPath(this.cache, path) : this.cache;
    }
    
    /**
     * Get the item for a JSON path for the passed object.
     * 
     * @param {object} object The object
     * @param {string} path The path
     * @returns {*}
     */
    getObjectPath(object, path) {
        let names = (path || '').split('.');
		for (let i = 0; object && i < names.length; i++) {
			object = object[names[i]];
		}
		return object;
    }
    
    /**
     * Set the item for a JSON path for the passed object.
     * 
     * @param {object} object The object
     * @param {string} path The path
     * @param {*} value The stringify-able value
     */
    setObjectPath(object, path, value) {
        let l, names = (path || '').split('.');
		if ( ( l = names.length ) > 0 ) {
			for (var i = 0; object && i < l - 1; i++) {
				if (!object[names[i]] ) {
					object[names[i]] = {};
				}
				object = object[names[i]];
			}
			object[names[l - 1]] = value;
		}
    }
}

/**
 * If true the current browser supports local storage.
 * 
 * @type boolean
 */
export const SUPPORTS_LOCAL_STORAGE = (function() {
    var test = 'test';
    try {
        var _localStorage = window["localStorage"];
        _localStorage.setItem(test, test);
        _localStorage.removeItem(test);
        return true;
    } catch(e) {
        return false;
    }
})();

/**
 * An object with the touchable event names for mouseup, mousedown, mousemove.
 * 
 * @type object
 */
export const touchable = 'ontouchstart' in window || window.navigator.maxTouchPoints
    ? { touch: true, up: "touchend", down: "touchstart", move: "touchmove" }
    : { touch: false, up: "mouseup", down: "mousedown", move: "mousemove" };

/**
 * Allows to inject a virtual CSS style sheet.
 * 
 * @param {string} id The unique id for the style. If already available it gets overriden by the new style rules
 * @param {string} style The style rules
 * @returns {DOMElement|boolean} If successful it returns the script-DOMElement and false if failure
 */
export function injectStyle(id, style) {
    try {
        // Remove old element
        const script = document.getElementById(id);
        script && script.remove();
        
        // Create element
        const elem = document.createElement('div');
        elem.id = id;
        elem.style.display = 'none';
        elem.innerHTML = '&shy;<style>' + style + '</style>';
        document.body.appendChild(elem);
        return elem;
    } catch (e) {
        return false;
    }
}

/**
 * Resizes a given container with calc(100% - width).
 * 
 * @param {string} containerId The container ID
 * @param {string} oppositeId The opposite ID of container
 * @param {int} width Width in px
 */
export function resizeOpposite(containerId, oppositeId, width) {
    return injectStyle(containerId + '-styleOpposite',
        '#' + oppositeId + '{ width: -webkit-calc(100% - ' + width + 'px);width: -moz-calc(100% - ' + width + 'px);width: calc(100% - ' + width + 'px); }');
}

/**
 * Allows to update a tree node item by id.
 * 
 * @param {string|id} id The id of the tree node
 * @param {object[]} tree The {@link module:react-aiot/components/TreeNode|TreeNode} properties to modify
 * @param {module:react-aiot/util#updateTreeItemByIdCallback} callback 
 * @param {module:react-aiot/util#updateTreeItemByIdSetState} setState 
 */
export function updateTreeItemById(id, tree, callback, setState) {
    const newTree = tree;
    for (let i = 0; i < newTree.length; i++) {
        if (newTree[i].id === id) {
            /**
             * This function is called when the tree node is found in updateTreeItemById.
             * You can then modify the node.
             * 
             * @callback module:react-aiot/util#updateTreeItemByIdCallback
             * @param {object} node {@link module:react-aiot/components/TreeNode|TreeNode}
             */
            callback(newTree[i]);
            break;
        }else if (newTree[i].childNodes) {
            updateTreeItemById(id, newTree[i].childNodes, callback);
        }
    }
    /**
     * This function is called when the tree node is successfully modified by your
     * callback and can no be setted as state in your component.
     * 
     * @callback module:react-aiot/util#updateTreeItemByIdSetState
     * @param {object[]} tree The new {@link module:react-aiot/components/TreeNode|TreeNode} properties
     */
    setState && newTree[0] && setState(newTree);
}

function _findTreeItemById(id, tree = [], callback, parentId = 0) {
    for (let i = 0; i < tree.length; i++) {
        if (tree[i].id === id) {
            callback(tree[i], parentId);
            break;
        }else if (tree[i].childNodes) {
            _findTreeItemById(id, tree[i].childNodes, callback, tree[i].id);
        }
    }
}

/**
 * Get the parent id of a tree node.
 * 
 * @param {string|int} id The id which you want to fetch the parent of 
 * @param {object[]} tree {@link module:react-aiot/components/TreeNode|TreeNode} properties
 * @param {string|int} [parentId=0] If the item has no parent this id is returned
 * @returns {string|int} Can also return undefined if not found
 */
export function getTreeParentById(id, tree, parentId = 0) {
    let found;
    _findTreeItemById(id, tree, (node, parentId) => (found = parentId), parentId);
    return found;
}

/**
 * Get the tree node by id.
 * 
 * @param {string|int} id The id
 * @param {object[]} tree {@link module:react-aiot/components/TreeNode|TreeNode} properties
 * @returns {object}
 */
export function getTreeItemById(id, tree) {
    let found;
    _findTreeItemById(id, tree, node => (found = node));
    return found;
}

/**
 * Builds an ordered parent pairs array.
 * 
 * @param {object[]} [tree=[]] The tree
 * @paremt {string|int} [rootId=0] The root id
 * @returns {object[]} With fid (node id) and the given pid (parent id)
 */
export function buildOrderedParentPairs(tree = [], rootId = 0) {
    const pairs = [], mapper = function(node) {
        pairs.push({ fid: node.id, pid: this });
        node.childNodes && node.childNodes.forEach(mapper.bind(node.id));
    };
    tree.forEach(mapper.bind(rootId));
    return pairs;
}

/**
 * Checks if two flat arrays are equal.
 */
export function flatArrayEqual(arr1, arr2) {
    if(arr1.length !== arr2.length)
        return false;
    for(let i = arr1.length; i--;) {
        if(arr1[i] !== arr2[i])
            return false;
    }
    return true;
}

/**
 * Shim layer with setTimeout fallback
 * 
 * @see https://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
 */
(function() {
    var lastTime = 0;
    var vendors = ['webkit', 'moz'];
    for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
        window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
        window.cancelAnimationFrame =
          window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];
    }

    if (!window.requestAnimationFrame)
        window.requestAnimationFrame = function(callback, element) {
            var currTime = new Date().getTime();
            var timeToCall = Math.max(0, 16 - (currTime - lastTime));
            var id = window.setTimeout(function() { callback(currTime + timeToCall); },
              timeToCall);
            lastTime = currTime + timeToCall;
            return id;
        };

    if (!window.cancelAnimationFrame)
        window.cancelAnimationFrame = function(id) {
            clearTimeout(id);
        };
}());