bolt.diy/app/routes/webcontainer.preview.$id.tsx

93 lines
2.7 KiB
TypeScript
Raw Permalink Normal View History

import { json, type LoaderFunctionArgs } from '@remix-run/cloudflare';
import { useLoaderData } from '@remix-run/react';
import { useCallback, useEffect, useRef, useState } from 'react';
const PREVIEW_CHANNEL = 'preview-updates';
export async function loader({ params }: LoaderFunctionArgs) {
const previewId = params.id;
if (!previewId) {
throw new Response('Preview ID is required', { status: 400 });
}
return json({ previewId });
}
export default function WebContainerPreview() {
const { previewId } = useLoaderData<typeof loader>();
const iframeRef = useRef<HTMLIFrameElement>(null);
const broadcastChannelRef = useRef<BroadcastChannel>();
const [previewUrl, setPreviewUrl] = useState('');
// Handle preview refresh
const handleRefresh = useCallback(() => {
if (iframeRef.current && previewUrl) {
// Force a clean reload
iframeRef.current.src = '';
requestAnimationFrame(() => {
if (iframeRef.current) {
iframeRef.current.src = previewUrl;
}
});
}
}, [previewUrl]);
// Notify other tabs that this preview is ready
const notifyPreviewReady = useCallback(() => {
if (broadcastChannelRef.current && previewUrl) {
broadcastChannelRef.current.postMessage({
type: 'preview-ready',
previewId,
url: previewUrl,
timestamp: Date.now(),
});
}
}, [previewId, previewUrl]);
useEffect(() => {
// Initialize broadcast channel
broadcastChannelRef.current = new BroadcastChannel(PREVIEW_CHANNEL);
// Listen for preview updates
broadcastChannelRef.current.onmessage = (event) => {
if (event.data.previewId === previewId) {
if (event.data.type === 'refresh-preview' || event.data.type === 'file-change') {
handleRefresh();
}
}
};
// Construct the WebContainer preview URL
const url = `https://${previewId}.local-credentialless.webcontainer-api.io`;
setPreviewUrl(url);
// Set the iframe src
if (iframeRef.current) {
iframeRef.current.src = url;
}
// Notify other tabs that this preview is ready
notifyPreviewReady();
// Cleanup
return () => {
broadcastChannelRef.current?.close();
};
}, [previewId, handleRefresh, notifyPreviewReady]);
return (
<div className="w-full h-full">
<iframe
ref={iframeRef}
title="WebContainer Preview"
className="w-full h-full border-none"
sandbox="allow-scripts allow-forms allow-popups allow-modals allow-storage-access-by-user-activation allow-same-origin"
allow="cross-origin-isolated"
loading="eager"
onLoad={notifyPreviewReady}
/>
</div>
);
}