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; onResetAll: (source: KnownGameSource) => Promise; 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 { 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); 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}-${source}`); const [availableCollapsed, toggleAvailable] = usePersistentCollapsed(`${AVAILABLE_COLLAPSED_KEY}-${source}`); const enabledToggleRef = useRef(null); useEffect(() => { if (!focusConfiguredToggle) return; const frame = requestAnimationFrame(() => { enabledToggleRef.current?.querySelector('[role="button"], button')?.focus(); onConfiguredToggleFocused?.(); }); return () => cancelAnimationFrame(frame); }, [enabledGames.length, focusConfiguredToggle, onConfiguredToggleFocused]); const confirmResetAll = () => { showModal( void onResetAll(source)} onCancel={() => {}} />, ); }; const confirmEnableAll = () => { showModal( void onEnableAll(source)} onCancel={() => {}} />, ); }; return ( <> {targets.length === 0 && ( )} {enableableGames.length > 0 && ( {`Enable all ${sourceName}`} )} {`Remove all ${sourceLabel(source)} profiles`} ); }