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 (
); } } export default connect(mapStateToProps)(Editor);