-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathPreviewPane.tsx
More file actions
93 lines (86 loc) · 2.94 KB
/
Copy pathPreviewPane.tsx
File metadata and controls
93 lines (86 loc) · 2.94 KB
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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
import { useEffect, useRef, useCallback } from 'react'
import type { DOMElement } from '../../lib/specChecks'
import type { StyleMode } from './StyleModeToggle'
interface Props {
htmlValue: string
cssValue: string
styleMode: StyleMode
onDOMSummary: (elements: DOMElement[]) => void
runCount: number // increments each time the user clicks Run
}
/**
* Renders user HTML inside a sandboxed iframe.
*
* After the iframe loads, an injected script reads the DOM,
* flattens it into a summary, and posts it back to the parent
* via postMessage. The parent (this component) listens for
* that message and passes it up to the scoring logic.
*/
export function PreviewPane({ htmlValue, cssValue, styleMode, onDOMSummary, runCount }: Props) {
const iframeRef = useRef<HTMLIFrameElement>(null)
// Listen for postMessage from the iframe
const handleMessage = useCallback((event: MessageEvent) => {
if (event.data?.type === 'dom-summary') {
onDOMSummary(event.data.payload as DOMElement[])
}
}, [onDOMSummary])
useEffect(() => {
window.addEventListener('message', handleMessage)
return () => window.removeEventListener('message', handleMessage)
}, [handleMessage])
// Build the srcdoc content
const inspectorScript = `
<script>
window.addEventListener('load', function() {
var elements = document.querySelectorAll('body *:not(script)');
var summary = [];
for (var i = 0; i < elements.length; i++) {
var el = elements[i];
var attrs = {};
for (var j = 0; j < el.attributes.length; j++) {
attrs[el.attributes[j].name] = el.attributes[j].value;
}
summary.push({
tag: el.tagName.toLowerCase(),
attrs: attrs,
text: (el.textContent || '').trim().slice(0, 200)
});
}
window.parent.postMessage({ type: 'dom-summary', payload: summary }, '*');
});
</script>`
const srcdoc = `<!DOCTYPE html>
<html>
<head>
${styleMode === 'tailwind'
? '<script src="https://cdn.tailwindcss.com"><\/script>'
: `<style>${cssValue}</style>`
}
</head>
<body style="padding: 1.5rem; background: white; font-family: system-ui, sans-serif;">
${htmlValue}
${inspectorScript}
</body>
</html>`
return (
<div className="flex flex-col flex-1 min-h-0">
<span className="text-zinc-600 dark:text-zinc-500 text-xs mb-1 font-mono">Preview</span>
<div className="flex-1 min-h-0 rounded-lg border border-zinc-300 dark:border-zinc-700 overflow-hidden bg-white">
{runCount > 0 ? (
<iframe
ref={iframeRef}
key={runCount}
sandbox="allow-scripts"
srcDoc={srcdoc}
className="w-full h-full border-0"
title="Component preview"
/>
) : (
<div className="flex items-center justify-center h-full text-zinc-500 dark:text-zinc-400 text-sm bg-zinc-100 dark:bg-zinc-800">
Click Run to see your component
</div>
)}
</div>
</div>
)
}