131 lines
3.2 KiB
JavaScript
131 lines
3.2 KiB
JavaScript
import { Subject } from 'rx';
|
|
import React, { PropTypes } from 'react';
|
|
import { createSelector } from 'reselect';
|
|
import { connect } from 'react-redux';
|
|
|
|
import Codemirror from 'react-codemirror';
|
|
import NoSSR from 'react-no-ssr';
|
|
import PureComponent from 'react-pure-render/component';
|
|
|
|
const mapStateToProps = createSelector(
|
|
state => state.app.windowHeight,
|
|
state => state.app.navHeight,
|
|
(windowHeight, navHeight) => ({ height: windowHeight - navHeight - 50 })
|
|
);
|
|
|
|
const editorDebounceTimeout = 750;
|
|
|
|
const options = {
|
|
lint: true,
|
|
lineNumbers: true,
|
|
mode: 'javascript',
|
|
theme: 'monokai',
|
|
runnable: true,
|
|
matchBrackets: true,
|
|
autoCloseBrackets: true,
|
|
scrollbarStyle: 'null',
|
|
lineWrapping: true,
|
|
gutters: ['CodeMirror-lint-markers']
|
|
};
|
|
|
|
export class Editor extends PureComponent {
|
|
constructor(...args) {
|
|
super(...args);
|
|
this._editorContent$ = new Subject();
|
|
this.handleChange = this.handleChange.bind(this);
|
|
}
|
|
static displayName = 'Editor';
|
|
static propTypes = {
|
|
executeChallenge: PropTypes.func,
|
|
height: PropTypes.number,
|
|
content: PropTypes.string,
|
|
mode: PropTypes.string,
|
|
updateFile: PropTypes.func
|
|
};
|
|
|
|
static defaultProps = {
|
|
content: '// Happy Coding!',
|
|
mode: 'javascript'
|
|
};
|
|
|
|
createOptions = createSelector(
|
|
state => state.options,
|
|
state => state.executeChallenge,
|
|
state => state.mode,
|
|
(options, executeChallenge, mode) => ({
|
|
...options,
|
|
mode,
|
|
extraKeys: {
|
|
Tab(cm) {
|
|
if (cm.somethingSelected()) {
|
|
return cm.indentSelection('add');
|
|
}
|
|
const spaces = Array(cm.getOption('indentUnit') + 1).join(' ');
|
|
return cm.replaceSelection(spaces);
|
|
},
|
|
'Shift-Tab': function(cm) {
|
|
if (cm.somethingSelected()) {
|
|
return cm.indentSelection('subtract');
|
|
}
|
|
const spaces = Array(cm.getOption('indentUnit') + 1).join(' ');
|
|
return cm.replaceSelection(spaces);
|
|
},
|
|
'Ctrl-Enter': function() {
|
|
executeChallenge();
|
|
return false;
|
|
},
|
|
'Cmd-Enter': function() {
|
|
executeChallenge();
|
|
return false;
|
|
}
|
|
}
|
|
})
|
|
);
|
|
|
|
componentDidMount() {
|
|
const { updateFile = (() => {}) } = this.props;
|
|
this._subscription = this._editorContent$
|
|
.debounce(editorDebounceTimeout)
|
|
.distinctUntilChanged()
|
|
.subscribe(
|
|
updateFile,
|
|
err => { throw err; }
|
|
);
|
|
}
|
|
|
|
componentWillUnmount() {
|
|
if (this._subscription) {
|
|
this._subscription.dispose();
|
|
this._subscription = null;
|
|
}
|
|
}
|
|
|
|
handleChange(value) {
|
|
if (this._subscription) {
|
|
this._editorContent$.onNext(value);
|
|
}
|
|
}
|
|
|
|
render() {
|
|
const { executeChallenge, content, height, mode } = this.props;
|
|
const style = {};
|
|
if (height) {
|
|
style.height = height + 'px';
|
|
}
|
|
return (
|
|
<div
|
|
className='challenges-editor'
|
|
style={ style }>
|
|
<NoSSR>
|
|
<Codemirror
|
|
onChange={ this.handleChange }
|
|
options={ this.createOptions({ executeChallenge, mode, options }) }
|
|
value={ content } />
|
|
</NoSSR>
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
|
|
export default connect(mapStateToProps)(Editor);
|