mirror of
https://github.com/stackblitz-labs/bolt.diy
synced 2025-01-22 19:06:12 +00:00
93 lines
2.7 KiB
TypeScript
93 lines
2.7 KiB
TypeScript
|
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>
|
||
|
);
|
||
|
}
|