File size: 1,815 Bytes
f56a29b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
import { useMemo, useRef, useEffect, useCallback } from 'react';
import type { InteractiveContent } from '@/lib/types/stage';
import { useWidgetIframeStore } from '@/lib/store/widget-iframe';
import { patchHtmlForIframe } from '@/lib/utils/iframe';
interface InteractiveRendererProps {
readonly content: InteractiveContent;
readonly sceneId: string;
}
export function InteractiveRenderer({ content, sceneId }: InteractiveRendererProps) {
const iframeRef = useRef<HTMLIFrameElement>(null);
const registerIframe = useWidgetIframeStore((state) => state.registerIframe);
const setActiveScene = useWidgetIframeStore((state) => state.setActiveScene);
const patchedHtml = useMemo(
() => (content.html ? patchHtmlForIframe(content.html) : undefined),
[content.html],
);
// Create iframe messaging callback
const sendMessageToIframe = useCallback((type: string, payload: Record<string, unknown>) => {
if (iframeRef.current?.contentWindow) {
iframeRef.current.contentWindow.postMessage({ type, ...payload }, '*');
}
}, []);
// Register iframe messaging callback on mount, unregister on unmount
// Key by sceneId to prevent race conditions on scene switch
useEffect(() => {
registerIframe(sceneId, sendMessageToIframe);
setActiveScene(sceneId);
return () => {
registerIframe(sceneId, null);
};
}, [sceneId, registerIframe, sendMessageToIframe, setActiveScene]);
return (
<div className="w-full h-full relative">
<iframe
ref={iframeRef}
srcDoc={patchedHtml}
src={patchedHtml ? undefined : content.url}
className="absolute inset-0 w-full h-full border-0"
title={`Interactive Scene ${sceneId}`}
sandbox="allow-scripts allow-same-origin allow-forms allow-popups"
/>
</div>
);
}
|