// Vessel Studio Frontend Javascript Utilities /** * Copies the provided string content to the system clipboard and displays a visual toast. * Supports modern Clipboard API and falls back to textarea selection for compatibility. * @param {string} text - Content to copy. */ function copyToClipboard(text) { if (!text) return; if (navigator.clipboard && navigator.clipboard.writeText) { navigator.clipboard.writeText(text).then(function() { showVesselToast("Copied to clipboard!"); }, function(err) { console.warn("Navigator clipboard API failed, trying fallback: ", err); fallbackCopyToClipboard(text); }); } else { fallbackCopyToClipboard(text); } } /** * Fallback clipboard copier using a temporary textarea element and document.execCommand. * Needed for non-HTTPS local bindings and sandboxed iframes (e.g. Hugging Face Spaces). * @param {string} text - Content to copy. */ function fallbackCopyToClipboard(text) { try { const textArea = document.createElement("textarea"); textArea.value = text; // Position off-screen and hide textArea.style.position = "fixed"; textArea.style.top = "0"; textArea.style.left = "0"; textArea.style.width = "2em"; textArea.style.height = "2em"; textArea.style.padding = "0"; textArea.style.border = "none"; textArea.style.outline = "none"; textArea.style.boxShadow = "none"; textArea.style.background = "transparent"; document.body.appendChild(textArea); textArea.focus(); textArea.select(); const successful = document.execCommand("copy"); document.body.removeChild(textArea); if (successful) { showVesselToast("Copied to clipboard!"); } else { console.error("Fallback execCommand copy failed."); showVesselToast("Copy failed. Please copy manually."); } } catch (err) { console.error("Fallback copy handler failed: ", err); showVesselToast("Copy failed. Please copy manually."); } } /** * Creates and displays a premium animated toast notification. * @param {string} message - Content message to display. */ function showVesselToast(message) { // Delete existing toast if active to prevent overlapping let existingToast = document.querySelector(".vessel-toast"); if (existingToast) { document.body.removeChild(existingToast); } let toast = document.createElement("div"); toast.className = "vessel-toast"; toast.innerText = message; // Apply styling parameters Object.assign(toast.style, { position: "fixed", bottom: "30px", right: "30px", backgroundColor: "#0d9488", color: "#ffffff", padding: "14px 24px", borderRadius: "8px", boxShadow: "0 10px 25px -5px rgba(13, 148, 136, 0.4)", zIndex: "99999", opacity: "0", transition: "opacity 0.25s ease, transform 0.25s ease", transform: "translateY(15px)", fontFamily: "'Plus Jakarta Sans', system-ui, sans-serif", fontSize: "0.9rem", fontWeight: "600", letterSpacing: "0.02em" }); document.body.appendChild(toast); // Trigger entrance animation setTimeout(() => { toast.style.opacity = "1"; toast.style.transform = "translateY(0)"; }, 30); // Trigger dismissal animation setTimeout(() => { toast.style.opacity = "0"; toast.style.transform = "translateY(15px)"; setTimeout(() => { if (toast.parentNode) { document.body.removeChild(toast); } }, 250); }, 2800); } // Explicitly bind helper functions to window scope to ensure visibility from Gradio components window.copyToClipboard = copyToClipboard; window.showVesselToast = showVesselToast; window.triggerBrowserPrint = function() { window.print(); }; // Initialize custom event integrations when Gradio loads document.addEventListener("DOMContentLoaded", () => { console.log("Shelf Scribe frontend initialized successfully."); });