From aac2d9d360592ceaf732419e9fddc758cfd2cb42 Mon Sep 17 00:00:00 2001 From: Kurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com> Date: Thu, 10 Sep 2026 16:31:13 -0400 Subject: feat: add flatpak profile state hook --- src/hooks/useFlatpakConfiguration.ts | 105 +++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 src/hooks/useFlatpakConfiguration.ts (limited to 'src/hooks/useFlatpakConfiguration.ts') diff --git a/src/hooks/useFlatpakConfiguration.ts b/src/hooks/useFlatpakConfiguration.ts new file mode 100644 index 0000000..e8fae99 --- /dev/null +++ b/src/hooks/useFlatpakConfiguration.ts @@ -0,0 +1,105 @@ +import { useCallback, useEffect, useMemo, useState } from "react"; +import { + enableFlatpakApp, + getFlatpakApps, + getRunningFlatpakApps, + removeFlatpakApp, + setFlatpakWorkaroundState, + updateFlatpakConfig, + type FlatpakApp, + type LsfgConfig, + type WorkaroundState, +} from "../api/lsfgApi"; +import { showErrorToast } from "../utils/toastUtils"; + +export function useFlatpakConfiguration(enabled: boolean) { + const [apps, setApps] = useState([]); + const [runningApps, setRunningApps] = useState([]); + 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<{ success: boolean; error?: string | null }>) => { + if (busyAppId) return false; + setBusyAppId(appId); + try { + const result = await operation(); + if (!result.success) throw new Error(result.error || "Flatpak operation failed"); + await reload(); + await pollRunning(); + return true; + } catch (error) { + showErrorToast("Flatpak operation failed", error instanceof Error ? error.message : String(error)); + return false; + } finally { + setBusyAppId(""); + } + }, [busyAppId, pollRunning, reload]); + + const enableApp = useCallback((appId: string) => operate(appId, () => enableFlatpakApp(appId)), [operate]); + const removeApp = useCallback((appId: string) => operate(appId, () => removeFlatpakApp(appId)), [operate]); + const updateConfig = useCallback( + (appId: string, config: LsfgConfig) => operate(appId, () => updateFlatpakConfig(appId, config)), + [operate], + ); + const updateWorkarounds = useCallback( + (appId: string, state: WorkaroundState) => operate(appId, () => setFlatpakWorkaroundState(appId, state)), + [operate], + ); + + const runningApp = useMemo(() => { + if (runningApps.length === 0) return null; + return runningApps.find((app) => app.active) || (runningApps.length === 1 ? runningApps[0] : null); + }, [runningApps]); + + return { + apps, + runningApps, + runningApp, + loading, + busyAppId, + reload, + enableApp, + removeApp, + updateConfig, + updateWorkarounds, + }; +} -- cgit v1.2.3 From 5c7c1c3547a9f298301fb2b3a03c0f76f24a2866 Mon Sep 17 00:00:00 2001 From: Kurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com> Date: Thu, 10 Sep 2026 16:40:08 -0400 Subject: perf: map running flatpaks from cached app state --- src/hooks/useFlatpakConfiguration.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'src/hooks/useFlatpakConfiguration.ts') diff --git a/src/hooks/useFlatpakConfiguration.ts b/src/hooks/useFlatpakConfiguration.ts index e8fae99..d5e6f7e 100644 --- a/src/hooks/useFlatpakConfiguration.ts +++ b/src/hooks/useFlatpakConfiguration.ts @@ -8,13 +8,14 @@ import { updateFlatpakConfig, type FlatpakApp, type LsfgConfig, + type RunningFlatpakApp, type WorkaroundState, } from "../api/lsfgApi"; import { showErrorToast } from "../utils/toastUtils"; export function useFlatpakConfiguration(enabled: boolean) { const [apps, setApps] = useState([]); - const [runningApps, setRunningApps] = useState([]); + const [runningApps, setRunningApps] = useState([]); const [loading, setLoading] = useState(false); const [busyAppId, setBusyAppId] = useState(""); @@ -87,8 +88,10 @@ export function useFlatpakConfiguration(enabled: boolean) { const runningApp = useMemo(() => { if (runningApps.length === 0) return null; - return runningApps.find((app) => app.active) || (runningApps.length === 1 ? runningApps[0] : null); - }, [runningApps]); + const running = runningApps.find((app) => app.active) || (runningApps.length === 1 ? runningApps[0] : null); + if (!running) return null; + return apps.find((app) => app.app_id === running.app_id) || null; + }, [apps, runningApps]); return { apps, -- cgit v1.2.3 From d2bfdafa92f3d31cc5392bf8a5a1f1df5c2358ce Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Fri, 11 Sep 2026 09:05:14 -0400 Subject: flatpak correctness, ui alignment, tests --- src/hooks/useFlatpakConfiguration.ts | 59 +++++++++++++++++++++++++++--------- 1 file changed, 45 insertions(+), 14 deletions(-) (limited to 'src/hooks/useFlatpakConfiguration.ts') diff --git a/src/hooks/useFlatpakConfiguration.ts b/src/hooks/useFlatpakConfiguration.ts index d5e6f7e..ce881f0 100644 --- a/src/hooks/useFlatpakConfiguration.ts +++ b/src/hooks/useFlatpakConfiguration.ts @@ -11,8 +11,16 @@ import { 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([]); const [runningApps, setRunningApps] = useState([]); @@ -58,39 +66,62 @@ export function useFlatpakConfiguration(enabled: boolean) { return () => window.clearInterval(interval); }, [enabled, pollRunning]); - const operate = useCallback(async (appId: string, operation: () => Promise<{ success: boolean; error?: string | null }>) => { - if (busyAppId) return false; + const operate = useCallback(async ( + appId: string, + operation: () => Promise, + refresh = true, + ): Promise => { + if (busyAppId) return { success: false }; setBusyAppId(appId); try { const result = await operation(); if (!result.success) throw new Error(result.error || "Flatpak operation failed"); - await reload(); - await pollRunning(); - return true; + if (refresh) { + await reload(); + await pollRunning(); + } + return result; } catch (error) { showErrorToast("Flatpak operation failed", error instanceof Error ? error.message : String(error)); - return false; + return { success: false, error: error instanceof Error ? error.message : String(error) }; } finally { setBusyAppId(""); } }, [busyAppId, pollRunning, reload]); - const enableApp = useCallback((appId: string) => operate(appId, () => enableFlatpakApp(appId)), [operate]); - const removeApp = useCallback((appId: string) => operate(appId, () => removeFlatpakApp(appId)), [operate]); + 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 updateConfig = useCallback( - (appId: string, config: LsfgConfig) => operate(appId, () => updateFlatpakConfig(appId, config)), + 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( - (appId: string, state: WorkaroundState) => operate(appId, () => setFlatpakWorkaroundState(appId, state)), + 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(() => { - if (runningApps.length === 0) return null; - const running = runningApps.find((app) => app.active) || (runningApps.length === 1 ? runningApps[0] : null); - if (!running) return null; - return apps.find((app) => app.app_id === running.app_id) || null; + return selectMostRecentRunningFlatpak(apps, runningApps); }, [apps, runningApps]); return { -- cgit v1.2.3 From a8388009b2ba141caafe7f2e35f7029d93e7811f Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Fri, 11 Sep 2026 09:38:35 -0400 Subject: handle edge cases on flatpaks and ordering --- src/hooks/useFlatpakConfiguration.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'src/hooks/useFlatpakConfiguration.ts') diff --git a/src/hooks/useFlatpakConfiguration.ts b/src/hooks/useFlatpakConfiguration.ts index ce881f0..6ff7a79 100644 --- a/src/hooks/useFlatpakConfiguration.ts +++ b/src/hooks/useFlatpakConfiguration.ts @@ -120,9 +120,7 @@ export function useFlatpakConfiguration(enabled: boolean) { [operate], ); - const runningApp = useMemo(() => { - return selectMostRecentRunningFlatpak(apps, runningApps); - }, [apps, runningApps]); + const runningApp = useMemo(() => selectMostRecentRunningFlatpak(apps, runningApps), [apps, runningApps]); return { apps, -- cgit v1.2.3 From 4d14bc1086a1ba05acd01d7466749ff3cffba75f Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Fri, 11 Sep 2026 12:58:16 -0400 Subject: enable all and disable all for flatpak --- src/hooks/useFlatpakConfiguration.ts | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) (limited to 'src/hooks/useFlatpakConfiguration.ts') diff --git a/src/hooks/useFlatpakConfiguration.ts b/src/hooks/useFlatpakConfiguration.ts index 6ff7a79..f936271 100644 --- a/src/hooks/useFlatpakConfiguration.ts +++ b/src/hooks/useFlatpakConfiguration.ts @@ -95,6 +95,27 @@ export function useFlatpakConfiguration(enabled: boolean) { const removeApp = useCallback(async (appId: string) => ( await operate(appId, () => removeFlatpakApp(appId)) ).success, [operate]); + const enableAll = useCallback(async (): Promise => { + 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 => { + 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); @@ -130,7 +151,9 @@ export function useFlatpakConfiguration(enabled: boolean) { busyAppId, reload, enableApp, + enableAll, removeApp, + removeAll, updateConfig, updateWorkarounds, }; -- cgit v1.2.3