summaryrefslogtreecommitdiff
path: root/src/hooks/useFlatpakConfiguration.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/hooks/useFlatpakConfiguration.ts')
-rw-r--r--src/hooks/useFlatpakConfiguration.ts160
1 files changed, 160 insertions, 0 deletions
diff --git a/src/hooks/useFlatpakConfiguration.ts b/src/hooks/useFlatpakConfiguration.ts
new file mode 100644
index 0000000..f936271
--- /dev/null
+++ b/src/hooks/useFlatpakConfiguration.ts
@@ -0,0 +1,160 @@
+import { useCallback, useEffect, useMemo, useState } from "react";
+import {
+ enableFlatpakApp,
+ getFlatpakApps,
+ getRunningFlatpakApps,
+ removeFlatpakApp,
+ setFlatpakWorkaroundState,
+ updateFlatpakConfig,
+ type FlatpakApp,
+ type LsfgConfig,
+ type RunningFlatpakApp,
+ type WorkaroundState,
+} from "../api/lsfgApi";
+import { selectMostRecentRunningFlatpak } from "../utils/nowPlaying";
+import { showErrorToast } from "../utils/toastUtils";
+
+type FlatpakOperationResult = {
+ success: boolean;
+ error?: string | null;
+ config?: LsfgConfig | null;
+ state?: WorkaroundState | null;
+};
+
+export function useFlatpakConfiguration(enabled: boolean) {
+ const [apps, setApps] = useState<FlatpakApp[]>([]);
+ const [runningApps, setRunningApps] = useState<RunningFlatpakApp[]>([]);
+ const [loading, setLoading] = useState(false);
+ const [busyAppId, setBusyAppId] = useState("");
+
+ const reload = useCallback(async () => {
+ if (!enabled) {
+ setApps([]);
+ return;
+ }
+ setLoading(true);
+ try {
+ const result = await getFlatpakApps();
+ if (!result.success) throw new Error(result.error || "Could not list Flatpak applications");
+ setApps(result.apps || []);
+ } catch (error) {
+ showErrorToast("Flatpak unavailable", error instanceof Error ? error.message : String(error));
+ } finally {
+ setLoading(false);
+ }
+ }, [enabled]);
+
+ const pollRunning = useCallback(async () => {
+ if (!enabled) {
+ setRunningApps([]);
+ return;
+ }
+ try {
+ const result = await getRunningFlatpakApps();
+ if (result.success) setRunningApps(result.apps || []);
+ } catch {}
+ }, [enabled]);
+
+ useEffect(() => {
+ void reload();
+ }, [reload]);
+
+ useEffect(() => {
+ void pollRunning();
+ if (!enabled) return;
+ const interval = window.setInterval(() => void pollRunning(), 2000);
+ return () => window.clearInterval(interval);
+ }, [enabled, pollRunning]);
+
+ const operate = useCallback(async (
+ appId: string,
+ operation: () => Promise<FlatpakOperationResult>,
+ refresh = true,
+ ): Promise<FlatpakOperationResult> => {
+ if (busyAppId) return { success: false };
+ setBusyAppId(appId);
+ try {
+ const result = await operation();
+ if (!result.success) throw new Error(result.error || "Flatpak operation failed");
+ if (refresh) {
+ await reload();
+ await pollRunning();
+ }
+ return result;
+ } catch (error) {
+ showErrorToast("Flatpak operation failed", error instanceof Error ? error.message : String(error));
+ return { success: false, error: error instanceof Error ? error.message : String(error) };
+ } finally {
+ setBusyAppId("");
+ }
+ }, [busyAppId, pollRunning, reload]);
+
+ const enableApp = useCallback(async (appId: string) => (
+ await operate(appId, () => enableFlatpakApp(appId))
+ ).success, [operate]);
+ const removeApp = useCallback(async (appId: string) => (
+ await operate(appId, () => removeFlatpakApp(appId))
+ ).success, [operate]);
+ const enableAll = useCallback(async (): Promise<void> => {
+ if (busyAppId) return;
+ const available = apps.filter((app) => (
+ !app.enabled && !(app.prepared && !app.owned) && !app.error
+ ));
+ for (const app of available) {
+ const result = await operate(app.app_id, () => enableFlatpakApp(app.app_id), false);
+ if (!result.success) break;
+ }
+ await reload();
+ await pollRunning();
+ }, [apps, busyAppId, operate, pollRunning, reload]);
+ const removeAll = useCallback(async (): Promise<void> => {
+ if (busyAppId) return;
+ for (const app of apps.filter((item) => item.enabled)) {
+ const result = await operate(app.app_id, () => removeFlatpakApp(app.app_id), false);
+ if (!result.success) break;
+ }
+ await reload();
+ await pollRunning();
+ }, [apps, busyAppId, operate, pollRunning, reload]);
+ const updateConfig = useCallback(
+ async (appId: string, config: LsfgConfig) => {
+ const result = await operate(appId, () => updateFlatpakConfig(appId, config), false);
+ if (result.success) {
+ setApps((current) => current.map((app) => (
+ app.app_id === appId ? { ...app, config: result.config || config } : app
+ )));
+ }
+ return result.success;
+ },
+ [operate],
+ );
+ const updateWorkarounds = useCallback(
+ async (appId: string, state: WorkaroundState) => {
+ const result = await operate(appId, () => setFlatpakWorkaroundState(appId, state), false);
+ if (result.success) {
+ setApps((current) => current.map((app) => (
+ app.app_id === appId ? { ...app, workarounds: result.state || state } : app
+ )));
+ }
+ return result.success;
+ },
+ [operate],
+ );
+
+ const runningApp = useMemo(() => selectMostRecentRunningFlatpak(apps, runningApps), [apps, runningApps]);
+
+ return {
+ apps,
+ runningApps,
+ runningApp,
+ loading,
+ busyAppId,
+ reload,
+ enableApp,
+ enableAll,
+ removeApp,
+ removeAll,
+ updateConfig,
+ updateWorkarounds,
+ };
+}