import { useEffect, useState, useRef } from "react" import { PanelSectionRow, DialogButtonPrimary, staticClasses } from "@decky/ui" import { Router } from "@decky/ui" import { fetchPluginGame, setPluginApiBaseUrl, type PluginGameResponse, } from "../lib/plugin-api" import { getHardwareInfo } from "../lib/api" interface Props { appId: number title: string hardwareSlug: string | null // detected device (from settings, may be null) baseUrl: string } function openExternalUrl(url: string) { try { if (Router?.NavigateToExternalWeb) { Router.NavigateToExternalWeb(url) return } } catch (e) { console.error("[DeckyVault] NavigateToExternalWeb failed:", e) } try { window.open(url, "_blank") } catch (e) { console.error("[DeckyVault] window.open failed:", e) } } export default function LibraryAppPanel({ appId, title, hardwareSlug, baseUrl }: Props) { const [data, setData] = useState(null) const [loading, setLoading] = useState(true) const [fetchError, setFetchError] = useState("") const [detectedSlug, setDetectedSlug] = useState(hardwareSlug ?? null) const reqIdRef = useRef(0) useEffect(() => { setPluginApiBaseUrl(baseUrl) let cancelled = false const id = ++reqIdRef.current async function load() { setLoading(true) try { // Detect hardware if not set in settings let slug = hardwareSlug ?? null if (!slug) { try { const hw = await getHardwareInfo() if (hw.slug && hw.slug !== "unknown") slug = hw.slug } catch { // Fall back to global } if (!cancelled) setDetectedSlug(slug) } const d = await fetchPluginGame(appId, slug, 3) if (cancelled || id !== reqIdRef.current) return setFetchError(d.error && !d.game ? d.error : "") setData(d) setLoading(false) } catch { if (!cancelled && id === reqIdRef.current) { setData(null) setFetchError("Request failed") setLoading(false) } } } load() return () => { cancelled = true } }, [appId, baseUrl]) // re-fetch only when app changes const gameUrl = data?.game?.steamAppId ? `${baseUrl}/game/${data.game.steamAppId}` : `${baseUrl}/games` if (loading) { return (
DeckyVault loading…
) } if (fetchError) { return (
DeckyVault: {fetchError}
) } if (!data || !data.game) { return (
Not on DeckyVault — add it
) } return (
{/* Stats */} {data.estFps ? ( {data.estFps.avg} avg {data.estFps.onePct != null && {data.estFps.onePct} 1% low} {data.estFps.low != null && {data.estFps.low} min} {data.estFps.high != null && {data.estFps.high} max} {data.estFps.tdpAvg != null && {data.estFps.tdpAvg}W TDP} ({data.estFps.count}) ) : ( No data yet )} {/* Device scope badge */} {detectedSlug ? detectedSlug : ⚠ global} {/* View Details — constrained width */}
openExternalUrl(gameUrl)}> View Details
) }