<commit_before>import React, { Component, PropTypes } from 'react'
import { Link } from 'react-router'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import Modal from 'react-modal'
import ReactTooltip from 'react-tooltip'
import { PGPActions } from '../store/pgp'

import { getWebAccountTypes } from '../../utils'

function mapStateToProps(state) {
  return {
    api: state.settings.api,
    pgpPublicKeys: state.profiles.pgp.publicKeys
  }
}

function mapDispatchToProps(dispatch) {
  return bindActionCreators(PGPActions, dispatch)
}

class PGPAccountItem extends Component {
  static propTypes = {
    editing: PropTypes.bool.isRequired,
    listItem: PropTypes.bool.isRequired,
    service: PropTypes.string.isRequired,
    identifier: PropTypes.string.isRequired,
    contentUrl: PropTypes.string,
    loadPGPPublicKey: PropTypes.func.isRequired,
    pgpPublicKeys: PropTypes.object,
    onClick: PropTypes.func
  }

  constructor(props) {
    super(props)

    this.state = {
      modalIsOpen: false
    }

    this.loadPublicKey = this.loadPublicKey.bind(this)
    this.openModal = this.openModal.bind(this)
    this.closeModal = this.closeModal.bind(this)
    this.getIconClass = this.getIconClass.bind(this)
    this.getIdentifier = this.getIdentifier.bind(this)
  }

  componentWillMount() {
    this.loadPublicKey()
  }

  openModal() {
    this.setState({
      modalIsOpen: true
    })
  }

  closeModal() {
    this.setState({
      modalIsOpen: false
    })
  }

  loadPublicKey() {
    if (this.props.contentUrl) {
      const contentUrl = this.props.contentUrl
      this.props.loadPGPPublicKey(contentUrl, this.props.identifier)
    }
  }

  getIconClass() {
    const webAccountTypes = getWebAccountTypes(this.props.api)
    let iconClass = ''
    if (webAccountTypes.hasOwnProperty(this.props.service)) {
      iconClass = webAccountTypes[this.props.service].iconClass
    }
    return iconClass
  }

  getIdentifier() {
    let identifier = this.props.identifier
    if (identifier.length >= 40) {
      identifier = identifier.slice(0, 40) + '...'
    }
    return identifier
  }

  getPlaceholderText(service) {
    const webAccountTypes = getWebAccountTypes(this.props.api)
    const webAccountType = webAccountTypes[this.props.service]
    let accountServiceName = webAccountType.label
    if (service === 'pgp' || service === 'ssh') {
      return (

          Prove your {service.toUpperCase()} key

      )
    }
  }

  onClick = (e) => {
    if (!this.props.placeholder && !this.props.editing) {

    } else {
      this.props.onClick(this.props.service)
    }
  }

  render() {
    const identifier = this.props.identifier
    const webAccountTypes = getWebAccountTypes(this.props.api)
    const pgpPublicKeys = this.props.pgpPublicKeys
    const verified = this.props.verified
    const verifiedClass = verified ? "verified" : "pending"
    const placeholderClass = this.props.placeholder ? "placeholder" : ""
    const identifierType = (this.props.service === 'pgp' || this.props.service === 'ssh') ? 'Key' : 'Address'

    let loading = false
    let error = false
    let key = null

    if(pgpPublicKeys && pgpPublicKeys.hasOwnProperty(identifier)) {
      const publicKey = pgpPublicKeys[identifier]
      if(publicKey.loading)
        loading = true
      if(publicKey.error)
        error = publicKey.error
      if(publicKey.key)
        key = publicKey.key
    }

    if (this.props.listItem === true) {
      return (
        <li className={`clickable ${verifiedClass} ${placeholderClass}`} onClick={this.onClick}>
          <Modal
            isOpen={this.state.modalIsOpen}
            contentLabel="PGP Key"
            shouldCloseOnOverlayClick={true}
            style={{overlay: {zIndex: 10}}}
            className="container-fluid react-modal-pgp">
            <button onClick={this.closeModal}>close</button>

PGP Key

Fingerprint: {identifier}

 { loading
              ?
              <textarea className="form-control" readOnly="true" rows="10"
                value="Loading...">
              </textarea>
              :

              { error ?
                <textarea className="form-control" readOnly="true" rows="10"
                  value={error}>
                </textarea>
                :
            <textarea className="form-control" readOnly="true" rows="10"
              value={key}>
            </textarea>
            }

            }

          </Modal>
          <ReactTooltip place="top" type="dark" effect="solid" id={`verified-${this.props.service}`} className="text-center">
            {this.props.verified ? 'Verified' : 'Pending...'}
          </ReactTooltip>
          {/*<a href="#" onClick={this.openModal} data-toggle="tooltip"*/}

            <i className={`fa fa-fw ${this.getIconClass()} fa-lg`} />

          {!this.props.placeholder && (

              {this.getIdentifier()}

          )}

          {(!this.props.placeholder && this.props.editing) && (

          )}

          { !this.props.placeholder && (

              {webAccountTypes[this.props.service].label}

          )}

          { this.props.placeholder && (

              Prove your {webAccountTypes[this.props.service].label} {identifierType.toLowerCase()}

          )}

          {/*this.props.verified ?
            <span className="float-right" data-tip data-for={`verified-${this.props.service}`}>

            :
            <span className="float-right" data-tip data-for={`verified-${this.props.service}`}>

          */}

      )
    } else {
      return (

          <i className={`fa ${this.getIconClass()}`} />
          {this.getIdentifier()}

      )
    }
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(PGPAccountItem)
<commit_msg>Change prove to add for bitcoin/ethereum/pgp/ssh accounts. #966<commit_after>import React, { Component, PropTypes } from 'react'
import { Link } from 'react-router'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import Modal from 'react-modal'
import ReactTooltip from 'react-tooltip'
import { PGPActions } from '../store/pgp'

import { getWebAccountTypes } from '../../utils'

function mapStateToProps(state) {
  return {
    api: state.settings.api,
    pgpPublicKeys: state.profiles.pgp.publicKeys
  }
}

function mapDispatchToProps(dispatch) {
  return bindActionCreators(PGPActions, dispatch)
}

class PGPAccountItem extends Component {
  static propTypes = {
    editing: PropTypes.bool.isRequired,
    listItem: PropTypes.bool.isRequired,
    service: PropTypes.string.isRequired,
    identifier: PropTypes.string.isRequired,
    contentUrl: PropTypes.string,
    loadPGPPublicKey: PropTypes.func.isRequired,
    pgpPublicKeys: PropTypes.object,
    onClick: PropTypes.func
  }

  constructor(props) {
    super(props)

    this.state = {
      modalIsOpen: false
    }

    this.loadPublicKey = this.loadPublicKey.bind(this)
    this.openModal = this.openModal.bind(this)
    this.closeModal = this.closeModal.bind(this)
    this.getIconClass = this.getIconClass.bind(this)
    this.getIdentifier = this.getIdentifier.bind(this)
  }

  componentWillMount() {
    this.loadPublicKey()
  }

  openModal() {
    this.setState({
      modalIsOpen: true
    })
  }

  closeModal() {
    this.setState({
      modalIsOpen: false
    })
  }

  loadPublicKey() {
    if (this.props.contentUrl) {
      const contentUrl = this.props.contentUrl
      this.props.loadPGPPublicKey(contentUrl, this.props.identifier)
    }
  }

  getIconClass() {
    const webAccountTypes = getWebAccountTypes(this.props.api)
    let iconClass = ''
    if (webAccountTypes.hasOwnProperty(this.props.service)) {
      iconClass = webAccountTypes[this.props.service].iconClass
    }
    return iconClass
  }

  getIdentifier() {
    let identifier = this.props.identifier
    if (identifier.length >= 40) {
      identifier = identifier.slice(0, 40) + '...'
    }
    return identifier
  }

  getPlaceholderText(service) {
    const webAccountTypes = getWebAccountTypes(this.props.api)
    const webAccountType = webAccountTypes[this.props.service]
    let accountServiceName = webAccountType.label
    if (service === 'pgp' || service === 'ssh') {
      return (

          Prove your {service.toUpperCase()} key

      )
    }
  }

  onClick = (e) => {
    if (!this.props.placeholder && !this.props.editing) {

    } else {
      this.props.onClick(this.props.service)
    }
  }

  render() {
    const identifier = this.props.identifier
    const webAccountTypes = getWebAccountTypes(this.props.api)
    const pgpPublicKeys = this.props.pgpPublicKeys
    const verified = this.props.verified
    const verifiedClass = verified ? "verified" : "pending"
    const placeholderClass = this.props.placeholder ? "placeholder" : ""
    const identifierType = (this.props.service === 'pgp' || this.props.service === 'ssh') ? 'Key' : 'Address'

    let loading = false
    let error = false
    let key = null

    if(pgpPublicKeys && pgpPublicKeys.hasOwnProperty(identifier)) {
      const publicKey = pgpPublicKeys[identifier]
      if(publicKey.loading)
        loading = true
      if(publicKey.error)
        error = publicKey.error
      if(publicKey.key)
        key = publicKey.key
    }

    if (this.props.listItem === true) {
      return (
        <li className={`clickable ${verifiedClass} ${placeholderClass}`} onClick={this.onClick}>
          <Modal
            isOpen={this.state.modalIsOpen}
            contentLabel="PGP Key"
            shouldCloseOnOverlayClick={true}
            style={{overlay: {zIndex: 10}}}
            className="container-fluid react-modal-pgp">
            <button onClick={this.closeModal}>close</button>

PGP Key

Fingerprint: {identifier}

 { loading
              ?
              <textarea className="form-control" readOnly="true" rows="10"
                value="Loading...">
              </textarea>
              :

              { error ?
                <textarea className="form-control" readOnly="true" rows="10"
                  value={error}>
                </textarea>
                :
            <textarea className="form-control" readOnly="true" rows="10"
              value={key}>
            </textarea>
            }

            }

          </Modal>
          <ReactTooltip place="top" type="dark" effect="solid" id={`verified-${this.props.service}`} className="text-center">
            {this.props.verified ? 'Verified' : 'Pending...'}
          </ReactTooltip>
          {/*<a href="#" onClick={this.openModal} data-toggle="tooltip"*/}

            <i className={`fa fa-fw ${this.getIconClass()} fa-lg`} />

          {!this.props.placeholder && (

              {this.getIdentifier()}

          )}

          {(!this.props.placeholder && this.props.editing) && (

          )}

          { !this.props.placeholder && (

              {webAccountTypes[this.props.service].label}

          )}

          { this.props.placeholder && (

              Add your {webAccountTypes[this.props.service].label} {identifierType.toLowerCase()}

          )}

          {/*this.props.verified ?
            <span className="float-right" data-tip data-for={`verified-${this.props.service}`}>

            :
            <span className="float-right" data-tip data-for={`verified-${this.props.service}`}>

          */}

      )
    } else {
      return (

          <i className={`fa ${this.getIconClass()}`} />
          {this.getIdentifier()}

      )
    }
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(PGPAccountItem)
<|endoftext|>


<commit_before>import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile';
import { defer, reject } from 'rsvp';
import { later } from '@ember/runloop';
import generateThings from 'dummy/utils/generate-things';
import {
  render,
  settled,
  triggerEvent,
  find,
  findAll,
  click,
  waitFor,
  waitUntil
} from '@ember/test-helpers';

module('infinite-scroller', function(hooks) {
  setupRenderingTest(hooks);

  hooks.beforeEach(function() {
    this.infiniteScroller = this.owner.lookup('service:-infinite-scroller');

    this.set('things', generateThings(1, 20));

    this.set('loadMore', () => {
      this.get('things').pushObjects(generateThings(21, 40));
    });
  });

  test('it renders', async function(assert) {
    assert.expect(1);

    await render(hbs`{{infinite-scroller}}`);

    assert.equal(findAll('.infinite-scroller').length, 1,
      'infinite scroller component has an appropriate class name');
  });

  test('load more action', async function(assert) {
    assert.expect(2);

    await render(hbs`
      {{#infinite-scroller
        class="example-1"
        on-load-more=(action loadMore) as |scroller|}}
        {{#each things as |thing|}}

{{thing.name}}

        {{/each}}
      {{/infinite-scroller}}
    `);

    find('.infinite-scroller').scrollTop = 450;

    later(() => {
      assert.equal(findAll('.thing').length, 20,
        'has not fired load more action due to debouncing of scroll event');
    }, 100);

    await settled();

    assert.equal(findAll('.thing').length, 40,
      'fires load more action at the element scroll boundary');
  });

  test('load more action (trigger-at)', async function(assert) {
    assert.expect(1);

    await render(hbs`
      {{#infinite-scroller
        class="example-1"
        trigger-at="50%"
        on-load-more=(action loadMore) as |scroller|}}
        {{#each things as |thing|}}

{{thing.name}}

        {{/each}}
      {{/infinite-scroller}}
    `);

    find('.infinite-scroller').scrollTop = 225;

    await waitUntil(() => findAll('.thing').length !== 20);

    assert.equal(findAll('.thing').length, 40,
      'fires load more action at the custom element scroll boundary');
  });

  test('load more action (scroll-debounce)', async function(assert) {
    assert.expect(2);

    await render(hbs`
      {{#infinite-scroller
        class="example-1"
        scroll-debounce=50
        on-load-more=(action loadMore) as |scroller|}}
        {{#each things as |thing|}}

{{thing.name}}

        {{/each}}
      {{/infinite-scroller}}
    `);

    find('.infinite-scroller').scrollTop = 450;

    later(() => {
      assert.equal(findAll('.thing').length, 20,
        'has not fired action due to custom debouncing of scroll event');
    }, 50);

    await settled();

    assert.equal(findAll('.thing').length, 40,
      'fires load more action after being debounced');
  });

  test('load more action (use-document)', async function(assert) {
    assert.expect(1);

    await render(hbs`
      {{#infinite-scroller
        class="example-2"
        use-document=true
        on-load-more=(action loadMore) as |scroller|}}
        {{#each things as |thing|}}

{{thing.name}}

        {{/each}}
      {{/infinite-scroller}}
    `);

    const fakeDocumentElement = {
      scrollTop: 1000,
      scrollHeight: 1000,
      clientHeight: 500
    };

    this.set('infiniteScroller.documentElement', fakeDocumentElement);

    await triggerEvent(document, 'scroll');

    await settled();

    assert.equal(findAll('.thing').length, 40,
      'fires load more action at the window scroll boundary');
  });

  test('loading class name', async function(assert) {
    assert.expect(3);

    const willLoad = defer();

    this.set('loadMore', () => {
      return willLoad.promise;
    });

    await render(hbs`
      {{#infinite-scroller on-load-more=(action loadMore) as |scroller|}}
        <button onclick={{action scroller.loadMore}}>Load more</button>
      {{/infinite-scroller}}
    `);

    assert.ok(!find('.infinite-scroller').classList.contains('is-loading'),
      'precondition: is not loading');

    await click('button');

    assert.ok(find('.infinite-scroller').classList.contains('is-loading'),
      'a loading class is added whilst the action is being performed');

    willLoad.resolve();

    await settled();

    assert.ok(!find('.infinite-scroller').classList.contains('is-loading'),
      'loading class name is removed after the action resolves');
  });

  test('yielded loading state', async function(assert) {
    assert.expect(3);

    const willLoad = defer();

    this.set('loadMore', () => {
      return willLoad.promise;
    });

    await render(hbs`
      {{#infinite-scroller on-load-more=(action loadMore) as |scroller|}}
        {{#if scroller.isLoading}}

Please wait...

        {{/if}}
        <button onclick={{action scroller.loadMore}}>Load more</button>
      {{/infinite-scroller}}
    `);

    assert.ok(!find('.infinite-scroller').textContent.match('Please wait...'),
      'precondition: is not loading');

    click('button');

    await waitFor('.infinite-scroller');

    assert.ok(find('.infinite-scroller').textContent.match('Please wait...'),
      'yields a hash with the loading state');

    willLoad.resolve();

    await settled();

    assert.ok(!find('.infinite-scroller').textContent.match('Please wait...'),
      'loading state is updated');
  });

  test('yielded error', async function(assert) {
    assert.expect(2);

    this.set('loadMore', () => {
      return reject(new Error('Fail'));
    });

    await render(hbs`
      {{#infinite-scroller on-load-more=(action loadMore) as |scroller|}}
        {{#if scroller.error}}

{{scroller.error.message}}

        {{/if}}
        <button onclick={{action scroller.loadMore}}>Load more</button>
      {{/infinite-scroller}}
    `);

    assert.ok(!find('.infinite-scroller').textContent.match('Fail'),
      'precondition: no error message');

    await click('button');

    assert.ok(find('.infinite-scroller').textContent.match('Fail'),
      'yields a hash with the last rejection error');
  });

  test('yielded loadMore action', async function(assert) {
    assert.expect(1);

    await render(hbs`
      {{#infinite-scroller on-load-more=(action loadMore) as |scroller|}}
        {{#each things as |thing|}}

{{thing.name}}

        {{/each}}
        <button onclick={{action scroller.loadMore}}>Load more</button>
      {{/infinite-scroller}}
    `);

    await click('button');

    assert.equal(findAll('.thing').length, 40,
      'yields an action that can trigger the load more action');
  });

  test('destroying (does not blow up)', async function(assert) {
    assert.expect(0);

    this.set('showScroller', true);

    const willLoad = defer();

    this.set('loadMore', () => {
      this.set('showScroller', false);
      return willLoad.promise;
    });

    await render(hbs`
      {{#if showScroller}}
        {{#infinite-scroller on-load-more=(action loadMore) as |scroller|}}
          <button onclick={{action scroller.loadMore}}>Load more</button>
        {{/infinite-scroller}}
      {{/if}}
    `);

    await click('button');

    willLoad.resolve();
  });

  test('no promise (does not blow up)', async function(assert) {
    assert.expect(0);

    this.set('loadMore', () => {
      return null;
    });

    await render(hbs`
      {{#infinite-scroller on-load-more=(action loadMore) as |scroller|}}
        <button onclick={{action scroller.loadMore}}>Load more</button>
      {{/infinite-scroller}}
    `);

    await click('button');
  });

  test('destroying during debounce (does not blow up)', async function(assert) {
    assert.expect(0);

    this.set('show', true);

    this.set('loadMore', () => {
      return null;
    });

    await render(hbs`
      {{#if show}}
        {{#infinite-scroller
          class="example-1"
          scroll-debounce=50
          on-load-more=(action loadMore) as |scroller|}}
          {{#each things as |thing|}}

{{thing.name}}

          {{/each}}
        {{/infinite-scroller}}
      {{/if}}
    `);

    find('.infinite-scroller').scrollTop = 450;

    later(() => {
      this.set('show', false);
    }, 25);
  });
});
<commit_msg>attempt to make tests more reliable, which pass locally, but fail on travis<commit_after>import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile';
import { defer, reject } from 'rsvp';
import { later } from '@ember/runloop';
import generateThings from 'dummy/utils/generate-things';
import {
  render,
  settled,
  triggerEvent,
  find,
  findAll,
  click,
  waitFor,
  waitUntil
} from '@ember/test-helpers';

module('infinite-scroller', function(hooks) {
  setupRenderingTest(hooks);

  hooks.beforeEach(function() {
    this.infiniteScroller = this.owner.lookup('service:-infinite-scroller');

    this.set('things', generateThings(1, 20));

    this.set('loadMore', () => {
      this.get('things').pushObjects(generateThings(21, 40));
    });
  });

  test('it renders', async function(assert) {
    assert.expect(1);

    await render(hbs`{{infinite-scroller}}`);

    assert.equal(findAll('.infinite-scroller').length, 1,
      'infinite scroller component has an appropriate class name');
  });

  test('load more action', async function(assert) {
    assert.expect(2);

    await render(hbs`
      {{#infinite-scroller
        class="example-1"
        on-load-more=(action loadMore) as |scroller|}}
        {{#each things as |thing|}}

{{thing.name}}

        {{/each}}
      {{/infinite-scroller}}
    `);

    find('.infinite-scroller').scrollTop = 450;

    later(() => {
      assert.equal(findAll('.thing').length, 20,
        'has not fired load more action due to debouncing of scroll event');
    }, 100);

    await waitUntil(() => findAll('.thing').length === 40);

    assert.ok(true,
      'fires load more action at the element scroll boundary');
  });

  test('load more action (trigger-at)', async function(assert) {
    assert.expect(1);

    await render(hbs`
      {{#infinite-scroller
        class="example-1"
        trigger-at="50%"
        on-load-more=(action loadMore) as |scroller|}}
        {{#each things as |thing|}}

{{thing.name}}

        {{/each}}
      {{/infinite-scroller}}
    `);

    find('.infinite-scroller').scrollTop = 225;

    await waitUntil(() => findAll('.thing').length === 40);

    assert.ok(true,
      'fires load more action at the custom element scroll boundary');
  });

  test('load more action (scroll-debounce)', async function(assert) {
    assert.expect(2);

    await render(hbs`
      {{#infinite-scroller
        class="example-1"
        scroll-debounce=50
        on-load-more=(action loadMore) as |scroller|}}
        {{#each things as |thing|}}

{{thing.name}}

        {{/each}}
      {{/infinite-scroller}}
    `);

    find('.infinite-scroller').scrollTop = 450;

    later(() => {
      assert.equal(findAll('.thing').length, 20,
        'has not fired action due to custom debouncing of scroll event');
    }, 50);

    await waitUntil(() => findAll('.thing').length === 40);

    assert.ok(true,
      'fires load more action after being debounced');
  });

  test('load more action (use-document)', async function(assert) {
    assert.expect(1);

    await render(hbs`
      {{#infinite-scroller
        class="example-2"
        use-document=true
        on-load-more=(action loadMore) as |scroller|}}
        {{#each things as |thing|}}

{{thing.name}}

        {{/each}}
      {{/infinite-scroller}}
    `);

    const fakeDocumentElement = {
      scrollTop: 1000,
      scrollHeight: 1000,
      clientHeight: 500
    };

    this.set('infiniteScroller.documentElement', fakeDocumentElement);

    await triggerEvent(document, 'scroll');

    await waitUntil(() => findAll('.thing').length === 40);

    assert.ok(true,
      'fires load more action at the window scroll boundary');
  });

  test('loading class name', async function(assert) {
    assert.expect(3);

    const willLoad = defer();

    this.set('loadMore', () => {
      return willLoad.promise;
    });

    await render(hbs`
      {{#infinite-scroller on-load-more=(action loadMore) as |scroller|}}
        <button onclick={{action scroller.loadMore}}>Load more</button>
      {{/infinite-scroller}}
    `);

    assert.ok(!find('.infinite-scroller').classList.contains('is-loading'),
      'precondition: is not loading');

    await click('button');

    assert.ok(find('.infinite-scroller').classList.contains('is-loading'),
      'a loading class is added whilst the action is being performed');

    willLoad.resolve();

    await settled();

    assert.ok(!find('.infinite-scroller').classList.contains('is-loading'),
      'loading class name is removed after the action resolves');
  });

  test('yielded loading state', async function(assert) {
    assert.expect(3);

    const willLoad = defer();

    this.set('loadMore', () => {
      return willLoad.promise;
    });

    await render(hbs`
      {{#infinite-scroller on-load-more=(action loadMore) as |scroller|}}
        {{#if scroller.isLoading}}

Please wait...

        {{/if}}
        <button onclick={{action scroller.loadMore}}>Load more</button>
      {{/infinite-scroller}}
    `);

    assert.ok(!find('.infinite-scroller').textContent.match('Please wait...'),
      'precondition: is not loading');

    click('button');

    await waitFor('.infinite-scroller');

    assert.ok(find('.infinite-scroller').textContent.match('Please wait...'),
      'yields a hash with the loading state');

    willLoad.resolve();

    await settled();

    assert.ok(!find('.infinite-scroller').textContent.match('Please wait...'),
      'loading state is updated');
  });

  test('yielded error', async function(assert) {
    assert.expect(2);

    this.set('loadMore', () => {
      return reject(new Error('Fail'));
    });

    await render(hbs`
      {{#infinite-scroller on-load-more=(action loadMore) as |scroller|}}
        {{#if scroller.error}}

{{scroller.error.message}}

        {{/if}}
        <button onclick={{action scroller.loadMore}}>Load more</button>
      {{/infinite-scroller}}
    `);

    assert.ok(!find('.infinite-scroller').textContent.match('Fail'),
      'precondition: no error message');

    await click('button');

    assert.ok(find('.infinite-scroller').textContent.match('Fail'),
      'yields a hash with the last rejection error');
  });

  test('yielded loadMore action', async function(assert) {
    assert.expect(1);

    await render(hbs`
      {{#infinite-scroller on-load-more=(action loadMore) as |scroller|}}
        {{#each things as |thing|}}

{{thing.name}}

        {{/each}}
        <button onclick={{action scroller.loadMore}}>Load more</button>
      {{/infinite-scroller}}
    `);

    await click('button');

    assert.equal(findAll('.thing').length, 40,
      'yields an action that can trigger the load more action');
  });

  test('destroying (does not blow up)', async function(assert) {
    assert.expect(0);

    this.set('showScroller', true);

    const willLoad = defer();

    this.set('loadMore', () => {
      this.set('showScroller', false);
      return willLoad.promise;
    });

    await render(hbs`
      {{#if showScroller}}
        {{#infinite-scroller on-load-more=(action loadMore) as |scroller|}}
          <button onclick={{action scroller.loadMore}}>Load more</button>
        {{/infinite-scroller}}
      {{/if}}
    `);

    await click('button');

    willLoad.resolve();
  });

  test('no promise (does not blow up)', async function(assert) {
    assert.expect(0);

    this.set('loadMore', () => {
      return null;
    });

    await render(hbs`
      {{#infinite-scroller on-load-more=(action loadMore) as |scroller|}}
        <button onclick={{action scroller.loadMore}}>Load more</button>
      {{/infinite-scroller}}
    `);

    await click('button');
  });

  test('destroying during debounce (does not blow up)', async function(assert) {
    assert.expect(0);

    this.set('show', true);

    this.set('loadMore', () => {
      return null;
    });

    await render(hbs`
      {{#if show}}
        {{#infinite-scroller
          class="example-1"
          scroll-debounce=50
          on-load-more=(action loadMore) as |scroller|}}
          {{#each things as |thing|}}

{{thing.name}}

          {{/each}}
        {{/infinite-scroller}}
      {{/if}}
    `);

    find('.infinite-scroller').scrollTop = 450;

    later(() => {
      this.set('show', false);
    }, 25);
  });
});
<|endoftext|>


<commit_before>var assert = require('assert');
var fs = require('fs');

var rimraf = require('rimraf');

var medea = require('../');

var directory = __dirname + '/tmp/medea_destroy_test';

describe('Medea#destroy', function() {
  before(function(done) {
    require('rimraf')(directory, function () {
      db = medea({ maxFileSize: 1024*1024 });
      db.open(directory, function(err) {
        done();
      });
    })
  });

  describe('initialized and closed db', function () {
    before(function (done) {
      rimraf(directory, function () {
        var db = medea();
        db.open(directory, function () {
          db.put('beep', 'boop', function () {
            db.close(done);
          });
        });
      });
    });

    it('deletes the folder', function (done) {
      medea.destroy(directory, function () {
        assert.equal(fs.existsSync(directory), false);
        done();
      });
    });
  });

  describe('folder with medea and some other files', function () {
    before(function (done) {
      rimraf(directory, function () {
        var db = medea();
        db.open(directory, function() {
          db.put('beep', 'boop', function () {
            db.close(function () {
              fs.writeFileSync(directory + '/beep.boop', 'beep boop');
              fs.writeFileSync(directory + '/hello.world', 'hello, world!');
              done();
            });
          });
        });
      });
    });

    it('deletes the medea files, but not the folder', function (done) {
      medea.destroy(directory, function (err) {
        if (err) return done(err);

        var filenames = fs.readdirSync(directory).sort();

        assert.deepEqual(filenames, [ 'beep.boop', 'hello.world' ]);
        done();
      });
    });
  });

});
<commit_msg>cleanup<commit_after>var assert = require('assert');
var fs = require('fs');

var rimraf = require('rimraf');

var medea = require('../');

var directory = __dirname + '/tmp/medea_destroy_test';

describe('Medea#destroy', function() {
  describe('initialized and closed db', function () {
    before(function (done) {
      rimraf(directory, function () {
        var db = medea();
        db.open(directory, function () {
          db.put('beep', 'boop', function () {
            db.close(done);
          });
        });
      });
    });

    it('deletes the folder', function (done) {
      medea.destroy(directory, function () {
        assert.equal(fs.existsSync(directory), false);
        done();
      });
    });
  });

  describe('folder with medea and some other files', function () {
    before(function (done) {
      rimraf(directory, function () {
        var db = medea();
        db.open(directory, function() {
          db.put('beep', 'boop', function () {
            db.close(function () {
              fs.writeFileSync(directory + '/beep.boop', 'beep boop');
              fs.writeFileSync(directory + '/hello.world', 'hello, world!');
              done();
            });
          });
        });
      });
    });

    it('deletes the medea files, but not the folder', function (done) {
      medea.destroy(directory, function (err) {
        if (err) return done(err);

        var filenames = fs.readdirSync(directory).sort();

        assert.deepEqual(filenames, [ 'beep.boop', 'hello.world' ]);
        done();
      });
    });
  });

});
<|endoftext|>


<commit_before>import React, { Component } from 'react'
import './index.css'
import PlayerCanvas from '../PlayerCanvas'
import ItemListing from '../ItemListing'
import EquippedItems from '../EquippedItems'
import CharacterProperties from '../CharacterProperties'
import _ from 'lodash'
import IntroModal from '../IntroModal'

class App extends Component {
  constructor(props) {
    super(props)

    let isOpen = (localStorage || {})['hideModal'] === 'true'
    if (isOpen === '' || isOpen === undefined || isOpen === 'undefined')
      isOpen = true

    this.state = {
      selectedItems: JSON.parse((localStorage || [])['selectedItems'] || '{}'),
      action: 'stand1',
      emotion: 'default',
      skin: Number(localStorage['skin']) || 2000,
      isModalOpen: isOpen
    }

    this.updateBannerAdBlur()
  }

  updateBannerAdBlur() {
    const topAd = document.getElementById("top-banner-ad")
    topAd.className = this.state.isModalOpen ? "modal-blur" : "";
  }

  render() {
    const { selectedItems, action, emotion, skin, isModalOpen } = this.state
    this.updateBannerAdBlur()

    return (

            MapleStory: Design

            Public Alpha A Crrio Project

Blog

Discord

        <PlayerCanvas
          selectedItems={_.values(selectedItems).map(item => item.Id)}
          action={action}
          emotion={emotion}
          skin={skin} />
        <ItemListing onItemSelected={this.userSelectedItem.bind(this)} />
        <EquippedItems
          equippedItems={selectedItems}
          skinId={skin}
          onRemoveItem={this.userRemovedItem.bind(this)}
          onRemoveItems={this.userRemovedItems.bind(this)} />
        <CharacterProperties
          equippedItems={selectedItems}
          action={action}
          emotion={emotion}
          skin={skin}
          onChangeAction={this.userChangedAction.bind(this)}
          onChangeEmotion={this.userChangedEmotion.bind(this)}
          onChangeSkin={this.userChangedSkin.bind(this)} />
        <IntroModal
          isOpen={isModalOpen}
          onSetModalOpen={this.setModalOpen.bind(this)} />

    )
  }

  setModalOpen (isModalOpen) {
    this.setState({ isModalOpen })
  }

  userChangedSkin (skin) {
    this.setState({ skin })
    localStorage['skin'] = skin
  }

  userChangedEmotion (emotion) {
    this.setState({ emotion })
    console.log('Changed emotion: ', emotion)
  }

  userChangedAction (action) {
    this.setState({ action })
    console.log('Changed action: ', action)
  }

  userSelectedItem (item) {
    let selectedItems = {
      ...this.state.selectedItems,
    }

    if (item.TypeInfo.SubCategory == 'Overall') {
      delete selectedItems['Top']
      delete selectedItems['Bottom']
    }

    selectedItems[item.TypeInfo.SubCategory] = item
    this.updateItems(selectedItems)
  }

  userRemovedItem (item) {
    let selectedItems = {
      ...this.state.selectedItems,
    }
    delete selectedItems[item.TypeInfo.SubCategory]
    this.updateItems(selectedItems);
  }

  userRemovedItems () {
    let selectedItems = {}
    this.updateItems(selectedItems);
  }

  updateItems (selectedItems) {
    console.log('New Items: ', selectedItems)
    this.setState({
      selectedItems
    })

    localStorage['selectedItems'] = JSON.stringify(selectedItems)
  }
}

export default App
<commit_msg>Correcting an internal tool<commit_after>import React, { Component } from 'react'
import './index.css'
import PlayerCanvas from '../PlayerCanvas'
import ItemListing from '../ItemListing'
import EquippedItems from '../EquippedItems'
import CharacterProperties from '../CharacterProperties'
import _ from 'lodash'
import IntroModal from '../IntroModal'

class App extends Component {
  constructor(props) {
    super(props)

    let isOpen = (localStorage || {})['hideModal'] !== 'true'
    if (isOpen === '' || isOpen === undefined || isOpen === 'undefined')
      isOpen = true

    this.state = {
      selectedItems: JSON.parse((localStorage || [])['selectedItems'] || '{}'),
      action: 'stand1',
      emotion: 'default',
      skin: Number(localStorage['skin']) || 2000,
      isModalOpen: isOpen
    }

    this.updateBannerAdBlur()
  }

  updateBannerAdBlur() {
    const topAd = document.getElementById("top-banner-ad")
    topAd.className = this.state.isModalOpen ? "modal-blur" : "";
  }

  render() {
    const { selectedItems, action, emotion, skin, isModalOpen } = this.state
    this.updateBannerAdBlur()

    return (

            MapleStory: Design

            Public Alpha A Crrio Project

Blog

Discord

        <PlayerCanvas
          selectedItems={_.values(selectedItems).map(item => item.Id)}
          action={action}
          emotion={emotion}
          skin={skin} />
        <ItemListing onItemSelected={this.userSelectedItem.bind(this)} />
        <EquippedItems
          equippedItems={selectedItems}
          skinId={skin}
          onRemoveItem={this.userRemovedItem.bind(this)}
          onRemoveItems={this.userRemovedItems.bind(this)} />
        <CharacterProperties
          equippedItems={selectedItems}
          action={action}
          emotion={emotion}
          skin={skin}
          onChangeAction={this.userChangedAction.bind(this)}
          onChangeEmotion={this.userChangedEmotion.bind(this)}
          onChangeSkin={this.userChangedSkin.bind(this)} />
        <IntroModal
          isOpen={isModalOpen}
          onSetModalOpen={this.setModalOpen.bind(this)} />

    )
  }

  setModalOpen (isModalOpen) {
    this.setState({ isModalOpen })
  }

  userChangedSkin (skin) {
    this.setState({ skin })
    localStorage['skin'] = skin
  }

  userChangedEmotion (emotion) {
    this.setState({ emotion })
    console.log('Changed emotion: ', emotion)
  }

  userChangedAction (action) {
    this.setState({ action })
    console.log('Changed action: ', action)
  }

  userSelectedItem (item) {
    let selectedItems = {
      ...this.state.selectedItems,
    }

    if (item.TypeInfo.SubCategory == 'Overall') {
      delete selectedItems['Top']
      delete selectedItems['Bottom']
    }

    selectedItems[item.TypeInfo.SubCategory] = item
    this.updateItems(selectedItems)
  }

  userRemovedItem (item) {
    let selectedItems = {
      ...this.state.selectedItems,
    }
    delete selectedItems[item.TypeInfo.SubCategory]
    this.updateItems(selectedItems);
  }

  userRemovedItems () {
    let selectedItems = {}
    this.updateItems(selectedItems);
  }

  updateItems (selectedItems) {
    console.log('New Items: ', selectedItems)
    this.setState({
      selectedItems
    })

    localStorage['selectedItems'] = JSON.stringify(selectedItems)
  }
}

export default App
<|endoftext|>


<commit_before>import React, { Component } from 'react'
import './index.css'
import PlayerCanvas from '../PlayerCanvas'
import ItemListing from '../ItemListing'
import EquippedItems from '../EquippedItems'
import CharacterProperties from '../CharacterProperties'
import _ from 'lodash'
import IntroModal from '../IntroModal'
import CharacterList from '../CharacterList'
import 'react-notifications/lib/notifications.css';
import {NotificationContainer, NotificationManager} from 'react-notifications';

var creatingId = null;

class App extends Component {
  constructor(props) {
    super(props)

    let isOpen = (localStorage || {})['hideModal'] !== 'true'
    if (isOpen === '' || isOpen === undefined || isOpen === 'undefined')
      isOpen = true

    // Try to recover any existing state
    this.state = {
      selectedItems: JSON.parse((localStorage || [])['selectedItems'] || '{}'),
      skin: Number(localStorage['skin']) || 2000,
      isModalOpen: isOpen,
      zoom: Number(localStorage['zoom']) || 1,
      frame: Number(localStorage['frame']) || 0,
      mercEars: localStorage['mercEars'] == "true" || localStorage['mercEars'] === true,
      illiumEars: localStorage['illiumEars'] == "true" || localStorage['illiumEars'] === true,
      characters: JSON.parse(localStorage['characters'] || 'false') || [false],
      pets: JSON.parse(localStorage['pets'] || 'false') || [],
      selectedIndex: JSON.parse(localStorage['selectedIndex'] || 'false') || 0
    }

    if (this.state.selectedIndex < 0) this.state.selectedIndex = false;

    // If we have a legacy character, upgrade to latest now
    if (!_.isEmpty(this.state.selectedItems || {})) {
      const currentCharacter = {
        selectedItems: this.state.selectedItems,
        skin: this.state.skin || 2000
      }
      if (this.state.characters[0] === false)
        this.state.characters[0] = currentCharacter;
      else
        this.state.characters.push(currentCharacter)
    }

    // If we have no characters at all, gen a new shell
    if (this.state.characters[0] === false)
      this.state.characters[0] = this.getNewCharacter()

    delete localStorage['selectedItems'];
    delete localStorage['skin'];
    delete localStorage['zoom'];
    delete localStorage['frame'];
    delete localStorage['mercEars'];
    delete localStorage['illiumEars'];

    if (localStorage['currentCharacter']) {
      this.state = JSON.parse(localStorage['currentCharacter'])
      delete localStorage['currentCharacter']
      localStorage['characters'] = JSON.stringify([...this.state.characters, this.state])
      this.state['characters'] = [...this.state.characters, this.state]
    }

    this.state.characters.forEach((character, index) => {
      if (!character.id) character.id = Date.now() + (index + 1)
      character.type = 'character'
      delete character.characters
      delete character.otherCharacters
      delete character.allCharacters
    })

    this.state.pets.forEach((pet, index) => {
      if (!pet.id) pet.id = Date.now() + (index + 1)
      pet.type = 'pet'
      pet.summary = `https://labs.maplestory.io/api/gms/latest/pet/${pet.petId}/${pet.animation || 'stand0'}/${pet.frame || 0}/${_.values(pet.selectedItems).map(item => item.Id).join(',')}?resize=${pet.zoom || 1}`
    })

    if ((this.state.selectedIndex + 1) > this.state.characters.length || !this.state.characters.length)
      this.state.selectedIndex = false;

    this.updateBannerAdBlur()
  }

  updateBannerAdBlur() {
    const topAd = document.getElementById("top-banner-ad")
    topAd.className = this.state.isModalOpen ? "modal-blur" : "";
  }

  render() {
    const { characters, pets, selectedIndex, selectedItems, action, emotion, skin, isModalOpen, mercEars, illiumEars, zoom, frame, summary } = this.state
    this.updateBannerAdBlur()

    const renderables = characters.concat(pets)

    return (

            MapleStory: Design

            Public Alpha

Blog

Discord

          {
            renderables
              .filter(renderable => renderable.visible)
              .map(renderable => {
                return (<PlayerCanvas summary={renderable.summary} key={'canvas' + renderable.id} onClick={this.userUpdateSelectedRenderable.bind(this, renderable)} />)
              })
          }

        { (selectedIndex !== false) ? <ItemListing target={renderables[selectedIndex]} onItemSelected={this.userSelectedItem.bind(this)} /> : '' }
        <CharacterList
          renderables={renderables}
          selectedIndex={selectedIndex}
          onAddCharacter={this.addCharacter.bind(this)}
          onAddPet={this.addPet.bind(this)}
          onDeleteCharacter={this.removeCharacter.bind(this)}
          onDeletePet={this.removePet.bind(this)}
          onUpdateSelectedCharacter={this.userUpdateSelectedRenderable.bind(this)}
          onUpdateCharacter={this.userUpdateCharacter.bind(this)}
          onUpdatePet={this.userUpdatePet.bind(this) }/>
        {
          (selectedIndex !== false && !_.isEmpty(renderables[selectedIndex].selectedItems) ? <EquippedItems
            equippedItems={renderables[selectedIndex].selectedItems}
            onRemoveItem={this.userRemovedItem.bind(this)}
            onUpdateItem={this.updateItem.bind(this)}
            onRemoveItems={this.userRemovedItems.bind(this)} /> : '')
        }

This project is actively being developed and considered a prototype.

        <IntroModal
          isOpen={isModalOpen}
          onSetModalOpen={this.setModalOpen.bind(this)} />
        <NotificationContainer />

    )
  }

  clickCanvas(e) {
    if (e.target == e.currentTarget && (this.state.characters.length + this.state.pets.length) > 1) {
      this.setState({ selectedIndex: false })
      localStorage['selectedIndex'] = 'false'
    }
  }

  addPet() {
    var pets = [...(this.state.pets || []), this.getNewPet()]
    this.setState({pets, selectedIndex: this.state.characters.length + this.state.pets.length})
    localStorage['pets'] = JSON.stringify(pets)
  }

  removePet(pet) {
    var pets = this.state.pets.filter(c => c != pet)
    this.setState({ pets, selectedIndex: false }) // Unselect any pet in case we delete the last pet
    localStorage['pets'] = JSON.stringify(pets)
  }

  getNewPet() {
    const andysFavePetIds = [5000000, 5000001, 5000002, 5000003, 5000004, 5000005]
    const petId = andysFavePetIds[Math.floor(Math.random() * andysFavePetIds.length)]
    return {
      petId,
      selectedItems: [],
      id: Date.now(),
      type: 'pet',
      summary: `https://labs.maplestory.io/api/gms/latest/pet/${petId}/stand0`,
      animation: 'stand0',
      visible: true,
      frame: 0,
      zoom: 1
    }
  }

  addCharacter() {
    var characters = [ ...this.state.characters, this.getNewCharacter() ]
    this.setState({ characters, selectedIndex: this.state.characters.length })
    localStorage['characters'] = JSON.stringify(characters)
  }

  removeCharacter(character) {
    var characters = this.state.characters.filter(c => c != character)
    this.setState({ characters, selectedIndex: false }) // Unselect any character in case we delete the last character
    localStorage['characters'] = JSON.stringify(characters)
  }

  userUpdateSelectedRenderable(renderable) {
    let selectedIndex = this.state.characters.indexOf(renderable)
    if (selectedIndex == -1) {
      selectedIndex = this.state.pets.indexOf(renderable)
      if (selectedIndex != -1) selectedIndex += this.state.characters.length
    }
    this.setState({
      selectedIndex
    })
    localStorage['selectedIndex'] = selectedIndex
  }

  userUpdatePet(pet, newProps) {
    if (pet.locked === true && !newProps.locked) {
      NotificationManager.error('Pet is locked', 'Error', 1000)
      return;
    }

    const pets = [...this.state.pets]
    const petIndex = pets.indexOf(pet)

    const currentPet = pets[petIndex] = {
      ...pet,
      ...newProps
    }

    currentPet.summary = `https://labs.maplestory.io/api/gms/latest/pet/${currentPet.petId}/${currentPet.animation || 'stand0'}/${currentPet.frame || 0}/${_.values(currentPet.selectedItems).map(item => item.Id).join(',')}?resize=${currentPet.zoom || 1}`

    this.setState({
        pets: pets
    })
    localStorage['pets'] = JSON.stringify(pets)
  }

  userUpdateCharacter(character, newProps) {
    if (character.locked === true && !newProps.locked) {
      NotificationManager.error('Character is locked', 'Error', 1000)
      return;
    }

    const characters = [...this.state.characters]
    const characterIndex = characters.indexOf(character)

    const currentCharacter = characters[characterIndex] = {
      ...character,
      ...newProps
    }

    const itemsWithEmotion = _.values(currentCharacter.selectedItems)
      .filter(item => item.Id && (item.visible === undefined || item.visible))
      .map(item => {
        var itemEntry = item.Id >= 20000 && item.Id <= 29999 ? `${item.Id}:${currentCharacter.emotion}` : item.Id
        if (item.hue) itemEntry = itemEntry + ';' + item.hue
        return itemEntry
      });
    currentCharacter.summary = `https://labs.maplestory.io/api/gms/latest/character/${currentCharacter.skin}/${(itemsWithEmotion.join(',') || 1102039)}/${currentCharacter.action}/${currentCharacter.frame}?showears=${currentCharacter.mercEars}&showLefEars=${currentCharacter.illiumEars}&resize=${currentCharacter.zoom}`

    this.setState({
        characters: characters
    })
    localStorage['characters'] = JSON.stringify(characters)
  }

  getNewCharacter() {
    return {
      id: Date.now(),
      action: 'stand1',
      emotion: 'default',
      skin: 2000,
      zoom: 1,
      frame: 0,
      mercEars: false,
      illiumEars: false,
      selectedItems: [],
      visible: true,
      summary: `https://labs.maplestory.io/api/gms/latest/character/2000/1102039/stand1/0?showears=false&showLefEars=false&resize=1`
    }
  }

  updateSelectedRenderable(props) {
    if (this.state.selectedIndex+1 > this.state.characters.length)
      this.userUpdatePet(this.state.pets[this.state.selectedIndex - this.state.characters.length], props)
    else
      this.userUpdateCharacter(this.state.characters[this.state.selectedIndex], props)
  }

  setModalOpen (isModalOpen) {
    this.setState({ isModalOpen })
  }

  userSelectedItem (item) {
    let selectedRenderable = null
    if (this.state.selectedIndex+1 > this.state.characters.length) selectedRenderable = this.state.pets[this.state.selectedIndex - this.state.characters.length]

    let selectedItems = {
      ...selectedRenderable.selectedItems,
    }

    if (item.TypeInfo) {
      if (item.TypeInfo.SubCategory === 'Overall') {
        delete selectedItems['Top']
        delete selectedItems['Bottom']
      }
    }

    if (item.similar) {
      item = { ...item }
      delete item['similar']
    }

    if (item.TypeInfo) {
      selectedItems[item.TypeInfo.SubCategory] = item
    }
    this.updateItems(selectedItems)
  }

  userRemovedItem (item) {
    let selectedItems = {
      ...this.state.characters[this.state.selectedIndex].selectedItems,
    }
    delete selectedItems[item.TypeInfo.SubCategory]
    this.updateItems(selectedItems);
  }

  userRemovedItems () {
    let selectedItems = {}
    this.updateItems(selectedItems);
  }

  updateItem (item, newProps) {
    let selectedItems = {
      ...this.state.characters[this.state.selectedIndex].selectedItems,
    }
    selectedItems[item.TypeInfo.SubCategory] = {
      ...item,
      ...newProps
    }
    this.updateItems(selectedItems);
  }

  updateItems (selectedItems) {
    console.log('New Items: ', selectedItems)
    this.updateSelectedRenderable({
      selectedItems
    })
  }
}

export default App
<commit_msg>Correcting not being able to select items for characters, correcting not being able to unlock characters<commit_after>import React, { Component } from 'react'
import './index.css'
import PlayerCanvas from '../PlayerCanvas'
import ItemListing from '../ItemListing'
import EquippedItems from '../EquippedItems'
import CharacterProperties from '../CharacterProperties'
import _ from 'lodash'
import IntroModal from '../IntroModal'
import CharacterList from '../CharacterList'
import 'react-notifications/lib/notifications.css';
import {NotificationContainer, NotificationManager} from 'react-notifications';

var creatingId = null;

class App extends Component {
  constructor(props) {
    super(props)

    let isOpen = (localStorage || {})['hideModal'] !== 'true'
    if (isOpen === '' || isOpen === undefined || isOpen === 'undefined')
      isOpen = true

    // Try to recover any existing state
    this.state = {
      selectedItems: JSON.parse((localStorage || [])['selectedItems'] || '{}'),
      skin: Number(localStorage['skin']) || 2000,
      isModalOpen: isOpen,
      zoom: Number(localStorage['zoom']) || 1,
      frame: Number(localStorage['frame']) || 0,
      mercEars: localStorage['mercEars'] == "true" || localStorage['mercEars'] === true,
      illiumEars: localStorage['illiumEars'] == "true" || localStorage['illiumEars'] === true,
      characters: JSON.parse(localStorage['characters'] || 'false') || [false],
      pets: JSON.parse(localStorage['pets'] || 'false') || [],
      selectedIndex: JSON.parse(localStorage['selectedIndex'] || 'false') || 0
    }

    if (this.state.selectedIndex < 0) this.state.selectedIndex = false;

    // If we have a legacy character, upgrade to latest now
    if (!_.isEmpty(this.state.selectedItems || {})) {
      const currentCharacter = {
        selectedItems: this.state.selectedItems,
        skin: this.state.skin || 2000
      }
      if (this.state.characters[0] === false)
        this.state.characters[0] = currentCharacter;
      else
        this.state.characters.push(currentCharacter)
    }

    // If we have no characters at all, gen a new shell
    if (this.state.characters[0] === false)
      this.state.characters[0] = this.getNewCharacter()

    delete localStorage['selectedItems'];
    delete localStorage['skin'];
    delete localStorage['zoom'];
    delete localStorage['frame'];
    delete localStorage['mercEars'];
    delete localStorage['illiumEars'];

    if (localStorage['currentCharacter']) {
      this.state = JSON.parse(localStorage['currentCharacter'])
      delete localStorage['currentCharacter']
      localStorage['characters'] = JSON.stringify([...this.state.characters, this.state])
      this.state['characters'] = [...this.state.characters, this.state]
    }

    this.state.characters.forEach((character, index) => {
      if (!character.id) character.id = Date.now() + (index + 1)
      character.type = 'character'
      delete character.characters
      delete character.otherCharacters
      delete character.allCharacters
    })

    this.state.pets.forEach((pet, index) => {
      if (!pet.id) pet.id = Date.now() + (index + 1)
      pet.type = 'pet'
      pet.summary = `https://labs.maplestory.io/api/gms/latest/pet/${pet.petId}/${pet.animation || 'stand0'}/${pet.frame || 0}/${_.values(pet.selectedItems).map(item => item.Id).join(',')}?resize=${pet.zoom || 1}`
    })

    if ((this.state.selectedIndex + 1) > this.state.characters.length || !this.state.characters.length)
      this.state.selectedIndex = false;

    this.updateBannerAdBlur()
  }

  updateBannerAdBlur() {
    const topAd = document.getElementById("top-banner-ad")
    topAd.className = this.state.isModalOpen ? "modal-blur" : "";
  }

  render() {
    const { characters, pets, selectedIndex, selectedItems, action, emotion, skin, isModalOpen, mercEars, illiumEars, zoom, frame, summary } = this.state
    this.updateBannerAdBlur()

    const renderables = characters.concat(pets)

    return (

            MapleStory: Design

            Public Alpha

Blog

Discord

          {
            renderables
              .filter(renderable => renderable.visible)
              .map(renderable => {
                return (<PlayerCanvas summary={renderable.summary} key={'canvas' + renderable.id} onClick={this.userUpdateSelectedRenderable.bind(this, renderable)} />)
              })
          }

        { (selectedIndex !== false) ? <ItemListing target={renderables[selectedIndex]} onItemSelected={this.userSelectedItem.bind(this)} /> : '' }
        <CharacterList
          renderables={renderables}
          selectedIndex={selectedIndex}
          onAddCharacter={this.addCharacter.bind(this)}
          onAddPet={this.addPet.bind(this)}
          onDeleteCharacter={this.removeCharacter.bind(this)}
          onDeletePet={this.removePet.bind(this)}
          onUpdateSelectedCharacter={this.userUpdateSelectedRenderable.bind(this)}
          onUpdateCharacter={this.userUpdateCharacter.bind(this)}
          onUpdatePet={this.userUpdatePet.bind(this) }/>
        {
          (selectedIndex !== false && !_.isEmpty(renderables[selectedIndex].selectedItems) ? <EquippedItems
            equippedItems={renderables[selectedIndex].selectedItems}
            onRemoveItem={this.userRemovedItem.bind(this)}
            onUpdateItem={this.updateItem.bind(this)}
            onRemoveItems={this.userRemovedItems.bind(this)} /> : '')
        }

This project is actively being developed and considered a prototype.

        <IntroModal
          isOpen={isModalOpen}
          onSetModalOpen={this.setModalOpen.bind(this)} />
        <NotificationContainer />

    )
  }

  clickCanvas(e) {
    if (e.target == e.currentTarget && (this.state.characters.length + this.state.pets.length) > 1) {
      this.setState({ selectedIndex: false })
      localStorage['selectedIndex'] = 'false'
    }
  }

  addPet() {
    var pets = [...(this.state.pets || []), this.getNewPet()]
    this.setState({pets, selectedIndex: this.state.characters.length + this.state.pets.length})
    localStorage['pets'] = JSON.stringify(pets)
  }

  removePet(pet) {
    var pets = this.state.pets.filter(c => c != pet)
    this.setState({ pets, selectedIndex: false }) // Unselect any pet in case we delete the last pet
    localStorage['pets'] = JSON.stringify(pets)
  }

  getNewPet() {
    const andysFavePetIds = [5000000, 5000001, 5000002, 5000003, 5000004, 5000005]
    const petId = andysFavePetIds[Math.floor(Math.random() * andysFavePetIds.length)]
    return {
      petId,
      selectedItems: [],
      id: Date.now(),
      type: 'pet',
      summary: `https://labs.maplestory.io/api/gms/latest/pet/${petId}/stand0`,
      animation: 'stand0',
      visible: true,
      frame: 0,
      zoom: 1
    }
  }

  addCharacter() {
    var characters = [ ...this.state.characters, this.getNewCharacter() ]
    this.setState({ characters, selectedIndex: this.state.characters.length })
    localStorage['characters'] = JSON.stringify(characters)
  }

  removeCharacter(character) {
    var characters = this.state.characters.filter(c => c != character)
    this.setState({ characters, selectedIndex: false }) // Unselect any character in case we delete the last character
    localStorage['characters'] = JSON.stringify(characters)
  }

  userUpdateSelectedRenderable(renderable) {
    let selectedIndex = this.state.characters.indexOf(renderable)
    if (selectedIndex == -1) {
      selectedIndex = this.state.pets.indexOf(renderable)
      if (selectedIndex != -1) selectedIndex += this.state.characters.length
    }
    this.setState({
      selectedIndex
    })
    localStorage['selectedIndex'] = selectedIndex
  }

  userUpdatePet(pet, newProps) {
    if (pet.locked === true && newProps.locked === undefined) {
      NotificationManager.error('Pet is locked', 'Error', 1000)
      return;
    }

    const pets = [...this.state.pets]
    const petIndex = pets.indexOf(pet)

    const currentPet = pets[petIndex] = {
      ...pet,
      ...newProps
    }

    currentPet.summary = `https://labs.maplestory.io/api/gms/latest/pet/${currentPet.petId}/${currentPet.animation || 'stand0'}/${currentPet.frame || 0}/${_.values(currentPet.selectedItems).map(item => item.Id).join(',')}?resize=${currentPet.zoom || 1}`

    this.setState({
        pets: pets
    })
    localStorage['pets'] = JSON.stringify(pets)
  }

  userUpdateCharacter(character, newProps) {
    if (character.locked === true && newProps.locked === undefined) {
      NotificationManager.error('Character is locked', 'Error', 1000)
      return;
    }

    const characters = [...this.state.characters]
    const characterIndex = characters.indexOf(character)

    const currentCharacter = characters[characterIndex] = {
      ...character,
      ...newProps
    }

    const itemsWithEmotion = _.values(currentCharacter.selectedItems)
      .filter(item => item.Id && (item.visible === undefined || item.visible))
      .map(item => {
        var itemEntry = item.Id >= 20000 && item.Id <= 29999 ? `${item.Id}:${currentCharacter.emotion}` : item.Id
        if (item.hue) itemEntry = itemEntry + ';' + item.hue
        return itemEntry
      });
    currentCharacter.summary = `https://labs.maplestory.io/api/gms/latest/character/${currentCharacter.skin}/${(itemsWithEmotion.join(',') || 1102039)}/${currentCharacter.action}/${currentCharacter.frame}?showears=${currentCharacter.mercEars}&showLefEars=${currentCharacter.illiumEars}&resize=${currentCharacter.zoom}`

    this.setState({
        characters: characters
    })
    localStorage['characters'] = JSON.stringify(characters)
  }

  getNewCharacter() {
    return {
      id: Date.now(),
      action: 'stand1',
      emotion: 'default',
      skin: 2000,
      zoom: 1,
      frame: 0,
      mercEars: false,
      illiumEars: false,
      selectedItems: [],
      visible: true,
      summary: `https://labs.maplestory.io/api/gms/latest/character/2000/1102039/stand1/0?showears=false&showLefEars=false&resize=1`
    }
  }

  updateSelectedRenderable(props) {
    if (this.state.selectedIndex+1 > this.state.characters.length)
      this.userUpdatePet(this.state.pets[this.state.selectedIndex - this.state.characters.length], props)
    else
      this.userUpdateCharacter(this.state.characters[this.state.selectedIndex], props)
  }

  setModalOpen (isModalOpen) {
    this.setState({ isModalOpen })
  }

  userSelectedItem (item) {
    let selectedRenderable = null
    if (this.state.selectedIndex+1 > this.state.characters.length) selectedRenderable = this.state.pets[this.state.selectedIndex - this.state.characters.length]
    else selectedRenderable = this.state.characters[this.state.selectedIndex]

    let selectedItems = {
      ...selectedRenderable.selectedItems,
    }

    if (item.TypeInfo) {
      if (item.TypeInfo.SubCategory === 'Overall') {
        delete selectedItems['Top']
        delete selectedItems['Bottom']
      }
    }

    if (item.similar) {
      item = { ...item }
      delete item['similar']
    }

    if (item.TypeInfo) {
      selectedItems[item.TypeInfo.SubCategory] = item
    }
    this.updateItems(selectedItems)
  }

  userRemovedItem (item) {
    let selectedItems = {
      ...this.state.characters[this.state.selectedIndex].selectedItems,
    }
    delete selectedItems[item.TypeInfo.SubCategory]
    this.updateItems(selectedItems);
  }

  userRemovedItems () {
    let selectedItems = {}
    this.updateItems(selectedItems);
  }

  updateItem (item, newProps) {
    let selectedItems = {
      ...this.state.characters[this.state.selectedIndex].selectedItems,
    }
    selectedItems[item.TypeInfo.SubCategory] = {
      ...item,
      ...newProps
    }
    this.updateItems(selectedItems);
  }

  updateItems (selectedItems) {
    console.log('New Items: ', selectedItems)
    this.updateSelectedRenderable({
      selectedItems
    })
  }
}

export default App
<|endoftext|>


<commit_before>import CSSModules from 'react-css-modules';
import React from 'react';

import styles from './card.css';

type Props = {
  children: any,
  /** add any other props to the component */
  other?: any,
  styles: any,
  /** html element */
  component?: string
}

function Card({ children, styles: CSSStyles, component: Component, ...other }: Props) {
  return (
    <Component styleName='card' {...other}>
      {children}
    </Component>
  );
}

export default CSSModules(Card, styles);
<commit_msg>change to card<commit_after>import CSSModules from 'react-css-modules';
import React from 'react';

import styles from './card.css';

type Props = {
  children: any,
  /** add any other props to the component */
  other?: any,
  styles: any,
  /** html element */
  component?: string
}

function Card({ children, styles: CSSStyles, component: Component, ...other }: Props) {
  return (
    <Component styleName='card' {...other}>
      {children}
    </Component>
  );
}

Card.defaultProps = {
  component: 'div'
};

export default CSSModules(Card, styles);
<|endoftext|>


<commit_before>/*
 *
 * List reducer
 *
 */

import { fromJS, List } from 'immutable';
import { findIndex, get } from 'lodash';
import {
  EMPTY_STORE,
  GET_MODEL_ENTRIES_SUCCEEDED,
  LOAD_MODELS,
  LOADED_MODELS,
  MOVE_ATTR,
  MOVE_ATTR_EDIT_VIEW,
  MOVE_VARIABLE_ATTR_EDIT_VIEW,
  ON_CHANGE,
  ON_CHANGE_SETTINGS,
  ON_CLICK_ADD_ATTR,
  ON_REMOVE,
  ON_REMOVE_EDIT_VIEW_ATTR,
  ON_REMOVE_EDIT_VIEW_FIELD_ATTR,
  ON_RESET,
  SUBMIT_SUCCEEDED,
} from './constants';

const initialState = fromJS({
  formValidations: List([]),
  loading: true,
  modelEntries: 0,
  modifiedSchema: fromJS({}),
  schema: fromJS({}),
  submitSuccess: false,
});

const getBootStrapCol = attrType => {
  switch(attrType) {
    case 'checkbox':
    case 'boolean':
      return 3;
    case 'date':
      return 4;
    case 'json':
    case 'wysiwyg':
      return 12;
    default:
      return 6;
  }
};

function appReducer(state = initialState, action) {
  switch (action.type) {
    case EMPTY_STORE:
      return state;
    case GET_MODEL_ENTRIES_SUCCEEDED:
      return state.set('modelEntries', action.count);
    case LOAD_MODELS:
      return state;
    case LOADED_MODELS:
      return state
        .update('schema', () => fromJS(action.models.models))
        .update('modifiedSchema', () => fromJS(action.models.models))
        .set('loading', false);
    case MOVE_ATTR:
      return state
        .updateIn(['modifiedSchema', 'models', ...action.keys.split('.'), 'listDisplay'], list => (
          list
            .delete(action.dragIndex)
            .insert(action.hoverIndex, list.get(action.dragIndex))
        ));
    case MOVE_ATTR_EDIT_VIEW:
      return state
        .updateIn(['modifiedSchema', 'models', ...action.keys.split('.')], list => (
          list
            .delete(action.dragIndex)
            .insert(action.hoverIndex, list.get(action.dragIndex))
        ));
    case MOVE_VARIABLE_ATTR_EDIT_VIEW:
      return state
        .updateIn(['modifiedSchema', 'models', ...action.keys.split('.'), 'fields'], list => {
          return list
            .delete(action.dragIndex)
            .insert(action.hoverIndex, list.get(action.dragIndex));
        });
    case ON_CHANGE:
      return state
        // NOTE: need comments
        .updateIn(['modifiedSchema'].concat(action.keys), () => action.value)
        .updateIn(['modifiedSchema', 'models'], models => {
          return models
            .keySeq()
            .reduce((acc, current) => {

              if (current !== 'plugins') {
                return acc.setIn([current, action.keys[1]], action.value);
              }

              return acc
                .get(current)
                .keySeq()
                .reduce((acc1, curr) => {
                  return acc1
                    .getIn([current, curr])
                    .keySeq()
                    .reduce((acc2, curr1) => {

                      return acc2.setIn([ current, curr, curr1, action.keys[1]], action.value);
                    }, acc1);
                }, acc);
            }, models);
        });
    case ON_CHANGE_SETTINGS:
      return state
        .updateIn(['modifiedSchema', 'models'].concat(action.keys), () => action.value);
    case ON_CLICK_ADD_ATTR:
      return state
        .updateIn(['modifiedSchema', 'models', ...action.keys.split('.')], list => list.push(fromJS(action.data)));
    case ON_REMOVE:
      return state.updateIn(['modifiedSchema', 'models', ...action.keys.split('.'), 'listDisplay'], list => {

        // If the list is empty add the default Id attribute
        if (list.size -1 === 0) {
          const attrToAdd = state.getIn(['schema', 'models', ...action.keys.split('.'), 'listDisplay'])
            .filter(attr => {
              return attr.get('name') === '_id' || attr.get('name') === 'id';
            });

          attrToAdd.setIn(['0', 'sortable'], () => true);

          return list
            .delete(action.index)
            .push(attrToAdd.get('0'));
        }

        return list.delete(action.index);
      });
    case ON_REMOVE_EDIT_VIEW_ATTR:
      return state.updateIn(['modifiedSchema', 'models'].concat(action.keys.split('.')), list => {
        return list.delete(action.index);
      });
    case ON_REMOVE_EDIT_VIEW_FIELD_ATTR:
      return state.updateIn(['modifiedSchema', 'models', ...action.keys.split('.'), 'fields'], list => {
        // Don't do any check if removing the item of the array
        if (action.index === list.size - 1) {
          return list.delete(action.index);
        }

        // Helpers
        /**
         *
         * @param {String} itemName
         * @returns {String} the item's type
         */
        const getType = (itemName) => {
          return state.getIn(['modifiedSchema', 'models', ...action.keys.split('.'), 'availableFields', itemName, 'type']);
        };
        /**
         *
         * @param {Number} itemIndex
         * @returns {String} The item's name
         */
        const getAttrName = itemIndex => {
          return state.getIn(['modifiedSchema', 'models', ...action.keys.split('.'), 'fields', itemIndex]);
        };
        /**
         *
         * @param {Number} leftBound
         * @param {Number} rightBound
         * @returns {Number} The line's col number sum
         */
        const getLineColSize = (leftBound, rightBound) => list.slice(leftBound + 1, rightBound).reduce((acc, current) => {
          // Simulates the layout
          const type = getType(current);
          // Simulates the layout
          const col = current.includes('long') ? 12 : getBootStrapCol(type);

          return acc += col;
        }, 0);

        // Retrieve the removed element default col
        const attrToRemoveIndex = action.index;
        const attrNameToRemove = getAttrName(attrToRemoveIndex);
        const attrToRemoveType = getType(attrNameToRemove);
        const attrToRemoveBootstrapCol = attrNameToRemove.includes('long') ? 12 : getBootStrapCol(attrToRemoveType);

        // TODO handle deleting the last visible element of line

        const arrayOfLastLineElements = [];
        let sum = 0;

        list.forEach((item, index) => {
          // Retrieve the item's bootstrap col
          // Simulates the layout that is not available in the core_store yet
          const itemType = item.includes('long') ? 'wysiwyg' : getType(item);
          const itemName = getAttrName(index);
          const itemColNumber = getBootStrapCol(itemType);
          sum += itemColNumber;

          if (sum === 12 || itemColNumber === 12) {
            const isFullSize = itemColNumber === 12;
            arrayOfLastLineElements.push({ itemName, index, isFullSize });
            sum = 0;
          }

          if (sum > 12) {
            sum = 0;
          }
        });

         /**
         *
         * @param {Bool} dir sup or min
         * @param {Number} pivot the center of
         * @returns {Object} the first sup or last sup
         */
        const getBound = (dir, pivot = attrToRemoveIndex) => {
          let result = {};
          let hasResult = false;

          arrayOfLastLineElements.forEach(item => {
            const cond = dir === true ? item.index >= pivot && !hasResult : item.index <= pivot;

            if (cond) {
              hasResult = true;
              result = item;
            }
          });

          return result;
        };

        // Retrieve the removed element bounds
        const nodeBounds = { left: getBound(false), right: getBound(true) };
        const isRemovingAFullWidthNode = attrToRemoveBootstrapCol === 12;

        // If removing we need to add the corresponding missing col in the prev line
        if (isRemovingAFullWidthNode) {
          const currentNodeLine = findIndex(arrayOfLastLineElements, ['index', attrToRemoveIndex]);
          const previousLineBounds = { left: getBound(false, attrToRemoveIndex - 1), right: getBound(true, attrToRemoveIndex - 1) };
          const leftBoundIndex = get(previousLineBounds, ['left', 'index'], 0);
          const rightBoundIndex = get(previousLineBounds, ['right', 'index'], 0);
          const previousLineNumberOfItems = Math.abs(leftBoundIndex - rightBoundIndex) - 1;
          const previousLineColNumber = getLineColSize(leftBoundIndex, rightBoundIndex);

          // Don't add node if removing from the first line or after a complete line
          if (currentNodeLine === 0 || previousLineNumberOfItems === -1 || previousLineColNumber >= 10) {
            return list.delete(action.index);
          }

          const colNumberToAdd = 12 - previousLineColNumber;
          const colsToAdd = colNumberToAdd === 8 ? ['col-md-4'] : (() => {
            switch(colNumberToAdd) {
              case 9:
                return ['col-md-3', 'col-md-6'];
              case 6:
                return ['col-md-6'];
              default:
                return ['col-md-3'];

            }
          })();

          let newList = list.delete(attrToRemoveIndex);

          if (colsToAdd.length > 1) {
            const newList = newList
              .insert(attrToRemoveIndex, colsToAdd[0])
              .insert(attrToRemoveIndex, colsToAdd[1]);
          } else {
            newList = newList.insert(attrToRemoveIndex, colsToAdd[0]);
          }

          return newList;
        } else {
          const leftBoundIndex = get(nodeBounds, ['left', 'index'], 0);
          const rightBoundIndex = get(nodeBounds, ['right', 'index'], 0);
          const currentLineColSize = getLineColSize(leftBoundIndex, rightBoundIndex);
          const isRemovingLine = currentLineColSize - attrToRemoveBootstrapCol === 0;

          if (isRemovingLine) {
            return list.delete(attrToRemoveIndex);
          }

          const lasLineIndexToAddCol = rightBoundIndex;
          // Else, just replace the right bound with the corresponding removed col number
          return list
            .delete(attrToRemoveIndex)
            .insert(lasLineIndexToAddCol, `col-md-${attrToRemoveBootstrapCol}`);
        }
      });
    case ON_RESET:
      return state
        .update('modifiedSchema', () => state.get('schema'));
    case SUBMIT_SUCCEEDED:
      return state
        .update('submitSuccess', v => v = !v)
        .update('schema', () => state.get('modifiedSchema'));
    default:
      return state;
  }
}

export default appReducer;
<commit_msg>fix conflict<commit_after>/*
 *
 * List reducer
 *
 */

import { fromJS, List } from 'immutable';
import { findIndex, get } from 'lodash';
import {
  EMPTY_STORE,
  GET_MODEL_ENTRIES_SUCCEEDED,
  LOAD_MODELS,
  LOADED_MODELS,
  MOVE_ATTR,
  MOVE_ATTR_EDIT_VIEW,
  MOVE_VARIABLE_ATTR_EDIT_VIEW,
  ON_CHANGE,
  ON_CHANGE_SETTINGS,
  ON_CLICK_ADD_ATTR,
  ON_REMOVE,
  ON_REMOVE_EDIT_VIEW_ATTR,
  ON_REMOVE_EDIT_VIEW_FIELD_ATTR,
  ON_RESET,
  SUBMIT_SUCCEEDED,
} from './constants';

const initialState = fromJS({
  formValidations: List([]),
  loading: true,
  modelEntries: 0,
  modifiedSchema: fromJS({}),
  schema: fromJS({}),
  submitSuccess: false,
});

const getBootStrapCol = attrType => {
  switch(attrType) {
    case 'checkbox':
    case 'boolean':
      return 3;
    case 'date':
      return 4;
    case 'json':
    case 'wysiwyg':
      return 12;
    default:
      return 6;
  }
};

function appReducer(state = initialState, action) {
  switch (action.type) {
    case EMPTY_STORE:
      return state;
    case GET_MODEL_ENTRIES_SUCCEEDED:
      return state.set('modelEntries', action.count);
    case LOAD_MODELS:
      return state;
    case LOADED_MODELS:
      return state
        .update('schema', () => fromJS(action.models.models))
        .update('modifiedSchema', () => fromJS(action.models.models))
        .set('loading', false);
    case MOVE_ATTR:
      return state
        .updateIn(['modifiedSchema', 'models', ...action.keys.split('.'), 'listDisplay'], list => (
          list
            .delete(action.dragIndex)
            .insert(action.hoverIndex, list.get(action.dragIndex))
        ));
    case MOVE_ATTR_EDIT_VIEW:
      return state
        .updateIn(['modifiedSchema', 'models', ...action.keys.split('.')], list => (
          list
            .delete(action.dragIndex)
            .insert(action.hoverIndex, list.get(action.dragIndex))
        ));
    case MOVE_VARIABLE_ATTR_EDIT_VIEW:
      return state
        .updateIn(['modifiedSchema', 'models', ...action.keys.split('.'), 'fields'], list => {
          return list
            .delete(action.dragIndex)
            .insert(action.hoverIndex, list.get(action.dragIndex));
        });
    case ON_CHANGE:
      return state
        // NOTE: need comments
        .updateIn(['modifiedSchema'].concat(action.keys), () => action.value)
        .updateIn(['modifiedSchema', 'models'], models => {
          return models
            .keySeq()
            .reduce((acc, current) => {

              if (current !== 'plugins') {
                return acc.setIn([current, action.keys[1]], action.value);
              }

              return acc
                .get(current)
                .keySeq()
                .reduce((acc1, curr) => {
                  return acc1
                    .getIn([current, curr])
                    .keySeq()
                    .reduce((acc2, curr1) => {

                      return acc2.setIn([ current, curr, curr1, action.keys[1]], action.value);
                    }, acc1);
                }, acc);
            }, models);
        });
    case ON_CHANGE_SETTINGS:
      return state
        .updateIn(['modifiedSchema', 'models'].concat(action.keys), () => action.value);
    case ON_CLICK_ADD_ATTR:
      return state
        .updateIn(['modifiedSchema', 'models', ...action.keys.split('.')], list => list.push(fromJS(action.data)));
    case ON_REMOVE:
      return state.updateIn(['modifiedSchema', 'models', ...action.keys.split('.'), 'listDisplay'], list => {

        // If the list is empty add the default Id attribute
        if (list.size -1 === 0) {
          const attrToAdd = state.getIn(['schema', 'models', ...action.keys.split('.'), 'listDisplay'])
            .filter(attr => {
              return attr.get('name') === '_id' || attr.get('name') === 'id';
            });

          attrToAdd.setIn(['0', 'sortable'], () => true);

          return list
            .delete(action.index)
            .push(attrToAdd.get('0'));
        }

        return list.delete(action.index);
      });
    case ON_REMOVE_EDIT_VIEW_ATTR:
      return state.updateIn(['modifiedSchema', 'models'].concat(action.keys.split('.')), list => {
        return list.delete(action.index);
      });
    case ON_REMOVE_EDIT_VIEW_FIELD_ATTR:
      return state.updateIn(['modifiedSchema', 'models', ...action.keys.split('.'), 'fields'], list => {
        // Don't do any check if removing the item of the array
        if (action.index === list.size - 1) {
          return list.delete(action.index);
        }

        // Helpers
        /**
         *
         * @param {String} itemName
         * @returns {String} the item's type
         */
        const getType = (itemName) => {
          return state.getIn(['modifiedSchema', 'models', ...action.keys.split('.'), 'availableFields', itemName, 'type']);
        };
        /**
         *
         * @param {Number} itemIndex
         * @returns {String} The item's name
         */
        const getAttrName = itemIndex => {
          return state.getIn(['modifiedSchema', 'models', ...action.keys.split('.'), 'fields', itemIndex]);
        };
        /**
         *
         * @param {Number} leftBound
         * @param {Number} rightBound
         * @returns {Number} The line's col number sum
         */
        const getLineColSize = (leftBound, rightBound) => list.slice(leftBound + 1, rightBound).reduce((acc, current) => {
          // Simulates the layout
          const type = getType(current);
          // Simulates the layout
          const col = current.includes('long') ? 12 : getBootStrapCol(type);

          return acc += col;
        }, 0);

        // Retrieve the removed element default col
        const attrToRemoveIndex = action.index;
        const attrNameToRemove = getAttrName(attrToRemoveIndex);
        const attrToRemoveType = getType(attrNameToRemove);
        const attrToRemoveBootstrapCol = attrNameToRemove.includes('long') ? 12 : getBootStrapCol(attrToRemoveType);

        // TODO handle deleting the last visible element of line

        const arrayOfLastLineElements = [];
        let sum = 0;

        list.forEach((item, index) => {
          // Retrieve the item's bootstrap col
          // Simulates the layout that is not available in the core_store yet
          const itemType = item.includes('long') ? 'wysiwyg' : getType(item);
          const itemName = getAttrName(index);
          const itemColNumber = getBootStrapCol(itemType);
          sum += itemColNumber;

          if (sum === 12 || itemColNumber === 12) {
            const isFullSize = itemColNumber === 12;
            arrayOfLastLineElements.push({ itemName, index, isFullSize });
            sum = 0;
          }

          if (sum > 12) {
            sum = 0;
          }
        });

        /**
         *
         * @param {Bool} dir sup or min
         * @param {Number} pivot the center of
         * @returns {Object} the first sup or last sup
         */
        const getBound = (dir, pivot = attrToRemoveIndex) => {
          let result = {};
          let hasResult = false;

          arrayOfLastLineElements.forEach(item => {
            const cond = dir === true ? item.index >= pivot && !hasResult : item.index <= pivot;

            if (cond) {
              hasResult = true;
              result = item;
            }
          });

          return result;
        };

        // Retrieve the removed element bounds
        const nodeBounds = { left: getBound(false), right: getBound(true) };
        const isRemovingAFullWidthNode = attrToRemoveBootstrapCol === 12;

        // If removing we need to add the corresponding missing col in the prev line
        if (isRemovingAFullWidthNode) {
          const currentNodeLine = findIndex(arrayOfLastLineElements, ['index', attrToRemoveIndex]);
          const previousLineBounds = { left: getBound(false, attrToRemoveIndex - 1), right: getBound(true, attrToRemoveIndex - 1) };
          const leftBoundIndex = get(previousLineBounds, ['left', 'index'], 0);
          const rightBoundIndex = get(previousLineBounds, ['right', 'index'], 0);
          const previousLineNumberOfItems = Math.abs(leftBoundIndex - rightBoundIndex) - 1;
          const previousLineColNumber = getLineColSize(leftBoundIndex, rightBoundIndex);

          // Don't add node if removing from the first line or after a complete line
          if (currentNodeLine === 0 || previousLineNumberOfItems === -1 || previousLineColNumber >= 10) {
            return list.delete(action.index);
          }

          const colNumberToAdd = 12 - previousLineColNumber;
          const colsToAdd = colNumberToAdd === 8 ? ['col-md-4'] : (() => {
            switch(colNumberToAdd) {
              case 9:
                return ['col-md-3', 'col-md-6'];
              case 6:
                return ['col-md-6'];
              default:
                return ['col-md-3'];

            }
          })();

          let newList = list.delete(attrToRemoveIndex);

          if (colsToAdd.length > 1) {
            const newList = newList
              .insert(attrToRemoveIndex, colsToAdd[0])
              .insert(attrToRemoveIndex, colsToAdd[1]);
          } else {
            newList = newList.insert(attrToRemoveIndex, colsToAdd[0]);
          }

          return newList;
        } else {
          const leftBoundIndex = get(nodeBounds, ['left', 'index'], 0);
          const rightBoundIndex = get(nodeBounds, ['right', 'index'], 0);
          const currentLineColSize = getLineColSize(leftBoundIndex, rightBoundIndex);
          const isRemovingLine = currentLineColSize - attrToRemoveBootstrapCol === 0;

          if (isRemovingLine) {
            return list.delete(attrToRemoveIndex);
          }

          const lasLineIndexToAddCol = rightBoundIndex;
          // Else, just replace the right bound with the corresponding removed col number
          return list
            .delete(attrToRemoveIndex)
            .insert(lasLineIndexToAddCol, `col-md-${attrToRemoveBootstrapCol}`);
        }
      });
    case ON_RESET:
      return state
        .update('modifiedSchema', () => state.get('schema'));
    case SUBMIT_SUCCEEDED:
      return state
        .update('submitSuccess', v => v = !v)
        .update('schema', () => state.get('modifiedSchema'));
    default:
      return state;
  }
}

export default appReducer;<|endoftext|>


<commit_before>// Global jQuery references
var $shareModal = null;
var $commentCount = null;
var $goButton = null;
var $audioPlayer = null;

// Global state
var firstShareLoad = true;

/*
 * Run on page load.
 */
var onDocumentLoad = function(e) {
    // Cache jQuery references
    $shareModal = $('#share-modal');
    $commentCount = $('.comment-count');
    $goButton = $('.js-go');
    $audioPlayer = $('#audio-player');

    // Bind events
    $shareModal.on('shown.bs.modal', onShareModalShown);
    $shareModal.on('hidden.bs.modal', onShareModalHidden);
    $goButton.on('click', onGoButtonClick);
    $(window).on('resize', onWindowResize);

    // configure ZeroClipboard on share panel
    ZeroClipboard.config({ swfPath: 'js/lib/ZeroClipboard.swf' });
    var clippy = new ZeroClipboard($(".clippy"));

    clippy.on('ready', function(readyEvent) {
        clippy.on('aftercopy', onClippyCopy);
    });

    onWindowResize();
    // renderExampleTemplate();
    // getCommentCount(showCommentCount);
    SONG_DATA = _.shuffle(SONG_DATA);
    setupAudio();
}

var setupAudio = function() {
     $audioPlayer.jPlayer({
        ready: function () {
            $(this).jPlayer('setMedia', {
                mp3: '/assets/songs/folk_song.mp3'
            });

            // $(this).jPlayer('play');

        },
        // ended: obj.startArchiveStream,
        supplied: 'mp3',
        loop: false,
    });
}
/*
 * Basic templating example.
 */
var renderExampleTemplate = function() {
    var context = $.extend(APP_CONFIG, {
        'template_path': 'jst/example.html',
        'config': JSON.stringify(APP_CONFIG, null, 4),
        'copy': JSON.stringify(COPY, null, 4)
    });

    var html = JST.example(context);

    $('#template-example').html(html);
}

/*
 * Display the comment count.
 */
var showCommentCount = function(count) {
    $commentCount.text(count);

    if (count > 0) {
        $commentCount.addClass('has-comments');
    }

    if (count > 1) {
        $commentCount.next('.comment-label').text('Comments');
    }
}

/*
 * Share modal opened.
 */
var onShareModalShown = function(e) {
    _gaq.push(['_trackEvent', APP_CONFIG.PROJECT_SLUG, 'open-share-discuss']);

    if (firstShareLoad) {
        loadComments();

        firstShareLoad = false;
    }
}

/*
 * Share modal closed.
 */
var onShareModalHidden = function(e) {
    _gaq.push(['_trackEvent', APP_CONFIG.PROJECT_SLUG, 'close-share-discuss']);
}

/*
 * Text copied to clipboard.
 */
var onClippyCopy = function(e) {
    alert('Copied to your clipboard!');

    _gaq.push(['_trackEvent', APP_CONFIG.PROJECT_SLUG, 'summary-copied']);
}

/*
 * Fade in the next song of the playlist
 */
var showNewSong = function(e) {
    // $('.played-song').slideDown();
    $('.new-song').fadeIn();
    _.delay(function(){
        $('html, body').animate({
            scrollTop: $(".new-song").offset().top
        }, 500);
    }, 200);
}

/*
 * Fade in the next song of the playlist
 */
var onGoButtonClick = function(e) {
    $('.current-song, .player, .playlist-filters, .filter-head').fadeIn();

    $(this).fadeOut();

    // _.delay(function(){
    //         $('html, body').animate({
    //             scrollTop: $(".current-song").offset().top
    //         }, 500);
    //     }, 200);

    $('.current-song, .player, .playlist-filters').fadeIn();

    $('html, body').animate({
        scrollTop: $('.current-song').offset().top
    }, 1000);
}

/*
 * Fade in the next song of the playlist
 */
var onWindowResize = function(e) {
    $('.landing').css('height', $(window).height());
}

$(onDocumentLoad);
<commit_msg>Stub out JS structure.<commit_after>// Global jQuery references
var $shareModal = null;
var $commentCount = null;
var $goButton = null;
var $audioPlayer = null;

// Global state
var firstShareLoad = true;
var playedSongs = [];
var playlist = [];

/*
 * Run on page load.
 */
var onDocumentLoad = function(e) {
    // Cache jQuery references
    $shareModal = $('#share-modal');
    $commentCount = $('.comment-count');
    $goButton = $('.js-go');
    $audioPlayer = $('#audio-player');

    // Bind events
    $shareModal.on('shown.bs.modal', onShareModalShown);
    $shareModal.on('hidden.bs.modal', onShareModalHidden);
    $goButton.on('click', onGoButtonClick);
    $(window).on('resize', onWindowResize);

    // configure ZeroClipboard on share panel
    ZeroClipboard.config({ swfPath: 'js/lib/ZeroClipboard.swf' });
    var clippy = new ZeroClipboard($(".clippy"));

    clippy.on('ready', function(readyEvent) {
        clippy.on('aftercopy', onClippyCopy);
    });

    onWindowResize();
    SONG_DATA = _.shuffle(SONG_DATA);
    setupAudio();
    loadPlayedSongs();
    buildPlaylist();
    playNextSong();
}

var setupAudio = function() {
     $audioPlayer.jPlayer({
        ready: function () {
            $(this).jPlayer('setMedia', {
                mp3: '/assets/songs/folk_song.mp3'
            });

            // $(this).jPlayer('play');

        },
        ended: playNextSong,
        supplied: 'mp3',
        loop: false,
    });
}

/*
 * Play the next song in the playlist.
 */
var playNextSong = function() {
    // TODO
    // Loop over playlist until we find a song that hasn't been played
    // What do we do if we don't find one? (we've played them all)

    // render "last played" JST of current song
    // render "currently playing" JST of next song
    // replace current song with the next song
    // add last song to stack of played songs (history)
    // rebind events inside the two JST's (or have use jquery's live(), maybe)

    // Starting playing new song
    markSongPlayed();
}

/*
 * Load previously played songs from browser state (cookie, whatever)
 */
var loadPlayedSongs = function() {
    // TODO
}

/*
 * Mark the current song as played and save state.
 */
var markSongPlayed = function () {
    // TODO
    // Add song id to list of played songs
    // Stash in cookie
}

/*
 * Build a playlist from a set of tags.
 */
var buildPlaylist = function(tags) {
    var playlist = [];

    // TODO
    // filter SONG_DATA, probably with _.filter()

    return playlist;
}

/*
 * Handle clicks on tags.
 */
var onTagClick = function(e) {
    e.preventDefault();

    playlist = buildPlaylist();

    // TODO
    // update display of songs in queue
    // if current song has this tag, stop it and playNextSong();
}

/*
 * Skip the current song.
 */
var onSkipClick = function(e) {
    e.preventDefault();

    playNextSong();
}

/*
 * Basic templating example.
 */
var renderExampleTemplate = function() {
    var context = $.extend(APP_CONFIG, {
        'template_path': 'jst/example.html',
        'config': JSON.stringify(APP_CONFIG, null, 4),
        'copy': JSON.stringify(COPY, null, 4)
    });

    var html = JST.example(context);

    $('#template-example').html(html);
}

/*
 * Display the comment count.
 */
var showCommentCount = function(count) {
    $commentCount.text(count);

    if (count > 0) {
        $commentCount.addClass('has-comments');
    }

    if (count > 1) {
        $commentCount.next('.comment-label').text('Comments');
    }
}

/*
 * Share modal opened.
 */
var onShareModalShown = function(e) {
    _gaq.push(['_trackEvent', APP_CONFIG.PROJECT_SLUG, 'open-share-discuss']);

    if (firstShareLoad) {
        loadComments();

        firstShareLoad = false;
    }
}

/*
 * Share modal closed.
 */
var onShareModalHidden = function(e) {
    _gaq.push(['_trackEvent', APP_CONFIG.PROJECT_SLUG, 'close-share-discuss']);
}

/*
 * Text copied to clipboard.
 */
var onClippyCopy = function(e) {
    alert('Copied to your clipboard!');

    _gaq.push(['_trackEvent', APP_CONFIG.PROJECT_SLUG, 'summary-copied']);
}

/*
 * Fade in the next song of the playlist
 */
var showNewSong = function(e) {
    // $('.played-song').slideDown();
    $('.new-song').fadeIn();
    _.delay(function(){
        $('html, body').animate({
            scrollTop: $(".new-song").offset().top
        }, 500);
    }, 200);
}

/*
 * Fade in the next song of the playlist
 */
var onGoButtonClick = function(e) {
    $('.current-song, .player, .playlist-filters, .filter-head').fadeIn();

    $(this).fadeOut();

    // _.delay(function(){
    //         $('html, body').animate({
    //             scrollTop: $(".current-song").offset().top
    //         }, 500);
    //     }, 200);

    $('.current-song, .player, .playlist-filters').fadeIn();

    $('html, body').animate({
        scrollTop: $('.current-song').offset().top
    }, 1000);
}

/*
 * Fade in the next song of the playlist
 */
var onWindowResize = function(e) {
    $('.landing').css('height', $(window).height());
}

$(onDocumentLoad);
<|endoftext|>