feat: added the "Open Preview in a New Tab" (#1101)
* added the "Open Preview in a New Tab"
* enhancement
[Open Preview] [▼] // Two buttons side by side
|
+-- [Mobile (375x667)] // Dropdown menu
|-- [Tablet (768x1024)]
|-- [Laptop (1366x768)]
+-- [Desktop (1920x1080)]
* Update Preview.tsx
* Update Preview.tsx
This commit is contained in:
92
app/routes/webcontainer.preview.$id.tsx
Normal file
92
app/routes/webcontainer.preview.$id.tsx
Normal file
@@ -0,0 +1,92 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user