diff options
| author | Kurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com> | 2025-07-29 07:53:45 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-07-29 10:53:45 -0400 |
| commit | 0b5e71fe916e92ef9ecf7de91ca43371c4bd6d25 (patch) | |
| tree | 30793600da162d9d8fcc63cddeebb1e5a0b058cb | |
| parent | 526e4e590bb0125f7f7a08e214986afec73e7439 (diff) | |
| download | Decky-Framegen-0b5e71fe916e92ef9ecf7de91ca43371c4bd6d25.tar.gz Decky-Framegen-0b5e71fe916e92ef9ecf7de91ca43371c4bd6d25.zip | |
wording and layout tweaks (#125)v0.11.8
* wording and layout tweaks
* red in remove button
* reorganize frontend components
* fix ld preload permissions issue for decky 3.1.10
* bump ver
| -rw-r--r-- | main.py | 16 | ||||
| -rw-r--r-- | package.json | 4 | ||||
| -rw-r--r-- | src/components/ClipboardCommands.tsx | 23 | ||||
| -rw-r--r-- | src/components/DocumentationButton.tsx | 41 | ||||
| -rw-r--r-- | src/components/FGModInstallerSection.tsx | 139 | ||||
| -rw-r--r-- | src/components/InstallationStatus.tsx | 28 | ||||
| -rw-r--r-- | src/components/InstructionCard.tsx | 23 | ||||
| -rw-r--r-- | src/components/OptiScalerControls.tsx | 92 | ||||
| -rw-r--r-- | src/components/OptiScalerHeader.tsx | 30 | ||||
| -rw-r--r-- | src/components/OptiScalerWiki.tsx | 28 | ||||
| -rw-r--r-- | src/components/README.md | 55 | ||||
| -rw-r--r-- | src/components/UninstallButton.tsx | 29 | ||||
| -rw-r--r-- | src/components/index.ts | 10 | ||||
| -rw-r--r-- | src/exports.ts | 2 | ||||
| -rw-r--r-- | src/index.tsx | 10 | ||||
| -rw-r--r-- | src/utils/constants.ts | 2 |
16 files changed, 339 insertions, 193 deletions
@@ -94,12 +94,17 @@ class Plugin: def _setup_flatpak_compatibility(self, fgmod_path): """Set up Flatpak compatibility if needed""" try: + # Create a clean environment to avoid PyInstaller issues + clean_env = os.environ.copy() + clean_env["LD_LIBRARY_PATH"] = "" + # Check if Flatpak Steam is installed flatpak_check = subprocess.run( ["flatpak", "list"], capture_output=True, text=True, - check=False + check=False, + env=clean_env ) if flatpak_check.returncode == 0 and "com.valvesoftware.Steam" in flatpak_check.stdout: @@ -109,7 +114,7 @@ class Plugin: "flatpak", "override", "--user", f"--filesystem={fgmod_path}", "com.valvesoftware.Steam" - ], check=False) + ], check=False, env=clean_env) decky.logger.info("Added Flatpak filesystem access") return True @@ -153,11 +158,16 @@ class Plugin: str(optiscaler_archive) ] + # Create a clean environment to avoid PyInstaller issues + clean_env = os.environ.copy() + clean_env["LD_LIBRARY_PATH"] = "" + extract_result = subprocess.run( extract_cmd, capture_output=True, text=True, - check=False + check=False, + env=clean_env ) if extract_result.returncode != 0: diff --git a/package.json b/package.json index 42ccb30..eae30d4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "decky-framegen", - "version": "0.11.6", + "version": "0.11.8", "description": "plugin to install OptiScaler bleeding-edge and enable upscaling and framegen in a large variety of games.", "type": "module", "scripts": { @@ -49,7 +49,7 @@ ] } }, - "remote_binary_bundling" : true, + "remote_binary_bundling" : false, "remote_binary": [ { diff --git a/src/components/ClipboardCommands.tsx b/src/components/ClipboardCommands.tsx new file mode 100644 index 0000000..5a6f38f --- /dev/null +++ b/src/components/ClipboardCommands.tsx @@ -0,0 +1,23 @@ +import { SmartClipboardButton } from "./SmartClipboardButton"; + +interface ClipboardCommandsProps { + pathExists: boolean | null; +} + +export function ClipboardCommands({ pathExists }: ClipboardCommandsProps) { + if (pathExists !== true) return null; + + return ( + <> + <SmartClipboardButton + command="~/fgmod/fgmod %command%" + buttonText="Copy Patch Command" + /> + + <SmartClipboardButton + command="~/fgmod/fgmod-uninstaller.sh %command%" + buttonText="Copy Unpatch Command" + /> + </> + ); +} diff --git a/src/components/DocumentationButton.tsx b/src/components/DocumentationButton.tsx deleted file mode 100644 index 7069bc2..0000000 --- a/src/components/DocumentationButton.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { PanelSection, PanelSectionRow, ButtonItem } from "@decky/ui"; -import { FaClipboard, FaBook } from "react-icons/fa"; - -export function DocumentationButton() { - const handleDocClick = () => { - window.open("https://github.com/xXJSONDeruloXx/Decky-Framegen/wiki", "_blank"); - }; - - const handleOptiScalerClick = () => { - window.open("https://github.com/optiscaler/OptiScaler/wiki", "_blank"); - }; - - return ( - <PanelSection> - {/* - <PanelSectionRow> - <ButtonItem - layout="below" - onClick={handleDocClick} - > - <div style={{ display: "flex", alignItems: "center", gap: "8px" }}> - <FaClipboard /> - <div>Copy Launch Command</div> - </div> - </ButtonItem> - </PanelSectionRow> - */} - <PanelSectionRow> - <ButtonItem - layout="below" - onClick={handleOptiScalerClick} - > - <div style={{ display: "flex", alignItems: "center", gap: "8px" }}> - <FaBook /> - <div>OptiScaler Wiki</div> - </div> - </ButtonItem> - </PanelSectionRow> - </PanelSection> - ); -} diff --git a/src/components/FGModInstallerSection.tsx b/src/components/FGModInstallerSection.tsx deleted file mode 100644 index b82e749..0000000 --- a/src/components/FGModInstallerSection.tsx +++ /dev/null @@ -1,139 +0,0 @@ -import { useState, useEffect } from "react"; -import { PanelSection, PanelSectionRow, ButtonItem } from "@decky/ui"; -import { runInstallFGMod, runUninstallFGMod } from "../api"; -import { OperationResult } from "./ResultDisplay"; -import { SmartClipboardButton } from "./SmartClipboardButton"; -import { createAutoCleanupTimer } from "../utils"; -import { TIMEOUTS, MESSAGES, STYLES } from "../utils/constants"; -import optiScalerImage from "../../assets/optiscaler.png"; - -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); - - useEffect(() => { - if (installResult) { - return createAutoCleanupTimer(() => setInstallResult(null), TIMEOUTS.resultDisplay); - } - return () => {}; // Ensure a cleanup function is always returned - }, [installResult]); - - useEffect(() => { - if (uninstallResult) { - return createAutoCleanupTimer(() => setUninstallResult(null), TIMEOUTS.resultDisplay); - } - return () => {}; // Ensure a cleanup function is always returned - }, [uninstallResult]); - - const handleInstallClick = async () => { - try { - setInstalling(true); - const result = await runInstallFGMod(); - setInstallResult(result); - if (result.status === "success") { - setPathExists(true); - } - } catch (e) { - console.error(e); - } finally { - setInstalling(false); - } - }; - - const handleUninstallClick = async () => { - try { - setUninstalling(true); - const result = await runUninstallFGMod(); - setUninstallResult(result); - if (result.status === "success") { - setPathExists(false); - } - } catch (e) { - console.error(e); - } finally { - setUninstalling(false); - } - }; - - return ( - <PanelSection> - {pathExists === false ? ( - <PanelSectionRow> - <div style={STYLES.statusNotInstalled}> - {MESSAGES.modNotInstalled} - </div> - </PanelSectionRow> - ) : null} - - {pathExists === false ? ( - <PanelSectionRow> - <ButtonItem layout="below" onClick={handleInstallClick} disabled={installing}> - {installing ? MESSAGES.installing : MESSAGES.installButton} - </ButtonItem> - </PanelSectionRow> - ) : null} - - {pathExists === true ? ( - <PanelSectionRow> - <ButtonItem layout="below" onClick={handleUninstallClick} disabled={uninstalling}> - {uninstalling ? MESSAGES.uninstalling : MESSAGES.uninstallButton} - </ButtonItem> - </PanelSectionRow> - ) : null} - - {pathExists === true ? ( - <PanelSectionRow> - <div style={{ - display: 'flex', - justifyContent: 'center', - marginBottom: '16px' - }}> - <img - src={optiScalerImage} - alt="OptiScaler" - style={{ - maxWidth: '100%', - height: 'auto', - borderRadius: '8px' - }} - /> - </div> - </PanelSectionRow> - ) : null} - - {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} - - {pathExists === true ? ( - <SmartClipboardButton - command="~/fgmod/fgmod %command%" - buttonText="Copy Patch Command" - /> - ) : null} - - {pathExists === true ? ( - <SmartClipboardButton - command="~/fgmod/fgmod-uninstaller.sh %command%" - buttonText="Copy Unpatch Command" - /> - ) : null} - </PanelSection> - ); -} diff --git a/src/components/InstallationStatus.tsx b/src/components/InstallationStatus.tsx new file mode 100644 index 0000000..713b5e1 --- /dev/null +++ b/src/components/InstallationStatus.tsx @@ -0,0 +1,28 @@ +import { PanelSectionRow, ButtonItem } from "@decky/ui"; +import { MESSAGES, STYLES } from "../utils/constants"; + +interface InstallationStatusProps { + pathExists: boolean | null; + installing: boolean; + onInstallClick: () => void; +} + +export function InstallationStatus({ pathExists, installing, onInstallClick }: InstallationStatusProps) { + if (pathExists !== false) return null; + + return ( + <> + <PanelSectionRow> + <div style={STYLES.statusNotInstalled}> + {MESSAGES.modNotInstalled} + </div> + </PanelSectionRow> + + <PanelSectionRow> + <ButtonItem layout="below" onClick={onInstallClick} disabled={installing}> + {installing ? MESSAGES.installing : MESSAGES.installButton} + </ButtonItem> + </PanelSectionRow> + </> + ); +} diff --git a/src/components/InstructionCard.tsx b/src/components/InstructionCard.tsx new file mode 100644 index 0000000..fdf6755 --- /dev/null +++ b/src/components/InstructionCard.tsx @@ -0,0 +1,23 @@ +import { PanelSectionRow } from "@decky/ui"; +import { MESSAGES, STYLES } from "../utils/constants"; + +interface InstructionCardProps { + pathExists: boolean | null; +} + +export function InstructionCard({ pathExists }: InstructionCardProps) { + if (pathExists !== true) return null; + + return ( + <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> + ); +} diff --git a/src/components/OptiScalerControls.tsx b/src/components/OptiScalerControls.tsx new file mode 100644 index 0000000..7b2db0e --- /dev/null +++ b/src/components/OptiScalerControls.tsx @@ -0,0 +1,92 @@ +import { useState, useEffect } from "react"; +import { PanelSection } from "@decky/ui"; +import { runInstallFGMod, runUninstallFGMod } from "../api"; +import { OperationResult } from "./ResultDisplay"; +import { createAutoCleanupTimer } from "../utils"; +import { TIMEOUTS } from "../utils/constants"; +import { InstallationStatus } from "./InstallationStatus"; +import { OptiScalerHeader } from "./OptiScalerHeader"; +import { ClipboardCommands } from "./ClipboardCommands"; +import { InstructionCard } from "./InstructionCard"; +import { OptiScalerWiki } from "./OptiScalerWiki"; +import { UninstallButton } from "./UninstallButton"; + +interface OptiScalerControlsProps { + pathExists: boolean | null; + setPathExists: (exists: boolean | null) => void; +} + +export function OptiScalerControls({ pathExists, setPathExists }: OptiScalerControlsProps) { + const [installing, setInstalling] = useState(false); + const [uninstalling, setUninstalling] = useState(false); + const [installResult, setInstallResult] = useState<OperationResult | null>(null); + const [uninstallResult, setUninstallResult] = useState<OperationResult | null>(null); + + useEffect(() => { + if (installResult) { + return createAutoCleanupTimer(() => setInstallResult(null), TIMEOUTS.resultDisplay); + } + return () => {}; // Ensure a cleanup function is always returned + }, [installResult]); + + useEffect(() => { + if (uninstallResult) { + return createAutoCleanupTimer(() => setUninstallResult(null), TIMEOUTS.resultDisplay); + } + return () => {}; // Ensure a cleanup function is always returned + }, [uninstallResult]); + + const handleInstallClick = async () => { + try { + setInstalling(true); + const result = await runInstallFGMod(); + setInstallResult(result); + if (result.status === "success") { + setPathExists(true); + } + } catch (e) { + console.error(e); + } finally { + setInstalling(false); + } + }; + + const handleUninstallClick = async () => { + try { + setUninstalling(true); + const result = await runUninstallFGMod(); + setUninstallResult(result); + if (result.status === "success") { + setPathExists(false); + } + } catch (e) { + console.error(e); + } finally { + setUninstalling(false); + } + }; + + return ( + <PanelSection> + <InstallationStatus + pathExists={pathExists} + installing={installing} + onInstallClick={handleInstallClick} + /> + + <OptiScalerHeader pathExists={pathExists} /> + + <ClipboardCommands pathExists={pathExists} /> + + <InstructionCard pathExists={pathExists} /> + + <OptiScalerWiki pathExists={pathExists} /> + + <UninstallButton + pathExists={pathExists} + uninstalling={uninstalling} + onUninstallClick={handleUninstallClick} + /> + </PanelSection> + ); +} diff --git a/src/components/OptiScalerHeader.tsx b/src/components/OptiScalerHeader.tsx new file mode 100644 index 0000000..27dc501 --- /dev/null +++ b/src/components/OptiScalerHeader.tsx @@ -0,0 +1,30 @@ +import { PanelSectionRow } from "@decky/ui"; +import optiScalerImage from "../../assets/optiscaler.png"; + +interface OptiScalerHeaderProps { + pathExists: boolean | null; +} + +export function OptiScalerHeader({ pathExists }: OptiScalerHeaderProps) { + if (pathExists !== true) return null; + + return ( + <PanelSectionRow> + <div style={{ + display: 'flex', + justifyContent: 'center', + marginBottom: '16px' + }}> + <img + src={optiScalerImage} + alt="OptiScaler" + style={{ + maxWidth: '100%', + height: 'auto', + borderRadius: '8px' + }} + /> + </div> + </PanelSectionRow> + ); +} diff --git a/src/components/OptiScalerWiki.tsx b/src/components/OptiScalerWiki.tsx new file mode 100644 index 0000000..f8545f9 --- /dev/null +++ b/src/components/OptiScalerWiki.tsx @@ -0,0 +1,28 @@ +import { PanelSectionRow, ButtonItem } from "@decky/ui"; +import { FaBook } from "react-icons/fa"; + +interface OptiScalerWikiProps { + pathExists: boolean | null; +} + +export function OptiScalerWiki({ pathExists }: OptiScalerWikiProps) { + if (pathExists !== true) return null; + + const handleWikiClick = () => { + window.open("https://github.com/optiscaler/OptiScaler/wiki", "_blank"); + }; + + return ( + <PanelSectionRow> + <ButtonItem + layout="below" + onClick={handleWikiClick} + > + <div style={{ display: "flex", alignItems: "center", gap: "8px" }}> + <FaBook /> + <div>OptiScaler Wiki</div> + </div> + </ButtonItem> + </PanelSectionRow> + ); +} diff --git a/src/components/README.md b/src/components/README.md new file mode 100644 index 0000000..19c50a7 --- /dev/null +++ b/src/components/README.md @@ -0,0 +1,55 @@ +# Components Structure + +This directory contains the organized component structure for the Decky Framegen plugin. + +## Component Hierarchy + +### Main Orchestrator +- **`OptiScalerControls.tsx`** - Main component that orchestrates all OptiScaler functionality and state management + +### Sub-components (Logical Sections) +- **`InstallationStatus.tsx`** - Shows installation status and setup button when mod is not installed +- **`OptiScalerHeader.tsx`** - Displays the OptiScaler logo/image when installed +- **`ClipboardCommands.tsx`** - Contains the patch/unpatch command copy buttons +- **`InstructionCard.tsx`** - Shows usage instructions and help text +- **`OptiScalerWiki.tsx`** - Wiki documentation button +- **`UninstallButton.tsx`** - Red remove/uninstall button + +### Utility Components +- **`SmartClipboardButton.tsx`** - Reusable clipboard copy button component +- **`ResultDisplay.tsx`** - Display component for operation results + +### Other Components +- **`InstalledGamesSection.tsx`** - Games section component (currently commented out) + +## State Management + +All state is managed in the main `OptiScalerControls` component: +- `pathExists` - Whether the mod is installed +- `installing` - Installation progress state +- `uninstalling` - Uninstallation progress state +- `installResult` - Result of installation operation +- `uninstallResult` - Result of uninstallation operation + +## Component Flow + +1. **Not Installed State**: Shows `InstallationStatus` with setup button +2. **Installed State**: Shows all components in order: + - OptiScaler header image + - Clipboard command buttons + - Instruction card + - Wiki button + - Uninstall button (red) + +## Benefits of This Structure + +- **Separation of Concerns**: Each component has a single responsibility +- **Reusability**: Components can be easily reused or rearranged +- **Maintainability**: Easier to find and modify specific functionality +- **Testability**: Smaller components are easier to test +- **State Management**: Centralized state in the orchestrator component +- **Clean Imports**: Barrel exports through `index.ts` for cleaner imports + +## Clean Architecture + +All legacy components have been removed and replaced with this organized structure. The codebase is now clean and follows modern React patterns. diff --git a/src/components/UninstallButton.tsx b/src/components/UninstallButton.tsx new file mode 100644 index 0000000..1f55548 --- /dev/null +++ b/src/components/UninstallButton.tsx @@ -0,0 +1,29 @@ +import { PanelSectionRow, ButtonItem } from "@decky/ui"; +import { MESSAGES } from "../utils/constants"; + +interface UninstallButtonProps { + pathExists: boolean | null; + uninstalling: boolean; + onUninstallClick: () => void; +} + +export function UninstallButton({ pathExists, uninstalling, onUninstallClick }: UninstallButtonProps) { + if (pathExists !== true) return null; + + return ( + <PanelSectionRow> + <ButtonItem + layout="below" + onClick={onUninstallClick} + disabled={uninstalling} + > + <div style={{ + color: '#ef4444', + fontWeight: 'bold' + }}> + {uninstalling ? MESSAGES.uninstalling : MESSAGES.uninstallButton} + </div> + </ButtonItem> + </PanelSectionRow> + ); +} diff --git a/src/components/index.ts b/src/components/index.ts new file mode 100644 index 0000000..3f67341 --- /dev/null +++ b/src/components/index.ts @@ -0,0 +1,10 @@ +// Component exports for cleaner imports +export { OptiScalerControls } from './OptiScalerControls'; +export { InstallationStatus } from './InstallationStatus'; +export { OptiScalerHeader } from './OptiScalerHeader'; +export { ClipboardCommands } from './ClipboardCommands'; +export { InstructionCard } from './InstructionCard'; +export { OptiScalerWiki } from './OptiScalerWiki'; +export { UninstallButton } from './UninstallButton'; +export { SmartClipboardButton } from './SmartClipboardButton'; +export { ResultDisplay } from './ResultDisplay'; diff --git a/src/exports.ts b/src/exports.ts index a95f7b6..56c2292 100644 --- a/src/exports.ts +++ b/src/exports.ts @@ -1,5 +1,5 @@ // Re-export components -export { FGModInstallerSection } from './components/FGModInstallerSection'; +export { OptiScalerControls } from './components'; export { InstalledGamesSection } from './components/InstalledGamesSection'; export { ResultDisplay } from './components/ResultDisplay'; diff --git a/src/index.tsx b/src/index.tsx index 5cf59e4..0ea93e0 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,9 +1,8 @@ import { definePlugin } from "@decky/api"; import { MdOutlineAutoAwesomeMotion } from "react-icons/md"; import { useState, useEffect } from "react"; -import { FGModInstallerSection } from "./components/FGModInstallerSection"; +import { OptiScalerControls } from "./components"; // import { InstalledGamesSection } from "./components/InstalledGamesSection"; -import { DocumentationButton } from "./components/DocumentationButton"; import { checkFGModPath } from "./api"; import { safeAsyncOperation } from "./utils"; import { TIMEOUTS } from "./utils/constants"; @@ -27,11 +26,10 @@ function MainContent() { return ( <> - <FGModInstallerSection pathExists={pathExists} setPathExists={setPathExists} /> + <OptiScalerControls pathExists={pathExists} setPathExists={setPathExists} /> {pathExists === true ? ( <> {/* <InstalledGamesSection /> */} - <DocumentationButton /> </> ) : null} </> @@ -39,12 +37,12 @@ function MainContent() { } export default definePlugin(() => ({ - name: "Framegen Plugin", + name: "Decky Framegen", titleView: <div>Decky Framegen</div>, alwaysRender: true, content: <MainContent />, icon: <MdOutlineAutoAwesomeMotion />, onDismount() { - console.log("Framegen Plugin unmounted"); + console.log("Decky Framegen Plugin unmounted"); }, })); diff --git a/src/utils/constants.ts b/src/utils/constants.ts index c406e5b..1f583c0 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -62,5 +62,5 @@ export const MESSAGES = { installSuccess: "✅ OptiScaler mod setup successfully!", uninstallSuccess: "✅ OptiScaler mod removed successfully.", instructionTitle: "How to Use:", - instructionText: "Click 'Copy Patch Command' or 'Copy Unpatch Command', then go to your game's properties, and paste the command into the Launch Options field.\n\nIn-game: Enable DLSS in graphics settings, or assign a back button to keyboard's 'Insert' key for extended OptiScaler options." + instructionText: "Click 'Copy Patch Command' or 'Copy Unpatch Command', then go to your game's properties, and paste the command into the Launch Options field.\n\nIn-game: Enable DLSS in graphics settings to unlock FSR 3.1/XeSS 2.0 in DirectX12 Games.\n\nFor extended OptiScaler options, assign a back button to a keyboard's 'Insert' key." }; |
