import { useEffect, useState } from "react";
import {
ConfirmModal,
Field,
PanelSection,
PanelSectionRow,
ToggleField,
showModal,
} from "@decky/ui";
import {
checkFlatpakExtensionStatus,
FlatpakApp,
FlatpakAppInfo,
FlatpakExtensionStatus,
getFlatpakApps,
installFlatpakExtension,
removeFlatpakAppOverride,
setFlatpakAppOverride,
uninstallFlatpakExtension,
} from "../api/lsfgApi";
import { showErrorToast } from "../utils/toastUtils";
import t from "../i18n/i18n";
const runtimeVersions = [
{ version: "23.08", key: "installed_23_08" },
{ version: "24.08", key: "installed_24_08" },
{ version: "25.08", key: "installed_25_08" },
] as const;
interface RuntimeRowProps {
version: string;
installed: boolean;
busy: boolean;
onAction: () => void;
}
function RuntimeRow({ version, installed, busy, onAction }: RuntimeRowProps) {
return (
onAction()}
disabled={busy}
/>
);
}
interface AppRowProps {
app: FlatpakApp;
busy: boolean;
onToggle: () => void;
}
function AppRow({ app, busy, onToggle }: AppRowProps) {
const configured = app.has_filesystem_override && app.has_env_override;
const partial = app.has_filesystem_override || app.has_env_override;
const status = configured
? t("FLATPAK_STATUS_CONFIGURED", "Configured")
: partial
? t("FLATPAK_STATUS_PARTIAL", "Partial")
: t("FLATPAK_STATUS_NO_OVERRIDES", "No overrides");
return (
);
}
export function FlatpaksTab() {
const [extensionStatus, setExtensionStatus] = useState(null);
const [apps, setApps] = useState(null);
const [loading, setLoading] = useState(true);
const [operation, setOperation] = useState(null);
const [error, setError] = useState(null);
const load = async () => {
setLoading(true);
try {
const [nextStatus, nextApps] = await Promise.all([
checkFlatpakExtensionStatus(),
getFlatpakApps(),
]);
setExtensionStatus(nextStatus);
setApps(nextApps);
} catch (loadError) {
setError(String(loadError));
} finally {
setLoading(false);
}
};
useEffect(() => {
void load();
}, []);
const runExtensionOperation = async (version: string, installed: boolean) => {
const action = installed ? "uninstall" : "install";
setOperation(`${action}-${version}`);
setError(null);
try {
const result = installed
? await uninstallFlatpakExtension(version)
: await installFlatpakExtension(version);
if (!result.success) throw new Error(result.error || result.message);
setExtensionStatus(await checkFlatpakExtensionStatus());
} catch (operationError) {
const message = String(operationError);
setError(message);
showErrorToast("Flatpak operation failed", message);
} finally {
setOperation(null);
}
};
const confirmExtensionOperation = (version: string, installed: boolean) => {
if (!installed) {
void runExtensionOperation(version, false);
return;
}
showModal(
void runExtensionOperation(version, true)}
onCancel={() => {}}
/>,
);
};
const toggleApp = async (app: FlatpakApp) => {
const configured = app.has_filesystem_override && app.has_env_override;
setOperation(`app-${app.app_id}`);
setError(null);
try {
const result = configured
? await removeFlatpakAppOverride(app.app_id)
: await setFlatpakAppOverride(app.app_id);
if (!result.success) throw new Error(result.error || result.message);
setApps(await getFlatpakApps());
} catch (operationError) {
const message = String(operationError);
setError(message);
showErrorToast("Flatpak override failed", message);
} finally {
setOperation(null);
}
};
if (loading) {
return ;
}
return (
<>
{error && }
{extensionStatus?.success ? runtimeVersions.map(({ version, key }) => (
confirmExtensionOperation(version, extensionStatus[key])}
/>
)) : }
{apps?.success ? apps.apps.length ? apps.apps.map((app) => (
void toggleApp(app)}
/>
)) : : }
>
);
}