summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorxXJSONDeruloXx <danielhimebauch@gmail.com>2025-07-21 23:15:29 -0400
committerxXJSONDeruloXx <danielhimebauch@gmail.com>2025-07-21 23:15:29 -0400
commit4112393b17d25e54f1b8822734210b045da97613 (patch)
tree7fd335fb69fc7bf2260acd72a505ae5476bbd6b6 /src
parente54b7e2c5f3a736f248353317007f922771ab0c7 (diff)
downloaddecky-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.tsx71
-rw-r--r--src/components/index.ts2
-rw-r--r--src/config/generatedConfigSchema.ts20
-rw-r--r--src/hooks/useInstallationActions.ts37
-rw-r--r--src/hooks/useLsfgHooks.ts15
-rw-r--r--src/utils/clipboardUtils.ts70
-rw-r--r--src/utils/toastUtils.ts115
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);
+}