├── .babelrc ├── .editorconfig ├── .eslintrc ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── README.md ├── example ├── README.md ├── package-lock.json ├── package.json ├── public │ ├── index.html │ └── manifest.json └── src │ ├── App.js │ ├── Demo.js │ ├── components │ ├── Docs │ │ ├── Doc │ │ │ ├── ComponentDocumentation.js │ │ │ ├── Doc.js │ │ │ ├── HocDocumentation.js │ │ │ └── index.js │ │ ├── Docs.js │ │ └── index.js │ └── Toolbar │ │ ├── Toolbar.js │ │ └── index.js │ ├── documentation │ ├── index.js │ ├── v1.0.X │ │ ├── Sandbox.js │ │ ├── SandboxInterpreter.js │ │ ├── ScriptEditor.js │ │ ├── StatelessSandbox.js │ │ ├── StylesheetEditor.js │ │ ├── TemplateEditor.js │ │ ├── index.js │ │ └── withDependencies.js │ └── v2.0.X │ │ ├── Sandbox.js │ │ ├── SandboxInterpreter.js │ │ ├── ScriptEditor.js │ │ ├── StatelessSandbox.js │ │ ├── StylesheetEditor.js │ │ ├── TemplateEditor.js │ │ ├── index.js │ │ └── withDependencies.js │ ├── index.css │ ├── index.js │ └── utils.js ├── package-lock.json ├── package.json ├── rollup.config.js └── src ├── .eslintrc ├── index.js ├── lib ├── Sandbox.js ├── SandboxInterpreter.js ├── StatelessSandbox.js ├── StatelessSandboxThemes.js ├── editors │ ├── index.js │ ├── script-editor │ │ └── ScriptEditor.js │ ├── stylesheet-editor │ │ └── StylesheetEditor.js │ └── template-editor │ │ └── TemplateEditor.js ├── processors.js └── utils.js ├── media ├── PlayButtonIcon.js ├── SplitViewIcon.js └── TabsViewIcon.js ├── styles.css └── test.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "modules": false 5 | }], 6 | "stage-0", 7 | "react" 8 | ], 9 | "plugins": [ 10 | "external-helpers" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": [ 4 | "standard", 5 | "standard-react" 6 | ], 7 | "env": { 8 | "es6": true 9 | }, 10 | "plugins": [ 11 | "react" 12 | ], 13 | "parserOptions": { 14 | "sourceType": "module", 15 | "allowImportExportEverywhere": true 16 | }, 17 | "rules": { 18 | // don't force es6 functions to include space before paren 19 | "space-before-function-paren": 0, 20 | 21 | // allow specifying true explicitly for boolean props 22 | "react/jsx-boolean-value": 0, 23 | 24 | // allow imports mixed with non-import statements at top of file 25 | "import/first": 0 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | **Describe the bug** 8 | A clear and concise description of what the bug is. 9 | 10 | **Expected behavior** 11 | A clear and concise description of what you expected to happen. 12 | 13 | **CodeSandbox** 14 | https://codesandbox.io/s/9lp34q03vr 15 | Please fork and include link to demo of bug. 16 | 17 | **Environment:** 18 | - Browser [e.g. chrome, safari, device] 19 | - Peer Dependency Versions (React, ReactDOM, PropTypes) 20 | 21 | **Additional context** 22 | Add any other context about the problem here. 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Describe alternatives you've considered** 14 | A clear and concise description of any alternative solutions or features you've considered. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # See https://help.github.com/ignore-files/ for more about ignoring files. 3 | 4 | # dependencies 5 | node_modules 6 | 7 | # builds 8 | build 9 | dist 10 | 11 | # misc 12 | .DS_Store 13 | .env 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-sandbox-editor 2 | 3 | > React components including a sandbox interpreter and editor 4 | 5 | :warning: **I do NOT recommend using this library in production apps as the bundle size is very large due to in-browser transpilation via babel** :warning: 6 | 7 | :fire: **COMING SOON: A few years have passed and i'm remaking this library in a MUCH better way https://github.com/malerba118/react-esm-sandbox** :fire: 8 | 9 | [![NPM](https://img.shields.io/npm/v/react-sandbox-editor.svg)](https://www.npmjs.com/package/react-sandbox-editor) [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com) 10 | 11 | ## Overview 12 | 13 | There are many hosted web playground solutions these days (CodeSandbox, JSFiddle, CodePen). 14 | CodeSandbox in particular has blown me away, props to Ives van Hoorne for putting it together. 15 | However, I've yet to find a good unhosted playground solution. The intent of this library is 16 | to provide a highly-customizable unhosted web playground solution so that in your React apps you 17 | can include web snippets as flexible React components and not as rigid iframes with embedded content. 18 | 19 | ## Demo/Reference 20 | 21 | https://malerba118.github.io/react-sandbox-editor/#/demo 22 |
23 | https://malerba118.github.io/react-sandbox-editor/#/docs/latest 24 | 25 | ### Other Examples 26 | Material UI Sandbox 27 |
28 | Read Only Sandbox With No Header 29 |
30 | Sandbox Inside Sandbox 31 |
32 | Vue Sandbox 33 |
34 | TypeScript Sandbox 35 |
36 | PreScript/PostScript Sandbox 37 |
38 | 39 | ## Install 40 | 41 | ```bash 42 | npm install --save react-sandbox-editor 43 | ``` 44 | 45 | ## Simple Usage 46 | 47 | ```jsx 48 | import React from 'react'; 49 | import ReactDOM from 'react-dom'; 50 | import { Sandbox, withDependencies } from "react-sandbox-editor"; 51 | 52 | const ReactSandbox = withDependencies([ 53 | "https://unpkg.com/react@16.6.0/umd/react.development.js", 54 | "https://unpkg.com/react-dom@16.6.0/umd/react-dom.development.js" 55 | ])(Sandbox); 56 | 57 | let App = () => ( 58 | Hello, world!,\n document.getElementById(\'root\')\n);', 62 | mode: 'jsx', 63 | readOnly: false, 64 | wrapLines: false 65 | }} 66 | templateEditor={{ 67 | defaultValue: '
', 68 | mode: 'html', 69 | readOnly: false, 70 | wrapLines: false 71 | }} 72 | /> 73 | ); 74 | 75 | ReactDOM.render( 76 | , 77 | document.getElementById('root') 78 | ); 79 | ``` 80 | 81 | ## Complex Usage 82 | 83 | ```jsx 84 | import React from "react"; 85 | import ReactDOM from "react-dom"; 86 | import { Sandbox, withDependencies } from "react-sandbox-editor"; 87 | 88 | const ReactSandbox = withDependencies([ 89 | "https://unpkg.com/react@16.6.0/umd/react.development.js", 90 | "https://unpkg.com/react-dom@16.6.0/umd/react-dom.development.js" 91 | ])(Sandbox); 92 | 93 | const jsxCode = `const { Chip, Avatar } = window["material-ui"] 94 | 95 | ReactDOM.render( 96 | MB} 98 | label="Clickable Chip" 99 | onClick={() => alert("Chip Clicked!")} 100 | />, 101 | document.getElementById('root') 102 | );`; 103 | 104 | class App extends React.Component { 105 | onTabClick = (event, tabName) => { 106 | console.log(tabName); 107 | }; 108 | 109 | onPlayButtonClick = (event) => { 110 | console.log("Play button clicked!"); 111 | }; 112 | 113 | onCodeChange = (editorName, value) => { 114 | console.log(`Code in ${editorName} editor has changed: ${value}`); 115 | }; 116 | 117 | render() { 118 | return ( 119 | ', 129 | mode: "html", 130 | readOnly: false, 131 | wrapLines: true 132 | }} 133 | stylesheetEditor={{ 134 | defaultValue: "body { background: pink; }", 135 | mode: "css", 136 | readOnly: false, 137 | wrapLines: true 138 | }} 139 | executeOnCodeChange={true} 140 | executeOnCodeChangeDebounce={1000} 141 | onTabClick={this.onTabClick} 142 | displayMode="horizontal-split" 143 | hideDisplayModeButton 144 | horizontalSplitOffset={60} 145 | onPlayButtonClick={this.onPlayButtonClick} 146 | onCodeChange={this.onCodeChange} 147 | permissions={[ 148 | "allow-forms", 149 | "allow-pointer-lock", 150 | "allow-popups", 151 | "allow-modals", 152 | "allow-same-origin", 153 | "allow-scripts", 154 | "allow-top-navigation" 155 | ]} 156 | dependencies={[ 157 | "https://unpkg.com/@material-ui/core@3.0.0/umd/material-ui.development.js" 158 | ]} 159 | /> 160 | ); 161 | } 162 | } 163 | 164 | ReactDOM.render(, document.getElementById("root")); 165 | ``` 166 | 167 | ## Sandbox Execution 168 | Under the hood, code is executed in a sandboxed iframe as follows: 169 | ``` 170 | 187 | ``` 188 | 189 | ## Compatibility 190 | 191 | Version 1.X.X requires react 15.X.X || 16.X.X and react-dom 15.X.X || 16.X.X 192 | 193 | Version 2.X.X requires react >= 16.3.0 and react-dom >= 16.3.0 194 | 195 | 196 | ## For local development 197 | Install nvm
198 | ```bash 199 | curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.11/install.sh | bash 200 | ``` 201 | 202 | Install node 8.9.3
203 | ```bash 204 | nvm install v8.9.3 205 | ``` 206 | 207 | Open two different terminal tabs and (assuming your pwd is the root of this repo) 208 | 209 | In tab 1:
210 | ```bash 211 | cd src 212 | npm link 213 | npm install 214 | npm start 215 | ``` 216 | 217 | In tab 2:
218 | ```bash 219 | cd example/src 220 | npm link react-sandbox-editor 221 | npm install 222 | npm start 223 | ``` 224 | 225 | `src` contains the code for the react-sandbox-editor library.
226 | `example` contains a demo app that consumes the react-sandbox-editor library 227 | 228 | ## License 229 | 230 | MIT © [malerba118](https://github.com/malerba118) 231 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-sandbox-editor-example", 3 | "homepage": "https://malerba118.github.io/react-sandbox-editor", 4 | "version": "0.0.0", 5 | "private": true, 6 | "license": "MIT", 7 | "dependencies": { 8 | "classnames": "^2.2.6", 9 | "prop-types": "^15.6.1", 10 | "rc-slider": "^8.6.4", 11 | "react": "^16.4.0", 12 | "react-ace": "^6.1.4", 13 | "react-dom": "^16.4.0", 14 | "react-element-to-jsx-string": "^14.0.1", 15 | "react-router": "^4.3.1", 16 | "react-router-dom": "^4.3.1", 17 | "react-sandbox-editor": "*", 18 | "react-scripts": "^1.1.1" 19 | }, 20 | "scripts": { 21 | "start": "react-scripts start", 22 | "build": "react-scripts build", 23 | "test": "react-scripts test --env=jsdom", 24 | "eject": "react-scripts eject", 25 | "predeploy": "npm run build", 26 | "deploy": "gh-pages -d build" 27 | }, 28 | "devDependencies": { 29 | "gh-pages": "^1.2.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /example/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | react-sandbox-editor 11 | 12 | 13 | 14 | 17 | 18 |
19 | 20 | 21 | -------------------------------------------------------------------------------- /example/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "react-sandbox-editor", 3 | "name": "react-sandbox-editor", 4 | "start_url": "./index.html", 5 | "display": "standalone", 6 | "theme_color": "#000000", 7 | "background_color": "#ffffff" 8 | } 9 | -------------------------------------------------------------------------------- /example/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import withStyles from '@material-ui/core/styles/withStyles'; 3 | import { MuiThemeProvider, createMuiTheme } from '@material-ui/core/styles'; 4 | import { HashRouter, Route, Switch, Redirect } from 'react-router-dom' 5 | import Docs from './components/Docs' 6 | import Toolbar from './components/Toolbar' 7 | import Demo from './Demo' 8 | 9 | const theme = createMuiTheme({ 10 | typography: { 11 | // Use the system font instead of the default Roboto font. 12 | fontFamily: [ 13 | '"Quicksand"', 14 | 'sans-serif' 15 | ].join(','), 16 | }, 17 | overrides: { 18 | MuiInput: { 19 | root: { 20 | fontSize: '14px', 21 | color: 'rgba(0,0,0,.6)' 22 | }, 23 | }, 24 | MuiTypography: { 25 | body1: { 26 | fontSize: '14px', 27 | color: 'rgba(0,0,0,.6)' 28 | }, 29 | }, 30 | }, 31 | }); 32 | 33 | const styles = (theme) => ({ 34 | 35 | }) 36 | 37 | class App extends Component { 38 | 39 | render () { 40 | return ( 41 | 42 | 43 |
44 | 45 |
46 | 47 | 48 | } /> 49 | 50 | } /> 51 | 52 |
53 |
54 |
55 |
56 | ) 57 | } 58 | } 59 | 60 | export default withStyles(styles)(App) 61 | -------------------------------------------------------------------------------- /example/src/Demo.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import InputLabel from '@material-ui/core/InputLabel'; 3 | import MenuItem from '@material-ui/core/MenuItem'; 4 | import FormControl from '@material-ui/core/FormControl'; 5 | import Select from '@material-ui/core/Select'; 6 | import FormGroup from '@material-ui/core/FormGroup'; 7 | import FormControlLabel from '@material-ui/core/FormControlLabel'; 8 | import Switch from '@material-ui/core/Switch'; 9 | import Input from '@material-ui/core/Input'; 10 | import ListItemText from '@material-ui/core/ListItemText'; 11 | import Checkbox from '@material-ui/core/Checkbox'; 12 | import Typography from '@material-ui/core/Typography'; 13 | import Slider from 'rc-slider'; 14 | import withStyles from '@material-ui/core/styles/withStyles'; 15 | import brace from 'brace'; 16 | import AceEditor from 'react-ace'; 17 | import {Sandbox, withDependencies} from 'react-sandbox-editor' 18 | import {getReactSandboxUsage} from './utils' 19 | import 'brace/mode/jsx'; 20 | import 'brace/theme/kuroir'; 21 | import 'rc-slider/assets/index.css'; 22 | 23 | const ReactSandbox = withDependencies([ 24 | "https://unpkg.com/react@16.6.0/umd/react.development.js", 25 | "https://unpkg.com/react-dom@16.6.0/umd/react-dom.development.js" 26 | ])(Sandbox); 27 | 28 | const toolbarHeight = 64 29 | 30 | const possiblePermissions = [ 31 | 'allow-forms', 32 | 'allow-pointer-lock', 33 | 'allow-popups', 34 | 'allow-modals', 35 | 'allow-same-origin', 36 | 'allow-scripts', 37 | 'allow-top-navigation' 38 | ] 39 | 40 | 41 | const styles = (theme) => ({ 42 | toolbar: { 43 | height: toolbarHeight, 44 | backgroundColor: '#fff', 45 | }, 46 | globalSettings: { 47 | width: '100%', 48 | marginTop: 24 49 | }, 50 | horizontalForm: { 51 | display: 'flex', 52 | justifyContent: 'center', 53 | width: '100%', 54 | }, 55 | formControl: { 56 | margin: theme.spacing.unit*3, 57 | maxWidth: 150, 58 | minWidth: 50, 59 | width: '20%' 60 | }, 61 | leftContent: { 62 | display: 'flex', 63 | flexDirection: 'column', 64 | height: '100%', 65 | boxSizing: 'border-box', 66 | padding: '3% 4%', 67 | flex: 1, 68 | maxWidth: '100%', 69 | }, 70 | rightContent: { 71 | display: 'flex', 72 | flexDirection: 'column', 73 | height: '100%', 74 | boxSizing: 'border-box', 75 | padding: '3% 4%', 76 | flex: 1, 77 | maxWidth: '100%', 78 | }, 79 | divider: { 80 | height: '75vh', 81 | marginTop: 'auto', 82 | marginBottom: 'auto', 83 | marginRight: 15, 84 | width: 2, 85 | backgroundColor: 'rgba(0,0,0,0.1)' 86 | }, 87 | sandboxUsage: { 88 | marginTop: 24, 89 | height: '100%', 90 | maxWidth: '90%', 91 | padding: 24 92 | }, 93 | 'cyan-header': { 94 | backgroundColor: 'cyan !important', 95 | }, 96 | subtitle: { 97 | fontSize: 24, 98 | textAlign: 'center', 99 | color: 'rgba(0,0,0,.65)' 100 | } 101 | }) 102 | 103 | class Demo extends Component { 104 | 105 | state = { 106 | theme: 'solarized_dark', 107 | headerClass: 'none', 108 | showDisplayButton: true, 109 | permissions: possiblePermissions, 110 | executeOnCodeChange: true, 111 | executeOnCodeChangeDebounce: 1000, 112 | horizontalSplitOffset: 50, 113 | displayMode: 'horizontal-split' 114 | } 115 | 116 | onCodeChange = (editorName, value) => { 117 | 118 | } 119 | 120 | onSelectChange = (key, value) => { 121 | this.setState({ 122 | [key]: value 123 | }) 124 | } 125 | 126 | onSwitchChange = (key, value) => { 127 | this.setState({ 128 | [key]: value 129 | }) 130 | } 131 | 132 | onHorizontalSplitOffsetChange = (val) => { 133 | this.setState({horizontalSplitOffset: val}) 134 | } 135 | 136 | onDisplayModeButtonClick = (e, val) => { 137 | this.setState({displayMode: val}) 138 | } 139 | 140 | render () { 141 | const {classes} = this.props 142 | let reactSandbox = ( 143 | `, 155 | mode: 'html', 156 | readOnly: false, 157 | wrapLines: false 158 | }} 159 | scriptEditor={{ 160 | defaultValue: `ReactDOM.render( 161 |

Hello, world!

, 162 | document.getElementById('root') 163 | );`, 164 | mode: 'jsx', 165 | }} 166 | classes={{header: classes[this.state.headerClass]}} 167 | /> 168 | ) 169 | return ( 170 |
171 |
172 |

Input

173 |
174 |
175 | 176 | Theme 177 | 196 | 197 | 198 | Classes 199 | 212 | 213 | 214 | 215 | this.onSwitchChange('showDisplayButton', e.target.checked)} 220 | value="showDisplayButton" 221 | /> 222 | } 223 | label="Display Controls" 224 | /> 225 | 226 | 227 |
228 |
229 | 230 | 231 | this.onSwitchChange('executeOnCodeChange', e.target.checked)} 236 | value="executeOnCodeChange" 237 | /> 238 | } 239 | label="Auto Update" 240 | /> 241 | 242 | 243 | 244 | 245 | Update Debounce 246 | 247 | 260 | 261 | 262 | Permissions 263 | } 268 | renderValue={selected => selected.join(', ')} 269 | // MenuProps={MenuProps} 270 | > 271 | {possiblePermissions.map(perm => ( 272 | 273 | -1} /> 274 | 275 | 276 | ))} 277 | 278 | 279 |
280 |
281 | 282 | Horizontal Split Offset (no-op when displayMode="tab") 283 | 292 | 293 |
294 |
295 |
296 | 313 |
314 |
315 | 316 |
317 |
318 |

Output

319 |
320 | {reactSandbox} 321 |
322 |
323 |
324 | ) 325 | } 326 | } 327 | 328 | export default withStyles(styles)(Demo) 329 | -------------------------------------------------------------------------------- /example/src/components/Docs/Doc/ComponentDocumentation.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { withStyles } from '@material-ui/core/styles'; 4 | import Table from '@material-ui/core/Table'; 5 | import TableBody from '@material-ui/core/TableBody'; 6 | import TableCell from '@material-ui/core/TableCell'; 7 | import TableHead from '@material-ui/core/TableHead'; 8 | import TableRow from '@material-ui/core/TableRow'; 9 | import Paper from '@material-ui/core/Paper'; 10 | import Typography from '@material-ui/core/Typography'; 11 | 12 | const CustomTableCell = withStyles(theme => ({ 13 | head: { 14 | // backgroundColor: theme.palette.common.black, 15 | // color: theme.palette.common.white, 16 | color: '#62bcfa', 17 | fontSize: 13, 18 | }, 19 | body: { 20 | fontSize: 13, 21 | color: 'rgba(0,0,0,.6)' 22 | }, 23 | }))(TableCell); 24 | 25 | const styles = theme => ({ 26 | root: { 27 | width: '100%', 28 | marginTop: theme.spacing.unit * 4, 29 | overflowX: 'auto', 30 | }, 31 | table: { 32 | minWidth: 700, 33 | }, 34 | row: { 35 | '&:nth-of-type(odd)': { 36 | backgroundColor: 'white', 37 | }, 38 | '&:nth-of-type(even)': { 39 | backgroundColor: 'white', 40 | }, 41 | }, 42 | tableTitle: { 43 | display: 'flex', 44 | alignItems: 'center', 45 | fontSize: '24px', 46 | padding: 12, 47 | paddingLeft: 8, 48 | borderBottom: '1px solid rgba(255,255,255,.2)', 49 | // backgroundColor: '#f2f2f2', 50 | color: 'rgba(0,0,0,.65)', 51 | textTransform: 'capitalize', 52 | minWidth: 776 53 | } 54 | }); 55 | 56 | function ComponentDocumentation(props) { 57 | const { classes } = props; 58 | const component = props.component 59 | return ( 60 | 61 |
{component.name}
62 | 63 | Props 64 | 65 | 66 | 67 | 68 | Prop 69 | Type 70 | Accepted Values 71 | Default Value 72 | Description 73 | 74 | 75 | 76 | {Object.keys(component.props).map((propName, i) => { 77 | const prop = component.props[propName] 78 | return ( 79 | 80 | 81 | {propName} 82 | 83 | {prop.type} 84 | {prop.acceptedValues.join(', ')} 85 | {prop.defaultValue} 86 | {prop.description} 87 | 88 | ); 89 | })} 90 | 91 |
92 | {Object.keys(component.methods).length !== 0 && ( 93 |
94 | 95 | Methods 96 | 97 | 98 | 99 | 100 | Method 101 | Params 102 | Return Value 103 | Description 104 | 105 | 106 | 107 | {Object.keys(component.methods).map((methodName, i) => { 108 | const method = component.methods[methodName] 109 | return ( 110 | 111 | 112 | {methodName} 113 | 114 | {method.params.join(', ')} 115 | {method.returnValue} 116 | {method.description} 117 | 118 | ); 119 | })} 120 | 121 |
122 |
123 | )} 124 |
125 | ); 126 | } 127 | 128 | ComponentDocumentation.propTypes = { 129 | classes: PropTypes.object.isRequired, 130 | }; 131 | 132 | export default withStyles(styles)(ComponentDocumentation); 133 | -------------------------------------------------------------------------------- /example/src/components/Docs/Doc/Doc.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { withRouter } from 'react-router-dom' 3 | import ComponentDocumentation from './ComponentDocumentation' 4 | import HocDocumentation from './HocDocumentation' 5 | import Typography from '@material-ui/core/Typography'; 6 | 7 | class Doc extends Component { 8 | render() { 9 | const doc = this.props.doc 10 | if (!doc) { 11 | return ( 12 |
13 | Documentation version does not exist 14 |
15 | ) 16 | } 17 | else { 18 | return ( 19 |
20 | 21 | Components 22 | 23 | {Object.keys(doc.components).map((componentName, i) => ( 24 |
25 | 29 |
30 | ))} 31 | 32 | Higher-Order Components 33 | 34 | {Object.keys(doc.hoc).map((hocName, i) => ( 35 |
36 | 40 |
41 | ))} 42 |
43 | ) 44 | } 45 | } 46 | } 47 | 48 | export default withRouter(Doc) 49 | -------------------------------------------------------------------------------- /example/src/components/Docs/Doc/HocDocumentation.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { withStyles } from '@material-ui/core/styles'; 4 | import Table from '@material-ui/core/Table'; 5 | import TableBody from '@material-ui/core/TableBody'; 6 | import TableCell from '@material-ui/core/TableCell'; 7 | import TableHead from '@material-ui/core/TableHead'; 8 | import TableRow from '@material-ui/core/TableRow'; 9 | import Paper from '@material-ui/core/Paper'; 10 | 11 | const CustomTableCell = withStyles(theme => ({ 12 | head: { 13 | // backgroundColor: theme.palette.common.black, 14 | // color: theme.palette.common.white, 15 | color: '#62bcfa', 16 | fontSize: 13, 17 | }, 18 | body: { 19 | fontSize: 13, 20 | color: 'rgba(0,0,0,.6)' 21 | }, 22 | }))(TableCell); 23 | 24 | const styles = theme => ({ 25 | root: { 26 | width: '100%', 27 | marginTop: theme.spacing.unit * 4, 28 | overflowX: 'auto', 29 | }, 30 | table: { 31 | minWidth: 700, 32 | }, 33 | row: { 34 | '&:nth-of-type(odd)': { 35 | backgroundColor: 'white', 36 | }, 37 | '&:nth-of-type(even)': { 38 | backgroundColor: 'white', 39 | }, 40 | }, 41 | tableTitle: { 42 | display: 'flex', 43 | alignItems: 'center', 44 | fontSize: '24px', 45 | padding: 12, 46 | paddingLeft: 8, 47 | borderBottom: '1px solid rgba(255,255,255,.2)', 48 | // backgroundColor: '#f2f2f2', 49 | color: 'rgba(0,0,0,.65)', 50 | minWidth: 776 51 | } 52 | }); 53 | 54 | function HocDocumentation(props) { 55 | const { classes } = props; 56 | const hoc = props.hoc 57 | return ( 58 | 59 |
{hoc.name}
60 | 61 | 62 | 63 | Method 64 | Params 65 | Return Value 66 | Description 67 | 68 | 69 | 70 | 71 | 72 | {hoc.name} 73 | 74 | {hoc.params.join(', ')} 75 | {hoc.returnValue} 76 | {hoc.description} 77 | 78 | 79 |
80 |
81 | ); 82 | } 83 | 84 | HocDocumentation.propTypes = { 85 | classes: PropTypes.object.isRequired, 86 | }; 87 | 88 | export default withStyles(styles)(HocDocumentation); 89 | -------------------------------------------------------------------------------- /example/src/components/Docs/Doc/index.js: -------------------------------------------------------------------------------- 1 | import Doc from './Doc' 2 | 3 | export default Doc 4 | 5 | export { 6 | Doc 7 | } 8 | -------------------------------------------------------------------------------- /example/src/components/Docs/Docs.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { withRouter } from 'react-router-dom' 3 | import documentation from '../../documentation' 4 | import Doc from './Doc' 5 | import Input from '@material-ui/core/Input'; 6 | import InputLabel from '@material-ui/core/InputLabel'; 7 | import MenuItem from '@material-ui/core/MenuItem'; 8 | import FormControl from '@material-ui/core/FormControl'; 9 | import Select from '@material-ui/core/Select'; 10 | 11 | class Docs extends Component { 12 | 13 | constructor(props) { 14 | super(props) 15 | this.state = { 16 | docId: this.getDocIdFromUrlParam(this.props.match.params.version) 17 | } 18 | } 19 | 20 | componentWillReceiveProps(nextProps) { 21 | this.setState({ 22 | docId: this.getDocIdFromUrlParam(nextProps.match.params.version) 23 | }) 24 | } 25 | 26 | getDocIdFromUrlParam(urlParam) { 27 | let docId 28 | if (urlParam === 'latest') { 29 | docId = this.getLatestDocId() 30 | } 31 | else { 32 | docId = urlParam 33 | } 34 | return docId 35 | } 36 | 37 | getLatestDocId() { 38 | let docId 39 | let docIds = this.getDocIds() 40 | docId = docIds.sort()[docIds.length - 1] 41 | return docId 42 | } 43 | 44 | getDocIds = () => { 45 | return Object.keys(documentation) 46 | } 47 | 48 | handleSelectChange = (key, val) => { 49 | this.props.history.push(`/docs/${val}`) 50 | } 51 | 52 | render() { 53 | return ( 54 |
55 |
56 | 57 | Version 58 | } 62 | > 63 | {this.getDocIds().map((docId, i) => ( 64 | {docId} 65 | ))} 66 | 67 | 68 |
69 | 70 |
71 | ) 72 | } 73 | } 74 | 75 | export default withRouter(Docs) 76 | -------------------------------------------------------------------------------- /example/src/components/Docs/index.js: -------------------------------------------------------------------------------- 1 | import Docs from './Docs' 2 | 3 | export default Docs 4 | 5 | export { 6 | Docs 7 | } 8 | -------------------------------------------------------------------------------- /example/src/components/Toolbar/Toolbar.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import withStyles from '@material-ui/core/styles/withStyles'; 3 | import { withRouter, Link } from 'react-router-dom' 4 | import classNames from 'classnames' 5 | 6 | const toolbarHeight = 64 7 | 8 | const styles = (theme) => ({ 9 | toolbar: { 10 | height: toolbarHeight, 11 | backgroundColor: '#fff', 12 | fontWeight: 100, 13 | display: 'flex', 14 | justifyContent: 'start', 15 | alignItems: 'center', 16 | borderBottom: '1px solid rgba(0,0,0,.08)', 17 | }, 18 | toolbarLink: { 19 | textDecoration: 'none', 20 | color: 'rgba(0,0,0,.3)', 21 | marginLeft: 28, 22 | }, 23 | active: { 24 | color: '#62bcfa' 25 | } 26 | }) 27 | 28 | class Toolbar extends Component { 29 | 30 | render () { 31 | const {classes} = this.props 32 | let active = this.props.location.pathname.split('/')[1] 33 | return ( 34 |
35 | Demo 39 | Documentation 43 |
44 | ) 45 | } 46 | } 47 | 48 | export default withRouter(withStyles(styles)(Toolbar)) 49 | -------------------------------------------------------------------------------- /example/src/components/Toolbar/index.js: -------------------------------------------------------------------------------- 1 | import Toolbar from './Toolbar' 2 | 3 | export default Toolbar 4 | 5 | export { 6 | Toolbar 7 | } 8 | -------------------------------------------------------------------------------- /example/src/documentation/index.js: -------------------------------------------------------------------------------- 1 | import v10X from './v1.0.X' 2 | import v20X from './v2.0.X' 3 | 4 | export default { 5 | "v1.0.X": v10X, 6 | "v2.0.X": v20X, 7 | } 8 | -------------------------------------------------------------------------------- /example/src/documentation/v1.0.X/Sandbox.js: -------------------------------------------------------------------------------- 1 | export default { 2 | props: { 3 | theme: { 4 | type: 'string', 5 | acceptedValues: [ 6 | 'monokai', 7 | 'twilight', 8 | 'solarized_dark', 9 | 'solarized_light', 10 | 'tomorrow', 11 | 'github' 12 | ], 13 | defaultValue: 'solarized_dark', 14 | description: 'Defines the sandbox color theme.' 15 | }, 16 | dependencies: { 17 | type: 'array', 18 | acceptedValues: ['urls'], 19 | defaultValue: '[]', 20 | description: 'Array of urls to be loaded via script tags prior to execution of editor code.' 21 | }, 22 | selectedTab: { 23 | type: 'string', 24 | acceptedValues: ['templateTab', 'scriptTab', 'stylesheetTab', 'resultTab'], 25 | defaultValue: 'null', 26 | description: 'If set, overrides default tab behavior.' 27 | }, 28 | displayMode: { 29 | type: 'string', 30 | acceptedValues: ['tab', 'horizontal-split'], 31 | defaultValue: 'null', 32 | description: 'If set, overrides default display mode behavior.' 33 | }, 34 | executeOnCodeChange: { 35 | type: 'boolean', 36 | acceptedValues: [ 37 | 'true', 'false' 38 | ], 39 | defaultValue: 'true', 40 | description: 'Execute code automatically when it changes.' 41 | }, 42 | executeOnCodeChangeDebounce: { 43 | type: 'int', 44 | acceptedValues: [ 45 | 'positive integer' 46 | ], 47 | defaultValue: '1000', 48 | description: 'Time to wait in milliseconds before automatically executing code. Only used when executeOnCodeChange is set to true.' 49 | }, 50 | permissions: { 51 | type: 'array', 52 | acceptedValues: [ 53 | 'allow-forms', 54 | 'allow-pointer-lock', 55 | 'allow-popups', 56 | 'allow-modals', 57 | 'allow-same-origin', 58 | 'allow-scripts', 59 | 'allow-top-navigation' 60 | ], 61 | defaultValue: `[ 62 | 'allow-forms', 63 | 'allow-pointer-lock', 64 | 'allow-popups', 65 | 'allow-modals', 66 | 'allow-same-origin', 67 | 'allow-scripts', 68 | 'allow-top-navigation' 69 | ]`, 70 | description: 'Permissions granted to the SandboxInterpreter.' 71 | }, 72 | onCodeChange: { 73 | type: 'function', 74 | acceptedValues: [`(editorName:string, editorValue:string) => {...}`], 75 | defaultValue: `no-op`, 76 | description: 'Invoked when code in any of the editors changes.' 77 | }, 78 | onTabClick: { 79 | type: 'function', 80 | acceptedValues: [`(tabName:string) => {...}`], 81 | defaultValue: `no-op`, 82 | description: 'Invoked when a tab is clicked.' 83 | }, 84 | onPlayButtonClick: { 85 | type: 'function', 86 | acceptedValues: [`() => {...}`], 87 | defaultValue: `no-op`, 88 | description: 'Invoked when the play button is clicked.' 89 | }, 90 | onDisplayModeButtonClick: { 91 | type: 'function', 92 | acceptedValues: [`(requestedMode:string) => {...}`], 93 | defaultValue: `no-op`, 94 | description: 'Invoked when the display mode button is clicked.' 95 | }, 96 | hideDisplayModeButton: { 97 | type: 'boolean', 98 | acceptedValues: ['true', 'false'], 99 | defaultValue: 'false', 100 | description: 'Hides the button to toggle the display mode.' 101 | }, 102 | style: { 103 | type: 'object', 104 | acceptedValues: ['{...}'], 105 | defaultValue: '{}', 106 | description: 'Styles applied to root of sandbox.' 107 | }, 108 | classes: { 109 | type: 'object', 110 | acceptedValues: [`{header, 111 | selectedTabIndicator, 112 | tab, 113 | selectedTab, 114 | iconButton, 115 | Header, 116 | SelectedTabIndicator, 117 | Tab, 118 | SelectedTab, 119 | IconButton}` 120 | ], 121 | defaultValue: '{}', 122 | description: 'Overrides styles globally/per theme (may need to use !important to override some).' 123 | }, 124 | templateEditor: { 125 | type: 'object', 126 | acceptedValues: [ 127 | `{ 128 | defaultValue: string, 129 | mode: oneOf(['html']), 130 | readOnly: boolean, 131 | wrapLines: boolean 132 | }` 133 | ], 134 | defaultValue: `{ 135 | defaultValue: '', 136 | mode: 'html', 137 | readOnly: false, 138 | wrapLines: false 139 | }`, 140 | description: 'Template editor options.' 141 | }, 142 | scriptEditor: { 143 | type: 'object', 144 | acceptedValues: [ 145 | `{ 146 | defaultValue: string, 147 | mode: oneOf(['javascript', 'jsx']), 148 | readOnly: boolean, 149 | wrapLines: boolean 150 | }` 151 | ], 152 | defaultValue: `{ 153 | defaultValue: '', 154 | mode: 'javascript', 155 | readOnly: false, 156 | wrapLines: false 157 | }`, 158 | description: 'Script editor options.' 159 | }, 160 | stylesheetEditor: { 161 | type: 'object', 162 | acceptedValues: [ 163 | `{ 164 | defaultValue: string, 165 | mode: oneOf(['css']), 166 | readOnly: boolean, 167 | wrapLines: boolean 168 | }` 169 | ], 170 | defaultValue: `{ 171 | defaultValue: '', 172 | mode: 'css', 173 | readOnly: false, 174 | wrapLines: false 175 | }`, 176 | description: 'Stylesheet editor options.' 177 | }, 178 | }, 179 | methods: { 180 | execute: { 181 | params: [], 182 | returnValue: '', 183 | description: 'Executes code' 184 | } 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /example/src/documentation/v1.0.X/SandboxInterpreter.js: -------------------------------------------------------------------------------- 1 | export default { 2 | props: { 3 | dependencies: { 4 | type: 'array', 5 | acceptedValues: ['urls'], 6 | defaultValue: '[]', 7 | description: 'Array of urls to be loaded via script tags prior to execution of template, script, and stylesheet code.' 8 | }, 9 | template: { 10 | type: 'string', 11 | acceptedValues: ['any string'], 12 | defaultValue: "''", 13 | description: 'Template code to be rendered as html body.' 14 | }, 15 | templateMode: { 16 | type: 'string', 17 | acceptedValues: ['html'], 18 | defaultValue: 'html', 19 | description: 'Defines how template should be proccessed.' 20 | }, 21 | script: { 22 | type: 'string', 23 | acceptedValues: ['any string'], 24 | defaultValue: "''", 25 | description: 'Script code to be interpreted as type specified by scriptMode.' 26 | }, 27 | scriptMode: { 28 | type: 'string', 29 | acceptedValues: ['javascript', 'jsx'], 30 | defaultValue: 'javascript', 31 | description: 'Defines how script should be proccessed.' 32 | }, 33 | stylesheet: { 34 | type: 'string', 35 | acceptedValues: ['any string'], 36 | defaultValue: "''", 37 | description: 'Stylesheet code to be interpreted as type specified by stylesheetMode.' 38 | }, 39 | stylesheetMode: { 40 | type: 'string', 41 | acceptedValues: ['css'], 42 | defaultValue: 'css', 43 | description: 'Defines how stylesheet should be proccessed.' 44 | }, 45 | style: { 46 | type: 'object', 47 | acceptedValues: ['{...}'], 48 | defaultValue: '{}', 49 | description: 'Styles applied to root of the editor.' 50 | }, 51 | className: { 52 | type: 'string', 53 | acceptedValues: ['Css class name'], 54 | defaultValue: "''", 55 | description: 'Css class applied to root of interpreter.' 56 | }, 57 | }, 58 | methods: { 59 | execute: { 60 | params: [], 61 | returnValue: '', 62 | description: 'Executes code' 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /example/src/documentation/v1.0.X/ScriptEditor.js: -------------------------------------------------------------------------------- 1 | export default { 2 | props: { 3 | theme: { 4 | type: 'string', 5 | acceptedValues: [ 6 | 'monokai', 7 | 'twilight', 8 | 'solarized_dark', 9 | 'solarized_light', 10 | 'tomorrow', 11 | 'github' 12 | ], 13 | defaultValue: 'solarized_dark', 14 | description: `Defines the editor's color theme.` 15 | }, 16 | onChange: { 17 | type: 'function', 18 | acceptedValues: [`(editorValue:string) => {...}`], 19 | defaultValue: `no-op`, 20 | description: 'Invoked when code in the editor changes.' 21 | }, 22 | style: { 23 | type: 'object', 24 | acceptedValues: ['{...}'], 25 | defaultValue: '{}', 26 | description: 'Styles applied to root of the editor.' 27 | }, 28 | classes: { 29 | type: 'object', 30 | acceptedValues: [`{root}` 31 | ], 32 | defaultValue: '{}', 33 | description: 'Overrides styles (may need to use !important to override some).' 34 | }, 35 | value: { 36 | type: 'string', 37 | acceptedValues: ['any string'], 38 | defaultValue: "''", 39 | description: 'Value displayed in the editor.' 40 | }, 41 | mode: { 42 | type: 'string', 43 | acceptedValues: ['javascript', 'jsx'], 44 | defaultValue: 'javascript', 45 | description: 'Syntax highlighting mode.' 46 | }, 47 | readOnly: { 48 | type: 'boolean', 49 | acceptedValues: ['true', 'false'], 50 | defaultValue: 'false', 51 | description: 'Disables typing in the editor.' 52 | }, 53 | wrapLines: { 54 | type: 'boolean', 55 | acceptedValues: ['true', 'false'], 56 | defaultValue: 'false', 57 | description: 'Forces lines to wrap if they overflow the editor width.' 58 | } 59 | }, 60 | methods: { 61 | 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /example/src/documentation/v1.0.X/StatelessSandbox.js: -------------------------------------------------------------------------------- 1 | export default { 2 | props: { 3 | theme: { 4 | type: 'string', 5 | acceptedValues: [ 6 | 'monokai', 7 | 'twilight', 8 | 'solarized_dark', 9 | 'solarized_light', 10 | 'tomorrow', 11 | 'github' 12 | ], 13 | defaultValue: 'solarized_dark', 14 | description: 'Defines the sandbox color theme.' 15 | }, 16 | dependencies: { 17 | type: 'array', 18 | acceptedValues: ['urls'], 19 | defaultValue: '[]', 20 | description: 'Array of urls to be loaded via script tags prior to execution of editor code.' 21 | }, 22 | selectedTab: { 23 | type: 'string', 24 | acceptedValues: ['templateTab', 'scriptTab', 'stylesheetTab', 'resultTab'], 25 | defaultValue: 'templateTab', 26 | description: 'Sets the selected tab.' 27 | }, 28 | displayMode: { 29 | type: 'string', 30 | acceptedValues: ['tab', 'horizontal-split'], 31 | defaultValue: 'tab', 32 | description: 'Sets the display mode.' 33 | }, 34 | executeOnCodeChange: { 35 | type: 'boolean', 36 | acceptedValues: [ 37 | 'true', 'false' 38 | ], 39 | defaultValue: 'true', 40 | description: 'Execute code automatically when it changes.' 41 | }, 42 | executeOnCodeChangeDebounce: { 43 | type: 'int', 44 | acceptedValues: [ 45 | 'positive integer' 46 | ], 47 | defaultValue: '1000', 48 | description: 'Time to wait in milliseconds before automatically executing code. Only used when executeOnCodeChange is set to true.' 49 | }, 50 | permissions: { 51 | type: 'array', 52 | acceptedValues: [ 53 | 'allow-forms', 54 | 'allow-pointer-lock', 55 | 'allow-popups', 56 | 'allow-modals', 57 | 'allow-same-origin', 58 | 'allow-scripts', 59 | 'allow-top-navigation' 60 | ], 61 | defaultValue: `[ 62 | 'allow-forms', 63 | 'allow-pointer-lock', 64 | 'allow-popups', 65 | 'allow-modals', 66 | 'allow-same-origin', 67 | 'allow-scripts', 68 | 'allow-top-navigation' 69 | ]`, 70 | description: 'Permissions granted to the SandboxInterpreter.' 71 | }, 72 | onCodeChange: { 73 | type: 'function', 74 | acceptedValues: [`(editorName:string, editorValue:string) => {...}`], 75 | defaultValue: `no-op`, 76 | description: 'Invoked when code in any of the editors changes.' 77 | }, 78 | onTabClick: { 79 | type: 'function', 80 | acceptedValues: [`(tabName:string) => {...}`], 81 | defaultValue: `no-op`, 82 | description: 'Invoked when a tab is clicked.' 83 | }, 84 | onPlayButtonClick: { 85 | type: 'function', 86 | acceptedValues: [`() => {...}`], 87 | defaultValue: `no-op`, 88 | description: 'Invoked when the play button is clicked.' 89 | }, 90 | onDisplayModeButtonClick: { 91 | type: 'function', 92 | acceptedValues: [`(requestedMode:string) => {...}`], 93 | defaultValue: `no-op`, 94 | description: 'Invoked when the display mode button is clicked.' 95 | }, 96 | hideDisplayModeButton: { 97 | type: 'boolean', 98 | acceptedValues: ['true', 'false'], 99 | defaultValue: 'false', 100 | description: 'Hides the button to toggle the display mode.' 101 | }, 102 | style: { 103 | type: 'object', 104 | acceptedValues: ['{...}'], 105 | defaultValue: '{}', 106 | description: 'Styles applied to root of sandbox.' 107 | }, 108 | classes: { 109 | type: 'object', 110 | acceptedValues: [`{header, 111 | selectedTabIndicator, 112 | tab, 113 | selectedTab, 114 | iconButton, 115 | Header, 116 | SelectedTabIndicator, 117 | Tab, 118 | SelectedTab, 119 | IconButton}` 120 | ], 121 | defaultValue: '{}', 122 | description: 'Overrides styles (may need to use !important to override some).' 123 | }, 124 | templateEditor: { 125 | type: 'object', 126 | acceptedValues: [ 127 | `{ 128 | defaultValue: string, 129 | mode: oneOf(['html']), 130 | readOnly: boolean, 131 | wrapLines: boolean 132 | }` 133 | ], 134 | defaultValue: `{ 135 | defaultValue: '', 136 | mode: 'html', 137 | readOnly: false, 138 | wrapLines: false 139 | }`, 140 | description: 'Template editor options.' 141 | }, 142 | scriptEditor: { 143 | type: 'object', 144 | acceptedValues: [ 145 | `{ 146 | defaultValue: string, 147 | mode: oneOf(['javascript', 'jsx']), 148 | readOnly: boolean, 149 | wrapLines: boolean 150 | }` 151 | ], 152 | defaultValue: `{ 153 | defaultValue: '', 154 | mode: 'javascript', 155 | readOnly: false, 156 | wrapLines: false 157 | }`, 158 | description: 'Script editor options.' 159 | }, 160 | stylesheetEditor: { 161 | type: 'object', 162 | acceptedValues: [ 163 | `{ 164 | defaultValue: string, 165 | mode: oneOf(['css']), 166 | readOnly: boolean, 167 | wrapLines: boolean 168 | }` 169 | ], 170 | defaultValue: `{ 171 | defaultValue: '', 172 | mode: 'css', 173 | readOnly: false, 174 | wrapLines: false 175 | }`, 176 | description: 'Stylesheet editor options.' 177 | }, 178 | }, 179 | methods: { 180 | execute: { 181 | params: [], 182 | returnValue: '', 183 | description: 'Executes code' 184 | } 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /example/src/documentation/v1.0.X/StylesheetEditor.js: -------------------------------------------------------------------------------- 1 | export default { 2 | props: { 3 | theme: { 4 | type: 'string', 5 | acceptedValues: [ 6 | 'monokai', 7 | 'twilight', 8 | 'solarized_dark', 9 | 'solarized_light', 10 | 'tomorrow', 11 | 'github' 12 | ], 13 | defaultValue: 'solarized_dark', 14 | description: `Defines the editor's color theme.` 15 | }, 16 | onChange: { 17 | type: 'function', 18 | acceptedValues: [`(editorValue:string) => {...}`], 19 | defaultValue: `no-op`, 20 | description: 'Invoked when code in the editor changes.' 21 | }, 22 | style: { 23 | type: 'object', 24 | acceptedValues: ['{...}'], 25 | defaultValue: '{}', 26 | description: 'Styles applied to root of the editor.' 27 | }, 28 | classes: { 29 | type: 'object', 30 | acceptedValues: [`{root}` 31 | ], 32 | defaultValue: '{}', 33 | description: 'Overrides styles (may need to use !important to override some).' 34 | }, 35 | value: { 36 | type: 'string', 37 | acceptedValues: ['any string'], 38 | defaultValue: "''", 39 | description: 'Value displayed in the editor.' 40 | }, 41 | mode: { 42 | type: 'string', 43 | acceptedValues: ['css'], 44 | defaultValue: 'css', 45 | description: 'Syntax highlighting mode.' 46 | }, 47 | readOnly: { 48 | type: 'boolean', 49 | acceptedValues: ['true', 'false'], 50 | defaultValue: 'false', 51 | description: 'Disables typing in the editor.' 52 | }, 53 | wrapLines: { 54 | type: 'boolean', 55 | acceptedValues: ['true', 'false'], 56 | defaultValue: 'false', 57 | description: 'Forces lines to wrap if they overflow the editor width.' 58 | } 59 | }, 60 | methods: { 61 | 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /example/src/documentation/v1.0.X/TemplateEditor.js: -------------------------------------------------------------------------------- 1 | export default { 2 | props: { 3 | theme: { 4 | type: 'string', 5 | acceptedValues: [ 6 | 'monokai', 7 | 'twilight', 8 | 'solarized_dark', 9 | 'solarized_light', 10 | 'tomorrow', 11 | 'github' 12 | ], 13 | defaultValue: 'solarized_dark', 14 | description: `Defines the editor's color theme.` 15 | }, 16 | onChange: { 17 | type: 'function', 18 | acceptedValues: [`(editorValue:string) => {...}`], 19 | defaultValue: `no-op`, 20 | description: 'Invoked when code in the editor changes.' 21 | }, 22 | style: { 23 | type: 'object', 24 | acceptedValues: ['{...}'], 25 | defaultValue: '{}', 26 | description: 'Styles applied to root of the editor.' 27 | }, 28 | classes: { 29 | type: 'object', 30 | acceptedValues: [`{root}` 31 | ], 32 | defaultValue: '{}', 33 | description: 'Overrides styles (may need to use !important to override some).' 34 | }, 35 | value: { 36 | type: 'string', 37 | acceptedValues: ['any string'], 38 | defaultValue: "''", 39 | description: 'Value displayed in the editor.' 40 | }, 41 | mode: { 42 | type: 'string', 43 | acceptedValues: ['html'], 44 | defaultValue: 'html', 45 | description: 'Syntax highlighting mode.' 46 | }, 47 | readOnly: { 48 | type: 'boolean', 49 | acceptedValues: ['true', 'false'], 50 | defaultValue: 'false', 51 | description: 'Disables typing in the editor.' 52 | }, 53 | wrapLines: { 54 | type: 'boolean', 55 | acceptedValues: ['true', 'false'], 56 | defaultValue: 'false', 57 | description: 'Forces lines to wrap if they overflow the editor width.' 58 | } 59 | }, 60 | methods: { 61 | 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /example/src/documentation/v1.0.X/index.js: -------------------------------------------------------------------------------- 1 | import Sandbox from './Sandbox' 2 | import StatelessSandbox from './StatelessSandbox' 3 | import TemplateEditor from './TemplateEditor' 4 | import ScriptEditor from './ScriptEditor' 5 | import StylesheetEditor from './StylesheetEditor' 6 | import SandboxInterpreter from './SandboxInterpreter' 7 | import withDependencies from './withDependencies' 8 | 9 | 10 | export default { 11 | "components": { 12 | Sandbox, 13 | StatelessSandbox, 14 | TemplateEditor, 15 | ScriptEditor, 16 | StylesheetEditor, 17 | SandboxInterpreter 18 | }, 19 | "hoc": { 20 | withDependencies 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /example/src/documentation/v1.0.X/withDependencies.js: -------------------------------------------------------------------------------- 1 | export default { 2 | params: ['dependencies: array'], 3 | returnValue: '(component: React.Component) => {} : React.Component', 4 | description: 'Takes a list of dependencies and returns a component decorator. Can be applied to any component that accepts a dependencies prop.' 5 | } 6 | -------------------------------------------------------------------------------- /example/src/documentation/v2.0.X/Sandbox.js: -------------------------------------------------------------------------------- 1 | export default { 2 | props: { 3 | theme: { 4 | type: 'string', 5 | acceptedValues: [ 6 | 'monokai', 7 | 'twilight', 8 | 'solarized_dark', 9 | 'solarized_light', 10 | 'tomorrow', 11 | 'github' 12 | ], 13 | defaultValue: 'solarized_dark', 14 | description: 'Defines the sandbox color theme.' 15 | }, 16 | dependencies: { 17 | type: 'array', 18 | acceptedValues: ['urls'], 19 | defaultValue: '[]', 20 | description: 'Array of urls to be loaded via script tags prior to execution of editor code.' 21 | }, 22 | selectedTab: { 23 | type: 'string', 24 | acceptedValues: ['templateTab', 'scriptTab', 'stylesheetTab', 'resultTab'], 25 | defaultValue: 'null', 26 | description: 'If set, overrides default tab behavior.' 27 | }, 28 | displayMode: { 29 | type: 'string', 30 | acceptedValues: ['tab', 'horizontal-split'], 31 | defaultValue: 'null', 32 | description: 'If set, overrides default display mode behavior.' 33 | }, 34 | executeOnCodeChange: { 35 | type: 'boolean', 36 | acceptedValues: [ 37 | 'true', 'false' 38 | ], 39 | defaultValue: 'true', 40 | description: 'Execute code automatically when it changes.' 41 | }, 42 | executeOnCodeChangeDebounce: { 43 | type: 'int', 44 | acceptedValues: [ 45 | 'positive integer' 46 | ], 47 | defaultValue: '1000', 48 | description: 'Time to wait in milliseconds before automatically executing code. Only used when executeOnCodeChange is set to true.' 49 | }, 50 | permissions: { 51 | type: 'array', 52 | acceptedValues: [ 53 | 'allow-forms', 54 | 'allow-pointer-lock', 55 | 'allow-popups', 56 | 'allow-modals', 57 | 'allow-same-origin', 58 | 'allow-scripts', 59 | 'allow-top-navigation' 60 | ], 61 | defaultValue: `[ 62 | 'allow-forms', 63 | 'allow-pointer-lock', 64 | 'allow-popups', 65 | 'allow-modals', 66 | 'allow-same-origin', 67 | 'allow-scripts', 68 | 'allow-top-navigation' 69 | ]`, 70 | description: 'Permissions granted to the SandboxInterpreter.' 71 | }, 72 | onCodeChange: { 73 | type: 'function', 74 | acceptedValues: [`(editorName:string, editorValue:string) => {...}`], 75 | defaultValue: `no-op`, 76 | description: 'Invoked when code in any of the editors changes.' 77 | }, 78 | onTabClick: { 79 | type: 'function', 80 | acceptedValues: [`(event: SyntheticEvent, tabName:string) => {...}`], 81 | defaultValue: `no-op`, 82 | description: 'Invoked when a tab is clicked.' 83 | }, 84 | onPlayButtonClick: { 85 | type: 'function', 86 | acceptedValues: [`(event: SyntheticEvent) => {...}`], 87 | defaultValue: `no-op`, 88 | description: 'Invoked when the play button is clicked.' 89 | }, 90 | onDisplayModeButtonClick: { 91 | type: 'function', 92 | acceptedValues: [`(event: SyntheticEvent, requestedMode:string) => {...}`], 93 | defaultValue: `no-op`, 94 | description: 'Invoked when the display mode button is clicked.' 95 | }, 96 | hideDisplayModeButton: { 97 | type: 'boolean', 98 | acceptedValues: ['true', 'false'], 99 | defaultValue: 'false', 100 | description: 'Hides the button to toggle the display mode.' 101 | }, 102 | style: { 103 | type: 'object', 104 | acceptedValues: ['{...}'], 105 | defaultValue: '{}', 106 | description: 'Styles applied to root of sandbox.' 107 | }, 108 | classes: { 109 | type: 'object', 110 | acceptedValues: [`{header, 111 | selectedTabIndicator, 112 | tab, 113 | selectedTab, 114 | iconButton, 115 | Header, 116 | SelectedTabIndicator, 117 | Tab, 118 | SelectedTab, 119 | IconButton}` 120 | ], 121 | defaultValue: '{}', 122 | description: 'Overrides styles globally/per theme (may need to use !important to override some).' 123 | }, 124 | templateEditor: { 125 | type: 'object', 126 | acceptedValues: [ 127 | `{ 128 | defaultValue: string, 129 | mode: oneOf(['html']), 130 | readOnly: boolean, 131 | wrapLines: boolean 132 | }` 133 | ], 134 | defaultValue: `{ 135 | defaultValue: '', 136 | mode: 'html', 137 | readOnly: false, 138 | wrapLines: false 139 | }`, 140 | description: 'Template editor options.' 141 | }, 142 | scriptEditor: { 143 | type: 'object', 144 | acceptedValues: [ 145 | `{ 146 | defaultValue: string, 147 | mode: oneOf(['javascript', 'jsx']), 148 | readOnly: boolean, 149 | wrapLines: boolean 150 | }` 151 | ], 152 | defaultValue: `{ 153 | defaultValue: '', 154 | mode: 'javascript', 155 | readOnly: false, 156 | wrapLines: false 157 | }`, 158 | description: 'Script editor options.' 159 | }, 160 | stylesheetEditor: { 161 | type: 'object', 162 | acceptedValues: [ 163 | `{ 164 | defaultValue: string, 165 | mode: oneOf(['css']), 166 | readOnly: boolean, 167 | wrapLines: boolean 168 | }` 169 | ], 170 | defaultValue: `{ 171 | defaultValue: '', 172 | mode: 'css', 173 | readOnly: false, 174 | wrapLines: false 175 | }`, 176 | description: 'Stylesheet editor options.' 177 | }, 178 | preScript: { 179 | type: 'string', 180 | acceptedValues: [ 181 | 'any string' 182 | ], 183 | defaultValue: `''`, 184 | description: `Script run prior to script editor's code. Convenient for hiding boilerplate set-up. The script preprocessor will also be applied to the preScript.` 185 | }, 186 | postScript: { 187 | type: 'string', 188 | acceptedValues: [ 189 | 'any string' 190 | ], 191 | defaultValue: `''`, 192 | description: `Script run after script editor's code. Convenient for hiding boilerplate set-up. The script preprocessor will also be applied to the postScript.` 193 | }, 194 | onRef: { 195 | type: 'function', 196 | acceptedValues: [ 197 | '(component: Sandbox) => {...}' 198 | ], 199 | defaultValue: `''`, 200 | description: `A way to get a component instance to call instance methods.` 201 | }, 202 | horizontalSplitOffset: { 203 | type: 'number', 204 | acceptedValues: [ 205 | '20 - 80' 206 | ], 207 | defaultValue: `50`, 208 | description: `Controls the offset of the split (as a percentage from the top) when the sandbox is in horizontal-split mode. A no-op when displayMode="tab"` 209 | }, 210 | }, 211 | methods: { 212 | execute: { 213 | params: [], 214 | returnValue: '', 215 | description: 'Executes code' 216 | } 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /example/src/documentation/v2.0.X/SandboxInterpreter.js: -------------------------------------------------------------------------------- 1 | export default { 2 | props: { 3 | dependencies: { 4 | type: 'array', 5 | acceptedValues: ['urls'], 6 | defaultValue: '[]', 7 | description: 'Array of urls to be loaded via script tags prior to execution of template, script, and stylesheet code.' 8 | }, 9 | template: { 10 | type: 'string', 11 | acceptedValues: ['any string'], 12 | defaultValue: "''", 13 | description: 'Template code to be rendered as html body.' 14 | }, 15 | templateMode: { 16 | type: 'string', 17 | acceptedValues: ['html'], 18 | defaultValue: 'html', 19 | description: 'Defines how template should be proccessed.' 20 | }, 21 | preScript: { 22 | type: 'string', 23 | acceptedValues: [ 24 | 'any string' 25 | ], 26 | defaultValue: `''`, 27 | description: `Script run before primary script. preScript code to be interpreted as type specified by scriptMode.` 28 | }, 29 | script: { 30 | type: 'string', 31 | acceptedValues: ['any string'], 32 | defaultValue: "''", 33 | description: 'Script code to be interpreted as type specified by scriptMode.' 34 | }, 35 | postScript: { 36 | type: 'string', 37 | acceptedValues: [ 38 | 'any string' 39 | ], 40 | defaultValue: `''`, 41 | description: `Script run after primary script. postScript code to be interpreted as type specified by scriptMode.` 42 | }, 43 | scriptMode: { 44 | type: 'string', 45 | acceptedValues: ['javascript', 'jsx'], 46 | defaultValue: 'javascript', 47 | description: 'Defines how script should be proccessed.' 48 | }, 49 | stylesheet: { 50 | type: 'string', 51 | acceptedValues: ['any string'], 52 | defaultValue: "''", 53 | description: 'Stylesheet code to be interpreted as type specified by stylesheetMode.' 54 | }, 55 | stylesheetMode: { 56 | type: 'string', 57 | acceptedValues: ['css'], 58 | defaultValue: 'css', 59 | description: 'Defines how stylesheet should be proccessed.' 60 | }, 61 | style: { 62 | type: 'object', 63 | acceptedValues: ['{...}'], 64 | defaultValue: '{}', 65 | description: 'Styles applied to root of the editor.' 66 | }, 67 | className: { 68 | type: 'string', 69 | acceptedValues: ['Css class name'], 70 | defaultValue: "''", 71 | description: 'Css class applied to root of interpreter.' 72 | }, 73 | onRef: { 74 | type: 'function', 75 | acceptedValues: [ 76 | '(component: SandboxInterpreter) => {...}' 77 | ], 78 | defaultValue: `''`, 79 | description: `A way to get a component instance to call instance methods.` 80 | }, 81 | }, 82 | methods: { 83 | execute: { 84 | params: [], 85 | returnValue: '', 86 | description: 'Executes code' 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /example/src/documentation/v2.0.X/ScriptEditor.js: -------------------------------------------------------------------------------- 1 | export default { 2 | props: { 3 | theme: { 4 | type: 'string', 5 | acceptedValues: [ 6 | 'monokai', 7 | 'twilight', 8 | 'solarized_dark', 9 | 'solarized_light', 10 | 'tomorrow', 11 | 'github' 12 | ], 13 | defaultValue: 'solarized_dark', 14 | description: `Defines the editor's color theme.` 15 | }, 16 | onChange: { 17 | type: 'function', 18 | acceptedValues: [`(editorValue:string) => {...}`], 19 | defaultValue: `no-op`, 20 | description: 'Invoked when code in the editor changes.' 21 | }, 22 | style: { 23 | type: 'object', 24 | acceptedValues: ['{...}'], 25 | defaultValue: '{}', 26 | description: 'Styles applied to root of the editor.' 27 | }, 28 | classes: { 29 | type: 'object', 30 | acceptedValues: [`{root}` 31 | ], 32 | defaultValue: '{}', 33 | description: 'Overrides styles (may need to use !important to override some).' 34 | }, 35 | value: { 36 | type: 'string', 37 | acceptedValues: ['any string'], 38 | defaultValue: "''", 39 | description: 'Value displayed in the editor.' 40 | }, 41 | mode: { 42 | type: 'string', 43 | acceptedValues: ['javascript', 'jsx'], 44 | defaultValue: 'javascript', 45 | description: 'Syntax highlighting mode.' 46 | }, 47 | readOnly: { 48 | type: 'boolean', 49 | acceptedValues: ['true', 'false'], 50 | defaultValue: 'false', 51 | description: 'Disables typing in the editor.' 52 | }, 53 | wrapLines: { 54 | type: 'boolean', 55 | acceptedValues: ['true', 'false'], 56 | defaultValue: 'false', 57 | description: 'Forces lines to wrap if they overflow the editor width.' 58 | } 59 | }, 60 | methods: { 61 | 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /example/src/documentation/v2.0.X/StatelessSandbox.js: -------------------------------------------------------------------------------- 1 | export default { 2 | props: { 3 | theme: { 4 | type: 'string', 5 | acceptedValues: [ 6 | 'monokai', 7 | 'twilight', 8 | 'solarized_dark', 9 | 'solarized_light', 10 | 'tomorrow', 11 | 'github' 12 | ], 13 | defaultValue: 'solarized_dark', 14 | description: 'Defines the sandbox color theme.' 15 | }, 16 | dependencies: { 17 | type: 'array', 18 | acceptedValues: ['urls'], 19 | defaultValue: '[]', 20 | description: 'Array of urls to be loaded via script tags prior to execution of editor code.' 21 | }, 22 | selectedTab: { 23 | type: 'string', 24 | acceptedValues: ['templateTab', 'scriptTab', 'stylesheetTab', 'resultTab'], 25 | defaultValue: 'templateTab', 26 | description: 'Sets the selected tab.' 27 | }, 28 | displayMode: { 29 | type: 'string', 30 | acceptedValues: ['tab', 'horizontal-split'], 31 | defaultValue: 'tab', 32 | description: 'Sets the display mode.' 33 | }, 34 | executeOnCodeChange: { 35 | type: 'boolean', 36 | acceptedValues: [ 37 | 'true', 'false' 38 | ], 39 | defaultValue: 'true', 40 | description: 'Execute code automatically when it changes.' 41 | }, 42 | executeOnCodeChangeDebounce: { 43 | type: 'int', 44 | acceptedValues: [ 45 | 'positive integer' 46 | ], 47 | defaultValue: '1000', 48 | description: 'Time to wait in milliseconds before automatically executing code. Only used when executeOnCodeChange is set to true.' 49 | }, 50 | permissions: { 51 | type: 'array', 52 | acceptedValues: [ 53 | 'allow-forms', 54 | 'allow-pointer-lock', 55 | 'allow-popups', 56 | 'allow-modals', 57 | 'allow-same-origin', 58 | 'allow-scripts', 59 | 'allow-top-navigation' 60 | ], 61 | defaultValue: `[ 62 | 'allow-forms', 63 | 'allow-pointer-lock', 64 | 'allow-popups', 65 | 'allow-modals', 66 | 'allow-same-origin', 67 | 'allow-scripts', 68 | 'allow-top-navigation' 69 | ]`, 70 | description: 'Permissions granted to the SandboxInterpreter.' 71 | }, 72 | onCodeChange: { 73 | type: 'function', 74 | acceptedValues: [`(editorName:string, editorValue:string) => {...}`], 75 | defaultValue: `no-op`, 76 | description: 'Invoked when code in any of the editors changes.' 77 | }, 78 | onTabClick: { 79 | type: 'function', 80 | acceptedValues: [`(event: SyntheticEvent, tabName:string) => {...}`], 81 | defaultValue: `no-op`, 82 | description: 'Invoked when a tab is clicked.' 83 | }, 84 | onPlayButtonClick: { 85 | type: 'function', 86 | acceptedValues: [`(event: SyntheticEvent) => {...}`], 87 | defaultValue: `no-op`, 88 | description: 'Invoked when the play button is clicked.' 89 | }, 90 | onDisplayModeButtonClick: { 91 | type: 'function', 92 | acceptedValues: [`(event: SyntheticEvent, requestedMode:string) => {...}`], 93 | defaultValue: `no-op`, 94 | description: 'Invoked when the display mode button is clicked.' 95 | }, 96 | hideDisplayModeButton: { 97 | type: 'boolean', 98 | acceptedValues: ['true', 'false'], 99 | defaultValue: 'false', 100 | description: 'Hides the button to toggle the display mode.' 101 | }, 102 | style: { 103 | type: 'object', 104 | acceptedValues: ['{...}'], 105 | defaultValue: '{}', 106 | description: 'Styles applied to root of sandbox.' 107 | }, 108 | classes: { 109 | type: 'object', 110 | acceptedValues: [`{header, 111 | selectedTabIndicator, 112 | tab, 113 | selectedTab, 114 | iconButton, 115 | Header, 116 | SelectedTabIndicator, 117 | Tab, 118 | SelectedTab, 119 | IconButton}` 120 | ], 121 | defaultValue: '{}', 122 | description: 'Overrides styles (may need to use !important to override some).' 123 | }, 124 | templateEditor: { 125 | type: 'object', 126 | acceptedValues: [ 127 | `{ 128 | defaultValue: string, 129 | mode: oneOf(['html']), 130 | readOnly: boolean, 131 | wrapLines: boolean 132 | }` 133 | ], 134 | defaultValue: `{ 135 | defaultValue: '', 136 | mode: 'html', 137 | readOnly: false, 138 | wrapLines: false 139 | }`, 140 | description: 'Template editor options.' 141 | }, 142 | scriptEditor: { 143 | type: 'object', 144 | acceptedValues: [ 145 | `{ 146 | defaultValue: string, 147 | mode: oneOf(['javascript', 'jsx']), 148 | readOnly: boolean, 149 | wrapLines: boolean 150 | }` 151 | ], 152 | defaultValue: `{ 153 | defaultValue: '', 154 | mode: 'javascript', 155 | readOnly: false, 156 | wrapLines: false 157 | }`, 158 | description: 'Script editor options.' 159 | }, 160 | stylesheetEditor: { 161 | type: 'object', 162 | acceptedValues: [ 163 | `{ 164 | defaultValue: string, 165 | mode: oneOf(['css']), 166 | readOnly: boolean, 167 | wrapLines: boolean 168 | }` 169 | ], 170 | defaultValue: `{ 171 | defaultValue: '', 172 | mode: 'css', 173 | readOnly: false, 174 | wrapLines: false 175 | }`, 176 | description: 'Stylesheet editor options.' 177 | }, 178 | preScript: { 179 | type: 'string', 180 | acceptedValues: [ 181 | 'any string' 182 | ], 183 | defaultValue: `''`, 184 | description: `Script run prior to script editor's code. Convenient for hiding boilerplate set-up. The script preprocessor will also be applied to the preScript.` 185 | }, 186 | postScript: { 187 | type: 'string', 188 | acceptedValues: [ 189 | 'any string' 190 | ], 191 | defaultValue: `''`, 192 | description: `Script run after script editor's code. Convenient for hiding boilerplate set-up. The script preprocessor will also be applied to the postScript.` 193 | }, 194 | onRef: { 195 | type: 'function', 196 | acceptedValues: [ 197 | '(component: StatelessSandbox) => {...}' 198 | ], 199 | defaultValue: `''`, 200 | description: `A way to get a component instance to call instance methods.` 201 | }, 202 | horizontalSplitOffset: { 203 | type: 'number', 204 | acceptedValues: [ 205 | '20 - 80' 206 | ], 207 | defaultValue: `50`, 208 | description: `Controls the offset of the split (as a percentage from the top) when the stateless sandbox is in horizontal-split mode. A no-op when displayMode="tab"` 209 | }, 210 | }, 211 | methods: { 212 | execute: { 213 | params: [], 214 | returnValue: '', 215 | description: 'Executes code' 216 | } 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /example/src/documentation/v2.0.X/StylesheetEditor.js: -------------------------------------------------------------------------------- 1 | export default { 2 | props: { 3 | theme: { 4 | type: 'string', 5 | acceptedValues: [ 6 | 'monokai', 7 | 'twilight', 8 | 'solarized_dark', 9 | 'solarized_light', 10 | 'tomorrow', 11 | 'github' 12 | ], 13 | defaultValue: 'solarized_dark', 14 | description: `Defines the editor's color theme.` 15 | }, 16 | onChange: { 17 | type: 'function', 18 | acceptedValues: [`(editorValue:string) => {...}`], 19 | defaultValue: `no-op`, 20 | description: 'Invoked when code in the editor changes.' 21 | }, 22 | style: { 23 | type: 'object', 24 | acceptedValues: ['{...}'], 25 | defaultValue: '{}', 26 | description: 'Styles applied to root of the editor.' 27 | }, 28 | classes: { 29 | type: 'object', 30 | acceptedValues: [`{root}` 31 | ], 32 | defaultValue: '{}', 33 | description: 'Overrides styles (may need to use !important to override some).' 34 | }, 35 | value: { 36 | type: 'string', 37 | acceptedValues: ['any string'], 38 | defaultValue: "''", 39 | description: 'Value displayed in the editor.' 40 | }, 41 | mode: { 42 | type: 'string', 43 | acceptedValues: ['css'], 44 | defaultValue: 'css', 45 | description: 'Syntax highlighting mode.' 46 | }, 47 | readOnly: { 48 | type: 'boolean', 49 | acceptedValues: ['true', 'false'], 50 | defaultValue: 'false', 51 | description: 'Disables typing in the editor.' 52 | }, 53 | wrapLines: { 54 | type: 'boolean', 55 | acceptedValues: ['true', 'false'], 56 | defaultValue: 'false', 57 | description: 'Forces lines to wrap if they overflow the editor width.' 58 | } 59 | }, 60 | methods: { 61 | 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /example/src/documentation/v2.0.X/TemplateEditor.js: -------------------------------------------------------------------------------- 1 | export default { 2 | props: { 3 | theme: { 4 | type: 'string', 5 | acceptedValues: [ 6 | 'monokai', 7 | 'twilight', 8 | 'solarized_dark', 9 | 'solarized_light', 10 | 'tomorrow', 11 | 'github' 12 | ], 13 | defaultValue: 'solarized_dark', 14 | description: `Defines the editor's color theme.` 15 | }, 16 | onChange: { 17 | type: 'function', 18 | acceptedValues: [`(editorValue:string) => {...}`], 19 | defaultValue: `no-op`, 20 | description: 'Invoked when code in the editor changes.' 21 | }, 22 | style: { 23 | type: 'object', 24 | acceptedValues: ['{...}'], 25 | defaultValue: '{}', 26 | description: 'Styles applied to root of the editor.' 27 | }, 28 | classes: { 29 | type: 'object', 30 | acceptedValues: [`{root}` 31 | ], 32 | defaultValue: '{}', 33 | description: 'Overrides styles (may need to use !important to override some).' 34 | }, 35 | value: { 36 | type: 'string', 37 | acceptedValues: ['any string'], 38 | defaultValue: "''", 39 | description: 'Value displayed in the editor.' 40 | }, 41 | mode: { 42 | type: 'string', 43 | acceptedValues: ['html'], 44 | defaultValue: 'html', 45 | description: 'Syntax highlighting mode.' 46 | }, 47 | readOnly: { 48 | type: 'boolean', 49 | acceptedValues: ['true', 'false'], 50 | defaultValue: 'false', 51 | description: 'Disables typing in the editor.' 52 | }, 53 | wrapLines: { 54 | type: 'boolean', 55 | acceptedValues: ['true', 'false'], 56 | defaultValue: 'false', 57 | description: 'Forces lines to wrap if they overflow the editor width.' 58 | } 59 | }, 60 | methods: { 61 | 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /example/src/documentation/v2.0.X/index.js: -------------------------------------------------------------------------------- 1 | import Sandbox from './Sandbox' 2 | import StatelessSandbox from './StatelessSandbox' 3 | import TemplateEditor from './TemplateEditor' 4 | import ScriptEditor from './ScriptEditor' 5 | import StylesheetEditor from './StylesheetEditor' 6 | import SandboxInterpreter from './SandboxInterpreter' 7 | import withDependencies from './withDependencies' 8 | 9 | 10 | export default { 11 | "components": { 12 | Sandbox, 13 | StatelessSandbox, 14 | TemplateEditor, 15 | ScriptEditor, 16 | StylesheetEditor, 17 | SandboxInterpreter 18 | }, 19 | "hoc": { 20 | withDependencies 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /example/src/documentation/v2.0.X/withDependencies.js: -------------------------------------------------------------------------------- 1 | export default { 2 | params: ['dependencies: array'], 3 | returnValue: '(component: React.Component) => {} : React.Component', 4 | description: 'Takes a list of dependencies and returns a component decorator. Can be applied to any component that accepts a dependencies prop.' 5 | } 6 | -------------------------------------------------------------------------------- /example/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: 'Quicksand', sans-serif; 5 | } 6 | 7 | .content-layout { 8 | display:flex; 9 | justify-content: center; 10 | } 11 | 12 | .sandbox-container { 13 | width: 100% !important; 14 | height: 420px !important; 15 | margin-top: 64px; 16 | box-shadow: 0 14px 28px rgba(0,0,0,0.25), 0 10px 10px rgba(0,0,0,0.22); 17 | border-radius: 4px; 18 | overflow: hidden; 19 | } 20 | 21 | @media only screen and (max-width: 975px) { 22 | .content-layout { 23 | display:flex; 24 | flex-direction: column; 25 | justify-content: center; 26 | } 27 | .small-screen-hide { 28 | display: none; 29 | } 30 | .sandbox-container { 31 | margin-top: 48px; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /example/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | 4 | import './index.css' 5 | import App from './App' 6 | 7 | ReactDOM.render(, document.getElementById('root')) 8 | -------------------------------------------------------------------------------- /example/src/utils.js: -------------------------------------------------------------------------------- 1 | import reactElementToJSXString from 'react-element-to-jsx-string'; 2 | 3 | function getReactSandboxUsage(component) { 4 | let componentString = reactElementToJSXString(component, { 5 | displayName: (component) => 'ReactSandbox', 6 | filterProps: ['onCodeChange'], 7 | tabStop: 2 8 | }) 9 | return ( 10 | `import {Sandbox, withDependencies} from 'react-sandbox-editor' 11 | 12 | const ReactSandbox = withDependencies([ 13 | 'https://unpkg.com/react@16.6.0/umd/react.development.js', 14 | 'https://unpkg.com/react-dom@16.6.0/umd/react-dom.development.js' 15 | ])(Sandbox) 16 | 17 | ${componentString}` 18 | ) 19 | } 20 | 21 | export { 22 | getReactSandboxUsage 23 | } 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-sandbox-editor", 3 | "version": "1.0.3", 4 | "description": "React component library for sandboxed execution of html/js/css", 5 | "author": "malerba118", 6 | "license": "MIT", 7 | "repository": "malerba118/react-sandbox-editor", 8 | "keywords": [ 9 | "react", 10 | "sandbox", 11 | "sandboxed", 12 | "playground", 13 | "ace", 14 | "editor", 15 | "component", 16 | "jsfiddle", 17 | "codepen", 18 | "codesandbox" 19 | ], 20 | "main": "dist/index.js", 21 | "module": "dist/index.es.js", 22 | "unpkg": "dist/index.umd.js", 23 | "jsnext:main": "dist/index.es.js", 24 | "scripts": { 25 | "test": "cross-env CI=1 react-scripts test --env=jsdom", 26 | "test:watch": "react-scripts test --env=jsdom", 27 | "build": "rollup -c", 28 | "start": "rollup -c -w", 29 | "prepare": "npm run build", 30 | "predeploy": "cd example && npm install && npm run build", 31 | "deploy": "gh-pages -d example/build" 32 | }, 33 | "dependencies": { 34 | "@babel/standalone": "^7.0.0-beta.51", 35 | "@material-ui/core": "^1.3.0", 36 | "classnames": "^2.2.6", 37 | "debounce": "^1.1.0", 38 | "react-ace": "^6.1.2", 39 | "react-resize-detector": "^3.0.1" 40 | }, 41 | "peerDependencies": { 42 | "prop-types": "^15.5.4", 43 | "react": ">= 16.3.0", 44 | "react-dom": ">= 16.3.0" 45 | }, 46 | "devDependencies": { 47 | "babel-eslint": "^8.2.1", 48 | "babel-plugin-external-helpers": "^6.22.0", 49 | "babel-preset-env": "^1.6.0", 50 | "babel-preset-stage-0": "^6.24.1", 51 | "cross-env": "^5.1.4", 52 | "eslint": "^4.19.1", 53 | "eslint-config-standard": "^11.0.0", 54 | "eslint-config-standard-react": "^6.0.0", 55 | "eslint-plugin-import": "^2.11.0", 56 | "eslint-plugin-node": "^6.0.1", 57 | "eslint-plugin-promise": "^3.7.0", 58 | "eslint-plugin-react": "^7.7.0", 59 | "eslint-plugin-standard": "^3.0.1", 60 | "gh-pages": "^1.2.0", 61 | "react": "^16.2.0", 62 | "react-dom": "^16.2.0", 63 | "react-scripts": "^1.1.1", 64 | "rollup": "^0.54.0", 65 | "rollup-plugin-analyzer": "^2.1.0", 66 | "rollup-plugin-babel": "^3.0.3", 67 | "rollup-plugin-commonjs": "^8.2.1", 68 | "rollup-plugin-node-resolve": "^3.0.2", 69 | "rollup-plugin-peer-deps-external": "^2.0.0", 70 | "rollup-plugin-postcss": "^1.1.0", 71 | "rollup-plugin-replace": "^2.0.0", 72 | "rollup-plugin-url": "^1.3.0" 73 | }, 74 | "files": [ 75 | "dist" 76 | ] 77 | } 78 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import babel from 'rollup-plugin-babel' 2 | import commonjs from 'rollup-plugin-commonjs' 3 | import external from 'rollup-plugin-peer-deps-external' 4 | import postcss from 'rollup-plugin-postcss' 5 | import resolve from 'rollup-plugin-node-resolve' 6 | import url from 'rollup-plugin-url' 7 | import replace from 'rollup-plugin-replace'; 8 | import pkg from './package.json' 9 | import { plugin as analyze } from 'rollup-plugin-analyzer' 10 | 11 | export default { 12 | external: ['react', 'react-dom', 'prop-types'], 13 | globals: { 14 | 'react': 'React', 15 | 'react-dom': 'ReactDOM', 16 | 'prop-types': 'PropTypes' 17 | }, 18 | input: 'src/index.js', 19 | output: [ 20 | { 21 | file: pkg.main, 22 | format: 'cjs' 23 | }, 24 | { 25 | file: pkg.module, 26 | format: 'es' 27 | }, 28 | { 29 | file: pkg.unpkg, 30 | format: 'umd', 31 | name: 'react-sandbox-editor' 32 | } 33 | ], 34 | plugins: [ 35 | external(), 36 | postcss({ 37 | modules: true 38 | }), 39 | url(), 40 | babel({ 41 | exclude: 'node_modules/**' 42 | }), 43 | resolve(), 44 | commonjs(), 45 | replace({ 46 | 'process.env.NODE_ENV': JSON.stringify( 'production' ) 47 | }), 48 | analyze() 49 | ] 50 | } 51 | -------------------------------------------------------------------------------- /src/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | }, 5 | "globals": { 6 | "expect": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { SandboxInterpreter } from './lib/SandboxInterpreter' 2 | import { StatelessSandbox } from './lib/StatelessSandbox' 3 | import { Sandbox } from './lib/Sandbox' 4 | import { withDependencies } from './lib/utils' 5 | import { ScriptEditor, StylesheetEditor, TemplateEditor } from './lib/editors' 6 | 7 | import styles from './styles.css' 8 | 9 | export { 10 | Sandbox, 11 | StatelessSandbox, 12 | SandboxInterpreter, 13 | ScriptEditor, 14 | StylesheetEditor, 15 | TemplateEditor, 16 | withDependencies, 17 | } 18 | -------------------------------------------------------------------------------- /src/lib/Sandbox.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import PropTypes from 'prop-types'; 4 | import Tabs from '@material-ui/core/Tabs'; 5 | import Tab from '@material-ui/core/Tab'; 6 | import Typography from '@material-ui/core/Typography'; 7 | import withStyles from '@material-ui/core/styles/withStyles'; 8 | import classNames from 'classnames'; 9 | import {StatelessSandbox} from './StatelessSandbox'; 10 | import JssProvider from 'react-jss/lib/JssProvider'; 11 | import createGenerateClassName from '@material-ui/core/styles/createGenerateClassName'; 12 | 13 | const generateClassName = createGenerateClassName({ 14 | productionPrefix: 'react-sandbox-editor', 15 | }); 16 | 17 | 18 | class Sandbox extends React.Component { 19 | state = { 20 | selectedTab: 'templateTab', 21 | displayMode: 'tab', 22 | template: { 23 | value: '', 24 | defaultValue: '' 25 | }, 26 | script: { 27 | value: '', 28 | defaultValue: '' 29 | }, 30 | stylesheet: { 31 | value: '', 32 | defaultValue: '' 33 | } 34 | }; 35 | 36 | componentDidMount() { 37 | this.props.onRef(this) 38 | } 39 | 40 | componentWillUnmount() { 41 | this.props.onRef(null) 42 | } 43 | 44 | static getDerivedStateFromProps(nextProps, prevState) { 45 | //if default values have changed update editor with new default 46 | let nextState = {...prevState} 47 | if ( 48 | nextProps.templateEditor.defaultValue !== prevState.template.defaultValue 49 | ) { 50 | nextState.template = { 51 | value: nextProps.templateEditor.defaultValue, 52 | defaultValue: nextProps.templateEditor.defaultValue 53 | } 54 | } 55 | if ( 56 | nextProps.scriptEditor.defaultValue !== prevState.script.defaultValue 57 | ) { 58 | nextState.script = { 59 | value: nextProps.scriptEditor.defaultValue, 60 | defaultValue: nextProps.scriptEditor.defaultValue 61 | } 62 | } 63 | if ( 64 | nextProps.stylesheetEditor.defaultValue !== prevState.stylesheet.defaultValue 65 | ) { 66 | nextState.script = { 67 | value: nextProps.stylesheetEditor.defaultValue, 68 | defaultValue: nextProps.stylesheetEditor.defaultValue 69 | } 70 | } 71 | return nextState 72 | } 73 | 74 | onTabClick = (event, value) => { 75 | this.setState({ selectedTab: value }) 76 | this.props.onTabClick(event, value) 77 | }; 78 | 79 | componentDidUpdate(prevProps, prevState) { 80 | if ( 81 | prevState.displayMode !== this.state.displayMode && 82 | this.state.displayMode === 'horizontal-split' && 83 | this.state.selectedTab === 'resultTab' 84 | ) { 85 | //when switching to split view mode the tab goes 86 | //away so we need to switch tabs 87 | this.setState({ selectedTab: 'stylesheetTab' }); 88 | } 89 | } 90 | 91 | onPlayButtonClick = (event) => { 92 | this.execute() 93 | this.props.onPlayButtonClick(event) 94 | } 95 | 96 | onDisplayModeButtonClick = (event, requestedMode) => { 97 | this.setState({displayMode: requestedMode}) 98 | this.props.onDisplayModeButtonClick(event, requestedMode) 99 | } 100 | 101 | execute = () => { 102 | if (this.statelessSandboxRef) { 103 | this.statelessSandboxRef.execute() 104 | } 105 | } 106 | 107 | onCodeChange = (editorName, value) => { 108 | this.setState((prevState) => { 109 | return { 110 | [editorName]: { 111 | ...prevState[editorName], 112 | value, 113 | } 114 | } 115 | }) 116 | this.props.onCodeChange(editorName, value) 117 | } 118 | 119 | render() { 120 | const { classes } = this.props; 121 | //props will override default behavior provided by state 122 | const displayMode = this.props.displayMode || this.state.displayMode 123 | const selectedTab = this.props.selectedTab || this.state.selectedTab 124 | return ( 125 | 126 | {this.statelessSandboxRef = ref}} 128 | classes={this.props.classes} 129 | style={this.props.style} 130 | onCodeChange={this.onCodeChange} 131 | executeOnCodeChange={this.props.executeOnCodeChange} 132 | executeOnCodeChangeDebounce={this.props.executeOnCodeChangeDebounce} 133 | onTabClick={this.onTabClick} 134 | selectedTab={selectedTab} 135 | onPlayButtonClick={this.onPlayButtonClick} 136 | onDisplayModeButtonClick={this.onDisplayModeButtonClick} 137 | displayMode={displayMode} 138 | theme={this.props.theme} 139 | permissions={this.props.permissions} 140 | dependencies={this.props.dependencies} 141 | hideDisplayModeButton={this.props.hideDisplayModeButton} 142 | horizontalSplitOffset={this.props.horizontalSplitOffset} 143 | preScript={this.props.preScript} 144 | postScript={this.props.postScript} 145 | templateEditor={{ 146 | ...this.props.templateEditor, 147 | value: this.state.template.value, 148 | }} 149 | scriptEditor={{ 150 | ...this.props.scriptEditor, 151 | value: this.state.script.value, 152 | }} 153 | stylesheetEditor={{ 154 | ...this.props.stylesheetEditor, 155 | value: this.state.stylesheet.value, 156 | }} 157 | /> 158 | 159 | ); 160 | } 161 | } 162 | 163 | Sandbox.defaultProps = { 164 | onCodeChange: () => {}, 165 | onTabClick: () => {}, 166 | onPlayButtonClick: () => {}, 167 | onDisplayModeButtonClick: () => {}, 168 | theme: 'solarized_dark', 169 | executeOnCodeChangeDebounce: 1000, 170 | executeOnCodeChange: true, 171 | permissions: [ 172 | 'allow-forms', 173 | 'allow-pointer-lock', 174 | 'allow-popups', 175 | 'allow-modals', 176 | 'allow-same-origin', 177 | 'allow-scripts', 178 | 'allow-top-navigation' 179 | ], 180 | preScript: '', 181 | postScript: '', 182 | templateEditor: { 183 | defaultValue: '', 184 | mode: 'html', 185 | readOnly: false, 186 | wrapLines: false, 187 | }, 188 | scriptEditor: { 189 | defaultValue: '', 190 | mode: 'javascript', 191 | readOnly: false, 192 | wrapLines: false, 193 | }, 194 | stylesheetEditor: { 195 | defaultValue: '', 196 | mode: 'css', 197 | readOnly: false, 198 | wrapLines: false, 199 | }, 200 | dependencies: [], 201 | horizontalSplitOffset: 50, 202 | onRef: () => {}, 203 | } 204 | 205 | Sandbox.propTypes = { 206 | permissions: PropTypes.arrayOf( 207 | PropTypes.oneOf([ 208 | 'allow-forms', 209 | 'allow-pointer-lock', 210 | 'allow-popups', 211 | 'allow-modals', 212 | 'allow-same-origin', 213 | 'allow-scripts', 214 | 'allow-top-navigation' 215 | ]) 216 | ), 217 | preScript: PropTypes.string, 218 | postScript: PropTypes.string, 219 | templateEditor: PropTypes.shape({ 220 | defaultValue: PropTypes.string, 221 | mode: PropTypes.oneOf(['html']), 222 | readOnly: PropTypes.bool, 223 | wrapLines: PropTypes.bool, 224 | }), 225 | scriptEditor: PropTypes.shape({ 226 | defaultValue: PropTypes.string, 227 | mode: PropTypes.oneOf(['javascript', 'jsx']), 228 | readOnly: PropTypes.bool, 229 | wrapLines: PropTypes.bool, 230 | }), 231 | stylesheetEditor: PropTypes.shape({ 232 | defaultValue: PropTypes.string, 233 | mode: PropTypes.oneOf(['css']), 234 | readOnly: PropTypes.bool, 235 | wrapLines: PropTypes.bool, 236 | }), 237 | onCodeChange: PropTypes.func, 238 | onTabClick: PropTypes.func, 239 | onPlayButtonClick: PropTypes.func, 240 | onDisplayModeButtonClick: PropTypes.func, 241 | theme: PropTypes.oneOf([ 242 | 'solarized_dark', 243 | 'solarized_light', 244 | 'twilight', 245 | 'tomorrow', 246 | 'github', 247 | 'monokai', 248 | ]), 249 | executeOnCodeChangeDebounce: PropTypes.number, 250 | executeOnCodeChange: PropTypes.bool, 251 | hideDisplayModeButton: PropTypes.bool, 252 | selectedTab: PropTypes.oneOf(['templateTab', 'scriptTab', 'stylesheetTab', 'resultTab']), 253 | displayMode: PropTypes.oneOf(['tab', 'horizontal-split']), 254 | dependencies: PropTypes.arrayOf(PropTypes.string), 255 | horizontalSplitOffset: PropTypes.number, 256 | onRef: PropTypes.func, 257 | } 258 | 259 | export {Sandbox} 260 | -------------------------------------------------------------------------------- /src/lib/SandboxInterpreter.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import PropTypes from 'prop-types'; 4 | import processors from './processors' 5 | 6 | export class SandboxInterpreter extends React.Component { 7 | 8 | constructor(props) { 9 | super(props) 10 | this.iframeContainerRef = null 11 | } 12 | 13 | buildDependencies = () => { 14 | return this.props.dependencies.map((dependency) => { 15 | return `` 16 | }) 17 | .join('\n') 18 | } 19 | 20 | buildStylesheet = () => { 21 | let stylesheet = '' 22 | let stylesheetProcessor = processors.getStylesheetProcessor(this.props.stylesheetMode) 23 | try { 24 | stylesheet = stylesheetProcessor(this.props.stylesheet) 25 | } 26 | catch (e){ 27 | console.error(e) 28 | } 29 | return (``) 30 | } 31 | 32 | buildHead = () => { 33 | return ( 34 | ` 35 | ${this.buildStylesheet()} 36 | ` 37 | ) 38 | } 39 | 40 | buildPreScript = () => { 41 | let preScript = '' 42 | let scriptProcessor = processors.getScriptProcessor(this.props.scriptMode) 43 | try { 44 | preScript = scriptProcessor(this.props.preScript) 45 | } 46 | catch (e){ 47 | console.error(e) 48 | } 49 | return (``) 50 | } 51 | 52 | buildScript = () => { 53 | let script = '' 54 | let scriptProcessor = processors.getScriptProcessor(this.props.scriptMode) 55 | try { 56 | script = scriptProcessor(this.props.script) 57 | } 58 | catch (e){ 59 | console.error(e) 60 | } 61 | return (``) 62 | } 63 | 64 | buildPostScript = () => { 65 | let postScript = '' 66 | let scriptProcessor = processors.getScriptProcessor(this.props.scriptMode) 67 | try { 68 | postScript = scriptProcessor(this.props.postScript) 69 | } 70 | catch (e){ 71 | console.error(e) 72 | } 73 | return (``) 74 | } 75 | 76 | buildTemplate = () => { 77 | let template = '' 78 | let templateProcessor = processors.getTemplateProcessor(this.props.templateMode) 79 | try { 80 | template = templateProcessor(this.props.template) 81 | } 82 | catch (e){ 83 | console.error(e) 84 | } 85 | return template 86 | } 87 | 88 | buildBody = () => { 89 | return ( 90 | ` 91 | ${this.buildTemplate()} 92 | ${this.buildDependencies()} 93 | ${this.buildPreScript()} 94 | ${this.buildScript()} 95 | ${this.buildPostScript()} 96 | ` 97 | ) 98 | } 99 | 100 | buildContents = () => { 101 | return ( 102 | ` 103 | ${this.buildHead()} 104 | ${this.buildBody()} 105 | ` 106 | ) 107 | } 108 | 109 | componentDidMount() { 110 | this.props.onRef(this) 111 | this.execute() 112 | } 113 | 114 | componentWillUnmount() { 115 | this.props.onRef(undefined) 116 | } 117 | 118 | componentDidUpdate(prevProps, prevState) { 119 | if ( 120 | prevProps.preScript !== this.props.preScript || 121 | prevProps.script !== this.props.script || 122 | prevProps.postScript !== this.props.postScript || 123 | prevProps.template !== this.props.template || 124 | prevProps.stylesheet !== this.props.stylesheet 125 | ) { 126 | this.execute() 127 | } 128 | } 129 | 130 | execute() { 131 | //remove all children 132 | while (this.iframeContainerRef.hasChildNodes()) { 133 | this.iframeContainerRef.removeChild(this.iframeContainerRef.lastChild); 134 | } 135 | //create new iframe 136 | let iframe = document.createElement('iframe'); 137 | iframe.height="100%" 138 | iframe.width="100%" 139 | iframe.sandbox=this.props.permissions.join(' ') 140 | iframe.style.border="none" 141 | try { 142 | iframe.srcdoc=this.buildContents() 143 | } 144 | catch (e){ 145 | console.error(e) 146 | } 147 | //insert it into dom 148 | this.iframeContainerRef.appendChild(iframe); 149 | } 150 | 151 | render() { 152 | return ( 153 |
{this.iframeContainerRef = element}} 156 | style={{ 157 | height: '100%', 158 | width: '100%', 159 | ...this.props.style, 160 | background: 'white' 161 | }}> 162 |
163 | ) 164 | } 165 | } 166 | 167 | SandboxInterpreter.defaultProps = { 168 | permissions: [ 169 | 'allow-forms', 170 | 'allow-pointer-lock', 171 | 'allow-popups', 172 | 'allow-modals', 173 | 'allow-same-origin', 174 | 'allow-scripts', 175 | 'allow-top-navigation' 176 | ], 177 | dependencies: [], 178 | preScript: '', 179 | script: '', 180 | postScript: '', 181 | scriptMode: 'javascript', 182 | template: '', 183 | templateMode: 'html', 184 | stylesheet: '', 185 | stylesheetMode: 'css', 186 | onRef: () => {} 187 | } 188 | 189 | SandboxInterpreter.propTypes = { 190 | permissions: PropTypes.arrayOf( 191 | PropTypes.oneOf([ 192 | 'allow-forms', 193 | 'allow-pointer-lock', 194 | 'allow-popups', 195 | 'allow-modals', 196 | 'allow-same-origin', 197 | 'allow-scripts', 198 | 'allow-top-navigation' 199 | ]) 200 | ), 201 | dependencies: PropTypes.arrayOf(PropTypes.string), 202 | preScript: PropTypes.string, 203 | script: PropTypes.string, 204 | postScript: PropTypes.string, 205 | scriptMode: PropTypes.oneOf(['javascript', 'jsx']), 206 | template: PropTypes.string, 207 | templateMode: PropTypes.oneOf(['html']), 208 | stylesheet: PropTypes.string, 209 | stylesheetMode: PropTypes.oneOf(['css']), 210 | onRef: PropTypes.func 211 | } 212 | -------------------------------------------------------------------------------- /src/lib/StatelessSandbox.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import PropTypes from 'prop-types'; 4 | import Tabs from '@material-ui/core/Tabs'; 5 | import Tab from '@material-ui/core/Tab'; 6 | import Typography from '@material-ui/core/Typography'; 7 | import IconButton from '@material-ui/core/IconButton'; 8 | import PlayCircleOutline from '../media/PlayButtonIcon'; 9 | import TabDisplayIcon from '../media/TabsViewIcon'; 10 | import SplitScreenDisplayIcon from '../media/SplitViewIcon'; 11 | import withStyles from '@material-ui/core/styles/withStyles'; 12 | import classNames from 'classnames'; 13 | import {ScriptEditor, TemplateEditor, StylesheetEditor} from './editors'; 14 | import {SandboxInterpreter} from './SandboxInterpreter'; 15 | import debounce from 'debounce'; 16 | import themeStyles from './StatelessSandboxThemes'; 17 | 18 | const styles = theme => ({ 19 | ...themeStyles, 20 | root: { 21 | display: 'flex', 22 | flexDirection: 'column', 23 | width: '100%', 24 | height: '100%', 25 | minWidth: 320, 26 | minHeight: 300, 27 | }, 28 | _center: { 29 | display:'flex', 30 | alignItems: 'center', 31 | justifyContent: 'center' 32 | }, 33 | _fill: { 34 | flex: 1 35 | }, 36 | _tabsContent: { 37 | flex: 1, 38 | position: 'relative' 39 | }, 40 | _interpreter: { 41 | position: 'absolute', 42 | transition: 'all .45s', 43 | zIndex: 0, 44 | }, 45 | _editor: { 46 | position: 'absolute', 47 | top: 0, 48 | transition: 'height .45s', 49 | } 50 | }); 51 | 52 | function getHorizontalSplit(horizontalSplitOffset) { 53 | let offset = horizontalSplitOffset 54 | if (offset > 80) { 55 | offset = 80 56 | } 57 | if (offset < 20) { 58 | offset = 20 59 | } 60 | return [ offset, 100 - offset ] 61 | } 62 | 63 | //Tabs was trying to pass props to divs and causing warnings 64 | //this is to prevent those warnings by discarding those props 65 | const DummyTab = (props) => ( 66 |
67 | {props.children} 68 |
69 | ) 70 | 71 | class StatelessSandbox extends React.Component { 72 | 73 | //oopsies, not quite stateless 74 | state = { 75 | interpreter: { 76 | script: '', 77 | template: '', 78 | stylesheet: '' 79 | }, 80 | displayModeTranistionPending: false 81 | }; 82 | 83 | tabNames = ['templateTab', 'scriptTab', 'stylesheetTab', 'resultTab'] 84 | 85 | onTabClick = (event, index) => { 86 | this.props.onTabClick(event, this.tabNames[index]) 87 | }; 88 | 89 | componentDidMount() { 90 | this.props.onRef(this) 91 | // run the interpreter on the initial props 92 | this.updateInterpreter() 93 | // set the initial auto refresh debounce time 94 | this.requestInterpreterUpdate = debounce( 95 | this.updateInterpreter, 96 | this.props.executeOnCodeChangeDebounce 97 | ) 98 | } 99 | 100 | componentWillUnmount() { 101 | this.props.onRef(undefined) 102 | } 103 | 104 | componentDidUpdate(prevProps, prevState) { 105 | if ( 106 | prevProps.displayMode !== this.props.displayMode && 107 | this.props.displayMode === 'horizontal-split' && 108 | prevProps.selectedTab === 'resultTab' 109 | ) { 110 | //when switching display modes to horizontal split mode while 111 | //the result tab is selected, we want to wait for the transition 112 | //before bringing the stylesheet editor to the front 113 | this.setState({displayModeTranistionPending: true}) 114 | setTimeout(() => { 115 | this.setState({displayModeTranistionPending: false}) 116 | }, 450) 117 | } 118 | if (this.props.executeOnCodeChangeDebounce !== prevProps.executeOnCodeChangeDebounce) { 119 | // if auto refresh time has changed, change the debounce time 120 | this.requestInterpreterUpdate = debounce( 121 | this.updateInterpreter, 122 | this.props.executeOnCodeChangeDebounce 123 | ) 124 | } 125 | if ( 126 | (this.props.executeOnCodeChange) && 127 | (this.props.preScript !== prevProps.preScript || 128 | this.props.postScript !== prevProps.postScript || 129 | this.props.scriptEditor.value !== prevProps.scriptEditor.value || 130 | this.props.templateEditor.value !== prevProps.templateEditor.value || 131 | this.props.stylesheetEditor.value !== prevProps.stylesheetEditor.value) 132 | ) { 133 | this.requestInterpreterUpdate() 134 | } 135 | } 136 | 137 | execute() { 138 | //first just see if interpreter needs it's props udpated 139 | let updated = this.updateInterpreter() 140 | if (!updated) { 141 | //if not, call execute on it 142 | if (this.interpreterRef) { 143 | this.interpreterRef.execute() 144 | } 145 | } 146 | } 147 | 148 | //return whether updated or not 149 | updateInterpreter = () => { 150 | const {templateEditor, scriptEditor, stylesheetEditor} = this.props 151 | let interpreter = this.state.interpreter 152 | if ( 153 | scriptEditor.value !== interpreter.script || 154 | templateEditor.value !== interpreter.template || 155 | stylesheetEditor.value !== interpreter.stylesheet 156 | ) { 157 | this.setState((prevState) => { 158 | return { 159 | interpreter: { 160 | script: scriptEditor.value, 161 | template: templateEditor.value, 162 | stylesheet: stylesheetEditor.value 163 | } 164 | } 165 | }) 166 | return true 167 | } 168 | return false 169 | } 170 | 171 | render() { 172 | const { classes, theme } = this.props; 173 | const selectedTabName = this.props.selectedTab; 174 | const horizontalSplit = getHorizontalSplit(this.props.horizontalSplitOffset) 175 | const tabsClasses = { 176 | root: classNames( 177 | classes[`${theme}Header`], 178 | classes.header, 179 | ), 180 | indicator: classNames( 181 | classes[`${theme}SelectedTabIndicator`], 182 | classes.selectedTabIndicator 183 | ) 184 | } 185 | const tabClasses = { 186 | root: classNames( 187 | classes[`${theme}Tab`], 188 | classes.tab 189 | ), 190 | selected: classNames( 191 | classes[`${theme}SelectedTab`], 192 | classes.selectedTab 193 | ) 194 | } 195 | const iconButtonClasses = classNames( 196 | classes._center, 197 | classes[`${theme}IconButton`], 198 | classes.iconButton 199 | ) 200 | return ( 201 |
202 | 207 | 212 | 217 | 222 | {this.props.displayMode === 'tab' && ( 223 | 228 | )} 229 | 230 | 231 | {this.props.displayMode === 'horizontal-split' && ( 232 | this.props.onDisplayModeButtonClick(event, 'tab')} 238 | > 239 | 240 | {this.props.displayMode === 'tab' && } 241 | 242 | )} 243 | {this.props.displayMode === 'tab' && ( 244 | this.props.onDisplayModeButtonClick(event, 'horizontal-split')} 250 | > 251 | 252 | 253 | )} 254 | 255 | 256 | 262 | 263 | 264 | 265 | 266 |
267 | 268 | this.props.onCodeChange('template', value)} 275 | value={this.props.templateEditor.value} 276 | readOnly={this.props.templateEditor.readOnly} 277 | wrapLines={this.props.templateEditor.wrapLines} 278 | theme={this.props.theme} 279 | /> 280 | this.props.onCodeChange('script', value)} 287 | value={this.props.scriptEditor.value} 288 | readOnly={this.props.scriptEditor.readOnly} 289 | wrapLines={this.props.scriptEditor.wrapLines} 290 | mode={this.props.scriptEditor.mode} 291 | theme={this.props.theme} 292 | /> 293 | this.props.onCodeChange('stylesheet', value)} 300 | value={this.props.stylesheetEditor.value} 301 | readOnly={this.props.stylesheetEditor.readOnly} 302 | wrapLines={this.props.stylesheetEditor.wrapLines} 303 | theme={this.props.theme} 304 | /> 305 | {this.interpreterRef = ref}} 307 | className={classes._interpreter} 308 | style={{ 309 | height: this.props.displayMode === 'horizontal-split' ? `${horizontalSplit[1]}%` : '100%', 310 | top: this.props.displayMode === 'horizontal-split' ? `${horizontalSplit[0]}%` : 0, 311 | }} 312 | permissions={this.props.permissions} 313 | dependencies={this.props.dependencies} 314 | preScript={this.props.preScript} 315 | script={this.state.interpreter.script} 316 | postScript={this.props.postScript} 317 | scriptMode={this.props.scriptEditor.mode} 318 | template={this.state.interpreter.template} 319 | templateMode={this.props.templateEditor.mode} 320 | stylesheet={this.state.interpreter.stylesheet} 321 | stylesheetMode={this.props.stylesheetEditor.mode} 322 | /> 323 |
324 |
325 | ); 326 | } 327 | } 328 | 329 | //HOC 330 | StatelessSandbox = withStyles(styles)(StatelessSandbox) 331 | 332 | StatelessSandbox.defaultProps = { 333 | theme: 'solarized_dark', 334 | selectedTab: 'templateTab', 335 | executeOnCodeChangeDebounce: 1000, 336 | executeOnCodeChange: true, 337 | displayMode: 'tab', 338 | onTabClick: () => {}, 339 | onPlayButtonClick: () => {}, 340 | onDisplayModeButtonClick: () => {}, 341 | permissions: [ 342 | 'allow-forms', 343 | 'allow-pointer-lock', 344 | 'allow-popups', 345 | 'allow-modals', 346 | 'allow-same-origin', 347 | 'allow-scripts', 348 | 'allow-top-navigation' 349 | ], 350 | preScript: '', 351 | postScript: '', 352 | templateEditor: { 353 | value: '', 354 | mode: 'html', 355 | readOnly: false, 356 | wrapLines: false, 357 | }, 358 | scriptEditor: { 359 | value: '', 360 | mode: 'javascript', 361 | readOnly: false, 362 | wrapLines: false, 363 | }, 364 | stylesheetEditor: { 365 | value: '', 366 | mode: 'css', 367 | readOnly: false, 368 | wrapLines: false, 369 | }, 370 | dependencies: [], 371 | horizontalSplitOffset: 50, 372 | onRef: () => {}, 373 | }; 374 | 375 | StatelessSandbox.propTypes = { 376 | permissions: PropTypes.arrayOf( 377 | PropTypes.oneOf([ 378 | 'allow-forms', 379 | 'allow-pointer-lock', 380 | 'allow-popups', 381 | 'allow-modals', 382 | 'allow-same-origin', 383 | 'allow-scripts', 384 | 'allow-top-navigation' 385 | ]) 386 | ), 387 | preScript: PropTypes.string, 388 | postScript: PropTypes.string, 389 | templateEditor: PropTypes.shape({ 390 | value: PropTypes.string, 391 | mode: PropTypes.oneOf(['html']), 392 | readOnly: PropTypes.bool, 393 | wrapLines: PropTypes.bool, 394 | }), 395 | scriptEditor: PropTypes.shape({ 396 | value: PropTypes.string, 397 | mode: PropTypes.oneOf(['javascript', 'jsx']), 398 | readOnly: PropTypes.bool, 399 | wrapLines: PropTypes.bool, 400 | }), 401 | stylesheetEditor: PropTypes.shape({ 402 | value: PropTypes.string, 403 | mode: PropTypes.oneOf(['css']), 404 | readOnly: PropTypes.bool, 405 | wrapLines: PropTypes.bool, 406 | }), 407 | onCodeChange: PropTypes.func, 408 | onTabClick: PropTypes.func, 409 | onPlayButtonClick: PropTypes.func, 410 | onDisplayModeButtonClick: PropTypes.func, 411 | theme: PropTypes.oneOf([ 412 | 'solarized_dark', 413 | 'solarized_light', 414 | 'twilight', 415 | 'tomorrow', 416 | 'github', 417 | 'monokai', 418 | ]), 419 | executeOnCodeChangeDebounce: PropTypes.number, 420 | executeOnCodeChange: PropTypes.bool, 421 | hideDisplayModeButton: PropTypes.bool, 422 | selectedTab: PropTypes.oneOf(['templateTab', 'scriptTab', 'stylesheetTab', 'resultTab']), 423 | displayMode: PropTypes.oneOf(['tab', 'horizontal-split']), 424 | dependencies: PropTypes.arrayOf(PropTypes.string), 425 | horizontalSplitOffset: PropTypes.number, 426 | onRef: PropTypes.func, 427 | } 428 | 429 | export {StatelessSandbox} 430 | -------------------------------------------------------------------------------- /src/lib/StatelessSandboxThemes.js: -------------------------------------------------------------------------------- 1 | const styles = { 2 | header: { 3 | }, 4 | selectedTabIndicator: { 5 | }, 6 | tab: { 7 | }, 8 | selectedTab: { 9 | }, 10 | iconButton: { 11 | }, 12 | monokaiHeader: { 13 | backgroundColor: '#1b1d1a', 14 | }, 15 | monokaiSelectedTabIndicator: { 16 | backgroundColor: '#62bcfa', 17 | }, 18 | monokaiTab: { 19 | textTransform: 'uppercase', 20 | color: 'rgba(255, 255, 255, 0.7)', 21 | opacity: 1, 22 | minWidth: 30, 23 | marginRight: 0, 24 | '&:hover': { 25 | color: '#62bcfa', 26 | }, 27 | }, 28 | monokaiSelectedTab: { 29 | color:'#62bcfa', 30 | }, 31 | monokaiIconButton: { 32 | color: 'white', 33 | height: 36, 34 | width: 36, 35 | margin: 4, 36 | '&:hover': { 37 | backgroundColor: 'rgba(255,255,255,0.1)', 38 | }, 39 | }, 40 | twilightHeader: { 41 | backgroundColor: '#1f1f1f', 42 | }, 43 | twilightSelectedTabIndicator: { 44 | backgroundColor: '#62bcfa', 45 | }, 46 | twilightTab: { 47 | textTransform: 'uppercase', 48 | color: 'rgba(255, 255, 255, 0.7)', 49 | opacity: 1, 50 | minWidth: 30, 51 | marginRight: 0, 52 | '&:hover': { 53 | color: '#62bcfa', 54 | }, 55 | }, 56 | twilightSelectedTab: { 57 | color:'#62bcfa', 58 | }, 59 | twilightIconButton: { 60 | color: 'white', 61 | height: 36, 62 | width: 36, 63 | margin: 4, 64 | '&:hover': { 65 | backgroundColor: 'rgba(255,255,255,0.1)', 66 | }, 67 | }, 68 | solarized_lightHeader: { 69 | backgroundColor: '#0B2027', 70 | }, 71 | solarized_lightSelectedTabIndicator: { 72 | backgroundColor: '#62bcfa', 73 | }, 74 | solarized_lightTab: { 75 | textTransform: 'uppercase', 76 | color: '#FEF7E2', 77 | opacity: .8, 78 | minWidth: 30, 79 | marginRight: 0, 80 | '&:hover': { 81 | color: '#62bcfa', 82 | }, 83 | }, 84 | solarized_lightSelectedTab: { 85 | color:'#62bcfa', 86 | }, 87 | solarized_lightIconButton: { 88 | color: '#FEF7E2', 89 | height: 36, 90 | width: 36, 91 | margin: 4, 92 | '&:hover': { 93 | backgroundColor: 'rgba(255,255,255,0.1)', 94 | }, 95 | }, 96 | solarized_darkHeader: { 97 | backgroundColor: '#00242e', 98 | }, 99 | solarized_darkSelectedTabIndicator: { 100 | backgroundColor: '#62bcfa', 101 | }, 102 | solarized_darkTab: { 103 | textTransform: 'uppercase', 104 | color: 'rgba(255,255,255,.95)', 105 | opacity: 1, 106 | minWidth: 30, 107 | marginRight: 0, 108 | '&:hover': { 109 | color: '#62bcfa', 110 | }, 111 | }, 112 | solarized_darkSelectedTab: { 113 | color:'#62bcfa', 114 | }, 115 | solarized_darkIconButton: { 116 | color: 'rgba(255,255,255,.95)', 117 | height: 36, 118 | width: 36, 119 | margin: 4, 120 | '&:hover': { 121 | backgroundColor: 'rgba(255,255,255,0.1)', 122 | }, 123 | }, 124 | tomorrowHeader: { 125 | backgroundColor: '#eee', 126 | }, 127 | tomorrowSelectedTabIndicator: { 128 | backgroundColor: '#5bc0de', 129 | }, 130 | tomorrowTab: { 131 | textTransform: 'uppercase', 132 | color: 'rgba(0, 0, 0, 0.54)', 133 | opacity: 1, 134 | minWidth: 30, 135 | marginRight: 0, 136 | '&:hover': { 137 | color: '#5bc0de', 138 | }, 139 | }, 140 | tomorrowSelectedTab: { 141 | color:'#5bc0de', 142 | }, 143 | tomorrowIconButton: { 144 | height: 36, 145 | width: 36, 146 | margin: 4 147 | }, 148 | githubHeader: { 149 | backgroundColor: '#eee', 150 | }, 151 | githubSelectedTabIndicator: { 152 | backgroundColor: '#5bc0de', 153 | }, 154 | githubTab: { 155 | textTransform: 'uppercase', 156 | color: 'rgba(0, 0, 0, 0.54)', 157 | opacity: 1, 158 | minWidth: 30, 159 | marginRight: 0, 160 | '&:hover': { 161 | color: '#5bc0de', 162 | }, 163 | }, 164 | githubSelectedTab: { 165 | color:'#5bc0de', 166 | }, 167 | githubIconButton: { 168 | height: 36, 169 | width: 36, 170 | margin: 4 171 | }, 172 | }; 173 | 174 | export default styles 175 | -------------------------------------------------------------------------------- /src/lib/editors/index.js: -------------------------------------------------------------------------------- 1 | import {ScriptEditor} from './script-editor/ScriptEditor' 2 | import {TemplateEditor} from './template-editor/TemplateEditor' 3 | import {StylesheetEditor} from './stylesheet-editor/StylesheetEditor' 4 | 5 | export { 6 | ScriptEditor, 7 | TemplateEditor, 8 | StylesheetEditor 9 | } 10 | -------------------------------------------------------------------------------- /src/lib/editors/script-editor/ScriptEditor.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import PropTypes from 'prop-types'; 4 | import brace from 'brace'; 5 | import AceEditor from 'react-ace'; 6 | import withStyles from '@material-ui/core/styles/withStyles'; 7 | import ReactResizeDetector from 'react-resize-detector'; 8 | import classNames from 'classnames'; 9 | 10 | import 'brace/mode/javascript'; 11 | import 'brace/mode/jsx'; 12 | import 'brace/mode/typescript'; 13 | import 'brace/theme/github'; 14 | import 'brace/theme/tomorrow'; 15 | import 'brace/theme/solarized_light'; 16 | import 'brace/theme/monokai'; 17 | import 'brace/theme/twilight'; 18 | import 'brace/theme/solarized_dark'; 19 | 20 | 21 | const styles = theme => ({ 22 | root: {height: '100%', width: '100%'} 23 | }); 24 | 25 | class ScriptEditor extends React.Component { 26 | 27 | //unique number for each instance (for dom id) 28 | static id = 0 29 | 30 | constructor(props) { 31 | super(props) 32 | 33 | this.state = { 34 | name: "script-editor-" + ScriptEditor.id++ 35 | } 36 | } 37 | 38 | render() { 39 | const {classes} = this.props 40 | return ( 41 |
42 | 43 | {(width, height) => { 44 | return ( 45 | 58 | ) 59 | }} 60 | 61 |
62 | ) 63 | } 64 | } 65 | 66 | ScriptEditor = withStyles(styles)(ScriptEditor) 67 | 68 | ScriptEditor.defaultProps = { 69 | mode: 'javascript', 70 | readOnly: false, 71 | wrapLines: false, 72 | theme: 'solarized_dark', 73 | value: '', 74 | onChange: () => {} 75 | } 76 | 77 | ScriptEditor.propTypes = { 78 | mode: PropTypes.oneOf(['javascript', 'jsx']), 79 | readOnly: PropTypes.bool, 80 | wrapLines: PropTypes.bool, 81 | theme: PropTypes.oneOf([ 82 | 'solarized_dark', 83 | 'solarized_light', 84 | 'twilight', 85 | 'tomorrow', 86 | 'github', 87 | 'monokai', 88 | ]), 89 | value: PropTypes.string, 90 | onChange: PropTypes.func 91 | } 92 | 93 | export {ScriptEditor} 94 | -------------------------------------------------------------------------------- /src/lib/editors/stylesheet-editor/StylesheetEditor.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import PropTypes from 'prop-types'; 4 | import brace from 'brace'; 5 | import AceEditor from 'react-ace'; 6 | import withStyles from '@material-ui/core/styles/withStyles'; 7 | import ReactResizeDetector from 'react-resize-detector'; 8 | import classNames from 'classnames'; 9 | 10 | import 'brace/mode/css'; 11 | import 'brace/theme/github'; 12 | import 'brace/theme/tomorrow'; 13 | import 'brace/theme/solarized_light'; 14 | import 'brace/theme/monokai'; 15 | import 'brace/theme/twilight'; 16 | import 'brace/theme/solarized_dark'; 17 | 18 | const styles = theme => ({ 19 | root: {height: '100%', width: '100%'} 20 | }); 21 | 22 | class StylesheetEditor extends React.Component { 23 | 24 | //unique number for each instance (for dom id) 25 | static id = 0 26 | 27 | constructor(props) { 28 | super(props) 29 | 30 | this.state = { 31 | name: "stylesheet-editor-" + StylesheetEditor.id++ 32 | } 33 | } 34 | 35 | render() { 36 | const {classes} = this.props 37 | return ( 38 |
39 | 40 | {(width, height) => { 41 | return ( 42 | 55 | ) 56 | }} 57 | 58 |
59 | ) 60 | } 61 | } 62 | 63 | StylesheetEditor = withStyles(styles)(StylesheetEditor) 64 | 65 | StylesheetEditor.defaultProps = { 66 | mode: 'css', 67 | readOnly: false, 68 | wrapLines: false, 69 | theme: 'solarized_dark', 70 | value: '', 71 | onChange: () => {} 72 | } 73 | 74 | StylesheetEditor.propTypes = { 75 | mode: PropTypes.oneOf(['css']), 76 | readOnly: PropTypes.bool, 77 | wrapLines: PropTypes.bool, 78 | theme: PropTypes.oneOf([ 79 | 'solarized_dark', 80 | 'solarized_light', 81 | 'twilight', 82 | 'tomorrow', 83 | 'github', 84 | 'monokai', 85 | ]), 86 | value: PropTypes.string, 87 | onChange: PropTypes.func 88 | } 89 | 90 | export {StylesheetEditor} 91 | -------------------------------------------------------------------------------- /src/lib/editors/template-editor/TemplateEditor.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import PropTypes from 'prop-types'; 4 | import brace from 'brace'; 5 | import AceEditor from 'react-ace'; 6 | import withStyles from '@material-ui/core/styles/withStyles'; 7 | import ReactResizeDetector from 'react-resize-detector'; 8 | import classNames from 'classnames'; 9 | 10 | import 'brace/mode/html'; 11 | import 'brace/theme/github'; 12 | import 'brace/theme/tomorrow'; 13 | import 'brace/theme/solarized_light'; 14 | import 'brace/theme/monokai'; 15 | import 'brace/theme/twilight'; 16 | import 'brace/theme/solarized_dark'; 17 | 18 | 19 | const styles = theme => ({ 20 | root: {height: '100%', width: '100%'} 21 | }); 22 | 23 | class TemplateEditor extends React.Component { 24 | 25 | //unique number for each instance (for dom id) 26 | static id = 0 27 | 28 | constructor(props) { 29 | super(props) 30 | 31 | this.state = { 32 | name: "template-editor-" + TemplateEditor.id++, 33 | } 34 | } 35 | 36 | render() { 37 | const {classes} = this.props 38 | return ( 39 |
40 | 41 | {(width, height) => { 42 | return ( 43 | 56 | ) 57 | }} 58 | 59 |
60 | ) 61 | } 62 | } 63 | 64 | TemplateEditor = withStyles(styles)(TemplateEditor) 65 | 66 | TemplateEditor.defaultProps = { 67 | mode: 'html', 68 | readOnly: false, 69 | wrapLines: false, 70 | theme: 'solarized_dark', 71 | value: '', 72 | onChange: () => {} 73 | } 74 | 75 | TemplateEditor.propTypes = { 76 | mode: PropTypes.oneOf(['html']), 77 | readOnly: PropTypes.bool, 78 | wrapLines: PropTypes.bool, 79 | theme: PropTypes.oneOf([ 80 | 'solarized_dark', 81 | 'solarized_light', 82 | 'twilight', 83 | 'tomorrow', 84 | 'github', 85 | 'monokai', 86 | ]), 87 | value: PropTypes.string, 88 | onChange: PropTypes.func 89 | } 90 | 91 | export {TemplateEditor} 92 | -------------------------------------------------------------------------------- /src/lib/processors.js: -------------------------------------------------------------------------------- 1 | import babel from '@babel/standalone'; 2 | 3 | function getScriptProcessor(mode) { 4 | if (mode === 'jsx') { 5 | return (code) => { 6 | return babel.transform(code, { 7 | "presets": [ 8 | ["stage-0", { "decoratorsLegacy": true }], 9 | "react" 10 | ] 11 | }).code 12 | } 13 | } 14 | else { 15 | return (code) => { 16 | return code 17 | } 18 | } 19 | } 20 | 21 | function getStylesheetProcessor(mode) { 22 | if (mode === 'scss') { 23 | return (code) => { 24 | return code 25 | } 26 | } 27 | else if (mode === 'sass') { 28 | return (code) => { 29 | return code 30 | } 31 | } 32 | else { 33 | return (code) => { 34 | return code 35 | } 36 | } 37 | } 38 | 39 | function getTemplateProcessor(mode) { 40 | return (code) => { 41 | return code 42 | } 43 | } 44 | 45 | export default { 46 | getScriptProcessor, 47 | getStylesheetProcessor, 48 | getTemplateProcessor 49 | } 50 | -------------------------------------------------------------------------------- /src/lib/utils.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | 4 | function withDependencies(dependencies) { 5 | return (SandboxComponent) => (class extends React.Component { 6 | render() { 7 | let propsCopy = {...this.props} 8 | delete propsCopy.dependencies 9 | let deps = [] 10 | if (dependencies instanceof Array) { 11 | deps = deps.concat(dependencies) 12 | } 13 | if (this.props.dependencies instanceof Array) { 14 | deps = deps.concat(this.props.dependencies) 15 | } 16 | return ( 17 | 18 | ) 19 | } 20 | }) 21 | } 22 | 23 | export { 24 | withDependencies 25 | } 26 | -------------------------------------------------------------------------------- /src/media/PlayButtonIcon.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import SvgIcon from '@material-ui/core/SvgIcon'; 3 | 4 | const Icon = props => ( 5 | 6 | 7 | 8 | ); 9 | 10 | export default Icon 11 | -------------------------------------------------------------------------------- /src/media/SplitViewIcon.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import SvgIcon from '@material-ui/core/SvgIcon'; 3 | 4 | const Icon = props => ( 5 | 6 | 7 | 8 | ); 9 | 10 | export default Icon 11 | -------------------------------------------------------------------------------- /src/media/TabsViewIcon.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import SvgIcon from '@material-ui/core/SvgIcon'; 3 | 4 | const Icon = props => ( 5 | 6 | 7 | 8 | ); 9 | 10 | export default Icon 11 | -------------------------------------------------------------------------------- /src/styles.css: -------------------------------------------------------------------------------- 1 | /* add css styles here (optional) */ 2 | 3 | .test { 4 | display: inline-block; 5 | margin: 2em auto; 6 | border: 2px solid #000; 7 | font-size: 2em; 8 | } 9 | -------------------------------------------------------------------------------- /src/test.js: -------------------------------------------------------------------------------- 1 | // import ExampleComponent from './' 2 | 3 | describe('ExampleComponent', () => { 4 | it('is truthy', () => { 5 | 6 | }) 7 | }) 8 | --------------------------------------------------------------------------------