Files
khadhroony-bot3/kb-app-demo-desktop/frontend/ts/splash.ts
2026-07-25 18:35:51 +02:00

113 lines
3.9 KiB
TypeScript

// file: kb-app-demo-desktop/frontend/ts/splash.ts
// version: 2
import { listen } from "@tauri-apps/api/event";
import type { SplashOrder } from "./bindings/kb_app_demo_desktop/splash/SplashOrder.ts";
const defaultFadeDurationMs = 500;
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> {
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);
const nextOpacity = fromOpacity + opacityDelta * easeInOut(rawProgress);
element.style.opacity = nextOpacity.toString();
if (rawProgress >= 1) {
element.style.opacity = toOpacity.toString();
element.style.willChange = "auto";
activeOpacityFrame = null;
resolve();
return;
}
activeOpacityFrame = requestAnimationFrame(updateOpacity);
};
activeOpacityFrame = requestAnimationFrame(updateOpacity);
});
}
function scrollToLatest(element: HTMLElement): void {
element.scrollTop = element.scrollHeight;
}
function addLogMessage(message: string): void {
const debugInfo = document.getElementById("debug-info");
if (!debugInfo) {
return;
}
const line = document.createElement("div");
line.textContent = `${new Date().toLocaleTimeString()}: ${message}`;
debugInfo.appendChild(line);
scrollToLatest(debugInfo);
}
function addMessage(message: string, status: string): void {
const messagesContainer = document.getElementById("messages-container");
if (!messagesContainer) {
return;
}
const messageElement = document.createElement("div");
messageElement.className = `splash-message ${status}`;
messageElement.textContent = message;
messagesContainer.appendChild(messageElement);
scrollToLatest(messagesContainer);
}
async function handleSplashOrder(order: SplashOrder): Promise<void> {
const container = document.getElementById("splash-container");
if (order.order === "fadein" && container) {
await animateOpacity(container, 0, 1, normalizeDurationMs(order.duration_ms));
return;
}
if (order.order === "fadeout" && container) {
await animateOpacity(container, 1, 0, normalizeDurationMs(order.duration_ms));
return;
}
if (order.order === "add_msg" && order.msg && order.status) {
addMessage(order.msg, order.status);
return;
}
if (order.order === "add_log" && order.msg) {
addLogMessage(order.msg);
}
}
async function initializeSplash(): Promise<void> {
const container = document.getElementById("splash-container");
if (container) {
container.style.opacity = "0";
container.style.willChange = "opacity";
}
await listen<SplashOrder>("splash", event => {
void handleSplashOrder(event.payload);
});
}
document.addEventListener("DOMContentLoaded", () => {
void initializeSplash();
});