// file: crates/ksp-app-solprices-desk/frontend/ts/main.ts // version: 5 import "bootstrap"; import ResizeObserver from "resize-observer-polyfill"; import "simplebar"; import { getCurrentWindow } from "@tauri-apps/api/window"; import type { MarketPriceRuntimeStatusDto } from "./bindings/ksp_app_solprices_desk/dto_common/MarketPriceRuntimeStatusDto.ts"; import type { MarketPriceProviderRowDto } from "./bindings/ksp_app_solprices_desk/market_price_runtime/MarketPriceProviderRowDto.ts"; import { frontendDebug, frontendInfo, frontendTrace, frontendWarn, installFrontendConsoleBridge } from "./frontend_log"; import { invokeKsp } from "./invoke"; (window as Window & typeof globalThis & { ResizeObserver?: typeof ResizeObserver }).ResizeObserver = ResizeObserver; installFrontendConsoleBridge("main"); type ViewId = "prices" | "diagnostics"; const viewTitles: Record = { prices: "Prices", diagnostics: "Diagnostics", }; let currentMarketPriceRows: MarketPriceProviderRowDto[] = []; function isViewId(value: string | undefined): value is ViewId { return value === "prices" || value === "diagnostics"; } function activateView(viewId: ViewId, source: "startup" | "user"): void { document.querySelectorAll("[data-view-panel]").forEach(panel => { panel.hidden = panel.dataset.viewPanel !== viewId; }); document.querySelectorAll("[data-view]").forEach(button => { button.classList.toggle("active", button.dataset.view === viewId); }); const header = document.querySelector("#headerViewTitle"); if (header) { header.textContent = viewTitles[viewId]; } document.title = `SOL Prices Desk — ${viewTitles[viewId]}`; if (source !== "startup") { frontendDebug("main", "SOL Prices Desk view activated", { viewId, source }); } } function bindNavigation(): void { document.querySelectorAll("[data-view]").forEach(button => { button.addEventListener("click", () => { const viewId = button.dataset.view; frontendDebug("main", "SOL Prices Desk navigation control clicked", { viewId: viewId ?? "unknown" }); if (isViewId(viewId)) { activateView(viewId, "user"); } }); }); frontendTrace("main", "SOL Prices Desk navigation handlers installed"); } function appendMarketPriceCell(row: HTMLTableRowElement, value: string): void { const cell = document.createElement("td"); cell.textContent = value; row.append(cell); } function clearPricesDiagnostic(): void { const diagnostic = document.querySelector("#pricesDiagnostic"); if (diagnostic) { diagnostic.hidden = true; diagnostic.textContent = ""; } } function showPricesDiagnostic(message: string): void { const diagnostic = document.querySelector("#pricesDiagnostic"); if (diagnostic) { diagnostic.hidden = false; diagnostic.textContent = message; } } async function refreshMarketPrice(providerId: string): Promise { frontendDebug("main", "SOL Prices Desk market-price refresh control clicked", { providerId }); clearPricesDiagnostic(); currentMarketPriceRows = currentMarketPriceRows.map(provider => { return provider.providerId === providerId ? { ...provider, loading: true } : provider; }); renderMarketPriceRows(currentMarketPriceRows); try { const refreshed = await invokeKsp("main", "refresh_market_price", { providerId }); currentMarketPriceRows = currentMarketPriceRows.map(provider => { return provider.providerId === refreshed.providerId ? refreshed : provider; }); renderMarketPriceRows(currentMarketPriceRows); frontendTrace("main", "SOL Prices Desk market-price refresh row applied", { providerId }); } catch { frontendWarn("main", "SOL Prices Desk market-price refresh failed", { providerId }); showPricesDiagnostic("Le provider sélectionné n'a pas pu être rafraîchi. L'état précédent est conservé lorsqu'il reste disponible."); try { await loadMarketPrices(); } catch { frontendWarn("main", "SOL Prices Desk market-price refresh recovery reload failed", { providerId }); } } } function appendRefreshCell(row: HTMLTableRowElement, provider: MarketPriceProviderRowDto): void { const cell = document.createElement("td"); const button = document.createElement("button"); button.type = "button"; button.className = "btn btn-sm btn-outline-primary text-nowrap"; button.disabled = provider.loading; button.dataset.providerId = provider.providerId; button.innerHTML = provider.loading ? 'Refreshing…' : 'Refresh'; button.addEventListener("click", () => { void refreshMarketPrice(provider.providerId); }); cell.append(button); row.append(cell); } function renderMarketPriceRows(rows: MarketPriceProviderRowDto[]): void { const loading = document.querySelector("#pricesLoading"); const empty = document.querySelector("#pricesEmpty"); const tableContainer = document.querySelector("#pricesTableContainer"); const tableBody = document.querySelector("#marketPriceRows"); if (loading) { loading.hidden = true; } if (!tableBody || !tableContainer || !empty) { frontendWarn("main", "SOL Prices Desk market-price table elements are missing"); return; } tableBody.replaceChildren(); empty.hidden = rows.length !== 0; tableContainer.hidden = rows.length === 0; for (const provider of rows) { const row = document.createElement("tr"); row.dataset.providerId = provider.providerId; appendMarketPriceCell(row, provider.displayName); appendMarketPriceCell(row, provider.pair); appendMarketPriceCell(row, provider.semantics); appendMarketPriceCell(row, provider.authMode); appendMarketPriceCell(row, provider.loading ? "loading" : provider.availability); appendMarketPriceCell(row, provider.price ?? "—"); appendMarketPriceCell(row, provider.providerTimestampUnixMillis ?? "—"); appendMarketPriceCell(row, provider.receivedAtUnixMillis ?? "—"); appendMarketPriceCell(row, provider.retryAtUnixMillis ?? "—"); appendRefreshCell(row, provider); tableBody.append(row); } frontendTrace("main", "SOL Prices Desk market-price registry rows rendered", { rowCount: rows.length }); } function renderRuntimeStatus(status: MarketPriceRuntimeStatusDto): void { const values: Record = { runtimeCompositeProfile: status.activeCompositeProfile, runtimeOffchainProfile: status.activeOffchainProfile, runtimeVersion: status.applicationVersion, runtimeShellPhase: status.shellPhase, runtimeConfigDocuments: status.configDocumentCount.toString(), runtimeProviderCount: status.providerCount.toString(), runtimeReadyProviderCount: status.readyProviderCount.toString(), runtimeUnavailableProviderCount: status.unavailableProviderCount.toString(), runtimeLoggingProfile: status.activeLoggingProfile ?? "fallback", runtimeLoggingFallback: status.fallbackLoggingActive ? "oui" : "non", }; for (const [elementId, value] of Object.entries(values)) { const element = document.querySelector(`#${elementId}`); if (element) { element.textContent = value; } } const diagnostic = document.querySelector("#runtimeDiagnostic"); if (diagnostic) { diagnostic.hidden = status.startupDiagnostic === null; diagnostic.textContent = status.startupDiagnostic === null ? "" : `${status.startupDiagnostic.domain}:${status.startupDiagnostic.code} — ${status.startupDiagnostic.message}`; } frontendTrace("main", "SOL Prices Desk market-price runtime status rendered", { fallbackLoggingActive: status.fallbackLoggingActive, configDocumentCount: status.configDocumentCount, providerCount: status.providerCount, readyProviderCount: status.readyProviderCount, unavailableProviderCount: status.unavailableProviderCount, }); } async function loadMarketPrices(): Promise { frontendTrace("main", "SOL Prices Desk provider registry load started"); const rows = await invokeKsp("main", "list_market_prices"); currentMarketPriceRows = rows; renderMarketPriceRows(rows); frontendTrace("main", "SOL Prices Desk provider registry load completed", { rowCount: rows.length }); } async function loadRuntimeStatus(): Promise { const status = await invokeKsp("main", "get_runtime_status"); renderRuntimeStatus(status); } async function initializeMain(): Promise { const windowLabel = getCurrentWindow().label; frontendInfo("main", "SOL Prices Desk main frontend loaded", { windowLabel }); bindNavigation(); activateView("prices", "startup"); try { await loadRuntimeStatus(); } catch { const diagnostic = document.querySelector("#runtimeDiagnostic"); if (diagnostic) { diagnostic.hidden = false; diagnostic.textContent = "Le statut runtime initial n'a pas pu être chargé."; } frontendWarn("main", "SOL Prices Desk startup runtime status load failed"); } try { await loadMarketPrices(); } catch { const loading = document.querySelector("#pricesLoading"); if (loading) { loading.hidden = true; } showPricesDiagnostic("L'inventaire provider-neutral n'a pas pu être chargé."); frontendWarn("main", "SOL Prices Desk provider registry load failed"); } } document.addEventListener("DOMContentLoaded", () => { void initializeMain(); });