From 0b5e71fe916e92ef9ecf7de91ca43371c4bd6d25 Mon Sep 17 00:00:00 2001 From: Kurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com> Date: Tue, 29 Jul 2025 07:53:45 -0700 Subject: wording and layout tweaks (#125) * wording and layout tweaks * red in remove button * reorganize frontend components * fix ld preload permissions issue for decky 3.1.10 * bump ver --- main.py | 16 +++- package.json | 4 +- src/components/ClipboardCommands.tsx | 23 +++++ src/components/DocumentationButton.tsx | 41 --------- src/components/FGModInstallerSection.tsx | 139 ------------------------------- src/components/InstallationStatus.tsx | 28 +++++++ src/components/InstructionCard.tsx | 23 +++++ src/components/OptiScalerControls.tsx | 92 ++++++++++++++++++++ src/components/OptiScalerHeader.tsx | 30 +++++++ src/components/OptiScalerWiki.tsx | 28 +++++++ src/components/README.md | 55 ++++++++++++ src/components/UninstallButton.tsx | 29 +++++++ src/components/index.ts | 10 +++ src/exports.ts | 2 +- src/index.tsx | 10 +-- src/utils/constants.ts | 2 +- 16 files changed, 339 insertions(+), 193 deletions(-) create mode 100644 src/components/ClipboardCommands.tsx delete mode 100644 src/components/DocumentationButton.tsx delete mode 100644 src/components/FGModInstallerSection.tsx create mode 100644 src/components/InstallationStatus.tsx create mode 100644 src/components/InstructionCard.tsx create mode 100644 src/components/OptiScalerControls.tsx create mode 100644 src/components/OptiScalerHeader.tsx create mode 100644 src/components/OptiScalerWiki.tsx create mode 100644 src/components/README.md create mode 100644 src/components/UninstallButton.tsx create mode 100644 src/components/index.ts diff --git a/main.py b/main.py index 44d02a4..4eb769d 100644 --- a/main.py +++ b/main.py @@ -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 ( + <> + + + + + ); +} 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 ( - - {/* - - -
- -
Copy Launch Command
-
-
-
- */} - - -
- -
OptiScaler Wiki
-
-
-
-
- ); -} 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(null); - const [uninstallResult, setUninstallResult] = useState(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 ( - - {pathExists === false ? ( - -
- {MESSAGES.modNotInstalled} -
-
- ) : null} - - {pathExists === false ? ( - - - {installing ? MESSAGES.installing : MESSAGES.installButton} - - - ) : null} - - {pathExists === true ? ( - - - {uninstalling ? MESSAGES.uninstalling : MESSAGES.uninstallButton} - - - ) : null} - - {pathExists === true ? ( - -
- OptiScaler -
-
- ) : null} - - {pathExists === true ? ( - -
-
- {MESSAGES.instructionTitle} -
-
- {MESSAGES.instructionText} -
-
-
- ) : null} - - {pathExists === true ? ( - - ) : null} - - {pathExists === true ? ( - - ) : null} -
- ); -} 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 ( + <> + +
+ {MESSAGES.modNotInstalled} +
+
+ + + + {installing ? MESSAGES.installing : MESSAGES.installButton} + + + + ); +} 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 ( + +
+
+ {MESSAGES.instructionTitle} +
+
+ {MESSAGES.instructionText} +
+
+
+ ); +} 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(null); + const [uninstallResult, setUninstallResult] = useState(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 ( + + + + + + + + + + + + + + ); +} 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 ( + +
+ OptiScaler +
+
+ ); +} 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 ( + + +
+ +
OptiScaler Wiki
+
+
+
+ ); +} 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 ( + + +
+ {uninstalling ? MESSAGES.uninstalling : MESSAGES.uninstallButton} +
+
+
+ ); +} 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 ( <> - + {pathExists === true ? ( <> {/* */} - ) : null} @@ -39,12 +37,12 @@ function MainContent() { } export default definePlugin(() => ({ - name: "Framegen Plugin", + name: "Decky Framegen", titleView:
Decky Framegen
, alwaysRender: true, content: , icon: , 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." }; -- cgit v1.2.3