237 lines
8.3 KiB
JavaScript
237 lines
8.3 KiB
JavaScript
/* WAM · interactivity.js — dueño D (núcleo).
|
|
Flujos de clic (quick replies, filas de lista, botones de plantilla y de card),
|
|
composer libre, indicador "escribiendo…", ticks automáticos y modo
|
|
"reproducir demo" paso a paso. No toca el DOM al cargar. */
|
|
window.WAM = window.WAM || {};
|
|
(function () {
|
|
"use strict";
|
|
|
|
var CANCELADO = "wam:cancelado";
|
|
var TIPOS_VALIDOS = [
|
|
"text", "image", "video", "audio", "document", "sticker", "location",
|
|
"contact", "template", "buttons", "list", "cta_url", "carousel"
|
|
];
|
|
|
|
/* Todo temporizador pasa por WAM.espera(); cancelarTodo() aborta los flujos
|
|
y la reproducción de la demo en curso. */
|
|
var cancelaciones = [];
|
|
var demoAndando = false;
|
|
|
|
function quitar(fn) {
|
|
var i = cancelaciones.indexOf(fn);
|
|
if (i !== -1) cancelaciones.splice(i, 1);
|
|
}
|
|
|
|
function cancelarTodo() {
|
|
var copia = cancelaciones.slice();
|
|
cancelaciones = [];
|
|
for (var i = 0; i < copia.length; i++) copia[i]();
|
|
}
|
|
|
|
function escribiendo(visible) {
|
|
if (typeof WAM.setEscribiendo === "function") WAM.setEscribiendo(visible);
|
|
}
|
|
|
|
/* Promise cancelable: se rechaza con CANCELADO si detenerDemo() la aborta. */
|
|
WAM.espera = function (ms) {
|
|
return new Promise(function (resolver, rechazar) {
|
|
var id = setTimeout(function () {
|
|
quitar(cancelar);
|
|
resolver();
|
|
}, ms);
|
|
function cancelar() {
|
|
clearTimeout(id);
|
|
quitar(cancelar);
|
|
rechazar(CANCELADO);
|
|
}
|
|
cancelaciones.push(cancelar);
|
|
});
|
|
};
|
|
|
|
WAM.demoActiva = function () { return demoAndando; };
|
|
|
|
function avisarDemo() {
|
|
if (typeof WAM.onDemoChange === "function") {
|
|
try { WAM.onDemoChange(demoAndando); } catch (e) { /* el hook es opcional */ }
|
|
}
|
|
}
|
|
|
|
/* ————————————————— alta de mensajes (mutación + guardar + DOM incremental) ————————————————— */
|
|
|
|
function anadir(msg) {
|
|
WAM.estado.mensajes.push(msg);
|
|
WAM.guardar();
|
|
if (typeof WAM.anadirMensajeDOM === "function") WAM.anadirMensajeDOM(msg);
|
|
}
|
|
|
|
function mensajeOut(texto) {
|
|
return {
|
|
id: WAM.generarId(),
|
|
dir: "out",
|
|
tipo: "text",
|
|
hora: WAM.horaAhora(),
|
|
estado: "sent",
|
|
texto: String(texto == null ? "" : texto)
|
|
};
|
|
}
|
|
|
|
/* Copia una respuesta anidada (Mensaje completo) para poder reutilizarla
|
|
cada vez que se vuelve a pulsar el mismo botón. */
|
|
function mensajeDesde(origen) {
|
|
var copia;
|
|
try {
|
|
copia = JSON.parse(JSON.stringify(
|
|
(origen && typeof origen === "object") ? origen : { tipo: "text", texto: String(origen == null ? "" : origen) }
|
|
));
|
|
} catch (e) {
|
|
copia = { tipo: "text", texto: "" };
|
|
}
|
|
if (!copia || typeof copia !== "object" || Array.isArray(copia)) copia = { tipo: "text", texto: "" };
|
|
if (TIPOS_VALIDOS.indexOf(copia.tipo) === -1) copia.tipo = "text";
|
|
copia.id = WAM.generarId();
|
|
copia.dir = "in";
|
|
copia.hora = WAM.horaAhora();
|
|
copia.estado = "sent";
|
|
return copia;
|
|
}
|
|
|
|
/* Ticks automáticos sent → delivered → read sobre la burbuja OUT indicada.
|
|
Se localiza por data-id del msg (no "la última fila") para que dos envíos
|
|
solapados avancen cada uno su propio tick. */
|
|
function repintarTickDe(msg, estado) {
|
|
var cuerpo = document.getElementById("chat-cuerpo");
|
|
if (!cuerpo || typeof WAM.tick !== "function") return;
|
|
if (!msg || typeof msg.id !== "string" || !msg.id) return;
|
|
var seguro = msg.id.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
var fila = cuerpo.querySelector('.wa-row[data-id="' + seguro + '"]');
|
|
if (!fila || !fila.classList.contains("out")) return;
|
|
var tick = fila.querySelector(".tick");
|
|
if (tick && tick.parentNode) {
|
|
/* WAM.tick devuelve un <svg> completo: se REEMPLAZA el nodo para no
|
|
anidar un svg dentro de otro (se recortaría el segundo tick). */
|
|
var aux = document.createElement("span");
|
|
aux.innerHTML = WAM.tick(estado);
|
|
if (aux.firstChild) tick.parentNode.replaceChild(aux.firstChild, tick);
|
|
}
|
|
}
|
|
|
|
async function animarTics(msg) {
|
|
await WAM.espera(800);
|
|
msg.estado = "delivered";
|
|
WAM.guardar();
|
|
repintarTickDe(msg, "delivered");
|
|
await WAM.espera(800);
|
|
msg.estado = "read";
|
|
WAM.guardar();
|
|
repintarTickDe(msg, "read");
|
|
}
|
|
|
|
/* ————————————————— flujos ————————————————— */
|
|
|
|
/* Burbuja OUT con el texto (ticks auto), typing ~900 ms y, por cada respuesta,
|
|
typing ~900 ms + anadirMensajeDOM (con scroll). Cancelable por detenerDemo(). */
|
|
WAM.flujo = function (texto, respuestas) {
|
|
var lista = Array.isArray(respuestas) ? respuestas : [];
|
|
var secuencia = (async function () {
|
|
try {
|
|
var out = mensajeOut(texto);
|
|
anadir(out);
|
|
await animarTics(out);
|
|
for (var i = 0; i < lista.length; i++) {
|
|
escribiendo(true);
|
|
await WAM.espera(900);
|
|
escribiendo(false);
|
|
anadir(mensajeDesde(lista[i]));
|
|
}
|
|
escribiendo(false);
|
|
} catch (e) {
|
|
escribiendo(false);
|
|
if (e !== CANCELADO) { /* el mockup nunca ensucia la consola */ }
|
|
}
|
|
})();
|
|
return secuencia;
|
|
};
|
|
|
|
/* Envío libre del composer: burbuja OUT con hora actual y ticks automáticos. */
|
|
WAM.enviarLibre = function (texto) {
|
|
var limpio = String(texto == null ? "" : texto).trim();
|
|
if (!limpio) return;
|
|
var out = mensajeOut(limpio);
|
|
anadir(out);
|
|
var tics = animarTics(out);
|
|
if (typeof tics.catch === "function") tics.catch(function () { /* cancelado */ });
|
|
};
|
|
|
|
/* Resuelve { texto, respuestas } desde los data-* que estampa render.js:
|
|
data-flujo (índice del mensaje) + data-btn (template/buttons),
|
|
data-sec + data-fila (lista) o data-card (carousel). */
|
|
WAM.resolverFlujo = function (el) {
|
|
var vacio = { texto: "", respuestas: [] };
|
|
if (!el || typeof el.getAttribute !== "function") return vacio;
|
|
var indice = parseInt(el.getAttribute("data-flujo"), 10);
|
|
var mensajes = (WAM.estado && WAM.estado.mensajes) ? WAM.estado.mensajes : [];
|
|
var msg = isNaN(indice) ? null : mensajes[indice];
|
|
if (!msg) return vacio;
|
|
var ds = el.dataset || {};
|
|
|
|
if (ds.sec !== undefined || ds.fila !== undefined) {
|
|
var seccion = (msg.secciones || [])[parseInt(ds.sec, 10)];
|
|
var fila = ((seccion && seccion.filas) || [])[parseInt(ds.fila, 10)];
|
|
return fila ? { texto: fila.titulo || "", respuestas: fila.respuestas || [] } : vacio;
|
|
}
|
|
if (ds.btn !== undefined) {
|
|
var boton = (msg.botones || [])[parseInt(ds.btn, 10)];
|
|
return boton ? { texto: boton.texto || "", respuestas: boton.respuestas || [] } : vacio;
|
|
}
|
|
if (ds.card !== undefined) {
|
|
var card = (msg.cards || [])[parseInt(ds.card, 10)];
|
|
return card ? { texto: card.textoBoton || "", respuestas: card.respuestas || [] } : vacio;
|
|
}
|
|
/* Elemento plano de un mensaje list: es el botón que abre la sheet
|
|
(main.js lo detecta y llama WAM.abrirSheet); sin flujo directo. */
|
|
return vacio;
|
|
};
|
|
|
|
/* ————————————————— reproducción de la demo ————————————————— */
|
|
|
|
WAM.reproducirDemo = function () {
|
|
if (demoAndando) return;
|
|
demoAndando = true;
|
|
avisarDemo();
|
|
var cuerpo = document.getElementById("chat-cuerpo");
|
|
if (cuerpo) cuerpo.innerHTML = "";
|
|
var secuencia = ((WAM.estado && WAM.estado.mensajes) || []).slice();
|
|
(async function () {
|
|
try {
|
|
for (var i = 0; i < secuencia.length; i++) {
|
|
var msg = secuencia[i];
|
|
if (!msg) continue;
|
|
if (msg.dir === "in") {
|
|
escribiendo(true);
|
|
await WAM.espera(700);
|
|
escribiendo(false);
|
|
}
|
|
if (typeof WAM.anadirMensajeDOM === "function") WAM.anadirMensajeDOM(msg);
|
|
await WAM.espera(600);
|
|
}
|
|
demoAndando = false;
|
|
escribiendo(false);
|
|
avisarDemo();
|
|
} catch (e) {
|
|
demoAndando = false;
|
|
escribiendo(false);
|
|
avisarDemo();
|
|
}
|
|
})();
|
|
};
|
|
|
|
WAM.detenerDemo = function () {
|
|
var estaba = demoAndando;
|
|
demoAndando = false;
|
|
cancelarTodo(); /* aborta también flujos y typing en curso */
|
|
escribiendo(false);
|
|
if (estaba && typeof WAM.render === "function") WAM.render();
|
|
if (estaba) avisarDemo();
|
|
};
|
|
})();
|