Files
khadhroony-solana-project/crates/ksp-app-config-desk/frontend/ts/splash.ts
2026-08-16 12:32:11 +02:00

114 lines
4.3 KiB
TypeScript

// file: crates/ksp-app-config-desk/frontend/ts/splash.ts
// version: 3
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");
const defaultFadeDurationMs = 300;
let activeOpacityFrame: number | null = null;
function normalizeDurationMs(value: number | null | undefined): number {
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
return defaultFadeDurationMs;
}
return value;
}
function easeInOut(progress: number): number {
if (progress < 0.5) {
return 2 * progress * progress;
}
return 1 - Math.pow(-2 * progress + 2, 2) / 2;
}
async function animateOpacity(element: HTMLElement, fromOpacity: number, toOpacity: number, durationMs: number): Promise<void> {
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<void>(resolve => requestAnimationFrame(() => resolve()));
await new Promise<void>(resolve => {
const startedAt = performance.now();
const opacityDelta = toOpacity - fromOpacity;
const updateOpacity = (currentTime: number): void => {
const elapsedMs = currentTime - startedAt;
const rawProgress = 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 replaceStatus(message: string | null): void {
if (!message) {
return;
}
const status = document.querySelector<HTMLElement>("#splash-status");
if (status) {
status.textContent = message;
frontendTrace("splash", "Splash status replaced", { message });
}
}
async function handleSplashOrder(order: SplashOrderDto): Promise<void> {
frontendTrace("splash", "Splash order received", { action: order.action });
const container = document.querySelector<HTMLElement>("#splash-container");
replaceStatus(order.message);
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<void> {
const windowLabel = getCurrentWindow().label;
frontendInfo("splash", "Config Desk splash frontend loaded", { windowLabel });
const container = document.querySelector<HTMLElement>("#splash-container");
if (container) {
container.style.opacity = "0";
container.style.willChange = "opacity";
frontendTrace("splash", "Splash container prepared for managed fade-in");
}
await listen<SplashOrderDto>("ksp-splash-order", event => {
void handleSplashOrder(event.payload);
});
frontendDebug("splash", "Splash lifecycle listener installed");
try {
await invokeKsp<void>("splash", "splash_frontend_ready");
} catch {
replaceStatus("Le lifecycle du splash n'a pas pu démarrer.");
if (container) {
container.style.opacity = "1";
container.style.willChange = "auto";
}
frontendError("splash", "Splash readiness command failed");
}
}
document.addEventListener("DOMContentLoaded", () => {
void initializeSplash();
});