/* ============================================================================ MAIN — el cableado ---------------------------------------------------------------------------- Aquí no hay reglas de negocio ni decisiones de diseño. Solo se conectan las piezas: los clics llaman al estado, el estado avisa a la vista, y aplicar llama al proceso. ========================================================================== */ import { crearEstado, estaListo } from "./estado.js"; import { crearVista } from "./vista.js"; import { encenderCielo, aplicarGrano } from "./cielo.js"; import { encenderAurora } from "./aurora.js"; import { aplicar } from "./proceso.js"; import { desdeArchivo, desdeEjemplo, descargar, liberar, urlResultado, EXTENSION } from "./imagen.js"; import { nombreDescarga } from "./textos.js"; import { registrar, EVENTOS } from "./uso.js"; const estado = crearEstado(); /** Cuánto dura el aplicar: sale de tokens.css (--t-aplicando). */ function duracionAplicar() { const valor = getComputedStyle(document.documentElement) .getPropertyValue("--t-aplicando").trim(); if (valor.endsWith("ms")) return parseFloat(valor); if (valor.endsWith("s")) return parseFloat(valor) * 1000; // Sin token no inventamos una duración: la decisión vive en tokens.css. console.warn("Falta el token --t-aplicando en css/tokens.css"); return 0; } const vista = crearVista({ async alElegirEjemplo(ejemplo) { const anterior = estado.obtener().imagen; const imagen = await desdeEjemplo(ejemplo); if (anterior) liberar(anterior); estado.elegirImagen(imagen); registrar(EVENTOS.ELIGE_IMAGEN, { origen: "ejemplo", nombre: ejemplo.nombre }); }, alElegirEnergia(clave) { estado.elegirEnergia(clave); registrar(EVENTOS.ELIGE_ENERGIA, { energia: clave }); }, }); const { nodos } = vista; estado.suscribir(vista.pintar); encenderCielo(); aplicarGrano(); encenderAurora({ estado }); /* ── Escuchas ────────────────────────────────────────────────────────── */ nodos.entrar.addEventListener("click", () => { estado.entrar(); registrar(EVENTOS.ENTRA); }); document.querySelectorAll("[data-abrir-archivo]").forEach((boton) => { boton.addEventListener("click", () => nodos.archivo.click()); }); nodos.archivo.addEventListener("change", async (evento) => { const archivo = evento.target.files?.[0]; if (!archivo) return; const anterior = estado.obtener().imagen; const imagen = await desdeArchivo(archivo); if (anterior) liberar(anterior); estado.elegirImagen(imagen); registrar(EVENTOS.ELIGE_IMAGEN, { origen: "propia" }); evento.target.value = ""; }); function abrirIntencion() { nodos.intencion.value = estado.obtener().intencion; nodos.modalIntencion.showModal(); } function cerrarIntencionSinGuardar() { nodos.intencion.value = estado.obtener().intencion; nodos.modalIntencion.close(); } nodos.abrirIntencion.addEventListener("click", abrirIntencion); nodos.cerrarIntencion.addEventListener("click", cerrarIntencionSinGuardar); nodos.cancelarIntencion.addEventListener("click", cerrarIntencionSinGuardar); nodos.guardarIntencion.addEventListener("click", () => { estado.escribirIntencion(nodos.intencion.value); nodos.modalIntencion.close(); }); nodos.modalIntencion.addEventListener("click", (evento) => { if (evento.target === nodos.modalIntencion) cerrarIntencionSinGuardar(); }); nodos.abrirHerramientas.addEventListener("click", vista.alternarHerramientas); nodos.girarImagen.addEventListener("click", vista.girarVista); nodos.verGrande.addEventListener("click", () => { vista.abrirVisor(estado.obtener().fase === "hecho"); }); nodos.compararImagenes.addEventListener("click", () => estado.alternarComparar()); nodos.cerrarVisor.addEventListener("click", () => nodos.visor.close()); nodos.visor.addEventListener("click", (evento) => { if (evento.target === nodos.visor) nodos.visor.close(); }); document.addEventListener("keydown", (evento) => { if (evento.key === "Escape") vista.cerrarHerramientas(); }); /* Evita que dos clics seguidos lancen dos procesos. */ let enMarcha = false; nodos.aplicar.addEventListener("click", async () => { const antes = estado.obtener(); if (enMarcha || !estaListo(antes)) return; enMarcha = true; nodos.aplicar.disabled = true; registrar(EVENTOS.APLICA, { energia: antes.energia, conIntencion: Boolean(antes.intencion.trim()), imagenPropia: !antes.imagen.ejemplo, }); try { // Primero el proceso, porque el revelado tiene que descubrir lo que sale // de él. Solo después empieza el momento, que dura lo que dice el diseño. const tratada = await aplicar({ imagen: antes.imagen, energia: antes.energia, intencion: antes.intencion, }); await vista.mostrarResultado(await urlResultado(tratada)); estado.empezarAplicar(); await new Promise((sigue) => setTimeout(sigue, duracionAplicar())); estado.terminarAplicar(tratada); } finally { enMarcha = false; } }); nodos.guardar.addEventListener("click", async () => { const { imagen, energia } = estado.obtener(); if (!imagen) return; await descargar(imagen, nombreDescarga(energia, imagen.nombre, EXTENSION)); registrar(EVENTOS.GUARDA, { energia }); }); nodos.otraVez.addEventListener("click", () => { const anterior = estado.obtener().imagen; estado.reiniciar(); if (anterior) liberar(anterior); registrar(EVENTOS.OTRA_VEZ); }); registrar(EVENTOS.ABRE);