import { useState, useCallback } from 'react'; import { Link } from 'react-router'; import { useQuery } from '@tanstack/react-query'; import { Globe, RefreshCw, Send, Terminal, Loader2 } from 'lucide-react'; import { toast } from 'sonner'; import { Button } from '@/components/ui/button'; import { useClient } from 'hooks/useClient'; import { useBrowserTargets, useSelectedTabId } from './shared'; export const TabPreview = () => { const client = useClient(); // Reads the URL itself rather than being told by the list. const selectedId = useSelectedTabId(); const { data: targets } = useBrowserTargets(!!selectedId); // A target id dies with the browser session, so a link or a reload can name a tab that is no longer // attached. Screenshotting it would 404 every ten seconds; better to say so once. const isKnownTab = !!selectedId && !!targets?.some((t) => t.id === selectedId); const [navUrl, setNavUrl] = useState(''); const [evalExpr, setEvalExpr] = useState(''); const [evalResult, setEvalResult] = useState(null); const [isNavigating, setIsNavigating] = useState(false); const [isEvaluating, setIsEvaluating] = useState(false); const { data: screenshot, isLoading, refetch, isFetching, } = useQuery({ queryKey: ['browser-screenshot', selectedId], queryFn: () => client.get<{ data: string; format: string }>(`/browser/targets/${selectedId}/screenshot`), enabled: isKnownTab, refetchInterval: 10000, }); const handleNavigate = useCallback(async () => { if (!selectedId || !navUrl.trim()) return; setIsNavigating(true); try { await client.post(`/browser/targets/${selectedId}/navigate`, { url: navUrl.trim() }); setTimeout(() => void refetch(), 1000); } catch { toast.error('Navigation failed'); } finally { setIsNavigating(false); } }, [client, selectedId, navUrl, refetch]); const handleEvaluate = useCallback(async () => { if (!selectedId || !evalExpr.trim()) return; setIsEvaluating(true); try { const res = await client.post<{ result: unknown }>(`/browser/targets/${selectedId}/evaluate`, { expression: evalExpr.trim(), }); setEvalResult(JSON.stringify(res.result, null, 2)); } catch (err) { setEvalResult(`Error: ${err instanceof Error ? err.message : String(err)}`); } finally { setIsEvaluating(false); } }, [client, selectedId, evalExpr]); if (!selectedId) { return (
Select a tab to preview
); } // Waiting on the list is not the same as knowing the tab is gone, so only say so once it has arrived. if (targets && !isKnownTab) { return (
That tab is no longer attached Back to the tab list
); } return (
{/* URL bar */}
{ ev.preventDefault(); void handleNavigate(); }} > setNavUrl(ev.target.value)} placeholder="Navigate to URL..." className="flex-1 rounded-md border bg-transparent px-2 py-1 text-sm outline-none focus:border-cyan-500" />
{/* Screenshot */}
{isLoading && !screenshot && (
)} {screenshot && ( Tab screenshot )}
{/* JS Console */}
{ ev.preventDefault(); void handleEvaluate(); }} > setEvalExpr(ev.target.value)} placeholder="Evaluate JavaScript..." className="flex-1 bg-transparent text-sm outline-none font-mono" /> {evalResult !== null && (
{evalResult}
)}
); };