-
Notifications
You must be signed in to change notification settings - Fork 2
/
JsDemo.js
125 lines (115 loc) · 4.3 KB
/
JsDemo.js
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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
import React, {useState, useEffect} from 'react';
import clsx from 'clsx';
import * as styles from './demo.module.css';
/*
Renders child code blocks as children and as an embedded iframe.
*/
const codeBlocks = nodes =>
nodes
.filter(node => node.props.originalType === 'pre')
.map(pre => pre.props.children)
.filter(codeEl => codeEl.props.mdxType === 'code');
const makeSrcString = blocks =>
blocks
.map(block => {
if (block.props.className === 'language-js') {
return `<script>\n${block.props.children}\n</script>`;
} else if (block.props.className === 'language-css') {
return `<style>\n${block.props.children}\n</style>`;
} else if (block.props.className === 'language-html') {
return block.props.children;
} else {
throw new Error('JsDemo code blocks must be html, css, or js');
}
})
.join('\n\n');
// global count of jsDemos as an id
// so that we can match up the log messages from embedded frames with the correct parents
let demoId = 1;
const JsDemo = ({children, defer = false}) => {
let [run, setRun] = useState(!defer);
const blocks = codeBlocks(React.Children.toArray(children));
const srcString = makeSrcString(blocks);
const hasJs = blocks.some(block => block.props.className === 'language-js');
const hasHtml = blocks.some(block => block.props.className === 'language-html');
// capture console output and show the log messages
const [captured, setCaptured] = useState(['']);
const [id, setId] = useState(null);
useEffect(() => {
let componentId = demoId++;
setId(componentId);
const logChildFrameMessage = event => {
if (event.data.id && event.data.id === componentId) {
if (event.data.message === 'clearConsole') {
setCaptured([]);
} else if (event.data.message === 'frameConsoleLog') {
setCaptured(messages => [...messages, ...event.data.value]);
} else if (event.data.message === 'frameError') {
setCaptured(messages => [...messages, event.data.error]);
}
}
};
window.addEventListener('message', logChildFrameMessage);
return () => window.removeEventListener('message', logChildFrameMessage);
}, []);
/*
Inject two handlers to the embedded iframe
1 - post a message to the parent window on error
2 - post a message to the parent window on a console log
Then, post a message to the parent window clearing the console
That way, if the iframe runs twice, we don't get double logs
*/
const injectCaptureConsole = `<script>
window.parent.postMessage({message: 'clearConsole', id: ${id}}, '*');
const originalLog = console.log;
console.log = function (...args) {
window.parent.postMessage({message: 'frameConsoleLog', id: ${id}, value: args}, '*');
originalLog(...args);
};
window.onerror = function(message, source, lineno, colno, error) {
window.parent.postMessage({message: 'frameError', id: ${id}, error: error}, '*');
return true;
}
</script>`;
return (
<div className={styles.wrapper}>
<div className={styles.tab}>
<div className={styles.label}>Code</div>
<div className={styles.code}>{children}</div>
</div>
<div className={`${styles.tab} ${styles.result}`}>
<div className={styles.label}>Result</div>
{defer && (
<button
className={'MuiButtonBase-root MuiButton-root MuiButton-containedPrimary'}
onClick={() => setRun(true)}
>
Run
</button>
)}
<div className={styles.content}>
<iframe
className={clsx(styles.frame, !hasHtml && styles.hide)}
srcDoc={
run ? [injectCaptureConsole, srcString].join('\n') : 'Click run to see the result'
}
></iframe>
{hasJs && (
<div className={clsx(styles.frame, styles.console)}>
{captured.map((message, index) =>
message instanceof Error ? (
<div key={index} className={styles.error}>
» {message.toString()}
</div>
) : (
<div key={index}>» {message}</div>
)
)}
</div>
)}
</div>
</div>
</div>
);
};
export default JsDemo;