From 9573344450de451b8f9c7295c11318010d67f1d5 Mon Sep 17 00:00:00 2001 From: Kurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com> Date: Mon, 21 Jul 2025 10:05:39 -0400 Subject: Refresh UI (#117) * initial visual refinement * rm dupe status pops * hide other menus if uninstalled opti * bump ver * fix ver bump --- package.json | 2 +- src/components/FGModInstallerSection.tsx | 58 ++++++++++++++++---------------- src/components/InstalledGamesSection.tsx | 29 +++++++++------- src/components/ResultDisplay.tsx | 31 ++++++++++------- src/index.tsx | 42 +++++++++++++++++++---- src/utils/constants.ts | 42 ++++++++++++++++++----- 6 files changed, 132 insertions(+), 72 deletions(-) diff --git a/package.json b/package.json index 791ca77..fba3228 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "decky-framegen", - "version": "0.11.1", + "version": "0.11.2", "description": "plugin to install OptiScaler bleeding-edge and enable upscaling and framegen in a large variety of games.", "type": "module", "scripts": { diff --git a/src/components/FGModInstallerSection.tsx b/src/components/FGModInstallerSection.tsx index f9f905e..1e347c0 100644 --- a/src/components/FGModInstallerSection.tsx +++ b/src/components/FGModInstallerSection.tsx @@ -1,30 +1,20 @@ import { useState, useEffect } from "react"; import { PanelSection, PanelSectionRow, ButtonItem } from "@decky/ui"; -import { checkFGModPath, runInstallFGMod, runUninstallFGMod } from "../api"; -import { ResultDisplay, OperationResult } from "./ResultDisplay"; -import { createAutoCleanupTimer, safeAsyncOperation } from "../utils"; -import { TIMEOUTS, MESSAGES } from "../utils/constants"; +import { runInstallFGMod, runUninstallFGMod } from "../api"; +import { OperationResult } from "./ResultDisplay"; +import { createAutoCleanupTimer } from "../utils"; +import { TIMEOUTS, MESSAGES, STYLES } from "../utils/constants"; -export function FGModInstallerSection() { +interface FGModInstallerSectionProps { + pathExists: boolean | null; + setPathExists: (exists: boolean | null) => void; +} + +export function FGModInstallerSection({ pathExists, setPathExists }: FGModInstallerSectionProps) { const [installing, setInstalling] = useState(false); const [uninstalling, setUninstalling] = useState(false); const [installResult, setInstallResult] = useState(null); const [uninstallResult, setUninstallResult] = useState(null); - const [pathExists, setPathExists] = useState(null); - - useEffect(() => { - const checkPath = async () => { - const result = await safeAsyncOperation( - async () => await checkFGModPath(), - 'useEffect -> checkPath' - ); - if (result) setPathExists(result.exists); - }; - - checkPath(); // Initial check - const intervalId = setInterval(checkPath, TIMEOUTS.pathCheck); // Check every 3 seconds - return () => clearInterval(intervalId); // Cleanup interval on component unmount - }, []); useEffect(() => { if (installResult) { @@ -45,6 +35,9 @@ export function FGModInstallerSection() { setInstalling(true); const result = await runInstallFGMod(); setInstallResult(result); + if (result.status === "success") { + setPathExists(true); + } } catch (e) { console.error(e); } finally { @@ -57,6 +50,9 @@ export function FGModInstallerSection() { setUninstalling(true); const result = await runUninstallFGMod(); setUninstallResult(result); + if (result.status === "success") { + setPathExists(false); + } } catch (e) { console.error(e); } finally { @@ -68,7 +64,7 @@ export function FGModInstallerSection() { {pathExists !== null ? ( -
+
{pathExists ? MESSAGES.modInstalled : MESSAGES.modNotInstalled}
@@ -90,14 +86,18 @@ export function FGModInstallerSection() { ) : null} - - - - -
- {MESSAGES.instructionText} -
-
+ {pathExists === true ? ( + +
+
+ {MESSAGES.instructionTitle} +
+
+ {MESSAGES.instructionText} +
+
+
+ ) : null} ); } diff --git a/src/components/InstalledGamesSection.tsx b/src/components/InstalledGamesSection.tsx index 30ca2a4..71278d7 100644 --- a/src/components/InstalledGamesSection.tsx +++ b/src/components/InstalledGamesSection.tsx @@ -40,19 +40,19 @@ export function InstalledGamesSection() { // Show confirmation modal showModal( { try { await SteamClient.Apps.SetAppLaunchOptions(selectedGame.appid, '~/fgmod/fgmod %COMMAND%'); - setResult(`Launch options set for ${selectedGame.name}. You can now select DLSS in the game's menu, and access OptiScaler with Insert key.`); + setResult(`✓ Frame generation enabled for ${selectedGame.name}. Launch the game, enable DLSS in graphics settings, then press Insert to access OptiScaler options.`); } catch (error) { logError('handlePatchClick: ' + String(error)); - setResult(error instanceof Error ? `Error setting launch options: ${error.message}` : 'Error setting launch options'); + setResult(error instanceof Error ? `Error: ${error.message}` : 'Error enabling frame generation'); } }} /> @@ -64,15 +64,15 @@ export function InstalledGamesSection() { try { await SteamClient.Apps.SetAppLaunchOptions(selectedGame.appid, '~/fgmod/fgmod-uninstaller.sh %COMMAND%'); - setResult(`OptiScaler will uninstall on next launch of ${selectedGame.name}.`); + setResult(`✓ Frame generation will be disabled on next launch of ${selectedGame.name}.`); } catch (error) { logError('handleUnpatchClick: ' + String(error)); - setResult(error instanceof Error ? `Error clearing launch options: ${error.message}` : 'Error clearing launch options'); + setResult(error instanceof Error ? `Error: ${error.message}` : 'Error disabling frame generation'); } }; return ( - + ({ @@ -85,15 +85,18 @@ export function InstalledGamesSection() { setSelectedGame(game || null); setResult(''); }} - strDefaultLabel="Select a game..." + strDefaultLabel="Choose a game" menuLabel="Installed Games" /> {result ? ( -
- {result} +
+ {result.includes('Error') ? '❌' : '✅'} {result}
) : null} @@ -105,7 +108,7 @@ export function InstalledGamesSection() { layout="below" onClick={handlePatchClick} > - Patch + Enable Frame Generation @@ -113,7 +116,7 @@ export function InstalledGamesSection() { layout="below" onClick={handleUnpatchClick} > - Unpatch + Disable Frame Generation diff --git a/src/components/ResultDisplay.tsx b/src/components/ResultDisplay.tsx index 0f58f0e..bcd66c0 100644 --- a/src/components/ResultDisplay.tsx +++ b/src/components/ResultDisplay.tsx @@ -12,25 +12,30 @@ interface ResultDisplayProps { export const ResultDisplay: FC = ({ result }) => { if (!result) return null; + const isSuccess = result.status === "success"; + return ( -
- Status:{" "} - - {result.status === "success" ? "Success" : "Error"} - -
- {result.output ? ( +
+ {isSuccess ? ( <> - Output: -
{result.output}
+ ✅ {result.output?.includes("uninstall") || result.output?.includes("remov") + ? "OptiScaler mod removed successfully" + : "OptiScaler mod installed successfully"} - ) : null} - {result.message ? ( + ) : ( <> - Error: {result.message} + ❌ Error: {result.message || "Operation failed"} - ) : null} + )} + {result.output && !isSuccess && ( +
+ View Details +
+              {result.output}
+            
+
+ )}
); diff --git a/src/index.tsx b/src/index.tsx index 41f8dc3..0bd00c0 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,20 +1,48 @@ import { definePlugin } from "@decky/api"; import { RiAiGenerate } from "react-icons/ri"; +import { useState, useEffect } from "react"; import { FGModInstallerSection } from "./components/FGModInstallerSection"; import { InstalledGamesSection } from "./components/InstalledGamesSection"; import { DocumentationButton } from "./components/DocumentationButton"; +import { checkFGModPath } from "./api"; +import { safeAsyncOperation } from "./utils"; +import { TIMEOUTS } from "./utils/constants"; + +function MainContent() { + const [pathExists, setPathExists] = useState(null); + + useEffect(() => { + const checkPath = async () => { + const result = await safeAsyncOperation( + async () => await checkFGModPath(), + 'MainContent -> checkPath' + ); + if (result) setPathExists(result.exists); + }; + + checkPath(); // Initial check + const intervalId = setInterval(checkPath, TIMEOUTS.pathCheck); // Check every 3 seconds + return () => clearInterval(intervalId); // Cleanup interval on component unmount + }, []); + + return ( + <> + + {pathExists === true ? ( + <> + + + + ) : null} + + ); +} export default definePlugin(() => ({ name: "Framegen Plugin", titleView:
Decky Framegen
, alwaysRender: true, - content: ( - <> - - - - - ), + content: , icon: , onDismount() { console.log("Framegen Plugin unmounted"); diff --git a/src/utils/constants.ts b/src/utils/constants.ts index fe78dd0..c949521 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -17,11 +17,32 @@ export const STYLES = { padding: '12px', marginTop: '16px', backgroundColor: 'var(--decky-selected-ui-bg)', - borderRadius: '4px' + borderRadius: '8px', + border: '1px solid var(--decky-border-color)', + fontSize: '14px' }, - statusSuccess: { color: "green" }, - statusError: { color: "red" }, - preWrap: { whiteSpace: "pre-wrap" as const } + statusInstalled: { + color: '#22c55e', + fontWeight: 'bold', + fontSize: '14px' + }, + statusNotInstalled: { + color: '#f97316', + fontWeight: 'bold', + fontSize: '14px' + }, + statusSuccess: { color: "#22c55e" }, + statusError: { color: "#ef4444" }, + preWrap: { whiteSpace: "pre-wrap" as const }, + instructionCard: { + padding: '14px', + backgroundColor: 'var(--decky-selected-ui-bg)', + borderRadius: '8px', + border: '1px solid var(--decky-border-color)', + marginTop: '8px', + fontSize: '13px', + lineHeight: '1.4' + } }; // Common timeout values @@ -32,11 +53,14 @@ export const TIMEOUTS = { // Message strings export const MESSAGES = { - modInstalled: "OptiScaler Mod Is Installed", - modNotInstalled: "OptiScaler Mod Not Installed", - installing: "Installing...", + modInstalled: "✅ OptiScaler Mod Installed", + modNotInstalled: "❌ OptiScaler Mod Not Installed", + installing: "Installing OptiScaler...", installButton: "Install OptiScaler FG Mod", - uninstalling: "Uninstalling...", + uninstalling: "Removing OptiScaler...", uninstallButton: "Uninstall OptiScaler FG Mod", - instructionText: "Install the OptiScaler-based mod above, then select and patch a game below to enable DLSS replacement with FSR Frame Generation. Map a button to \"insert\" key to bring up the OptiScaler menu in-game." + installSuccess: "✅ OptiScaler mod installed successfully!", + uninstallSuccess: "✅ OptiScaler mod removed successfully.", + instructionTitle: "How to Use:", + instructionText: "Select and patch a game below to enable frame generation (or use clipboard button to copy and paste into launch options)\n\nIn-game: Enable DLSS in graphics settings, or assign a back button to keyboard's 'Insert' key for extended OptiScaler options" }; -- cgit v1.2.3