import React, { useState, useCallback, useRef, useEffect } from 'react'; import { HiExclamationTriangle, HiEye } from 'react-icons/hi2'; import Typography from '../../../ui/Typography'; import { useBlockTesterStore } from '../../../../stores/blockTesterStore'; import { useConfigStore } from '../../../../stores/configStore'; import { cn } from '../../../../lib/utils'; import PreviewControls from './PreviewControls'; import * as Handlebars from 'handlebars'; import { Logger } from '@hashgraphonline/standards-sdk'; const logger = new Logger({ module: 'BlockPreview' }); interface BlockPreviewProps { className?: string; } /** * Secure block preview component with iframe sandboxing * Renders block templates with attribute substitution in a safe environment */ const BlockPreview: React.FC = ({ className }) => { const { currentBlock, template, attributes, actions, previewMode, addError } = useBlockTesterStore(); const { config } = useConfigStore(); const isDarkMode = config?.advanced?.theme === 'dark'; const [previewContent, setPreviewContent] = useState(''); const [isLoading, setIsLoading] = useState(false); const [_lastRefresh, setLastRefresh] = useState(Date.now()); const iframeRef = useRef(null); useEffect(() => { const handlePreviewMessage = (event: MessageEvent) => { if (event.data?.type === 'preview-log') { const { level, message, data } = event.data; switch (level) { case 'debug': logger.debug(`Preview: ${message}`, data); break; case 'info': logger.info(`Preview: ${message}`, data); break; case 'warn': logger.warn(`Preview: ${message}`, data); break; case 'error': logger.error(`Preview: ${message}`, data); break; default: logger.info(`Preview: ${message}`, data); } } }; window.addEventListener('message', handlePreviewMessage); return () => window.removeEventListener('message', handlePreviewMessage); }, []); const getViewportDimensions = (mode: string) => { switch (mode) { case 'mobile': return { width: 375, height: 667 }; case 'tablet': return { width: 768, height: 1024 }; case 'desktop': default: return { width: 1200, height: 800 }; } }; const processTemplate = useCallback((templateStr: string, attributeValues: Record) => { try { const template = Handlebars.compile(templateStr); const context = { content: 'Sample content', ...attributeValues }; const rendered = template(context); return rendered; } catch (error) { logger.error('Handlebars template processing error', { error: error.message }); throw new Error(`Template processing failed: ${error.message}`); } }, []); const generatePreviewHTML = useCallback(async () => { if (!currentBlock || !template) { setPreviewContent(''); return; } try { setIsLoading(true); const attributeValues: Record = {}; Object.entries(attributes).forEach(([key, data]) => { if (data && typeof data === 'object' && 'value' in data) { attributeValues[key] = data.value; } else { attributeValues[key] = data; } }); const processedContent = processTemplate(template, attributeValues); const fullHTML = ` Block Preview - ${currentBlock.title || currentBlock.name}
${processedContent}
`; setPreviewContent(fullHTML); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); logger.error('Preview generation error', { error: errorMessage }); addError({ type: 'preview', source: 'preview', message: `Preview generation failed: ${errorMessage}` }); setPreviewContent(` Preview Error

Preview Error

${errorMessage}

${template || 'No template'}
`); } finally { setIsLoading(false); } }, [currentBlock, template, attributes, actions, isDarkMode, processTemplate, addError]); const debouncedGenerate = useCallback(() => { const debounced = debounce(generatePreviewHTML, 300); debounced(); }, [generatePreviewHTML]); useEffect(() => { debouncedGenerate(); }, [template, attributes, currentBlock, previewMode, isDarkMode, debouncedGenerate]); const handleRefresh = () => { setLastRefresh(Date.now()); generatePreviewHTML(); }; const handleOpenInNewWindow = () => { if (previewContent) { const newWindow = window.open('', '_blank', 'width=1200,height=800'); if (newWindow) { newWindow.document.write(previewContent); newWindow.document.close(); } } }; const { width, height } = getViewportDimensions(previewMode); if (!currentBlock) { return (
No Block Preview Select or create a block to see the live preview
); } if (!template.trim()) { return (
Preview
Empty Template Add content to your template to see the preview
); } return (
{previewMode} • {width} × {height}px {isLoading && ( <> • Updating... )}
{isLoading && (
)}