Files
whatsapp-api-mockup/js/main.js
T

311 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* WAM · main.js — dueño D (núcleo).
Bootstrap: carga el estado, aplica tema, renderiza chat y editor,
y enlaza la delegación de clics del chat, el composer, la reproducción
de la demo (velocidad, reiniciar chat) y la toolbar. Único script con
ejecución al cargar. */
window.WAM = window.WAM || {};
(function () {
"use strict";
function porId(id) {
return document.getElementById(id);
}
function al(id, evento, manejador) {
var el = porId(id);
if (el) el.addEventListener(evento, manejador);
}
/* ————————————————— aviso visible (toast) ————————————————— */
/* Aviso flotante accesible. Estilos por CSSOM (compatibles con una CSP
style-src 'self'); no depende de ningún CSS ajeno. */
var avisoTimer = null;
WAM.avisar = function (texto, opciones) {
var op = opciones || {};
var caja = porId("wam-aviso");
if (!caja) {
caja = document.createElement("div");
caja.id = "wam-aviso";
caja.setAttribute("role", "alert");
caja.setAttribute("aria-live", "assertive");
var s = caja.style;
s.position = "fixed";
s.left = "50%";
s.bottom = "20px";
s.transform = "translateX(-50%)";
s.zIndex = "9999";
s.maxWidth = "min(560px, calc(100vw - 32px))";
s.padding = "12px 40px 12px 16px";
s.borderRadius = "12px";
s.font = "500 14px/1.4 -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif";
s.boxShadow = "0 8px 28px rgba(0,0,0,.28)";
var cerrar = document.createElement("button");
cerrar.type = "button";
cerrar.setAttribute("aria-label", "Cerrar aviso");
cerrar.textContent = "×";
var c = cerrar.style;
c.position = "absolute";
c.top = "6px";
c.right = "8px";
c.border = "0";
c.background = "transparent";
c.color = "inherit";
c.font = "600 20px/1 Arial, sans-serif";
c.cursor = "pointer";
cerrar.addEventListener("click", function () { caja.hidden = true; });
var txt = document.createElement("span");
txt.className = "wam-aviso-texto";
caja.appendChild(txt);
caja.appendChild(cerrar);
document.body.appendChild(caja);
}
var error = op.tipo !== "info";
caja.style.background = error ? "#b3261e" : "#1f2c33";
caja.style.color = "#fff";
caja.querySelector(".wam-aviso-texto").textContent = String(texto == null ? "" : texto);
caja.hidden = false;
if (avisoTimer) clearTimeout(avisoTimer);
avisoTimer = null;
if (op.duracion) {
avisoTimer = setTimeout(function () { caja.hidden = true; }, op.duracion);
}
};
/* ————————————————— botón "Reproducir demo" ————————————————— */
function pintarBotonDemo() {
var btn = porId("btn-reproducir");
if (!btn) return;
var activa = WAM.demoActiva();
btn.setAttribute("aria-pressed", activa ? "true" : "false");
var span = btn.querySelector("span");
if (!span) return;
if (!span.dataset.etiqueta) span.dataset.etiqueta = span.textContent;
span.textContent = activa ? "■ Detener" : span.dataset.etiqueta;
}
/* Evita que un doble clic arranque y detenga la demo en el mismo gesto. */
var ultimoReproducir = 0;
function alReproducir() {
var ahora = Date.now();
if (ahora - ultimoReproducir < 350) return;
ultimoReproducir = ahora;
if (WAM.demoActiva()) WAM.detenerDemo();
else WAM.reproducirDemo();
pintarBotonDemo();
}
function alReiniciarChat() {
if (typeof WAM.reiniciarChat === "function") WAM.reiniciarChat();
else WAM.render();
pintarBotonDemo();
}
function alVelocidad(e) {
var v = WAM.setVelocidad(e.currentTarget.value);
e.currentTarget.value = String(v);
}
/* ————————————————— toolbar ————————————————— */
function alTema() {
WAM.estado.tema = WAM.estado.tema === "oscuro" ? "claro" : "oscuro";
WAM.guardar();
WAM.setTema(WAM.estado.tema);
}
function alPresentacion() {
document.body.classList.toggle("presentacion");
var btn = porId("btn-presentacion");
if (btn) {
btn.setAttribute("aria-pressed",
document.body.classList.contains("presentacion") ? "true" : "false");
}
}
function reRenderTotal() {
paso(function () { WAM.setTema(WAM.estado.tema); });
paso(function () { WAM.render(); });
paso(function () { WAM.renderEditor(); });
pintarBotonDemo();
}
function alExportar() {
if (typeof WAM.guardarYa === "function") WAM.guardarYa();
if (typeof WAM.exportarJSON === "function") WAM.exportarJSON();
}
function alImportar() {
var abrir = function () {
var archivo = porId("importar-archivo");
if (archivo) archivo.click();
};
/* Hook opcional de js/ux.js: <dialog> con «Exportar antes» */
if (typeof WAM.confirmarReemplazo === "function") { WAM.confirmarReemplazo("importar", abrir); return; }
abrir();
}
function alArchivoImport(e) {
var input = e.currentTarget;
var archivo = input.files && input.files[0];
input.value = ""; /* permite reimportar el mismo archivo */
if (!archivo || typeof WAM.importarJSON !== "function") return;
WAM.detenerDemo();
var promesa;
try { promesa = WAM.importarJSON(archivo); } catch (err) { promesa = Promise.reject(err); }
if (promesa && typeof promesa.then === "function") {
promesa.then(reRenderTotal, function () {
reRenderTotal();
WAM.avisar("No se pudo importar: el archivo no es un JSON válido de la demo.", { duracion: 6000 });
});
} else {
reRenderTotal();
}
}
function alReset() {
var ejecutar = function () {
WAM.detenerDemo();
WAM.resetear();
reRenderTotal();
};
/* Hook opcional de js/ux.js: <dialog> con «Exportar antes» + deshacer */
if (typeof WAM.confirmarReemplazo === "function") { WAM.confirmarReemplazo("reset", ejecutar); return; }
var nombre = (WAM.estado && WAM.estado.cuenta && WAM.estado.cuenta.nombre) || "la demo";
if (!window.confirm("¿Restablecer la demo «Café Aurora»? Se perderán los cambios no exportados de «" +
nombre + "».")) return;
WAM.detenerDemo();
WAM.resetear();
reRenderTotal();
}
/* ————————————————— chat: delegación de clics (contrato B→D) ————————————————— */
/* Enlaces del chat con esquema no permitido (javascript:, data:…) no se abren. */
function bloquearEnlaceInseguro(e) {
var a = (e.target && e.target.closest) ? e.target.closest("a[href]") : null;
if (!a) return false;
var href = a.getAttribute("href");
if (typeof WAM.urlSegura === "function" && !WAM.urlSegura(href, "enlace")) {
e.preventDefault();
return true;
}
return false;
}
function alClicChat(e) {
if (bloquearEnlaceInseguro(e)) return;
var objetivo = e.target;
var el = (objetivo && objetivo.closest) ? objetivo.closest("[data-flujo]") : null;
if (!el) return;
e.preventDefault(); /* protege frente a botones montados sobre <a href="#"> */
var ds = el.dataset || {};
if (ds.fila !== undefined) return; /* filas del sheet: se atienden en #pantalla */
if (el.classList.contains("usado") || el.getAttribute("aria-disabled") === "true") return;
var msg = WAM.mensajeDeElemento(el);
var plano = ds.btn === undefined && ds.sec === undefined && ds.card === undefined;
if (plano) {
if (msg && msg.tipo === "list") WAM.abrirLista(msg);
return;
}
var flujo = WAM.resolverFlujo(el);
if (!flujo || !flujo.msg) return;
WAM.marcarUsado(el); /* gris + bloquea el doble clic */
WAM.flujo(flujo.texto, flujo.respuestas, flujo.cita);
}
/* Opciones y cierre de la hoja de lista (vive en #pantalla, fuera de #chat-cuerpo). */
function alClicPantalla(e) {
var objetivo = e.target;
if (!objetivo || !objetivo.closest) return;
var hoja = porId("sheet");
if (!hoja || !hoja.contains(objetivo)) return;
if (objetivo.closest(".sheet-cerrar")) {
e.preventDefault();
WAM.cerrarSheet();
return;
}
var fila = objetivo.closest("[data-fila]");
if (!fila) return;
e.preventDefault();
var flujo = WAM.resolverFlujo(fila);
WAM.cerrarSheet();
if (flujo && flujo.msg) WAM.flujo(flujo.texto, flujo.respuestas, flujo.cita);
}
/* Teclado: Enter / Espacio activan los botones del teléfono (span role=button);
Escape cierra la hoja de lista. */
function alTeclaPantalla(e) {
if (e.key === "Escape") {
if (porId("sheet")) { e.preventDefault(); WAM.cerrarSheet(); }
return;
}
if (e.key !== "Enter" && e.key !== " " && e.key !== "Spacebar") return;
var t = e.target;
if (!t || !t.closest || t.tagName === "A" || t.tagName === "BUTTON" ||
t.tagName === "INPUT" || t.tagName === "TEXTAREA") return;
var el = t.closest("[data-flujo],[data-ver-todas],.sheet-cerrar");
if (!el || el !== t) return;
e.preventDefault();
el.click();
}
/* ————————————————— composer ————————————————— */
function alComposer(e) {
e.preventDefault();
var input = porId("composer-input");
var texto = input ? input.value : "";
WAM.enviarLibre(texto);
if (input) {
input.value = "";
input.focus();
}
}
/* ————————————————— bootstrap ————————————————— */
/* Un fallo en un módulo (render o editor) no debe dejar sin enlazar el
resto: cada paso del arranque se aísla. */
function paso(fn) {
try { fn(); } catch (e) {
setTimeout(function () { throw e; }, 0); /* visible en consola, sin cortar init */
}
}
WAM.init = function () {
WAM.onErrorGuardar = function (texto) { WAM.avisar(texto); };
paso(function () { WAM.estado = WAM.cargar(); });
if (!WAM.estado) WAM.estado = WAM.normalizar(WAM.datosDemo());
paso(function () { WAM.setTema(WAM.estado.tema); });
paso(function () { WAM.render(); });
paso(function () { WAM.renderEditor(); });
WAM.onDemoChange = pintarBotonDemo;
al("chat-cuerpo", "click", alClicChat);
al("pantalla", "click", alClicPantalla);
al("pantalla", "keydown", alTeclaPantalla);
al("composer", "submit", alComposer);
al("btn-reproducir", "click", alReproducir);
al("btn-reiniciar", "click", alReiniciarChat);
var vel = porId("sel-velocidad");
if (vel) {
vel.value = String(WAM.getVelocidad());
vel.addEventListener("change", alVelocidad);
}
al("btn-tema", "click", alTema);
al("btn-presentacion", "click", alPresentacion);
al("btn-exportar", "click", alExportar);
al("btn-importar", "click", alImportar);
al("importar-archivo", "change", alArchivoImport);
al("btn-reset", "click", alReset);
pintarBotonDemo();
};
WAM.init();
})();