Skip to content

Instantly share code, notes, and snippets.

@image72
Last active December 27, 2021 22:03
Show Gist options
  • Save image72/710cd0e18edca3d35474166a5f2cb80f to your computer and use it in GitHub Desktop.
Save image72/710cd0e18edca3d35474166a5f2cb80f to your computer and use it in GitHub Desktop.
simple react sandbox playground
import {EditorState, EditorView, basicSetup} from "@codemirror/basic-setup"
import {javascript} from "@codemirror/lang-javascript"
const jscode=`
// Note:
// 1. Use React.something instead of importing something
// 2. Container id is 'app'
function Counter({initialCount}) {
const [count, setCount] = React.useState(initialCount);
return (
<>
Count: {count}
<button onClick={() => setCount(initialCount)}>Reset</button>
<button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
<button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
</>
);
}
ReactDOM.render(
<Counter initialCount={1} />,
document.getElementById('app')
);
`;
const myTheme = EditorView.baseTheme({
"&.cm-editor": {
fontSize: '16px',
},
".cm-scroller": {
fontFamily:'Consolas, Menlo, Monaco, source-code-pro, Courier New, monospace'
},
})
let timer;
const evaluateCode = (code) => {
console.clear();
try{
const sandbox = document.getElementById('sandbox');
sandbox.contentWindow.postMessage({ code }, '*');
}
catch(err) {
console.error(err);
}
}
let index = 0;
const editor = new EditorView({
state: EditorState.create({
extensions: [
basicSetup,
javascript(),
myTheme,
EditorView.updateListener.of((v)=> {
if(v.docChanged) {
if(timer) clearTimeout(timer);
timer = setTimeout(() => {
evaluateCode(editor.state.doc.toString())
}, 500 );
}
})
],
doc: jscode
}),
parent: document.getElementById('editor')
})
// first time evaluation
window.addEventListener('load', (event) => {
setTimeout( () => {
evaluateCode(editor.state.doc.toString())
}, 1000);
})
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>React Playground</title>
<link rel=stylesheet href="styles.css">
<link rel=stylesheet href="theme.css">
</head>
<body>
<header>
React Playground
</header>
<main>
<div id="editor">
</div>
<iframe id="sandbox" src="./sandbox.html" sandbox='allow-scripts allow-same-origin allow-forms'></iframe>
</main>
<script src="editor.bundle.js"></script>
</body>
</html>
{
"name": "react-playground",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "rollup -c"
},
"author": "",
"license": "ISC",
"dependencies": {
"@codemirror/basic-setup": "^0.18.2",
"@codemirror/lang-html": "^0.18.1",
"@codemirror/lang-javascript": "^0.18.0",
"@codemirror/theme-one-dark": "^0.18.1",
"@rollup/plugin-node-resolve": "^13.0.0",
"rollup": "^2.50.6"
}
}
import {nodeResolve} from "@rollup/plugin-node-resolve"
export default {
input: "./editor.js",
output: {
file: "./editor.bundle.js",
format: "iife"
},
plugins: [nodeResolve()]
}
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/react@17/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel" data-presets="es2015,react"></script>
<script>
let origin;
window.addEventListener('message', e => {
origin = e.source;
document.getElementById('error').style.display='none';
document.getElementById('app').style.display='block';
const { code } = e.data;
if (code) {
// here the code is evaluated in the iframe
try {
let result = Babel.transform(code, { presets: ["env", "react"] }).code;
Function(result)(window);
}
catch(err){
document.getElementById('app').style.display='none';
document.getElementById('error').style.display='block';
document.getElementById('error').innerHTML = err.message;
}
}
});
</script>
<style>
body {
font-family: 'Gill Sans','Gill Sans MT',Calibri,'Trebuchet MS',sans-serif;
color: #222;
padding: 1rem;
}
#error{
background: #fef5f6;
color: #de0000;
padding: 1rem;
border: 1px solid #de0000;
border-radius: 5px;
display: none;
white-space: pre-wrap;
}
button {
margin: .1rem .3rem;
padding: .2rem .5rem;
}
</style>
</head>
<body>
<pre id="error">
</pre>
<div id="app"></div>
</body>
</html>
html,
body {
margin: 0;
padding: 0;
background: #fff;
color: #444;
height: 100%;
overflow: hidden;
font-family: 'Gill Sans','Gill Sans MT',Calibri,'Trebuchet MS',sans-serif
}
body{
display: flex;
flex-direction: column;
}
header{
height: 60px;
align-items: center;
display: flex;
font-size: 1.5rem;
padding: 0 1.5rem;
border-bottom: 1px solid #aaa;
display: flex;
}
header > h1{
font-size: 1.5rem;
flex-grow: 1;
text-align: center;
}
main{
display: flex;
height: 100%;
flex-grow: 1;
overflow: hidden;
}
#editor{
overflow: auto;
min-width: 60%;
width: 60%;
box-shadow: 0px 0px 40px rgb(0 0 0 / 30%);
min-width: 826px;
}
#sandbox{
border: 0;
height: 100%;
width: 40%;
}
@media only screen and (max-width: 480px) {
main{
flex-direction: column;
height: auto;
}
#editor,
#sandbox{
min-width: 100%;
width: 100%;
height: auto;
}
}
/* UBUNTU THEME */
.CodeMirror, .CodeMirror-gutter {
background-color: #300a24;
}
.CodeMirror {
color: white;
}
.CodeMirror ::selection {
background-color: #b6b6b6;
}
.CodeMirror-gutter {
border-right: 1px solid #533d51;
}
.CodeMirror-gutter-element {
color: #fce94f;
}
.CodeMirror-content {
caret-color: white;
}
.cm-keyword,
.cm-comment,
.cm-bracket,
.cm-attribute,
.CodeMirror-matchingbracket {
color: #34e2e2; /* neon blue */
}
.CodeMirror-matchingbracket {
font-weight: bold;
}
.cm-keyword {
font-weight: bold;
}
.cm-atom,
.cm-string,
.cm-string-2,
.cm-qualifier {
color: #ad7fa8; /* purple */
}
.cm-property {
color: #87ffaf; /* pale green */
}
/*
.cm-number,
.cm-def,
.cm-variable,
.cm-punctuation,
.cm-operator,
.cm-variable-2,
.cm-variable-3,
.cm-type,
.cm-meta,
.cm-builtin,
.cm-tag,
.cm-hr,
.cm-link {
}
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment