import { useStore } from '@nanostores/react'; import { memo, useEffect, useRef, useState } from 'react'; import { IconButton } from '~/components/ui/IconButton'; import { workbenchStore } from '~/lib/stores/workbench'; import { simulationReloaded } from '~/lib/replay/SimulationPrompt'; import { PointSelector } from './PointSelector'; type ResizeSide = 'left' | 'right' | null; let gCurrentIFrame: HTMLIFrameElement | undefined; export function getCurrentIFrame() { return gCurrentIFrame; } export const Preview = memo(() => { const iframeRef = useRef(null); const containerRef = useRef(null); const inputRef = useRef(null); const [isPortDropdownOpen, setIsPortDropdownOpen] = useState(false); const [isFullscreen, setIsFullscreen] = useState(false); const [url, setUrl] = useState(''); const [iframeUrl, setIframeUrl] = useState(); const [isSelectionMode, setIsSelectionMode] = useState(false); const [selectionPoint, setSelectionPoint] = useState<{ x: number; y: number } | null>(null); const previewURL = useStore(workbenchStore.previewURL); const previewError = useStore(workbenchStore.previewError); // Toggle between responsive mode and device mode const [isDeviceModeOn, setIsDeviceModeOn] = useState(false); // Use percentage for width const [widthPercent, setWidthPercent] = useState(37.5); // 375px assuming 1000px window width initially const resizingState = useRef({ isResizing: false, side: null as ResizeSide, startX: 0, startWidthPercent: 37.5, windowWidth: window.innerWidth, }); // Define the scaling factor const SCALING_FACTOR = 2; // Adjust this value to increase/decrease sensitivity gCurrentIFrame = iframeRef.current ?? undefined; useEffect(() => { if (!previewURL) { setUrl(''); setIframeUrl(undefined); setSelectionPoint(null); return; } setUrl(previewURL); setIframeUrl(previewURL); }, [previewURL]); const reloadPreview = () => { if (iframeRef.current) { simulationReloaded(); iframeRef.current.src = iframeRef.current.src; } setIsSelectionMode(false); setSelectionPoint(null); }; const toggleFullscreen = async () => { if (!isFullscreen && containerRef.current) { await containerRef.current.requestFullscreen(); } else if (document.fullscreenElement) { await document.exitFullscreen(); } }; useEffect(() => { const handleFullscreenChange = () => { setIsFullscreen(!!document.fullscreenElement); }; document.addEventListener('fullscreenchange', handleFullscreenChange); return () => { document.removeEventListener('fullscreenchange', handleFullscreenChange); }; }, []); const toggleDeviceMode = () => { setIsDeviceModeOn((prev) => !prev); }; const startResizing = (e: React.MouseEvent, side: ResizeSide) => { if (!isDeviceModeOn) { return; } // Prevent text selection document.body.style.userSelect = 'none'; resizingState.current.isResizing = true; resizingState.current.side = side; resizingState.current.startX = e.clientX; resizingState.current.startWidthPercent = widthPercent; resizingState.current.windowWidth = window.innerWidth; document.addEventListener('mousemove', onMouseMove); document.addEventListener('mouseup', onMouseUp); e.preventDefault(); // Prevent any text selection on mousedown }; const onMouseMove = (e: MouseEvent) => { if (!resizingState.current.isResizing) { return; } const dx = e.clientX - resizingState.current.startX; const windowWidth = resizingState.current.windowWidth; // Apply scaling factor to increase sensitivity const dxPercent = (dx / windowWidth) * 100 * SCALING_FACTOR; let newWidthPercent = resizingState.current.startWidthPercent; if (resizingState.current.side === 'right') { newWidthPercent = resizingState.current.startWidthPercent + dxPercent; } else if (resizingState.current.side === 'left') { newWidthPercent = resizingState.current.startWidthPercent - dxPercent; } // Clamp the width between 10% and 90% newWidthPercent = Math.max(10, Math.min(newWidthPercent, 90)); setWidthPercent(newWidthPercent); }; const onMouseUp = () => { resizingState.current.isResizing = false; resizingState.current.side = null; document.removeEventListener('mousemove', onMouseMove); document.removeEventListener('mouseup', onMouseUp); // Restore text selection document.body.style.userSelect = ''; }; // Handle window resize to ensure widthPercent remains valid useEffect(() => { const handleWindowResize = () => { /* * Optional: Adjust widthPercent if necessary * For now, since widthPercent is relative, no action is needed */ }; window.addEventListener('resize', handleWindowResize); return () => { window.removeEventListener('resize', handleWindowResize); }; }, []); // A small helper component for the handle's "grip" icon const GripIcon = () => (
••• •••
); return (
{isPortDropdownOpen && (
setIsPortDropdownOpen(false)} /> )}
{ setSelectionPoint(null); setIsSelectionMode(!isSelectionMode); }} className={isSelectionMode ? 'bg-bolt-elements-background-depth-3' : ''} />
{ setUrl(event.target.value); }} onKeyDown={(event) => { let newUrl; if (event.key === 'Enter') { setIframeUrl(newUrl); if (inputRef.current) { inputRef.current.blur(); } } }} />
{/* Device mode toggle button */} {/* Fullscreen toggle button */}
{previewURL ? ( <>