scup/Atellier

View on GitHub
src/Stage.js

Summary

Maintainability
A
0 mins
Test Coverage
import React, { PropTypes } from 'react';
import ReactDOM from 'react-dom';
import classNames from 'classnames';

class Stage extends React.Component {

  static defaultProps = {
    component: {},
    properties: {}
  };

  static propTypes = {
    component: PropTypes.shape({
      component: PropTypes.func,
      componentName: PropTypes.string
    }),
    properties: PropTypes.object
  };

  constructor(props){
    super(props);
    this.state = {
      constrast: false,
      componentError: null,
      reloadNeeded: false
    };
    this._defineElement(props);
  }

  componentWillReceiveProps(nextProps) {
    this._defineElement(nextProps);
  }

  _renderComponent(){
    let { component, properties } = this.props;
    let targetRender = document.getElementById('__stage_render__');
    try {
      ReactDOM.unstable_renderSubtreeIntoContainer(
        this,
        <div className="stage">
          {this._renderStageBoard()}
          {this._renderStageTools()}
        </div>,
        targetRender
      );
    } catch(error) {
      targetRender.innerHTML = '';
      ReactDOM.unstable_renderSubtreeIntoContainer(
        this,
        <div className="stage">
          {this._renderErrorAlert(error)}
        </div>,
        targetRender
      );
    }
  }

  render() {
    return (
      <div id="__stage_render__" className="stage"></div>
    );
  }

  componentDidMount(){
    this._renderComponent();
  }

  componentDidUpdate(){
    this._renderComponent();
  }

  _renderStageBoard() {
    let className = classNames('stage-board', {'stage-board-dark': this.state.constrast});
    return (
      <div className={className}>
        {this._instance}
      </div>
    );
  }

  onReloadAtellier = () => {
    window.location.reload();
  };

  _renderErrorAlert(error) {
    this._setInvalidComponent( this.props.component );
    return (
      <div className="stage-render-error">
       <button onClick={this.onReloadAtellier} className="reload">Reload</button>
        <span className="stage-error-text">
          React component <b>{this.state.componentError} </b> crashed!
        </span>
        <div className="stage-error-message">{error.message}</div>
      </div>
    );
  }

  _setInvalidComponent(component){
    let componentName = component.componentName,
        indexKey = component.indexKey;

    let invalidComponents = JSON.parse(localStorage.getItem('invalidComponents')) || [];

    let exists = invalidComponents.filter( (item) => {
      return (item.componentName === componentName && item.indexKey === indexKey);
    });

    if ( !exists.length && !this.state.reloadNeeded ) {
      this.setState({
        reloadNeeded: true
      });
      invalidComponents.push({
        indexKey: indexKey,
        componentName: componentName
      });
    } else if(exists.length && !this.state.reloadNeeded) {
      this.setState({
        reloadNeeded: true
      });
    }

    localStorage.setItem('invalidComponents', JSON.stringify(invalidComponents));
  }

  _renderStageTools() {
    return (
      <div className="stage-tools">
        <label>Stage color</label>
        <select className="atellier-input" onChange={this._handleChangeConstrast}>
          <option value="false">Light</option>
          <option value="true">Dark</option>
        </select>
      </div>
    );
  }

  _handleChangeConstrast = (event) => {
    this.setState({
      constrast: event.target.value === 'true'
    });
  };

  _defineElement = (props) => {
    let { component, properties } = props;
    if (component && component.component) {
      this._instance = React.cloneElement(React.createElement(component.component, properties))
    }
  };
}

export default Stage;