import classNames from "classnames"; import { useRef, useEffect, useState, forwardRef } from "react"; import { TbReload, TbLoader, TbExternalLink } from "react-icons/tb"; import { WebContainer } from '@webcontainer/api'; // PreviewEye icon component const PreviewEye = ({ className = "w-4 h-4" }: { className?: string }) => ( ); // Tooltip component const Tooltip = ({ children, content, position = "top" }: { children: React.ReactNode; content: string; position?: "top" | "bottom" | "left" | "right" }) => { const [isVisible, setIsVisible] = useState(false); const positionClasses = { top: "bottom-full left-1/2 transform -translate-x-1/2 mb-2", bottom: "top-full left-1/2 transform -translate-x-1/2 mt-2", left: "right-full top-1/2 transform -translate-y-1/2 mr-2", right: "left-full top-1/2 transform -translate-y-1/2 ml-2" }; return (
setIsVisible(true)} onMouseLeave={() => setIsVisible(false)} > {children} {isVisible && (
{content}
)}
); }; type PreviewProps = { files: Record; isResizing: boolean; isAiWorking: boolean; packageJson?: any; className?: string; }; const Preview = forwardRef( ({ files, isResizing, isAiWorking, packageJson, className }, ref) => { const iframeRef = useRef(null); const [webcontainer, setWebcontainer] = useState(null); const [url, setUrl] = useState(''); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(''); const [isFullscreen, setIsFullscreen] = useState(false); // Initialize WebContainer useEffect(() => { const initWebContainer = async () => { try { const container = await WebContainer.boot(); setWebcontainer(container); } catch (err) { setError('Failed to initialize WebContainer'); console.error('WebContainer init error:', err); } }; initWebContainer(); }, []); // Mount files and start dev server useEffect(() => { if (!webcontainer || !files) return; const setupProject = async () => { setIsLoading(true); setError(''); try { await webcontainer.mount(files); const defaultPackageJson = { name: 'preview-app', type: 'module', scripts: { dev: 'vite', build: 'vite build', preview: 'vite preview' }, devDependencies: { vite: '^5.0.0' }, ...packageJson }; if (!files['package.json']) { await webcontainer.fs.writeFile( 'package.json', JSON.stringify(defaultPackageJson, null, 2) ); } const installProcess = await webcontainer.spawn('npm', ['install']); const installExitCode = await installProcess.exit; if (installExitCode !== 0) { throw new Error('Failed to install dependencies'); } const serverProcess = await webcontainer.spawn('npm', ['run', 'dev']); webcontainer.on('server-ready', (port, url) => { setUrl(url); setIsLoading(false); }); serverProcess.output.pipeTo( new WritableStream({ write(data) { console.log('[Server]', data); }, }) ); } catch (err) { setError(err instanceof Error ? err.message : 'Setup failed'); setIsLoading(false); console.error('Setup error:', err); } }; setupProject(); }, [webcontainer, files, packageJson]); const handleRefresh = async () => { if (!webcontainer || !url) return; setIsLoading(true); try { await webcontainer.spawn('npm', ['run', 'dev']); if (iframeRef.current) { const iframe = iframeRef.current; iframe.src = ''; setTimeout(() => { iframe.src = url; }, 100); } } catch (err) { console.error('Refresh error:', err); } finally { setIsLoading(false); } }; const handleRestartContainer = async () => { if (!webcontainer) return; setIsLoading(true); setUrl(''); try { await webcontainer.spawn('pkill', ['-f', 'node']); await webcontainer.mount(files); const serverProcess = await webcontainer.spawn('npm', ['run', 'dev']); webcontainer.on('server-ready', (port, url) => { setUrl(url); setIsLoading(false); }); } catch (err) { setError('Failed to restart container'); setIsLoading(false); console.error('Restart error:', err); } }; const openInNewTab = () => { if (url) { window.open(url, '_blank'); } }; return (
{/* Header bar - Vercel style */}
Preview {url && ( {new URL(url).host} )}
{/* Content area */}
{error ? (

Container Error

{error}

) : !url || isLoading ? (
{!webcontainer ? 'Booting WebContainer...' : 'Setting up project...'}
This may take a moment
) : (