// file: crates/ksp-app-wallet-desk/frontend/ts/main.ts // version: 4 import "bootstrap"; import DataTable from "datatables.net-bs5"; import "datatables.net-select-bs5"; import ResizeObserver from "resize-observer-polyfill"; import "simplebar"; import { getCurrentWindow } from "@tauri-apps/api/window"; import type { RuntimeStatusDto } from "./bindings/ksp_app_wallet_desk/dto_common/RuntimeStatusDto.ts"; import type { LockedWalletDto } from "./bindings/ksp_app_wallet_desk/wallet_inventory/LockedWalletDto.ts"; import type { WalletInventoryEntryDto } from "./bindings/ksp_app_wallet_desk/wallet_inventory/WalletInventoryEntryDto.ts"; import type { WalletSelectionRequestDto } from "./bindings/ksp_app_wallet_desk/wallet_inventory/WalletSelectionRequestDto.ts"; import { frontendDebug, frontendInfo, frontendTrace, frontendWarn, installFrontendConsoleBridge } from "./frontend_log"; import { invokeKsp } from "./invoke"; import "../sass/main.scss"; (window as Window & typeof globalThis & { ResizeObserver?: typeof ResizeObserver }).ResizeObserver = ResizeObserver; installFrontendConsoleBridge("main"); type ViewId = "dashboard" | "wallets" | "create-import" | "details" | "security" | "diagnostics"; const viewTitles: Record = { dashboard: "Dashboard", wallets: "Wallets", "create-import": "Create / Import", details: "Details", security: "Security", diagnostics: "Diagnostics", }; function isViewId(value: string): value is ViewId { return value in viewTitles; } function activateView(viewId: ViewId, source: "startup" | "user"): void { if (source === "user") { frontendDebug("main", "Wallet Desk navigation activated", { viewId }); } const title = viewTitles[viewId]; const headerTitle = document.querySelector("#headerViewTitle"); const viewTitle = document.querySelector("#viewTitle"); if (headerTitle) { headerTitle.textContent = title; } if (viewTitle) { viewTitle.textContent = title; } document.title = `Wallet Desk — ${title}`; document.querySelectorAll("[data-view]").forEach(button => { const active = button.dataset.view === viewId; button.classList.toggle("active", active); button.setAttribute("aria-current", active ? "page" : "false"); }); document.querySelectorAll("[data-view-panel]").forEach(panel => { panel.hidden = panel.dataset.viewPanel !== viewId; }); frontendTrace("main", "Wallet Desk view DOM updated", { viewId, source }); } function bindFrontendInteractions(): void { document.addEventListener( "click", event => { const source = event.target; if (!(source instanceof Element)) { return; } const control = source.closest('button, a, input, select, textarea, [role="button"], [role="tab"], [data-view], [data-shell-action]'); if (!control) { return; } frontendTrace("main", "Frontend control clicked", { controlId: control.id || null, controlType: control instanceof HTMLButtonElement ? control.type : null, disabled: control instanceof HTMLButtonElement ? control.disabled : control.getAttribute("aria-disabled") === "true", role: control.getAttribute("role"), shellAction: control.dataset.shellAction ?? null, tagName: control.tagName.toLowerCase(), viewId: control.dataset.view ?? null, }); }, true, ); frontendTrace("main", "Frontend control interaction logger installed"); } function bindNavigation(): void { document.querySelectorAll("[data-view]").forEach(button => { button.addEventListener("click", () => { const requestedView = button.dataset.view; frontendTrace("main", "Wallet Desk navigation clicked", { requestedView: requestedView ?? null }); if (requestedView && isViewId(requestedView)) { activateView(requestedView, "user"); } }); }); frontendTrace("main", "Wallet Desk navigation handlers installed"); } function appendIconText(cell: HTMLTableCellElement, iconClass: string, text: string): void { const icon = document.createElement("i"); icon.className = `fa-solid ${iconClass} me-2`; icon.setAttribute("aria-hidden", "true"); const label = document.createElement("span"); label.textContent = text; cell.append(icon, label); } function renderWalletInventoryRow(entry: WalletInventoryEntryDto): HTMLTableRowElement { const row = document.createElement("tr"); row.dataset.walletId = entry.walletId; row.dataset.walletSelectable = entry.inspectionStatus === "valid" ? "true" : "false"; row.dataset.inspectionStatus = entry.inspectionStatus; const state = document.createElement("td"); if (entry.state === "locked") { appendIconText(state, "fa-lock", "Locked"); } else { appendIconText(state, "fa-triangle-exclamation", "Erreur"); } const filename = document.createElement("td"); filename.textContent = entry.filename; const format = document.createElement("td"); format.textContent = entry.formatVersion === null ? "—" : entry.formatVersion.toString(); const view = document.createElement("td"); view.textContent = entry.viewEnabled === null ? "—" : entry.viewEnabled ? "enabled" : "disabled"; const inspection = document.createElement("td"); if (entry.inspectionStatus === "valid") { inspection.textContent = "Valide"; } else if (entry.diagnostic) { inspection.textContent = `Invalide — ${entry.diagnostic.domain}.${entry.diagnostic.code}`; inspection.title = entry.diagnostic.message; } else { inspection.textContent = "Invalide"; } row.append(state, filename, format, view, inspection); return row; } function initializeWalletTable(): void { if (DataTable.isDataTable("#walletInventoryTable")) { new DataTable("#walletInventoryTable").destroy(); } new DataTable("#walletInventoryTable", { order: [[1, "asc"]], pageLength: 10, select: { selector: "tbody tr[data-wallet-selectable='true'] td", style: "single", }, language: { emptyTable: "Aucun wallet .kspwallet disponible.", search: "Filtrer :", zeroRecords: "Aucun wallet correspondant.", }, }); frontendDebug("main", "Wallet inventory DataTable initialized", { phase: "pre.004-wallet-inventory" }); } function renderWalletInventory(entries: WalletInventoryEntryDto[]): void { if (DataTable.isDataTable("#walletInventoryTable")) { new DataTable("#walletInventoryTable").destroy(); } const body = document.querySelector("#walletInventoryBody"); if (body) { body.replaceChildren(...entries.map(entry => renderWalletInventoryRow(entry))); } initializeWalletTable(); const invalidCount = entries.filter(entry => entry.inspectionStatus === "invalid").length; frontendDebug("main", "Wallet inventory rendered", { entryCount: entries.length, invalidCount }); } function clearSelectedWallet(): void { const icon = document.querySelector("#currentWalletIcon"); const filename = document.querySelector("#currentWalletFilename"); const state = document.querySelector("#currentWalletState"); const format = document.querySelector("#currentWalletFormat"); const view = document.querySelector("#currentWalletView"); if (icon) { icon.className = "fa-solid fa-lock fa-2x mb-3 text-body-secondary"; } if (filename) { filename.textContent = "Aucun wallet sélectionné."; } if (state) { state.textContent = "—"; } if (format) { format.textContent = "—"; } if (view) { view.textContent = "—"; } } function renderSelectedWallet(wallet: LockedWalletDto): void { const icon = document.querySelector("#currentWalletIcon"); const filename = document.querySelector("#currentWalletFilename"); const state = document.querySelector("#currentWalletState"); const format = document.querySelector("#currentWalletFormat"); const view = document.querySelector("#currentWalletView"); if (icon) { icon.className = "fa-solid fa-lock fa-2x mb-3"; } if (filename) { filename.textContent = wallet.filename; } if (state) { state.textContent = "Locked"; } if (format) { format.textContent = wallet.formatVersion.toString(); } if (view) { view.textContent = wallet.viewEnabled ? "enabled" : "disabled"; } frontendDebug("main", "Locked Wallet selection rendered", { walletId: wallet.walletId, formatVersion: wallet.formatVersion, viewEnabled: wallet.viewEnabled }); } async function selectWallet(walletId: string): Promise { const request: WalletSelectionRequestDto = { walletId }; try { const wallet = await invokeKsp("main", "select_wallet", { request }); renderSelectedWallet(wallet); } catch { clearSelectedWallet(); frontendWarn("main", "Locked Wallet selection failed", { walletId }); } } function bindWalletTableSelection(): void { const table = document.querySelector("#walletInventoryTable"); if (!table) { return; } table.addEventListener("click", event => { const source = event.target; if (!(source instanceof Element)) { return; } const row = source.closest("tbody tr[data-wallet-id]"); if (!row) { return; } const walletId = row.dataset.walletId; if (!walletId) { return; } if (row.dataset.walletSelectable !== "true") { frontendDebug("main", "Invalid Wallet inventory row selection ignored", { walletId }); return; } frontendTrace("main", "Wallet inventory row selected", { walletId }); void selectWallet(walletId); }); frontendTrace("main", "Wallet inventory row selection handler installed"); } async function loadWalletInventory(command: "list_wallets" | "refresh_wallets"): Promise { frontendDebug("main", "Wallet inventory load requested", { command }); const entries = await invokeKsp("main", command); clearSelectedWallet(); renderWalletInventory(entries); } function renderRuntimeStatus(status: RuntimeStatusDto): void { const version = document.querySelector("#runtimeVersion"); const compositeProfile = document.querySelector("#runtimeCompositeProfile"); const loggingProfile = document.querySelector("#runtimeLoggingProfile"); const walletProfile = document.querySelector("#runtimeWalletProfile"); const fallback = document.querySelector("#runtimeLoggingFallback"); const documents = document.querySelector("#runtimeConfigDocuments"); const walletsRoot = document.querySelector("#runtimeWalletsDirectory"); const walletsSubdirectory = document.querySelector("#runtimeWalletsSubdirectory"); const effectiveWalletsDirectory = document.querySelector("#runtimeEffectiveWalletsDirectory"); const walletDirectoryCreated = document.querySelector("#runtimeWalletDirectoryCreated"); const phase = document.querySelector("#runtimeShellPhase"); const shellStatus = document.querySelector("#shellStatus"); if (version) { version.textContent = status.applicationVersion; } if (compositeProfile) { compositeProfile.textContent = status.activeCompositeProfile; } if (loggingProfile) { loggingProfile.textContent = status.activeLoggingProfile ?? "fallback transitoire"; } if (walletProfile) { walletProfile.textContent = status.activeWalletProfile; } if (fallback) { fallback.textContent = status.fallbackLoggingActive ? "oui" : "non"; } if (documents) { documents.textContent = status.configDocumentCount.toString(); } if (walletsRoot) { walletsRoot.textContent = status.walletsDirectory; } if (walletsSubdirectory) { walletsSubdirectory.textContent = status.walletsSubdirectory ?? "— (racine globale)"; } if (effectiveWalletsDirectory) { effectiveWalletsDirectory.textContent = status.effectiveWalletsDirectory; } if (walletDirectoryCreated) { walletDirectoryCreated.textContent = status.effectiveWalletsDirectoryCreatedOnStartup ? "oui" : "non, déjà présent"; } if (phase) { phase.textContent = status.shellPhase; } if (shellStatus) { shellStatus.textContent = "Config résolue ; inventaire Wallet prêt."; } frontendTrace("main", "Wallet Desk runtime status rendered", { compositeProfile: status.activeCompositeProfile, fallbackLoggingActive: status.fallbackLoggingActive, shellPhase: status.shellPhase, walletDirectoryCreatedOnStartup: status.effectiveWalletsDirectoryCreatedOnStartup, walletProfile: status.activeWalletProfile, }); } async function loadRuntimeStatus(): Promise { const status = await invokeKsp("main", "get_runtime_status"); renderRuntimeStatus(status); } function bindShellActions(): void { document.querySelectorAll("[data-shell-action]").forEach(button => { button.addEventListener("click", () => { const action = button.dataset.shellAction ?? "unknown"; frontendDebug("main", "Wallet Desk shell action clicked", { action, enabled: !button.disabled }); if (action === "refresh-wallets" && !button.disabled) { void loadWalletInventory("refresh_wallets").catch(() => { frontendWarn("main", "Wallet inventory refresh failed"); }); } }); }); frontendTrace("main", "Wallet Desk shell action handlers installed"); } async function initializeMain(): Promise { const windowLabel = getCurrentWindow().label; frontendInfo("main", "Wallet Desk main frontend loaded", { windowLabel }); bindFrontendInteractions(); bindNavigation(); bindWalletTableSelection(); bindShellActions(); activateView("dashboard", "startup"); try { await loadRuntimeStatus(); await loadWalletInventory("list_wallets"); } catch { renderWalletInventory([]); const shellStatus = document.querySelector("#shellStatus"); if (shellStatus) { shellStatus.textContent = "Le statut runtime ou l'inventaire Wallet n'a pas pu être chargé."; } frontendWarn("main", "Wallet Desk startup data load failed"); } } document.addEventListener("DOMContentLoaded", () => { void initializeMain(); });