summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorxXJSONDeruloXx <danielhimebauch@gmail.com>2025-07-21 12:28:36 -0400
committerxXJSONDeruloXx <danielhimebauch@gmail.com>2025-07-21 12:28:36 -0400
commit0668428a5ebc221d39b907f251dc0dc43e30a2df (patch)
treeb60414cb79057b3e1f38c7e114ab9ec0f6c3d61d /src
parenta7fb5ee69c8d74534f2994263558ddcd9c8c0d41 (diff)
downloaddecky-lsfg-vk-0668428a5ebc221d39b907f251dc0dc43e30a2df.tar.gz
decky-lsfg-vk-0668428a5ebc221d39b907f251dc0dc43e30a2df.zip
testing alt keyboard copy methods
Diffstat (limited to 'src')
-rw-r--r--src/api/lsfgApi.ts10
-rw-r--r--src/components/ClipboardExperiments.tsx509
-rw-r--r--src/components/Content.tsx6
-rw-r--r--src/components/SmartClipboardButton.tsx245
-rw-r--r--src/components/index.ts2
5 files changed, 772 insertions, 0 deletions
diff --git a/src/api/lsfgApi.ts b/src/api/lsfgApi.ts
index 74caa57..b984612 100644
--- a/src/api/lsfgApi.ts
+++ b/src/api/lsfgApi.ts
@@ -36,6 +36,13 @@ 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;
@@ -99,6 +106,9 @@ 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
new file mode 100644
index 0000000..ec7d9ab
--- /dev/null
+++ b/src/components/ClipboardExperiments.tsx
@@ -0,0 +1,509 @@
+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 9ce4c35..f6f24dc 100644
--- a/src/components/Content.tsx
+++ b/src/components/Content.tsx
@@ -8,6 +8,8 @@ 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";
@@ -83,6 +85,10 @@ export function Content() {
<WikiButton />
<ClipboardButton />
+ <SmartClipboardButton />
+
+ {/* Experimental Clipboard Automation */}
+ <ClipboardExperiments />
{/* Plugin Update Checker */}
<PluginUpdateChecker />
diff --git a/src/components/SmartClipboardButton.tsx b/src/components/SmartClipboardButton.tsx
new file mode 100644
index 0000000..229560d
--- /dev/null
+++ b/src/components/SmartClipboardButton.tsx
@@ -0,0 +1,245 @@
+import { useState } from "react";
+import { PanelSectionRow, ButtonItem } from "@decky/ui";
+import { FaClipboard, FaRocket } from "react-icons/fa";
+import { toaster } from "@decky/api";
+import { getLaunchOption, copyToSystemClipboard } from "../api/lsfgApi";
+
+export function SmartClipboardButton() {
+ const [isLoading, setIsLoading] = useState(false);
+
+ const getLaunchOptionText = async (): Promise<string> => {
+ try {
+ const result = await getLaunchOption();
+ return result.launch_option || "~/lsfg %command%";
+ } catch (error) {
+ return "~/lsfg %command%";
+ }
+ };
+
+ const copyToClipboard = async () => {
+ if (isLoading) return;
+
+ setIsLoading(true);
+ try {
+ const text = await getLaunchOptionText();
+
+ // Strategy 1: Try direct navigator.clipboard first (fastest)
+ let directSuccess = false;
+ try {
+ await navigator.clipboard.writeText(text);
+ // Verify it worked
+ const readBack = await navigator.clipboard.readText();
+ directSuccess = readBack === text;
+ } catch (e) {
+ // Direct clipboard failed, will try alternatives
+ }
+
+ if (directSuccess) {
+ toaster.toast({
+ title: "Copied to Clipboard!",
+ body: "Launch option ready to paste"
+ });
+ return;
+ }
+
+ // Strategy 2: Try backend system clipboard
+ try {
+ const result = await copyToSystemClipboard(text);
+ if (result.success) {
+ toaster.toast({
+ title: "Copied to Clipboard!",
+ body: `Using ${result.method || "system clipboard"}`
+ });
+ return;
+ }
+ } catch (e) {
+ // Backend failed, fall back to browser
+ }
+
+ // 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",
+ 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 ? <FaRocket style={{ animation: "spin 1s linear infinite" }} /> : <FaClipboard />}
+ <div>{isLoading ? "Copying..." : "Smart Clipboard Copy"}</div>
+ </div>
+ </ButtonItem>
+ <style>{`
+ @keyframes spin {
+ from { transform: rotate(0deg); }
+ to { transform: rotate(360deg); }
+ }
+ `}</style>
+ </PanelSectionRow>
+ );
+}
diff --git a/src/components/index.ts b/src/components/index.ts
index c0c4804..1a36327 100644
--- a/src/components/index.ts
+++ b/src/components/index.ts
@@ -5,6 +5,8 @@ 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";