Source: public/src/util/index.js

/** @module util */
import $ from 'jquery';
import addUrlParam from './addUrlParam';
import hooks from './hooks';
import rmlOpts from 'rmlopts';
import wpApiSettings from 'wpApiSettings';
import { Icon } from 'react-aiot';
import T from 'i18n-react';
import { TreeNode } from 'react-aiot';
import './wpRfc';

/**
 * Creates a React component (span) with the translated markdown.
 * 
 * @param {string} key The key in rmlOpts.lang
 * @param {object} [params] The parameters
 * @param {object|string('maxWidth')} [spanWrapperProps] Wraps an additinal span wrapper with custom attributes
 * @see https://github.com/alexdrel/i18n-react
 * @returns {React.Element} Or null if key not found
 */
export function i18n(key, params, spanWrapperProps) {
    if (rmlOpts && rmlOpts.lang && rmlOpts.lang[key]) { // @TODO rmlOpts.lang check remove
        const span = <T.span text={rmlOpts.lang[key]} {...params} />;
        
        // Predefined span wrapper props
        if (typeof spanWrapperProps === 'string') {
            switch (spanWrapperProps) {
                case 'maxWidth':
                    spanWrapperProps = { style: { display: 'inline-block', maxWidth: 200 } };
                    break;
                default: break;
            }
        }
        
        return spanWrapperProps ? <span {...spanWrapperProps}>{ span }</span> : span;
    }
    return key;
}

/**
 * Get URL parameter of current url.
 * 
 * @param {string} name The parameter name
 * @param {string} [url=window.location.href]
 * @returns {string|null}
 */
export function urlParam(name, url = window.location.href){
	const results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(url);
	return results && results[1] || null;
}

/**
 * Execute a jQuery request with X-WP-Nonce header.
 * 
 * @param {string} url The url appended to ".../wp-json/realmedialibrary/v1/"
 * @param {object} [settings] The options for jQuery.ajax
 * @param {string} [url='realmedialibrary/v1'] The API namespace
 * @returns Result of jQuery.ajax
 */
export function ajax(url, settings = {}, urlNamespace = 'realmedialibrary/v1') {
    const apiUrlRoot = wpApiSettings.root, lang = urlParam('lang', apiUrlRoot),
        apiUrl = apiUrlRoot.split('?')[0];
    let useUrl = apiUrl + urlNamespace + '/' + url;
    
    if (lang) {
        useUrl = addUrlParam(useUrl, 'lang', lang.replace('/', ''));
    }
    
    return $.ajax(useUrl, $.extend(true, settings, {
        headers: {
            'X-WP-Nonce': wpApiSettings.nonce
        }
    }));
}

/**
 * Icon showing a opened folder.
 * 
 * @type React.Element
 */
export const ICON_OBJ_FOLDER_OPEN = <Icon type="folder-open" />;

/**
 * Icon showing a closed folder.
 * 
 * @type React.Element
 */
export const ICON_OBJ_FOLDER_CLOSED = <Icon type="folder" />;

/**
 * Icon showing a home icon for Unorganized.
 * 
 * @type React.Element
 */
export const ICON_OBJ_FOLDER_ROOT = <Icon type="home" />;

/**
 * Icon showing a collection.
 * 
 * @type React.Element
 */
export const ICON_OBJ_FOLDER_COLLECTION = <i className="rmlicon-collection" />;

/**
 * Icon showing a gallery.
 * 
 * @type React.Element
 */
export const ICON_OBJ_FOLDER_GALLERY = <i className="rmlicon-gallery" />;

/**
 * Handle tree node defaults for loaded folder items and new items.
 * 
 * @param {object[]} folders The folders
 * @returns object[]
 */
export function applyNodeDefaults(arr) {
    return arr.map(({
        id,
        name,
        cnt,
        children,
        contentCustomOrder,
        lastOrderBy,
        orderAutomatically,
        ...rest
    }) => (node => {
        // Update node
        switch (node.properties.type) {
            case 0:
                node.iconActive = ICON_OBJ_FOLDER_OPEN;
                break;
            case 1:
                node.icon = ICON_OBJ_FOLDER_COLLECTION;
                break;
            case 2:
                node.icon = ICON_OBJ_FOLDER_GALLERY;
                break;
            default: break;
        }
        
        /**
         * A tree node is fetched from the server and should be prepared
         * for the {@link module:store/TreeNode~TreeNode} class.
         * 
         * @event module:util/hooks#tree/node
         * @param {object} node The node object
         */
        hooks.call('tree/node', [node]);
        return node;
    })($.extend({}, TreeNode.defaultProps, { // Default node
        id,
        title: name,
        icon: ICON_OBJ_FOLDER_CLOSED,
        count: cnt,
        childNodes: children ? applyNodeDefaults(children) : [],
        properties: rest,
        className: {},
        contentCustomOrder,
        lastOrderBy: !!lastOrderBy ? lastOrderBy : "",
        orderAutomatically: !!orderAutomatically,
        $visible: true
    })));
}

/**
 * Execute the REST query to fetch the category tree.
 * 
 * @param {object} [settings] Additional options for jQuery.ajax
 * @returns {object} The original AJAX result and the tree result prepared for AIO
 */
export async function fetchTree(settings) {
    const { tree, ...rest } = await ajax('tree', settings);
    return { tree: applyNodeDefaults(tree), ...rest };
}

/**
 * Allows you to find an object path.
 * 
 * @param {object} obj The object
 * @param {string} path The path
 * @returns {mixed|undefined}
 */
export function findDeep(obj, path) {
    const paths = path.split('.');
    let current = obj;
    for (var i = 0; i < paths.length; ++i) {
        if (current[paths[i]] == undefined) {
            return undefined;
        } else {
            current = current[paths[i]];
        }
    }
    return current;
}

/**
 * Transform bytes to humand readable string.
 * 
 * @param {int} bytes The bytes
 * @returns {string}
 * @see http://stackoverflow.com/questions/10420352/converting-file-size-in-bytes-to-human-readable
 */
export function humanFileSize(bytes, si = true) {
    const thresh = si ? 1000 : 1024;
    if (Math.abs(bytes) < thresh) {
        return bytes + ' B';
    }
    const units = si
        ? ['kB','MB','GB','TB','PB','EB','ZB','YB']
        : ['KiB','MiB','GiB','TiB','PiB','EiB','ZiB','YiB'];
    let u = -1;
    do {
        bytes /= thresh;
        ++u;
    } while(Math.abs(bytes) >= thresh && u < units.length - 1);
    return bytes.toFixed(1)+' '+units[u];
}

/**
 * Transform seconds to readable HH:mm:ss.
 * 
 * @param {int} totalSec The seconds
 * @returns {string}
 */
export function secondsFormat(totalSec) {
    const hours = Math.floor(totalSec / 3600),
        minutes = Math.floor((totalSec - (hours * 3600)) / 60),
        seconds = totalSec - (hours * 3600) - (minutes * 60);
    return (hours < 10 ? '0' + hours : hours) + ':' + (minutes < 10 ? '0' + minutes : minutes) + ':' + (seconds  < 10 ? '0' + seconds : seconds);
}

/**
 * Export Data URI to blob instance.
 * 
 * @param {string} sUri
 * @returns {Blob}
 */
export function dataUriToBlob(sUri) {
	// convert base64/URLEncoded data component to raw binary data held in a string
	let byteString;
	if (sUri.split(",")[0].indexOf("base64") >= 0) {
		byteString = window.atob(sUri.split(',')[1]);
	} else {
		byteString = unescape(sUri.split(',')[1]);
	}

	// separate out the mime component
	const type = sUri.split(',')[0].split(':')[1].split(';')[0];

	// write the bytes of the string to a typed array
	const ia = new Uint8Array(byteString.length);
	for (let i = 0; i < byteString.length; i++) {
		ia[i] = byteString.charCodeAt(i);
	}

	return new window.Blob([ia], { type });
}

export {
    /**
     * @type module:util/addUrlParam
     */
    addUrlParam,
    
    /**
     * @type module:util/hooks
     */
    hooks,
    
    /**
     * The localized Real Media Library script object.
     * 
     * @type object
     */
    rmlOpts
};