From 22db1125238e54b29538102727c36284e122e703 Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Mon, 7 Sep 2026 16:02:26 -0400 Subject: feat: refine game discovery and profile UX --- src/components/ConfigurationTab.tsx | 59 +++++++++++++++++----------- src/components/Content.tsx | 8 ++-- src/components/GameConfigurationSelector.tsx | 50 ++++++++++++++++++++--- src/components/NowPlayingTab.tsx | 14 ++----- 4 files changed, 88 insertions(+), 43 deletions(-) (limited to 'src/components') diff --git a/src/components/ConfigurationTab.tsx b/src/components/ConfigurationTab.tsx index 0d8553c..83f46c1 100644 --- a/src/components/ConfigurationTab.tsx +++ b/src/components/ConfigurationTab.tsx @@ -12,6 +12,7 @@ interface ConfigurationTabProps { onSelect: (appid: string) => void; onConfigChange: (fieldName: keyof ConfigurationData, value: boolean | number | string | string[]) => Promise; onEnable: (appid: string) => Promise; + onEnableAll: () => Promise; onReset: () => Promise; onResetAll: () => Promise; } @@ -23,29 +24,32 @@ export function ConfigurationTab({ onSelect, onConfigChange, onEnable, + onEnableAll, onReset, onResetAll, }: ConfigurationTabProps) { const [detailAppId, setDetailAppId] = useState(null); const [focusFpsMultiplier, setFocusFpsMultiplier] = useState(false); - const [focusBackToGames, setFocusBackToGames] = useState(false); + const [focusDetailAction, setFocusDetailAction] = useState<"enable" | "back" | null>(null); const backToGamesRef = useRef(null); + const enableRef = useRef(null); const promptedRunningAppId = useRef(null); const closeDetails = useCallback(() => { setFocusFpsMultiplier(false); - setFocusBackToGames(false); + setFocusDetailAction(null); setDetailAppId(null); }, []); const clearFpsFocusRequest = useCallback(() => setFocusFpsMultiplier(false), []); useEffect(() => { - if (!focusBackToGames) return; + if (!focusDetailAction) return; const frame = requestAnimationFrame(() => { - backToGamesRef.current?.querySelector('[role="button"]')?.focus(); - setFocusBackToGames(false); + const ref = focusDetailAction === "enable" ? enableRef : backToGamesRef; + ref.current?.querySelector('[role="button"]')?.focus(); + setFocusDetailAction(null); }); return () => cancelAnimationFrame(frame); - }, [focusBackToGames]); + }, [focusDetailAction]); useEffect(() => { if (!runningGame || runningGame.configured) { @@ -54,6 +58,7 @@ export function ConfigurationTab({ } if (promptedRunningAppId.current !== runningGame.appid && detailAppId === null) { promptedRunningAppId.current = runningGame.appid; + setFocusDetailAction(runningGame.configured ? "back" : "enable"); setDetailAppId(runningGame.appid); } }, [detailAppId, runningGame?.appid, runningGame?.configured]); @@ -67,10 +72,11 @@ export function ConfigurationTab({ targets={targets} runningGame={runningGame} onSelect={(appid) => { - setFocusBackToGames(true); + setFocusDetailAction(targets.find((target) => target.appid === appid)?.configured ? "back" : "enable"); onSelect(appid); setDetailAppId(appid); }} + onEnableAll={onEnableAll} onResetAll={onResetAll} /> @@ -79,8 +85,17 @@ export function ConfigurationTab({ const profileLabel = selectedTarget?.name || "Game profile"; const profileDescription = selectedTarget - ? `${selectedTarget.nonSteam ? "Non-Steam" : "Steam"} · App ID ${selectedTarget.appid} · ${selectedTarget.configured ? "Configured" : "Not configured"}` + ? `${selectedTarget.nonSteam ? "Non-Steam" : "Steam"} · App ID ${selectedTarget.appid} · ${selectedTarget.configured ? "LSFG-VK Enabled" : "LSFG-VK not enabled"}` : "Game is no longer available"; + const handleProfileAction = async () => { + if (selectedTarget?.configured) { + promptedRunningAppId.current = detailAppId; + await onReset(); + closeDetails(); + } else if (detailAppId && await onEnable(detailAppId)) { + setFocusFpsMultiplier(true); + } + }; return ( @@ -88,6 +103,13 @@ export function ConfigurationTab({ + {!selectedTarget?.configured && selectedTarget && ( + + + Enable for next launch + + + )} Back to games @@ -102,22 +124,11 @@ export function ConfigurationTab({ onFpsMultiplierFocused={clearFpsFocusRequest} /> )} - - { - if (selectedTarget?.configured) { - promptedRunningAppId.current = detailAppId; - await onReset(); - closeDetails(); - } else if (detailAppId) { - if (await onEnable(detailAppId)) setFocusFpsMultiplier(true); - } - }} - > - {selectedTarget?.configured ? "Remove profile" : "Enable for next launch"} - - + {selectedTarget?.configured && ( + + Remove profile + + )} ); } diff --git a/src/components/Content.tsx b/src/components/Content.tsx index 1bc6e95..f1c8c11 100644 --- a/src/components/Content.tsx +++ b/src/components/Content.tsx @@ -6,7 +6,7 @@ import { tabStyles } from "../styles"; import { useGameConfiguration } from "../hooks/useGameConfiguration"; import { useInstallationActions } from "../hooks/useInstallationActions"; import { useInstallationStatus } from "../hooks/useLsfgHooks"; -// import { ConfigFileTab } from "./ConfigFileTab"; +import { ConfigFileTab } from "./ConfigFileTab"; import { ConfigurationTab } from "./ConfigurationTab"; import { FlatpaksTab } from "./FlatpaksTab"; import { NowPlayingTab } from "./NowPlayingTab"; @@ -38,6 +38,7 @@ export function Content() { setSelectedAppId, save, enable, + enableAll, resetSelected, resetAll, reload, @@ -115,7 +116,6 @@ export function Content() { game={runningGame} config={config} onConfigChange={handleConfigChange} - onRemove={resetSelected} /> ), }] : []), @@ -130,14 +130,14 @@ export function Content() { onSelect={setSelectedAppId} onConfigChange={handleConfigChange} onEnable={enable} + onEnableAll={enableAll} onReset={resetSelected} onResetAll={resetAll} /> ), }, { id: "Flatpak", title: tabIcons.flatpak, content: }, - // Keep the configuration-file view available for future use without exposing it in the UI. - // { id: "ConfigFile", title: tabIcons.configFile, content: }, + { id: "ConfigFile", title: tabIcons.configFile, content: }, // comment out for prod { id: "Setup", title: tabIcons.setup, content: setupContent }, ] : [ diff --git a/src/components/GameConfigurationSelector.tsx b/src/components/GameConfigurationSelector.tsx index 91fdc39..a046594 100644 --- a/src/components/GameConfigurationSelector.tsx +++ b/src/components/GameConfigurationSelector.tsx @@ -7,10 +7,11 @@ interface Props { targets: GameTarget[]; runningGame: GameTarget | null; onSelect: (appid: string) => void; + onEnableAll: () => Promise; onResetAll: () => Promise; } -const CONFIGURED_COLLAPSED_KEY = "lsfg-configured-games-collapsed"; +const CONFIGURED_COLLAPSED_KEY = "lsfg-configured-games-collapsed-v2"; const AVAILABLE_COLLAPSED_KEY = "lsfg-available-games-collapsed"; function usePersistentCollapsed(key: string) { @@ -73,7 +74,7 @@ function GameGroup({ ); } -export function GameConfigurationSelector({ targets, runningGame, onSelect, onResetAll }: Props) { +export function GameConfigurationSelector({ targets, runningGame, onSelect, onEnableAll, onResetAll }: Props) { const sortGames = (games: GameTarget[]) => [...games].sort((a, b) => { if (a.appid === runningGame?.appid) return -1; if (b.appid === runningGame?.appid) return 1; @@ -81,8 +82,14 @@ export function GameConfigurationSelector({ targets, runningGame, onSelect, onRe }); const configuredGames = sortGames(targets.filter((game) => game.configured)); const availableGames = sortGames(targets.filter((game) => !game.configured)); + const configuredSteamGames = configuredGames.filter((game) => !game.nonSteam); + const configuredNonSteamGames = configuredGames.filter((game) => game.nonSteam); + const availableSteamGames = availableGames.filter((game) => !game.nonSteam); + const availableNonSteamGames = availableGames.filter((game) => game.nonSteam); const [configuredCollapsed, toggleConfigured] = usePersistentCollapsed(CONFIGURED_COLLAPSED_KEY); + const [configuredNonSteamCollapsed, toggleConfiguredNonSteam] = usePersistentCollapsed(`${CONFIGURED_COLLAPSED_KEY}-non-steam`); const [availableCollapsed, toggleAvailable] = usePersistentCollapsed(AVAILABLE_COLLAPSED_KEY); + const [availableNonSteamCollapsed, toggleAvailableNonSteam] = usePersistentCollapsed(`${AVAILABLE_COLLAPSED_KEY}-non-steam`); const confirmResetAll = () => { showModal( , ); }; + const confirmEnableAll = () => { + showModal( + void onEnableAll()} + onCancel={() => {}} + />, + ); + }; return ( <> @@ -102,20 +121,41 @@ export function GameConfigurationSelector({ targets, runningGame, onSelect, onRe )} + {availableGames.length > 0 && ( + + + Enable all available games + + + )} + + Promise; - onRemove: () => Promise; } -export function NowPlayingTab({ game, config, onConfigChange, onRemove }: Props) { +export function NowPlayingTab({ game, config, onConfigChange }: Props) { return ( - + - - void onRemove()}> - Remove profile - - ); } -- cgit v1.2.3