Spaces:
Running
Running
| import { useEffect, useRef } from 'react'; | |
| import ReplayButton from './ReplayButton'; | |
| import GifDownloadButton from './GifDownloadButton'; | |
| import { loadingSketch } from '../sketches'; | |
| export default function SketchRenderer({ | |
| generatedCode, | |
| isGenerating, | |
| error, | |
| showCodePanel, | |
| onReplay, | |
| onGifCapture, | |
| isCapturingGif, | |
| isInitialSketch | |
| }) { | |
| const sketchContainer = useRef(null); | |
| const currentSketch = useRef(null); | |
| useEffect(() => { | |
| // Add message listener for GIF capture completion | |
| const handleGifComplete = (event) => { | |
| if (event.data.type === 'gifCaptureComplete') { | |
| onGifCapture(false); // Signal completion | |
| } | |
| }; | |
| window.addEventListener('message', handleGifComplete); | |
| return () => { | |
| window.removeEventListener('message', handleGifComplete); | |
| }; | |
| }, [onGifCapture]); | |
| useEffect(() => { | |
| if (generatedCode && window.p5 && sketchContainer.current) { | |
| // Cleanup previous sketch if it exists | |
| const cleanup = () => { | |
| if (currentSketch.current) { | |
| currentSketch.current.remove(); | |
| currentSketch.current = null; | |
| } | |
| }; | |
| cleanup(); | |
| try { | |
| const formattedCodeResponse = ` | |
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.9.0/p5.js"></script> | |
| <title>p5.js Sketch</title> | |
| <style> | |
| body { | |
| padding: 0; | |
| margin: 0; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| min-height: 100vh; | |
| overflow: hidden; | |
| background: black; | |
| } | |
| canvas { | |
| max-width: 100% !important; | |
| height: auto !important; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <script> | |
| try { | |
| ${isGenerating ? loadingSketch : generatedCode} | |
| // Add setup wrapper | |
| if (typeof window.setup === 'function') { | |
| const originalSetup = window.setup; | |
| window.setup = function() { | |
| originalSetup(); | |
| frameRate(30); // Set consistent frame rate for GIF | |
| }; | |
| } | |
| // Add draw wrapper | |
| if (typeof window.draw === 'function') { | |
| const originalDraw = window.draw; | |
| window.draw = function() { | |
| originalDraw(); | |
| }; | |
| } | |
| // Handle GIF capture message | |
| window.addEventListener('message', function(event) { | |
| if (event.data.type === 'startGifCapture') { | |
| saveGif('word-to-code-animation', 5, { silent: true }) | |
| .then(() => { | |
| window.parent.postMessage({ type: 'gifCaptureComplete' }, '*'); | |
| }); | |
| } | |
| }); | |
| new p5(); | |
| } catch (error) { | |
| console.error('Sketch error:', error); | |
| document.body.innerHTML = '<div style="color: red; padding: 20px;"><h3>🔴 Error:</h3><pre>' + error.message + '</pre></div>'; | |
| } | |
| </script> | |
| </body> | |
| </html> | |
| `; | |
| const iframe = document.createElement('iframe'); | |
| iframe.srcdoc = formattedCodeResponse; | |
| iframe.style.width = '100%'; | |
| iframe.style.height = '100%'; | |
| iframe.style.border = 'none'; | |
| iframe.id = 'sketch-iframe'; | |
| iframe.onload = () => { | |
| iframe.contentWindow.addEventListener('error', (e) => { | |
| if (e.message.includes('Receiving end does not exist') || | |
| e.message.includes('p5.js seems to have been imported multiple times')) { | |
| e.preventDefault(); | |
| } | |
| }); | |
| }; | |
| sketchContainer.current.innerHTML = ''; | |
| sketchContainer.current.appendChild(iframe); | |
| } catch (error) { | |
| console.error('Error running p5.js sketch:', error); | |
| setError('Error running the animation: ' + error.message); | |
| } | |
| } | |
| return () => { | |
| if (sketchContainer.current) { | |
| sketchContainer.current.innerHTML = ''; | |
| } | |
| }; | |
| }, [generatedCode, isGenerating]); | |
| return ( | |
| <div className="w-full aspect-square bg-black rounded-3xl flex items-center justify-center overflow-hidden relative"> | |
| <div ref={sketchContainer} className="w-full h-full" /> | |
| {isInitialSketch && !error && !isGenerating && ( | |
| <div className="absolute inset-0 flex items-end justify-center pb-5 text-gray-300 pointer-events-none z-10"> | |
| Type a word below to get started | |
| </div> | |
| )} | |
| {error && ( | |
| <div className="w-full h-full flex items-center justify-center text-red-500 p-4 text-center"> | |
| {error} | |
| </div> | |
| )} | |
| {showCodePanel && !error && ( | |
| <> | |
| <ReplayButton onClick={onReplay} title="Replay animation" /> | |
| <GifDownloadButton | |
| onClick={() => onGifCapture(true)} | |
| title="Download as GIF" | |
| isCapturing={isCapturingGif} | |
| /> | |
| </> | |
| )} | |
| </div> | |
| ); | |
| } |