summaryrefslogtreecommitdiff
path: root/src/components/GameConfigurationSelector.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/components/GameConfigurationSelector.tsx')
-rw-r--r--src/components/GameConfigurationSelector.tsx155
1 files changed, 44 insertions, 111 deletions
diff --git a/src/components/GameConfigurationSelector.tsx b/src/components/GameConfigurationSelector.tsx
index 92eacba..91dc3df 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, useState, type RefObject } from "react";
-import { RiArrowDownSFill, RiArrowUpSFill } from "react-icons/ri";
-import { GameTarget } from "../hooks/useGameConfiguration";
+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: () => Promise<void>;
- onResetAll: () => Promise<void>;
+ onEnableAll: (source: KnownGameSource) => Promise<void>;
+ onResetAll: (source: KnownGameSource) => Promise<void>;
focusConfiguredToggle?: boolean;
onConfiguredToggleFocused?: () => void;
}
@@ -16,85 +19,18 @@ 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 {
- // Persisting the view preference is optional.
- }
- }, [collapsed, key]);
-
- 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,
- collapsed,
- onToggle,
- onSelect,
- toggleRef,
-}: {
- title: string;
- games: GameTarget[];
- collapsed: boolean;
- onToggle: () => void;
- onSelect: (appid: string) => void;
- toggleRef?: RefObject<HTMLDivElement>;
-}) {
- if (games.length === 0) return null;
-
- return (
- <>
- <PanelSectionRow>
- <Field label={title + " (" + games.length + ")"} bottomSeparator="none" />
- </PanelSectionRow>
- <PanelSectionRow>
- <div
- ref={toggleRef}
- className="LSFG_GameGroupCollapseButton_Container"
- style={{ marginTop: "-2px", marginBottom: "4px" }}
- >
- <ButtonItem
- layout="below"
- bottomSeparator={collapsed ? "standard" : "none"}
- onClick={onToggle}
- >
- {collapsed ? <RiArrowDownSFill /> : <RiArrowUpSFill />}
- </ButtonItem>
- </div>
- </PanelSectionRow>
- {!collapsed && games.map((game) => (
- <PanelSectionRow key={game.appid}>
- <Field
- label={game.name}
- description={targetDescription(game)}
- onActivate={() => onSelect(game.appid)}
- highlightOnFocus
- />
- </PanelSectionRow>
- ))}
- </>
- );
+ 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,
@@ -108,8 +44,20 @@ export function GameConfigurationSelector({
});
const enabledGames = sortGames(targets.filter((game) => game.configured));
const availableGames = sortGames(targets.filter((game) => !game.configured));
- const [enabledCollapsed, toggleEnabled] = usePersistentCollapsed(ENABLED_COLLAPSED_KEY);
- const [availableCollapsed, toggleAvailable] = usePersistentCollapsed(AVAILABLE_COLLAPSED_KEY);
+ 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(() => {
@@ -124,10 +72,10 @@ export function GameConfigurationSelector({
const confirmResetAll = () => {
showModal(
<ConfirmModal
- strTitle="Remove all profiles?"
+ strTitle={`Remove all ${sourceName} profiles?`}
strOKButtonText="Remove all"
strCancelButtonText="Cancel"
- onOK={() => void onResetAll()}
+ onOK={() => void onResetAll(source)}
onCancel={() => {}}
/>,
);
@@ -136,11 +84,11 @@ export function GameConfigurationSelector({
const confirmEnableAll = () => {
showModal(
<ConfirmModal
- strTitle="Enable all available games?"
- strDescription="Create individual LSFG-VK profiles for every available game using the plugin defaults. Flatpak targets will be provisioned as needed."
+ 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()}
+ onOK={() => void onEnableAll(source)}
onCancel={() => {}}
/>,
);
@@ -149,47 +97,32 @@ export function GameConfigurationSelector({
return (
<>
<style>
- {`
- .LSFG_GameGroupCollapseButton_Container > div > div > div > button,
- .LSFG_GameGroupCollapseButton_Container > div > div > div > div > button {
- height: 24px !important;
- min-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;
- }
- `}
+ {collapsibleItemGroupStyles}
</style>
{targets.length === 0 && (
<PanelSectionRow>
- <Field label="No installed games" description="Steam has not reported any eligible games" />
+ <Field label={`No ${sourceName} found`} description={emptyDescription} />
</PanelSectionRow>
)}
- <GameGroup
+ <CollapsibleItemGroup
title="Enabled"
- games={enabledGames}
+ items={enabledGames.map(toItem)}
collapsed={enabledCollapsed}
onToggle={toggleEnabled}
onSelect={onSelect}
toggleRef={enabledToggleRef}
/>
- <GameGroup
+ <CollapsibleItemGroup
title="Available"
- games={availableGames}
+ items={availableGames.map(toItem)}
collapsed={availableCollapsed}
onToggle={toggleAvailable}
onSelect={onSelect}
/>
- {availableGames.length > 0 && (
+ {enableableGames.length > 0 && (
<PanelSectionRow>
- <ButtonItem layout="below" onClick={confirmEnableAll}>
- Enable all available games
+ <ButtonItem layout="below" onClick={confirmEnableAll} disabled={bulkOperationBusy}>
+ {`Enable all ${sourceName}`}
</ButtonItem>
</PanelSectionRow>
)}
@@ -197,9 +130,9 @@ export function GameConfigurationSelector({
<ButtonItem
layout="below"
onClick={confirmResetAll}
- disabled={!targets.some((target) => target.configured)}
+ disabled={bulkOperationBusy || removableGames.length === 0}
>
- Remove all profiles
+ {`Remove all ${sourceLabel(source)} profiles`}
</ButtonItem>
</PanelSectionRow>
</>