diff options
| author | Kurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com> | 2025-07-21 10:05:39 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-07-21 10:05:39 -0400 |
| commit | 9573344450de451b8f9c7295c11318010d67f1d5 (patch) | |
| tree | 1df1f75d3add63a65b9554a08c54165f41bf415f | |
| parent | a5796c1bd7957731215e858d169f2831a328cb55 (diff) | |
| download | Decky-Framegen-0.11.2.tar.gz Decky-Framegen-0.11.2.zip | |
Refresh UI (#117)v0.11.2
* initial visual refinement
* rm dupe status pops
* hide other menus if uninstalled opti
* bump ver
* fix ver bump
| -rw-r--r-- | package.json | 2 | ||||
| -rw-r--r-- | src/components/FGModInstallerSection.tsx | 58 | ||||
| -rw-r--r-- | src/components/InstalledGamesSection.tsx | 29 | ||||
| -rw-r--r-- | src/components/ResultDisplay.tsx | 31 | ||||
| -rw-r--r-- | src/index.tsx | 42 | ||||
| -rw-r--r-- | 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<OperationResult | null>(null); const [uninstallResult, setUninstallResult] = useState<OperationResult | null>(null); - const [pathExists, setPathExists] = useState<boolean | null>(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() { <PanelSection> {pathExists !== null ? ( <PanelSectionRow> - <div style={{ color: pathExists ? "green" : "red" }}> + <div style={pathExists ? STYLES.statusInstalled : STYLES.statusNotInstalled}> {pathExists ? MESSAGES.modInstalled : MESSAGES.modNotInstalled} </div> </PanelSectionRow> @@ -90,14 +86,18 @@ export function FGModInstallerSection() { </PanelSectionRow> ) : null} - <ResultDisplay result={installResult} /> - <ResultDisplay result={uninstallResult} /> - - <PanelSectionRow> - <div> - {MESSAGES.instructionText} - </div> - </PanelSectionRow> + {pathExists === true ? ( + <PanelSectionRow> + <div style={STYLES.instructionCard}> + <div style={{ fontWeight: 'bold', marginBottom: '8px', color: 'var(--decky-accent-text)' }}> + {MESSAGES.instructionTitle} + </div> + <div style={{ whiteSpace: 'pre-line' }}> + {MESSAGES.instructionText} + </div> + </div> + </PanelSectionRow> + ) : null} </PanelSection> ); } 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( <ConfirmModal - strTitle={`Patch ${selectedGame.name}?`} + strTitle={`Enable Frame Generation for ${selectedGame.name}?`} strDescription={ - "WARNING: Decky Framegen does not unpatch games when uninstalled. Be sure to unpatch the game or verify the integrity of your game files if you choose to uninstall the plugin or the game has issues." + "⚠️ Important: This plugin does not automatically unpatch games when uninstalled. If you uninstall this plugin or experience game issues, use the 'Disable Frame Generation' option or verify game file integrity through Steam." } - strOKButtonText="Yeah man, I wanna do it" + strOKButtonText="Enable Frame Generation" strCancelButtonText="Cancel" onOK={async () => { 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 ( - <PanelSection title="Select a game to patch:"> + <PanelSection title="Select a Game to Patch:"> <PanelSectionRow> <DropdownItem rgOptions={games.map(game => ({ @@ -85,15 +85,18 @@ export function InstalledGamesSection() { setSelectedGame(game || null); setResult(''); }} - strDefaultLabel="Select a game..." + strDefaultLabel="Choose a game" menuLabel="Installed Games" /> </PanelSectionRow> {result ? ( <PanelSectionRow> - <div style={STYLES.resultBox}> - {result} + <div style={{ + ...STYLES.preWrap, + ...(result.includes('Error') ? STYLES.statusNotInstalled : STYLES.statusInstalled) + }}> + {result.includes('Error') ? '❌' : '✅'} {result} </div> </PanelSectionRow> ) : null} @@ -105,7 +108,7 @@ export function InstalledGamesSection() { layout="below" onClick={handlePatchClick} > - Patch + Enable Frame Generation </ButtonItem> </PanelSectionRow> <PanelSectionRow> @@ -113,7 +116,7 @@ export function InstalledGamesSection() { layout="below" onClick={handleUnpatchClick} > - Unpatch + Disable Frame Generation </ButtonItem> </PanelSectionRow> </> 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<ResultDisplayProps> = ({ result }) => { if (!result) return null; + const isSuccess = result.status === "success"; + return ( <PanelSectionRow> - <div> - <strong>Status:</strong>{" "} - <span style={result.status === "success" ? STYLES.statusSuccess : STYLES.statusError}> - {result.status === "success" ? "Success" : "Error"} - </span> - <br /> - {result.output ? ( + <div style={isSuccess ? STYLES.statusInstalled : STYLES.statusNotInstalled}> + {isSuccess ? ( <> - <strong>Output:</strong> - <pre style={STYLES.preWrap}>{result.output}</pre> + ✅ {result.output?.includes("uninstall") || result.output?.includes("remov") + ? "OptiScaler mod removed successfully" + : "OptiScaler mod installed successfully"} </> - ) : null} - {result.message ? ( + ) : ( <> - <strong>Error:</strong> {result.message} + ❌ <strong>Error:</strong> {result.message || "Operation failed"} </> - ) : null} + )} + {result.output && !isSuccess && ( + <details style={{ marginTop: '8px' }}> + <summary style={{ cursor: 'pointer', fontSize: '12px' }}>View Details</summary> + <pre style={{ ...STYLES.preWrap, fontSize: '11px', marginTop: '4px' }}> + {result.output} + </pre> + </details> + )} </div> </PanelSectionRow> ); 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<boolean | null>(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 ( + <> + <FGModInstallerSection pathExists={pathExists} setPathExists={setPathExists} /> + {pathExists === true ? ( + <> + <InstalledGamesSection /> + <DocumentationButton /> + </> + ) : null} + </> + ); +} export default definePlugin(() => ({ name: "Framegen Plugin", titleView: <div>Decky Framegen</div>, alwaysRender: true, - content: ( - <> - <FGModInstallerSection /> - <InstalledGamesSection /> - <DocumentationButton /> - </> - ), + content: <MainContent />, icon: <RiAiGenerate />, 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" }; |
