diff options
| author | Kurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com> | 2025-07-22 00:17:50 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-07-22 00:17:50 -0400 |
| commit | 4fee88babae0dc69a332480c3e491500dab64d7c (patch) | |
| tree | 2e9ee6360337978e9264173a2e20daf5ec195393 /src | |
| parent | 2106ef8eb31ee46611fce07dd715d3ac1c4ca0ab (diff) | |
| parent | e83f026b0d1edf5a7ee1477f4b10eb574f506f95 (diff) | |
| download | decky-lsfg-vk-4fee88babae0dc69a332480c3e491500dab64d7c.tar.gz decky-lsfg-vk-4fee88babae0dc69a332480c3e491500dab64d7c.zip | |
Merge pull request #63 from xXJSONDeruloXx/cleanup-jul21
refactor: remove unused backend files and improve configuration handl…
Diffstat (limited to 'src')
| -rw-r--r-- | src/components/SmartClipboardButton.tsx | 71 | ||||
| -rw-r--r-- | src/components/UsageInstructions.tsx | 22 | ||||
| -rw-r--r-- | src/components/index.ts | 2 | ||||
| -rw-r--r-- | src/config/configSchema.ts | 252 | ||||
| -rw-r--r-- | src/config/generatedConfigSchema.ts | 127 | ||||
| -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 |
9 files changed, 451 insertions, 260 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/UsageInstructions.tsx b/src/components/UsageInstructions.tsx index bcf258b..0c27517 100644 --- a/src/components/UsageInstructions.tsx +++ b/src/components/UsageInstructions.tsx @@ -5,7 +5,7 @@ interface UsageInstructionsProps { config: ConfigurationData; } -export function UsageInstructions({ config }: UsageInstructionsProps) { +export function UsageInstructions({ config: _config }: UsageInstructionsProps) { return ( <> <PanelSectionRow> @@ -56,26 +56,6 @@ export function UsageInstructions({ config }: UsageInstructionsProps) { </div> </PanelSectionRow> - {/* <PanelSectionRow> - <div - style={{ - fontSize: "12px", - lineHeight: "1.4", - opacity: "0.8", - whiteSpace: "pre-wrap" - }} - > - {`Current Configuration: -• DLL Path: ${config.dll} -• Multiplier: ${config.multiplier}x -• Flow Scale: ${Math.round(config.flow_scale * 100)}% -• Performance Mode: ${config.performance_mode ? "Yes" : "No"} -• HDR Mode: ${config.hdr_mode ? "Yes" : "No"} -• Present Mode: ${config.experimental_present_mode || "FIFO (VSync)"} -• DXVK Frame Rate: ${config.dxvk_frame_rate > 0 ? `${config.dxvk_frame_rate} FPS` : "Off"}`} - </div> - </PanelSectionRow> */} - <PanelSectionRow> <div style={{ 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/configSchema.ts b/src/config/configSchema.ts index 03b1510..4ab0d25 100644 --- a/src/config/configSchema.ts +++ b/src/config/configSchema.ts @@ -1,181 +1,147 @@ /** - * Centralized configuration schema for lsfg-vk frontend. + * Configuration schema and management for LSFG VK plugin * - * This mirrors the Python configuration schema to ensure consistency - * between frontend and backend configuration handling. + * This file re-exports auto-generated configuration constants from generatedConfigSchema.ts + * and provides the ConfigurationManager class for handling configuration operations. */ -// Configuration field type enum -export enum ConfigFieldType { - BOOLEAN = "boolean", - INTEGER = "integer", - FLOAT = "float", - STRING = "string" -} +import { callable } from "@decky/api"; +import type { ConfigurationData } from './generatedConfigSchema'; +import { getDefaults } from './generatedConfigSchema'; -// Configuration field definition -export interface ConfigField { - name: string; - fieldType: ConfigFieldType; - default: boolean | number | string; - description: string; -} +// Re-export all auto-generated configuration constants +export { + ConfigFieldType, + ConfigField, + CONFIG_SCHEMA, + ConfigurationData, + getFieldNames, + getDefaults, + getFieldTypes +} from './generatedConfigSchema'; -// Configuration schema - must match Python CONFIG_SCHEMA -export const CONFIG_SCHEMA: Record<string, ConfigField> = { - dll: { - name: "dll", - fieldType: ConfigFieldType.STRING, - default: "/games/Lossless Scaling/Lossless.dll", - description: "specify where Lossless.dll is stored" - }, - - multiplier: { - name: "multiplier", - fieldType: ConfigFieldType.INTEGER, - default: 1, - description: "change the fps multiplier" - }, - - flow_scale: { - name: "flow_scale", - fieldType: ConfigFieldType.FLOAT, - default: 0.8, - description: "change the flow scale" - }, - - performance_mode: { - name: "performance_mode", - fieldType: ConfigFieldType.BOOLEAN, - default: true, - description: "toggle performance mode" - }, - - hdr_mode: { - name: "hdr_mode", - fieldType: ConfigFieldType.BOOLEAN, - default: false, - description: "enable hdr in games that support it" - }, - - experimental_present_mode: { - name: "experimental_present_mode", - fieldType: ConfigFieldType.STRING, - default: "fifo", - description: "experimental: override vulkan present mode (fifo/mailbox/immediate)" - }, - - dxvk_frame_rate: { - name: "dxvk_frame_rate", - fieldType: ConfigFieldType.INTEGER, - default: 0, - description: "Base framerate cap for DirectX games, before frame multiplier (0 = disabled, requires game re-launch)" - }, - - enable_wow64: { - name: "enable_wow64", - fieldType: ConfigFieldType.BOOLEAN, - default: false, - description: "enable PROTON_USE_WOW64=1 for 32-bit games (use with ProtonGE to fix crashing)" - }, - - disable_steamdeck_mode: { - name: "disable_steamdeck_mode", - fieldType: ConfigFieldType.BOOLEAN, - default: false, - description: "disable Steam Deck mode (unlocks hidden settings in some games)" - } -}; +/** + * Configuration management class + * Handles CRUD operations for plugin configuration + */ +export class ConfigurationManager { + private static instance: ConfigurationManager; + private _config: ConfigurationData | null = null; -// Type-safe configuration data structure -export interface ConfigurationData { - dll: string; - multiplier: number; - flow_scale: number; - performance_mode: boolean; - hdr_mode: boolean; - experimental_present_mode: string; - dxvk_frame_rate: number; - enable_wow64: boolean; - disable_steamdeck_mode: boolean; -} + // Callable methods for backend communication + private getConfiguration = callable<[], { success: boolean; data?: ConfigurationData; error?: string }>("get_configuration"); + private setConfiguration = callable<[{ config_data: ConfigurationData }], { success: boolean; error?: string }>("set_configuration"); + private resetConfiguration = callable<[], { success: boolean; data?: ConfigurationData; error?: string }>("reset_configuration"); + + private constructor() {} + + static getInstance(): ConfigurationManager { + if (!ConfigurationManager.instance) { + ConfigurationManager.instance = new ConfigurationManager(); + } + return ConfigurationManager.instance; + } -// Centralized configuration manager -export class ConfigurationManager { /** * Get default configuration values */ static getDefaults(): ConfigurationData { - const defaults = {} as ConfigurationData; - Object.values(CONFIG_SCHEMA).forEach(field => { - (defaults as any)[field.name] = field.default; - }); - return defaults; + return getDefaults(); } /** - * Get ordered list of configuration field names + * Create args array from config object for lsfg API calls */ - static getFieldNames(): string[] { - return Object.keys(CONFIG_SCHEMA); + static createArgsFromConfig(config: ConfigurationData): [string, number, number, boolean, boolean, string, number, boolean, boolean] { + return [ + config.dll, + config.multiplier, + config.flow_scale, + config.performance_mode, + config.hdr_mode, + config.experimental_present_mode, + config.dxvk_frame_rate, + config.enable_wow64, + config.disable_steamdeck_mode + ]; } /** - * Get field type mapping + * Load configuration from backend */ - static getFieldTypes(): Record<string, ConfigFieldType> { - return Object.values(CONFIG_SCHEMA).reduce((acc, field) => { - acc[field.name] = field.fieldType; - return acc; - }, {} as Record<string, ConfigFieldType>); + async loadConfig(): Promise<ConfigurationData> { + try { + const result = await this.getConfiguration(); + if (result.success && result.data) { + this._config = result.data; + return this._config; + } else { + throw new Error(result.error || 'Failed to load configuration'); + } + } catch (error) { + console.error('Error loading configuration:', error); + throw error; + } } /** - * Create ordered arguments array from configuration object + * Save configuration to backend */ - static createArgsFromConfig(config: ConfigurationData): (boolean | number | string)[] { - return this.getFieldNames().map(fieldName => - config[fieldName as keyof ConfigurationData] - ); + async saveConfig(config: ConfigurationData): Promise<void> { + try { + const result = await this.setConfiguration({ config_data: config }); + if (result.success) { + this._config = config; + } else { + throw new Error(result.error || 'Failed to save configuration'); + } + } catch (error) { + console.error('Error saving configuration:', error); + throw error; + } } /** - * Validate configuration object against schema + * Update a single configuration field */ - static validateConfig(config: Partial<ConfigurationData>): ConfigurationData { - const defaults = this.getDefaults(); - const validated = { ...defaults }; - - Object.entries(CONFIG_SCHEMA).forEach(([fieldName, fieldDef]) => { - const value = config[fieldName as keyof ConfigurationData]; - if (value !== undefined) { - // Type validation - if (fieldDef.fieldType === ConfigFieldType.BOOLEAN) { - (validated as any)[fieldName] = Boolean(value); - } else if (fieldDef.fieldType === ConfigFieldType.INTEGER) { - (validated as any)[fieldName] = parseInt(String(value), 10); - } else if (fieldDef.fieldType === ConfigFieldType.FLOAT) { - (validated as any)[fieldName] = parseFloat(String(value)); - } else if (fieldDef.fieldType === ConfigFieldType.STRING) { - (validated as any)[fieldName] = String(value); - } - } - }); - - return validated; + async updateField(fieldName: keyof ConfigurationData, value: any): Promise<void> { + if (!this._config) { + await this.loadConfig(); + } + + const updatedConfig = { + ...this._config!, + [fieldName]: value + }; + + await this.saveConfig(updatedConfig); } /** - * Get configuration field definition + * Get current configuration (cached) */ - static getFieldDef(fieldName: string): ConfigField | undefined { - return CONFIG_SCHEMA[fieldName]; + getConfig(): ConfigurationData | null { + return this._config; } /** - * Get all field definitions + * Reset configuration to defaults */ - static getAllFieldDefs(): ConfigField[] { - return Object.values(CONFIG_SCHEMA); + async resetToDefaults(): Promise<ConfigurationData> { + try { + const result = await this.resetConfiguration(); + if (result.success && result.data) { + this._config = result.data; + return this._config; + } else { + throw new Error(result.error || 'Failed to reset configuration'); + } + } catch (error) { + console.error('Error resetting configuration:', error); + throw error; + } } } + +// Export singleton instance +export const configManager = ConfigurationManager.getInstance(); diff --git a/src/config/generatedConfigSchema.ts b/src/config/generatedConfigSchema.ts new file mode 100644 index 0000000..9813f17 --- /dev/null +++ b/src/config/generatedConfigSchema.ts @@ -0,0 +1,127 @@ +/** + * Auto-generated TypeScript configuration schema. + * DO NOT EDIT MANUALLY - Generated from shared_config.py + * + * To update this file, run: python3 generate_ts_config.py > src/config/generatedConfigSchema.ts + */ + +// Configuration field type enum - matches Python +export enum ConfigFieldType { + BOOLEAN = "boolean", + INTEGER = "integer", + FLOAT = "float", + STRING = "string" +} + +// Configuration field definition +export interface ConfigField { + name: string; + fieldType: ConfigFieldType; + default: boolean | number | string; + description: string; +} + +// Configuration schema - auto-generated from Python +export const CONFIG_SCHEMA: Record<string, ConfigField> = { + dll: { + name: "dll", + fieldType: ConfigFieldType.STRING, + default: "/games/Lossless Scaling/Lossless.dll", + description: "specify where Lossless.dll is stored" + }, + multiplier: { + name: "multiplier", + fieldType: ConfigFieldType.INTEGER, + default: 1, + description: "change the fps multiplier" + }, + flow_scale: { + name: "flow_scale", + fieldType: ConfigFieldType.FLOAT, + default: 0.8, + description: "change the flow scale" + }, + performance_mode: { + name: "performance_mode", + fieldType: ConfigFieldType.BOOLEAN, + default: true, + description: "use a lighter model for FG (recommended for most games)" + }, + hdr_mode: { + name: "hdr_mode", + fieldType: ConfigFieldType.BOOLEAN, + default: false, + description: "enable HDR mode (only for games that support HDR)" + }, + experimental_present_mode: { + name: "experimental_present_mode", + fieldType: ConfigFieldType.STRING, + default: "fifo", + description: "override Vulkan present mode (may cause crashes)" + }, + dxvk_frame_rate: { + name: "dxvk_frame_rate", + fieldType: ConfigFieldType.INTEGER, + default: 0, + description: "base framerate cap for DirectX games before frame multiplier" + }, + enable_wow64: { + name: "enable_wow64", + fieldType: ConfigFieldType.BOOLEAN, + default: false, + description: "enable PROTON_USE_WOW64=1 for 32-bit games (use with ProtonGE to fix crashing)" + }, + disable_steamdeck_mode: { + name: "disable_steamdeck_mode", + fieldType: ConfigFieldType.BOOLEAN, + default: false, + description: "disable Steam Deck mode (unlocks hidden settings in some games)" + }, +}; + +// Type-safe configuration data structure +export interface ConfigurationData { + dll: string; + multiplier: number; + flow_scale: number; + performance_mode: boolean; + hdr_mode: boolean; + experimental_present_mode: string; + dxvk_frame_rate: number; + enable_wow64: boolean; + disable_steamdeck_mode: boolean; +} + +// Helper functions +export function getFieldNames(): string[] { + return Object.keys(CONFIG_SCHEMA); +} + +export function getDefaults(): ConfigurationData { + return { + dll: "/games/Lossless Scaling/Lossless.dll", + multiplier: 1, + flow_scale: 0.8, + performance_mode: true, + hdr_mode: false, + experimental_present_mode: "fifo", + dxvk_frame_rate: 0, + enable_wow64: false, + disable_steamdeck_mode: false, + }; +} + +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, + }; +} + 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); +} |
