feat(plugin): add settings panel (API key, export path, MangoHud setup)
This commit is contained in:
@@ -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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user