727 lines
29 KiB
HTML
727 lines
29 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_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_dictionaris = (...items) => {
|
|
|
|
/** @type {Array.<Object.<string, any|null>>} */
|
|
const dictionaries = [];
|
|
|
|
items.forEach(subitem => {
|
|
if(Utils.is_dictionary(subitem))
|
|
dictionaries.push(subitem);
|
|
else if(Utils.is_array(subitem))
|
|
dictionaries.push(...Utils.get_dictionaris(...subitem));
|
|
});
|
|
|
|
return dictionaries;
|
|
};
|
|
|
|
/**
|
|
* @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_dictionaris(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{
|
|
|
|
/** @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;
|
|
};
|
|
|
|
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}
|
|
*/
|
|
|
|
/** @type {OpoQuizTiny} */
|
|
const self = this,
|
|
/** @type {Array.<string>} */
|
|
ids = [];
|
|
/** @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;
|
|
|
|
/**
|
|
* @returns {void}
|
|
* @access private
|
|
*/
|
|
const constructor = () => {
|
|
Utils.preload(self.get("position"), (position, error, asynchronous) => {
|
|
if(error){
|
|
console.error("position_error");
|
|
return;
|
|
};
|
|
Utils.html(position, ["div", {
|
|
class : "opo-quiz-tiny"
|
|
}, [
|
|
["header", null, [
|
|
["h1", {
|
|
class : "logo",
|
|
data_i18n : "opo_quiz_tiny",
|
|
data_i18n_without : true,
|
|
title : "OpoQuizTiny"
|
|
}, [
|
|
["a", {
|
|
href : "#",
|
|
target : "_blank"
|
|
}, [
|
|
["span", {class : "image"}, [
|
|
["img", {src : ""}],
|
|
["span", {style : "background-image:url('');"}]
|
|
]],
|
|
["span", {data_i18n : "opo_quiz_tiny"}, "OpoQuizTiny"]
|
|
]]
|
|
]]
|
|
]],
|
|
["main", null, [self.build_main_form()]],
|
|
["footer", null, [
|
|
["p", {class : "licenses"}, [
|
|
["span", {class : "copyright"}, [
|
|
["span", null, "© 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"}, [
|
|
["span", {data_i18n : "cc_by_nc_sa_4"}, "Creative Commons 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"}]
|
|
]]
|
|
]]
|
|
]]
|
|
]]);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* @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} name
|
|
* @returns {[string, Object.<string, string>]}
|
|
* @access public
|
|
*/
|
|
this.icon = name => ["span", {data_icon : name}];
|
|
|
|
/**
|
|
* @param {string} name
|
|
* @returns {[string, Object.<string, string>, string]}
|
|
* @access public
|
|
*/
|
|
this.i18n = (name, text = null) => ["span", {data_i18n : name}, text !== null ? text : name];
|
|
|
|
/**
|
|
* @param {!string} name
|
|
* @param {!opo_quiz_tiny_event} 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 : text !== null ? text : name,
|
|
...(action ? {on_click : action} : {})
|
|
}, [
|
|
self.icon(name),
|
|
self.i18n(name, text)
|
|
]];
|
|
|
|
/**
|
|
* @returns {string}
|
|
* @access public
|
|
*/
|
|
this.get_id = () => {
|
|
|
|
/** @type {number} */
|
|
let l = id_alphabet.length;
|
|
/** @type {string} */
|
|
let id;
|
|
|
|
do{
|
|
id = "";
|
|
while((id += alphabet[Math.random() * l >> 0]).length < 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}
|
|
*/
|
|
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, [
|
|
["legend", {data_i18n : name}, title],
|
|
["p", {data_i18n : name + "_text"}, text],
|
|
["div", {class : "form-structure"}, structure.map(item => {
|
|
|
|
/** @type {string} */
|
|
const id = item.id || self.get_id();
|
|
|
|
item.id || (item.id = id);
|
|
|
|
return ["tr", {}, [
|
|
["label", {for : id}, [
|
|
["span", {data_i18n : item.label}, item.label],
|
|
["span", {data_i18n : item.label + "_description"}, 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", [], [
|
|
["clean", null, "reset", "Limpiar"],
|
|
["load_subjects", load_subjects, "button", "Cargar temario"]
|
|
], "Formulario de inicio", "Este formulario nos sirve para configurar nuestro nuevo Test y establecer contenidos para el mismo.");
|
|
|
|
/**
|
|
* @param {!HTMLElement} item
|
|
* @param {!Event} event
|
|
* @returns {void}
|
|
* @access public
|
|
*/
|
|
const load_subjects = (item, event) => {
|
|
Utils.upload((...data) => {
|
|
console.log(data.map(subject => Utils.json_decode(subject.data)));
|
|
}, {multiple : true});
|
|
};
|
|
|
|
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> |