summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com>2025-07-28 07:21:34 -0700
committerGitHub <noreply@github.com>2025-07-28 10:21:34 -0400
commit56eac1ac74e9f32bbf2541929e80b79cdb6cbcb1 (patch)
treece3e213466aeba1c538679e0fb5e266a4141f953
parent2461cb5d9c2ae31afb33bfd3c3c9a8faa9a7603c (diff)
downloadDecky-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.pngbin0 -> 100615 bytes
-rw-r--r--assets/optiscaler.pngbin0 -> 43729 bytes
-rw-r--r--package.json2
-rw-r--r--plugin.json4
-rw-r--r--src/components/FGModInstallerSection.tsx29
-rw-r--r--src/components/SmartClipboardButton.tsx62
-rw-r--r--src/index.tsx6
-rw-r--r--src/utils/constants.ts6
8 files changed, 67 insertions, 42 deletions
diff --git a/assets/opti-graph.png b/assets/opti-graph.png
new file mode 100644
index 0000000..1a601fb
--- /dev/null
+++ b/assets/opti-graph.png
Binary files differ
diff --git a/assets/optiscaler.png b/assets/optiscaler.png
new file mode 100644
index 0000000..21af233
--- /dev/null
+++ b/assets/optiscaler.png
Binary files differ
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."