diff options
| author | Kurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com> | 2025-07-28 07:21:34 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-07-28 10:21:34 -0400 |
| commit | 56eac1ac74e9f32bbf2541929e80b79cdb6cbcb1 (patch) | |
| tree | ce3e213466aeba1c538679e0fb5e266a4141f953 | |
| parent | 2461cb5d9c2ae31afb33bfd3c3c9a8faa9a7603c (diff) | |
| download | Decky-Framegen-56eac1ac74e9f32bbf2541929e80b79cdb6cbcb1.tar.gz Decky-Framegen-56eac1ac74e9f32bbf2541929e80b79cdb6cbcb1.zip | |
refined copy to clipboard ui feedback (#122)
* copy feedback
* add opti logo and update wording
* branding updates
* hide check mark when installed
| -rw-r--r-- | assets/opti-graph.png | bin | 0 -> 100615 bytes | |||
| -rw-r--r-- | assets/optiscaler.png | bin | 0 -> 43729 bytes | |||
| -rw-r--r-- | package.json | 2 | ||||
| -rw-r--r-- | plugin.json | 4 | ||||
| -rw-r--r-- | src/components/FGModInstallerSection.tsx | 29 | ||||
| -rw-r--r-- | src/components/SmartClipboardButton.tsx | 62 | ||||
| -rw-r--r-- | src/index.tsx | 6 | ||||
| -rw-r--r-- | src/utils/constants.ts | 6 |
8 files changed, 67 insertions, 42 deletions
diff --git a/assets/opti-graph.png b/assets/opti-graph.png Binary files differnew file mode 100644 index 0000000..1a601fb --- /dev/null +++ b/assets/opti-graph.png diff --git a/assets/optiscaler.png b/assets/optiscaler.png Binary files differnew file mode 100644 index 0000000..21af233 --- /dev/null +++ b/assets/optiscaler.png diff --git a/package.json b/package.json index 2f22876..42ccb30 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "decky-framegen", - "version": "0.11.5", + "version": "0.11.6", "description": "plugin to install OptiScaler bleeding-edge and enable upscaling and framegen in a large variety of games.", "type": "module", "scripts": { diff --git a/plugin.json b/plugin.json index 125d5ad..1e4c179 100644 --- a/plugin.json +++ b/plugin.json @@ -1,6 +1,6 @@ { - "name": "Decky-Framegen", - "author": "JSON Derulo", + "name": "Decky Framegen", + "author": "Kurt Himebauch", "flags": [], "api_version": 1, "publish": { diff --git a/src/components/FGModInstallerSection.tsx b/src/components/FGModInstallerSection.tsx index 6777301..b82e749 100644 --- a/src/components/FGModInstallerSection.tsx +++ b/src/components/FGModInstallerSection.tsx @@ -5,6 +5,7 @@ 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; @@ -63,10 +64,10 @@ export function FGModInstallerSection({ pathExists, setPathExists }: FGModInstal return ( <PanelSection> - {pathExists !== null ? ( + {pathExists === false ? ( <PanelSectionRow> - <div style={pathExists ? STYLES.statusInstalled : STYLES.statusNotInstalled}> - {pathExists ? MESSAGES.modInstalled : MESSAGES.modNotInstalled} + <div style={STYLES.statusNotInstalled}> + {MESSAGES.modNotInstalled} </div> </PanelSectionRow> ) : null} @@ -89,6 +90,26 @@ export function FGModInstallerSection({ pathExists, setPathExists }: FGModInstal {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} @@ -104,7 +125,6 @@ export function FGModInstallerSection({ pathExists, setPathExists }: FGModInstal <SmartClipboardButton command="~/fgmod/fgmod %command%" buttonText="Copy Patch Command" - successMessage="Patch command ready to paste" /> ) : null} @@ -112,7 +132,6 @@ export function FGModInstallerSection({ pathExists, setPathExists }: FGModInstal <SmartClipboardButton command="~/fgmod/fgmod-uninstaller.sh %command%" buttonText="Copy Unpatch Command" - successMessage="Unpatch command ready to paste" /> ) : null} </PanelSection> diff --git a/src/components/SmartClipboardButton.tsx b/src/components/SmartClipboardButton.tsx index 7d250f5..095da15 100644 --- a/src/components/SmartClipboardButton.tsx +++ b/src/components/SmartClipboardButton.tsx @@ -1,31 +1,37 @@ -import { useState } from "react"; +import { useState, useEffect } from "react"; import { PanelSectionRow, ButtonItem } from "@decky/ui"; -import { FaClipboard } from "react-icons/fa"; +import { FaClipboard, FaCheck } from "react-icons/fa"; import { toaster } from "@decky/api"; interface SmartClipboardButtonProps { command?: string; buttonText?: string; - successMessage?: string; } export function SmartClipboardButton({ command = "~/fgmod/fgmod %command%", - buttonText = "Copy Launch Command", - successMessage = "Launch option ready to paste" + buttonText = "Copy Launch Command" }: SmartClipboardButtonProps) { const [isLoading, setIsLoading] = useState(false); + const [showSuccess, setShowSuccess] = useState(false); - const getLaunchOptionText = (): string => { - return command; - }; + // 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) return; + if (isLoading || showSuccess) return; setIsLoading(true); try { - const text = getLaunchOptionText(); + const text = command; // Use the proven input simulation method const tempInput = document.createElement('input'); @@ -58,27 +64,18 @@ export function SmartClipboardButton({ document.body.removeChild(tempInput); if (copySuccess) { + // Show success feedback in the button instead of toast + setShowSuccess(true); // Verify the copy worked by reading back try { const readBack = await navigator.clipboard.readText(); - if (readBack === text) { - toaster.toast({ - title: "Copied to Clipboard!", - body: successMessage - }); - } else { - // Copy worked but verification failed - still consider it success - toaster.toast({ - title: "Copied to Clipboard!", - body: "Launch option copied (verification unavailable)" - }); + if (readBack !== text) { + // Copy worked but verification failed - still show success + console.log('Copy verification failed but copy likely worked'); } } catch (e) { // Verification failed but copy likely worked - toaster.toast({ - title: "Copied to Clipboard!", - body: "Launch option copied successfully" - }); + console.log('Copy verification unavailable but copy likely worked'); } } else { toaster.toast({ @@ -102,10 +99,14 @@ export function SmartClipboardButton({ <ButtonItem layout="below" onClick={copyToClipboard} - disabled={isLoading} + disabled={isLoading || showSuccess} > <div style={{ display: "flex", alignItems: "center", gap: "8px" }}> - {isLoading ? ( + {showSuccess ? ( + <FaCheck style={{ + color: "#4CAF50" // Green color for success + }} /> + ) : isLoading ? ( <FaClipboard style={{ animation: "pulse 1s ease-in-out infinite", opacity: 0.7 @@ -113,7 +114,12 @@ export function SmartClipboardButton({ ) : ( <FaClipboard /> )} - <div>{isLoading ? "Copying..." : buttonText}</div> + <div style={{ + color: showSuccess ? "#4CAF50" : "inherit", + fontWeight: showSuccess ? "bold" : "normal" + }}> + {showSuccess ? "Copied to clipboard" : isLoading ? "Copying..." : buttonText} + </div> </div> </ButtonItem> <style>{` diff --git a/src/index.tsx b/src/index.tsx index b85c6b1..5cf59e4 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,8 +1,8 @@ import { definePlugin } from "@decky/api"; -import { RiAiGenerate } from "react-icons/ri"; +import { MdOutlineAutoAwesomeMotion } from "react-icons/md"; import { useState, useEffect } from "react"; import { FGModInstallerSection } from "./components/FGModInstallerSection"; -import { InstalledGamesSection } from "./components/InstalledGamesSection"; +// import { InstalledGamesSection } from "./components/InstalledGamesSection"; import { DocumentationButton } from "./components/DocumentationButton"; import { checkFGModPath } from "./api"; import { safeAsyncOperation } from "./utils"; @@ -43,7 +43,7 @@ export default definePlugin(() => ({ titleView: <div>Decky Framegen</div>, alwaysRender: true, content: <MainContent />, - icon: <RiAiGenerate />, + icon: <MdOutlineAutoAwesomeMotion />, onDismount() { console.log("Framegen Plugin unmounted"); }, diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 7741b64..c406e5b 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -56,10 +56,10 @@ export const MESSAGES = { modInstalled: "✅ OptiScaler Mod Installed", modNotInstalled: "❌ OptiScaler Mod Not Installed", installing: "Installing OptiScaler...", - installButton: "Install OptiScaler FG Mod", + installButton: "Setup OptiScaler Mod", uninstalling: "Removing OptiScaler...", - uninstallButton: "Uninstall OptiScaler FG Mod", - installSuccess: "✅ OptiScaler mod installed successfully!", + uninstallButton: "Remove OptiScaler Mod", + 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." |
