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.", mapeate_iniciando : "La aplicación 'Mapeate' se está iniciando...", 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.", mapeate_vistas_construyendose : "El módulo de gestión de vistas de la aplicación 'Mapeatea' se está construyendo...", mapeate_vistas_construido : "El módulo de gestión de vistas de la aplicación 'Mapeatea' se construyó completamente.", mapeate_mapas_construyendose : "El módulo de gestión de mapas de la aplicación 'Mapeatea' se está construyendo...", mapeate_mapas_construido : "El módulo de gestión de mapas de la aplicación 'Mapeatea' se construyó completamente.", mapeate_mapas_selector_construyendose : "El módulo de gestión de selección de mapas de la aplicación 'Mapeatea' se está construyendo...", mapeate_mapas_selector_construido : "El módulo de gestión de selección de mapas de la aplicación 'Mapeatea' se construyó completamente.", mapeate_base_construyendose : "El módulo de gestión básica de DPIs de la aplicación 'Mapeatea' se está construyendo...", mapeate_base_construido : "El módulo de gestión básica de DPIs de la aplicación 'Mapeatea' se construyó completamente." } }, terminal_tipos = [], hashes = [], hilos_de_proceso = []; let iniciado = false, configuracion_sobreescribir = false, i18n_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, claves_padre_por_defecto = ["tagName", "nodeName"]; 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.ejecutar = (callback, ...argumentos) => typeof callback == "function" && callback(...argumentos); this.iniciar = callback => { const terminar = estado => self.ejecutar(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(); self.ejecutar(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"); const nuevas_claves_padre = self.coger_claves(self.configuracion(("claves_padre_por_defecto", "claves_padre"))); 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"); nuevas_claves_padre.length && (claves_padre_por_defecto = nuevas_claves_padre); 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 = valor => valor && typeof valor == "object" && valor instanceof Array ? valor : [valor]; 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.coger_claves = valor => self.coger_array_de_strings(valor).filter(elemento => elemento); 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) && self.ejecutar(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){ self.ejecutar(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 self.ejecutar(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; }; this.cargar_diccionarios = (entradas, callback_parcial, callback_final, i) => { if(!entradas || (i || (i = 0)) >= entradas.length){ self.ejecutar(callback_final); return; }; const terminar = () => self.cargar_diccionarios(entradas, callback_parcial, callback_final, i + 1); if(!entradas[i]) terminar(); else if(typeof entradas[i] == "string") self.procesar_json(entradas[i], json => self.cargar_diccionarios(self.coger_array(json), callback_parcial, terminar, 0), () => { self.leer_archivo(entradas[i], respuesta => self.procesar_json(respuesta, json => self.cargar_diccionarios(self.coger_array(json), callback_parcial, terminar, 0), terminar)); }); else if(typeof entradas[i] == "object"){ if(entradas[i] instanceof Array) self.cargar_diccionarios(entradas[i], callback_parcial, terminar, 0); else self.ejecutar(callback_parcial, entradas[i], terminar); }else terminar(); }; this.configuracion_anadir = (entradas, sobreescribir, callback) => { typeof sobreescribir == "boolean" || (sobreescribir = configuracion_sobreescribir); self.cargar_diccionarios(self.coger_array(entradas), (nueva_configuracion, callback) => { for(const clave in nueva_configuracion) (sobreescribir || configuracion[clave] === undefined) && (configuracion[clave] = nueva_configuracion[clave]); self.ejecutar(callback); }, callback, 0); }; const formatear_parametro = (clave, string) => ' ' + ([ "onclick", "onmouseover", "onmouseout", "onmousedown", "onmouseup", "onmousemove" ].includes(clave) ? '' : '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, valores_por_defecto = { nombre_objeto : nombre_objeto, hash_self : hash_self }; 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]; if(valores_por_defecto[clave] !== undefined) return valores_por_defecto[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); this.i18n_anadir = (entradas, sobreescribir, callback) => { typeof sobreescribir == "boolean" || (sobreescribir = i18n_sobreescribir); self.cargar_diccionarios(self.coger_array_de_strings(entradas), (nuevos_textos, callback) => { for(const idioma in nuevos_textos){ textos[idioma] || (textos[idioma] = {}); if(nuevos_textos[idioma] && typeof nuevos_textos[idioma] == "object") for(const clave in nuevos_textos[idioma]) (sobreescribir || textos[idioma][clave] === undefined) && (textos[idioma][clave] = nuevos_textos[idioma][clave]); }; self.ejecutar(callback) }, 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 => { if(typeof callback == "function") try{ callback(elemento); }catch(exception){ console.error(exception); }; }; 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); this.porcentuar = (x1, y1, x2, y2, cubrir) => { const r1 = x1 / y1, r2 = x2 / y2; if((cubrir && r1 > r2) || (!cubrir && r1 < r2)){ const x = x1 * y2 / y1; return [(x2 - x) / 2, 0, x, y2]; }; const y = y1 * x2 / x1; return [0, (y2 - y) / 2, x2, y]; }; this.consola = (texto, variables) => { si_mismo && (si_mismo.querySelector(".juego .consola section").appendChild(document.createElement("div")).innerText = self.i18n(texto, variables)); self.print("test", texto, variables); }; this.coger_padre = (elemento, objetivo, atributos) => { (atributos = self.coger_claves(atributos)).length || (atributos = claves_padre_por_defecto); objetivo = objetivo.toLowerCase(); while(elemento && elemento.hasAttribute && !atributos.some(atributo => elemento[atributo].toLowerCase() == objetivo) && (elemento = elemento.parentNode)); return (elemento && elemento.hasAttribute && elemento) || null; }; this.alerta = (mensaje, callback) => { alert(self.i18n(mensaje)); self.ejecutar(callback); }; this.confirmar = (mensaje, si, no) => self.ejecutar(confirm(self.i18n(mensaje)) ? si : no); constructor(); };