diff options
| author | Kurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com> | 2025-07-21 13:50:23 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-07-21 13:50:23 -0400 |
| commit | b0621d15c675b7b8c1615b0699cc85ea1430a728 (patch) | |
| tree | 01f555d9b592be219da5a1da6a70950f11c7cdc0 /src/components | |
| parent | 9573344450de451b8f9c7295c11318010d67f1d5 (diff) | |
| download | Decky-Framegen-b0621d15c675b7b8c1615b0699cc85ea1430a728.tar.gz Decky-Framegen-b0621d15c675b7b8c1615b0699cc85ea1430a728.zip | |
* hooking clipboard automation button, hide plugin wiki for now
* add direct copy to clip buttons for patch and unpatch
Diffstat (limited to 'src/components')
| -rw-r--r-- | src/components/DocumentationButton.tsx | 2 | ||||
| -rw-r--r-- | src/components/FGModInstallerSection.tsx | 17 | ||||
| -rw-r--r-- | src/components/SmartClipboardButton.tsx | 128 |
3 files changed, 147 insertions, 0 deletions
diff --git a/src/components/DocumentationButton.tsx b/src/components/DocumentationButton.tsx index 4125fd3..7069bc2 100644 --- a/src/components/DocumentationButton.tsx +++ b/src/components/DocumentationButton.tsx @@ -12,6 +12,7 @@ export function DocumentationButton() { return ( <PanelSection> + {/* <PanelSectionRow> <ButtonItem layout="below" @@ -23,6 +24,7 @@ export function DocumentationButton() { </div> </ButtonItem> </PanelSectionRow> + */} <PanelSectionRow> <ButtonItem layout="below" diff --git a/src/components/FGModInstallerSection.tsx b/src/components/FGModInstallerSection.tsx index 1e347c0..6777301 100644 --- a/src/components/FGModInstallerSection.tsx +++ b/src/components/FGModInstallerSection.tsx @@ -2,6 +2,7 @@ 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"; @@ -98,6 +99,22 @@ export function FGModInstallerSection({ pathExists, setPathExists }: FGModInstal </div> </PanelSectionRow> ) : null} + + {pathExists === true ? ( + <SmartClipboardButton + command="~/fgmod/fgmod %command%" + buttonText="Copy Patch Command" + successMessage="Patch command ready to paste" + /> + ) : null} + + {pathExists === true ? ( + <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 new file mode 100644 index 0000000..7d250f5 --- /dev/null +++ b/src/components/SmartClipboardButton.tsx @@ -0,0 +1,128 @@ +import { useState } from "react"; +import { PanelSectionRow, ButtonItem } from "@decky/ui"; +import { FaClipboard } 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" +}: SmartClipboardButtonProps) { + const [isLoading, setIsLoading] = useState(false); + + const getLaunchOptionText = (): string => { + return command; + }; + + const copyToClipboard = async () => { + if (isLoading) return; + + setIsLoading(true); + try { + const text = getLaunchOptionText(); + + // Use the proven input simulation method + const tempInput = document.createElement('input'); + tempInput.value = text; + tempInput.style.position = 'absolute'; + tempInput.style.left = '-9999px'; + document.body.appendChild(tempInput); + + // Focus and select the text + tempInput.focus(); + tempInput.select(); + + // Try copying using execCommand first (most reliable in gaming mode) + let copySuccess = false; + try { + if (document.execCommand('copy')) { + copySuccess = true; + } + } catch (e) { + // If execCommand fails, try navigator.clipboard as fallback + try { + await navigator.clipboard.writeText(text); + copySuccess = true; + } catch (clipboardError) { + console.error('Both copy methods failed:', e, clipboardError); + } + } + + // Clean up + document.body.removeChild(tempInput); + + if (copySuccess) { + // 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)" + }); + } + } catch (e) { + // Verification failed but copy likely worked + toaster.toast({ + title: "Copied to Clipboard!", + body: "Launch option copied successfully" + }); + } + } else { + toaster.toast({ + title: "Copy Failed", + body: "Unable to copy to clipboard" + }); + } + + } catch (error) { + toaster.toast({ + title: "Copy Failed", + body: `Error: ${String(error)}` + }); + } finally { + setIsLoading(false); + } + }; + + return ( + <PanelSectionRow> + <ButtonItem + layout="below" + onClick={copyToClipboard} + disabled={isLoading} + > + <div style={{ display: "flex", alignItems: "center", gap: "8px" }}> + {isLoading ? ( + <FaClipboard style={{ + animation: "pulse 1s ease-in-out infinite", + opacity: 0.7 + }} /> + ) : ( + <FaClipboard /> + )} + <div>{isLoading ? "Copying..." : buttonText}</div> + </div> + </ButtonItem> + <style>{` + @keyframes pulse { + 0% { opacity: 0.7; } + 50% { opacity: 1; } + 100% { opacity: 0.7; } + } + `}</style> + </PanelSectionRow> + ); +} |
