133 lines
		
	
	
		
			3.2 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			133 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);
 |