You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
343 lines
9.2 KiB
343 lines
9.2 KiB
/**
|
|
* Copyright (c) 2013-present, Facebook, Inc.
|
|
*
|
|
* @emails react-core
|
|
*/
|
|
|
|
import React, {Component} from 'react';
|
|
import ReactDOM from 'react-dom';
|
|
import Remarkable from 'remarkable';
|
|
import {LiveEditor, LiveProvider} from 'react-live';
|
|
import {colors, media} from 'theme';
|
|
import MetaTitle from 'templates/components/MetaTitle';
|
|
|
|
const compileES5 = (
|
|
code, // eslint-disable-next-line no-undef
|
|
) => Babel.transform(code, {presets: ['es2015', 'react']}).code;
|
|
|
|
// eslint-disable-next-line no-undef
|
|
const compileES6 = code => Babel.transform(code, {presets: ['react']}).code;
|
|
|
|
class CodeEditor extends Component {
|
|
constructor(props, context) {
|
|
super(props, context);
|
|
|
|
this.state = this._updateState(props.code);
|
|
this.state.showJSX = true;
|
|
}
|
|
|
|
componentDidMount() {
|
|
// Initial render() will always be a no-op,
|
|
// Because the mountNode ref won't exist yet.
|
|
this._render();
|
|
}
|
|
|
|
componentDidUpdate(prevProps, prevState) {
|
|
if (prevState.compiled !== this.state.compiled) {
|
|
this._render();
|
|
}
|
|
}
|
|
|
|
render() {
|
|
const {children} = this.props;
|
|
const {
|
|
compiledES6,
|
|
code,
|
|
error,
|
|
showBabelErrorMessage,
|
|
showJSX,
|
|
} = this.state;
|
|
|
|
let errorMessage;
|
|
if (showBabelErrorMessage) {
|
|
errorMessage = (
|
|
<span>
|
|
Babel could not be loaded.
|
|
<br />
|
|
<br />
|
|
This can be caused by an ad blocker. If you're using one, consider
|
|
adding reactjs.org to the whitelist so the live code examples will
|
|
work.
|
|
</span>
|
|
);
|
|
} else if (error != null) {
|
|
errorMessage = error.message;
|
|
}
|
|
|
|
return (
|
|
<LiveProvider code={showJSX ? code : compiledES6} mountStylesheet={false}>
|
|
<div
|
|
css={{
|
|
[media.greaterThan('xlarge')]: {
|
|
display: 'flex',
|
|
flexDirection: 'row',
|
|
},
|
|
|
|
[media.lessThan('large')]: {
|
|
display: 'block',
|
|
},
|
|
}}>
|
|
{children && (
|
|
<div
|
|
css={{
|
|
flex: '0 0 33%',
|
|
|
|
[media.lessThan('xlarge')]: {
|
|
marginBottom: 20,
|
|
},
|
|
|
|
'& h3': {
|
|
color: colors.dark,
|
|
maxWidth: '11em',
|
|
paddingTop: 0,
|
|
},
|
|
|
|
'& p': {
|
|
marginTop: 15,
|
|
marginRight: 40,
|
|
lineHeight: 1.7,
|
|
|
|
[media.greaterThan('xlarge')]: {
|
|
marginTop: 25,
|
|
},
|
|
},
|
|
}}>
|
|
{children}
|
|
</div>
|
|
)}
|
|
|
|
<div
|
|
css={{
|
|
[media.greaterThan('medium')]: {
|
|
flex: '0 0 67%',
|
|
display: 'flex',
|
|
alignItems: 'stretch',
|
|
flexDirection: 'row',
|
|
},
|
|
|
|
[media.lessThan('small')]: {
|
|
display: 'block',
|
|
},
|
|
}}>
|
|
<div
|
|
css={{
|
|
flex: '0 0 70%',
|
|
overflow: 'hidden',
|
|
borderRadius: '10px 0 0 10px',
|
|
|
|
[media.lessThan('medium')]: {
|
|
borderRadius: '10px 10px 0 0',
|
|
},
|
|
}}>
|
|
<div
|
|
css={{
|
|
padding: '0px 10px',
|
|
background: colors.darker,
|
|
color: colors.white,
|
|
}}>
|
|
<MetaTitle onDark={true}>
|
|
Live JSX Editor
|
|
<label
|
|
css={{
|
|
fontSize: 14,
|
|
float: 'right',
|
|
cursor: 'pointer',
|
|
}}>
|
|
<input
|
|
checked={this.state.showJSX}
|
|
onChange={event =>
|
|
this.setState({showJSX: event.target.checked})
|
|
}
|
|
type="checkbox"
|
|
/>{' '}
|
|
JSX?
|
|
</label>
|
|
</MetaTitle>
|
|
</div>
|
|
<div
|
|
css={{
|
|
height: '100%',
|
|
width: '100%',
|
|
borderRadius: '0',
|
|
maxHeight: '340px !important',
|
|
marginTop: '0 !important',
|
|
marginLeft: '0 !important',
|
|
paddingLeft: '0 !important',
|
|
marginRight: '0 !important',
|
|
paddingRight: '0 !important',
|
|
marginBottom: '0 !important',
|
|
paddingBottom: '20px !important',
|
|
[media.lessThan('medium')]: {
|
|
marginBottom: '0 !important',
|
|
},
|
|
|
|
'& pre.prism-code[contenteditable]': {
|
|
outline: 0,
|
|
overflow: 'auto',
|
|
marginRight: '0 !important',
|
|
marginBottom: '0 !important',
|
|
},
|
|
}}
|
|
className="gatsby-highlight">
|
|
<LiveEditor ignoreTabKey={true} onChange={this._onChange} />
|
|
</div>
|
|
</div>
|
|
{error && (
|
|
<div
|
|
css={{
|
|
flex: '0 0 30%',
|
|
overflow: 'hidden',
|
|
border: `1px solid ${colors.error}`,
|
|
borderRadius: '0 10px 10px 0',
|
|
fontSize: 12,
|
|
lineHeight: 1.5,
|
|
|
|
[media.lessThan('medium')]: {
|
|
borderRadius: '0 0 10px 10px',
|
|
},
|
|
}}>
|
|
<div
|
|
css={{
|
|
padding: '0px 10px',
|
|
background: colors.error,
|
|
color: colors.white,
|
|
}}>
|
|
<MetaTitle
|
|
cssProps={{
|
|
color: colors.white,
|
|
}}>
|
|
Error
|
|
</MetaTitle>
|
|
</div>
|
|
<pre
|
|
css={{
|
|
whiteSpace: 'pre-wrap',
|
|
wordBreak: 'break-word',
|
|
color: colors.error,
|
|
padding: 10,
|
|
}}>
|
|
{errorMessage}
|
|
</pre>
|
|
</div>
|
|
)}
|
|
{!error && (
|
|
<div
|
|
css={{
|
|
flex: '0 0 30%',
|
|
overflow: 'hidden',
|
|
border: `1px solid ${colors.divider}`,
|
|
borderRadius: '0 10px 10px 0',
|
|
|
|
[media.lessThan('medium')]: {
|
|
borderRadius: '0 0 10px 10px',
|
|
},
|
|
}}>
|
|
<div
|
|
css={{
|
|
padding: '0 10px',
|
|
backgroundColor: colors.divider,
|
|
}}>
|
|
<MetaTitle>Result</MetaTitle>
|
|
</div>
|
|
<div
|
|
css={{
|
|
padding: 10,
|
|
maxHeight: '340px !important',
|
|
overflow: 'auto',
|
|
|
|
'& input': {
|
|
width: '100%',
|
|
display: 'block',
|
|
border: '1px solid #ccc', // TODO
|
|
padding: 5,
|
|
},
|
|
|
|
'& button': {
|
|
marginTop: 10,
|
|
padding: '5px 10px',
|
|
},
|
|
|
|
'& textarea': {
|
|
width: '100%',
|
|
marginTop: 10,
|
|
height: 60,
|
|
padding: 5,
|
|
},
|
|
}}
|
|
ref={this._setMountRef}
|
|
/>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</LiveProvider>
|
|
);
|
|
}
|
|
|
|
_render() {
|
|
if (!this._mountNode) {
|
|
return;
|
|
}
|
|
|
|
const {compiled} = this.state;
|
|
|
|
try {
|
|
// Example code requires React, ReactDOM, and Remarkable to be within scope.
|
|
// It also requires a "mountNode" variable for ReactDOM.render()
|
|
// eslint-disable-next-line no-new-func
|
|
new Function('React', 'ReactDOM', 'Remarkable', 'mountNode', compiled)(
|
|
React,
|
|
ReactDOM,
|
|
Remarkable,
|
|
this._mountNode,
|
|
);
|
|
} catch (error) {
|
|
console.error(error);
|
|
|
|
this.setState({
|
|
compiled: null,
|
|
error,
|
|
});
|
|
}
|
|
}
|
|
|
|
_setMountRef = ref => {
|
|
this._mountNode = ref;
|
|
};
|
|
|
|
_updateState(code, showJSX = true) {
|
|
try {
|
|
let newState = {
|
|
compiled: compileES5(code),
|
|
error: null,
|
|
};
|
|
|
|
if (showJSX) {
|
|
newState.code = code;
|
|
newState.compiledES6 = compileES6(code);
|
|
} else {
|
|
newState.compiledES6 = code;
|
|
}
|
|
|
|
return newState;
|
|
} catch (error) {
|
|
console.error(error);
|
|
|
|
// Certain ad blockers (eg Fair AdBlocker) prevent Babel from loading.
|
|
// If we suspect this is the case, we can show a more helpful error.
|
|
const showBabelErrorMessage = !window.Babel;
|
|
|
|
return {
|
|
compiled: null,
|
|
error,
|
|
showBabelErrorMessage,
|
|
};
|
|
}
|
|
}
|
|
|
|
_onChange = code => {
|
|
this.setState(state => this._updateState(code, state.showJSX));
|
|
};
|
|
}
|
|
|
|
export default CodeEditor;
|
|
|