summaryrefslogtreecommitdiff
path: root/src/components
diff options
context:
space:
mode:
authorxXJSONDeruloXx <danielhimebauch@gmail.com>2026-09-06 22:04:54 -0400
committerxXJSONDeruloXx <danielhimebauch@gmail.com>2026-09-06 22:04:54 -0400
commit7bc5f685186b1ff03ce0f7c99e7bec411beabaeb (patch)
treecf94a5752af7f45f6d4a87e89546705f32096766 /src/components
parent170d183fdafc1ec1a686c006fd6a2431c1f30c71 (diff)
downloaddecky-lsfg-vk-7bc5f685186b1ff03ce0f7c99e7bec411beabaeb.tar.gz
decky-lsfg-vk-7bc5f685186b1ff03ce0f7c99e7bec411beabaeb.zip
feat: make ui suck less, frfr
Diffstat (limited to 'src/components')
-rw-r--r--src/components/ConfigurationSection.tsx10
-rw-r--r--src/components/ConfigurationTab.tsx86
-rw-r--r--src/components/Content.tsx45
-rw-r--r--src/components/FgmodClipboardButton.tsx110
-rw-r--r--src/components/FpsMultiplierControl.tsx70
-rw-r--r--src/components/GameConfigurationControls.tsx17
-rw-r--r--src/components/GameConfigurationSelector.tsx67
-rw-r--r--src/components/NowPlayingTab.tsx32
-rw-r--r--src/components/index.ts3
9 files changed, 221 insertions, 219 deletions
diff --git a/src/components/ConfigurationSection.tsx b/src/components/ConfigurationSection.tsx
index c2bba7e..1f17264 100644
--- a/src/components/ConfigurationSection.tsx
+++ b/src/components/ConfigurationSection.tsx
@@ -10,19 +10,19 @@ interface ConfigurationSectionProps {
export function ConfigurationSection({ config, onConfigChange }: ConfigurationSectionProps) {
return <>
<PanelSectionRow>
- <SliderField label={`Flow Scale (${Math.round(config.flow_scale * 100)}%)`} description="Motion estimation resolution scale" value={config.flow_scale} min={0.25} max={1} step={0.01} onChange={(value) => onConfigChange(FLOW_SCALE, value)} />
+ <SliderField label={`Flow Scale (${Math.round(config.flow_scale * 100)}%)`} value={config.flow_scale} min={0.25} max={1} step={0.01} onChange={(value) => onConfigChange(FLOW_SCALE, value)} />
</PanelSectionRow>
<PanelSectionRow>
- <ToggleField label="FP16 Acceleration" description="Use FP16 shaders when supported" checked={!config.no_fp16} onChange={(value) => onConfigChange(NO_FP16, !value)} />
+ <ToggleField label="FP16 Acceleration" checked={!config.no_fp16} onChange={(value) => onConfigChange(NO_FP16, !value)} />
</PanelSectionRow>
<PanelSectionRow>
- <ToggleField label="Performance Mode" description="Use the lighter frame generation model" checked={config.performance_mode} onChange={(value) => onConfigChange(PERFORMANCE_MODE, value)} />
+ <ToggleField label="Performance Mode" checked={config.performance_mode} onChange={(value) => onConfigChange(PERFORMANCE_MODE, value)} />
</PanelSectionRow>
<PanelSectionRow>
- <ToggleField label="Present Mode Override" description="Force FIFO/VSync pacing" checked={config.override_present_mode} onChange={(value) => onConfigChange(OVERRIDE_PRESENT_MODE, value)} />
+ <ToggleField label="Present Mode Override" checked={config.override_present_mode} onChange={(value) => onConfigChange(OVERRIDE_PRESENT_MODE, value)} />
</PanelSectionRow>
<PanelSectionRow>
- <ToggleField label="Preserve Swapchain Image Count" description="Do not change the application's swapchain image count" checked={config.preserve_swapchain_image_count} onChange={(value) => onConfigChange(PRESERVE_SWAPCHAIN_IMAGE_COUNT, value)} />
+ <ToggleField label="Preserve Swapchain Image Count" checked={config.preserve_swapchain_image_count} onChange={(value) => onConfigChange(PRESERVE_SWAPCHAIN_IMAGE_COUNT, value)} />
</PanelSectionRow>
</>;
}
diff --git a/src/components/ConfigurationTab.tsx b/src/components/ConfigurationTab.tsx
index 6f3f474..dab6f19 100644
--- a/src/components/ConfigurationTab.tsx
+++ b/src/components/ConfigurationTab.tsx
@@ -1,19 +1,17 @@
-import { PanelSection } from "@decky/ui";
+import { ButtonItem, Field, Focusable, PanelSection, PanelSectionRow } from "@decky/ui";
+import { useEffect, useRef, useState } from "react";
import { ConfigurationData } from "../config/configSchema";
import { GameTarget } from "../hooks/useGameConfiguration";
-import { ConfigurationSection } from "./ConfigurationSection";
-import { FgmodClipboardButton } from "./FgmodClipboardButton";
-import { FpsMultiplierControl } from "./FpsMultiplierControl";
+import { GameConfigurationControls } from "./GameConfigurationControls";
import { GameConfigurationSelector } from "./GameConfigurationSelector";
-import t from "../i18n/i18n";
interface ConfigurationTabProps {
config: ConfigurationData;
targets: GameTarget[];
runningGame: GameTarget | null;
- selectedAppId: string;
onSelect: (appid: string) => void;
onConfigChange: (fieldName: keyof ConfigurationData, value: boolean | number | string | string[]) => Promise<void>;
+ onEnable: (appid: string) => Promise<boolean>;
onReset: () => Promise<void>;
onResetAll: () => Promise<void>;
}
@@ -22,33 +20,77 @@ export function ConfigurationTab({
config,
targets,
runningGame,
- selectedAppId,
onSelect,
onConfigChange,
+ onEnable,
onReset,
onResetAll,
}: ConfigurationTabProps) {
- return (
- <>
- <PanelSection title={t("CONTENT_FPS_MULTIPLIER", "FPS Multiplier")}>
- <FpsMultiplierControl config={config} onConfigChange={onConfigChange} />
- </PanelSection>
- <PanelSection title="Game Profile">
+ const [detailAppId, setDetailAppId] = useState<string | null>(null);
+ const promptedRunningAppId = useRef<string | null>(null);
+
+ useEffect(() => {
+ if (!runningGame || runningGame.configured) {
+ promptedRunningAppId.current = null;
+ return;
+ }
+ if (promptedRunningAppId.current !== runningGame.appid && detailAppId === null) {
+ promptedRunningAppId.current = runningGame.appid;
+ setDetailAppId(runningGame.appid);
+ }
+ }, [detailAppId, runningGame?.appid, runningGame?.configured]);
+
+ const selectedTarget = detailAppId ? targets.find((target) => target.appid === detailAppId) : null;
+
+ if (detailAppId === null) {
+ return (
+ <PanelSection title="Games">
<GameConfigurationSelector
targets={targets}
runningGame={runningGame}
- selectedAppId={selectedAppId}
- onSelect={onSelect}
- onReset={onReset}
+ onSelect={(appid) => {
+ onSelect(appid);
+ setDetailAppId(appid);
+ }}
onResetAll={onResetAll}
/>
</PanelSection>
- <PanelSection title="Options">
- <ConfigurationSection config={config} onConfigChange={onConfigChange} />
- </PanelSection>
- <PanelSection>
- <FgmodClipboardButton />
+ );
+ }
+
+ const profileLabel = selectedTarget?.name || "Game profile";
+ const running = selectedTarget?.appid === runningGame?.appid;
+ const profileDescription = selectedTarget
+ ? `${selectedTarget.nonSteam ? "Non-Steam" : "Steam"} · App ID ${selectedTarget.appid} · ${selectedTarget.configured ? running && !runningGame?.configured ? "Profile saved · applies next launch" : "Profile active" : "Not configured · changes apply next launch"}`
+ : "Game is no longer available";
+
+ return (
+ <Focusable onCancelButton={() => setDetailAppId(null)}>
+ <PanelSection title="Game Profile">
+ <PanelSectionRow>
+ <Field label={profileLabel} description={profileDescription} />
+ </PanelSectionRow>
+ <PanelSectionRow>
+ <ButtonItem layout="below" onClick={() => setDetailAppId(null)}>Back to games</ButtonItem>
+ </PanelSectionRow>
</PanelSection>
- </>
+ <GameConfigurationControls config={config} onConfigChange={onConfigChange} />
+ <PanelSectionRow>
+ <ButtonItem
+ layout="below"
+ onClick={async () => {
+ if (selectedTarget?.configured) {
+ promptedRunningAppId.current = detailAppId;
+ await onReset();
+ setDetailAppId(null);
+ } else if (detailAppId) {
+ await onEnable(detailAppId);
+ }
+ }}
+ >
+ {selectedTarget?.configured ? "Remove profile" : "Enable for next launch"}
+ </ButtonItem>
+ </PanelSectionRow>
+ </Focusable>
);
}
diff --git a/src/components/Content.tsx b/src/components/Content.tsx
index de8f996..9281d39 100644
--- a/src/components/Content.tsx
+++ b/src/components/Content.tsx
@@ -1,6 +1,6 @@
import { Tabs } from "@decky/ui";
-import { useEffect, useState } from "react";
-import { FaFileAlt, FaGamepad, FaLayerGroup, FaTools } from "react-icons/fa";
+import { useEffect, useRef, useState } from "react";
+import { FaFileAlt, FaGamepad, FaLayerGroup, FaList, FaTools } from "react-icons/fa";
import { ConfigurationData } from "../config/configSchema";
import { tabStyles } from "../styles";
import { useGameConfiguration } from "../hooks/useGameConfiguration";
@@ -9,10 +9,12 @@ import { useInstallationStatus } from "../hooks/useLsfgHooks";
import { ConfigFileTab } from "./ConfigFileTab";
import { ConfigurationTab } from "./ConfigurationTab";
import { FlatpaksTab } from "./FlatpaksTab";
+import { NowPlayingTab } from "./NowPlayingTab";
import { SetupTab } from "./SetupTab";
const tabIcons = {
- configuration: <FaGamepad size={18} />,
+ nowPlaying: <FaGamepad size={18} />,
+ configuration: <FaList size={18} />,
flatpak: <FaLayerGroup size={18} />,
configFile: <FaFileAlt size={18} />,
setup: <FaTools size={18} />,
@@ -33,9 +35,9 @@ export function Content() {
config,
targets,
runningGame,
- selectedAppId,
setSelectedAppId,
save,
+ enable,
resetSelected,
resetAll,
reload,
@@ -48,10 +50,27 @@ export function Content() {
steamBranchStatus?.success === true &&
steamBranchStatus.installed &&
!steamBranchStatus.needs_switch;
+ const previousRunningState = useRef<{ appid: string; configured: boolean } | null>(null);
useEffect(() => {
- setTab(setupComplete ? "Configuration" : "Setup");
- }, [setupComplete]);
+ if (!setupComplete) {
+ setTab("Setup");
+ return;
+ }
+ setTab((current) => current === "Setup" ? (runningGame?.configured ? "NowPlaying" : "Configuration") : current);
+ }, [runningGame?.configured, setupComplete]);
+
+ useEffect(() => {
+ if (!setupComplete) return;
+ const current = runningGame ? { appid: runningGame.appid, configured: runningGame.configured } : null;
+ const previous = previousRunningState.current;
+ previousRunningState.current = current;
+ if (current?.appid && (current.appid !== previous?.appid || current.configured !== previous?.configured)) {
+ setTab(current.configured ? "NowPlaying" : "Configuration");
+ } else if (!current && previous) {
+ setTab((currentTab) => currentTab === "NowPlaying" ? "Configuration" : currentTab);
+ }
+ }, [runningGame?.appid, runningGame?.configured, setupComplete]);
useEffect(() => {
if (isInstalled) void reload();
@@ -88,6 +107,18 @@ export function Content() {
const tabs = setupComplete
? [
+ ...(runningGame?.configured ? [{
+ id: "NowPlaying",
+ title: tabIcons.nowPlaying,
+ content: (
+ <NowPlayingTab
+ game={runningGame}
+ config={config}
+ onConfigChange={handleConfigChange}
+ onRemove={resetSelected}
+ />
+ ),
+ }] : []),
{
id: "Configuration",
title: tabIcons.configuration,
@@ -96,9 +127,9 @@ export function Content() {
config={config}
targets={targets}
runningGame={runningGame}
- selectedAppId={selectedAppId}
onSelect={setSelectedAppId}
onConfigChange={handleConfigChange}
+ onEnable={enable}
onReset={resetSelected}
onResetAll={resetAll}
/>
diff --git a/src/components/FgmodClipboardButton.tsx b/src/components/FgmodClipboardButton.tsx
deleted file mode 100644
index efc5490..0000000
--- a/src/components/FgmodClipboardButton.tsx
+++ /dev/null
@@ -1,110 +0,0 @@
-import { useState, useEffect } from "react";
-import { PanelSectionRow, ButtonItem } from "@decky/ui";
-import { FaClipboard, FaCheck } from "react-icons/fa";
-import { checkFgmodDirectory } from "../api/lsfgApi";
-import { showClipboardErrorToast } from "../utils/toastUtils";
-import { copyWithVerification } from "../utils/clipboardUtils";
-import t from '../i18n/i18n';
-
-export function FgmodClipboardButton() {
- const [isLoading, setIsLoading] = useState(false);
- const [showSuccess, setShowSuccess] = useState(false);
- const [fgmodExists, setFgmodExists] = useState(false);
- const [checkingFgmod, setCheckingFgmod] = useState(true);
-
- // Check for fgmod directory on component mount
- useEffect(() => {
- const checkFgmod = async () => {
- try {
- const result = await checkFgmodDirectory();
- setFgmodExists(result.exists);
- } catch (error) {
- console.error("Error checking fgmod directory:", error);
- setFgmodExists(false);
- } finally {
- setCheckingFgmod(false);
- }
- };
-
- checkFgmod();
- }, []);
-
- // Reset success state after 3 seconds
- useEffect(() => {
- if (showSuccess) {
- const timer = setTimeout(() => {
- setShowSuccess(false);
- }, 3000);
- return () => clearTimeout(timer);
- }
- return undefined;
- }, [showSuccess]);
-
- const copyToClipboard = async () => {
- if (isLoading || showSuccess) return;
-
- setIsLoading(true);
- try {
- const text = "~/fgmod/fgmod ~/lsfg %command%";
- const { success, verified } = await copyWithVerification(text);
-
- if (success) {
- // Show success feedback in the button instead of toast
- setShowSuccess(true);
- if (!verified) {
- // Copy worked but verification failed - still show success
- console.log('Copy verification failed but copy likely worked');
- }
- } else {
- showClipboardErrorToast();
- }
- } catch (error) {
- showClipboardErrorToast();
- } finally {
- setIsLoading(false);
- }
- };
-
- // Don't render if fgmod directory doesn't exist or we're still checking
- if (checkingFgmod || !fgmodExists) {
- return null;
- }
-
- return (
- <PanelSectionRow>
- <ButtonItem
- layout="below"
- onClick={copyToClipboard}
- disabled={isLoading || showSuccess}
- >
- <div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
- {showSuccess ? (
- <FaCheck style={{
- color: "#4CAF50" // Green color for success
- }} />
- ) : isLoading ? (
- <FaClipboard style={{
- animation: "pulse 1s ease-in-out infinite",
- opacity: 0.7
- }} />
- ) : (
- <FaClipboard />
- )}
- <div style={{
- color: showSuccess ? "#4CAF50" : "inherit",
- fontWeight: showSuccess ? "bold" : "normal"
- }}>
- {showSuccess ? t('CLIPBOARD_COPIED', 'Copied to clipboard') : isLoading ? t('CLIPBOARD_COPYING', 'Copying...') : t('CLIPBOARD_LSFG_FGMOD', 'LSFG + DeckyFG')}
- </div>
- </div>
- </ButtonItem>
- <style>{`
- @keyframes pulse {
- 0% { opacity: 0.7; }
- 50% { opacity: 1; }
- 100% { opacity: 0.7; }
- }
- `}</style>
- </PanelSectionRow>
- );
-}
diff --git a/src/components/FpsMultiplierControl.tsx b/src/components/FpsMultiplierControl.tsx
index 5069c9a..e197dc3 100644
--- a/src/components/FpsMultiplierControl.tsx
+++ b/src/components/FpsMultiplierControl.tsx
@@ -1,4 +1,4 @@
-import { PanelSectionRow, DialogButton, Focusable } from "@decky/ui";
+import { PanelSectionRow, SliderField } from "@decky/ui";
import { ConfigurationData } from "../config/configSchema";
import { MULTIPLIER } from "../config/generatedConfigSchema";
import t from "../i18n/i18n";
@@ -12,62 +12,22 @@ export function FpsMultiplierControl({
config,
onConfigChange
}: FpsMultiplierControlProps) {
+ const multiplierLabel = config.multiplier === 1
+ ? t("MULTIPLIER_OFF", "Off")
+ : `${config.multiplier}x`;
+
return (
<PanelSectionRow>
- <Focusable
- style={{
- marginTop: "6px",
- marginBottom: "6px",
- display: "flex",
- justifyContent: "center",
- alignItems: "center"
- }}
- flow-children="horizontal"
- >
- <DialogButton
- style={{
- marginLeft: "0px",
- height: "30px",
- display: "flex",
- alignItems: "center",
- justifyContent: "center",
- padding: "5px 0px 0px 0px",
- minWidth: "40px",
- }}
- onClick={() => onConfigChange(MULTIPLIER, Math.max(1, config.multiplier - 1))}
- disabled={config.multiplier <= 1}
- >
- −
- </DialogButton>
- <div
- style={{
- marginLeft: "20px",
- marginRight: "20px",
- fontSize: "16px",
- fontWeight: "bold",
- color: config.multiplier > 4 ? "red" : "white",
- minWidth: "60px",
- textAlign: "center"
- }}
- >
- {config.multiplier === 1 ? t('MULTIPLIER_OFF', 'OFF') : `${config.multiplier}X`}
- </div>
- <DialogButton
- style={{
- marginLeft: "0px",
- height: "30px",
- display: "flex",
- alignItems: "center",
- justifyContent: "center",
- padding: "5px 0px 0px 0px",
- minWidth: "40px",
- }}
- onClick={() => onConfigChange(MULTIPLIER, Math.min(4, config.multiplier + 1))}
- disabled={config.multiplier >= 4}
- >
- +
- </DialogButton>
- </Focusable>
+ <SliderField
+ label={`FPS multiplier · ${multiplierLabel}`}
+ value={config.multiplier}
+ min={1}
+ max={4}
+ step={1}
+ notchCount={4}
+ showValue={false}
+ onChange={(value) => void onConfigChange(MULTIPLIER, value)}
+ />
</PanelSectionRow>
);
}
diff --git a/src/components/GameConfigurationControls.tsx b/src/components/GameConfigurationControls.tsx
new file mode 100644
index 0000000..bdf9985
--- /dev/null
+++ b/src/components/GameConfigurationControls.tsx
@@ -0,0 +1,17 @@
+import { ConfigurationData } from "../config/configSchema";
+import { ConfigurationSection } from "./ConfigurationSection";
+import { FpsMultiplierControl } from "./FpsMultiplierControl";
+
+interface Props {
+ config: ConfigurationData;
+ onConfigChange: (fieldName: keyof ConfigurationData, value: boolean | number | string | string[]) => Promise<void>;
+}
+
+export function GameConfigurationControls({ config, onConfigChange }: Props) {
+ return (
+ <>
+ <FpsMultiplierControl config={config} onConfigChange={onConfigChange} />
+ <ConfigurationSection config={config} onConfigChange={onConfigChange} />
+ </>
+ );
+}
diff --git a/src/components/GameConfigurationSelector.tsx b/src/components/GameConfigurationSelector.tsx
index a231477..fcdd0aa 100644
--- a/src/components/GameConfigurationSelector.tsx
+++ b/src/components/GameConfigurationSelector.tsx
@@ -1,29 +1,58 @@
-import { Dropdown, DropdownOption, PanelSectionRow, ButtonItem } from "@decky/ui";
+import { ButtonItem, Field, PanelSectionRow } from "@decky/ui";
import { GameTarget } from "../hooks/useGameConfiguration";
interface Props {
targets: GameTarget[];
runningGame: GameTarget | null;
- selectedAppId: string;
onSelect: (appid: string) => void;
- onReset: () => Promise<void>;
onResetAll: () => Promise<void>;
}
-export function GameConfigurationSelector({ targets, runningGame, selectedAppId, onSelect, onReset, onResetAll }: Props) {
- const options: DropdownOption[] = [
- { data: "", label: runningGame ? `Default (editing template) · ${runningGame.name}` : "Default" },
- ...targets.map((target) => ({ data: target.appid, label: `${target.nonSteam ? "Non-Steam · " : ""}${target.name} · ${target.appid}` })),
- ];
- return <>
- <PanelSectionRow>
- <Dropdown rgOptions={options} selectedOption={selectedAppId} onChange={(option) => onSelect(String(option.data))} />
- </PanelSectionRow>
- <PanelSectionRow>
- <ButtonItem layout="below" onClick={() => void onReset()} disabled={!selectedAppId}>Reset selected game</ButtonItem>
- </PanelSectionRow>
- <PanelSectionRow>
- <ButtonItem layout="below" onClick={() => void onResetAll()} disabled={!targets.some((target) => target.configured)}>Reset all game profiles</ButtonItem>
- </PanelSectionRow>
- </>;
+const profileDescription = (target: GameTarget, running: boolean, active: boolean) => [
+ running ? "Now playing" : "",
+ target.nonSteam ? "Non-Steam" : "Steam",
+ target.configured
+ ? active ? "Profile active" : "Profile saved · applies next launch"
+ : "Not configured · changes apply next launch",
+].filter(Boolean).join(" · ");
+
+export function GameConfigurationSelector({ targets, runningGame, onSelect, onResetAll }: Props) {
+ const games = [...targets].sort((a, b) => {
+ if (a.appid === runningGame?.appid) return -1;
+ if (b.appid === runningGame?.appid) return 1;
+ return a.name.localeCompare(b.name);
+ });
+
+ return (
+ <>
+ {games.length === 0 && (
+ <PanelSectionRow>
+ <Field label="No installed games" description="Steam has not reported any eligible games" />
+ </PanelSectionRow>
+ )}
+ {games.map((game) => (
+ <PanelSectionRow key={game.appid}>
+ <Field
+ label={game.name}
+ description={profileDescription(
+ game,
+ game.appid === runningGame?.appid,
+ game.appid === runningGame?.appid ? runningGame.configured : game.configured,
+ )}
+ onActivate={() => onSelect(game.appid)}
+ highlightOnFocus
+ />
+ </PanelSectionRow>
+ ))}
+ <PanelSectionRow>
+ <ButtonItem
+ layout="below"
+ onClick={() => void onResetAll()}
+ disabled={!targets.some((target) => target.configured)}
+ >
+ Remove all profiles
+ </ButtonItem>
+ </PanelSectionRow>
+ </>
+ );
}
diff --git a/src/components/NowPlayingTab.tsx b/src/components/NowPlayingTab.tsx
new file mode 100644
index 0000000..956db4a
--- /dev/null
+++ b/src/components/NowPlayingTab.tsx
@@ -0,0 +1,32 @@
+import { ButtonItem, Field, Focusable, PanelSection, PanelSectionRow } from "@decky/ui";
+import { ConfigurationData } from "../config/configSchema";
+import { GameTarget } from "../hooks/useGameConfiguration";
+import { GameConfigurationControls } from "./GameConfigurationControls";
+
+interface Props {
+ game: GameTarget;
+ config: ConfigurationData;
+ onConfigChange: (fieldName: keyof ConfigurationData, value: boolean | number | string | string[]) => Promise<void>;
+ onRemove: () => Promise<void>;
+}
+
+export function NowPlayingTab({ game, config, onConfigChange, onRemove }: Props) {
+ return (
+ <Focusable>
+ <PanelSection title="Now Playing">
+ <PanelSectionRow>
+ <Field
+ label={game.name}
+ description={`${game.nonSteam ? "Non-Steam" : "Steam"} · App ID ${game.appid} · Profile active`}
+ />
+ </PanelSectionRow>
+ </PanelSection>
+ <GameConfigurationControls config={config} onConfigChange={onConfigChange} />
+ <PanelSectionRow>
+ <ButtonItem layout="below" onClick={() => void onRemove()}>
+ Remove profile
+ </ButtonItem>
+ </PanelSectionRow>
+ </Focusable>
+ );
+}
diff --git a/src/components/index.ts b/src/components/index.ts
index 5089b6f..424a360 100644
--- a/src/components/index.ts
+++ b/src/components/index.ts
@@ -3,9 +3,10 @@ export { StatusDisplay } from "./StatusDisplay";
export { InstallationButton } from "./InstallationButton";
export { ConfigurationSection } from "./ConfigurationSection";
export { FpsMultiplierControl } from "./FpsMultiplierControl";
-export { FgmodClipboardButton } from "./FgmodClipboardButton";
export { ConfigurationTab } from "./ConfigurationTab";
export { SetupTab } from "./SetupTab";
export { ConfigFileTab } from "./ConfigFileTab";
export { FlatpaksTab } from "./FlatpaksTab";
export { GameConfigurationSelector } from "./GameConfigurationSelector";
+export { GameConfigurationControls } from "./GameConfigurationControls";
+export { NowPlayingTab } from "./NowPlayingTab";