From ad2b182777bfd0a5ceef6e654df75ff13eb8b503 Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Sun, 6 Sep 2026 15:08:45 -0400 Subject: refactor: offload configuration to lsfg-vk --- src/components/GameConfigurationSelector.tsx | 29 ++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/components/GameConfigurationSelector.tsx (limited to 'src/components/GameConfigurationSelector.tsx') diff --git a/src/components/GameConfigurationSelector.tsx b/src/components/GameConfigurationSelector.tsx new file mode 100644 index 0000000..8a0df6c --- /dev/null +++ b/src/components/GameConfigurationSelector.tsx @@ -0,0 +1,29 @@ +import { Dropdown, DropdownOption, PanelSectionRow, ButtonItem } from "@decky/ui"; +import { GameTarget } from "../hooks/useGameConfiguration"; + +interface Props { + targets: GameTarget[]; + runningGame: GameTarget | null; + selectedAppId: string; + onSelect: (appid: string) => void; + onReset: () => Promise; + onResetAll: () => Promise; +} + +export function GameConfigurationSelector({ targets, runningGame, selectedAppId, onSelect, onReset, onResetAll }: Props) { + const options: DropdownOption[] = [ + { data: "", label: runningGame ? `Default (editing template) · ${runningGame.name}` : "Default" }, + ...targets.map((target) => ({ data: target.appid, label: `${target.name} · ${target.appid}` })), + ]; + return <> + + onSelect(String(option.data))} /> + + + void onReset()} disabled={!selectedAppId}>Reset selected game + + + void onResetAll()} disabled={!targets.some((target) => target.configured)}>Reset all game profiles + + ; +} -- cgit v1.2.3 From c9be32287ad5b72fcd86b10fa726d09dbd97d7fd Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Sun, 6 Sep 2026 17:09:54 -0400 Subject: refactor: organize plugin into native tabs --- src/components/GameConfigurationSelector.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/components/GameConfigurationSelector.tsx') diff --git a/src/components/GameConfigurationSelector.tsx b/src/components/GameConfigurationSelector.tsx index 8a0df6c..a231477 100644 --- a/src/components/GameConfigurationSelector.tsx +++ b/src/components/GameConfigurationSelector.tsx @@ -13,7 +13,7 @@ interface Props { export function GameConfigurationSelector({ targets, runningGame, selectedAppId, onSelect, onReset, onResetAll }: Props) { const options: DropdownOption[] = [ { data: "", label: runningGame ? `Default (editing template) · ${runningGame.name}` : "Default" }, - ...targets.map((target) => ({ data: target.appid, label: `${target.name} · ${target.appid}` })), + ...targets.map((target) => ({ data: target.appid, label: `${target.nonSteam ? "Non-Steam · " : ""}${target.name} · ${target.appid}` })), ]; return <> -- cgit v1.2.3 From 7bc5f685186b1ff03ce0f7c99e7bec411beabaeb Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Sun, 6 Sep 2026 22:04:54 -0400 Subject: feat: make ui suck less, frfr --- src/components/GameConfigurationSelector.tsx | 67 ++++++++++++++++++++-------- 1 file changed, 48 insertions(+), 19 deletions(-) (limited to 'src/components/GameConfigurationSelector.tsx') diff --git a/src/components/GameConfigurationSelector.tsx b/src/components/GameConfigurationSelector.tsx index a231477..fcdd0aa 100644 --- a/src/components/GameConfigurationSelector.tsx +++ b/src/components/GameConfigurationSelector.tsx @@ -1,29 +1,58 @@ -import { Dropdown, DropdownOption, PanelSectionRow, ButtonItem } from "@decky/ui"; +import { ButtonItem, Field, PanelSectionRow } from "@decky/ui"; import { GameTarget } from "../hooks/useGameConfiguration"; interface Props { targets: GameTarget[]; runningGame: GameTarget | null; - selectedAppId: string; onSelect: (appid: string) => void; - onReset: () => Promise; onResetAll: () => Promise; } -export function GameConfigurationSelector({ targets, runningGame, selectedAppId, onSelect, onReset, onResetAll }: Props) { - const options: DropdownOption[] = [ - { data: "", label: runningGame ? `Default (editing template) · ${runningGame.name}` : "Default" }, - ...targets.map((target) => ({ data: target.appid, label: `${target.nonSteam ? "Non-Steam · " : ""}${target.name} · ${target.appid}` })), - ]; - return <> - - onSelect(String(option.data))} /> - - - void onReset()} disabled={!selectedAppId}>Reset selected game - - - void onResetAll()} disabled={!targets.some((target) => target.configured)}>Reset all game profiles - - ; +const profileDescription = (target: GameTarget, running: boolean, active: boolean) => [ + running ? "Now playing" : "", + target.nonSteam ? "Non-Steam" : "Steam", + target.configured + ? active ? "Profile active" : "Profile saved · applies next launch" + : "Not configured · changes apply next launch", +].filter(Boolean).join(" · "); + +export function GameConfigurationSelector({ targets, runningGame, onSelect, onResetAll }: Props) { + const games = [...targets].sort((a, b) => { + if (a.appid === runningGame?.appid) return -1; + if (b.appid === runningGame?.appid) return 1; + return a.name.localeCompare(b.name); + }); + + return ( + <> + {games.length === 0 && ( + + + + )} + {games.map((game) => ( + + onSelect(game.appid)} + highlightOnFocus + /> + + ))} + + void onResetAll()} + disabled={!targets.some((target) => target.configured)} + > + Remove all profiles + + + + ); } -- cgit v1.2.3 From d1f8263bf9f22753ad53e1b0105172a3bd0faf5f Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Sun, 6 Sep 2026 23:12:28 -0400 Subject: feat: organize game profiles and focus enabled settings --- src/components/GameConfigurationSelector.tsx | 107 +++++++++++++++++++++------ 1 file changed, 84 insertions(+), 23 deletions(-) (limited to 'src/components/GameConfigurationSelector.tsx') diff --git a/src/components/GameConfigurationSelector.tsx b/src/components/GameConfigurationSelector.tsx index fcdd0aa..982fcef 100644 --- a/src/components/GameConfigurationSelector.tsx +++ b/src/components/GameConfigurationSelector.tsx @@ -1,4 +1,6 @@ import { ButtonItem, Field, PanelSectionRow } from "@decky/ui"; +import { useEffect, useState } from "react"; +import { RiArrowDownSFill, RiArrowUpSFill } from "react-icons/ri"; import { GameTarget } from "../hooks/useGameConfiguration"; interface Props { @@ -8,42 +10,101 @@ interface Props { onResetAll: () => Promise; } -const profileDescription = (target: GameTarget, running: boolean, active: boolean) => [ - running ? "Now playing" : "", - target.nonSteam ? "Non-Steam" : "Steam", - target.configured - ? active ? "Profile active" : "Profile saved · applies next launch" - : "Not configured · changes apply next launch", -].filter(Boolean).join(" · "); +const CONFIGURED_COLLAPSED_KEY = "lsfg-configured-games-collapsed"; +const AVAILABLE_COLLAPSED_KEY = "lsfg-available-games-collapsed"; -export function GameConfigurationSelector({ targets, runningGame, onSelect, onResetAll }: Props) { - const games = [...targets].sort((a, b) => { - if (a.appid === runningGame?.appid) return -1; - if (b.appid === runningGame?.appid) return 1; - return a.name.localeCompare(b.name); +function usePersistentCollapsed(key: string) { + const [collapsed, setCollapsed] = useState(() => { + try { + return localStorage.getItem(key) === "true"; + } catch { + return false; + } }); + useEffect(() => { + try { + localStorage.setItem(key, String(collapsed)); + } catch { + // Persisting the view preference is optional. + } + }, [collapsed, key]); + + return [collapsed, () => setCollapsed((value) => !value)] as const; +} + +function GameGroup({ + title, + games, + collapsed, + onToggle, + onSelect, +}: { + title: string; + games: GameTarget[]; + collapsed: boolean; + onToggle: () => void; + onSelect: (appid: string) => void; +}) { + if (games.length === 0) return null; + return ( <> - {games.length === 0 && ( - - - - )} - {games.map((game) => ( + + + {collapsed ? : } {title} ({games.length}) + + + {!collapsed && games.map((game) => ( onSelect(game.appid)} highlightOnFocus /> ))} + + ); +} + +export function GameConfigurationSelector({ targets, runningGame, onSelect, onResetAll }: Props) { + const sortGames = (games: GameTarget[]) => [...games].sort((a, b) => { + if (a.appid === runningGame?.appid) return -1; + if (b.appid === runningGame?.appid) return 1; + return a.name.localeCompare(b.name); + }); + const configuredGames = sortGames(targets.filter((game) => game.configured)); + const availableGames = sortGames(targets.filter((game) => !game.configured)); + const [configuredCollapsed, toggleConfigured] = usePersistentCollapsed(CONFIGURED_COLLAPSED_KEY); + const [availableCollapsed, toggleAvailable] = usePersistentCollapsed(AVAILABLE_COLLAPSED_KEY); + + return ( + <> + {targets.length === 0 && ( + + + + )} + + Date: Mon, 7 Sep 2026 00:39:16 -0400 Subject: fix: improve profile management focus and reset confirmation --- src/components/GameConfigurationSelector.tsx | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) (limited to 'src/components/GameConfigurationSelector.tsx') diff --git a/src/components/GameConfigurationSelector.tsx b/src/components/GameConfigurationSelector.tsx index 982fcef..91fdc39 100644 --- a/src/components/GameConfigurationSelector.tsx +++ b/src/components/GameConfigurationSelector.tsx @@ -1,4 +1,4 @@ -import { ButtonItem, Field, PanelSectionRow } from "@decky/ui"; +import { ButtonItem, ConfirmModal, Field, PanelSectionRow, showModal } from "@decky/ui"; import { useEffect, useState } from "react"; import { RiArrowDownSFill, RiArrowUpSFill } from "react-icons/ri"; import { GameTarget } from "../hooks/useGameConfiguration"; @@ -83,6 +83,17 @@ export function GameConfigurationSelector({ targets, runningGame, onSelect, onRe const availableGames = sortGames(targets.filter((game) => !game.configured)); const [configuredCollapsed, toggleConfigured] = usePersistentCollapsed(CONFIGURED_COLLAPSED_KEY); const [availableCollapsed, toggleAvailable] = usePersistentCollapsed(AVAILABLE_COLLAPSED_KEY); + const confirmResetAll = () => { + showModal( + void onResetAll()} + onCancel={() => {}} + />, + ); + }; return ( <> @@ -108,7 +119,7 @@ export function GameConfigurationSelector({ targets, runningGame, onSelect, onRe void onResetAll()} + onClick={confirmResetAll} disabled={!targets.some((target) => target.configured)} > Remove all profiles -- cgit v1.2.3 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/GameConfigurationSelector.tsx | 50 +++++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 5 deletions(-) (limited to 'src/components/GameConfigurationSelector.tsx') 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 + + + )} + + Date: Tue, 8 Sep 2026 09:07:51 -0400 Subject: fix: make game group collapse controls compact --- src/components/GameConfigurationSelector.tsx | 32 ++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) (limited to 'src/components/GameConfigurationSelector.tsx') diff --git a/src/components/GameConfigurationSelector.tsx b/src/components/GameConfigurationSelector.tsx index a046594..15f2465 100644 --- a/src/components/GameConfigurationSelector.tsx +++ b/src/components/GameConfigurationSelector.tsx @@ -52,13 +52,25 @@ function GameGroup({ return ( <> - + + +
- {collapsed ? : } {title} ({games.length}) - + + {collapsed ? ( + + ) : ( + + )} + +
{!collapsed && games.map((game) => ( @@ -116,6 +128,14 @@ export function GameConfigurationSelector({ targets, runningGame, onSelect, onEn return ( <> + {targets.length === 0 && ( -- cgit v1.2.3 From b3749ecc4b094a46d2ab9f638ee810f70840f67a Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Tue, 8 Sep 2026 11:31:46 -0400 Subject: update layout and config and hash --- src/components/GameConfigurationSelector.tsx | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) (limited to 'src/components/GameConfigurationSelector.tsx') diff --git a/src/components/GameConfigurationSelector.tsx b/src/components/GameConfigurationSelector.tsx index 15f2465..5f23b91 100644 --- a/src/components/GameConfigurationSelector.tsx +++ b/src/components/GameConfigurationSelector.tsx @@ -11,15 +11,15 @@ interface Props { onResetAll: () => Promise; } -const CONFIGURED_COLLAPSED_KEY = "lsfg-configured-games-collapsed-v2"; -const AVAILABLE_COLLAPSED_KEY = "lsfg-available-games-collapsed"; +const CONFIGURED_COLLAPSED_KEY = "lsfg-configured-games-collapsed-v3"; +const AVAILABLE_COLLAPSED_KEY = "lsfg-available-games-collapsed-v2"; function usePersistentCollapsed(key: string) { const [collapsed, setCollapsed] = useState(() => { try { - return localStorage.getItem(key) === "true"; + return localStorage.getItem(key) !== "false"; } catch { - return false; + return true; } }); @@ -65,9 +65,9 @@ function GameGroup({ onClick={onToggle} > {collapsed ? ( - + ) : ( - + )}
@@ -132,7 +132,16 @@ export function GameConfigurationSelector({ targets, runningGame, onSelect, onEn {` .LSFG_GameGroupCollapseButton_Container > div > div > div > button, .LSFG_GameGroupCollapseButton_Container > div > div > div > div > button { - height: 10px !important; + height: 24px !important; + padding: 0 !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; + } + + .LSFG_GameGroupCollapseButton_Container svg { + display: block; + margin: 0; } `} -- cgit v1.2.3 From 790132668c4421c68c32bdc8fc9792b0d6028f97 Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Tue, 8 Sep 2026 22:18:13 -0400 Subject: I really dont want to but here you go little guy --- src/components/GameConfigurationSelector.tsx | 29 ++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) (limited to 'src/components/GameConfigurationSelector.tsx') diff --git a/src/components/GameConfigurationSelector.tsx b/src/components/GameConfigurationSelector.tsx index 5f23b91..50b2cf8 100644 --- a/src/components/GameConfigurationSelector.tsx +++ b/src/components/GameConfigurationSelector.tsx @@ -1,5 +1,5 @@ import { ButtonItem, ConfirmModal, Field, PanelSectionRow, showModal } from "@decky/ui"; -import { useEffect, useState } from "react"; +import { useEffect, useRef, useState, type RefObject } from "react"; import { RiArrowDownSFill, RiArrowUpSFill } from "react-icons/ri"; import { GameTarget } from "../hooks/useGameConfiguration"; @@ -9,6 +9,8 @@ interface Props { onSelect: (appid: string) => void; onEnableAll: () => Promise; onResetAll: () => Promise; + focusConfiguredToggle?: boolean; + onConfiguredToggleFocused?: () => void; } const CONFIGURED_COLLAPSED_KEY = "lsfg-configured-games-collapsed-v3"; @@ -40,12 +42,14 @@ function GameGroup({ collapsed, onToggle, onSelect, + toggleRef, }: { title: string; games: GameTarget[]; collapsed: boolean; onToggle: () => void; onSelect: (appid: string) => void; + toggleRef?: RefObject; }) { if (games.length === 0) return null; @@ -56,6 +60,7 @@ function GameGroup({
@@ -86,7 +91,15 @@ function GameGroup({ ); } -export function GameConfigurationSelector({ targets, runningGame, onSelect, onEnableAll, onResetAll }: Props) { +export function GameConfigurationSelector({ + targets, + runningGame, + onSelect, + onEnableAll, + onResetAll, + focusConfiguredToggle = false, + onConfiguredToggleFocused, +}: Props) { const sortGames = (games: GameTarget[]) => [...games].sort((a, b) => { if (a.appid === runningGame?.appid) return -1; if (b.appid === runningGame?.appid) return 1; @@ -102,6 +115,17 @@ export function GameConfigurationSelector({ targets, runningGame, onSelect, onEn 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 configuredToggleRef = useRef(null); + + useEffect(() => { + if (!focusConfiguredToggle) return; + const frame = requestAnimationFrame(() => { + configuredToggleRef.current?.querySelector('[role="button"], button')?.focus(); + onConfiguredToggleFocused?.(); + }); + return () => cancelAnimationFrame(frame); + }, [configuredGames.length, focusConfiguredToggle, onConfiguredToggleFocused]); + const confirmResetAll = () => { showModal( Date: Wed, 9 Sep 2026 09:51:21 -0400 Subject: move all games --- src/components/GameConfigurationSelector.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'src/components/GameConfigurationSelector.tsx') diff --git a/src/components/GameConfigurationSelector.tsx b/src/components/GameConfigurationSelector.tsx index 50b2cf8..df0c3e9 100644 --- a/src/components/GameConfigurationSelector.tsx +++ b/src/components/GameConfigurationSelector.tsx @@ -174,13 +174,6 @@ export function GameConfigurationSelector({ )} - {availableGames.length > 0 && ( - - - Enable all available games - - - )} + {availableGames.length > 0 && ( + + + Enable all available games + + + )} Date: Wed, 9 Sep 2026 19:16:30 -0400 Subject: refactor: unify flatpak targets with steam profiles --- src/components/GameConfigurationSelector.tsx | 101 ++++++--------------------- 1 file changed, 22 insertions(+), 79 deletions(-) (limited to 'src/components/GameConfigurationSelector.tsx') diff --git a/src/components/GameConfigurationSelector.tsx b/src/components/GameConfigurationSelector.tsx index df0c3e9..2a92c67 100644 --- a/src/components/GameConfigurationSelector.tsx +++ b/src/components/GameConfigurationSelector.tsx @@ -7,14 +7,13 @@ interface Props { targets: GameTarget[]; runningGame: GameTarget | null; onSelect: (appid: string) => void; - onEnableAll: () => Promise; onResetAll: () => Promise; focusConfiguredToggle?: boolean; onConfiguredToggleFocused?: () => void; } -const CONFIGURED_COLLAPSED_KEY = "lsfg-configured-games-collapsed-v3"; -const AVAILABLE_COLLAPSED_KEY = "lsfg-available-games-collapsed-v2"; +const ENABLED_COLLAPSED_KEY = "lsfg-enabled-games-collapsed-v4"; +const AVAILABLE_COLLAPSED_KEY = "lsfg-available-games-collapsed-v3"; function usePersistentCollapsed(key: string) { const [collapsed, setCollapsed] = useState(() => { @@ -36,6 +35,11 @@ function usePersistentCollapsed(key: string) { return [collapsed, () => setCollapsed((value) => !value)] as const; } +function targetDescription(game: GameTarget): string { + if (game.transport.kind === "flatpak") return "Non-Steam · Flatpak"; + return game.nonSteam ? "Non-Steam" : "Steam"; +} + function GameGroup({ title, games, @@ -56,7 +60,7 @@ function GameGroup({ return ( <> - +
- {collapsed ? ( - - ) : ( - - )} + {collapsed ? : }
@@ -81,7 +81,7 @@ function GameGroup({ onSelect(game.appid)} highlightOnFocus /> @@ -95,7 +95,6 @@ export function GameConfigurationSelector({ targets, runningGame, onSelect, - onEnableAll, onResetAll, focusConfiguredToggle = false, onConfiguredToggleFocused, @@ -105,26 +104,20 @@ export function GameConfigurationSelector({ if (b.appid === runningGame?.appid) return 1; return a.name.localeCompare(b.name); }); - const configuredGames = sortGames(targets.filter((game) => game.configured)); + const enabledGames = 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 [enabledCollapsed, toggleEnabled] = usePersistentCollapsed(ENABLED_COLLAPSED_KEY); const [availableCollapsed, toggleAvailable] = usePersistentCollapsed(AVAILABLE_COLLAPSED_KEY); - const [availableNonSteamCollapsed, toggleAvailableNonSteam] = usePersistentCollapsed(`${AVAILABLE_COLLAPSED_KEY}-non-steam`); - const configuredToggleRef = useRef(null); + const enabledToggleRef = useRef(null); useEffect(() => { if (!focusConfiguredToggle) return; const frame = requestAnimationFrame(() => { - configuredToggleRef.current?.querySelector('[role="button"], button')?.focus(); + enabledToggleRef.current?.querySelector('[role="button"], button')?.focus(); onConfiguredToggleFocused?.(); }); return () => cancelAnimationFrame(frame); - }, [configuredGames.length, focusConfiguredToggle, onConfiguredToggleFocused]); + }, [enabledGames.length, focusConfiguredToggle, onConfiguredToggleFocused]); const confirmResetAll = () => { showModal( @@ -137,79 +130,29 @@ export function GameConfigurationSelector({ />, ); }; - const confirmEnableAll = () => { - showModal( - void onEnableAll()} - onCancel={() => {}} - />, - ); - }; return ( <> - {targets.length === 0 && ( )} - - - {availableGames.length > 0 && ( - - - Enable all available games - - - )} Date: Wed, 9 Sep 2026 20:45:20 -0400 Subject: fix: restore profile and Flatpak controls --- src/components/GameConfigurationSelector.tsx | 40 ++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) (limited to 'src/components/GameConfigurationSelector.tsx') diff --git a/src/components/GameConfigurationSelector.tsx b/src/components/GameConfigurationSelector.tsx index 2a92c67..92eacba 100644 --- a/src/components/GameConfigurationSelector.tsx +++ b/src/components/GameConfigurationSelector.tsx @@ -7,6 +7,7 @@ interface Props { targets: GameTarget[]; runningGame: GameTarget | null; onSelect: (appid: string) => void; + onEnableAll: () => Promise; onResetAll: () => Promise; focusConfiguredToggle?: boolean; onConfiguredToggleFocused?: () => void; @@ -95,6 +96,7 @@ export function GameConfigurationSelector({ targets, runningGame, onSelect, + onEnableAll, onResetAll, focusConfiguredToggle = false, onConfiguredToggleFocused, @@ -131,8 +133,39 @@ export function GameConfigurationSelector({ ); }; + const confirmEnableAll = () => { + showModal( + void onEnableAll()} + onCancel={() => {}} + />, + ); + }; + return ( <> + {targets.length === 0 && ( @@ -153,6 +186,13 @@ export function GameConfigurationSelector({ onToggle={toggleAvailable} onSelect={onSelect} /> + {availableGames.length > 0 && ( + + + Enable all available games + + + )} Date: Thu, 10 Sep 2026 15:50:35 -0400 Subject: refactor: remove flatpak transport labels --- src/components/GameConfigurationSelector.tsx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) (limited to 'src/components/GameConfigurationSelector.tsx') diff --git a/src/components/GameConfigurationSelector.tsx b/src/components/GameConfigurationSelector.tsx index 92eacba..1c95820 100644 --- a/src/components/GameConfigurationSelector.tsx +++ b/src/components/GameConfigurationSelector.tsx @@ -28,16 +28,14 @@ function usePersistentCollapsed(key: string) { useEffect(() => { try { localStorage.setItem(key, String(collapsed)); - } catch { - // Persisting the view preference is optional. - } + } catch {} }, [collapsed, key]); return [collapsed, () => setCollapsed((value) => !value)] as const; } function targetDescription(game: GameTarget): string { - if (game.transport.kind === "flatpak") return "Non-Steam · Flatpak"; + if (game.directFlatpak) return "Non-Steam · Direct Flatpak"; return game.nonSteam ? "Non-Steam" : "Steam"; } @@ -137,7 +135,7 @@ export function GameConfigurationSelector({ showModal( void onEnableAll()} -- cgit v1.2.3 From 1949dd018bd7492c77736e19f9a25b14367187ff Mon Sep 17 00:00:00 2001 From: Kurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com> Date: Thu, 10 Sep 2026 16:36:31 -0400 Subject: refactor: separate flatpak from games selector --- src/components/GameConfigurationSelector.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'src/components/GameConfigurationSelector.tsx') diff --git a/src/components/GameConfigurationSelector.tsx b/src/components/GameConfigurationSelector.tsx index 1c95820..1f0bc73 100644 --- a/src/components/GameConfigurationSelector.tsx +++ b/src/components/GameConfigurationSelector.tsx @@ -35,7 +35,6 @@ function usePersistentCollapsed(key: string) { } function targetDescription(game: GameTarget): string { - if (game.directFlatpak) return "Non-Steam · Direct Flatpak"; return game.nonSteam ? "Non-Steam" : "Steam"; } @@ -135,7 +134,7 @@ export function GameConfigurationSelector({ showModal( void onEnableAll()} -- 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/components/GameConfigurationSelector.tsx | 85 ++++------------------------ 1 file changed, 12 insertions(+), 73 deletions(-) (limited to 'src/components/GameConfigurationSelector.tsx') diff --git a/src/components/GameConfigurationSelector.tsx b/src/components/GameConfigurationSelector.tsx index 1f0bc73..ee87423 100644 --- a/src/components/GameConfigurationSelector.tsx +++ b/src/components/GameConfigurationSelector.tsx @@ -1,7 +1,7 @@ import { ButtonItem, ConfirmModal, Field, PanelSectionRow, showModal } from "@decky/ui"; -import { useEffect, useRef, useState, type RefObject } from "react"; -import { RiArrowDownSFill, RiArrowUpSFill } from "react-icons/ri"; +import { useEffect, useRef, useState } from "react"; import { GameTarget } from "../hooks/useGameConfiguration"; +import { CollapsibleItemGroup, collapsibleItemGroupStyles } from "./CollapsibleItemGroup"; interface Props { targets: GameTarget[]; @@ -38,57 +38,6 @@ function targetDescription(game: GameTarget): string { return game.nonSteam ? "Non-Steam" : "Steam"; } -function GameGroup({ - title, - games, - collapsed, - onToggle, - onSelect, - toggleRef, -}: { - title: string; - games: GameTarget[]; - collapsed: boolean; - onToggle: () => void; - onSelect: (appid: string) => void; - toggleRef?: RefObject; -}) { - if (games.length === 0) return null; - - return ( - <> - - - - -
- - {collapsed ? : } - -
-
- {!collapsed && games.map((game) => ( - - onSelect(game.appid)} - highlightOnFocus - /> - - ))} - - ); -} - export function GameConfigurationSelector({ targets, runningGame, @@ -105,6 +54,11 @@ export function GameConfigurationSelector({ }); const enabledGames = sortGames(targets.filter((game) => game.configured)); const availableGames = sortGames(targets.filter((game) => !game.configured)); + const toItem = (game: GameTarget) => ({ + id: game.appid, + label: game.name, + description: targetDescription(game), + }); const [enabledCollapsed, toggleEnabled] = usePersistentCollapsed(ENABLED_COLLAPSED_KEY); const [availableCollapsed, toggleAvailable] = usePersistentCollapsed(AVAILABLE_COLLAPSED_KEY); const enabledToggleRef = useRef(null); @@ -146,39 +100,24 @@ export function GameConfigurationSelector({ return ( <> {targets.length === 0 && ( )} - - Date: Fri, 11 Sep 2026 09:38:35 -0400 Subject: handle edge cases on flatpaks and ordering --- src/components/GameConfigurationSelector.tsx | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) (limited to 'src/components/GameConfigurationSelector.tsx') diff --git a/src/components/GameConfigurationSelector.tsx b/src/components/GameConfigurationSelector.tsx index ee87423..2c683ae 100644 --- a/src/components/GameConfigurationSelector.tsx +++ b/src/components/GameConfigurationSelector.tsx @@ -1,7 +1,7 @@ import { ButtonItem, ConfirmModal, Field, PanelSectionRow, showModal } from "@decky/ui"; -import { useEffect, useRef, useState } from "react"; +import { useEffect, useRef } from "react"; import { GameTarget } from "../hooks/useGameConfiguration"; -import { CollapsibleItemGroup, collapsibleItemGroupStyles } from "./CollapsibleItemGroup"; +import { CollapsibleItemGroup, collapsibleItemGroupStyles, usePersistentCollapsed } from "./CollapsibleItemGroup"; interface Props { targets: GameTarget[]; @@ -16,24 +16,6 @@ interface Props { const ENABLED_COLLAPSED_KEY = "lsfg-enabled-games-collapsed-v4"; const AVAILABLE_COLLAPSED_KEY = "lsfg-available-games-collapsed-v3"; -function usePersistentCollapsed(key: string) { - const [collapsed, setCollapsed] = useState(() => { - try { - return localStorage.getItem(key) !== "false"; - } catch { - return true; - } - }); - - useEffect(() => { - try { - localStorage.setItem(key, String(collapsed)); - } catch {} - }, [collapsed, key]); - - return [collapsed, () => setCollapsed((value) => !value)] as const; -} - function targetDescription(game: GameTarget): string { return game.nonSteam ? "Non-Steam" : "Steam"; } -- cgit v1.2.3 From f3074fbe1427411dc3b5597d2e87918383299e3f Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Fri, 11 Sep 2026 13:47:52 -0400 Subject: feat: non steam tab, faster bulk actions --- src/components/GameConfigurationSelector.tsx | 47 ++++++++++++++++++---------- 1 file changed, 30 insertions(+), 17 deletions(-) (limited to 'src/components/GameConfigurationSelector.tsx') diff --git a/src/components/GameConfigurationSelector.tsx b/src/components/GameConfigurationSelector.tsx index 2c683ae..ad3537d 100644 --- a/src/components/GameConfigurationSelector.tsx +++ b/src/components/GameConfigurationSelector.tsx @@ -1,14 +1,17 @@ import { ButtonItem, ConfirmModal, Field, PanelSectionRow, showModal } from "@decky/ui"; import { useEffect, useRef } from "react"; -import { GameTarget } from "../hooks/useGameConfiguration"; +import type { GameTarget, KnownGameSource } from "../utils/gameTargets"; +import { sourceLabel } from "../utils/gameTargets"; import { CollapsibleItemGroup, collapsibleItemGroupStyles, usePersistentCollapsed } from "./CollapsibleItemGroup"; interface Props { targets: GameTarget[]; runningGame: GameTarget | null; + source: KnownGameSource; + bulkOperationBusy: boolean; onSelect: (appid: string) => void; - onEnableAll: () => Promise; - onResetAll: () => Promise; + onEnableAll: (source: KnownGameSource) => Promise; + onResetAll: (source: KnownGameSource) => Promise; focusConfiguredToggle?: boolean; onConfiguredToggleFocused?: () => void; } @@ -17,12 +20,16 @@ const ENABLED_COLLAPSED_KEY = "lsfg-enabled-games-collapsed-v4"; const AVAILABLE_COLLAPSED_KEY = "lsfg-available-games-collapsed-v3"; function targetDescription(game: GameTarget): string { - return game.nonSteam ? "Non-Steam" : "Steam"; + return game.source === "unknown" + ? "Unknown source · excluded from bulk actions" + : sourceLabel(game.source); } export function GameConfigurationSelector({ targets, runningGame, + source, + bulkOperationBusy, onSelect, onEnableAll, onResetAll, @@ -36,13 +43,19 @@ export function GameConfigurationSelector({ }); const enabledGames = sortGames(targets.filter((game) => game.configured)); const availableGames = sortGames(targets.filter((game) => !game.configured)); + const enableableGames = availableGames.filter((game) => game.source === source); + const removableGames = enabledGames.filter((game) => game.source === source); + const sourceName = source === "nonSteam" ? "non-Steam shortcuts" : "Steam games"; + const emptyDescription = source === "nonSteam" + ? "Steam has not reported any eligible non-Steam shortcuts" + : "Steam has not reported any eligible installed games"; const toItem = (game: GameTarget) => ({ id: game.appid, label: game.name, description: targetDescription(game), }); - const [enabledCollapsed, toggleEnabled] = usePersistentCollapsed(ENABLED_COLLAPSED_KEY); - const [availableCollapsed, toggleAvailable] = usePersistentCollapsed(AVAILABLE_COLLAPSED_KEY); + const [enabledCollapsed, toggleEnabled] = usePersistentCollapsed(`${ENABLED_COLLAPSED_KEY}-${source}`); + const [availableCollapsed, toggleAvailable] = usePersistentCollapsed(`${AVAILABLE_COLLAPSED_KEY}-${source}`); const enabledToggleRef = useRef(null); useEffect(() => { @@ -57,10 +70,10 @@ export function GameConfigurationSelector({ const confirmResetAll = () => { showModal( void onResetAll()} + onOK={() => void onResetAll(source)} onCancel={() => {}} />, ); @@ -69,11 +82,11 @@ export function GameConfigurationSelector({ const confirmEnableAll = () => { showModal( void onEnableAll()} + onOK={() => void onEnableAll(source)} onCancel={() => {}} />, ); @@ -86,7 +99,7 @@ export function GameConfigurationSelector({ {targets.length === 0 && ( - + )} - {availableGames.length > 0 && ( + {enableableGames.length > 0 && ( - - Enable all available games + + {`Enable all ${sourceName}`} )} @@ -115,9 +128,9 @@ export function GameConfigurationSelector({ target.configured)} + disabled={bulkOperationBusy || removableGames.length === 0} > - Remove all profiles + {`Remove all ${sourceLabel(source)} profiles`}
-- cgit v1.2.3 From 81feb03288166755545401b9df2ff2c9bc3ae7d7 Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Fri, 11 Sep 2026 16:48:35 -0400 Subject: handle flatpak grey, id proton and exclusions --- src/components/GameConfigurationSelector.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'src/components/GameConfigurationSelector.tsx') diff --git a/src/components/GameConfigurationSelector.tsx b/src/components/GameConfigurationSelector.tsx index ad3537d..91dc3df 100644 --- a/src/components/GameConfigurationSelector.tsx +++ b/src/components/GameConfigurationSelector.tsx @@ -20,6 +20,7 @@ const ENABLED_COLLAPSED_KEY = "lsfg-enabled-games-collapsed-v4"; const AVAILABLE_COLLAPSED_KEY = "lsfg-available-games-collapsed-v3"; function targetDescription(game: GameTarget): string { + if (game.isFlatpakShortcut) return "Non-Steam | Use Flatpak Tab"; return game.source === "unknown" ? "Unknown source · excluded from bulk actions" : sourceLabel(game.source); @@ -43,8 +44,8 @@ export function GameConfigurationSelector({ }); const enabledGames = sortGames(targets.filter((game) => game.configured)); const availableGames = sortGames(targets.filter((game) => !game.configured)); - const enableableGames = availableGames.filter((game) => game.source === source); - const removableGames = enabledGames.filter((game) => game.source === source); + const enableableGames = availableGames.filter((game) => game.source === source && !game.isFlatpakShortcut); + const removableGames = enabledGames.filter((game) => game.source === source && !game.isFlatpakShortcut); const sourceName = source === "nonSteam" ? "non-Steam shortcuts" : "Steam games"; const emptyDescription = source === "nonSteam" ? "Steam has not reported any eligible non-Steam shortcuts" @@ -53,6 +54,7 @@ export function GameConfigurationSelector({ id: game.appid, label: game.name, description: targetDescription(game), + disabled: game.isFlatpakShortcut, }); const [enabledCollapsed, toggleEnabled] = usePersistentCollapsed(`${ENABLED_COLLAPSED_KEY}-${source}`); const [availableCollapsed, toggleAvailable] = usePersistentCollapsed(`${AVAILABLE_COLLAPSED_KEY}-${source}`); -- cgit v1.2.3