// file: crates/ksp-app-config-desk/frontend/ts/splash.ts // version: 4 import { listen } from "@tauri-apps/api/event"; import { getCurrentWindow } from "@tauri-apps/api/window"; import type { SplashOrderDto } from "./bindings/ksp_app_config_desk/splash/SplashOrderDto.ts"; import { frontendDebug, frontendError, frontendInfo, frontendTrace, installFrontendConsoleBridge } from "./frontend_log"; import { invokeKsp } from "./invoke"; installFrontendConsoleBridge("splash"); let activeOpacityFrame: number | null = null; function easeInOut(value: number): number { return value < 0.5 ? 2 * value * value : 1 - Math.pow(-2 * value + 2, 2) / 2; } function normalizeDurationMs(value: number | null): number { return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : 0; } async function animateOpacity(element: HTMLElement, fromOpacity: number, toOpacity: number, durationMs: number): Promise { frontendTrace("splash", "Splash opacity animation started", { fromOpacity, toOpacity, durationMs }); if (activeOpacityFrame !== null) { cancelAnimationFrame(activeOpacityFrame); activeOpacityFrame = null; } element.style.opacity = fromOpacity.toString(); element.style.willChange = "opacity"; await new Promise(resolve => requestAnimationFrame(() => resolve())); await new Promise(resolve => { const startedAt = performance.now(); const opacityDelta = toOpacity - fromOpacity; const updateOpacity = (currentTime: number): void => { const elapsedMs = currentTime - startedAt; const rawProgress = durationMs === 0 ? 1 : Math.min(elapsedMs / durationMs, 1); element.style.opacity = (fromOpacity + opacityDelta * easeInOut(rawProgress)).toString(); if (rawProgress >= 1) { element.style.opacity = toOpacity.toString(); element.style.willChange = "auto"; activeOpacityFrame = null; resolve(); return; } activeOpacityFrame = requestAnimationFrame(updateOpacity); }; activeOpacityFrame = requestAnimationFrame(updateOpacity); }); frontendTrace("splash", "Splash opacity animation completed", { toOpacity, durationMs }); } function scrollToLatest(element: HTMLElement): void { element.scrollTop = element.scrollHeight; } function addMessage(message: string, status: string | null): void { const container = document.querySelector("#messages-container"); if (!container) { return; } const line = document.createElement("div"); const safeStatus = status === "warning" || status === "error" || status === "success" ? status : "info"; line.className = `splash-message ${safeStatus}`; line.textContent = message; container.appendChild(line); scrollToLatest(container); } function addDebugMessage(message: string): void { const container = document.querySelector("#debug-info"); if (!container) { return; } container.hidden = false; const line = document.createElement("div"); line.textContent = `${new Date().toLocaleTimeString()}: ${message}`; container.appendChild(line); scrollToLatest(container); } async function handleSplashOrder(order: SplashOrderDto): Promise { frontendTrace("splash", "Splash order received", { action: order.action }); const container = document.querySelector("#splash-container"); if (order.action === "add_message" && order.message) { addMessage(order.message, order.status); return; } if (order.action === "add_debug" && order.message) { addDebugMessage(order.message); return; } if (!container) { return; } if (order.action === "fade_in") { await animateOpacity(container, 0, 1, normalizeDurationMs(order.durationMs)); return; } if (order.action === "fade_out") { await animateOpacity(container, 1, 0, normalizeDurationMs(order.durationMs)); } } async function initializeSplash(): Promise { const windowLabel = getCurrentWindow().label; frontendInfo("splash", "Config Desk splash frontend loaded", { windowLabel }); const container = document.querySelector("#splash-container"); if (container) { container.style.opacity = "0"; container.style.willChange = "opacity"; frontendTrace("splash", "Splash container prepared for managed fade-in"); } await listen("ksp-splash-order", event => { void handleSplashOrder(event.payload); }); frontendDebug("splash", "Splash lifecycle listener installed"); try { await invokeKsp("splash", "splash_frontend_ready"); } catch { addMessage("Le lifecycle du splash n'a pas pu démarrer.", "error"); if (container) { container.style.opacity = "1"; container.style.willChange = "auto"; } frontendError("splash", "Splash readiness command failed"); } } document.addEventListener("DOMContentLoaded", () => { void initializeSplash(); });