<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import Button from 'react-bootstrap/lib/Button';

import Modal from './Modal';
import Icon from '../components/Icon';
import { showErrorModal } from './ErrorModal';
import { load } from '../tangram-play';
import { getSceneURLFromGistAPI } from '../tools/gist-url';
import { getGists, removeNonexistentGistFromLocalStorage } from '../storage/gist';

class OpenGistModal extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      loaded: false,
      gists: [],
      selected: null,
    };

    this.onClickCancel = this.onClickCancel.bind(this);
    this.onClickConfirm = this.onClickConfirm.bind(this);
  }

  componentWillMount() {
    // Always load new set of saved Gists from memory each time this modal is
    // opened, in case it has changed during use
    getGists().then((gists = []) => {
      this.setState({
        loaded: true,
        gists,
      });
    });
  }

  onClickCancel() {
    this.props.dispatch({
      type: 'HIDE_MODAL',
      id: this.props.modalId,
    });
  }

  onClickConfirm() {
    if (this.state.selected) {
      this.onClickCancel(); // to close modal

      getSceneURLFromGistAPI(this.state.selected.url)
        .then((url) => {
          load({
            url,
            data: this.state.selected,
            source: 'GIST',
          });
        })
        .catch((error) => {
          this.handleError(error, this.state.selected.url);
        });
    }
  }

  /**
   * If opening a URL is not successful
   *
   * @param {Error} error - thrown by something else
   *    if error.message is a number, then it is a status code from Fetch
   *    but status code numbers are converted to strings
   *    there must be a better way of doing this
   * @param {string} url - the Gist URL that was attempted
   */
  handleError(error, value) {
    // Close the modal, if still present
    if (this.component) {
      this.onClickCancel();
    }

    let message = '';

    if (error.message === '404') {
      message = 'This Gist could not be found.';
    } else if (error.message === '403') {
      message = 'We exceeded the rate limit for GitHub’s non-authenticated request API.';
    } else if (Number.isInteger(window.parseInt(error.message, 10))) {
      message = `The Gist server gave us an error code of ${error.message}`;
    }

    showErrorModal(`Could not load the Gist! ${message}`);

    if (error.message === '404') {
      removeNonexistentGistFromLocalStorage(value);
    }
  }

  render() {
    const gists = this.state.gists;

    let gistList;

    if (this.state.loaded === true && gists.length === 0) {
      gistList = 'No gists have been saved!';
    } else {
      gistList = gists.map((item, index) => {
        // If the scene is selected, a special class is applied later to it
        let classString = 'open-scene-option';

        // TODO: Do not hardcode.
        const descPlaceholder = '[This is a Tangram scene, made with Tangram Play.]';

        if (item.description) {
          item.description = item.description.replace(descPlaceholder, '');
        }
        if (!item.description || item.description.length === 0) {
          item.description = 'No description provided.';
        }

        if (this.state.selected && this.state.selected.url === item.url) {
          classString += ' open-scene-selected';
        }

        // TODO:
        // There is actually a lot more info stored than is currently being
        // displayed. We have date, user, public gist or not, and map view.
        return (
          <div
            className={classString}
            role="menuitem"
            tabIndex={0}
            key={item.url}
            data-url={item.url}
            onFocus={(e) => { this.setState({ selected: item }); }}
            onClick={() => { this.setState({ selected: item }); }}
            onDoubleClick={this.onClickConfirm}
          >
            <div className="open-scene-option-thumbnail">
              <img src={item.thumbnail} alt="" />
            </div>
            <div className="open-scene-option-info">
              <div className="open-scene-option-name">
                {item.name}
              </div>
              <div className="open-scene-option-description">
                {item.description}
              </div>
              <div className="open-scene-option-date">
                {/* Show the date this was saved.
                    TODO: better formatting;
                    maybe use moment.js */}
                Saved on {new Date(item.created_at).toLocaleString()}
              </div>
            </div>
          </div>
        );
      });
    }

    // Render the entire modal
    return (
      <Modal
        className="modal-alt open-scene-modal"
        ref={(ref) => { this.component = ref; }}
        cancelFunction={this.onClickCancel}
        confirmFunction={this.onClickConfirm}
      >
        <h4>Open a previously saved Gist</h4>

        <div className="modal-content modal-well open-scene-list">
          {gistList}
        </div>

        <div className="modal-buttons">
          <Button onClick={this.onClickCancel} className="button-cancel">
            <Icon type="bt-times" /> Cancel
          </Button>
          <Button
            onClick={this.onClickConfirm}
            className="button-confirm"
            disabled={this.state.selected === null}
          >
            <Icon type="bt-check" /> Open
          </Button>
        </div>
      </Modal>
    );
  }
}

OpenGistModal.propTypes = {
  dispatch: PropTypes.func.isRequired,
  modalId: PropTypes.number.isRequired,
};

export default connect()(OpenGistModal);
<commit_msg>Use SceneItem component in open-gist modal<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import Button from 'react-bootstrap/lib/Button';

import Modal from './Modal';
import SceneItem from './SceneItem';
import Icon from '../components/Icon';
import { showErrorModal } from './ErrorModal';
import { load } from '../tangram-play';
import { getSceneURLFromGistAPI } from '../tools/gist-url';
import { getGists, removeNonexistentGistFromLocalStorage } from '../storage/gist';

class OpenGistModal extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      loaded: false,
      gists: [],
      selected: null,
    };

    this.onClickCancel = this.onClickCancel.bind(this);
    this.onClickConfirm = this.onClickConfirm.bind(this);
  }

  componentWillMount() {
    // Always load new set of saved Gists from memory each time this modal is
    // opened, in case it has changed during use
    getGists().then((gists = []) => {
      this.setState({
        loaded: true,
        gists,
      });
    });
  }

  onClickCancel() {
    this.props.dispatch({
      type: 'HIDE_MODAL',
      id: this.props.modalId,
    });
  }

  onClickConfirm() {
    if (this.state.selected) {
      this.onClickCancel(); // to close modal

      getSceneURLFromGistAPI(this.state.selected.url)
        .then((url) => {
          load({
            url,
            data: this.state.selected,
            source: 'GIST',
          });
        })
        .catch((error) => {
          this.handleError(error, this.state.selected.url);
        });
    }
  }

  /**
   * If opening a URL is not successful
   *
   * @param {Error} error - thrown by something else
   *    if error.message is a number, then it is a status code from Fetch
   *    but status code numbers are converted to strings
   *    there must be a better way of doing this
   * @param {string} url - the Gist URL that was attempted
   */
  handleError(error, value) {
    // Close the modal, if still present
    if (this.component) {
      this.onClickCancel();
    }

    let message = '';

    if (error.message === '404') {
      message = 'This Gist could not be found.';
    } else if (error.message === '403') {
      message = 'We exceeded the rate limit for GitHub’s non-authenticated request API.';
    } else if (Number.isInteger(window.parseInt(error.message, 10))) {
      message = `The Gist server gave us an error code of ${error.message}`;
    }

    showErrorModal(`Could not load the Gist! ${message}`);

    if (error.message === '404') {
      removeNonexistentGistFromLocalStorage(value);
    }
  }

  render() {
    const gists = this.state.gists;

    let gistList;

    if (this.state.loaded === true && gists.length === 0) {
      gistList = 'No gists have been saved!';
    } else {
      gistList = gists.map((item, index) => {
        // TODO: Do not hardcode.
        const descPlaceholder = '[This is a Tangram scene, made with Tangram Play.]';

        if (item.description) {
          item.description = item.description.replace(descPlaceholder, '');
        }

        let classString = '';
        if (this.state.selected && this.state.selected.url === item.url) {
          classString = 'open-scene-selected';
        }

        // TODO:
        // There is actually a lot more info stored than is currently being
        // displayed. We have date, user, public gist or not, and map view.
        return (
          <div
            className={classString}
            role="menuitem"
            tabIndex={0}
            key={item.url}
            data-url={item.url}
            onFocus={(e) => { this.setState({ selected: item }); }}
            onClick={() => { this.setState({ selected: item }); }}
            onDoubleClick={this.onClickConfirm}
          >
            <SceneItem
              thumbnail={item.thumbnail}
              name={item.name}
              description={item.description}
              date={item.created_at}
            />
          </div>
        );
      });
    }

    // Render the entire modal
    return (
      <Modal
        className="modal-alt open-scene-modal"
        ref={(ref) => { this.component = ref; }}
        cancelFunction={this.onClickCancel}
        confirmFunction={this.onClickConfirm}
      >
        <h4>Open a previously saved Gist</h4>

        <div className="modal-content modal-well open-scene-list">
          {gistList}
        </div>

        <div className="modal-buttons">
          <Button onClick={this.onClickCancel} className="button-cancel">
            <Icon type="bt-times" /> Cancel
          </Button>
          <Button
            onClick={this.onClickConfirm}
            className="button-confirm"
            disabled={this.state.selected === null}
          >
            <Icon type="bt-check" /> Open
          </Button>
        </div>
      </Modal>
    );
  }
}

OpenGistModal.propTypes = {
  dispatch: PropTypes.func.isRequired,
  modalId: PropTypes.number.isRequired,
};

export default connect()(OpenGistModal);

{'fraction_non_alphanumeric': 0.13009851763189392, 'fraction_numerical': 0.002854249148328883, 'mean_word_length': 2.048554588829638, 'pattern_counts': {'":': 0, '<': 47, '<?xml version=': 0, '>': 61, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1533184', 'n_tokens_mistral': 3149, 'n_tokens_neox': 3103, 'n_words': 806}