| // src/components/workspace/WebBrowserPanel.tsx | |
| import { useState, useRef, useCallback, useEffect } from "react"; | |
| import { Search, ArrowLeft, ArrowRight, RotateCw, Home, ExternalLink, Copy, Check, BookmarkPlus, Settings } from "lucide-react"; | |
| import { toast } from "react-toastify"; | |
| interface WebBrowserPanelProps { | |
| initialUrl?: string; | |
| panelId?: string; | |
| } | |
| export function WebBrowserPanel({ initialUrl = "https://scholar.google.com", panelId }: WebBrowserPanelProps) { | |
| const [url, setUrl] = useState(initialUrl); | |
| const [input, setInput] = useState(initialUrl); | |
| const [loading, setLoading] = useState(false); | |
| const [canGoBack, setCanGoBack] = useState(false); | |
| const [canGoForward, setCanGoForward] = useState(false); | |
| const [copied, setCopied] = useState(false); | |
| const iframeRef = useRef<HTMLIFrameElement>(null); | |
| const navigate = useCallback((newUrl: string) => { | |
| let finalUrl = newUrl.trim(); | |
| if (!/^https?:\/\//i.test(finalUrl) && !finalUrl.startsWith("about:")) { | |
| if (finalUrl.includes(" ") || !finalUrl.includes(".")) { | |
| finalUrl = `https://scholar.google.com/scholar?q=${encodeURIComponent(finalUrl)}`; | |
| } else { | |
| finalUrl = "https://" + finalUrl; | |
| } | |
| } | |
| setUrl(finalUrl); | |
| setInput(finalUrl); | |
| setLoading(true); | |
| }, []); | |
| const goBack = () => { | |
| iframeRef.current?.contentWindow?.history.back(); | |
| }; | |
| const goForward = () => { | |
| iframeRef.current?.contentWindow?.history.forward(); | |
| }; | |
| const reload = () => { | |
| iframeRef.current?.contentWindow?.location.reload(); | |
| setLoading(true); | |
| }; | |
| const goHome = () => { | |
| navigate(initialUrl); | |
| }; | |
| const handleLoad = () => { | |
| setLoading(false); | |
| // Update navigation buttons state | |
| try { | |
| const iframe = iframeRef.current; | |
| if (iframe?.contentWindow?.history.length) { | |
| // Cannot reliably check back/forward due to cross-origin restrictions | |
| } | |
| } catch (e) { | |
| // Cross-origin, ignore | |
| } | |
| }; | |
| const copyUrl = async () => { | |
| await navigator.clipboard.writeText(url); | |
| setCopied(true); | |
| toast.success("URL copied to clipboard"); | |
| setTimeout(() => setCopied(false), 2000); | |
| }; | |
| const openInNewTab = () => { | |
| window.open(url, "_blank"); | |
| }; | |
| const handleInputKeyDown = (e: React.KeyboardEvent) => { | |
| if (e.key === "Enter") navigate(input); | |
| }; | |
| return ( | |
| <div className="flex flex-col h-full bg-white/40 dark:bg-slate-950/20 backdrop-blur-md"> | |
| {/* Toolbar */} | |
| <div className="flex items-center gap-1.5 p-3 bg-white/30 dark:bg-slate-900/30 border-b border-white/10 dark:border-white/5 backdrop-blur-lg"> | |
| <button | |
| onClick={goBack} | |
| className="p-2 rounded-xl text-slate-650 hover:text-slate-900 dark:text-slate-300 dark:hover:text-white hover:bg-white/40 dark:hover:bg-slate-800/40 transition-all duration-200 active:scale-95" | |
| title="Go back" | |
| > | |
| <ArrowLeft className="w-4 h-4" /> | |
| </button> | |
| <button | |
| onClick={goForward} | |
| className="p-2 rounded-xl text-slate-650 hover:text-slate-900 dark:text-slate-300 dark:hover:text-white hover:bg-white/40 dark:hover:bg-slate-800/40 transition-all duration-200 active:scale-95" | |
| title="Go forward" | |
| > | |
| <ArrowRight className="w-4 h-4" /> | |
| </button> | |
| <button | |
| onClick={reload} | |
| className="p-2 rounded-xl text-slate-650 hover:text-slate-900 dark:text-slate-300 dark:hover:text-white hover:bg-white/40 dark:hover:bg-slate-800/40 transition-all duration-200 active:scale-95" | |
| title="Reload" | |
| > | |
| <RotateCw className={`w-4 h-4 ${loading ? "animate-spin text-teal-500" : ""}`} /> | |
| </button> | |
| <button | |
| onClick={goHome} | |
| className="p-2 rounded-xl text-slate-650 hover:text-slate-900 dark:text-slate-300 dark:hover:text-white hover:bg-white/40 dark:hover:bg-slate-800/40 transition-all duration-200 active:scale-95" | |
| title="Home" | |
| > | |
| <Home className="w-4 h-4" /> | |
| </button> | |
| <div className="flex-1 flex items-center gap-2 bg-slate-100/60 dark:bg-slate-900/60 border border-white/10 dark:border-white/5 rounded-xl px-3 py-1.5 shadow-inner"> | |
| <Search className="w-4 h-4 text-slate-400" /> | |
| <input | |
| type="text" | |
| value={input} | |
| onChange={(e) => setInput(e.target.value)} | |
| onKeyDown={handleInputKeyDown} | |
| className="flex-1 bg-transparent border-none outline-none text-xs text-slate-800 dark:text-slate-200 placeholder:text-slate-500 font-medium" | |
| placeholder="Enter URL or search term..." | |
| /> | |
| </div> | |
| <button | |
| onClick={copyUrl} | |
| className="p-2 rounded-xl text-slate-655 hover:text-slate-900 dark:text-slate-300 dark:hover:text-white hover:bg-white/40 dark:hover:bg-slate-800/40 transition-all duration-200 active:scale-95" | |
| title="Copy URL" | |
| > | |
| {copied ? <Check className="w-4 h-4 text-emerald-500" /> : <Copy className="w-4 h-4" />} | |
| </button> | |
| <button | |
| onClick={openInNewTab} | |
| className="p-2 rounded-xl text-slate-655 hover:text-slate-900 dark:text-slate-300 dark:hover:text-white hover:bg-white/40 dark:hover:bg-slate-800/40 transition-all duration-200 active:scale-95" | |
| title="Open in new tab" | |
| > | |
| <ExternalLink className="w-4 h-4" /> | |
| </button> | |
| </div> | |
| {/* Iframe with error handling */} | |
| <div className="flex-1 relative bg-white dark:bg-slate-950"> | |
| {loading && ( | |
| <div className="absolute inset-0 flex items-center justify-center bg-white/70 dark:bg-slate-950/70 backdrop-blur-sm z-10"> | |
| <div className="flex flex-col items-center gap-3 p-6 premium-glass rounded-2xl border border-white/10 dark:border-white/5"> | |
| <RotateCw className="w-6 h-6 animate-spin text-teal-500" /> | |
| <p className="text-xs font-bold text-slate-500 dark:text-slate-400">Loading resources...</p> | |
| </div> | |
| </div> | |
| )} | |
| <iframe | |
| ref={iframeRef} | |
| src={url} | |
| className="w-full h-full border-0" | |
| title="Web Browser" | |
| sandbox="allow-same-origin allow-scripts allow-popups allow-forms allow-downloads allow-modals" | |
| onLoad={handleLoad} | |
| onError={() => { | |
| setLoading(false); | |
| toast.error(`Failed to load: ${url}`); | |
| }} | |
| /> | |
| </div> | |
| </div> | |
| ); | |
| } | |
| export default WebBrowserPanel; | |
Xet Storage Details
- Size:
- 6.57 kB
- Xet hash:
- 13d046d3846e4077e184b0ab237be92a35048175b68838b631b69215d40fef0b
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.