import { useState } from "react"; import { downloadAndInstallApp, launchApp, uninstallApp, } from "../lib/commands"; import type { AppEntry, InstalledApp } from "../types/manifest"; import "./AppCard.css"; interface Props { app: AppEntry; installed?: InstalledApp; onRefresh: () => void; } type ActionState = "idle" | "downloading" | "launching" | "error"; export function AppCard({ app, installed, onRefresh }: Props) { const [state, setState] = useState("idle"); const [errMsg, setErrMsg] = useState(null); const isInstalled = !!installed; const hasUpdate = isInstalled && installed.version !== app.current_version; const initial = app.name.charAt(0).toUpperCase(); async function handleInstallOrUpdate() { setState("downloading"); setErrMsg(null); try { await downloadAndInstallApp(app); onRefresh(); } catch (e: unknown) { const msg = e && typeof e === "object" && "message" in e ? String((e as { message: unknown }).message) : String(e); setErrMsg(msg); setState("error"); return; } setState("idle"); } async function handleLaunch() { setState("launching"); setErrMsg(null); try { await launchApp(app.id); } catch (e: unknown) { const msg = e && typeof e === "object" && "message" in e ? String((e as { message: unknown }).message) : String(e); setErrMsg(msg); setState("error"); return; } setState("idle"); } async function handleUninstall() { if (!confirm(`Remove ${app.name} from the launcher? (Files are kept.)`)) return; try { await uninstallApp(app.id); onRefresh(); } catch (e: unknown) { console.error(e); } } const busy = state === "downloading" || state === "launching"; return (
{hasUpdate && ( )}
{app.icon_url ? ( {app.name} ) : ( {initial} )}

{app.name}

{app.description}

{isInstalled ? ( <> {installed.version} {hasUpdate && ( → {app.current_version} )} ) : ( `v${app.current_version}` )} {app.category}
{state === "error" && errMsg && (

{errMsg}

)}
{isInstalled && ( )} {(!isInstalled || hasUpdate) && ( )} {isInstalled && ( )}
); }