Files
freeCodeCamp/common/app/routes/challenges/components/classic/Editor.jsx
2016-07-28 23:39:17 -07:00

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);