summaryrefslogtreecommitdiff
path: root/src/components
diff options
context:
space:
mode:
Diffstat (limited to 'src/components')
-rw-r--r--src/components/ConfigFileTab.tsx119
-rw-r--r--src/components/ConfigurationTab.tsx50
-rw-r--r--src/components/Content.tsx34
3 files changed, 163 insertions, 40 deletions
diff --git a/src/components/ConfigFileTab.tsx b/src/components/ConfigFileTab.tsx
index e3cc09d..07408d7 100644
--- a/src/components/ConfigFileTab.tsx
+++ b/src/components/ConfigFileTab.tsx
@@ -1,13 +1,79 @@
+import { ButtonItem, Field, PanelSection, PanelSectionRow, Spinner } from "@decky/ui";
import { useEffect, useState } from "react";
-import { Field, PanelSection, PanelSectionRow, Spinner } from "@decky/ui";
-import { getConfigFileContent, FileContentResult } from "../api/lsfgApi";
+import { RiArrowDownSFill, RiArrowUpSFill } from "react-icons/ri";
+import { getDebugFileContents, type DebugFileContent, type DebugFileContentsResult } from "../api/lsfgApi";
import t from "../i18n/i18n";
+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 DebugFileSection({ file }: { file: DebugFileContent }) {
+ const [collapsed, toggleCollapsed] = usePersistentCollapsed(`lsfg-debug-file-${file.id}-collapsed-v1`);
+ const status = file.exists ? "Present" : "Not present";
+
+ return (
+ <>
+ <PanelSectionRow>
+ <Field
+ label={file.label}
+ description={`${file.path} ยท ${status}`}
+ bottomSeparator="none"
+ />
+ </PanelSectionRow>
+ <PanelSectionRow>
+ <div
+ className="LSFG_DebugFileCollapseButton_Container"
+ style={{ marginTop: "-2px", marginBottom: "4px" }}
+ >
+ <ButtonItem
+ layout="below"
+ bottomSeparator={collapsed ? "standard" : "none"}
+ onClick={toggleCollapsed}
+ >
+ {collapsed ? <RiArrowDownSFill /> : <RiArrowUpSFill />}
+ </ButtonItem>
+ </div>
+ </PanelSectionRow>
+ {!collapsed && (
+ <PanelSectionRow>
+ {file.exists && file.content !== null && file.content !== undefined ? (
+ <pre style={{ whiteSpace: "pre-wrap", overflowWrap: "anywhere" }}>
+ {file.content}
+ </pre>
+ ) : (
+ <Field
+ label="File unavailable"
+ description={file.error || "The file has not been created yet."}
+ />
+ )}
+ </PanelSectionRow>
+ )}
+ </>
+ );
+}
+
export function ConfigFileTab() {
- const [result, setResult] = useState<FileContentResult | null>(null);
+ const [result, setResult] = useState<DebugFileContentsResult | null>(null);
useEffect(() => {
- getConfigFileContent().then(setResult).catch((error) => {
+ getDebugFileContents().then(setResult).catch((error) => {
setResult({ success: false, error: String(error) });
});
}, []);
@@ -23,24 +89,35 @@ export function ConfigFileTab() {
}
return (
- <PanelSection title={t("NERD_CONFIG_FILE", "Configuration File")}>
- {result.error && (
- <PanelSectionRow>
- <Field label="Error" description={result.error} />
- </PanelSectionRow>
- )}
- {result.success && result.content && (
- <>
- <PanelSectionRow>
- <Field label="Config file" description={result.path} />
- </PanelSectionRow>
+ <>
+ <style>
+ {`
+ .LSFG_DebugFileCollapseButton_Container > div > div > div > button,
+ .LSFG_DebugFileCollapseButton_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_DebugFileCollapseButton_Container svg {
+ display: block;
+ margin: 0;
+ }
+ `}
+ </style>
+ <PanelSection title={t("NERD_CONFIG_FILE", "Configuration File")}>
+ {result.error && (
<PanelSectionRow>
- <pre style={{ whiteSpace: "pre-wrap", overflowWrap: "anywhere" }}>
- {result.content}
- </pre>
+ <Field label="Error" description={result.error} />
</PanelSectionRow>
- </>
- )}
- </PanelSection>
+ )}
+ {result.success && result.files?.map((file) => (
+ <DebugFileSection key={file.id} file={file} />
+ ))}
+ </PanelSection>
+ </>
);
}
diff --git a/src/components/ConfigurationTab.tsx b/src/components/ConfigurationTab.tsx
index 5ae4a87..4e2d93d 100644
--- a/src/components/ConfigurationTab.tsx
+++ b/src/components/ConfigurationTab.tsx
@@ -1,4 +1,4 @@
-import { ButtonItem, ConfirmModal, DialogButton, Focusable, PanelSection, PanelSectionRow, gamepadDialogClasses, showModal } from "@decky/ui";
+import { ButtonItem, ConfirmModal, DialogButton, Focusable, PanelSection, PanelSectionRow, ToggleField, gamepadDialogClasses, showModal } from "@decky/ui";
import { useCallback, useEffect, useRef, useState } from "react";
import { FaArrowLeft } from "react-icons/fa";
import { ConfigurationData } from "../config/configSchema";
@@ -11,6 +11,8 @@ interface ConfigurationTabProps {
config: ConfigurationData;
targets: GameTarget[];
runningGame: GameTarget | null;
+ showDebugTab: boolean;
+ onShowDebugTabChange: (value: boolean) => void;
onSelect: (appid: string) => void;
onConfigChange: (fieldName: keyof ConfigurationData, value: boolean | number | string | string[]) => Promise<void>;
onEnable: (appid: string) => Promise<boolean>;
@@ -24,6 +26,8 @@ export function ConfigurationTab({
config,
targets,
runningGame,
+ showDebugTab,
+ onShowDebugTabChange,
onSelect,
onConfigChange,
onEnable,
@@ -63,22 +67,34 @@ export function ConfigurationTab({
if (detailAppId === null) {
return (
- <PanelSection title="Games">
- <GameConfigurationSelector
- targets={targets}
- runningGame={runningGame}
- onSelect={(appid) => {
- setFocusConfiguredToggle(false);
- setFocusDetailAction(targets.find((target) => target.appid === appid)?.configured ? "fps" : "enable");
- onSelect(appid);
- setDetailAppId(appid);
- }}
- onEnableAll={onEnableAll}
- onResetAll={onResetAll}
- focusConfiguredToggle={focusConfiguredToggle}
- onConfiguredToggleFocused={clearConfiguredToggleFocusRequest}
- />
- </PanelSection>
+ <>
+ <PanelSection title="Games">
+ <GameConfigurationSelector
+ targets={targets}
+ runningGame={runningGame}
+ onSelect={(appid) => {
+ setFocusConfiguredToggle(false);
+ setFocusDetailAction(targets.find((target) => target.appid === appid)?.configured ? "fps" : "enable");
+ onSelect(appid);
+ setDetailAppId(appid);
+ }}
+ onEnableAll={onEnableAll}
+ onResetAll={onResetAll}
+ focusConfiguredToggle={focusConfiguredToggle}
+ onConfiguredToggleFocused={clearConfiguredToggleFocusRequest}
+ />
+ </PanelSection>
+ <PanelSection title="Settings">
+ <PanelSectionRow>
+ <ToggleField
+ label="Show debug tab"
+ description="Show the raw configuration and generated files tab for troubleshooting."
+ checked={showDebugTab}
+ onChange={onShowDebugTabChange}
+ />
+ </PanelSectionRow>
+ </PanelSection>
+ </>
);
}
diff --git a/src/components/Content.tsx b/src/components/Content.tsx
index 578f984..43720c0 100644
--- a/src/components/Content.tsx
+++ b/src/components/Content.tsx
@@ -17,6 +17,29 @@ const tabIcons = {
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 {
+ // Persisting the visibility preference is optional.
+ }
+ }, [key, value]);
+
+ return [value, setValue] as const;
+}
+
export function Content() {
const {
config,
@@ -43,6 +66,7 @@ export function Content() {
uninstall,
} = useInstallation(reload);
const [tab, setTab] = useState("Setup");
+ const [showDebugTab, setShowDebugTab] = usePersistentBoolean(DEBUG_TAB_VISIBILITY_KEY, true);
const previousRunningAppId = useRef<string | null>(null);
const setupComplete =
isInstalled &&
@@ -74,6 +98,10 @@ export function Content() {
if (isInstalled) void reload();
}, [isInstalled, reload]);
+ useEffect(() => {
+ if (!showDebugTab && tab === "ConfigFile") setTab("Games");
+ }, [showDebugTab, tab]);
+
const handleConfigChange = async (
fieldName: keyof ConfigurationData,
value: boolean | number | string | string[],
@@ -116,6 +144,8 @@ export function Content() {
config={config}
targets={targets}
runningGame={runningGame}
+ showDebugTab={showDebugTab}
+ onShowDebugTabChange={setShowDebugTab}
onSelect={setSelectedAppId}
onConfigChange={(field, value) => handleConfigChange(field, value, true)}
onEnable={enable}
@@ -126,7 +156,7 @@ export function Content() {
/>
),
},
- { id: "ConfigFile", title: tabIcons.configFile, content: <ConfigFileTab /> },
+ ...(showDebugTab ? [{ id: "ConfigFile", title: tabIcons.configFile, content: <ConfigFileTab /> }] : []),
{ id: "Setup", title: tabIcons.setup, content: setup },
]
: [{ id: "Setup", title: tabIcons.setup, content: setup }];
@@ -137,7 +167,7 @@ export function Content() {
style={{ height: "95%", width: "300px", position: "fixed", marginTop: "-12px", overflow: "hidden" }}
>
<style>{tabStyles}</style>
- <Tabs activeTab={tab} onShowTab={setTab} tabs={tabs} />
+ <Tabs activeTab={!showDebugTab && tab === "ConfigFile" ? "Games" : tab} onShowTab={setTab} tabs={tabs} />
</div>
);
}