summaryrefslogtreecommitdiff
path: root/src/components/Content.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/components/Content.tsx')
-rw-r--r--src/components/Content.tsx210
1 files changed, 142 insertions, 68 deletions
diff --git a/src/components/Content.tsx b/src/components/Content.tsx
index 58512d5..91775df 100644
--- a/src/components/Content.tsx
+++ b/src/components/Content.tsx
@@ -1,80 +1,126 @@
import { Tabs } from "@decky/ui";
-import { useEffect, useRef, useState } from "react";
-import { FaFileAlt, FaGamepad, FaList, FaTools } from "react-icons/fa";
+import { useEffect, useRef, useState, type FocusEvent, type ReactNode } from "react";
+import { FaCube, FaFileAlt, FaGamepad, FaList, FaTools } from "react-icons/fa";
import { ConfigurationData } from "../config/configSchema";
-import { tabStyles } from "../styles";
+import { useFlatpakConfiguration } from "../hooks/useFlatpakConfiguration";
import { useGameConfiguration } from "../hooks/useGameConfiguration";
-import { useInstallationActions } from "../hooks/useInstallationActions";
-import { useInstallationStatus } from "../hooks/useLsfgHooks";
-import { ConfigurationTab } from "./ConfigurationTab";
+import { useInstallation } from "../hooks/useLsfgHooks";
+import { tabStyles } from "../styles";
+import { resolveNowPlayingTarget } from "../utils/nowPlaying";
import { ConfigFileTab } from "./ConfigFileTab";
+import { ConfigurationTab } from "./ConfigurationTab";
+import { FlatpakNowPlayingTab } from "./FlatpakNowPlayingTab";
+import { FlatpakTab } from "./FlatpakTab";
import { NowPlayingTab } from "./NowPlayingTab";
import { SetupTab } from "./SetupTab";
const tabIcons = {
nowPlaying: <FaGamepad size={18} />,
games: <FaList size={18} />,
+ flatpak: <FaCube size={18} />,
configFile: <FaFileAlt size={18} />,
setup: <FaTools size={18} />,
};
+const DEBUG_TAB_VISIBILITY_KEY = "lsfg-debug-tab-visible-v1";
+
+function usePersistentBoolean(key: string, defaultValue: boolean) {
+ const [value, setValue] = useState(() => {
+ try {
+ const stored = localStorage.getItem(key);
+ return stored === null ? defaultValue : stored === "true";
+ } catch {
+ return defaultValue;
+ }
+ });
+
+ useEffect(() => {
+ try {
+ localStorage.setItem(key, String(value));
+ } catch {}
+ }, [key, value]);
+
+ return [value, setValue] as const;
+}
+
export function Content() {
const {
- isInstalled,
- installationStatus,
- setIsInstalled,
- setInstallationStatus,
- losslessScalingInstalled,
- losslessScalingStatus,
- steamBranchStatus,
- checkInstallation,
- } = useInstallationStatus();
- const {
config,
+ runningConfig,
+ globalConfig,
targets,
runningGame,
setSelectedAppId,
save,
+ saveFor,
+ updateGlobal,
enable,
enableAll,
repair,
resetSelected,
resetAll,
+ cleanupAllWorkarounds,
reload,
} = useGameConfiguration();
- const { isInstalling, isUninstalling, handleInstall, handleUninstall } = useInstallationActions();
- const [tab, setTab] = useState("Setup");
+ const {
+ isInstalled,
+ installationStatus,
+ losslessScalingInstalled,
+ losslessScalingStatus,
+ steamBranchStatus,
+ isInstalling,
+ isUninstalling,
+ install,
+ uninstall,
+ } = useInstallation(reload, cleanupAllWorkarounds);
const setupComplete =
isInstalled &&
losslessScalingInstalled &&
steamBranchStatus?.success === true &&
steamBranchStatus.installed &&
!steamBranchStatus.needs_switch;
- const previousRunningAppId = useRef<string | null>(null);
+ const flatpak = useFlatpakConfiguration(setupComplete);
+ const [tab, setTab] = useState("Setup");
+ const [showDebugTab, setShowDebugTab] = usePersistentBoolean(DEBUG_TAB_VISIBILITY_KEY, false);
+ const [contentFocused, setContentFocused] = useState(false);
+ const previousRunningWorkload = useRef<string | null>(null);
+ const runningFlatpak = flatpak.runningApp;
+ const nowPlayingTarget = resolveNowPlayingTarget(runningGame, runningFlatpak);
+ const hasNowPlaying = Boolean(nowPlayingTarget);
+ const runningWorkload = nowPlayingTarget
+ ? nowPlayingTarget.kind === "flatpak"
+ ? `flatpak:${nowPlayingTarget.app.app_id}:${nowPlayingTarget.launcher?.appid ?? ""}`
+ : `steam:${nowPlayingTarget.game.appid}`
+ : null;
useEffect(() => {
if (!setupComplete) {
setTab("Setup");
return;
}
- setTab((current) => current === "Setup" ? (runningGame ? "NowPlaying" : "Games") : current);
- }, [runningGame?.appid, setupComplete]);
+ setTab((current) => current === "Setup" ? (hasNowPlaying ? "NowPlaying" : "Games") : current);
+ }, [hasNowPlaying, setupComplete]);
useEffect(() => {
if (!setupComplete) return;
- const appid = runningGame?.appid || null;
- const previous = previousRunningAppId.current;
- previousRunningAppId.current = appid;
- if (appid && appid !== previous) {
- setTab("NowPlaying");
- } else if (!appid && previous) {
- setTab((currentTab) => currentTab === "NowPlaying" ? "Games" : currentTab);
+ const previous = previousRunningWorkload.current;
+ previousRunningWorkload.current = runningWorkload;
+ if (runningWorkload && runningWorkload !== previous) setTab("NowPlaying");
+ else if (!runningWorkload && previous) {
+ setTab((current) => current === "NowPlaying" ? "Games" : current);
+ }
+ }, [runningWorkload, setupComplete]);
+
+ useEffect(() => {
+ if (isInstalled) {
+ void reload();
+ void flatpak.reload();
}
- }, [runningGame?.appid, runningGame?.configured, setupComplete]);
+ }, [isInstalled, reload, flatpak.reload]);
useEffect(() => {
- if (isInstalled) void reload();
- }, [isInstalled, reload]);
+ if (!showDebugTab && tab === "ConfigFile") setTab("Games");
+ }, [showDebugTab, tab]);
const handleConfigChange = async (
fieldName: keyof ConfigurationData,
@@ -84,15 +130,7 @@ export function Content() {
await save({ ...config, [fieldName]: value }, cleanupLaunchOptions);
};
- const onInstall = () => {
- void handleInstall(setIsInstalled, setInstallationStatus, reload, checkInstallation);
- };
-
- const onUninstall = () => {
- void handleUninstall(setIsInstalled, setInstallationStatus, checkInstallation);
- };
-
- const setupContent = (
+ const setup = (
<SetupTab
isInstalled={isInstalled}
installationStatus={installationStatus}
@@ -101,63 +139,99 @@ export function Content() {
steamBranchStatus={steamBranchStatus}
isInstalling={isInstalling}
isUninstalling={isUninstalling}
- onInstall={onInstall}
- onUninstall={onUninstall}
- flatpakRelevant={targets.some((target) => target.transport.kind === "flatpak")}
+ globalConfig={globalConfig}
+ showDebugTab={showDebugTab}
+ onGlobalConfigChange={updateGlobal}
+ onShowDebugTabChange={setShowDebugTab}
+ onInstall={() => void install()}
+ onUninstall={() => void uninstall()}
/>
);
+ const tabContent = (content: ReactNode) => (
+ <div className="lsfg-vk-tab-content">{content}</div>
+ );
+
+ const nowPlaying = nowPlayingTarget?.kind === "steam" ? (
+ <NowPlayingTab
+ game={nowPlayingTarget.game}
+ config={runningConfig}
+ onConfigChange={async (field, value) => {
+ await saveFor(nowPlayingTarget.game.appid, { ...runningConfig, [field]: value }, true);
+ }}
+ />
+ ) : nowPlayingTarget?.kind === "flatpak" ? (
+ <FlatpakNowPlayingTab
+ app={nowPlayingTarget.app}
+ launcher={nowPlayingTarget.launcher}
+ onConfigChange={flatpak.updateConfig}
+ />
+ ) : null;
+
const tabs = setupComplete
? [
- ...(runningGame ? [{
- id: "NowPlaying",
- title: tabIcons.nowPlaying,
- content: (
- <NowPlayingTab
- game={runningGame}
- config={config}
- onConfigChange={(fieldName, value) => handleConfigChange(fieldName, value)}
- onEnable={enable}
- onRepair={repair}
- />
- ),
- }] : []),
+ ...(nowPlaying ? [{ id: "NowPlaying", title: tabIcons.nowPlaying, content: tabContent(nowPlaying) }] : []),
{
id: "Games",
title: tabIcons.games,
- content: (
+ content: tabContent(
<ConfigurationTab
config={config}
targets={targets}
runningGame={runningGame}
onSelect={setSelectedAppId}
- onConfigChange={(fieldName, value) => handleConfigChange(fieldName, value, true)}
+ onConfigChange={(field, value) => handleConfigChange(field, value, true)}
onEnable={enable}
onEnableAll={enableAll}
onRepair={repair}
onReset={resetSelected}
onResetAll={resetAll}
- />
+ />,
),
},
{
- id: "ConfigFile",
- title: tabIcons.configFile,
- content: <ConfigFileTab />,
+ id: "Flatpak",
+ title: tabIcons.flatpak,
+ content: tabContent(
+ <FlatpakTab
+ apps={flatpak.apps}
+ runningApp={runningFlatpak}
+ loading={flatpak.loading}
+ busyAppId={flatpak.busyAppId}
+ onRefresh={flatpak.reload}
+ onEnable={flatpak.enableApp}
+ onRemove={flatpak.removeApp}
+ onConfigChange={flatpak.updateConfig}
+ onWorkaroundChange={flatpak.updateWorkarounds}
+ />,
+ ),
},
- { id: "Setup", title: tabIcons.setup, content: setupContent },
+ ...(showDebugTab ? [{ id: "ConfigFile", title: tabIcons.configFile, content: tabContent(<ConfigFileTab />) }] : []),
+ { id: "Setup", title: tabIcons.setup, content: tabContent(setup) },
]
- : [
- { id: "Setup", title: tabIcons.setup, content: setupContent },
- ];
+ : [{ id: "Setup", title: tabIcons.setup, content: tabContent(setup) }];
+
+ const availableTabIds = new Set(tabs.map(({ id }) => id));
+ const activeTab = availableTabIds.has(tab) ? tab : setupComplete ? "Games" : "Setup";
+ const handleFocusCapture = (event: FocusEvent<HTMLDivElement>) => {
+ const focusedElement = event.target as HTMLElement | null;
+ setContentFocused(!focusedElement?.closest?.('[role="tab"]'));
+ };
return (
<div
- className="lsfg-vk-tabs"
+ className={`lsfg-vk-tabs${contentFocused ? " lsfg-vk-tabs--content-focused" : ""}`}
style={{ height: "95%", width: "300px", position: "fixed", marginTop: "-12px", overflow: "hidden" }}
+ onFocusCapture={handleFocusCapture}
>
<style>{tabStyles}</style>
- <Tabs activeTab={tab} onShowTab={setTab} tabs={tabs} />
+ <Tabs
+ activeTab={activeTab}
+ onShowTab={(nextTab: string) => {
+ if (availableTabIds.has(nextTab)) setTab(nextTab);
+ }}
+ tabs={tabs}
+ />
</div>
);
}