Mapeate/Public/ecma/Mapeate.ecma.js

688 lines
26 KiB
JavaScript

Mapeate = function(entradas){
const self = this,
configuracion_por_defecto = {
nombre_objeto : "mapeate",
nulos : false,
valor_por_defecto : null,
autoiniciar : true,
archivos_de_configuracion_por_defecto : [
"/json/Mapeate.configuracion.json",
"/json/Mapeate.configuracion.secretos.json"
],
texto_por_defecto : "",
configuracion_sobreescribir : false,
i18n_sobreescribir : false,
formato_terminal : "[{tipo}] {yyyy}{mm}{dd} {hh}{ii}{ss} [{linea}]{archivo}({metodo}): {mensaje}",
terminal_tipos_por_defecto : [
["unkn", "unknown", "desconocido"],
[" ok ", "ok", "si", "correcto"],
["info", "informacion", "information"],
["note", "nota", "comment", "comentario"],
["warn", "warning", "aviso", "advertencia"],
["erro", "error", "incrrecto"],
["fail", "failure", "fallo"],
["exce", "excepcion"]
],
archivos_de_i18n_por_defecto : [
"/json/i18n/Mapeate.i18n.espanol.json"
],
ajax_timeout : 2000,
terminal_unkn_oscuro : "color : #AAA;",
terminal_unkn_claro : "color : #666;",
terminal_ok_oscuro : "color : #9F9;",
terminal_ok_claro : "color : #090;",
terminal_info_oscuro : "color : #99F;",
terminal_info_claro : "color : #009;",
terminal_note_oscuro : "color : #222;",
terminal_note_claro : "color : #EFEFEF;"
},
configuracion = {},
textos = {
espanol : {
mapeate : "Mapeate",
mapeate_construyendose : "La aplicación 'Mapéate' se está construyendo...",
mapeate_construido : "La aplicación 'Mapeate' se construyó completamente.",
cargando_configuracion : "Cargando la configuración...",
configuracion_cargada : "La configuración ha sido cargada completamente.",
cargando_terminal_tipos : "Cargando los tipos de salidas de terminal...",
terminal_tipos_cargados : "Los tipos de terminal han sido cargados completamente.",
cargando_i18n : "Cargando los textos internacionalizados...",
i18n_cargada : "Los textos internacionalizados han sido cargados completamente."
}
},
terminal_tipos = [],
hashes = [],
hilos_de_proceso = [];
let iniciado = false,
configuracion_sobreescribir = false,
idioma_por_defecto = null,
idioma = null,
modo_dispositivo = "por_defecto",
modo_gui = "por_defecto",
terminal_unkn_oscuro, terminal_unkn_claro,
terminal_ok_oscuro, terminal_ok_claro,
terminal_info_oscuro, terminal_info_claro,
terminal_note_oscuro, terminal_note_claro,
modo_gui_oscuro = window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches,
formato_terminal = "[{tipo}] {yyyy}{mm}{dd} {hh}{ii}{ss} [{linea}]{archivo}({metodo}): {mensaje}",
hash_alfabeto, hash_longitud,
intervalo_de_procesos, precarga_timeout,
construido = false;
let hash_self = this.hash_self;
let si_mismo = this.si_mismo = document;
let nombre_objeto = this.nombre_objeto;
let vistas = this.vistas;
let mapas = this.mapas;
let base = this.base;
const re_bloque_de_traza = new RegExp("^(" + [
/\s*at\s+(([^\s]+)\s+\()?(([^\(\)\:]+\:)?[^\(\)\:]+)(\:([0-9]+)\:[0-9]+)?\)?/.source, // Webkit
/([^\@]+)\@([^:]+\:[^\:]+)\:([0-9]+)\:[0-9]+/.source, // Gecko
].join("|") + ")$")
const constructor = () => {
nombre_objeto = self.nombre_objeto = self.configuracion("nombre_objeto");
configuracion_por_defecto.terminal_tipos_por_defecto.forEach(entrada => terminal_tipos.push(entrada));
establecer_variables_comunes();
self.print("info", "mapeate_construyendose");
vistas = this.vistas = new Mapeate.Vistas(self, entradas);
mapas = this.mapas = new Mapeate.Mapas(self, entradas);
base = this.base = new Mapeate.Base(self, entradas);
self.print("ok", "mapeate_construido");
self.configuracion("autoiniciar") && self.iniciar();
};
const establecer_variables_comunes = () => {
configuracion_sobreescribir = self.configuracion(["configuracion_sobreescribir", "sobreescribir"]);
formato_terminal = self.configuracion("formato_terminal");
terminal_unkn_oscuro = self.configuracion("terminal_unkn_oscuro");
terminal_unkn_claro = self.configuracion("terminal_ounkn_claro");
terminal_ok_oscuro = self.configuracion("terminal_ok_oscuro");
terminal_ok_claro = self.configuracion("terminal_ok_claro");
terminal_info_oscuro = self.configuracion("terminal_info_oscuro");
terminal_info_claro = self.configuracion("terminal_info_claro");
terminal_note_oscuro = self.configuracion("terminal_note_oscuro");
terminal_note_claro = self.configuracion("terminal_note_claro");
};
this.iniciar = callback => {
const terminar = estado => typeof callback == "function" && callback(estado);
self.print("info", "mapeate_iniciando");
if(iniciado){
self.print("warn", "mapeate_ya_iniciado");
terminar(false);
return false;
};
iniciado = true;
establecer_variables_comunes();
self.print("info", "cargando_configuracion");
self.ejecutar_array_asincrono([
"archivos_de_configuracion_por_defecto",
"archivos_de_configuracion"
], (clave, callback) => self.configuracion_anadir(self.configuracion(clave), true, () => {
establecer_variables_comunes();
callback()
}), () => {
self.print("ok", "configuracion_cargada");
self.print("info", "cargando_terminal_tipos");
self.ejecutar_array_asincrono([
"terminal_tipos_por_defecto",
"terminal_tipos"
], (clave, callback) => self.terminal_tipos_anadir(self.configuracion(clave), callback), () => {
self.print("ok", "terminal_tipos_cargados");
i18n_sobreescribir = self.configuracion(["i18n_sobreescribir", "sobreescribir"]);
idioma_por_defecto = self.configuracion(["idioma_por_defecto", "idioma"]);
idioma = self.configuracion(["idioma", "idioma_por_defecto"]);
hash_alfabeto = self.configuracion("hash_alfabeto");
hash_longitud = self.configuracion("hash_longitud");
precarga_timeout = self.configuracion("precarga_timeout");
intervalo_de_procesos = setInterval(ejecutar_hilos_de_proceso, 1000 / self.configuracion("ejecutar_hilos_de_proceso"));
self.print("info", "cargando_i18n");
self.ejecutar_array_asincrono([
"archivos_de_i18n_por_defecto",
"archivos_de_i18n"
], (clave, callback) => self.i18n_anadir(self.configuracion(clave), true, callback), () => {
self.print("ok", "i18n_cargada");
self.ejecutar_array_asincrono([vistas, mapas, base], (elemento, callback) => elemento.iniciar(callback), () => {
self.print("ok", "mapeate_iniciado");
terminar(true);
});
});
});
});
return true;
};
this.nulos = nulos => typeof nulos != "boolean" ? self.configuracion("nulos", null, false, false) : nulos;
this.valor_por_defecto = (valor_por_defecto, nulos) => (
valor_por_defecto !== undefined && (self.nulos(nulos) || valor_por_defecto !== null) ? valor_por_defecto :
self.configuracion("valor_por_defecto", null, null, true)
);
this.coger_array_de_diccionarios = valor => valor && typeof valor == "object" ? valor instanceof Array ? valor : [valor] : [];
this.coger_array_de_strings = valor => (
valor && typeof valor == "object" && valor instanceof Array ? valor : [valor]
).filter(elemento => typeof elemento == "string" && elemento.trim()).map(elemento => elemento.trim());
this.configuracion = (claves, entradas, valor_por_defecto, nulos) => {
const m = (claves = self.coger_array_de_strings(claves)).length;
if(m){
const l = (entradas = [].concat(
self.coger_array_de_diccionarios(entradas),
[entradas, configuracion, configuracion_por_defecto]
)).length;
nulos = self.nulos(nulos);
for(let i = 0; i < l; i ++)
if(entradas[i] && typeof entradas[i] == "object")
for(let j = 0; j < m; j ++)
if(entradas[i][claves[j]] !== undefined && (nulos || entradas[i][claves[j]] !== null))
return entradas[i][claves[j]];
};
return self.valor_por_defecto(valor_por_defecto, nulos);
};
this.leer_archivo = (url, entradas) => {
let terminado = false;
const ajax = new XMLHttpRequest(),
callback = self.configuracion("callback",
typeof entradas == "function" ? (entradas = {callback : entradas}) :
entradas && typeof entradas == "object" ? entradas :
{}),
timeout = self.configuracion(["ajax_timeout", "timeout"], entradas),
terminar = codigo => (
!terminado &&
(terminado = true) &&
typeof callback == "function" &&
callback(ajax.responseText, ajax.status, ajax.readyState, codigo)
),
fecha = Date.now();
ajax.open("get", url, true);
ajax.timeout = timeout;
ajax.onreadystatechange = () => {
if(terminado)
return;
if(ajax.readyState == 4)
terminar(
(ajax.status >= 200 && ajax.status < 300) || [301, 302, 304].includes(ajax.status) ? "OK" :
"HTTP_ERROR");
else if(Date.now() - fecha > timeout)
terminar("TIMEOUT_FORZADO");
};
ajax.send(null);
ajax.onabort = () => terminar("ABORTADO");
ajax.onerror = () => terminar("ERROR");
ajax.ontimeout = () => terminar("TIMEOUT");
return ajax;
};
const ejecutar_array_asincrono = (elementos, callback_parcial, callback_final, i) => {
if(!elementos || i >= elementos.length){
typeof callback_final == "function" && callback_final();
return;
};
const terminar = () => ejecutar_array_asincrono(elementos, callback_parcial, callback_final, i + 1);
if(!elementos[i]){
terminar();
return;
};
if(typeof elementos[i] == "object" && elementos[i] instanceof Array)
ejecutar_array_asincrono(elementos[i], terminar, 0);
else
typeof callback_parcial == "function" && callback_parcial(elementos[i], terminar);
};
this.ejecutar_array_asincrono = (elementos, callback_parcial, callback_final) => ejecutar_array_asincrono(
typeof elementos == "object" && elementos instanceof Array ? elementos : [elementos],
callback_parcial,
callback_final,
0
);
this.procesar_json = (string, si, no) => {
let json;
try{
json = JSON.parse(string);
}catch(excepcion){};
if(json){
typeof si == "function" && si(json);
return true;
};
typeof no == "function" && no(null);
return false;
};
const configuracion_anadir = (entradas, sobreescribir, callback, i) => {
if(i >= entradas.length){
callback();
return;
};
const terminar = () => configuracion_anadir(entradas, sobreescribir, callback, i + 1);
if(!entradas[i]){
terminar();
return;
};
if(typeof entradas[i] == "object"){
if(entradas[i] instanceof Array)
configuracion_anadir(entradas[i], sobreescribir, terminar, 0);
else{
for(const clave in entradas[i])
if(sobreescribir || configuracion[clave] === undefined)
configuracion[clave] = entradas[i][clave];
terminar();
};
}else if(typeof entradas[i] == "string"){
if(!(entradas[i] = entradas[i].trim())){
terminar();
return;
};
const procesar = string => self.procesar_json(
string,
json => configuracion_anadir(json instanceof Array ? json : [json], sobreescribir, terminar, 0),
terminar
);
if(entradas[i].match(/^(\{(.|[\r\n])+\}|\[(.|[\r\n])+\])$/))
procesar(entradas[i]);
else
self.leer_archivo(entradas[i], procesar);
}else
terminar();
};
this.configuracion_anadir = (entradas, sobreescribir, callback) => configuracion_anadir(
entradas && typeof entradas == "object" && entradas instanceof Array ? entradas : [entradas],
typeof sobreescribir == "boolean" ? sobreescribir : configuracion_sobreescribir,
callback,
0
);
const formatear_parametro = (clave, string) => ' data-' + clave.replace(/[^a-z0-9]+/g, '-') + '="' + ("" + string).replace(/"/g, "\\\"") + '"';
this.string_variables = (string, variables, por_defecto) => {
const l = (variables = self.coger_array_de_diccionarios(variables)).length;
return (string || "" + string).replace(/(nombre_objeto)/g, "{$1}").replace(/\{{2}([^\{\}]+)\}{2}|\{([^\{\}]+)\}| +data-([^=\s]+)="\[{2}\]{2}"/g, (crudo, i18n, clave, parametros) => {
if(i18n){
let texto = i18n;
const argumentos = i18n.match(/^(((?!(_descripcion)).)+)(_descripcion)?$/),
[clave, descripcion] = argumentos ? [argumentos[1], argumentos[4]] : [i18n, null];
for(let i = 0; i < l; i ++)
if(typeof variables[i][clave] == "string" && /^[a-z0-9_]+$/i.test(variables[i][clave])){
texto = variables[i][clave];
break;
};
return self.i18n(texto + (descripcion || ""), variables);
}if(parametros){
for(let i = 0; i < l; i ++)
switch(typeof variables[i][parametros]){
case "object":
let html = '';
for(const clave in variables[i][parametros])
html += formatear_parametro(clave, variables[i][parametros][clave]);
return html;
case "string":
default:
if(variables[i][parametros] !== undefined)
return formatear_parametro(parametros, variables[i][parametros])
};
return "";
};
for(let i = 0; i < l; i ++)
if(variables[i][clave] !== undefined)
return variables[i][clave];
return por_defecto === undefined ? crudo : por_defecto;
});
};
const i18n = (claves, por_defecto) => {
const m = (claves = self.coger_array_de_strings(claves)).length,
idiomas = [idioma_por_defecto, idioma].concat(Object.keys(textos)),
l = idiomas.length;
for(let i = 0; i < l; i ++)
if(typeof idiomas[i] == "string" && textos[idiomas[i]])
for(let j = 0; j < m; j ++)
if(textos[idiomas[i]][claves[j]] !== undefined)
return textos[idiomas[i]][claves[j]];
return (
por_defecto !== undefined ? por_defecto :
m ? claves[0] :
self.configuracion("texto_por_defecto"));
};
this.i18n = (claves, variables, por_defecto) => self.string_variables(i18n(claves, por_defecto), variables);
const i18n_anadir = (entradas, sobreescribir, callback, i) => {
if(i >= entradas.length){
typeof callback == "function" && callback();
return;
};
const terminar = () => i18n_anadir(entradas, sobreescribir, callback, i + 1);
if(!entradas[i]){
terminar();
return;
};
if(typeof entradas[i] == "object"){
if(entradas[i] instanceof Array)
i18n_anadir(entradas[i], sobreescribir, terminar, 0);
else{
for(const lenguage in entradas[i]){
textos[lenguage] === undefined && (textos[lenguage] = {});
if(entradas[i][lenguage] && typeof entradas[i][lenguage] == "object")
for(const clave in entradas[i][lenguage])
if(sobreescribir || textos[lenguage][clave] === undefined)
textos[lenguage][clave] = entradas[i][lenguage][clave];
};
terminar();
};
}else if(typeof entradas[i] == "string"){
if(!(entradas[i] = entradas[i].trim())){
terminar();
return;
};
const procesar = string => self.procesar_json(
string,
json => i18n_anadir(json instanceof Array ? json : [json], sobreescribir, terminar, 0),
terminar
);
if(entradas[i].match(/^(\{(.|[\r\n])+\}|\[(.|[\r\n])+\])$/))
procesar(entradas[i]);
else
self.leer_archivo(entradas[i], procesar);
}else
terminar();
};
this.i18n_anadir = (entradas, sobreescribir, callback) => i18n_anadir(
entradas && typeof entradas == "object" && entradas instanceof Array ? entradas : [entradas],
typeof sobreescribir == "boolean" ? sobreescribir : i18n_sobreescribir,
callback,
0
);
const terminal_tipos_anadir = (entradas, callback, i, clave) => {
if(i > 10)
return;
if(!entradas || i >= entradas.length){
typeof callback == "function" && callback();
return;
};
const terminar = () => terminal_tipos_anadir(entradas, callback, i + 1, clave);
if(!entradas[i]){
terminar();
return;
};
if(typeof entradas[i] == "string"){
if(/^ *[a-z0-9]+ *$/i.test(entradas[i])){
if(clave === undefined && !terminal_tipos.some((tipos, j) => tipos.includes(entradas[i]) && !isNaN(clave = j))){
clave = terminal_tipos.length;
terminal_tipos.push([entradas[i]]);
}else
!terminal_tipos[clave].includes(entradas[i]) && terminal_tipos[clave].push(entradas[i]);
terminar();
return;
};
const procesar = string => self.procesar_json(
string,
json => terminal_tipos_anadir(json instanceof Array ? json : [json], terminar, 0),
terminar
);
if(/^(\[(.|[\r\n])+\])$/.test(entradas[i]))
procesar(entradas[i]);
else
self.leer_archivo(entradas[i], procesar);
}else if(typeof entradas[i] == "object" && entradas[i] instanceof Array)
terminal_tipos_anadir(entradas[i], terminar, 0);
else
terminar();
};
this.es_dispositivo_movil = () => modo_dispositivo == "movil" || (modo_dispositivo != "escritorio" && es_movil);
this.modo_gui_oscuro = () => modo_gui == "oscuro" || (modo_gui != "claro" && modo_gui_oscuro);
this.terminal_tipos_anadir = (entradas, callback) => terminal_tipos_anadir(entradas, callback, 0);
this.coger_id_tipo_terminal = tipo => {
const l = terminal_tipos.length;
if(typeof tipo == "string" && (tipo = tipo.trim().toLowerCase()))
for(let i = 0; i < l; i ++)
if(terminal_tipos[i].includes(tipo))
return i;
return 0;
};
this.coger_tipo_terminal = tipo => terminal_tipos[self.coger_id_tipo_terminal(tipo)][0].toUpperCase();
this.coger_traza = i => (new Error()).stack.replace(/^Error\s*?[\r\n]+/, "").trim().split(/[\r\n]+/).slice(1 + (i || 0)).map(linea => {
const matches = linea.match(re_bloque_de_traza);
return matches ? {
archivo : matches[4] || matches[9],
metodo : matches[3] || matches[8] || "",
linea : Number(matches[7] || matches[10])
} : null;
}).filter(linea => linea);
this.print = (tipo, mensaje, variables, i) => {
const fecha = new Date(),
conjunto = {
tipo_crudo : tipo,
tipo : self.coger_tipo_terminal(tipo),
i18n : mensaje,
...(typeof variables == "object" ? variables : {}),
...(self.coger_traza(isNaN(i) ? 1 : i)[0] || {
linea : -1,
metodo : "UNKNOWN",
archivo : "UNKNOWN"
})
};
let mensaje_procesado;
conjunto.line = conjunto.linea;
conjunto.method = conjunto.metodo;
conjunto.file = conjunto.archivo;
["year", "month", "date", "hours", "minutes", "seconds"].forEach(clave => {
let k = clave.substring(0, 1);
const valor = fecha["get" + (clave == "year" ? "FullYear" : k.toUpperCase() + clave.substring(1))]();
conjunto[clave == "date" ? "day" : clave] = valor;
conjunto[clave == "minutes" ? k = "i" : k] = valor;
conjunto[k + k] = ("00" + valor).slice(-2);
});
conjunto["yyyy"] = conjunto["year"];
conjunto.mensaje = self.i18n(mensaje, conjunto);
mensaje_procesado = self.string_variables(formato_terminal, conjunto);
switch(conjunto.tipo){
case " OK ":
console.log("%c" + mensaje_procesado, self.modo_gui_oscuro() ? terminal_ok_oscuro : terminal_ok_claro);
break;
case "INFO":
console.log("%c" + mensaje_procesado, self.modo_gui_oscuro() ? terminal_info_oscuro : terminal_info_claro);
break;
case "UNKN":
console.log("%c" + mensaje_procesado, self.modo_gui_oscuro() ? terminal_note_oscuro : terminal_note_claro);
break;
case "ERRO":
case "FAIL":
case "EXCE":
console.error(mensaje_procesado);
break;
case "WARN":
console.warn(mensaje_procesado);
break;
case "UNKN":
default:
console.log("%c" + mensaje_procesado, self.modo_gui_oscuro() ? terminal_unkn_oscuro : terminal_unkn_claro);
break;
};
};
this.hash = () => {
let hash;
const l = hash_alfabeto.length;
do{
hash = "";
while((hash += hash_alfabeto[Math.random() * l >> 0]).length < hash_longitud);
}while(
hashes.includes(hash) ||
/^[0-9]/.test(hash) ||
document.querySelector("#" + hash + ",." + hash + ",[name=" + hash + "]")
);
hashes.push(hash);
return hash;
};
const ejecutar_hilos_de_proceso = () => hilos_de_proceso.forEach(hilo => hilo && hilo());
this.anadir_hilo_de_proceso = metodo => {
if(typeof metodo != "function")
return null;
let i = 0;
const l = hilos_de_proceso.length;
for(; i < l; i ++)
if(!hilos_de_proceso[i])
break;
hilos_de_proceso[i] = metodo;
return i;
};
this.eliminar_hilo_de_proceso = i => !isNaN(i) && hilos_de_proceso[i] && (hilos_de_proceso[i] = null);
this.precargar = (selector, callback) => {
const terminar = elemento => typeof callback == "function" && callback(elemento);
if(!selector)
terminar(null);
else if(selector.nodeName || selector.tagName)
terminar(selector);
else if(typeof selector == "string" && (selector = selector.trim())){
let elemento;
try{
if(elemento = si_mismo.querySelector(selector)){
terminar(elemento);
return;
};
}catch(excepcion){
terminar(null);
return;
};
const fecha = Date.now(),
precarga = self.anadir_hilo_de_proceso(() => {
if(elemento = si_mismo.querySelector(selector)){
self.eliminar_hilo_de_proceso(precarga);
terminar(elemento);
}else if(Date.now() - fecha > precarga_timeout){
self.eliminar_hilo_de_proceso(precarga);
terminar(null);
};
});
}else
terminar(null);
};
this.coger_hash = () => hash_self || (hash_self = self.hash_self = mapeate.hash());
this.establecer_si_mismo = elemento => (!si_mismo.constructor || si_mismo.constructor.name == "HTMLDocument") && (si_mismo = self.si_mismo = elemento);
constructor();
};