diff options
| author | Kurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com> | 2026-09-12 21:29:08 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-09-12 21:29:08 -0400 |
| commit | e580c6bd60c92af36f92d4c29b5d9a44f73d47d7 (patch) | |
| tree | 7fc6799626519e5b01fb137f5440c99fda5faca7 /src/components/GameConfigurationSelector.tsx | |
| parent | e997a3fb74fa70f5e60b60807fb0897120e98313 (diff) | |
| parent | 81feb03288166755545401b9df2ff2c9bc3ae7d7 (diff) | |
| download | decky-lsfg-vk-e580c6bd60c92af36f92d4c29b5d9a44f73d47d7.tar.gz decky-lsfg-vk-e580c6bd60c92af36f92d4c29b5d9a44f73d47d7.zip | |
Merge pull request #264 from xXJSONDeruloXx/chore/migrate
the big one
Diffstat (limited to 'src/components/GameConfigurationSelector.tsx')
| -rw-r--r-- | src/components/GameConfigurationSelector.tsx | 140 |
1 files changed, 140 insertions, 0 deletions
diff --git a/src/components/GameConfigurationSelector.tsx b/src/components/GameConfigurationSelector.tsx new file mode 100644 index 0000000..91dc3df --- /dev/null +++ b/src/components/GameConfigurationSelector.tsx @@ -0,0 +1,140 @@ +import { ButtonItem, ConfirmModal, Field, PanelSectionRow, showModal } from "@decky/ui"; +import { useEffect, useRef } from "react"; +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: (source: KnownGameSource) => Promise<void>; + onResetAll: (source: KnownGameSource) => Promise<void>; + focusConfiguredToggle?: boolean; + onConfiguredToggleFocused?: () => void; +} + +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); +} + +export function GameConfigurationSelector({ + targets, + runningGame, + source, + bulkOperationBusy, + 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; + return a.name.localeCompare(b.name); + }); + const enabledGames = sortGames(targets.filter((game) => game.configured)); + const availableGames = sortGames(targets.filter((game) => !game.configured)); + 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" + : "Steam has not reported any eligible installed games"; + const toItem = (game: GameTarget) => ({ + 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}`); + const enabledToggleRef = useRef<HTMLDivElement>(null); + + useEffect(() => { + if (!focusConfiguredToggle) return; + const frame = requestAnimationFrame(() => { + enabledToggleRef.current?.querySelector<HTMLElement>('[role="button"], button')?.focus(); + onConfiguredToggleFocused?.(); + }); + return () => cancelAnimationFrame(frame); + }, [enabledGames.length, focusConfiguredToggle, onConfiguredToggleFocused]); + + const confirmResetAll = () => { + showModal( + <ConfirmModal + strTitle={`Remove all ${sourceName} profiles?`} + strOKButtonText="Remove all" + strCancelButtonText="Cancel" + onOK={() => void onResetAll(source)} + onCancel={() => {}} + />, + ); + }; + + const confirmEnableAll = () => { + showModal( + <ConfirmModal + strTitle={`Enable all available ${sourceName}?`} + strDescription={`Create individual LSFG-VK profiles for every available ${sourceName}. Unknown-source profiles are excluded. Flatpak profiles are managed separately in the Flatpak tab.`} + strOKButtonText="Enable all" + strCancelButtonText="Cancel" + onOK={() => void onEnableAll(source)} + onCancel={() => {}} + />, + ); + }; + + return ( + <> + <style> + {collapsibleItemGroupStyles} + </style> + {targets.length === 0 && ( + <PanelSectionRow> + <Field label={`No ${sourceName} found`} description={emptyDescription} /> + </PanelSectionRow> + )} + <CollapsibleItemGroup + title="Enabled" + items={enabledGames.map(toItem)} + collapsed={enabledCollapsed} + onToggle={toggleEnabled} + onSelect={onSelect} + toggleRef={enabledToggleRef} + /> + <CollapsibleItemGroup + title="Available" + items={availableGames.map(toItem)} + collapsed={availableCollapsed} + onToggle={toggleAvailable} + onSelect={onSelect} + /> + {enableableGames.length > 0 && ( + <PanelSectionRow> + <ButtonItem layout="below" onClick={confirmEnableAll} disabled={bulkOperationBusy}> + {`Enable all ${sourceName}`} + </ButtonItem> + </PanelSectionRow> + )} + <PanelSectionRow> + <ButtonItem + layout="below" + onClick={confirmResetAll} + disabled={bulkOperationBusy || removableGames.length === 0} + > + {`Remove all ${sourceLabel(source)} profiles`} + </ButtonItem> + </PanelSectionRow> + </> + ); +} |
