feat(plugin): add settings panel (API key, export path, MangoHud setup)

This commit is contained in:
2026-06-28 07:58:34 +08:00
parent 092071e410
commit 82fbd86fb0
@@ -0,0 +1,266 @@
import { useState } from "react"
import {
ButtonItem,
PanelSection,
PanelSectionRow,
Field,
DropdownItem,
staticClasses,
} from "@decky/ui"
import {
FaCheck,
FaTimes,
FaDownload,
FaCog,
} from "react-icons/fa"
import type { PluginSettings } from "../lib/store"
import { KNOWN_HARDWARE_SLUGS } from "@deckyvault/shared"
import { testApiKey, checkMangohud, writeMangohudConfig, getMangohudConfig } from "../lib/api"
interface SettingsPanelProps {
settings: PluginSettings
onUpdateSetting: <K extends keyof PluginSettings>(
key: K,
value: string | null
) => void
}
const HARDWARE_OPTIONS = [
{ label: "Auto-detect", value: "" },
...KNOWN_HARDWARE_SLUGS.map((slug) => ({ label: slug, value: slug })),
]
export default function SettingsPanel({
settings,
onUpdateSetting,
}: SettingsPanelProps) {
const [keyTestStatus, setKeyTestStatus] = useState<"idle" | "testing" | "valid" | "invalid">("idle")
const [keyTestMessage, setKeyTestMessage] = useState("")
const [mangohudStatus, setMangohudStatus] = useState<{
checked: boolean
installed: boolean
path: string
version: string
}>({ checked: false, installed: false, path: "", version: "" })
const [showMangohudGuide, setShowMangohudGuide] = useState(false)
const [configWritten, setConfigWritten] = useState(false)
async function handleTestKey() {
if (!settings.apiKey) {
setKeyTestStatus("invalid")
setKeyTestMessage("Enter an API key first")
return
}
setKeyTestStatus("testing")
setKeyTestMessage("")
const result = await testApiKey(settings.apiKey, settings.baseUrl)
if (result.valid) {
setKeyTestStatus("valid")
setKeyTestMessage("API key is valid")
} else {
setKeyTestStatus("invalid")
setKeyTestMessage(result.error || "Invalid API key")
}
}
async function handleCheckMangohud() {
const result = await checkMangohud()
setMangohudStatus({
checked: true,
installed: result.installed,
path: result.path,
version: result.version,
})
}
async function handleWriteConfig() {
const result = await writeMangohudConfig()
setConfigWritten(result.success)
}
return (
<>
{/* ── API Key ─────────────────────────────────────────────── */}
<PanelSection title="DeckyVault Account">
<PanelSectionRow>
<Field label="API Key" bottomSeparator="none">
<input
type="password"
value={settings.apiKey}
onChange={(e) => onUpdateSetting("apiKey", e.target.value)}
placeholder="dv_..."
style={{ width: "100%", padding: "4px 8px" }}
/>
</Field>
</PanelSectionRow>
<PanelSectionRow>
<ButtonItem layout="below" onClick={handleTestKey} disabled={keyTestStatus === "testing"}>
{keyTestStatus === "testing" ? "Testing..." : "Test Key"}
{keyTestStatus === "valid" && <FaCheck style={{ color: "#2ecc71", marginLeft: "8px" }} />}
{keyTestStatus === "invalid" && <FaTimes style={{ color: "#e74c3c", marginLeft: "8px" }} />}
</ButtonItem>
</PanelSectionRow>
{keyTestMessage && (
<PanelSectionRow>
<div
className={staticClasses.Text}
style={{
fontSize: "12px",
color: keyTestStatus === "valid" ? "#2ecc71" : "#e74c3c",
padding: "4px 0",
}}
>
{keyTestMessage}
</div>
</PanelSectionRow>
)}
<PanelSectionRow>
<div className={staticClasses.Text} style={{ fontSize: "11px", opacity: 0.6, padding: "4px 0" }}>
Get your API key from DeckyVault Profile Settings API Keys
</div>
</PanelSectionRow>
</PanelSection>
{/* ── Export Path ─────────────────────────────────────────── */}
<PanelSection title="Export">
<PanelSectionRow>
<Field label="Export Path" bottomSeparator="none">
<input
type="text"
value={settings.exportPath}
onChange={(e) => onUpdateSetting("exportPath", e.target.value)}
placeholder="/home/deck/Downloads"
style={{ width: "100%", padding: "4px 8px" }}
/>
</Field>
</PanelSectionRow>
<PanelSectionRow>
<Field label="Server URL" bottomSeparator="none">
<input
type="text"
value={settings.baseUrl}
onChange={(e) => onUpdateSetting("baseUrl", e.target.value)}
placeholder="https://deckyvault.xyz"
style={{ width: "100%", padding: "4px 8px" }}
/>
</Field>
</PanelSectionRow>
<PanelSectionRow>
<DropdownItem
label="Default Hardware"
rgOptions={HARDWARE_OPTIONS}
selectedOption={settings.hardwareSlug || ""}
onChange={(opt) => onUpdateSetting("hardwareSlug", opt.data as string || null)}
/>
</PanelSectionRow>
</PanelSection>
{/* ── MangoHud Setup ──────────────────────────────────────── */}
<PanelSection title="MangoHud Setup">
<PanelSectionRow>
<ButtonItem layout="below" onClick={handleCheckMangohud}>
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
<FaCog />
Check MangoHud Status
</div>
</ButtonItem>
</PanelSectionRow>
{mangohudStatus.checked && (
<PanelSectionRow>
<div className={staticClasses.Text} style={{ fontSize: "13px", padding: "8px 0" }}>
{mangohudStatus.installed ? (
<>
<FaCheck style={{ color: "#2ecc71" }} /> MangoHud installed
<br />
<span style={{ opacity: 0.7 }}>
Path: {mangohudStatus.path}
<br />
Version: {mangohudStatus.version}
</span>
</>
) : (
<>
<FaTimes style={{ color: "#e74c3c" }} /> MangoHud not found
<br />
<span style={{ opacity: 0.7 }}>See installation guide below</span>
</>
)}
</div>
</PanelSectionRow>
)}
<PanelSectionRow>
<ButtonItem layout="below" onClick={handleWriteConfig}>
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
<FaDownload />
Write MangoHud Config
</div>
</ButtonItem>
</PanelSectionRow>
{configWritten && (
<PanelSectionRow>
<div className={staticClasses.Text} style={{ fontSize: "12px", color: "#2ecc71", padding: "4px 0" }}>
<FaCheck /> Config written to ~/.config/MangoHud/MangoHud.conf
</div>
</PanelSectionRow>
)}
<PanelSectionRow>
<ButtonItem layout="below" onClick={() => setShowMangohudGuide(!showMangohudGuide)}>
{showMangohudGuide ? "Hide Guide" : "Show Installation Guide"}
</ButtonItem>
</PanelSectionRow>
{showMangohudGuide && (
<PanelSectionRow>
<div className={staticClasses.Text} style={{ fontSize: "12px", padding: "8px", lineHeight: "1.6" }}>
<strong>Steam Deck (SteamOS):</strong>
<br />
MangoHud is pre-installed. Enable it per-game by adding
<code style={{ display: "block", margin: "4px 0", padding: "4px", background: "rgba(255,255,255,0.1)" }}>
mangohud %command%
</code>
to the game's Steam launch options (right-click game Properties Launch Options).
<br /><br />
<strong>Other Linux handhelds</strong> (ROG Ally, Legion Go):
<br />
Install via package manager:
<code style={{ display: "block", margin: "4px 0", padding: "4px", background: "rgba(255,255,255,0.1)" }}>
sudo apt install mangohud
</code>
or Flatpak:
<code style={{ display: "block", margin: "4px 0", padding: "4px", background: "rgba(255,255,255,0.1)" }}>
flatpak install flathub org.freedesktop.Platform.VulkanLayer.MangoHud
</code>
<br /><br />
<strong>Manual build:</strong>
<br />
See{" "}
<a href="https://github.com/flightlessmango/MangoHud" style={{ color: "#66c0f4" }}>
github.com/flightlessmango/MangoHud
</a>
<br /><br />
<strong>Troubleshooting:</strong>
<br />
Log file empty? Check MangoHud is enabled for the game and the config was written.
<br />
Wrong path? Ensure the plugin can write to /tmp/.
<br />
Not attaching? Try adding <code>mangohud %command%</code> to Steam launch options explicitly.
</div>
</PanelSectionRow>
)}
</PanelSection>
</>
)
}