diff options
| author | xXJSONDeruloXx <danielhimebauch@gmail.com> | 2025-07-21 23:15:29 -0400 |
|---|---|---|
| committer | xXJSONDeruloXx <danielhimebauch@gmail.com> | 2025-07-21 23:15:29 -0400 |
| commit | 4112393b17d25e54f1b8822734210b045da97613 (patch) | |
| tree | 7fd335fb69fc7bf2260acd72a505ae5476bbd6b6 /src | |
| parent | e54b7e2c5f3a736f248353317007f922771ab0c7 (diff) | |
| download | decky-lsfg-vk-4112393b17d25e54f1b8822734210b045da97613.tar.gz decky-lsfg-vk-4112393b17d25e54f1b8822734210b045da97613.zip | |
dry up with py generators for ts backends
Diffstat (limited to 'src')
| -rw-r--r-- | src/components/SmartClipboardButton.tsx | 71 | ||||
| -rw-r--r-- | src/components/index.ts | 2 | ||||
| -rw-r--r-- | src/config/generatedConfigSchema.ts | 20 | ||||
| -rw-r--r-- | src/hooks/useInstallationActions.ts | 37 | ||||
| -rw-r--r-- | src/hooks/useLsfgHooks.ts | 15 | ||||
| -rw-r--r-- | src/utils/clipboardUtils.ts | 70 | ||||
| -rw-r--r-- | src/utils/toastUtils.ts | 115 |
7 files changed, 224 insertions, 106 deletions
diff --git a/src/components/SmartClipboardButton.tsx b/src/components/SmartClipboardButton.tsx index 81223bd..098b53d 100644 --- a/src/components/SmartClipboardButton.tsx +++ b/src/components/SmartClipboardButton.tsx @@ -1,8 +1,9 @@ import { useState } from "react"; import { PanelSectionRow, ButtonItem } from "@decky/ui"; import { FaClipboard } from "react-icons/fa"; -import { toaster } from "@decky/api"; import { getLaunchOption } from "../api/lsfgApi"; +import { showClipboardSuccessToast, showClipboardErrorToast, showSuccessToast } from "../utils/toastUtils"; +import { copyWithVerification } from "../utils/clipboardUtils"; export function SmartClipboardButton() { const [isLoading, setIsLoading] = useState(false); @@ -22,72 +23,20 @@ export function SmartClipboardButton() { setIsLoading(true); try { const text = await getLaunchOptionText(); + const { success, verified } = await copyWithVerification(text); - // 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: "Launch option ready to paste" - }); - } 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" - }); + if (success) { + if (verified) { + showClipboardSuccessToast(); + } else { + showSuccessToast("Copied to Clipboard!", "Launch option copied (verification unavailable)"); } } else { - toaster.toast({ - title: "Copy Failed", - body: "Unable to copy to clipboard" - }); + showClipboardErrorToast(); } } catch (error) { - toaster.toast({ - title: "Copy Failed", - body: `Error: ${String(error)}` - }); + showClipboardErrorToast(); } finally { setIsLoading(false); } diff --git a/src/components/index.ts b/src/components/index.ts index 682598c..e4568f1 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -2,7 +2,7 @@ export { Content } from "./Content"; export { StatusDisplay } from "./StatusDisplay"; export { InstallationButton } from "./InstallationButton"; export { ConfigurationSection } from "./ConfigurationSection"; -// export { UsageInstructions } from "./UsageInstructions"; +export { UsageInstructions } from "./UsageInstructions"; export { WikiButton } from "./WikiButton"; export { SmartClipboardButton } from "./SmartClipboardButton"; export { LaunchOptionInfo } from "./LaunchOptionInfo"; diff --git a/src/config/generatedConfigSchema.ts b/src/config/generatedConfigSchema.ts index d862ad2..9813f17 100644 --- a/src/config/generatedConfigSchema.ts +++ b/src/config/generatedConfigSchema.ts @@ -111,17 +111,17 @@ export function getDefaults(): ConfigurationData { }; } -export function getFieldTypes(): Record<string, string> { +export function getFieldTypes(): Record<string, ConfigFieldType> { return { - dll: "ConfigFieldType.STRING", - multiplier: "ConfigFieldType.INTEGER", - flow_scale: "ConfigFieldType.FLOAT", - performance_mode: "ConfigFieldType.BOOLEAN", - hdr_mode: "ConfigFieldType.BOOLEAN", - experimental_present_mode: "ConfigFieldType.STRING", - dxvk_frame_rate: "ConfigFieldType.INTEGER", - enable_wow64: "ConfigFieldType.BOOLEAN", - disable_steamdeck_mode: "ConfigFieldType.BOOLEAN", + dll: ConfigFieldType.STRING, + multiplier: ConfigFieldType.INTEGER, + flow_scale: ConfigFieldType.FLOAT, + performance_mode: ConfigFieldType.BOOLEAN, + hdr_mode: ConfigFieldType.BOOLEAN, + experimental_present_mode: ConfigFieldType.STRING, + dxvk_frame_rate: ConfigFieldType.INTEGER, + enable_wow64: ConfigFieldType.BOOLEAN, + disable_steamdeck_mode: ConfigFieldType.BOOLEAN, }; } diff --git a/src/hooks/useInstallationActions.ts b/src/hooks/useInstallationActions.ts index 8dcf831..18de6b5 100644 --- a/src/hooks/useInstallationActions.ts +++ b/src/hooks/useInstallationActions.ts @@ -1,6 +1,11 @@ import { useState } from "react"; -import { toaster } from "@decky/api"; import { installLsfgVk, uninstallLsfgVk } from "../api/lsfgApi"; +import { + showInstallSuccessToast, + showInstallErrorToast, + showUninstallSuccessToast, + showUninstallErrorToast +} from "../utils/toastUtils"; export function useInstallationActions() { const [isInstalling, setIsInstalling] = useState<boolean>(false); @@ -19,10 +24,7 @@ export function useInstallationActions() { if (result.success) { setIsInstalled(true); setInstallationStatus("lsfg-vk installed successfully!"); - toaster.toast({ - title: "Installation Complete", - body: "lsfg-vk has been installed successfully" - }); + showInstallSuccessToast(); // Reload lsfg config after installation if (reloadConfig) { @@ -30,17 +32,11 @@ export function useInstallationActions() { } } else { setInstallationStatus(`Installation failed: ${result.error}`); - toaster.toast({ - title: "Installation Failed", - body: result.error || "Unknown error occurred" - }); + showInstallErrorToast(result.error); } } catch (error) { setInstallationStatus(`Installation failed: ${error}`); - toaster.toast({ - title: "Installation Failed", - body: `Error: ${error}` - }); + showInstallErrorToast(String(error)); } finally { setIsInstalling(false); } @@ -58,23 +54,14 @@ export function useInstallationActions() { if (result.success) { setIsInstalled(false); setInstallationStatus("lsfg-vk uninstalled successfully!"); - toaster.toast({ - title: "Uninstallation Complete", - body: result.message || "lsfg-vk has been uninstalled successfully" - }); + showUninstallSuccessToast(); } else { setInstallationStatus(`Uninstallation failed: ${result.error}`); - toaster.toast({ - title: "Uninstallation Failed", - body: result.error || "Unknown error occurred" - }); + showUninstallErrorToast(result.error); } } catch (error) { setInstallationStatus(`Uninstallation failed: ${error}`); - toaster.toast({ - title: "Uninstallation Failed", - body: `Error: ${error}` - }); + showUninstallErrorToast(String(error)); } finally { setIsUninstalling(false); } diff --git a/src/hooks/useLsfgHooks.ts b/src/hooks/useLsfgHooks.ts index e514d72..e5dea63 100644 --- a/src/hooks/useLsfgHooks.ts +++ b/src/hooks/useLsfgHooks.ts @@ -1,5 +1,4 @@ import { useState, useEffect, useCallback } from "react"; -import { toaster } from "@decky/api"; import { checkLsfgVkInstalled, checkLosslessScalingDll, @@ -8,6 +7,7 @@ import { type ConfigUpdateResult } from "../api/lsfgApi"; import { ConfigurationData, ConfigurationManager } from "../config/configSchema"; +import { showErrorToast, ToastMessages } from "../utils/toastUtils"; export function useInstallationStatus() { const [isInstalled, setIsInstalled] = useState<boolean>(false); @@ -95,17 +95,14 @@ export function useLsfgConfig() { if (result.success) { setConfig(newConfig); } else { - toaster.toast({ - title: "Update Failed", - body: result.error || "Failed to update configuration" - }); + showErrorToast( + ToastMessages.CONFIG_UPDATE_ERROR.title, + result.error || ToastMessages.CONFIG_UPDATE_ERROR.body + ); } return result; } catch (error) { - toaster.toast({ - title: "Update Failed", - body: `Error: ${error}` - }); + showErrorToast(ToastMessages.CONFIG_UPDATE_ERROR.title, String(error)); return { success: false, error: String(error) }; } }, []); diff --git a/src/utils/clipboardUtils.ts b/src/utils/clipboardUtils.ts new file mode 100644 index 0000000..2d480fc --- /dev/null +++ b/src/utils/clipboardUtils.ts @@ -0,0 +1,70 @@ +/** + * Clipboard utilities for reliable copy operations across different environments + */ + +/** + * Reliably copy text to clipboard using multiple fallback methods + * This is especially important in gaming mode where clipboard APIs may behave differently + */ +export async function copyToClipboard(text: string): Promise<boolean> { + // 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); + + try { + // 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); + } + } + + return copySuccess; + } finally { + // Clean up + document.body.removeChild(tempInput); + } +} + +/** + * Verify that text was successfully copied to clipboard + */ +export async function verifyCopy(expectedText: string): Promise<boolean> { + try { + const readBack = await navigator.clipboard.readText(); + return readBack === expectedText; + } catch (e) { + // Verification not available, assume success + return true; + } +} + +/** + * Copy text with verification and return success status + */ +export async function copyWithVerification(text: string): Promise<{ success: boolean; verified: boolean }> { + const copySuccess = await copyToClipboard(text); + + if (!copySuccess) { + return { success: false, verified: false }; + } + + const verified = await verifyCopy(text); + return { success: true, verified }; +} diff --git a/src/utils/toastUtils.ts b/src/utils/toastUtils.ts new file mode 100644 index 0000000..dce0a59 --- /dev/null +++ b/src/utils/toastUtils.ts @@ -0,0 +1,115 @@ +/** + * Centralized toast notification utilities + * Provides consistent success/error messaging patterns + */ + +import { toaster } from "@decky/api"; + +export interface ToastOptions { + title: string; + body: string; +} + +/** + * Show a success toast notification + */ +export function showSuccessToast(title: string, body: string): void { + toaster.toast({ + title, + body + }); +} + +/** + * Show an error toast notification + */ +export function showErrorToast(title: string, body: string): void { + toaster.toast({ + title, + body + }); +} + +/** + * Standard success messages for common operations + */ +export const ToastMessages = { + INSTALL_SUCCESS: { + title: "Installation Complete", + body: "lsfg-vk has been installed successfully" + }, + INSTALL_ERROR: { + title: "Installation Failed", + body: "Unknown error occurred" + }, + UNINSTALL_SUCCESS: { + title: "Uninstallation Complete", + body: "lsfg-vk has been uninstalled successfully" + }, + UNINSTALL_ERROR: { + title: "Uninstallation Failed", + body: "Unknown error occurred" + }, + CONFIG_UPDATE_ERROR: { + title: "Update Failed", + body: "Failed to update configuration" + }, + CLIPBOARD_SUCCESS: { + title: "Copied to Clipboard!", + body: "Launch option ready to paste" + }, + CLIPBOARD_ERROR: { + title: "Copy Failed", + body: "Unable to copy to clipboard" + } +} as const; + +/** + * Show a toast with dynamic error message + */ +export function showErrorToastWithMessage(title: string, error: unknown): void { + const errorMessage = error instanceof Error ? error.message : String(error); + showErrorToast(title, errorMessage); +} + +/** + * Show installation success toast + */ +export function showInstallSuccessToast(): void { + showSuccessToast(ToastMessages.INSTALL_SUCCESS.title, ToastMessages.INSTALL_SUCCESS.body); +} + +/** + * Show installation error toast + */ +export function showInstallErrorToast(error?: string): void { + showErrorToast(ToastMessages.INSTALL_ERROR.title, error || ToastMessages.INSTALL_ERROR.body); +} + +/** + * Show uninstallation success toast + */ +export function showUninstallSuccessToast(): void { + showSuccessToast(ToastMessages.UNINSTALL_SUCCESS.title, ToastMessages.UNINSTALL_SUCCESS.body); +} + +/** + * Show uninstallation error toast + */ +export function showUninstallErrorToast(error?: string): void { + showErrorToast(ToastMessages.UNINSTALL_ERROR.title, error || ToastMessages.UNINSTALL_ERROR.body); +} + +/** + * Show clipboard success toast + */ +export function showClipboardSuccessToast(): void { + showSuccessToast(ToastMessages.CLIPBOARD_SUCCESS.title, ToastMessages.CLIPBOARD_SUCCESS.body); +} + +/** + * Show clipboard error toast + */ +export function showClipboardErrorToast(): void { + showErrorToast(ToastMessages.CLIPBOARD_ERROR.title, ToastMessages.CLIPBOARD_ERROR.body); +} |
