1474 lines
		
	
	
		
			66 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
			
		
		
	
	
			1474 lines
		
	
	
		
			66 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
<!DOCTYPE html>
 | 
						|
<html lang="es">
 | 
						|
    <head>
 | 
						|
        <title data-i18n="opo_quiz_tiny">OpoQuizTiny</title>
 | 
						|
        <meta http-equiv="content-type" content="text/html;charset=utf-8" />
 | 
						|
        <meta name="viewport" content="width=device-width, initial-scale=1" />
 | 
						|
        <meta charset="UTF-8" />
 | 
						|
 | 
						|
        <script data-module="data" data-type="text/javascript" data-lagnuage="ECMAScript 2015" charset="utf-8">
 | 
						|
            "use strict";
 | 
						|
 | 
						|
            /** @type {Object.<string, any|null>} */
 | 
						|
            const SETTINGS = {
 | 
						|
                /** @type {string} */
 | 
						|
                position : "body"
 | 
						|
            };
 | 
						|
 | 
						|
        </script>
 | 
						|
 | 
						|
        <script data-module="styles" data-type="text/javascript" data-lagnuage="ECMAScript 2015" charset="utf-8">
 | 
						|
            "use strict";
 | 
						|
 | 
						|
            /**
 | 
						|
             * @class SASS
 | 
						|
             * @constuctor
 | 
						|
             * @returns {void}
 | 
						|
             * @access public
 | 
						|
             * @static
 | 
						|
             */
 | 
						|
            const SASS = (function(){
 | 
						|
                
 | 
						|
                /**
 | 
						|
                 * @constructs SASS
 | 
						|
                 * @returns {void}
 | 
						|
                 * @access private
 | 
						|
                 * @static
 | 
						|
                 */
 | 
						|
                const SASS = function(){};
 | 
						|
 | 
						|
                /**
 | 
						|
                 * @param {!string} string
 | 
						|
                 * @returns {string}
 | 
						|
                 * @access public
 | 
						|
                 * @static
 | 
						|
                 */
 | 
						|
                SASS.to_kebab = string => string.replace(/([A-Z]+)|([^a-z0-9]+)/g, (_, upper, special) => upper ? "-" + upper.toLowerCase() : "-");
 | 
						|
 | 
						|
                /**
 | 
						|
                 * @param {?any} item
 | 
						|
                 * @returns {boolean}
 | 
						|
                 * @access public
 | 
						|
                 * @static
 | 
						|
                 */
 | 
						|
                SASS.is_css_block = item => item && item.constructor == Object;
 | 
						|
                
 | 
						|
                /**
 | 
						|
                 * @param {!Object.<string, any|null>} data
 | 
						|
                 * @returns {void}
 | 
						|
                 * @access public
 | 
						|
                 * @static
 | 
						|
                 */
 | 
						|
                SASS.set = data => {
 | 
						|
 | 
						|
                    /** @type {HTMLStyleElement} */
 | 
						|
                    let style = document.querySelector("head>style");
 | 
						|
                    
 | 
						|
                    if(!style){
 | 
						|
                        style = document.querySelector("head").appendChild(document.createElement("style"));
 | 
						|
                        Object.entries({
 | 
						|
                            data_type : "text/css;charset=utf-8", 
 | 
						|
                            data_language : "CSS3",
 | 
						|
                            charset : "utf-8"
 | 
						|
                        }).forEach(([key, value]) => style.setAttribute(SASS.to_kebab(key), (
 | 
						|
                            value === null ? "none" : 
 | 
						|
                        value)));
 | 
						|
                    };
 | 
						|
 | 
						|
                    style.appendChild(document.createTextNode(SASS.build(data).join("")));
 | 
						|
 | 
						|
                };
 | 
						|
 | 
						|
                /**
 | 
						|
                 * @method build
 | 
						|
                 * @param {!Object.<string, any|null>} data
 | 
						|
                 * @returns {Array<string>}
 | 
						|
                 * @access public
 | 
						|
                 * @static
 | 
						|
                 */
 | 
						|
                SASS.build = data => {
 | 
						|
                    
 | 
						|
                    /** @type {Arrya.<string>} */
 | 
						|
                    const childs = [];
 | 
						|
                    /** @type {string} */
 | 
						|
                    let css = ``;
 | 
						|
 | 
						|
                    Object.entries(data).forEach(([key, value]) => {
 | 
						|
                        if(SASS.is_css_block(value))
 | 
						|
                            childs.push(`\n\n${SASS.to_kebab(key)} {\n${value[0]}}`, ...value.slice(1));
 | 
						|
                        else 
 | 
						|
                            css += `\n    ${SASS.to_kebab(key)} : ${value};`;
 | 
						|
                    });
 | 
						|
 | 
						|
                    return [css, ...childs];
 | 
						|
                };
 | 
						|
 | 
						|
                return SASS
 | 
						|
            })();
 | 
						|
 | 
						|
            SASS.set({});
 | 
						|
 | 
						|
        </script>
 | 
						|
 | 
						|
        <script data-module="common" data-type="text/javascript" data-lagnuage="ECMAScript 2015" charset="utf-8">
 | 
						|
            "use strict";
 | 
						|
            
 | 
						|
            /**
 | 
						|
             * @class Utils
 | 
						|
             * @constructor
 | 
						|
             * @returns {void}
 | 
						|
             * @access public
 | 
						|
             * @static
 | 
						|
             */
 | 
						|
            const Utils = (function(){
 | 
						|
 | 
						|
                /**
 | 
						|
                 * @callback utils_execute_callback
 | 
						|
                 * @param {...(any|null)} [parameters] 
 | 
						|
                 * @returns {any|null}
 | 
						|
                 */
 | 
						|
 | 
						|
                /**
 | 
						|
                 * @callback utils_upload_callback
 | 
						|
                 * @param {...Object.<string, any|null>} data 
 | 
						|
                 * @returns {void}
 | 
						|
                 */
 | 
						|
 | 
						|
                /**
 | 
						|
                 * @callback utils_preload_callback
 | 
						|
                 * @param {?HTMLElement} item 
 | 
						|
                 * @param {!number} error 
 | 
						|
                 * @param {!boolean} asynchronous 
 | 
						|
                 * @returns {void}
 | 
						|
                 */
 | 
						|
 | 
						|
                /**
 | 
						|
                 * @constructs Utils
 | 
						|
                 * @returns {void}
 | 
						|
                 * @access private
 | 
						|
                 * @static
 | 
						|
                 */
 | 
						|
                const Utils = function(){};
 | 
						|
 | 
						|
                /**
 | 
						|
                 * @param {?any} item
 | 
						|
                 * @returns {boolean}
 | 
						|
                 * @access public
 | 
						|
                 * @static
 | 
						|
                 */
 | 
						|
                Utils.is_string = item => typeof item == "string";
 | 
						|
 | 
						|
                /**
 | 
						|
                 * @param {?any} item
 | 
						|
                 * @returns {boolean}
 | 
						|
                 * @access public
 | 
						|
                 * @static
 | 
						|
                 */
 | 
						|
                Utils.is_key = item => Utils.is_string(item) && /^[a-z_][a-z0-9_]*$/.test(item);
 | 
						|
 | 
						|
                /**
 | 
						|
                 * @param {?any} item
 | 
						|
                 * @returns {boolean}
 | 
						|
                 * @access public
 | 
						|
                 * @static
 | 
						|
                 */
 | 
						|
                Utils.is_array = item => item instanceof Array;
 | 
						|
 | 
						|
                /**
 | 
						|
                 * @param {?any} item
 | 
						|
                 * @returns {boolean}
 | 
						|
                 * @access public
 | 
						|
                 * @static
 | 
						|
                 */
 | 
						|
                Utils.is_dictionary = item => item && item.constructor == Object;
 | 
						|
 | 
						|
                /**
 | 
						|
                 * @param {?any} item
 | 
						|
                 * @returns {boolean}
 | 
						|
                 * @access public
 | 
						|
                 * @static
 | 
						|
                 */
 | 
						|
                Utils.is_function = item => typeof item == "function";
 | 
						|
 | 
						|
                /**
 | 
						|
                 * @param {?any} item
 | 
						|
                 * @returns {boolean}
 | 
						|
                 * @access public
 | 
						|
                 * @static
 | 
						|
                 */
 | 
						|
                Utils.is_html_item = item => item && (item.tagName || item.nodeType);
 | 
						|
 | 
						|
                /**
 | 
						|
                 * @param {?any} item
 | 
						|
                 * @returns {boolean}
 | 
						|
                 * @access public
 | 
						|
                 * @static
 | 
						|
                 */
 | 
						|
                Utils.is_null_or_undefined = item => item === undefined || item === null;
 | 
						|
 | 
						|
                /**
 | 
						|
                 * @param {!utils_execute_callback} callback
 | 
						|
                 * @param {...(any|null)} [parameters] 
 | 
						|
                 * @returns {any|null}
 | 
						|
                 * @access public
 | 
						|
                 * @static
 | 
						|
                 */
 | 
						|
                Utils.execute = (callback, ...parameters) => Utils.is_function(callback) ? callback(...parameters) : null;
 | 
						|
 | 
						|
                /**
 | 
						|
                 * @param {...(any|null)} items
 | 
						|
                 * @returns {Array.<string>}
 | 
						|
                 * @access public
 | 
						|
                 * @static
 | 
						|
                 */
 | 
						|
                Utils.get_strings = (...items) => items.reduce((strings, item) => (
 | 
						|
                    Utils.is_string(item) ? strings.concat([item]) : 
 | 
						|
                    Utils.is_array(item) ? strings.concat(Utils.get_strings(...item)) : 
 | 
						|
                strings), []);
 | 
						|
 | 
						|
                /**
 | 
						|
                 * @param {...(any|null)} items
 | 
						|
                 * @returns {Array.<string>}
 | 
						|
                 * @access public
 | 
						|
                 * @static
 | 
						|
                 */
 | 
						|
                Utils.get_keys = (...items) => {
 | 
						|
                    
 | 
						|
                    /** @type {Array.<string>} */
 | 
						|
                    const keys = [];
 | 
						|
 | 
						|
                    items.forEach(subitem => {
 | 
						|
                        if(Utils.is_key(subitem))
 | 
						|
                            keys.push(subitem);
 | 
						|
                        else if(Utils.is_array(subitem))
 | 
						|
                            keys.push(...Utils.get_keys(...subitem));
 | 
						|
                    });
 | 
						|
 | 
						|
                    return keys;
 | 
						|
                };
 | 
						|
 | 
						|
                /**
 | 
						|
                 * @param {...(any|null)} items
 | 
						|
                 * @returns {Array.<Object.<string, any|null>>}
 | 
						|
                 * @access public
 | 
						|
                 * @static
 | 
						|
                 */
 | 
						|
                Utils.get_dictionaries = (...items) => items.reduce((dictionaries, item) => (
 | 
						|
                    Utils.is_dictionary(item) ? dictionaries.concat([item]) : 
 | 
						|
                    Utils.is_array(item) ? dictionaries.concat(Utils.get_dictionary(...item)) : 
 | 
						|
                dictionaries), []);
 | 
						|
 | 
						|
                /**
 | 
						|
                 * @param {...(any|null)} items
 | 
						|
                 * @returns {Object.<string, any|null>}
 | 
						|
                 * @access public
 | 
						|
                 * @static
 | 
						|
                 */
 | 
						|
                Utils.get_dictionary = (...items) => items.reduce((dictionary, item) => (
 | 
						|
                    Utils.is_dictionary(item) ? Object.assign(dictionary, item) : 
 | 
						|
                    Utils.is_array(item) ? Object.assign(dictionary, Utils.get_dictionary(...item)) : 
 | 
						|
                dictionary), {});
 | 
						|
 | 
						|
                /**
 | 
						|
                 * @param {!(string|Array.<string>)} keys
 | 
						|
                 * @param {!(Object.<string, any|null>|Array.<any|null>} inputs
 | 
						|
                 * @param {?any} [_default = null]
 | 
						|
                 * @returns {any|null}
 | 
						|
                 * @access public
 | 
						|
                 * @static
 | 
						|
                 */
 | 
						|
                Utils.get_value = (keys, inputs, _default = null) => {
 | 
						|
 | 
						|
                    /** @type {number} */
 | 
						|
                    const l = (inputs = Utils.get_dictionaries(inputs)).length, 
 | 
						|
                          /** @type {number} */
 | 
						|
                          m = (keys = Utils.get_keys(keys)).length;
 | 
						|
 | 
						|
                    for(let i = 0; i < l; i ++)
 | 
						|
                        for(let j = 0; j < m; j ++)
 | 
						|
                            if(inputs[j] && inputs[j][keys[i]] !== undefined)
 | 
						|
                                return inputs[j][keys[i]];
 | 
						|
                    return _default;
 | 
						|
                };
 | 
						|
 | 
						|
                /**
 | 
						|
                 * @param {!utils_upload_callback} callback
 | 
						|
                 * @param {?(Object.<string, any|null>|Array.<any|null>)} [options = null] 
 | 
						|
                 * @returns {void}
 | 
						|
                 * @access public
 | 
						|
                 * @static
 | 
						|
                 */
 | 
						|
                Utils.upload = (callback, options = null) => {
 | 
						|
 | 
						|
                    /** @type {HTMLInputElement} */
 | 
						|
                    const input = document.createElement("input");
 | 
						|
 | 
						|
                    Object.entries({
 | 
						|
                        type : "file", 
 | 
						|
                        accept : Utils.get_value("accept", options, "*/*"), 
 | 
						|
                        multiple : Utils.get_value("multiple", options, false)
 | 
						|
                    }).forEach(([key, value]) => {
 | 
						|
                        input.setAttribute(key, value);
 | 
						|
                    });
 | 
						|
 | 
						|
                    input.addEventListener("change", (event) => {
 | 
						|
 | 
						|
                        /* @type {number} */
 | 
						|
                        let loaded = 0;
 | 
						|
                        /** @type {number} */
 | 
						|
                        const l = event.target.files.length, 
 | 
						|
                              /** @type {Array.<Object.<string, any|null>>} */
 | 
						|
                              data = [], 
 | 
						|
                              /**
 | 
						|
                               * @param {!number} i
 | 
						|
                               * @param {!Object.<string, any|null>} response
 | 
						|
                               * @returns {void}
 | 
						|
                               */
 | 
						|
                              end = (i, response) => {
 | 
						|
                                  data[i] = response;
 | 
						|
                                  if(++ loaded == l)
 | 
						|
                                      callback(...data);
 | 
						|
                              };
 | 
						|
 | 
						|
                        [...event.target.files].forEach((file, i) => {
 | 
						|
 | 
						|
                            /** @type {FileReader} */
 | 
						|
                            const reader = new FileReader();
 | 
						|
 | 
						|
                            reader.onload = event => {
 | 
						|
                                end(i, {
 | 
						|
                                    name : file.name, 
 | 
						|
                                    mime : file.type, 
 | 
						|
                                    last_modified : file.lastModified, 
 | 
						|
                                    data : event.target.result
 | 
						|
                                });
 | 
						|
                            };
 | 
						|
                            reader.readAsText(file);
 | 
						|
 | 
						|
                        });
 | 
						|
 | 
						|
                    });
 | 
						|
 | 
						|
                    input.click();
 | 
						|
 | 
						|
                };
 | 
						|
 | 
						|
                /**
 | 
						|
                 * @param {!string} data
 | 
						|
                 * @returns {?(Object.<string, any|null>|Array.<any|null>)}
 | 
						|
                 * @access public
 | 
						|
                 * @static
 | 
						|
                 */
 | 
						|
                Utils.json_decode = data => {
 | 
						|
                    try{
 | 
						|
                        return JSON.parse(data);
 | 
						|
                    }catch(exception){};
 | 
						|
                    return null;
 | 
						|
                };
 | 
						|
 | 
						|
                /**
 | 
						|
                 * @param {!(string|HTMLElement)} item
 | 
						|
                 * @param {!utils_preload_callback} callback
 | 
						|
                 * @param {?(Object.<string, any|null>|Array.<any|null>)} [options = null] 
 | 
						|
                 * @returns {[HTMLElement|null, number, boolean]}
 | 
						|
                 * @access public
 | 
						|
                 * @static
 | 
						|
                 */
 | 
						|
                Utils.preload = (selector, callback, options = null) => {
 | 
						|
 | 
						|
                    /** @type {number} */
 | 
						|
                    let error = (
 | 
						|
                            !Utils.is_function(callback) ? 1 << 1 : 
 | 
						|
                        0), 
 | 
						|
                        /** @type {boolean} */
 | 
						|
                        asynchronous = false, 
 | 
						|
                        /** @type {HTMLElement|null} */
 | 
						|
                        item = null;
 | 
						|
 | 
						|
                    if(!error){
 | 
						|
                        if(Utils.is_string(selector)){
 | 
						|
                            if(selector){
 | 
						|
                                
 | 
						|
                                try{
 | 
						|
                                    if(item = document.querySelector(selector))
 | 
						|
                                        Utils.execute(callback, item, error, asynchronous);
 | 
						|
                                }catch(exception){
 | 
						|
                                    error |= 1 << 4;
 | 
						|
                                };
 | 
						|
 | 
						|
                                if(asynchronous = !error && !item){
 | 
						|
 | 
						|
                                    /** @type {number} */
 | 
						|
                                    const date = Date.now(), 
 | 
						|
                                          /** @type {number} */
 | 
						|
                                          timeout = Utils.get_value("timeout", options, 2000);
 | 
						|
                                    /** @type {number} */
 | 
						|
                                    let preload = setInterval(() => {
 | 
						|
                                        if(item = document.querySelector(selector)){
 | 
						|
                                            clearInterval(preload);
 | 
						|
                                            Utils.execute(callback, item, error, asynchronous);
 | 
						|
                                        }else if(Date.now() - date > timeout){
 | 
						|
                                            clearInterval(preload);
 | 
						|
                                            error |= 1 << 5;
 | 
						|
                                            Utils.execute(callback, item, error, asynchronous);
 | 
						|
                                        };
 | 
						|
                                    }, Utils.get_value("timer", options, 100));
 | 
						|
 | 
						|
                                };
 | 
						|
 | 
						|
                            }else 
 | 
						|
                                error |= 1 << 3;
 | 
						|
                        }else if(Utils.is_html_item(selector))
 | 
						|
                            Utils.execute(callback, item = selector, error, asynchronous);
 | 
						|
                        else 
 | 
						|
                            error |= 1 << 2;
 | 
						|
 | 
						|
                    };
 | 
						|
 | 
						|
                    return [item, error, asynchronous];
 | 
						|
                };
 | 
						|
 | 
						|
                /**
 | 
						|
                 * @param {!string} string
 | 
						|
                 * @returns {string}
 | 
						|
                 * @access public
 | 
						|
                 * @static
 | 
						|
                 */
 | 
						|
                Utils.to_kebab = string => string.replace(/([A-Z]+)|([^a-z0-9]+)/g, (_, upper, special) => upper ? "-" + upper.toLowerCase() : "-");
 | 
						|
 | 
						|
                /**
 | 
						|
                 * @param {!HTMLElement} item
 | 
						|
                 * @param {!Object.<string, any>} attributes
 | 
						|
                 * @returns {void}
 | 
						|
                 * @access public
 | 
						|
                 * @static
 | 
						|
                 */
 | 
						|
                Utils.attributes = (item, attributes) => {
 | 
						|
                    Object.entries(attributes).forEach(([key, value]) => {
 | 
						|
 | 
						|
                        if(/^on[_\_]?/i.test(key)){
 | 
						|
                            item.addEventListener(key.replace(/^on[_\_]?|[_\-]+/gi, ""), event => {
 | 
						|
                                value(item, event);
 | 
						|
                            });
 | 
						|
                            return;
 | 
						|
                        };
 | 
						|
 | 
						|
                        key = Utils.to_kebab(key);
 | 
						|
 | 
						|
                        if(Utils.is_null_or_undefined(value))
 | 
						|
                            item.hasAttribute(key) && item.removeAttribute(key);
 | 
						|
                        else
 | 
						|
                            item.setAttribute(key, value);
 | 
						|
 | 
						|
                    });
 | 
						|
                };
 | 
						|
 | 
						|
                /**
 | 
						|
                 * @param {!HTMLElement} item
 | 
						|
                 * @param {...[string, Object.<string, any>|null, Array.<any|null>|null]} structure
 | 
						|
                 * @returns {Array.<HTMLElement>}
 | 
						|
                 * @access public
 | 
						|
                 * @static
 | 
						|
                 */
 | 
						|
                Utils.html = (item, ...structure) => {
 | 
						|
 | 
						|
                    /** @type {Array.<HTMLElement>} */
 | 
						|
                    const items = [];
 | 
						|
 | 
						|
                    structure.forEach(subitem => {
 | 
						|
                        if(Utils.is_string(subitem))
 | 
						|
                            item.innerHTML += subitem;
 | 
						|
                        else if(Utils.is_html_item(subitem))
 | 
						|
                            items.push(item.appendChild(subitem));
 | 
						|
                        else if(Utils.is_array(subitem)){
 | 
						|
 | 
						|
                            /** @type {[string, Object.<string, any>|null, Array.<any|null>|null]} */
 | 
						|
                            const [tag, attributes, children] = subitem.concat([null, null]), 
 | 
						|
                                  /** @type {HTMLElement} */
 | 
						|
                                  html_item = item.appendChild(document.createElement(tag));
 | 
						|
 | 
						|
                            Utils.is_dictionary(attributes) && Utils.attributes(html_item, attributes);
 | 
						|
                            if(Utils.is_array(children))
 | 
						|
                                Utils.html(html_item, ...children);
 | 
						|
                            else if(Utils.is_string(children))
 | 
						|
                                html_item.innerHTML = children;
 | 
						|
 | 
						|
                            items.push(html_item);
 | 
						|
 | 
						|
                        };
 | 
						|
                    });
 | 
						|
 | 
						|
                    return items;
 | 
						|
                };
 | 
						|
 | 
						|
                /**
 | 
						|
                 * @param {!string} string
 | 
						|
                 * @param {!(Object.<string, any>|Array.<any|null>)} variables
 | 
						|
                 * @param {?any} [_default = null]
 | 
						|
                 * @returns {string}
 | 
						|
                 * @access public
 | 
						|
                 * @static
 | 
						|
                 */
 | 
						|
                Utils.string_variables = (string, variables, _default = null) => {
 | 
						|
 | 
						|
                    variables = Utils.get_dictionary(variables);
 | 
						|
 | 
						|
                    return ("" + string).replace(/\{([^\{\}]+)\}/g, (origin, key) => (
 | 
						|
                        variables[key] !== undefined ? variables[key] : 
 | 
						|
                        _default !== null ? _default : 
 | 
						|
                    origin));
 | 
						|
                };
 | 
						|
 | 
						|
                /**
 | 
						|
                 * @param {?any} item
 | 
						|
                 * @param {!number} [i = 0]
 | 
						|
                 * @returns {any|null}
 | 
						|
                 * @access public
 | 
						|
                 * @static
 | 
						|
                 */
 | 
						|
                Utils.get = (item, i = 0) => (Utils.is_array(item) ? item : [item])[i];
 | 
						|
 | 
						|
                /**
 | 
						|
                 * @param {?any} item
 | 
						|
                 * @returns {Array.<any|null>}
 | 
						|
                 * @access public
 | 
						|
                 * @static
 | 
						|
                 */
 | 
						|
                Utils.get_array = item => Utils.is_array(item) ? item : [item];
 | 
						|
 | 
						|
                /**
 | 
						|
                 * @param {...(any|null)} items
 | 
						|
                 * @returns {any|null}
 | 
						|
                 * @access public
 | 
						|
                 * @static
 | 
						|
                 */
 | 
						|
                Utils.get_random_from = (...items) => items[Math.random() * items.length >> 0];
 | 
						|
 | 
						|
                /**
 | 
						|
                 * @param {!number} from
 | 
						|
                 * @param {?number} [to = null]
 | 
						|
                 * @returns {number}
 | 
						|
                 * @access public
 | 
						|
                 * @static
 | 
						|
                 */
 | 
						|
                Utils.get_random_integer = (from, to = null) => to === null ? Math.random() * from >> 0 : Math.random() * (to - from) + from;
 | 
						|
 | 
						|
                return Utils;
 | 
						|
            })();
 | 
						|
 | 
						|
        </script>
 | 
						|
 | 
						|
        <script data-module="application" data-type="text/javascript" data-lagnuage="ECMAScript 2015" charset="utf-8">
 | 
						|
            "use strict";
 | 
						|
 | 
						|
            /**
 | 
						|
             * @class OpoQuizTiny
 | 
						|
             * @constructor
 | 
						|
             * @param {?(Object.<string, any|null>|Array.<any|null>)} [customs = null] 
 | 
						|
             * @returns {void}
 | 
						|
             * @access public
 | 
						|
             * @static
 | 
						|
             */
 | 
						|
            const OpoQuizTiny = (function(){
 | 
						|
 | 
						|
                /**
 | 
						|
                 * @constructs OpoQuizTiny
 | 
						|
                 * @param {?(Object.<string, any|null>|Array.<any|null>)} [customs = null] 
 | 
						|
                 * @returns {void}
 | 
						|
                 * @access private
 | 
						|
                 * @static
 | 
						|
                 */
 | 
						|
                const OpoQuizTiny = function(customs = null){
 | 
						|
 | 
						|
                    /**
 | 
						|
                     * @callback opo_quiz_tiny_event_callback
 | 
						|
                     * @param {!HTMLElement} item 
 | 
						|
                     * @param {!Event} event 
 | 
						|
                     * @returns {any|null}
 | 
						|
                     */
 | 
						|
 | 
						|
                    /**
 | 
						|
                     * @callback opo_quiz_tiny_default_callback
 | 
						|
                     * @returns {void}
 | 
						|
                     */
 | 
						|
 | 
						|
                    /** @type {OpoQuizTiny} */
 | 
						|
                    const self = this, 
 | 
						|
                          /** @type {Array.<string>} */
 | 
						|
                          ids = [], 
 | 
						|
                          /** @type {Object.<string, any>} */
 | 
						|
                          subjects = {};
 | 
						|
                    /** @type {string|Array.<string>} */
 | 
						|
                    let id_alphabet = ['1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'], 
 | 
						|
                        /** @type {number} */
 | 
						|
                        id_length = 11,
 | 
						|
                        /** @type {Object.<string, any>} */
 | 
						|
                        quiz_data = {};
 | 
						|
 | 
						|
                    /** @type {HTMLDivElement} */
 | 
						|
                    this.item_self;
 | 
						|
 | 
						|
                    /**
 | 
						|
                     * @returns {void}
 | 
						|
                     * @access private
 | 
						|
                     */
 | 
						|
                    const constructor = () => {
 | 
						|
                        Utils.preload(self.get("position"), (position, error, asynchronous) => {
 | 
						|
                            if(error){
 | 
						|
                                console.error("position_error");
 | 
						|
                                return;
 | 
						|
                            };
 | 
						|
                            self.item_self = Utils.html(position, ["div", {
 | 
						|
                                class : "opo-quiz-tiny"
 | 
						|
                            }, [
 | 
						|
                                ["header", null, [
 | 
						|
                                    ["h1", {
 | 
						|
                                        class : "logo", 
 | 
						|
                                        data_i18n : "opo_quiz_tiny", 
 | 
						|
                                        data_i18n_without : true, 
 | 
						|
                                        title : self.get_i18n("opo_quiz_tiny", null, "OpoQuizTiny")
 | 
						|
                                    }, [
 | 
						|
                                        ["a", {
 | 
						|
                                            href : "#", 
 | 
						|
                                            target : "_blank"
 | 
						|
                                        }, [
 | 
						|
                                            ["span", {class : "image"}, [
 | 
						|
                                                ["img", {src : "", data_i18n : "opo_quiz_tiny", data_i18n_without : true, alt : self.get_i18n("opo_quiz_tiny", null, "OpoQuizTiny")}], 
 | 
						|
                                                ["span", {style : "background-image:url('');"}]
 | 
						|
                                            ]], 
 | 
						|
                                            self.i18n("opo_quiz_tiny", "span", "OpoQuizTiny")
 | 
						|
                                        ]]
 | 
						|
                                    ]]
 | 
						|
                                ]], 
 | 
						|
                                ["main", null, [self.build_main_form()]], 
 | 
						|
                                ["footer", null, [
 | 
						|
                                    ["p", {class : "licenses"}, [
 | 
						|
                                        ["span", {class : "copyright", data_i18n : "copyright", data_i18n_without : true, title : self.get_i18n("copyright", null, "© CopyRight 2025-2026")}, [
 | 
						|
                                            self.i18n("copyright", "span", "© CopyRight 2025-2026"), 
 | 
						|
                                            ["span", {class : "kyman"}, "KyMAN"]
 | 
						|
                                        ]], 
 | 
						|
                                        ["a", {
 | 
						|
                                            class : "cc-by-nc-sa-4", 
 | 
						|
                                            href : "https://creativecommons.org/licenses/by-nc-sa/4.0/", 
 | 
						|
                                            target : "_blank", 
 | 
						|
                                            data_i18n : "cc_by_nc_sa_4", 
 | 
						|
                                            data_i18n_without : true, 
 | 
						|
                                            title : self.get_i18n("cc_by_nc_sa_4", null, "Creative Commons BY-NC-SA (Attribution-NoCommerce-ShareDAlike) 4.0")
 | 
						|
                                        }, [
 | 
						|
                                            self.i18n("cc_by_nc_sa_4", "span", "Creative Commons BY-NC-SA (Attribution-NoCommerce-ShareDAlike) 4.0"), 
 | 
						|
                                            ["img", {src : "https://licensebuttons.net/l/by-nc-sa/3.0/88x31.png", alt : "Creative Commons BY-NC-SA 4.0"}]
 | 
						|
                                        ]]
 | 
						|
                                    ]]
 | 
						|
                                ]]
 | 
						|
                            ]])[0];
 | 
						|
                        });
 | 
						|
                    };
 | 
						|
 | 
						|
                    /**
 | 
						|
                     * @param {string} keys
 | 
						|
                     * @param {?(Object.<string, any|null>|Array.<any|null>)} [inputs = null]
 | 
						|
                     * @param {?(Object.<string, any|null>|Array.<any|null>)} [_default = null]
 | 
						|
                     * @returns {any|null}
 | 
						|
                     * @access public
 | 
						|
                     */
 | 
						|
                    this.get = (keys, inputs = null, _default = null) => Utils.get_value(keys, [inputs, customs, SETTINGS], _default);
 | 
						|
 | 
						|
                    /**
 | 
						|
                     * @param {string} keys
 | 
						|
                     * @param {?(Object.<string, any|null>|Array.<any|null>)} [inputs = null]
 | 
						|
                     * @param {?(Object.<string, any|null>|Array.<any|null>)} [_default = null]
 | 
						|
                     * @returns {any|null}
 | 
						|
                     * @access public
 | 
						|
                     */
 | 
						|
                    this.get_i18n = (keys, inputs = null, _default = null) => {
 | 
						|
 | 
						|
                        /** @type {string} */
 | 
						|
                        const text = _default || Utils.get_strings(keys)[0] || "";
 | 
						|
 | 
						|
                        return inputs ? Utils.string_variables(text, inputs) : text;
 | 
						|
                    };
 | 
						|
 | 
						|
                    /**
 | 
						|
                     * @param {string} name
 | 
						|
                     * @returns {[string, Object.<string, string>]}
 | 
						|
                     * @access public
 | 
						|
                     */
 | 
						|
                    this.icon = name => ["span", {data_icon : name}];
 | 
						|
 | 
						|
                    /**
 | 
						|
                     * @param {!string} name 
 | 
						|
                     * @param {!string} [tag = "span"] 
 | 
						|
                     * @param {?string} [text = null] 
 | 
						|
                     * @param {?(Object.<string, any|null>|Array.<any|null>)} [inputs = null] 
 | 
						|
                     * @returns {[string, Object.<string, string>, string]}
 | 
						|
                     * @access public
 | 
						|
                     */
 | 
						|
                    this.i18n = (name, tag = "span", text = null, inputs = null) => [tag, {data_i18n : name}, self.get_i18n(name, inputs, text)];
 | 
						|
 | 
						|
                    /**
 | 
						|
                     * @param {!string} name 
 | 
						|
                     * @param {!opo_quiz_tiny_event_callback} action 
 | 
						|
                     * @param {!string} [type = "button"]
 | 
						|
                     * @param {?string} [text = null]
 | 
						|
                     * @returns {[string, Object.<string, any>, Array.<[string, Object.<string, any>, string|null]>]}
 | 
						|
                     * @access public
 | 
						|
                     */
 | 
						|
                    this.button = (name, action, type = "button", text = null) => ["button", {
 | 
						|
                        type : type, 
 | 
						|
                        data_i18n : name, 
 | 
						|
                        data_i18n_without : true, 
 | 
						|
                        title : self.get_i18n(name, null, text), 
 | 
						|
                        ...(action ? {on_click : action} : {})
 | 
						|
                    }, [
 | 
						|
                        self.icon(name), 
 | 
						|
                        self.i18n(name, "span", text)
 | 
						|
                    ]];
 | 
						|
 | 
						|
                    /**
 | 
						|
                     * @param {!Object.<string, any|null>} input
 | 
						|
                     * @returns {Array.<any|null>}
 | 
						|
                     * @access public
 | 
						|
                     */
 | 
						|
                    this.number = input => ["input", {
 | 
						|
                        type : "number", 
 | 
						|
                        ...[
 | 
						|
                            ["id"], 
 | 
						|
                            ["name"], 
 | 
						|
                            ["value"], 
 | 
						|
                            ["min", "minimum"], 
 | 
						|
                            ["max", "maximum"], 
 | 
						|
                            ["step"], 
 | 
						|
                            ["onchange", "on_change", "change"]
 | 
						|
                        ].reduce((fields, keys) => {
 | 
						|
 | 
						|
                            /** @type {any|null} */
 | 
						|
                            const value = self.get(keys, input);
 | 
						|
 | 
						|
                            Utils.is_null_or_undefined(value) || (fields[keys[0]] = value);
 | 
						|
 | 
						|
                            return fields;
 | 
						|
                        }, {}), 
 | 
						|
                        ...(input.i18n || (input.i18n = input.name) ? {data_i18n : input.i18n, data_i18n_without : true} : {}), 
 | 
						|
                        ...(input.i18n || input.text ? {placeholder : self.get_i18n(input.i18n, input, input.text) + "..." } : {})
 | 
						|
                    }];
 | 
						|
 | 
						|
                    /**
 | 
						|
                     * @param {!string} name 
 | 
						|
                     * @param {!boolean} [checked = false] 
 | 
						|
                     * @param {?opo_quiz_tiny_event_callback} [on_change = null] 
 | 
						|
                     * @param {?(Object.<string, any|null>|Array.<any|null>)} [input = null] 
 | 
						|
                     * @param {?string} [text = null] 
 | 
						|
                     * @returns {Array.<any|null>}
 | 
						|
                     * @access public
 | 
						|
                     */
 | 
						|
                    this.checkbox = (name, checked = false, on_change = null, input = null, text = null) => ["label", {
 | 
						|
                        class : "checkbox", 
 | 
						|
                        data_i18n : name, 
 | 
						|
                        data_i18n_without : true, 
 | 
						|
                        title : self.get_i18n(name, input, text), 
 | 
						|
                        ...Utils.get_dictionary(input)
 | 
						|
                    }, [
 | 
						|
                        ["input", {
 | 
						|
                            type : "checkbox", 
 | 
						|
                            name : name, 
 | 
						|
                            ...(checked ? {checked : checked} : {}), 
 | 
						|
                            ...(on_change ? {on_change : on_change} : {})
 | 
						|
                        }], 
 | 
						|
                        self.icon("checkbox"), 
 | 
						|
                        self.i18n(name, "span", text)
 | 
						|
                    ]];
 | 
						|
 | 
						|
                    /**
 | 
						|
                     * @param {!string} name 
 | 
						|
                     * @param {!boolean} [checked = false] 
 | 
						|
                     * @param {?opo_quiz_tiny_event_callback} [on_change = null] 
 | 
						|
                     * @param {?(Object.<string, any|null>|Array.<any|null>)} [input = null] 
 | 
						|
                     * @param {?string} [text = null] 
 | 
						|
                     * @returns {Array.<any|null>}
 | 
						|
                     * @access public
 | 
						|
                     */
 | 
						|
                    this.radiobutton = (name, checked = false, on_change = null, input = null, text = null) => ["label", {
 | 
						|
                        class : "radiobutton", 
 | 
						|
                        data_i18n : name, 
 | 
						|
                        data_i18n_without : true, 
 | 
						|
                        title : self.get_i18n(name, input, text), 
 | 
						|
                        ...Utils.get_dictionary(input)
 | 
						|
                    }, [
 | 
						|
                        ["input", {
 | 
						|
                            type : "radio", 
 | 
						|
                            name : name, 
 | 
						|
                            ...(checked ? {checked : checked} : {}), 
 | 
						|
                            ...(on_change ? {on_change : on_change} : {})
 | 
						|
                        }], 
 | 
						|
                        self.icon("radiobutton"), 
 | 
						|
                        self.i18n(name, "span", text)
 | 
						|
                    ]];
 | 
						|
 | 
						|
                    /**
 | 
						|
                     * @returns {string}
 | 
						|
                     * @access public
 | 
						|
                     */
 | 
						|
                    this.get_id = () => {
 | 
						|
 | 
						|
                        /** @type {number} */
 | 
						|
                        let l = id_alphabet.length;
 | 
						|
                        /** @type {string} */
 | 
						|
                        let id;
 | 
						|
 | 
						|
                        do{
 | 
						|
                            id = "";
 | 
						|
                            while((id += id_alphabet[Math.random() * l >> 0]).length < id_length);
 | 
						|
                        }while(
 | 
						|
                            ids.includes(id) || 
 | 
						|
                            /^[^a-z]/i.test(id) || 
 | 
						|
                            document.querySelector("." + id + ",#" + id + ",[name=" + id + "]")
 | 
						|
                        );
 | 
						|
                        ids.push(id);
 | 
						|
 | 
						|
                        return id;
 | 
						|
                    };
 | 
						|
 | 
						|
                    /**
 | 
						|
                     * @param {!string} name
 | 
						|
                     * @param {!Array.<Object.<string, any|null>>} structure
 | 
						|
                     * @param {!Array.<[string, opo_quiz_tiny_event_callback, string|null, string|null]>} actions
 | 
						|
                     * @param {?string} [title = null]
 | 
						|
                     * @param {?string} [text = null]
 | 
						|
                     * @returns {Array}
 | 
						|
                     * @access public
 | 
						|
                     */
 | 
						|
                    this.create_form = (name, structure, actions, title = null, text = null) => ["form", {
 | 
						|
                        class : Utils.to_kebab(name), 
 | 
						|
                        method : "get", 
 | 
						|
                        action : "#", 
 | 
						|
                        on_submit : (actions.filter(action => action[2] == "submit")[0] || [(item, event) => false])[0]
 | 
						|
                    }, [
 | 
						|
                        ["fieldset", null, [
 | 
						|
                            self.i18n(name, "legend", title), 
 | 
						|
                            self.i18n(name + "_text", "p", text), 
 | 
						|
                            ["div", {class : "form-structure"}, structure.map((item, i) => {
 | 
						|
 | 
						|
                                /** @type {string} */
 | 
						|
                                const id = item.id || self.get_id();
 | 
						|
 | 
						|
                                // Implementar ID para el FOR LABEL.
 | 
						|
                                // item.input && Utils.is_dictionary(item.input[0]) && (item.input[0].id = id);
 | 
						|
 | 
						|
                                return ["tr", {data_i : i, data_i18n : item.label, data_i18n_without : true, title : self.get_i18n(item.label, null, item.label)}, [
 | 
						|
                                    ["label", {for : id}, [
 | 
						|
                                        self.i18n(item.label, "span", item.label), 
 | 
						|
                                        self.i18n(item.label + "_description", "span", item.label + "_description")
 | 
						|
                                    ]],
 | 
						|
                                    ["span", {class : "input"}, item.input], 
 | 
						|
                                    ["ul", {class : "errors"}]
 | 
						|
                                ]];
 | 
						|
                            })], 
 | 
						|
                            ["div", {class : "buttons"}, actions.map(action => self.button(...action))]
 | 
						|
                        ]]
 | 
						|
                    ]];
 | 
						|
 | 
						|
                    /**
 | 
						|
                     * @returns {Array.<any|null>}
 | 
						|
                     * @access public
 | 
						|
                     */
 | 
						|
                    this.build_main_form = () => self.create_form(
 | 
						|
                        "main_form", 
 | 
						|
                        [
 | 
						|
                            {label : "subjects", input : [
 | 
						|
                                ["nav", null, [
 | 
						|
                                    ["ul", null, draw_subjects(subjects)]
 | 
						|
                                ]], 
 | 
						|
                                ["div", {class : "buttons"}, [
 | 
						|
                                    self.button("reset_subjects", reset_subjects, "button", "Restablecer temario"), 
 | 
						|
                                    self.button("load_subjects", load_subjects, "button", "Cargar temario")
 | 
						|
                                ]]
 | 
						|
                            ]}, 
 | 
						|
                            {label : "minimum_questions_number", input : [
 | 
						|
                                self.number({name : "minimum_questions_number", step : 1, minimum : 1, maximum : 250, value : 10})
 | 
						|
                            ]}, 
 | 
						|
                            {label : "maximum_questions_number", input : [
 | 
						|
                                self.number({name : "maximum_questions_number", step : 1, minimum : 1, maximum : 250, value : 50})
 | 
						|
                            ]}, 
 | 
						|
                            {label : "allow_true_or_false", input : [
 | 
						|
                                self.checkbox("allow_true_or_false", true)
 | 
						|
                            ]}, 
 | 
						|
                            {label : "allow_normal", input : [
 | 
						|
                                self.checkbox("allow_normal", true)
 | 
						|
                            ]}, 
 | 
						|
                            {label : "allow_multianswers", input : [
 | 
						|
                                self.checkbox("allow_multianswers", true)
 | 
						|
                            ]}, 
 | 
						|
                            {label : "allow_negative_answers", input : [
 | 
						|
                                self.checkbox("allow_negative_answers", true)
 | 
						|
                            ]}, 
 | 
						|
                            {label : "minimum_answers_number", input : [
 | 
						|
                                self.number({name : "minimum_answers_number", step : 1, minimum : 2, maximum : 25, value : 2})
 | 
						|
                            ]}, 
 | 
						|
                            {label : "maximum_answers_number", input : [
 | 
						|
                                self.number({name : "maximum_answers_number", step : 1, minimum : 2, maximum : 25, value : 8})
 | 
						|
                            ]}, 
 | 
						|
                            {label : "allow_all_true", input : [
 | 
						|
                                self.checkbox("allow_all_true", true)
 | 
						|
                            ]}, 
 | 
						|
                            {label : "allow_all_false", input : [
 | 
						|
                                self.checkbox("allow_all_false", true)
 | 
						|
                            ]}, 
 | 
						|
                            {label : "allow_belongs_questions", input : [
 | 
						|
                                self.checkbox("allow_belongs_questions", false)
 | 
						|
                            ]}, 
 | 
						|
                            {label : "allow_has_questions", input : [
 | 
						|
                                self.checkbox("allow_has_questions", false)
 | 
						|
                            ]}, 
 | 
						|
                            {label : "allow_is_questions", input : [
 | 
						|
                                self.checkbox("allow_is_questions", true)
 | 
						|
                            ]}, 
 | 
						|
                            {label : "nested_probability", input : [
 | 
						|
                                self.number({name : "nested_probability", step : 1, minimum : 0, maximum : 99, value : 50})
 | 
						|
                            ]}, 
 | 
						|
                            {label : "minimum_levels", input : [
 | 
						|
                                self.number({name : "minimum_levels", step : 1, minimum : 1, maximum : 10, value : 1})
 | 
						|
                            ]}, 
 | 
						|
                            {label : "maximum_levels", input : [
 | 
						|
                                self.number({name : "maximum_levels", step : 1, minimum : 1, maximum : 10, value : 4})
 | 
						|
                            ]}, 
 | 
						|
                            {label : "minimum_tries", input : [
 | 
						|
                                self.number({name : "minimum_tries", step : 1, minimum : 1, maximum : 100, value : 10})
 | 
						|
                            ]}, 
 | 
						|
                            {label : "maximum_tries", input : [
 | 
						|
                                self.number({name : "maximum_tries", step : 1, minimum : 1, maximum : 100, value : 10})
 | 
						|
                            ]}
 | 
						|
                        ], 
 | 
						|
                        [
 | 
						|
                            ["clean", null, "reset", "Limpiar"], 
 | 
						|
                            ["start", build_quiz, "button", "Iniciar"]
 | 
						|
                        ], 
 | 
						|
                        self.get_i18n("main_form", null, "Formulario de inicio"), 
 | 
						|
                        self.get_i18n("main_form_text", null, "Este formulario nos sirve para configurar nuestro nuevo Test y establecer contenidos para el mismo.")
 | 
						|
                    );
 | 
						|
 | 
						|
                    /**
 | 
						|
                     * @param {!Object.<string, any>} set
 | 
						|
                     * @param {!number} [i = 0]
 | 
						|
                     * @returns {Array.<Object.<string, any|null>>}
 | 
						|
                     * @access private
 | 
						|
                     */
 | 
						|
                    const draw_subjects = (set, i = 0) => {
 | 
						|
 | 
						|
                        /** @type {Array.<Object.<string, any|null>>} */
 | 
						|
                        const items = [];
 | 
						|
 | 
						|
                        for(const id in set){
 | 
						|
 | 
						|
                            /** @type {[Object.<string, any|null>, boolean, string]} */
 | 
						|
                            const [data, checked, name] = set[id];
 | 
						|
 | 
						|
                            if(i || !data.nested){
 | 
						|
                                items.push(["li", {
 | 
						|
                                    data_subject : id, 
 | 
						|
                                    title : name
 | 
						|
                                }, [
 | 
						|
                                    self.checkbox(id, checked, select_subject, null, name), 
 | 
						|
                                    data.nodes ? ["ul", null, draw_subjects(data.nodes.reduce((subset, id) => {
 | 
						|
                                        subjects[id] && (subset[id] = subjects[id]);
 | 
						|
                                        return subset;
 | 
						|
                                    }, {}), i + 1)] : null
 | 
						|
                                ]]);
 | 
						|
                            };
 | 
						|
 | 
						|
                        };
 | 
						|
 | 
						|
                        return items;
 | 
						|
                    };
 | 
						|
 | 
						|
                    /**
 | 
						|
                     * @param {!HTMLElement} item
 | 
						|
                     * @param {!Event} event
 | 
						|
                     * @returns {void}
 | 
						|
                     * @access private
 | 
						|
                     */
 | 
						|
                    const load_subjects = (item, event) => {
 | 
						|
                        Utils.upload((...data) => {
 | 
						|
 | 
						|
                            /** @type {HTMLUListElement} */
 | 
						|
                            const list = item.parentNode.parentNode.querySelector("nav ul");
 | 
						|
 | 
						|
                            data.forEach(items => {
 | 
						|
                                Utils.get_array(Utils.json_decode(items.data)).forEach(json => {
 | 
						|
                                    json && 
 | 
						|
                                    (subjects[json.id || (json.id = self.get_id())] = [json, true, Utils.get(json.name)]);
 | 
						|
                                });
 | 
						|
                            });
 | 
						|
                            Object.entries(subjects).forEach(([id, [subject, ..._]]) => {
 | 
						|
                                subject.parent && Utils.get_array(subject.parent).forEach(parent => {
 | 
						|
                                    if(subjects[parent]){ 
 | 
						|
                                        !(subjects[parent][0].nodes || (subjects[parent][0].nodes = [])).includes(id) && 
 | 
						|
                                        subjects[parent][0].nodes.push(id);
 | 
						|
                                        subject.nested || (subject.nested = true);
 | 
						|
                                    };
 | 
						|
                                });
 | 
						|
                            });
 | 
						|
 | 
						|
                            list.innerHTML = ``;
 | 
						|
                            Utils.html(list, ...draw_subjects(subjects));
 | 
						|
 | 
						|
                        }, {multiple : true});
 | 
						|
                    };
 | 
						|
 | 
						|
                    /**
 | 
						|
                     * @param {!HTMLElement} item
 | 
						|
                     * @param {!Event} event
 | 
						|
                     * @returns {void}
 | 
						|
                     * @access private
 | 
						|
                     */
 | 
						|
                    const reset_subjects = (item, event) => {};
 | 
						|
 | 
						|
                    /**
 | 
						|
                     * @param {!Object.<string, any>} data
 | 
						|
                     * @param {!string} key
 | 
						|
                     * @returns {number}
 | 
						|
                     * @access private
 | 
						|
                     */
 | 
						|
                    const get_random_from = (data, key) => Math.random() * (data["maximum_" + key] - data["minimum_" + key]) + data["minimum_" + key] >> 0;
 | 
						|
 | 
						|
                    /**
 | 
						|
                     * @param {!Array.<[string, boolean]>} answers
 | 
						|
                     * @param {!string} answer
 | 
						|
                     * @param {!boolean} ok
 | 
						|
                     * @returns {boolean}
 | 
						|
                     * @access private
 | 
						|
                     */
 | 
						|
                    const add_answer = (answers, answer, ok) => {
 | 
						|
                        if(!answers.some(data => data && answer == data[0])){
 | 
						|
                            answers[Utils.get_random_from(...answers.map((data, i) => [i, !data]).filter(([_, ok]) => ok))[0]] = [answer, ok];
 | 
						|
                            return true;
 | 
						|
                        };
 | 
						|
                        return false;
 | 
						|
                    };
 | 
						|
 | 
						|
                    /**
 | 
						|
                     * @param {!number} rights
 | 
						|
                     * @param {!number} tries
 | 
						|
                     * @param {!number} responses
 | 
						|
                     * @param {!opo_quiz_tiny_default_callback} right_callback
 | 
						|
                     * @param {!opo_quiz_tiny_default_callback} wrong_callback
 | 
						|
                     * @param {!boolean} wrong_mode
 | 
						|
                     * @returns {Array.<[string, boolean]>}
 | 
						|
                     * @access private
 | 
						|
                     */
 | 
						|
                    const set_answers = (rights, tries, responses, right_callback, wrong_callback, wrong_mode) => {
 | 
						|
 | 
						|
                        /** @type {Array.<[string, boolean]>} */
 | 
						|
                        const answers = [];
 | 
						|
 | 
						|
                        for(let i = 0; i < responses; i ++)
 | 
						|
                            answers.push(null);
 | 
						|
 | 
						|
                        wrong_mode && ([right_callback, wrong_callback] = [wrong_callback, right_callback]);
 | 
						|
                        
 | 
						|
                        for(let i = 0; i < rights; i ++)
 | 
						|
                            for(let j = 0; j < tries && !add_answer(answers, right_callback(), true); j ++);
 | 
						|
                        for(let i = answers.filter(answer => !!answer).length; i < responses; i ++)
 | 
						|
                            for(let j = 0; j < tries && !add_answer(answers, wrong_callback(), false); j ++);
 | 
						|
 | 
						|
                        return answers.filter(answer => !!answer);
 | 
						|
                    };
 | 
						|
 | 
						|
                    /**
 | 
						|
                     * @param {!Object.<string, any>} position
 | 
						|
                     * @param {?number} [levels = null]
 | 
						|
                     * @returns {[Array.<number|string>, Array.<any>]}
 | 
						|
                     * @access private
 | 
						|
                     */
 | 
						|
                    const get_road_map = (position, levels = null) => {
 | 
						|
 | 
						|
                        /** @type {number|string} */
 | 
						|
                        const road_map = [];
 | 
						|
                        /** @type {number} */
 | 
						|
                        let i = 0;
 | 
						|
 | 
						|
                        if(!position.name){
 | 
						|
                            road_map.push(Utils.get_random_from(...Object.keys(position)));
 | 
						|
                            position = position[road_map[0]][0];
 | 
						|
                            i ++;
 | 
						|
                        };
 | 
						|
 | 
						|
                        for(; (
 | 
						|
                            levels === null ? Math.random() < quiz_data.nested_probability : i < levels
 | 
						|
                        ) && position.children && position.children.length; i ++){
 | 
						|
 | 
						|
                            /** @type {number} */
 | 
						|
                            const i = Utils.get_random_integer(position.children.length);
 | 
						|
 | 
						|
                            road_map.push(i);
 | 
						|
                            position = position.children[i];
 | 
						|
 | 
						|
                        };
 | 
						|
 | 
						|
                        return [road_map, position];
 | 
						|
                    };
 | 
						|
 | 
						|
                    /**
 | 
						|
                     * @param {!Array.<number|string>} road_map
 | 
						|
                     * @param {!number} levels
 | 
						|
                     * @returns {[Object.<string, any>, Array.<number, string>]}
 | 
						|
                     * @access private
 | 
						|
                     */
 | 
						|
                    const set_road_map = (road_map, levels) => {
 | 
						|
 | 
						|
                        /** @type {Object.<string, any>} */
 | 
						|
                        let position = subjects;
 | 
						|
 | 
						|
                        road_map.splice(road_map.length - levels);
 | 
						|
                        road_map.forEach(i => {
 | 
						|
                            position = (
 | 
						|
                                Utils.is_string(i) ? position[i][0] : 
 | 
						|
                            position.children[i]);
 | 
						|
                        });
 | 
						|
 | 
						|
                        return [position, road_map];
 | 
						|
                    };
 | 
						|
 | 
						|
                    /**
 | 
						|
                     * @param {!Array.<number|string>} road_map
 | 
						|
                     * @param {!string} name
 | 
						|
                     * @returns {string}
 | 
						|
                     * @access private
 | 
						|
                     */
 | 
						|
                    const get_by_name = (road_map, name) => road_map.slice(0, road_map.length - 1).reduce(([position, texts], i, j) => [
 | 
						|
                        position = j ? position.children[i] : position[i][0], 
 | 
						|
                        texts.concat([Utils.get_random_from(...Utils.get_array(position.name))
 | 
						|
                    ])], [subjects, []])[1].filter(item => item).concat([name]).reverse().reduce((results, name) => results ? self.get_i18n("name_of", {
 | 
						|
                        a : results, 
 | 
						|
                        b : name
 | 
						|
                    }, "{a} del {b}") : name, "");
 | 
						|
 | 
						|
                    /**
 | 
						|
                     * @param {!Array.<string|number>} map 
 | 
						|
                     * @param {!Object.<string, any>} position
 | 
						|
                     * @param {!Array.<string>} keys
 | 
						|
                     * @returns {string}
 | 
						|
                     * @access private
 | 
						|
                     */
 | 
						|
                    const get_is_right_answer = (map, position, keys) => {
 | 
						|
 | 
						|
                        /** @type {string} */
 | 
						|
                        const key = Utils.get_random_from(...keys), 
 | 
						|
                              /** @type {string} */
 | 
						|
                              response = Utils.get_random_from(...Utils.get_array(position[key]));
 | 
						|
                        
 | 
						|
                        return key == "name" ? get_by_name(map, response) : response;
 | 
						|
                    };
 | 
						|
 | 
						|
                    /**
 | 
						|
                     * @param {!Array.<number|string>} map
 | 
						|
                     * @param {!number} levels
 | 
						|
                     * @param {!Array.<string>} keys
 | 
						|
                     * @returns {string}
 | 
						|
                     * @access private
 | 
						|
                     */
 | 
						|
                    const get_is_wrong_answer = (map, levels, keys) => {
 | 
						|
 | 
						|
                        const key = Utils.get_random_from(...keys);
 | 
						|
                        /** @type {[Object.<string, any>, Array.<string|number>]} */
 | 
						|
                        let [position, road_map] = set_road_map(map, levels > map.length ? levels = map.length : levels), 
 | 
						|
                            /** @type {Array.<string|number>} */
 | 
						|
                            submap;
 | 
						|
 | 
						|
                        [submap, position] = get_road_map(position, levels);
 | 
						|
                        road_map.push(...submap);
 | 
						|
 | 
						|
                        /** @type {string} */
 | 
						|
                        const response = Utils.get_random_from(...Utils.get_array(position[key]));
 | 
						|
 | 
						|
                        return key == "name" ? get_by_name(road_map, response) : response;
 | 
						|
                    };
 | 
						|
 | 
						|
                    /**
 | 
						|
                     * @param {!Object.<string, any>} data 
 | 
						|
                     * @param {!number} rights
 | 
						|
                     * @param {!number} responses
 | 
						|
                     * @returns {[string, string, Array.<[string, boolean]>, boolean]}
 | 
						|
                     * @access private
 | 
						|
                     */
 | 
						|
                    const get_question_data = (data, rights, responses) => {
 | 
						|
 | 
						|
                        /** @type {Array.<any>} */
 | 
						|
                        let [road_map, position] = get_road_map(subjects), 
 | 
						|
                            /** @type {string} */
 | 
						|
                            question, 
 | 
						|
                            /** @type {string} */
 | 
						|
                            mode, 
 | 
						|
                            /** @type {string} */
 | 
						|
                            data_from, 
 | 
						|
                            /** @type {Array.<[string, boolean]>} */
 | 
						|
                            answers = [], 
 | 
						|
                            /** @type {Array.<string>} */
 | 
						|
                            data_to = [];
 | 
						|
                        /** @type {Array.<number>} */
 | 
						|
                        const levels = get_random_from(data, "levels"), 
 | 
						|
                              /** @type {number} */
 | 
						|
                              tries = get_random_from(data, "tries"), 
 | 
						|
                              /** @type {Array.<string>} */
 | 
						|
                              all_data_keys = [].concat(
 | 
						|
                                  position.description ? ["description"] : [], 
 | 
						|
                                  position.name ? ["name"] : []
 | 
						|
                              ), 
 | 
						|
                              /** @type {boolean} */
 | 
						|
                              wrong_mode = data.allow_negative_answers ? Math.random() < .5 : false;
 | 
						|
 | 
						|
                        data_from = Utils.get_random_from(...all_data_keys);
 | 
						|
                        data_to = all_data_keys.filter(key => key != data_from);
 | 
						|
 | 
						|
                        question = Utils.get_random_from(...Utils.get_array(position[data_from]));
 | 
						|
                        data_from == "name" && (question = get_by_name(road_map, question));
 | 
						|
 | 
						|
                        switch(mode = Utils.get_random_from(...[].concat(
 | 
						|
                            road_map.length > 1 ? ["belongs"] : [], 
 | 
						|
                            // position.children && position.children.length ? ["has"] : [],
 | 
						|
                            position.description && position.name ? ["is"] : []
 | 
						|
                        ))){
 | 
						|
                            case "belongs":
 | 
						|
                                answers = set_answers(rights, tries, responses, 
 | 
						|
                                    () => get_is_right_answer(...set_road_map([].concat(road_map), 1).reverse(), data_to), 
 | 
						|
                                    () => get_is_wrong_answer([].concat(road_map).slice(0, road_map.length - 1), levels, data_to), 
 | 
						|
                                wrong_mode);
 | 
						|
                            break;
 | 
						|
                            // case "has":
 | 
						|
                            //     answers = set_answers(rights, tries, responses, 
 | 
						|
                            //         () => get_is_right_answer(road_map, position, data_to), 
 | 
						|
                            //         () => get_is_wrong_answer(road_map, levels, data_to), 
 | 
						|
                            //     wrong_mode);
 | 
						|
                            // break;
 | 
						|
                            case "is":
 | 
						|
                                answers = set_answers(rights, tries, responses, 
 | 
						|
                                    () => get_is_right_answer([].concat(road_map), position, data_to), 
 | 
						|
                                    () => get_is_wrong_answer([].concat(road_map), levels, data_to), 
 | 
						|
                                wrong_mode);
 | 
						|
                            break;
 | 
						|
                        };
 | 
						|
 | 
						|
                        return [mode, question, answers, wrong_mode];
 | 
						|
                    };
 | 
						|
 | 
						|
                    /**
 | 
						|
                     * @param {!string} type
 | 
						|
                     * @param {!string} mode
 | 
						|
                     * @param {!string} question
 | 
						|
                     * @param {!Array.<[string, boolean]>} answers
 | 
						|
                     * @returns {Array.<any>}
 | 
						|
                     * @access private
 | 
						|
                     */
 | 
						|
                    const build_question = (type, mode, question, answers) => {
 | 
						|
                        return ["form", {class : "question", data_type : type, data_result : answers.reduce((bits, [_, ok], i) => bits | (ok ? 1 << i : 0), 0)}, [
 | 
						|
                            ["fieldset", null, [
 | 
						|
                                ["legend", null, question], 
 | 
						|
                                ["nav", null, [
 | 
						|
                                    ["ul", null, answers.map(([answer, _], i) => ["li", {data_i : i}, [
 | 
						|
                                        self[mode]("answer", false, null, null, answer)
 | 
						|
                                    ]])]
 | 
						|
                                ]]
 | 
						|
                            ]]
 | 
						|
                        ]];
 | 
						|
                    };
 | 
						|
 | 
						|
                    /**
 | 
						|
                     * @param {!Object.<string, any>} data
 | 
						|
                     * @returns {Array.<any>}
 | 
						|
                     * @access private
 | 
						|
                     */
 | 
						|
                    const build_normal_question = data => {
 | 
						|
 | 
						|
                        /** @type {[string, string, Array.<string>, boolean]} */
 | 
						|
                        const [mode, question, answers, wrong_mode] = get_question_data(data, 1, get_random_from(data, "answers_number")), 
 | 
						|
                              /** @type {Array.<[string, string]>} */
 | 
						|
                              question_string = (
 | 
						|
                                  mode == "belongs" ? ["what_one_belongs", "¿A qué {wrong}pertenece {data}?"] : 
 | 
						|
                                  mode == "has" ? ["which_one_has", "¿Cuál {wrong}tiene {data}?"] : 
 | 
						|
                                  mode == "is" ? ["what_one_is", "¿Qué {wrong}es {data}?"] : 
 | 
						|
                              ["unknown", "Unknown"]);
 | 
						|
 | 
						|
                        return build_question("normal", "radiobutton", self.get_i18n(question_string[0], {
 | 
						|
                            data : question, 
 | 
						|
                            wrong : wrong_mode ? "no " : ""
 | 
						|
                        }, question_string[1]), answers);
 | 
						|
                    };
 | 
						|
 | 
						|
                    /**
 | 
						|
                     * @param {!Object.<string, any>} data
 | 
						|
                     * @returns {Array.<any>}
 | 
						|
                     * @access private
 | 
						|
                     */
 | 
						|
                    const build_true_or_false_question = data => {
 | 
						|
 | 
						|
                        /** @type {[string, string, Array.<string>, boolean]} */
 | 
						|
                        const [mode, question, answers, wrong_mode] = get_question_data(data, 1, get_random_from(data, "answers_number")), 
 | 
						|
                              /** @type {Array.<[string, string]>} */
 | 
						|
                              question_string = (
 | 
						|
                                  mode == "belongs" ? ["its_belongs", "¿{data} {wrong}pertenece a {answer}?"] : 
 | 
						|
                                  mode == "has" ? ["its_has", "¿{data} {wrong}tiene: {answer}"] : 
 | 
						|
                                  mode == "is" ? ["its_is", "¿{data} {wrong}es {answer}?"] : 
 | 
						|
                              ["unknown", "Unknown"]);
 | 
						|
 | 
						|
                        return build_question("true_or_false", "radiobutton", self.get_i18n(question_string[0], {
 | 
						|
                            data : question, 
 | 
						|
                            wrong : wrong_mode ? "no " : "", 
 | 
						|
                            answer : answers[0][0]
 | 
						|
                        }, question_string[1]), [
 | 
						|
                            [self.get_i18n("true", null, "Verdadero"), answers[0][1]], 
 | 
						|
                            [self.get_i18n("false", null, "False"), !answers[0][1]]
 | 
						|
                        ]);
 | 
						|
                    };
 | 
						|
 | 
						|
                    /**
 | 
						|
                     * @param {!Object.<string, any>} data
 | 
						|
                     * @returns {Array.<any>}
 | 
						|
                     * @access private
 | 
						|
                     */
 | 
						|
                    const build_multianswers_question = data => {
 | 
						|
 | 
						|
                        /** @type {[string, string, Array.<string>, boolean]} */
 | 
						|
                        const [mode, question, answers, wrong_mode] = get_question_data(data, 1, get_random_from(data, "answers_number")), 
 | 
						|
                              /** @type {Array.<[string, string]>} */
 | 
						|
                              question_string = (
 | 
						|
                                  mode == "belongs" ? ["what_belongs", "¿A qué {wrong}pertenece {data}?"] : 
 | 
						|
                                  mode == "has" ? ["which_has", "¿Cuáles {wrong}tienen {data}?"] : 
 | 
						|
                                  mode == "is" ? ["what_is", "¿Cuáles {wrong}son {data}?"] : 
 | 
						|
                              ["unknown", "Unknown"]);
 | 
						|
 | 
						|
                        return build_question("multianswer", "checkbox", self.get_i18n(question_string[0], {
 | 
						|
                            data : question, 
 | 
						|
                            wrong : wrong_mode ? "no " : ""
 | 
						|
                        }, question_string[1]), answers);
 | 
						|
                    };
 | 
						|
 | 
						|
                    /**
 | 
						|
                     * @param {!HTMLElement} item
 | 
						|
                     * @param {!Event} event
 | 
						|
                     * @returns {void}
 | 
						|
                     * @access private
 | 
						|
                     */
 | 
						|
                    const build_quiz = (item, event) => {
 | 
						|
 | 
						|
                        /** @type {number} */
 | 
						|
                        const l = get_random_from(quiz_data = {
 | 
						|
                                  subjects : [...self.item_self.querySelectorAll(".main-form tr[data-i18n=subjects] [type=checkbox]:checked")].map(checkbox => checkbox.getAttribute("name")), 
 | 
						|
                                  ...[...self.item_self.querySelectorAll(".main-form tr+tr [name]")].reduce((data, input) => {
 | 
						|
                                  
 | 
						|
                                      /** @type {string} */
 | 
						|
                                      const name = input.getAttribute("name");
 | 
						|
                                  
 | 
						|
                                      if(input.type == "checkbox")
 | 
						|
                                          data[name] = input.checked;
 | 
						|
                                      else if(input.type == "number")
 | 
						|
                                          data[name] = Number(input.value);
 | 
						|
                                  
 | 
						|
                                      return data;
 | 
						|
                                  }, {})
 | 
						|
                              }, "questions_number"), 
 | 
						|
                              /** @type {Array.<string>} */
 | 
						|
                              modes = ["normal", "true_or_false", "multianswers"].filter(key => quiz_data["allow_" + key]), 
 | 
						|
                              /** @type {Array.<any>} */
 | 
						|
                              questions = [], 
 | 
						|
                              /** @type {HTMLMainElement} */
 | 
						|
                              main = self.item_self.querySelector("main");
 | 
						|
 | 
						|
                        console.log([subjects, quiz_data, l, modes]);
 | 
						|
 | 
						|
                        for(let i = 0; i < l; i ++)
 | 
						|
                            switch(Utils.get_random_from(...modes)){
 | 
						|
                                case "normal":
 | 
						|
                                    questions.push(build_normal_question(quiz_data));
 | 
						|
                                break;
 | 
						|
                                case "true_or_false":
 | 
						|
                                    questions.push(build_true_or_false_question(quiz_data));
 | 
						|
                                break;
 | 
						|
                                case "multianswers":
 | 
						|
                                    questions.push(build_multianswers_question(quiz_data));
 | 
						|
                                break;
 | 
						|
                            };
 | 
						|
 | 
						|
                        main.innerHTML = ``;
 | 
						|
                        Utils.html(main, ["fieldset", {class : "quiz"}, [
 | 
						|
                            self.i18n("quiz", "legend", "Cuestionario"), 
 | 
						|
                            ["fieldset", {class : "questions"}, [
 | 
						|
                                self.i18n("questions", "legend", "Preguntas"), 
 | 
						|
                                ["section", null, questions], 
 | 
						|
                                ["div", {class : "buttons"}, [
 | 
						|
                                    self.button("cancel", got_to_main_menu, "button", "Cancelar"), 
 | 
						|
                                    self.button("restart", restart_quiz, "button", "Reiniciar"), 
 | 
						|
                                    self.button("finish", finish_quiz, "button", "Terminar")
 | 
						|
                                ]]
 | 
						|
                            ]]
 | 
						|
                        ]]);
 | 
						|
 | 
						|
                    };
 | 
						|
 | 
						|
                    /**
 | 
						|
                     * @param {!HTMLElement} item
 | 
						|
                     * @param {!Event} event
 | 
						|
                     * @returns {void}
 | 
						|
                     * @access private
 | 
						|
                     */
 | 
						|
                    const got_to_main_menu = (item, event) => {
 | 
						|
 | 
						|
                        const main = self.item_self.querySelector("main");
 | 
						|
 | 
						|
                        main.innerHTML = ``;
 | 
						|
                        Utils.html(main, self.build_main_form());
 | 
						|
 | 
						|
                    };
 | 
						|
 | 
						|
                    /**
 | 
						|
                     * @param {!HTMLElement} item
 | 
						|
                     * @param {!Event} event
 | 
						|
                     * @returns {void}
 | 
						|
                     * @access private
 | 
						|
                     */
 | 
						|
                    const restart_quiz = (item, event) => {};
 | 
						|
 | 
						|
                    /**
 | 
						|
                     * @param {!HTMLElement} item
 | 
						|
                     * @param {!Event} event
 | 
						|
                     * @returns {void}
 | 
						|
                     * @access private
 | 
						|
                     */
 | 
						|
                    const finish_quiz = (item, event) => {};
 | 
						|
 | 
						|
                    /**
 | 
						|
                     * @param {!HTMLElement} item
 | 
						|
                     * @param {!Event} event
 | 
						|
                     * @returns {void}
 | 
						|
                     * @access private
 | 
						|
                     */
 | 
						|
                    const select_subject = (item, event) => {
 | 
						|
                        
 | 
						|
                        /** @type {string} */
 | 
						|
                        const id = item.getAttribute("name");
 | 
						|
 | 
						|
                        subjects[id] && (subjects[id][1] = item.checked);
 | 
						|
                        item.parentNode.parentNode.querySelectorAll("[type=checkbox]").forEach(checkbox => {
 | 
						|
                            checkbox.checked = item.checked;
 | 
						|
                        });
 | 
						|
 | 
						|
                    };
 | 
						|
 | 
						|
                    constructor();
 | 
						|
 | 
						|
                };
 | 
						|
 | 
						|
                return OpoQuizTiny;
 | 
						|
            })();
 | 
						|
 | 
						|
        </script>
 | 
						|
 | 
						|
        <script data-module="starter" data-type="text/javascript" data-lagnuage="ECMAScript 2015" charset="utf-8">
 | 
						|
            "use strict";
 | 
						|
 | 
						|
            /** @type {OpoQuizTiny} */
 | 
						|
            const opo_quiz_tiny = new OpoQuizTiny();
 | 
						|
 | 
						|
        </script>
 | 
						|
 | 
						|
    </head>
 | 
						|
    <body></body>
 | 
						|
</html> |