diff options
| -rw-r--r-- | py_modules/lsfg_vk/plugin.py | 81 | ||||
| -rw-r--r-- | src/api/lsfgApi.ts | 10 | ||||
| -rw-r--r-- | src/components/ClipboardExperiments.tsx | 509 | ||||
| -rw-r--r-- | src/components/Content.tsx | 10 | ||||
| -rw-r--r-- | src/components/SmartClipboardButton.tsx | 253 | ||||
| -rw-r--r-- | src/components/index.ts | 2 |
6 files changed, 67 insertions, 798 deletions
diff --git a/py_modules/lsfg_vk/plugin.py b/py_modules/lsfg_vk/plugin.py index 83b318a..980cb53 100644 --- a/py_modules/lsfg_vk/plugin.py +++ b/py_modules/lsfg_vk/plugin.py @@ -493,87 +493,6 @@ class Plugin: "error": f"Error reading launch script: {str(e)}" } - async def copy_to_system_clipboard(self, text: str) -> Dict[str, Any]: - """Copy text to system clipboard using native tools - - Args: - text: The text to copy to clipboard - - Returns: - Dict indicating success or failure - """ - try: - # Detect desktop environment and available clipboard tools - clipboard_tools = [] - - # Check for Wayland (wl-clipboard) - if os.environ.get('WAYLAND_DISPLAY'): - if self._command_exists('wl-copy'): - clipboard_tools.append(('wl-copy', lambda t: subprocess.run(['wl-copy'], input=t, text=True, check=True))) - - # Check for X11 (xclip or xsel) - if os.environ.get('DISPLAY'): - if self._command_exists('xclip'): - clipboard_tools.append(('xclip', lambda t: subprocess.run(['xclip', '-selection', 'clipboard'], input=t, text=True, check=True))) - elif self._command_exists('xsel'): - clipboard_tools.append(('xsel', lambda t: subprocess.run(['xsel', '--clipboard', '--input'], input=t, text=True, check=True))) - - # Check for pbcopy (macOS - unlikely on Steam Deck but just in case) - if self._command_exists('pbcopy'): - clipboard_tools.append(('pbcopy', lambda t: subprocess.run(['pbcopy'], input=t, text=True, check=True))) - - if not clipboard_tools: - return { - "success": False, - "error": "No compatible clipboard tools found (tried: wl-copy, xclip, xsel, pbcopy)", - "method": None - } - - # Try each clipboard tool until one works - for tool_name, copy_func in clipboard_tools: - try: - copy_func(text) - return { - "success": True, - "method": tool_name, - "message": f"Successfully copied text using {tool_name}" - } - except subprocess.CalledProcessError as e: - continue # Try next tool - except Exception as e: - continue # Try next tool - - return { - "success": False, - "error": "All clipboard tools failed to copy text", - "method": None - } - - except Exception as e: - return { - "success": False, - "error": f"Error accessing system clipboard: {str(e)}", - "method": None - } - - def _command_exists(self, command: str) -> bool: - """Check if a command exists in the system PATH - - Args: - command: Command name to check - - Returns: - True if command exists, False otherwise - """ - try: - subprocess.run(['which', command], - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL, - check=True) - return True - except (subprocess.CalledProcessError, FileNotFoundError): - return False - # Lifecycle methods async def _main(self): """ diff --git a/src/api/lsfgApi.ts b/src/api/lsfgApi.ts index b984612..74caa57 100644 --- a/src/api/lsfgApi.ts +++ b/src/api/lsfgApi.ts @@ -36,13 +36,6 @@ export interface DllStatsResult { error?: string; } -export interface ClipboardResult { - success: boolean; - method?: string; - message?: string; - error?: string; -} - // Use centralized configuration data type export type LsfgConfig = ConfigurationData; @@ -106,9 +99,6 @@ export const getLaunchOption = callable<[], LaunchOptionResult>("get_launch_opti export const getConfigFileContent = callable<[], FileContentResult>("get_config_file_content"); export const getLaunchScriptContent = callable<[], FileContentResult>("get_launch_script_content"); -// Clipboard API -export const copyToSystemClipboard = callable<[string], ClipboardResult>("copy_to_system_clipboard"); - // Updated config function using centralized configuration export const updateLsfgConfig = callable< [string, number, number, boolean, boolean, string, number, boolean, boolean], diff --git a/src/components/ClipboardExperiments.tsx b/src/components/ClipboardExperiments.tsx deleted file mode 100644 index ec7d9ab..0000000 --- a/src/components/ClipboardExperiments.tsx +++ /dev/null @@ -1,509 +0,0 @@ -import { useState } from "react"; -import { PanelSectionRow, ButtonItem, Field, Focusable } from "@decky/ui"; -import { FaClipboard, FaFlask, FaRocket, FaCog, FaTerminal } from "react-icons/fa"; -import { toaster } from "@decky/api"; -import { getLaunchOption, copyToSystemClipboard } from "../api/lsfgApi"; - -interface ExperimentResult { - success: boolean; - method: string; - error?: string; - details?: string; -} - -export function ClipboardExperiments() { - const [results, setResults] = useState<ExperimentResult[]>([]); - const [isLoading, setIsLoading] = useState<string | null>(null); - - const addResult = (result: ExperimentResult) => { - setResults(prev => [...prev, { ...result, timestamp: Date.now() }]); - }; - - const getLaunchOptionText = async (): Promise<string> => { - try { - const result = await getLaunchOption(); - return result.launch_option || "~/lsfg %command%"; - } catch (error) { - return "~/lsfg %command%"; - } - }; - - // Approach 1: Direct Navigator Clipboard API - const testDirectClipboard = async () => { - setIsLoading("direct"); - try { - const text = await getLaunchOptionText(); - await navigator.clipboard.writeText(text); - - // Test if it actually worked by reading back - const readBack = await navigator.clipboard.readText(); - const success = readBack === text; - - addResult({ - success, - method: "Direct Navigator Clipboard", - details: success ? `Successfully copied: "${text}"` : `Mismatch: wrote "${text}", read "${readBack}"` - }); - - if (success) { - toaster.toast({ - title: "Clipboard Success!", - body: "Direct navigator.clipboard API worked" - }); - } - } catch (error) { - addResult({ - success: false, - method: "Direct Navigator Clipboard", - error: String(error) - }); - } finally { - setIsLoading(null); - } - }; - - // Approach 2: CEF Browser with Data URL - const testDataUrlApproach = async () => { - setIsLoading("dataurl"); - try { - const text = await getLaunchOptionText(); - const htmlContent = ` - <!DOCTYPE html> - <html> - <head> - <meta charset="utf-8"> - <title>Clipboard Helper</title> - <style> - body { - font-family: 'Motiva Sans', Arial, sans-serif; - background: #1e2328; - color: white; - padding: 20px; - text-align: center; - } - .content { background: #2a475e; padding: 20px; border-radius: 8px; margin: 20px; } - .success { color: #66bb6a; font-weight: bold; } - .error { color: #f44336; font-weight: bold; } - code { background: rgba(255,255,255,0.1); padding: 4px 8px; border-radius: 4px; } - </style> - </head> - <body> - <div class="content"> - <h2>๐ Clipboard Automation Test</h2> - <p>Attempting to copy launch option: <code>${text}</code></p> - <div id="status">Working...</div> - <div id="details"></div> - <button onclick="window.close()" style="margin-top: 20px; padding: 8px 16px;">Close</button> - </div> - <script> - (async function() { - const statusEl = document.getElementById('status'); - const detailsEl = document.getElementById('details'); - const textToCopy = ${JSON.stringify(text)}; - - try { - await navigator.clipboard.writeText(textToCopy); - - // Verify it worked - const readBack = await navigator.clipboard.readText(); - if (readBack === textToCopy) { - statusEl.innerHTML = '<span class="success">โ
Success! Text copied to clipboard</span>'; - detailsEl.innerHTML = 'The launch option is now in your clipboard. You can close this window.'; - } else { - statusEl.innerHTML = '<span class="error">โ ๏ธ Partial Success</span>'; - detailsEl.innerHTML = 'Text was written but verification failed. Check clipboard manually.'; - } - } catch (error) { - statusEl.innerHTML = '<span class="error">โ Failed</span>'; - detailsEl.innerHTML = 'Error: ' + error.message; - } - })(); - </script> - </body> - </html> - `; - - const dataUrl = 'data:text/html;charset=utf-8,' + encodeURIComponent(htmlContent); - window.open(dataUrl, '_blank'); - - addResult({ - success: true, - method: "Data URL Browser Window", - details: "Opened data URL with auto-copy script" - }); - } catch (error) { - addResult({ - success: false, - method: "Data URL Browser Window", - error: String(error) - }); - } finally { - setIsLoading(null); - } - }; - - // Approach 3: Focused Element + Selection + Input API - const testInputSimulation = async () => { - setIsLoading("input"); - try { - const text = await getLaunchOptionText(); - - // Create a temporary input element - 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 different copy methods - let copySuccess = false; - let method = ''; - - // Method 1: execCommand (deprecated but might work) - try { - if (document.execCommand('copy')) { - copySuccess = true; - method = 'execCommand'; - } - } catch (e) {} - - // Method 2: Navigator clipboard on selected text - if (!copySuccess) { - try { - await navigator.clipboard.writeText(text); - copySuccess = true; - method = 'navigator.clipboard'; - } catch (e) {} - } - - // Clean up - document.body.removeChild(tempInput); - - if (copySuccess) { - // Verify - try { - const readBack = await navigator.clipboard.readText(); - const verified = readBack === text; - addResult({ - success: verified, - method: `Input Simulation (${method})`, - details: verified ? "Successfully copied and verified" : "Copy worked but verification failed" - }); - } catch (e) { - addResult({ - success: true, - method: `Input Simulation (${method})`, - details: "Copy appeared to work but couldn't verify" - }); - } - } else { - addResult({ - success: false, - method: "Input Simulation", - error: "All copy methods failed" - }); - } - } catch (error) { - addResult({ - success: false, - method: "Input Simulation", - error: String(error) - }); - } finally { - setIsLoading(null); - } - }; - - // Approach 4: Backend Clipboard - const testBackendClipboard = async () => { - setIsLoading("backend"); - try { - const text = await getLaunchOptionText(); - - const result = await copyToSystemClipboard(text); - - if (result.success) { - addResult({ - success: true, - method: `Backend System Clipboard (${result.method})`, - details: result.message || "Successfully copied to system clipboard" - }); - - toaster.toast({ - title: "Clipboard Success!", - body: `Copied using ${result.method}` - }); - } else { - addResult({ - success: false, - method: "Backend System Clipboard", - error: result.error || "Unknown error" - }); - } - } catch (error) { - addResult({ - success: false, - method: "Backend System Clipboard", - error: String(error) - }); - } finally { - setIsLoading(null); - } - }; - - // Approach 5: Hybrid approach with immediate feedback - const testHybridApproach = async () => { - setIsLoading("hybrid"); - try { - const text = await getLaunchOptionText(); - - // Try direct first - let directWorked = false; - try { - await navigator.clipboard.writeText(text); - const readBack = await navigator.clipboard.readText(); - directWorked = readBack === text; - } catch (e) {} - - if (directWorked) { - addResult({ - success: true, - method: "Hybrid (Direct Success)", - details: "Direct clipboard API worked, no browser needed" - }); - - toaster.toast({ - title: "Clipboard Success!", - body: "Launch option copied to clipboard" - }); - } else { - // Fall back to optimized browser approach - const htmlContent = ` - <!DOCTYPE html> - <html> - <head> - <meta charset="utf-8"> - <title>Quick Copy</title> - <style> - body { font-family: system-ui; background: #1a1a1a; color: white; padding: 20px; } - .container { max-width: 400px; margin: 0 auto; text-align: center; } - .success { color: #4CAF50; } - button { padding: 12px 24px; font-size: 16px; margin: 10px; } - </style> - </head> - <body> - <div class="container"> - <h3>๐ Clipboard Helper</h3> - <p>Copying: <strong>${text}</strong></p> - <div id="status">โณ Working...</div> - <button onclick="copyAndClose()" id="copyBtn">Copy & Close</button> - <button onclick="window.close()">Just Close</button> - </div> - <script> - const textToCopy = ${JSON.stringify(text)}; - let copied = false; - - async function autoCopy() { - try { - await navigator.clipboard.writeText(textToCopy); - document.getElementById('status').innerHTML = '<span class="success">โ
Copied successfully!</span>'; - copied = true; - setTimeout(() => window.close(), 1500); - } catch (e) { - document.getElementById('status').innerHTML = 'โ Auto-copy failed. Use button below.'; - } - } - - async function copyAndClose() { - try { - await navigator.clipboard.writeText(textToCopy); - window.close(); - } catch (e) { - alert('Copy failed: ' + e.message); - } - } - - // Auto-copy on load - autoCopy(); - </script> - </body> - </html> - `; - - const dataUrl = 'data:text/html;charset=utf-8,' + encodeURIComponent(htmlContent); - window.open(dataUrl, '_blank', 'width=500,height=300'); - - addResult({ - success: true, - method: "Hybrid (Browser Fallback)", - details: "Direct failed, opened optimized browser window" - }); - } - } catch (error) { - addResult({ - success: false, - method: "Hybrid Approach", - error: String(error) - }); - } finally { - setIsLoading(null); - } - }; - - const clearResults = () => { - setResults([]); - }; - - return ( - <> - <PanelSectionRow> - <div - style={{ - fontSize: "14px", - fontWeight: "bold", - marginTop: "16px", - marginBottom: "8px", - borderBottom: "1px solid rgba(255, 255, 255, 0.2)", - paddingBottom: "4px", - color: "white" - }} - > - ๐งช Clipboard Automation Experiments - </div> - </PanelSectionRow> - - <PanelSectionRow> - <div style={{ fontSize: "12px", opacity: 0.8, marginBottom: "8px" }}> - Testing different approaches to automate clipboard access in Steam Deck gaming mode: - </div> - </PanelSectionRow> - - {/* Test Buttons */} - <PanelSectionRow> - <ButtonItem - layout="below" - onClick={testDirectClipboard} - disabled={isLoading === "direct"} - > - <div style={{ display: "flex", alignItems: "center", gap: "8px" }}> - <FaClipboard /> - <div>Test Direct Clipboard API</div> - {isLoading === "direct" && <div>โณ</div>} - </div> - </ButtonItem> - </PanelSectionRow> - - <PanelSectionRow> - <ButtonItem - layout="below" - onClick={testDataUrlApproach} - disabled={isLoading === "dataurl"} - > - <div style={{ display: "flex", alignItems: "center", gap: "8px" }}> - <FaRocket /> - <div>Test Data URL Browser</div> - {isLoading === "dataurl" && <div>โณ</div>} - </div> - </ButtonItem> - </PanelSectionRow> - - <PanelSectionRow> - <ButtonItem - layout="below" - onClick={testInputSimulation} - disabled={isLoading === "input"} - > - <div style={{ display: "flex", alignItems: "center", gap: "8px" }}> - <FaCog /> - <div>Test Input Simulation</div> - {isLoading === "input" && <div>โณ</div>} - </div> - </ButtonItem> - </PanelSectionRow> - - <PanelSectionRow> - <ButtonItem - layout="below" - onClick={testBackendClipboard} - disabled={isLoading === "backend"} - > - <div style={{ display: "flex", alignItems: "center", gap: "8px" }}> - <FaTerminal /> - <div>Test Backend Clipboard</div> - {isLoading === "backend" && <div>โณ</div>} - </div> - </ButtonItem> - </PanelSectionRow> - - <PanelSectionRow> - <ButtonItem - layout="below" - onClick={testHybridApproach} - disabled={isLoading === "hybrid"} - > - <div style={{ display: "flex", alignItems: "center", gap: "8px" }}> - <FaFlask /> - <div>Test Hybrid Approach (Recommended)</div> - {isLoading === "hybrid" && <div>โณ</div>} - </div> - </ButtonItem> - </PanelSectionRow> - - {/* Results Section */} - {results.length > 0 && ( - <> - <PanelSectionRow> - <Field - label={`Test Results (${results.length})`} - bottomSeparator="none" - > - <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}> - <div style={{ fontSize: "12px", opacity: 0.8 }}> - {results.filter(r => r.success).length} successful, {results.filter(r => !r.success).length} failed - </div> - <ButtonItem - layout="inline" - onClick={clearResults} - > - Clear - </ButtonItem> - </div> - </Field> - </PanelSectionRow> - - {results.slice(-5).map((result, index) => ( - <PanelSectionRow key={index}> - <Focusable> - <div style={{ - padding: "8px", - backgroundColor: result.success ? "rgba(76, 175, 80, 0.1)" : "rgba(244, 67, 54, 0.1)", - borderLeft: `3px solid ${result.success ? "#4CAF50" : "#f44336"}`, - borderRadius: "4px", - fontSize: "11px" - }}> - <div style={{ fontWeight: "bold", marginBottom: "4px" }}> - {result.success ? "โ
" : "โ"} {result.method} - </div> - {result.details && ( - <div style={{ color: "#4CAF50", marginBottom: "2px" }}> - {result.details} - </div> - )} - {result.error && ( - <div style={{ color: "#f44336" }}> - Error: {result.error} - </div> - )} - </div> - </Focusable> - </PanelSectionRow> - ))} - </> - )} - </> - ); -} diff --git a/src/components/Content.tsx b/src/components/Content.tsx index f6f24dc..5f4e139 100644 --- a/src/components/Content.tsx +++ b/src/components/Content.tsx @@ -7,9 +7,7 @@ import { InstallationButton } from "./InstallationButton"; import { ConfigurationSection } from "./ConfigurationSection"; import { UsageInstructions } from "./UsageInstructions"; import { WikiButton } from "./WikiButton"; -import { ClipboardButton } from "./ClipboardButton"; import { SmartClipboardButton } from "./SmartClipboardButton"; -import { ClipboardExperiments } from "./ClipboardExperiments"; import { PluginUpdateChecker } from "./PluginUpdateChecker"; import { NerdStuffModal } from "./NerdStuffModal"; import { ConfigurationData } from "../config/configSchema"; @@ -84,16 +82,10 @@ export function Content() { <UsageInstructions config={config} /> <WikiButton /> - <ClipboardButton /> <SmartClipboardButton /> - {/* Experimental Clipboard Automation */} - <ClipboardExperiments /> - {/* Plugin Update Checker */} - <PluginUpdateChecker /> - - {/* Nerd Stuff Button */} + <PluginUpdateChecker /> {/* Nerd Stuff Button */} <PanelSectionRow> <ButtonItem layout="below" diff --git a/src/components/SmartClipboardButton.tsx b/src/components/SmartClipboardButton.tsx index 229560d..81223bd 100644 --- a/src/components/SmartClipboardButton.tsx +++ b/src/components/SmartClipboardButton.tsx @@ -1,8 +1,8 @@ import { useState } from "react"; import { PanelSectionRow, ButtonItem } from "@decky/ui"; -import { FaClipboard, FaRocket } from "react-icons/fa"; +import { FaClipboard } from "react-icons/fa"; import { toaster } from "@decky/api"; -import { getLaunchOption, copyToSystemClipboard } from "../api/lsfgApi"; +import { getLaunchOption } from "../api/lsfgApi"; export function SmartClipboardButton() { const [isLoading, setIsLoading] = useState(false); @@ -23,195 +23,66 @@ export function SmartClipboardButton() { try { const text = await getLaunchOptionText(); - // Strategy 1: Try direct navigator.clipboard first (fastest) - let directSuccess = false; + // 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 { - await navigator.clipboard.writeText(text); - // Verify it worked - const readBack = await navigator.clipboard.readText(); - directSuccess = readBack === text; + if (document.execCommand('copy')) { + copySuccess = true; + } } catch (e) { - // Direct clipboard failed, will try alternatives - } - - if (directSuccess) { - toaster.toast({ - title: "Copied to Clipboard!", - body: "Launch option ready to paste" - }); - return; + // 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); + } } - - // Strategy 2: Try backend system clipboard - try { - const result = await copyToSystemClipboard(text); - if (result.success) { + + // 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: `Using ${result.method || "system clipboard"}` + body: "Launch option copied successfully" }); - return; } - } catch (e) { - // Backend failed, fall back to browser + } else { + toaster.toast({ + title: "Copy Failed", + body: "Unable to copy to clipboard" + }); } - // Strategy 3: Fall back to optimized browser window - const htmlContent = ` - <!DOCTYPE html> - <html> - <head> - <meta charset="utf-8"> - <title>Quick Copy - Steam Deck Clipboard Helper</title> - <style> - body { - font-family: 'Motiva Sans', system-ui, sans-serif; - background: linear-gradient(135deg, #1e2328 0%, #2a475e 100%); - color: white; - padding: 20px; - margin: 0; - display: flex; - align-items: center; - justify-content: center; - min-height: 100vh; - } - .container { - background: rgba(42, 71, 94, 0.9); - padding: 30px; - border-radius: 12px; - text-align: center; - box-shadow: 0 8px 32px rgba(0,0,0,0.3); - border: 1px solid rgba(255,255,255,0.1); - max-width: 500px; - width: 100%; - } - h2 { - margin-top: 0; - color: #66c0f4; - font-size: 24px; - } - .launch-option { - background: rgba(0,0,0,0.3); - padding: 15px; - border-radius: 8px; - font-family: 'Fira Code', 'Courier New', monospace; - font-size: 16px; - margin: 20px 0; - word-break: break-all; - border: 1px solid rgba(102, 192, 244, 0.3); - } - .status { - margin: 20px 0; - font-size: 16px; - min-height: 24px; - } - .success { color: #66bb6a; } - .error { color: #f44336; } - button { - background: linear-gradient(135deg, #417a9b 0%, #67c1f5 100%); - color: white; - border: none; - padding: 12px 24px; - font-size: 16px; - border-radius: 6px; - cursor: pointer; - margin: 8px; - transition: all 0.2s; - font-family: inherit; - } - button:hover { - background: linear-gradient(135deg, #4e8bb8 0%, #7bc8f7 100%); - transform: translateY(-1px); - } - button:active { - transform: translateY(0px); - } - .close-timer { - font-size: 14px; - opacity: 0.7; - margin-top: 15px; - } - </style> - </head> - <body> - <div class="container"> - <h2>๐ Steam Deck Clipboard Helper</h2> - <div>Copy this launch option for your Steam games:</div> - <div class="launch-option">${text}</div> - <div id="status" class="status">โณ Copying to clipboard...</div> - <div> - <button onclick="copyAndClose()" id="copyBtn">Copy & Close</button> - <button onclick="window.close()">Close</button> - </div> - <div class="close-timer" id="timer"></div> - </div> - <script> - const textToCopy = ${JSON.stringify(text)}; - let copied = false; - let autoCloseTimer = null; - - async function autoCopy() { - try { - await navigator.clipboard.writeText(textToCopy); - // Verify it worked - const readBack = await navigator.clipboard.readText(); - if (readBack === textToCopy) { - document.getElementById('status').innerHTML = '<span class="success">โ
Successfully copied to clipboard!</span>'; - copied = true; - startAutoClose(); - } else { - document.getElementById('status').innerHTML = '<span class="error">โ ๏ธ Copy may have failed - use button below</span>'; - } - } catch (e) { - document.getElementById('status').innerHTML = '<span class="error">โ Auto-copy failed - click "Copy & Close" below</span>'; - } - } - - async function copyAndClose() { - try { - await navigator.clipboard.writeText(textToCopy); - const readBack = await navigator.clipboard.readText(); - if (readBack === textToCopy) { - window.close(); - } else { - alert('Copy verification failed. Please try again or copy manually.'); - } - } catch (e) { - alert('Copy failed: ' + e.message); - } - } - - function startAutoClose() { - let seconds = 3; - const timerEl = document.getElementById('timer'); - timerEl.textContent = \`Window will close in \${seconds} seconds...\`; - - autoCloseTimer = setInterval(() => { - seconds--; - if (seconds <= 0) { - clearInterval(autoCloseTimer); - window.close(); - } else { - timerEl.textContent = \`Window will close in \${seconds} seconds...\`; - } - }, 1000); - } - - // Auto-copy on load - window.addEventListener('load', autoCopy); - </script> - </body> - </html> - `; - - const dataUrl = 'data:text/html;charset=utf-8,' + encodeURIComponent(htmlContent); - window.open(dataUrl, '_blank', 'width=600,height=400,scrollbars=no,resizable=yes'); - - toaster.toast({ - title: "Browser Helper Opened", - body: "Clipboard helper window opened with auto-copy" - }); - } catch (error) { toaster.toast({ title: "Copy Failed", @@ -230,14 +101,22 @@ export function SmartClipboardButton() { disabled={isLoading} > <div style={{ display: "flex", alignItems: "center", gap: "8px" }}> - {isLoading ? <FaRocket style={{ animation: "spin 1s linear infinite" }} /> : <FaClipboard />} - <div>{isLoading ? "Copying..." : "Smart Clipboard Copy"}</div> + {isLoading ? ( + <FaClipboard style={{ + animation: "pulse 1s ease-in-out infinite", + opacity: 0.7 + }} /> + ) : ( + <FaClipboard /> + )} + <div>{isLoading ? "Copying..." : "Copy Launch Option"}</div> </div> </ButtonItem> <style>{` - @keyframes spin { - from { transform: rotate(0deg); } - to { transform: rotate(360deg); } + @keyframes pulse { + 0% { opacity: 0.7; } + 50% { opacity: 1; } + 100% { opacity: 0.7; } } `}</style> </PanelSectionRow> diff --git a/src/components/index.ts b/src/components/index.ts index 1a36327..682598c 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -4,9 +4,7 @@ export { InstallationButton } from "./InstallationButton"; export { ConfigurationSection } from "./ConfigurationSection"; // export { UsageInstructions } from "./UsageInstructions"; export { WikiButton } from "./WikiButton"; -export { ClipboardButton } from "./ClipboardButton"; export { SmartClipboardButton } from "./SmartClipboardButton"; -export { ClipboardExperiments } from "./ClipboardExperiments"; export { LaunchOptionInfo } from "./LaunchOptionInfo"; export { PluginUpdateChecker } from "./PluginUpdateChecker"; export { NerdStuffModal } from "./NerdStuffModal"; |
