<commit_before>import React from 'react';
import CharacterCounter from './character_counter';
import Button from '../../../components/button';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import ReplyIndicatorContainer from '../containers/reply_indicator_container';
import AutosuggestTextarea from '../../../components/autosuggest_textarea';
import { debounce } from 'lodash';
import UploadButtonContainer from '../containers/upload_button_container';
import { defineMessages, injectIntl } from 'react-intl';
import Collapsable from '../../../components/collapsable';
import SpoilerButtonContainer from '../containers/spoiler_button_container';
import PrivacyDropdownContainer from '../containers/privacy_dropdown_container';
import ComposeAdvancedOptionsContainer from '../../../../glitch/components/compose/advanced_options/container';
import SensitiveButtonContainer from '../containers/sensitive_button_container';
import EmojiPickerDropdown from './emoji_picker_dropdown';
import UploadFormContainer from '../containers/upload_form_container';
import WarningContainer from '../containers/warning_container';
import { isMobile } from '../../../is_mobile';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { length } from 'stringz';
import { countableText } from '../util/counter';

const messages = defineMessages({
  placeholder: { id: 'compose_form.placeholder', defaultMessage: 'What is on your mind?' },
  spoiler_placeholder: { id: 'compose_form.spoiler_placeholder', defaultMessage: 'Write your warning here' },
  publish: { id: 'compose_form.publish', defaultMessage: 'Toot' },
  publishLoud: { id: 'compose_form.publish_loud', defaultMessage: '{publish}!' },
});

@injectIntl
export default class ComposeForm extends ImmutablePureComponent {

  static propTypes = {
    intl: PropTypes.object.isRequired,
    text: PropTypes.string.isRequired,
    suggestion_token: PropTypes.string,
    suggestions: ImmutablePropTypes.list,
    spoiler: PropTypes.bool,
    privacy: PropTypes.string,
    advanced_options: ImmutablePropTypes.contains({
      do_not_federate: PropTypes.bool,
    }),
    spoiler_text: PropTypes.string,
    focusDate: PropTypes.instanceOf(Date),
    preselectDate: PropTypes.instanceOf(Date),
    is_submitting: PropTypes.bool,
    is_uploading: PropTypes.bool,
    me: PropTypes.number,
    onChange: PropTypes.func.isRequired,
    onSubmit: PropTypes.func.isRequired,
    onClearSuggestions: PropTypes.func.isRequired,
    onFetchSuggestions: PropTypes.func.isRequired,
    onPrivacyChange: PropTypes.func.isRequired,
    onSuggestionSelected: PropTypes.func.isRequired,
    onChangeSpoilerText: PropTypes.func.isRequired,
    onPaste: PropTypes.func.isRequired,
    onPickEmoji: PropTypes.func.isRequired,
    showSearch: PropTypes.bool,
    settings : ImmutablePropTypes.map.isRequired,
    filesAttached : PropTypes.bool,
  };

  static defaultProps = {
    showSearch: false,
  };

  handleChange = (e) => {
    this.props.onChange(e.target.value);
  }

  handleKeyDown = (e) => {
    if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) {
      this.handleSubmit();
    }
  }

  handleSubmit2 = () => {
    this.props.onPrivacyChange(this.props.settings.get('side_arm'));
    this.handleSubmit();
  }

  handleSubmit = () => {
    if (this.props.text !== this.autosuggestTextarea.textarea.value) {
      // Something changed the text inside the textarea (e.g. browser extensions like Grammarly)
      // Update the state to match the current text
      this.props.onChange(this.autosuggestTextarea.textarea.value);
    }

    this.props.onSubmit();
  }

  onSuggestionsClearRequested = () => {
    this.props.onClearSuggestions();
  }

  onSuggestionsFetchRequested = debounce((token) => {
    this.props.onFetchSuggestions(token);
  }, 500, { trailing: true })

  onLocalSuggestionsFetchRequested = debounce((token) => {
    this.props.onFetchSuggestions(token);
  }, 100, { trailing: true })

  onSuggestionSelected = (tokenStart, token, value) => {
    this._restoreCaret = null;
    this.props.onSuggestionSelected(tokenStart, token, value);
  }

  handleChangeSpoilerText = (e) => {
    this.props.onChangeSpoilerText(e.target.value);
  }

  componentWillReceiveProps (nextProps) {
    // If this is the update where we've finished uploading,
    // save the last caret position so we can restore it below!
    if (!nextProps.is_uploading && this.props.is_uploading) {
      this._restoreCaret = this.autosuggestTextarea.textarea.selectionStart;
    }
  }

  componentDidUpdate (prevProps) {
    // This statement does several things:
    // - If we're beginning a reply, and,
    //     - Replying to zero or one users, places the cursor at the end of the textbox.
    //     - Replying to more than one user, selects any usernames past the first;
    //       this provides a convenient shortcut to drop everyone else from the conversation.
    // - If we've just finished uploading an image, and have a saved caret position,
    //   restores the cursor to that position after the text changes!
    if (this.props.focusDate !== prevProps.focusDate || (prevProps.is_uploading && !this.props.is_uploading && typeof this._restoreCaret === 'number')) {
      let selectionEnd, selectionStart;

      if (this.props.preselectDate !== prevProps.preselectDate) {
        selectionEnd   = this.props.text.length;
        selectionStart = this.props.text.search(/\s/) + 1;
      } else if (typeof this._restoreCaret === 'number') {
        selectionStart = this._restoreCaret;
        selectionEnd   = this._restoreCaret;
      } else {
        selectionEnd   = this.props.text.length;
        selectionStart = selectionEnd;
      }

      this.autosuggestTextarea.textarea.setSelectionRange(selectionStart, selectionEnd);
      this.autosuggestTextarea.textarea.focus();
    } else if(prevProps.is_submitting && !this.props.is_submitting) {
      this.autosuggestTextarea.textarea.focus();
    }
  }

  setAutosuggestTextarea = (c) => {
    this.autosuggestTextarea = c;
  }

  handleEmojiPick = (data) => {
    const position     = this.autosuggestTextarea.textarea.selectionStart;
    const emojiChar    = data.unicode.split('-').map(code => String.fromCodePoint(parseInt(code, 16))).join('');
    this._restoreCaret = position + emojiChar.length + 1;
    this.props.onPickEmoji(position, data);
  }

  render () {
    const { intl, onPaste, showSearch, filesAttached } = this.props;
    const disabled = this.props.is_submitting;
    const maybeEye = (this.props.advanced_options && this.props.advanced_options.do_not_federate) ? ' 👁' : '';
    const text     = [this.props.spoiler_text, countableText(this.props.text), maybeEye].join('');

    const secondaryVisibility = this.props.settings.get('side_arm');
    const isWideView = this.props.settings.get('stretch');
    let showSideArm = secondaryVisibility !== 'none';

    let publishText = '';

    const privacyIcons = {
      none: '',
      public: 'globe',
      unlisted: 'unlock-alt',
      private: 'lock',
      direct: 'envelope',
    };

    if (showSideArm) {
      publishText = (

          {
            <i
              className={`fa fa-${privacyIcons[this.props.privacy]}`}
              style={{
                paddingRight: (filesAttached || !isWideView) ? '0' : '5px',
              }}
            />
          }{
            (filesAttached || !isWideView) ? '' :
              intl.formatMessage(messages.publish)
          }

      );
    } else {
      if (this.props.privacy === 'private' || this.props.privacy === 'direct') {
        publishText =  {intl.formatMessage(messages.publish)};
      } else {
        publishText = this.props.privacy !== 'unlisted' ? intl.formatMessage(messages.publishLoud, { publish: intl.formatMessage(messages.publish) }) : intl.formatMessage(messages.publish);
      }
    }

    // side-arm
    let publishText2 = (
      <i
        className={`fa fa-${privacyIcons[secondaryVisibility]}`}
        aria-label={`${intl.formatMessage(messages.publish)}: ${intl.formatMessage({ id: `privacy.${secondaryVisibility}.short` })}`}
      />
    );

    const submitDisabled = disabled || this.props.is_uploading || length(text) > 500 || (text.length !== 0 && text.trim().length === 0);

    return (

        <Collapsable isVisible={this.props.spoiler} fullHeight={50}>

              {intl.formatMessage(messages.spoiler_placeholder)}
              <input placeholder={intl.formatMessage(messages.spoiler_placeholder)} value={this.props.spoiler_text} onChange={this.handleChangeSpoilerText} onKeyDown={this.handleKeyDown} type='text' className='spoiler-input__input'  id='cw-spoiler-input' />

        </Collapsable>

        <WarningContainer />

        <ReplyIndicatorContainer />

          <AutosuggestTextarea
            ref={this.setAutosuggestTextarea}
            placeholder={intl.formatMessage(messages.placeholder)}
            disabled={disabled}
            value={this.props.text}
            onChange={this.handleChange}
            suggestions={this.props.suggestions}
            onKeyDown={this.handleKeyDown}
            onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
            onLocalSuggestionsFetchRequested={this.onLocalSuggestionsFetchRequested}
            onSuggestionsClearRequested={this.onSuggestionsClearRequested}
            onSuggestionSelected={this.onSuggestionSelected}
            onPaste={onPaste}
            autoFocus={!showSearch && !isMobile(window.innerWidth)}
          />

          <EmojiPickerDropdown onPickEmoji={this.handleEmojiPick} />

          <UploadFormContainer />

            <UploadButtonContainer />
            <PrivacyDropdownContainer />
            <ComposeAdvancedOptionsContainer />
            <SensitiveButtonContainer />
            <SpoilerButtonContainer />

<CharacterCounter max={500} text={text} />

              {
                showSideArm ?
                  <Button
                    className='compose-form__publish__side-arm'
                    text={publishText2}
                    onClick={this.handleSubmit2}
                    disabled={submitDisabled}
                  /> :
                  ''
              }
              <Button
                className='compose-form__publish__primary'
                text={publishText}
                onClick={this.handleSubmit}
                disabled={submitDisabled}
                block
              />

    );
  }

}
<commit_msg>Fix intl warning with disabled secondary toot btn - Moved building the secondary btn text into the if to avoid trying to translate 'none' visibility<commit_after>import React from 'react';
import CharacterCounter from './character_counter';
import Button from '../../../components/button';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import ReplyIndicatorContainer from '../containers/reply_indicator_container';
import AutosuggestTextarea from '../../../components/autosuggest_textarea';
import { debounce } from 'lodash';
import UploadButtonContainer from '../containers/upload_button_container';
import { defineMessages, injectIntl } from 'react-intl';
import Collapsable from '../../../components/collapsable';
import SpoilerButtonContainer from '../containers/spoiler_button_container';
import PrivacyDropdownContainer from '../containers/privacy_dropdown_container';
import ComposeAdvancedOptionsContainer from '../../../../glitch/components/compose/advanced_options/container';
import SensitiveButtonContainer from '../containers/sensitive_button_container';
import EmojiPickerDropdown from './emoji_picker_dropdown';
import UploadFormContainer from '../containers/upload_form_container';
import WarningContainer from '../containers/warning_container';
import { isMobile } from '../../../is_mobile';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { length } from 'stringz';
import { countableText } from '../util/counter';

const messages = defineMessages({
  placeholder: { id: 'compose_form.placeholder', defaultMessage: 'What is on your mind?' },
  spoiler_placeholder: { id: 'compose_form.spoiler_placeholder', defaultMessage: 'Write your warning here' },
  publish: { id: 'compose_form.publish', defaultMessage: 'Toot' },
  publishLoud: { id: 'compose_form.publish_loud', defaultMessage: '{publish}!' },
});

@injectIntl
export default class ComposeForm extends ImmutablePureComponent {

  static propTypes = {
    intl: PropTypes.object.isRequired,
    text: PropTypes.string.isRequired,
    suggestion_token: PropTypes.string,
    suggestions: ImmutablePropTypes.list,
    spoiler: PropTypes.bool,
    privacy: PropTypes.string,
    advanced_options: ImmutablePropTypes.contains({
      do_not_federate: PropTypes.bool,
    }),
    spoiler_text: PropTypes.string,
    focusDate: PropTypes.instanceOf(Date),
    preselectDate: PropTypes.instanceOf(Date),
    is_submitting: PropTypes.bool,
    is_uploading: PropTypes.bool,
    me: PropTypes.number,
    onChange: PropTypes.func.isRequired,
    onSubmit: PropTypes.func.isRequired,
    onClearSuggestions: PropTypes.func.isRequired,
    onFetchSuggestions: PropTypes.func.isRequired,
    onPrivacyChange: PropTypes.func.isRequired,
    onSuggestionSelected: PropTypes.func.isRequired,
    onChangeSpoilerText: PropTypes.func.isRequired,
    onPaste: PropTypes.func.isRequired,
    onPickEmoji: PropTypes.func.isRequired,
    showSearch: PropTypes.bool,
    settings : ImmutablePropTypes.map.isRequired,
    filesAttached : PropTypes.bool,
  };

  static defaultProps = {
    showSearch: false,
  };

  handleChange = (e) => {
    this.props.onChange(e.target.value);
  }

  handleKeyDown = (e) => {
    if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) {
      this.handleSubmit();
    }
  }

  handleSubmit2 = () => {
    this.props.onPrivacyChange(this.props.settings.get('side_arm'));
    this.handleSubmit();
  }

  handleSubmit = () => {
    if (this.props.text !== this.autosuggestTextarea.textarea.value) {
      // Something changed the text inside the textarea (e.g. browser extensions like Grammarly)
      // Update the state to match the current text
      this.props.onChange(this.autosuggestTextarea.textarea.value);
    }

    this.props.onSubmit();
  }

  onSuggestionsClearRequested = () => {
    this.props.onClearSuggestions();
  }

  onSuggestionsFetchRequested = debounce((token) => {
    this.props.onFetchSuggestions(token);
  }, 500, { trailing: true })

  onLocalSuggestionsFetchRequested = debounce((token) => {
    this.props.onFetchSuggestions(token);
  }, 100, { trailing: true })

  onSuggestionSelected = (tokenStart, token, value) => {
    this._restoreCaret = null;
    this.props.onSuggestionSelected(tokenStart, token, value);
  }

  handleChangeSpoilerText = (e) => {
    this.props.onChangeSpoilerText(e.target.value);
  }

  componentWillReceiveProps (nextProps) {
    // If this is the update where we've finished uploading,
    // save the last caret position so we can restore it below!
    if (!nextProps.is_uploading && this.props.is_uploading) {
      this._restoreCaret = this.autosuggestTextarea.textarea.selectionStart;
    }
  }

  componentDidUpdate (prevProps) {
    // This statement does several things:
    // - If we're beginning a reply, and,
    //     - Replying to zero or one users, places the cursor at the end of the textbox.
    //     - Replying to more than one user, selects any usernames past the first;
    //       this provides a convenient shortcut to drop everyone else from the conversation.
    // - If we've just finished uploading an image, and have a saved caret position,
    //   restores the cursor to that position after the text changes!
    if (this.props.focusDate !== prevProps.focusDate || (prevProps.is_uploading && !this.props.is_uploading && typeof this._restoreCaret === 'number')) {
      let selectionEnd, selectionStart;

      if (this.props.preselectDate !== prevProps.preselectDate) {
        selectionEnd   = this.props.text.length;
        selectionStart = this.props.text.search(/\s/) + 1;
      } else if (typeof this._restoreCaret === 'number') {
        selectionStart = this._restoreCaret;
        selectionEnd   = this._restoreCaret;
      } else {
        selectionEnd   = this.props.text.length;
        selectionStart = selectionEnd;
      }

      this.autosuggestTextarea.textarea.setSelectionRange(selectionStart, selectionEnd);
      this.autosuggestTextarea.textarea.focus();
    } else if(prevProps.is_submitting && !this.props.is_submitting) {
      this.autosuggestTextarea.textarea.focus();
    }
  }

  setAutosuggestTextarea = (c) => {
    this.autosuggestTextarea = c;
  }

  handleEmojiPick = (data) => {
    const position     = this.autosuggestTextarea.textarea.selectionStart;
    const emojiChar    = data.unicode.split('-').map(code => String.fromCodePoint(parseInt(code, 16))).join('');
    this._restoreCaret = position + emojiChar.length + 1;
    this.props.onPickEmoji(position, data);
  }

  render () {
    const { intl, onPaste, showSearch, filesAttached } = this.props;
    const disabled = this.props.is_submitting;
    const maybeEye = (this.props.advanced_options && this.props.advanced_options.do_not_federate) ? ' 👁' : '';
    const text     = [this.props.spoiler_text, countableText(this.props.text), maybeEye].join('');

    const secondaryVisibility = this.props.settings.get('side_arm');
    const isWideView = this.props.settings.get('stretch');
    let showSideArm = secondaryVisibility !== 'none';

    let publishText = '';
    let publishText2 = '';

    const privacyIcons = {
      none: '',
      public: 'globe',
      unlisted: 'unlock-alt',
      private: 'lock',
      direct: 'envelope',
    };

    if (showSideArm) {
      publishText = (

          {
            <i
              className={`fa fa-${privacyIcons[this.props.privacy]}`}
              style={{
                paddingRight: (filesAttached || !isWideView) ? '0' : '5px',
              }}
            />
          }{
            (filesAttached || !isWideView) ? '' :
              intl.formatMessage(messages.publish)
          }

      );

      publishText2 = (
        <i
          className={`fa fa-${privacyIcons[secondaryVisibility]}`}
          aria-label={`${intl.formatMessage(messages.publish)}: ${intl.formatMessage({ id: `privacy.${secondaryVisibility}.short` })}`}
        />
      );
    } else {
      if (this.props.privacy === 'private' || this.props.privacy === 'direct') {
        publishText =  {intl.formatMessage(messages.publish)};
      } else {
        publishText = this.props.privacy !== 'unlisted' ? intl.formatMessage(messages.publishLoud, { publish: intl.formatMessage(messages.publish) }) : intl.formatMessage(messages.publish);
      }
    }

    const submitDisabled = disabled || this.props.is_uploading || length(text) > 500 || (text.length !== 0 && text.trim().length === 0);

    return (

        <Collapsable isVisible={this.props.spoiler} fullHeight={50}>

              {intl.formatMessage(messages.spoiler_placeholder)}
              <input placeholder={intl.formatMessage(messages.spoiler_placeholder)} value={this.props.spoiler_text} onChange={this.handleChangeSpoilerText} onKeyDown={this.handleKeyDown} type='text' className='spoiler-input__input'  id='cw-spoiler-input' />

        </Collapsable>

        <WarningContainer />

        <ReplyIndicatorContainer />

          <AutosuggestTextarea
            ref={this.setAutosuggestTextarea}
            placeholder={intl.formatMessage(messages.placeholder)}
            disabled={disabled}
            value={this.props.text}
            onChange={this.handleChange}
            suggestions={this.props.suggestions}
            onKeyDown={this.handleKeyDown}
            onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
            onLocalSuggestionsFetchRequested={this.onLocalSuggestionsFetchRequested}
            onSuggestionsClearRequested={this.onSuggestionsClearRequested}
            onSuggestionSelected={this.onSuggestionSelected}
            onPaste={onPaste}
            autoFocus={!showSearch && !isMobile(window.innerWidth)}
          />

          <EmojiPickerDropdown onPickEmoji={this.handleEmojiPick} />

          <UploadFormContainer />

            <UploadButtonContainer />
            <PrivacyDropdownContainer />
            <ComposeAdvancedOptionsContainer />
            <SensitiveButtonContainer />
            <SpoilerButtonContainer />

<CharacterCounter max={500} text={text} />

              {
                showSideArm ?
                  <Button
                    className='compose-form__publish__side-arm'
                    text={publishText2}
                    onClick={this.handleSubmit2}
                    disabled={submitDisabled}
                  /> : ''
              }
              <Button
                className='compose-form__publish__primary'
                text={publishText}
                onClick={this.handleSubmit}
                disabled={submitDisabled}
                block
              />

    );
  }

}
<|endoftext|>


<commit_before>var screens = {}
var widgets = {}
var already_loaded = {}

export function load(url, options){
  if (already_loaded[url]){
    return Promise.resolve()
  }
  let promise = new Promise(function(accept, reject){
    let full_url=`/static/${url}`
    if (localStorage.servername)
    full_url=`${localStorage.servername}${full_url}`

    options = $.extend( options || {}, {
      dataType: "script",
      cache: true,
      url: full_url
    });

    return jQuery.ajax( options ).done( accept ).fail( reject )
  })
  promise = promise.then( () => {
    already_loaded[url]=true
    console.log("Loaded JS %o", url)
  })
  return promise
}

let waiting_for_screen={}

function when_screen_added(id){
  return new Promise(function(accept, reject){
    waiting_for_screen[id] = (waiting_for_screen[id] || []).concat({accept, reject})
    setTimeout(30000, () => { // Will always fire, but maybe empty
      for (let {reject} of (waiting_for_screen[id] || []))
        reject(id)
    })
  })
}

export function add_screen(id, fn){
  screens[id]=fn
  for (let {accept} of (waiting_for_screen[id] || [])){
    accept(fn)
  }
  delete waiting_for_screen[id]
}

export function do_screen(id, el, data){

  if (id in screens){
    let cleanf=screens[id](el, data)
    return Promise.resolve(cleanf)
  }
  return when_screen_added(id).then( (screen) => {
    return screen(el,data)
  })
}

// Allow to be added asynchronously
let waiting_for_widgets={}

export function add_widget(id, fn){
  widgets[id]=fn
  if (id in waiting_for_widgets)
    for (let {accept, el, data} of waiting_for_widgets[id])
      accept(fn(el, data))
  delete waiting_for_widgets[id]
}

export function do_widget(id, el, data){
  if (!(id in widgets)){
    let p = new Promise(function(accept, reject){
      waiting_for_widgets[id]=(waiting_for_widgets[id] || []).concat({accept, reject, el, data})
      setTimeout(function(){
        console.error("timeout waiting for widget %o", id)
        if (id in waiting_for_widgets)
          reject("Timeout")
      }, 1000)
    })
    return p
  }
  return Promise.resolve(widgets[id](el, data))
}

export default {load, add_screen, do_screen, add_widget, do_widget}
<commit_msg>Exported plugin.join_path to ease plugin use<commit_after>var screens = {}
var widgets = {}
var already_loaded = {}

export function join_path(url){
  let full_url=`/static/${url}`
  if (localStorage.servername)
    full_url=`${localStorage.servername}${full_url}`
  return full_url
}

export function load(url, options){
  if (already_loaded[url]){
    return Promise.resolve()
  }
  let promise = new Promise(function(accept, reject){
    let full_url=join_path(url)

    options = $.extend( options || {}, {
      dataType: "script",
      cache: true,
      url: full_url
    });

    return jQuery.ajax( options ).done( accept ).fail( reject )
  })
  promise = promise.then( () => {
    already_loaded[url]=true
    console.log("Loaded JS %o", url)
  })
  return promise
}

let waiting_for_screen={}

function when_screen_added(id){
  return new Promise(function(accept, reject){
    waiting_for_screen[id] = (waiting_for_screen[id] || []).concat({accept, reject})
    setTimeout(30000, () => { // Will always fire, but maybe empty
      for (let {reject} of (waiting_for_screen[id] || []))
        reject(id)
    })
  })
}

export function add_screen(id, fn){
  screens[id]=fn
  for (let {accept} of (waiting_for_screen[id] || [])){
    accept(fn)
  }
  delete waiting_for_screen[id]
}

export function do_screen(id, el, data){

  if (id in screens){
    let cleanf=screens[id](el, data)
    return Promise.resolve(cleanf)
  }
  return when_screen_added(id).then( (screen) => {
    return screen(el,data)
  })
}

// Allow to be added asynchronously
let waiting_for_widgets={}

export function add_widget(id, fn){
  widgets[id]=fn
  if (id in waiting_for_widgets)
    for (let {accept, el, data} of waiting_for_widgets[id])
      accept(fn(el, data))
  delete waiting_for_widgets[id]
}

export function do_widget(id, el, data){
  if (!(id in widgets)){
    let p = new Promise(function(accept, reject){
      waiting_for_widgets[id]=(waiting_for_widgets[id] || []).concat({accept, reject, el, data})
      setTimeout(function(){
        console.error("timeout waiting for widget %o", id)
        if (id in waiting_for_widgets)
          reject("Timeout")
      }, 1000)
    })
    return p
  }
  return Promise.resolve(widgets[id](el, data))
}

export default {load, add_screen, do_screen, add_widget, do_widget, join_path}
<|endoftext|>


<commit_before>var Backbone = require('backbone');
var CategoriesListView = require('../../../../../../../../../../javascripts/cartodb3/components/form-components/editors/fill/input-color/input-qualitative-ramps/input-categories-list/input-color-categories-list-view');

describe('components/form-components/editors/fill/input-color/input-qualitative-ramps/input-categories-list/input-color-categories-list-view', function () {
  var view, model;
  var range = ['#000000', '#1D6996'];
  var rangeInRGB = ['rgb(0, 0, 0)', 'rgb(29, 105, 150)'];

  describe('when the attribute is boolean', function () {
    beforeEach(function () {
      model = new Backbone.Model({
        type: 'color',
        attribute: 'fake_boolean_attribute',
        attribute_type: 'boolean',
        images: ['', ''],
        range: range,
        domain: [true, false],
        selected: true
      });

      view = new CategoriesListView({
        maxValues: 10,
        model: model,
        imageEnabled: false
      });

      view.render();
    });

    describe('.render', function () {
      it('should have a back button', function () {
        expect(view.$('.js-back')).toBeDefined();
      });

      it('should back button should have the "Color schemes" label', function () {
        expect(view.$('.label').text().trim()).toEqual('Color Schemes');
      });

      it('should be properly rendered', function () {
        // Different browsers may return CSS color values that are logically
        // but not textually equal, e.g., #FFF, #ffffff, and rgb(255,255,255)
        // http://api.jquery.com/css/
        expect(view.$('.js-color:eq(0)').css('background-color')).toEqual(rangeInRGB[0]);
        expect(view.$('.js-color:eq(1)').css('background-color')).toEqual(rangeInRGB[1]);
        expect(view.$('.js-color:eq(2)').css('background-color')).toEqual(undefined);
      });

      it('should be properly rendered when there are null values', function () {
        var colorRange = ['#5F4690', '#1D6996', '#FFFFFF'];
        var colorRangeInRGB = ['rgb(95, 70, 144)', 'rgb(29, 105, 150)', 'rgb(255, 255, 255)'];

        var model = new Backbone.Model({
          type: 'color',
          attribute: 'fake_boolean_attribute',
          attribute_type: 'boolean',
          images: ['', ''],
          range: colorRange,
          domain: [true, false, null],
          selected: true
        });

        var view = new CategoriesListView({
          maxValues: 10,
          model: model,
          imageEnabled: false
        });

        view.render();
        expect(view.$('.js-color:eq(0)').css('background-color')).toEqual(colorRangeInRGB[0]);
        expect(view.$('.js-color:eq(1)').css('background-color')).toEqual(colorRangeInRGB[1]);
        expect(view.$('.js-color:eq(2)').css('background-color')).toEqual(colorRangeInRGB[2]);
        view.remove();
      });
    });

    afterEach(function () {
      view.remove();
    });
  });

  describe('when the attribute is numeric', function () {
    it('.render', function () {
      pending('IMPLEMENT ME');
    });
  });

  describe('when the attribute is string', function () {
    it('.render', function () {
      pending('IMPLEMENT ME');
    });
  });

  describe('when the attribute is a date', function () {
    it('.render', function () {
      pending('IMPLEMENT ME');
    });
  });

  // TODO: Add test for edge cases: (ie: more than 11 values un attribute domain)
});
<commit_msg>Add more tests to input-color-categories-list-view<commit_after>var Backbone = require('backbone');
var CategoriesListView = require('../../../../../../../../../../javascripts/cartodb3/components/form-components/editors/fill/input-color/input-qualitative-ramps/input-categories-list/input-color-categories-list-view');

describe('components/form-components/editors/fill/input-color/input-qualitative-ramps/input-categories-list/input-color-categories-list-view', function () {
  var view, model;
  var range = ['#000000', '#1D6996'];
  var rangeInRGB = ['rgb(0, 0, 0)', 'rgb(29, 105, 150)'];

  describe('when the attribute is boolean', function () {
    beforeEach(function () {
      model = new Backbone.Model({
        type: 'color',
        attribute: 'fake_boolean_attribute',
        attribute_type: 'boolean',
        images: ['', ''],
        range: range,
        domain: [true, false],
        selected: true
      });

      view = new CategoriesListView({
        maxValues: 10,
        model: model,
        imageEnabled: false
      });

      view.render();
    });

    describe('.render', function () {
      it('should have a back button', function () {
        expect(view.$('.js-back')).toBeDefined();
      });

      it('should back button should have the "Color schemes" label', function () {
        expect(view.$('.label').text().trim()).toEqual('Color Schemes');
      });

      it('should be properly rendered', function () {
        // Different browsers may return CSS color values that are logically
        // but not textually equal, e.g., #FFF, #ffffff, and rgb(255,255,255)
        // http://api.jquery.com/css/
        expect(view.$('.js-color:eq(0)').css('background-color')).toEqual(rangeInRGB[0]);
        expect(view.$('.js-color:eq(1)').css('background-color')).toEqual(rangeInRGB[1]);
        expect(view.$('.js-color:eq(2)').css('background-color')).toEqual(undefined);
      });

      it('should be properly rendered when there are null values', function () {
        var colorRange = ['#5F4690', '#1D6996', '#FFFFFF'];
        var colorRangeInRGB = ['rgb(95, 70, 144)', 'rgb(29, 105, 150)', 'rgb(255, 255, 255)'];

        var model = new Backbone.Model({
          type: 'color',
          attribute: 'fake_boolean_attribute',
          attribute_type: 'boolean',
          images: ['', ''],
          range: colorRange,
          domain: [true, false, null],
          selected: true
        });

        var view = new CategoriesListView({
          maxValues: 10,
          model: model,
          imageEnabled: false
        });

        view.render();
        expect(view.$('.js-color:eq(0)').css('background-color')).toEqual(colorRangeInRGB[0]);
        expect(view.$('.js-color:eq(1)').css('background-color')).toEqual(colorRangeInRGB[1]);
        expect(view.$('.js-color:eq(2)').css('background-color')).toEqual(colorRangeInRGB[2]);
        view.remove();
      });
    });

    afterEach(function () {
      view.remove();
    });
  });

  describe('when the attribute is numeric', function () {
    beforeEach(function () {
      model = new Backbone.Model({
        type: 'color',
        attribute: 'fake_numeric_attribute',
        attribute_type: 'numeric',
        images: ['', ''],
        range: range,
        domain: [1, 2],
        selected: true
      });

      view = new CategoriesListView({
        maxValues: 10,
        model: model,
        imageEnabled: false
      });

      view.render();
    });

    describe('.render', function () {
      it('should have a back button', function () {
        expect(view.$('.js-back')).toBeDefined();
      });

      it('should back button should have the "Color schemes" label', function () {
        expect(view.$('.label').text().trim()).toEqual('Color Schemes');
      });

      it('should be properly rendered', function () {
        // Different browsers may return CSS color values that are logically
        // but not textually equal, e.g., #FFF, #ffffff, and rgb(255,255,255)
        // http://api.jquery.com/css/
        expect(view.$('.js-color:eq(0)').css('background-color')).toEqual(rangeInRGB[0]);
        expect(view.$('.js-color:eq(1)').css('background-color')).toEqual(rangeInRGB[1]);
        expect(view.$('.js-color:eq(2)').css('background-color')).toEqual(undefined);
      });

      it('should be properly rendered when there are null values', function () {
        var colorRange = ['#5F4690', '#1D6996', '#FFFFFF'];
        var colorRangeInRGB = ['rgb(95, 70, 144)', 'rgb(29, 105, 150)', 'rgb(255, 255, 255)'];

        var model = new Backbone.Model({
          type: 'color',
          attribute: 'fake_numeric_attribute',
          attribute_type: 'numeric',
          images: ['', ''],
          range: colorRange,
          domain: [1, 2, null],
          selected: true
        });

        var view = new CategoriesListView({
          maxValues: 10,
          model: model,
          imageEnabled: false
        });

        view.render();
        expect(view.$('.js-color:eq(0)').css('background-color')).toEqual(colorRangeInRGB[0]);
        expect(view.$('.js-color:eq(1)').css('background-color')).toEqual(colorRangeInRGB[1]);
        expect(view.$('.js-color:eq(2)').css('background-color')).toEqual(colorRangeInRGB[2]);
        view.remove();
      });
    });

    afterEach(function () {
      view.remove();
    });
  });

  describe('when the attribute is string', function () {
    beforeEach(function () {
      model = new Backbone.Model({
        type: 'color',
        attribute: 'fake_string_attribute',
        attribute_type: 'string',
        images: ['', ''],
        range: range,
        domain: ['string_attribute_1', 'string_attribute_2'],
        selected: true
      });

      view = new CategoriesListView({
        maxValues: 10,
        model: model,
        imageEnabled: false
      });

      view.render();
    });

    describe('.render', function () {
      it('should have a back button', function () {
        expect(view.$('.js-back')).toBeDefined();
      });

      it('should back button should have the "Color schemes" label', function () {
        expect(view.$('.label').text().trim()).toEqual('Color Schemes');
      });

      it('should be properly rendered', function () {
        // Different browsers may return CSS color values that are logically
        // but not textually equal, e.g., #FFF, #ffffff, and rgb(255,255,255)
        // http://api.jquery.com/css/
        expect(view.$('.js-color:eq(0)').css('background-color')).toEqual(rangeInRGB[0]);
        expect(view.$('.js-color:eq(1)').css('background-color')).toEqual(rangeInRGB[1]);
        expect(view.$('.js-color:eq(2)').css('background-color')).toEqual(undefined);
      });

      it('should be properly rendered when there are null values', function () {
        var colorRange = ['#5F4690', '#1D6996', '#FFFFFF'];
        var colorRangeInRGB = ['rgb(95, 70, 144)', 'rgb(29, 105, 150)', 'rgb(255, 255, 255)'];

        var model = new Backbone.Model({
          type: 'color',
          attribute: 'fake_string_attribute',
          attribute_type: 'date',
          images: ['', ''],
          range: colorRange,
          domain: ['string_attribute_1', 'string_attribute_2', null],
          selected: true
        });

        var view = new CategoriesListView({
          maxValues: 10,
          model: model,
          imageEnabled: false
        });

        view.render();
        expect(view.$('.js-color:eq(0)').css('background-color')).toEqual(colorRangeInRGB[0]);
        expect(view.$('.js-color:eq(1)').css('background-color')).toEqual(colorRangeInRGB[1]);
        expect(view.$('.js-color:eq(2)').css('background-color')).toEqual(colorRangeInRGB[2]);
        view.remove();
      });
    });

    afterEach(function () {
      view.remove();
    });
  });

  describe('when the attribute is a date', function () {
    beforeEach(function () {
      model = new Backbone.Model({
        type: 'color',
        attribute: 'fake_date_attribute',
        attribute_type: 'date',
        images: ['', ''],
        range: range,
        domain: ['2018-01-01T00:00:00+00:00', '2018-01-02T00:00:00+00:00'],
        selected: true
      });

      view = new CategoriesListView({
        maxValues: 10,
        model: model,
        imageEnabled: false
      });

      view.render();
    });

    describe('.render', function () {
      it('should have a back button', function () {
        expect(view.$('.js-back')).toBeDefined();
      });

      it('should back button should have the "Color schemes" label', function () {
        expect(view.$('.label').text().trim()).toEqual('Color Schemes');
      });

      it('should be properly rendered', function () {
        // Different browsers may return CSS color values that are logically
        // but not textually equal, e.g., #FFF, #ffffff, and rgb(255,255,255)
        // http://api.jquery.com/css/
        expect(view.$('.js-color:eq(0)').css('background-color')).toEqual(rangeInRGB[0]);
        expect(view.$('.js-color:eq(1)').css('background-color')).toEqual(rangeInRGB[1]);
        expect(view.$('.js-color:eq(2)').css('background-color')).toEqual(undefined);
      });

      it('should be properly rendered when there are null values', function () {
        var colorRange = ['#5F4690', '#1D6996', '#FFFFFF'];
        var colorRangeInRGB = ['rgb(95, 70, 144)', 'rgb(29, 105, 150)', 'rgb(255, 255, 255)'];

        var model = new Backbone.Model({
          type: 'color',
          attribute: 'fake_date_attribute',
          attribute_type: 'date',
          images: ['', ''],
          range: colorRange,
          domain: ['2018-01-01T00:00:00+00:00', '2018-01-02T00:00:00+00:00', null],
          selected: true
        });

        var view = new CategoriesListView({
          maxValues: 10,
          model: model,
          imageEnabled: false
        });

        view.render();
        expect(view.$('.js-color:eq(0)').css('background-color')).toEqual(colorRangeInRGB[0]);
        expect(view.$('.js-color:eq(1)').css('background-color')).toEqual(colorRangeInRGB[1]);
        expect(view.$('.js-color:eq(2)').css('background-color')).toEqual(colorRangeInRGB[2]);
        view.remove();
      });
    });

    afterEach(function () {
      view.remove();
    });
  });

  // TODO: Add test for edge cases: (ie: more than 11 values un attribute domain)
});
<|endoftext|>


<commit_before>App.InvestigationsController = Ember.ArrayController.extend({
  needs: ['project'],

  actions: {
    createInvestigation: function () {
      var investigation = this.store.createRecord('investigation');

      var project = this.get('controllers.project.content');

      investigation.save().then(function(){
        project.get('investigations').addObject(investigation);

        project.save();
      });
    }
  }
});<commit_msg>Call pushObject rather than addObject<commit_after>App.InvestigationsController = Ember.ArrayController.extend({
  needs: ['project'],

  actions: {
    createInvestigation: function () {
      var investigation = this.store.createRecord('investigation');

      var project = this.get('controllers.project.content');

      investigation.save().then(function(){
        project.get('investigations').pushObject(investigation);
        project.save();
      });
    }
  }
});<|endoftext|>


<commit_before>var args = arguments[0] || {};

Ti.API.info("ARGUMENTS PASSED TO GAME DETAIL: " + JSON.stringify(args));

$.gameDetail.rightNavButton = Ti.UI.createButton({
	backgroundImage: 'images/gameDetail/share.png',
	width: '43dp',
	height: '29dp'
});<commit_msg>Fixed share button visibilty in game detail window<commit_after>var args = arguments[0] || {};

Ti.API.info("ARGUMENTS PASSED TO GAME DETAIL: " + JSON.stringify(args));

$.gameDetail.rightNavButton = Ti.UI.createButton({
	backgroundImage: '/images/game_detail/share.png',
	width: '43dp',
	height: '29dp'
});<|endoftext|>


<commit_before>var haml            = require('hamljs');
var pickFiles       = require('broccoli-static-compiler');
var renderTemplates = require('broccoli-render-template');
var renameFiles     = require('broccoli-rename-files');

var templates = pickFiles('lib/hooks', {
  srcDir:  '/',
  files:   ['*/help.html.haml'],
  destDir: 'templates/idobata-hooks'
});

templates = renderTemplates(templates);

templates = renameFiles(templates, {
  transformFilename: function(filename, basename, extname) {
    return basename.replace('.html', '.hbs');
  }
});

module.exports = renderTemplates(templates);
<commit_msg>Remove duplication [ci skip]<commit_after>var haml            = require('hamljs');
var pickFiles       = require('broccoli-static-compiler');
var renderTemplates = require('broccoli-render-template');
var renameFiles     = require('broccoli-rename-files');

var templates = pickFiles('lib/hooks', {
  srcDir:  '/',
  files:   ['*/help.html.haml'],
  destDir: 'templates/idobata-hooks'
});

templates = renderTemplates(templates);

module.exports = renameFiles(templates, {
  transformFilename: function(filename, basename, extname) {
    return basename.replace('.html', '.hbs');
  }
});
<|endoftext|>


<commit_before>var args = arguments[0] || {};

Ti.API.info("ARGUMENTS PASSED TO GAME DETAIL: " + JSON.stringify(args));

if (OS_IOS) {
   var leftButton = Ti.UI.createButton({
    title: ' Nearby',
    backgroundImage: '/images/game_detail/backButton.png',
    width: '58dp',
    height: '29dp',
    font: {fontSize: '12dp', fontWeight: 'bold'}
  });
  leftButton.addEventListener('click', function(){
    Alloy.globals.tabgroup.activeTab.close($.gameDetail);
  });
  $.gameDetail.leftNavButton = leftButton;

  $.gameDetail.rightNavButton = Ti.UI.createButton({
    backgroundImage: '/images/game_detail/share.png',
    width: '43dp',
    height: '29dp'
  });
}
<commit_msg>Animation on detail window close<commit_after>var args = arguments[0] || {};

Ti.API.info("ARGUMENTS PASSED TO GAME DETAIL: " + JSON.stringify(args));

if (OS_IOS) {
   var leftButton = Ti.UI.createButton({
    title: ' Nearby',
    backgroundImage: '/images/game_detail/backButton.png',
    width: '58dp',
    height: '29dp',
    font: {fontSize: '12dp', fontWeight: 'bold'}
  });
  leftButton.addEventListener('click', function(){
    Alloy.globals.tabgroup.activeTab.close($.gameDetail, {animated: true});
  });
  $.gameDetail.leftNavButton = leftButton;

  $.gameDetail.rightNavButton = Ti.UI.createButton({
    backgroundImage: '/images/game_detail/share.png',
    width: '43dp',
    height: '29dp'
  });
}
<|endoftext|>


<commit_before>import React from 'react';

import SPELLS from 'common/SPELLS';
import SpellIcon from 'common/SpellIcon';

import DualStatisticBox, { STATISTIC_ORDER } from 'Interface/Others/DualStatisticBox';
import { formatPercentage, formatNumber } from 'common/format';
import Combatants from 'Parser/Core/Modules/Combatants';
import Analyzer from 'Parser/Core/Analyzer';

import calculateEffectiveDamage from 'Parser/Core/calculateEffectiveDamage';
import calculateEffectiveHealing from 'Parser/Core/calculateEffectiveHealing';
import isAtonement from '../Core/isAtonement';
import Atonement from './Atonement';

const SINS_OF_THE_MANY_BASE_BONUS = 0.12;
const SINS_OF_THE_MANY_FLOOR_BONUS = 0.03;

/**
 * Sins allows you to have one Atonement active whilst keeping the full bonus
 * from the passive. Hence this map should be seen as overrides for a linear
 * progression from the max to the floor per Atonement active.
 */
const BONUS_DAMAGE_MAP = {
  1: 0.12,
};

class SinsOfTheMany extends Analyzer {
  static dependencies = {
    combatants: Combatants,
    atonement: Atonement,
  };

  bonusDamage = 0;
  bonusHealing = 0;

  constructor(...args) {
    super(...args);
    this.active = this.selectedCombatant.hasTalent(SPELLS.SINS_OF_THE_MANY_TALENT.id);
  }

  get currentBonus() {
    const baseBonus = SINS_OF_THE_MANY_BASE_BONUS * 100;
    const floorBonus = SINS_OF_THE_MANY_FLOOR_BONUS * 100;
    const activeBuffs = this.atonement.numAtonementsActive;

    // Return an override, if necessary
    if (BONUS_DAMAGE_MAP[activeBuffs]) return BONUS_DAMAGE_MAP[activeBuffs];

    // Return the floor if we're below it
    if (floorBonus >= baseBonus - activeBuffs) {
      return SINS_OF_THE_MANY_FLOOR_BONUS;
    }

    // Return the calculated bonus
    return (baseBonus - activeBuffs) / 100;
  }

  /**
   * Sins of the Many buffs all of your damage, there is no whitelist
   */
  on_byPlayer_damage(event) {
    this.bonusDamage += calculateEffectiveDamage(event, this.currentBonus);
  }

  on_byPlayerPet_damage(event) {
    this.bonusDamage += calculateEffectiveDamage(event, this.currentBonus);
  }

  /**
   * This is whitelisted by virtue of Atonement naturally not occuring
   * from abilities not in the whitelist.
   */
  on_byPlayer_heal(event) {
    if (!isAtonement(event)) {
      return;
    }

    this.bonusHealing += calculateEffectiveHealing(event, this.currentBonus);
  }

  statistic() {
    return (
      <DualStatisticBox
        icon={<SpellIcon id={SPELLS.SINS_OF_THE_MANY_TALENT.id} />}
        values={[
          `${formatNumber(
            (this.bonusHealing / this.owner.fightDuration) * 1000
          )} HPS`,
          `${formatNumber(
            (this.bonusDamage /
              this.owner.fightDuration) *
              1000
          )} DPS`,
        ]}
        footer={(
          <dfn
            data-tip={`
              The effective healing contributed by Sins of the Many was ${formatPercentage(
                this.owner.getPercentageOfTotalHealingDone(this.bonusHealing)
              )}% of total healing done.

              The direct damage contributed by Sins of the Many was ${formatPercentage(
                this.owner.getPercentageOfTotalDamageDone(this.bonusDamage)
              )}% of total damage done.
            `}
          >
            Sins of the Many Output Details
          </dfn>
        )}
      />
    );
  }
  statisticOrder = STATISTIC_ORDER.OPTIONAL();
}

export default SinsOfTheMany;
<commit_msg>implement correct Sin of the Many scaling<commit_after>import React from 'react';

import SPELLS from 'common/SPELLS';
import SpellIcon from 'common/SpellIcon';

import DualStatisticBox, { STATISTIC_ORDER } from 'Interface/Others/DualStatisticBox';
import { formatPercentage, formatNumber } from 'common/format';
import Combatants from 'Parser/Core/Modules/Combatants';
import Analyzer from 'Parser/Core/Analyzer';

import calculateEffectiveDamage from 'Parser/Core/calculateEffectiveDamage';
import calculateEffectiveHealing from 'Parser/Core/calculateEffectiveHealing';
import isAtonement from '../Core/isAtonement';
import Atonement from './Atonement';

const SINS_OF_THE_MANY_FLOOR_BONUS = 0.03;

/**
 * Sins isn't linear,
 * it allows you to have one Atonement active whilst keeping the full bonus
 * from the passive and from 6 onwards it only decreases 0.005.
 * Hence this map with the values for each Atonement count.
 */
const BONUS_DAMAGE_MAP = {
  0: 0.12,
  1: 0.12,
  2: 0.10,
  3: 0.08,
  4: 0.07,
  5: 0.06,
  6: 0.055,
  7: 0.05,
  8: 0.045,
  9: 0.04,
};

class SinsOfTheMany extends Analyzer {
  static dependencies = {
    combatants: Combatants,
    atonement: Atonement,
  };

  bonusDamage = 0;
  bonusHealing = 0;

  constructor(...args) {
    super(...args);
    this.active = this.selectedCombatant.hasTalent(SPELLS.SINS_OF_THE_MANY_TALENT.id);
  }

  get currentBonus() {
    const activeBuffs = this.atonement.numAtonementsActive;

    // Return an override, if necessary
    if (BONUS_DAMAGE_MAP[activeBuffs]) return BONUS_DAMAGE_MAP[activeBuffs];

    // Return the floor if we have more atonements than in the map
    return SINS_OF_THE_MANY_FLOOR_BONUS;
  }

  /**
   * Sins of the Many buffs all of your damage, there is no whitelist
   */
  on_byPlayer_damage(event) {
    this.bonusDamage += calculateEffectiveDamage(event, this.currentBonus);
  }

  on_byPlayerPet_damage(event) {
    this.bonusDamage += calculateEffectiveDamage(event, this.currentBonus);
  }

  /**
   * This is whitelisted by virtue of Atonement naturally not occuring
   * from abilities not in the whitelist.
   */
  on_byPlayer_heal(event) {
    if (!isAtonement(event)) {
      return;
    }

    this.bonusHealing += calculateEffectiveHealing(event, this.currentBonus);
  }

  statistic() {
    return (
      <DualStatisticBox
        icon={<SpellIcon id={SPELLS.SINS_OF_THE_MANY_TALENT.id} />}
        values={[
          `${formatNumber(
            (this.bonusHealing / this.owner.fightDuration) * 1000
          )} HPS`,
          `${formatNumber(
            (this.bonusDamage /
              this.owner.fightDuration) *
              1000
          )} DPS`,
        ]}
        footer={(
          <dfn
            data-tip={`
              The effective healing contributed by Sins of the Many was ${formatPercentage(
                this.owner.getPercentageOfTotalHealingDone(this.bonusHealing)
              )}% of total healing done.

              The direct damage contributed by Sins of the Many was ${formatPercentage(
                this.owner.getPercentageOfTotalDamageDone(this.bonusDamage)
              )}% of total damage done.
            `}
          >
            Sins of the Many Output Details
          </dfn>
        )}
      />
    );
  }
  statisticOrder = STATISTIC_ORDER.OPTIONAL();
}

export default SinsOfTheMany;
<|endoftext|>


<commit_before>import { Meteor } from 'meteor/meteor';
import { Mongo } from 'meteor/mongo';
import { check } from 'meteor/check';
import { HTTP } from 'meteor/http';

export const Songs = new Mongo.Collection('songs');

if (Meteor.isServer) {
  // This code only runs on the server
  Meteor.publish('songs', () => Songs.find({}));
}

Meteor.methods({
  songRate(songId, rating) {
    check(songId, String);
    check(rating, Number);
    const song = Songs.findOne(songId);
    if(!this.userId || song.submittedBy === this.userId){
      throw new Meteor.Error('not-authorized');
    }
    Songs.update(songId, { $inc: { ratingSum: rating, ratingCount: 1 }, $push: { ratedBy: this.userId} });
  },
  songInsert(country, data) {
    //check(country, String);

    // Make sure the user is logged in before inserting a song
    if (! this.userId) {
      throw new Meteor.Error('not-authorized');
    }

    Songs.insert({
      country,
      data,
      submittedOn: new Date(),
      submittedBy: this.userId,
      username: Meteor.users.findOne(this.userId).username,
      rating: 0,
      ratedBy: [],
    });
  },
  songLastFM(song) {
    console.log(process.env.LASTFM_APIKEY);
    HTTP.get('https://ws.audioscrobbler.com/2.0/', {
      params: { method: 'track.search', track: song.data.name + ' ' + song.data.artists[0].name, api_key: process.env.LASTFM_APIKEY, format: 'json' } }, (error, result) => {
        if (!error) {
          console.log(result);
          Songs.update(song._id, { lastFm: result.data.results.trackmatches.track[0] });
        }
      });
  },
});
<commit_msg>Another fix<commit_after>import { Meteor } from 'meteor/meteor';
import { Mongo } from 'meteor/mongo';
import { check } from 'meteor/check';
import { HTTP } from 'meteor/http';

export const Songs = new Mongo.Collection('songs');

if (Meteor.isServer) {
  // This code only runs on the server
  Meteor.publish('songs', () => Songs.find({}));
}

Meteor.methods({
  songRate(songId, rating) {
    check(songId, String);
    check(rating, Number);
    const song = Songs.findOne(songId);
    if(!this.userId || song.submittedBy === this.userId){
      throw new Meteor.Error('not-authorized');
    }
    Songs.update(songId, { $inc: { ratingSum: rating, ratingCount: 1 }, $push: { ratedBy: this.userId} });
  },
  songInsert(country, data) {
    //check(country, String);

    // Make sure the user is logged in before inserting a song
    if (! this.userId) {
      throw new Meteor.Error('not-authorized');
    }

    Songs.insert({
      country,
      data,
      submittedOn: new Date(),
      submittedBy: this.userId,
      username: Meteor.users.findOne(this.userId).username,
      rating: 0,
      ratedBy: [],
    });
  },
  songLastFM(song) {
    console.log(process.env.LASTFM_APIKEY);
    HTTP.get('https://ws.audioscrobbler.com/2.0/', {
      params: { method: 'track.search', track: song.data.name + ' ' + song.data.artists[0].name, api_key: process.env.LASTFM_APIKEY, format: 'json' } }, (error, result) => {
        if (!error) {
          console.log(result);
          Songs.update(song._id, { $set: { lastFm: result.data.results.trackmatches.track[0] } });
        }
      });
  },
});
<|endoftext|>


<commit_before>import React, {PureComponent} from 'react'
import PropTypes from 'prop-types'
import CardTitle from '.'
import CardSubtitle from '.'
import classNames from 'classnames'

class CardPrimary extends PureComponent {
  static displayName = 'CardPrimary'

  static propTypes = {
    children: PropTypes.node,
    className: PropTypes.string,
    subtitle: PropTypes.string,
    title: PropTypes.string,
    titleLarge: PropTypes.bool,
  }

  _renderTitle() {
    const {
      title,
      titleLarge,
    } = this.props
    return title && (
      <CardTitle large={titleLarge}>{title}</CardTitle>
    )
  }

  _renderSubtitle() {
    const {subtitle} = this.props
    return subtitle && (
      <CardSubtitle>{subtitle}</CardSubtitle>
    )
  }

  render() {
    const {
      children,
      className,
      subtitle,
      title,
      titleLarge,
      ...otherProps,
    } = this.props
    const cssClasses = classNames('mdc-card__primary', className)
    return (
      <section
        {...otherProps}
        className={cssClasses}
      >
        {children}
        {this._renderTitle()}
        {this._renderSubtitle()}
      </section>
    )
  }
}

export default CardPrimary
<commit_msg>fix(components): Fix CardTitle import Card<commit_after>import React, {PureComponent} from 'react'
import PropTypes from 'prop-types'
import {CardTitle} from '.'
import {CardSubtitle} from '.'
import classNames from 'classnames'

class CardPrimary extends PureComponent {
  static displayName = 'CardPrimary'

  static propTypes = {
    children: PropTypes.node,
    className: PropTypes.string,
    subtitle: PropTypes.string,
    title: PropTypes.string,
    titleLarge: PropTypes.bool,
  }

  _renderTitle() {
    const {
      title,
      titleLarge,
    } = this.props
    return title && (
      <CardTitle large={titleLarge}>{title}</CardTitle>
    )
  }

  _renderSubtitle() {
    const {subtitle} = this.props
    return subtitle && (
      <CardSubtitle>{subtitle}</CardSubtitle>
    )
  }

  render() {
    const {
      children,
      className,
      subtitle,
      title,
      titleLarge,
      ...otherProps,
    } = this.props
    const cssClasses = classNames('mdc-card__primary', className)
    return (
      <section
        {...otherProps}
        className={cssClasses}
      >
        {children}
        {this._renderTitle()}
        {this._renderSubtitle()}
      </section>
    )
  }
}

export default CardPrimary
<|endoftext|>


<commit_before>import $ from 'jquery';
import {sunrayAngle} from '../solar-system-data.js';
import {colorInterpolation} from '../utils.js';

const DEG_2_RAD = Math.PI / 180;

const SKY_COLOR = '#99ADF1';
const DIST_MARKER_COLOR = '#87C2E8';

const GROUND_FRACTION = 0.2;
const DIST_MARKER_HEIGHT_FRACTION = 0.1;
const NUM_BEAMS = 10;

const MAX_DAY = 364;
// Color palette for ground.
// Keys defines time of year normalized to [0, 1] range. So, Jan 1st is 0, Dec 31st is 1.
const GROUND_COLORS = {
  0.21: [255, 255, 255], // white, winter
  0.23: [118, 199, 68], // light green, spring
  0.60: [72, 140, 42], // green, summer
  0.70: [100, 132, 0], /// dark green, summer
  0.73: [147, 112, 22], // brown, autumn
  0.96: [125, 74, 5], // brown, autumn
  0.98: [255, 255, 255] // white, winter
};

const DEFAULT_PROPS = {
  day: 0,
  lat: 0,
  earthTilt: true,
  sunrayColor: '#D8D8AC',
  groundColor: 'auto', // different for each season
  sunrayDistMarker: false
};

export default class {
  constructor(parentEl, props = DEFAULT_PROPS) {
    this.canvas = document.createElement('canvas');
    parentEl.appendChild(this.canvas);
    this.ctx = this.canvas.getContext('2d');
    this.groundColorFunc = colorInterpolation(GROUND_COLORS);

    this.props = {};
    this.setProps(props);

    this.resize();
  }

  setProps(newProps) {
    let oldProps = $.extend(this.props);
    this.props = $.extend(this.props, newProps);

    if (this.props.day !== oldProps.day ||
      this.props.earthTilt !== oldProps.earthTilt ||
      this.props.lat !== oldProps.lat ||
      this.props.sunrayColor !== oldProps.sunrayColor ||
      this.props.groundColor !== oldProps.groundColor ||
      this.props.sunrayDistMarker !== oldProps.sunrayDistMarker) {
      this.render();
    }
  }

  render() {
    this.drawSky(SKY_COLOR);
    this.drawGround(0, GROUND_FRACTION);
    this.drawPolarNightOverlay();
    this.drawRays();
    this.drawDistanceBetweenRays();
  }

  get solarAngle() {
    return sunrayAngle(this.props.day, this.props.earthTilt, this.props.lat) * DEG_2_RAD;
  }

  get polarNight() {
    let solarAngle = this.solarAngle;
    return solarAngle < 0 || solarAngle > 180 * DEG_2_RAD;
  }

  drawRays() {
    let solarAngle = this.solarAngle;
    if (solarAngle < 0 || solarAngle > 180 * DEG_2_RAD) return;

    let width = this.width;
    let height = this.height;
    let groundHeight = GROUND_FRACTION * height;
    let skyHeight = height - groundHeight;

    // Longest possible line.
    let maxLength = Math.sqrt(skyHeight * skyHeight + width * width);
    let x;
    let dx = raysXDiff(solarAngle, width);
    let lineRotationRadians = 90 * DEG_2_RAD - solarAngle;

    this.ctx.save();
    this.ctx.strokeStyle = this.props.sunrayColor;
    this.ctx.fillStyle = this.props.sunrayColor;

    // Could be +/- Infinity when solarAngle is 0
    if (isFinite(dx)) {
      let idx = 0;
      for (x = dx / 2; x < width; x += dx, idx += 1) {
        this.ctx.save();
        this.ctx.translate(x, skyHeight);
        this.drawLine(lineRotationRadians, 5, maxLength);
        this.drawArrow(lineRotationRadians);
        this.ctx.restore();
      }
    }

    let dy = Math.abs(width / (NUM_BEAMS + 1) / Math.cos(solarAngle));
    let yInitial = solarAngle < 90 * DEG_2_RAD ? (dy / 2) : (((x - width) / dx) * dy);
    let xEdge = solarAngle < 90 * DEG_2_RAD ? 0 : width;
    if (isFinite(dy)) {
      for (let y = skyHeight - yInitial; y > 0; y -= dy) {
        this.ctx.save();
        this.ctx.translate(xEdge, y);
        this.drawLine(lineRotationRadians, 0, maxLength);
        this.ctx.restore();
      }
    }
    this.ctx.restore();
  }

  drawDistanceBetweenRays() {
    if (this.props.sunrayDistMarker && !this.polarNight) {
      let dx = raysXDiff(this.solarAngle, this.width);
      this.drawRaysDistMarker(dx * 0.5, this.height * (1 - GROUND_FRACTION), dx);
    }
  }

  // Resize canvas to fill its parent.
  resize() {
    let $parent = $(this.canvas).parent();
    this.width = $parent.width();
    this.height = $parent.height();
    // Update canvas attributes (they can be undefined if canvas size is set using CSS).
    this.canvas.width = this.width;
    this.canvas.height = this.height;
    this.render();
  }

  drawSky(color) {
    this.ctx.save();
    this.ctx.fillStyle = color;
    this.ctx.fillRect(0, 0, this.width, this.height);
    this.ctx.restore();
  }

  drawGround(angle, groundFraction) {
    this.ctx.save();
    this.ctx.translate(this.width * 0.5,  this.height * (1 - groundFraction));
    this.ctx.rotate(angle);
    this.ctx.fillStyle = this.props.groundColor === 'auto' ? this.groundColorFunc(this.props.day / MAX_DAY) : this.props.groundColor;
    this.ctx.fillRect(-this.width, 0, this.width * 2, this.height);
    this.ctx.restore();
  }

  drawPolarNightOverlay() {
    if (this.solarAngle < 0 || this.solarAngle > 180 * DEG_2_RAD) {
      this.ctx.save();
      this.ctx.fillStyle = 'rgba(0, 0, 0, 0.4)';
      this.ctx.fillRect(0, 0, this.width, this.height);
      this.ctx.restore();
    }
  }

  drawLine(angle, lengthAdjustment, length) {
    this.ctx.save();
    this.ctx.lineWidth = 4;
    this.ctx.rotate(angle);
    this.ctx.beginPath();
    this.ctx.moveTo(0, -lengthAdjustment);
    this.ctx.lineTo(0, -length);
    this.ctx.stroke();
    this.ctx.restore();
  }

  drawArrow(angle, scaleX=1, scaleY=1) {
    this.ctx.save();
    this.ctx.lineWidth = 1;
    this.ctx.rotate(angle);
    this.ctx.scale(scaleX, scaleY);
    this.ctx.beginPath();
    this.ctx.moveTo(0, 0);
    this.ctx.lineTo(-10, -20);
    this.ctx.lineTo(0, -16);
    this.ctx.lineTo(10, -20);
    this.ctx.lineTo(0, 0);
    this.ctx.fill();
    this.ctx.restore();
  }

  drawRaysDistMarker(x, y, len, angle=0) {
    let height = DIST_MARKER_HEIGHT_FRACTION * this.height;
    this.ctx.save();
    this.ctx.translate(x, y);
    this.ctx.rotate(angle);
    this.ctx.lineWidth = 4;
    this.ctx.strokeStyle = DIST_MARKER_COLOR;
    this.ctx.fillStyle = DIST_MARKER_COLOR;
    this.ctx.beginPath();
    this.ctx.moveTo(0, 0);
    this.ctx.lineTo(0, height);
    this.ctx.moveTo(len, 0);
    this.ctx.lineTo(len, height);
    this.ctx.moveTo(0, 0.5 * height);
    this.ctx.lineTo(len, 0.5 * height);
    this.ctx.stroke();
    this.ctx.translate(0, 0.5 * height);
    this.drawArrow(Math.PI / 2, 0.9, 0.7);
    this.ctx.translate(len, 0);
    this.drawArrow(-Math.PI / 2, 0.9, 0.7);
    this.ctx.restore();
  }
}

function raysXDiff(solarAngle, width) {
  return width / (NUM_BEAMS + 1) / Math.sin(solarAngle);
}
<commit_msg>Update ground color palette<commit_after>import $ from 'jquery';
import {sunrayAngle} from '../solar-system-data.js';
import {colorInterpolation} from '../utils.js';

const DEG_2_RAD = Math.PI / 180;

const SKY_COLOR = '#99ADF1';
const DIST_MARKER_COLOR = '#87C2E8';

const GROUND_FRACTION = 0.2;
const DIST_MARKER_HEIGHT_FRACTION = 0.1;
const NUM_BEAMS = 10;

const MAX_DAY = 364;
// Color palette for ground.
// Keys defines time of year normalized to [0, 1] range. So, Jan 1st is 0, Dec 31st is 1.
const GROUND_COLORS = {
  0.20: [200, 200, 200], // gray, winter
  0.23: [118, 199, 68], // light green, spring
  0.60: [72, 140, 42], // green, summer
  0.70: [100, 132, 0], /// dark green, summer
  0.73: [147, 112, 22], // brown, autumn
  0.96: [125, 74, 5], // brown, autumn
  0.99: [140, 140, 140] // gray, winter
};

const DEFAULT_PROPS = {
  day: 0,
  lat: 0,
  earthTilt: true,
  sunrayColor: '#D8D8AC',
  groundColor: 'auto', // different for each season
  sunrayDistMarker: false
};

export default class {
  constructor(parentEl, props = DEFAULT_PROPS) {
    this.canvas = document.createElement('canvas');
    parentEl.appendChild(this.canvas);
    this.ctx = this.canvas.getContext('2d');
    this.groundColorFunc = colorInterpolation(GROUND_COLORS);

    this.props = {};
    this.setProps(props);

    this.resize();
  }

  setProps(newProps) {
    let oldProps = $.extend(this.props);
    this.props = $.extend(this.props, newProps);

    if (this.props.day !== oldProps.day ||
      this.props.earthTilt !== oldProps.earthTilt ||
      this.props.lat !== oldProps.lat ||
      this.props.sunrayColor !== oldProps.sunrayColor ||
      this.props.groundColor !== oldProps.groundColor ||
      this.props.sunrayDistMarker !== oldProps.sunrayDistMarker) {
      this.render();
    }
  }

  render() {
    this.drawSky(SKY_COLOR);
    this.drawGround(0, GROUND_FRACTION);
    this.drawPolarNightOverlay();
    this.drawRays();
    this.drawDistanceBetweenRays();
  }

  get solarAngle() {
    return sunrayAngle(this.props.day, this.props.earthTilt, this.props.lat) * DEG_2_RAD;
  }

  get polarNight() {
    let solarAngle = this.solarAngle;
    return solarAngle < 0 || solarAngle > 180 * DEG_2_RAD;
  }

  drawRays() {
    let solarAngle = this.solarAngle;
    if (solarAngle < 0 || solarAngle > 180 * DEG_2_RAD) return;

    let width = this.width;
    let height = this.height;
    let groundHeight = GROUND_FRACTION * height;
    let skyHeight = height - groundHeight;

    // Longest possible line.
    let maxLength = Math.sqrt(skyHeight * skyHeight + width * width);
    let x;
    let dx = raysXDiff(solarAngle, width);
    let lineRotationRadians = 90 * DEG_2_RAD - solarAngle;

    this.ctx.save();
    this.ctx.strokeStyle = this.props.sunrayColor;
    this.ctx.fillStyle = this.props.sunrayColor;

    // Could be +/- Infinity when solarAngle is 0
    if (isFinite(dx)) {
      let idx = 0;
      for (x = dx / 2; x < width; x += dx, idx += 1) {
        this.ctx.save();
        this.ctx.translate(x, skyHeight);
        this.drawLine(lineRotationRadians, 5, maxLength);
        this.drawArrow(lineRotationRadians);
        this.ctx.restore();
      }
    }

    let dy = Math.abs(width / (NUM_BEAMS + 1) / Math.cos(solarAngle));
    let yInitial = solarAngle < 90 * DEG_2_RAD ? (dy / 2) : (((x - width) / dx) * dy);
    let xEdge = solarAngle < 90 * DEG_2_RAD ? 0 : width;
    if (isFinite(dy)) {
      for (let y = skyHeight - yInitial; y > 0; y -= dy) {
        this.ctx.save();
        this.ctx.translate(xEdge, y);
        this.drawLine(lineRotationRadians, 0, maxLength);
        this.ctx.restore();
      }
    }
    this.ctx.restore();
  }

  drawDistanceBetweenRays() {
    if (this.props.sunrayDistMarker && !this.polarNight) {
      let dx = raysXDiff(this.solarAngle, this.width);
      this.drawRaysDistMarker(dx * 0.5, this.height * (1 - GROUND_FRACTION), dx);
    }
  }

  // Resize canvas to fill its parent.
  resize() {
    let $parent = $(this.canvas).parent();
    this.width = $parent.width();
    this.height = $parent.height();
    // Update canvas attributes (they can be undefined if canvas size is set using CSS).
    this.canvas.width = this.width;
    this.canvas.height = this.height;
    this.render();
  }

  drawSky(color) {
    this.ctx.save();
    this.ctx.fillStyle = color;
    this.ctx.fillRect(0, 0, this.width, this.height);
    this.ctx.restore();
  }

  drawGround(angle, groundFraction) {
    this.ctx.save();
    this.ctx.translate(this.width * 0.5,  this.height * (1 - groundFraction));
    this.ctx.rotate(angle);
    this.ctx.fillStyle = this.props.groundColor === 'auto' ? this.groundColorFunc(this.props.day / MAX_DAY) : this.props.groundColor;
    this.ctx.fillRect(-this.width, 0, this.width * 2, this.height);
    this.ctx.restore();
  }

  drawPolarNightOverlay() {
    if (this.solarAngle < 0 || this.solarAngle > 180 * DEG_2_RAD) {
      this.ctx.save();
      this.ctx.fillStyle = 'rgba(0, 0, 0, 0.4)';
      this.ctx.fillRect(0, 0, this.width, this.height);
      this.ctx.restore();
    }
  }

  drawLine(angle, lengthAdjustment, length) {
    this.ctx.save();
    this.ctx.lineWidth = 4;
    this.ctx.rotate(angle);
    this.ctx.beginPath();
    this.ctx.moveTo(0, -lengthAdjustment);
    this.ctx.lineTo(0, -length);
    this.ctx.stroke();
    this.ctx.restore();
  }

  drawArrow(angle, scaleX=1, scaleY=1) {
    this.ctx.save();
    this.ctx.lineWidth = 1;
    this.ctx.rotate(angle);
    this.ctx.scale(scaleX, scaleY);
    this.ctx.beginPath();
    this.ctx.moveTo(0, 0);
    this.ctx.lineTo(-10, -20);
    this.ctx.lineTo(0, -16);
    this.ctx.lineTo(10, -20);
    this.ctx.lineTo(0, 0);
    this.ctx.fill();
    this.ctx.restore();
  }

  drawRaysDistMarker(x, y, len, angle=0) {
    let height = DIST_MARKER_HEIGHT_FRACTION * this.height;
    this.ctx.save();
    this.ctx.translate(x, y);
    this.ctx.rotate(angle);
    this.ctx.lineWidth = 4;
    this.ctx.strokeStyle = DIST_MARKER_COLOR;
    this.ctx.fillStyle = DIST_MARKER_COLOR;
    this.ctx.beginPath();
    this.ctx.moveTo(0, 0);
    this.ctx.lineTo(0, height);
    this.ctx.moveTo(len, 0);
    this.ctx.lineTo(len, height);
    this.ctx.moveTo(0, 0.5 * height);
    this.ctx.lineTo(len, 0.5 * height);
    this.ctx.stroke();
    this.ctx.translate(0, 0.5 * height);
    this.drawArrow(Math.PI / 2, 0.9, 0.7);
    this.ctx.translate(len, 0);
    this.drawArrow(-Math.PI / 2, 0.9, 0.7);
    this.ctx.restore();
  }
}

function raysXDiff(solarAngle, width) {
  return width / (NUM_BEAMS + 1) / Math.sin(solarAngle);
}
<|endoftext|>


<commit_before>var webservices = require("libraryWS"),
    header = require("HeaderRow");

function resources(json, entry) {
    var data = [];
    var labs = json.computers;

    var pcHeader = new header.HeaderRow("Available Computers");
    data.push(pcHeader);

    for(var i=0; i < json.computers.length; i++) {
        var labRow = Ti.UI.createTableViewRow();
        var labName = Titanium.UI.createLabel({
            text:labs[i].name,
            font:{
                fontSize:18,
                fontWeight:'bold'
            },
            color: '#414444',
            left:10,
            textAlign: 'left'
        });
        var labPcs = Titanium.UI.createLabel({
            text:labs[i].available_count,
            font:{fontSize:18},
            color: '#414444',
            right:10,
            textAlign: 'right'
        });

        labRow.add(labName);
        labRow.add(labPcs);
        data.push(labRow);
    }

    var arHeaderRow = Ti.UI.createTableViewRow({
        backgroundColor: "#D79A27"
    });
    var arHeader = Titanium.UI.createLabel({
        text: "Additional Resources",
        left: 10,
        color: "#FFF",
        font: {
            fontSize: 18,
            fontWeight: "bold"
        }
    });
    arHeaderRow.add(arHeader);
    data.push(arHeaderRow);

    var ar = json.resources;
    for (var j=0; j < ar.length; j++) {
        var arRow = Ti.UI.createTableViewRow();
        var arName = Titanium.UI.createLabel({
            text:ar[j].name,
            font:{
                fontSize:18,
                fontWeight:'bold'
            },
            color: '#414444',
            left:10,
            textAlign: 'left'
        });
        var arNum = Titanium.UI.createLabel({
            text:ar[j].available_count,
            font:{fontSize:18},
            color: '#414444',
            right:10,
            textAlign: 'right'
        });

        arRow.add(arName);
        arRow.add(arNum);
        data.push(arRow);
    }
    $.groupFinderTable.setData(data);
}

function getLibrary(entry) {
    webservices.resources({
        success: resources,
        onerror: onerror,
        entry: entry
    });
}

function onerror() {
    alert("There was an error retrieving the available computers");
}

if(Ti.Network.online) {
    getLibrary("Available Computers");
} else {
    alert("No network connection detected");
}<commit_msg>GroupFinder works. Next commits should be device specific customizations.<commit_after>var webservices = require("libraryWS"),
    header = require("HeaderRow"),
    datetime = require("datetimeJS");

function groupSection(section, data) {
    for(var i=0; i<section.length; i++) {
        var groupHeader = new header.HeaderRow(section[i].title,'#414444');
        var locationRow = Ti.UI.createTableViewRow();
        var locationLabel = Titanium.UI.createLabel({
            text:section[i].location,
            font:{fontSize:18},
            color: '#414444',
            left:10,
            textAlign: 'left'
        });
        locationRow.add(locationLabel);
        var openTime = datetime.cleanTime(section[i].start);
        var closeTime = datetime.cleanTime(section[i].end);
        var time = openTime + " - " + closeTime;
        var timeRow = Ti.UI.createTableViewRow();
        var timeLabel = Titanium.UI.createLabel({
            text:time,
            font:{
                fontSize:18
            },
            color: '#414444',
            left:10,
            textAlign: 'left'
        });
        timeRow.add(timeLabel);

        data.push(groupHeader);
        data.push(locationRow);
        data.push(timeRow);
    }
}

function groupFinder(groups, entry){
    var data = [];
    if(groups.today.length >= 1) {
        var todayHeader = new header.HeaderRow("Today's Groups");
        data.push(todayHeader);
        groupSection(groups.today,data);
    }

    if(groups.tomorrow.length >= 1) {
        var tomorrowHeader = new header.HeaderRow("Tomorrow's Groups");
        data.push(tomorrowHeader);
        groupSection(groups.tomorrow, data);
    }

    if(groups.upcoming.length >= 1) {
        var upcomingHeader = new header.HeaderRow("Upcoming Groups");
        data.push(upcomingHeader);
        var lastDate = '';
        for(var i=0; i<groups.upcoming.length; i++) {
            var startDate = new Date(groups.upcoming[i].start);
            // If there is a new session time, insert a header in the table.
            if(lastDate === '' || startDate.toDateString() !== lastDate) {
                lastDate = startDate.toDateString();
                var dateRow = new header.HeaderRow(datetime.cleanDate(startDate), '#cc3333');
                data.push(dateRow);
            }
            var groupHeader = new header.HeaderRow(groups.upcoming[i].title,'#414444');
            var locationRow = Ti.UI.createTableViewRow();
            var locationLabel = Titanium.UI.createLabel({
                text:groups.upcoming[i].location,
                font:{fontSize:18},
                color: '#414444',
                left:10,
                textAlign: 'left'
            });
            locationRow.add(locationLabel);
            var openTime = datetime.cleanTime(groups.upcoming[i].start);
            var closeTime = datetime.cleanTime(groups.upcoming[i].end);
            var time = openTime + " - " + closeTime;
            var timeRow = Ti.UI.createTableViewRow();
            var timeLabel = Titanium.UI.createLabel({
                text:time,
                font:{fontSize:18},
                color: '#414444',
                left:10,
                textAlign: 'left'
            });
            timeRow.add(timeLabel);

            data.push(groupHeader);
            data.push(locationRow);
            data.push(timeRow);
        }
    }
    if (groups.today.length === 0 && groups.tomorrow.length === 0 && groups.upcoming.length === 0 ) {
        var noData = new header.HeaderRow("No Groups are scheduled");
        data.push(noData);
    }

    $.groupFinderTable.setData(data);
}

function getLibrary(entry) {
    webservices.groupFinder({
        success: groupFinder,
        onerror: onerror,
        entry: entry
    });
}

function onerror() {
    alert("Group Finder encountered an error");
}

if(Ti.Network.online) {
    getLibrary("GroupFinder");
} else {
    alert("No network connection detected");
}<|endoftext|>


<commit_before>

import SPELLS from 'common/SPELLS';

import CoreCastEfficiency from 'Parser/Core/Modules/CastEfficiency';

class CastEfficiency extends CoreCastEfficiency {
  static CPM_ABILITIES = [
    ...CoreCastEfficiency.CPM_ABILITIES,
    {
      spell: SPELLS.STRIKE_OF_THE_WINDLORD,
      category: CastEfficiency.SPELL_CATEGORIES.ROTATIONAL,
      getCooldown: haste => 40,
    },
    {
      spell: SPELLS.FISTS_OF_FURY_CAST,
      category: CastEfficiency.SPELL_CATEGORIES.ROTATIONAL,
      getCooldown: haste => 24,
    },
    {
      spell: SPELLS.RISING_SUN_KICK,
      category: CastEfficiency.SPELL_CATEGORIES.ROTATIONAL,
      getCooldown: haste => 10,
    },
    {
      spell: SPELLS.WHIRLING_DRAGON_PUNCH_TALENT,
      category: CastEfficiency.SPELL_CATEGORIES.ROTATIONAL,
      getCooldown: haste => 24,
      isActive: combatant => combatant.hasTalent(SPELLS.WHIRLING_DRAGON_PUNCH_TALENT.id),
    },
    // cooldowns
    {
      spell: SPELLS.TOUCH_OF_KARMA_CAST,
      category: CastEfficiency.SPELL_CATEGORIES.COOLDOWNS,
      getCooldown: haste => 90,
    },
    {
      spell: SPELLS.TOUCH_OF_DEATH,
      category: CastEfficiency.SPELL_CATEGORIES.COOLDOWNS,
      getCooldown: haste => 120,
    },
    {
      spell: SPELLS.SERENITY_TALENT,
      category: CastEfficiency.SPELL_CATEGORIES.COOLDOWNS,
      getCooldown: (_, combatant) => combatant.owner.modules.sef.reducedCooldownWithTraits,
      isActive: combatant => combatant.hasTalent(SPELLS.SERENITY_TALENT.id),
    },
    // other spells
    {
      spell: SPELLS.BLACKOUT_KICK,
      category: CastEfficiency.SPELL_CATEGORIES.OTHERS,
      getCooldown: haste => null,
    },
    {
      spell: SPELLS.TIGER_PALM,
      category: CastEfficiency.SPELL_CATEGORIES.OTHERS,
      getCooldown: haste => null,
    },
    {
      spell: SPELLS.CHI_WAVE_TALENT,
      category: CastEfficiency.SPELL_CATEGORIES.OTHERS,
      getCooldown: haste => 15,
      isActive: combatant => combatant.hasTalent(SPELLS.CHI_WAVE_TALENT.id),
    },
    {
      spell: SPELLS.SPINNING_CRANE_KICK,
      category: CastEfficiency.SPELL_CATEGORIES.OTHERS,
      getCooldown: haste => null,
    },
  ];
}

export default CastEfficiency;
<commit_msg>renamed another reference<commit_after>

import SPELLS from 'common/SPELLS';

import CoreCastEfficiency from 'Parser/Core/Modules/CastEfficiency';

class CastEfficiency extends CoreCastEfficiency {
  static CPM_ABILITIES = [
    ...CoreCastEfficiency.CPM_ABILITIES,
    {
      spell: SPELLS.STRIKE_OF_THE_WINDLORD,
      category: CastEfficiency.SPELL_CATEGORIES.ROTATIONAL,
      getCooldown: haste => 40,
    },
    {
      spell: SPELLS.FISTS_OF_FURY_CAST,
      category: CastEfficiency.SPELL_CATEGORIES.ROTATIONAL,
      getCooldown: haste => 24,
    },
    {
      spell: SPELLS.RISING_SUN_KICK,
      category: CastEfficiency.SPELL_CATEGORIES.ROTATIONAL,
      getCooldown: haste => 10,
    },
    {
      spell: SPELLS.WHIRLING_DRAGON_PUNCH_TALENT,
      category: CastEfficiency.SPELL_CATEGORIES.ROTATIONAL,
      getCooldown: haste => 24,
      isActive: combatant => combatant.hasTalent(SPELLS.WHIRLING_DRAGON_PUNCH_TALENT.id),
    },
    // cooldowns
    {
      spell: SPELLS.TOUCH_OF_KARMA_CAST,
      category: CastEfficiency.SPELL_CATEGORIES.COOLDOWNS,
      getCooldown: haste => 90,
    },
    {
      spell: SPELLS.TOUCH_OF_DEATH,
      category: CastEfficiency.SPELL_CATEGORIES.COOLDOWNS,
      getCooldown: haste => 120,
    },
    {
      spell: SPELLS.SERENITY_TALENT,
      category: CastEfficiency.SPELL_CATEGORIES.COOLDOWNS,
      getCooldown: (_, combatant) => combatant.owner.modules.stormearthandfire.reducedCooldownWithTraits,
      isActive: combatant => combatant.hasTalent(SPELLS.SERENITY_TALENT.id),
    },
    // other spells
    {
      spell: SPELLS.BLACKOUT_KICK,
      category: CastEfficiency.SPELL_CATEGORIES.OTHERS,
      getCooldown: haste => null,
    },
    {
      spell: SPELLS.TIGER_PALM,
      category: CastEfficiency.SPELL_CATEGORIES.OTHERS,
      getCooldown: haste => null,
    },
    {
      spell: SPELLS.CHI_WAVE_TALENT,
      category: CastEfficiency.SPELL_CATEGORIES.OTHERS,
      getCooldown: haste => 15,
      isActive: combatant => combatant.hasTalent(SPELLS.CHI_WAVE_TALENT.id),
    },
    {
      spell: SPELLS.SPINNING_CRANE_KICK,
      category: CastEfficiency.SPELL_CATEGORIES.OTHERS,
      getCooldown: haste => null,
    },
  ];
}

export default CastEfficiency;
<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import NextLink from 'next/link';
import { withStyles } from 'material-ui/styles';
import { capitalize } from 'material-ui/utils/helpers';

const styles = theme => ({
  root: {
    textDecoration: 'none',
    '&:hover': {
      textDecoration: 'underline',
    },
  },
  variantDefault: {
    color: 'inherit',
  },
  variantPrimary: {
    color: theme.palette.primary.main,
  },
  variantSecondary: {
    color: theme.palette.secondary.main,
  },
  variantButton: {
    '&:hover': {
      textDecoration: 'inherit',
    },
  },
});

class OnClick extends React.Component {
  handleClick = event => {
    if (this.props.onClick) {
      this.props.onClick(event);
    }

    if (this.props.onCustomClick) {
      this.props.onCustomClick(event);
    }
  };

  render() {
    const { component: ComponentProp, onCustomClick, ...props } = this.props;
    return <ComponentProp {...props} onClick={this.handleClick} />;
  }
}

OnClick.propTypes = {
  component: PropTypes.string.isRequired,
  onClick: PropTypes.func,
  onCustomClick: PropTypes.func,
};

function Link(props, context) {
  const {
    activeClassName,
    children: childrenProp,
    component: ComponentProp,
    classes,
    className: classNameProp,
    variant,
    href,
    onClick,
    prefetch,
    ...other
  } = props;

  let ComponentRoot;
  const className = classNames(
    classes.root,
    classes[`variant${capitalize(variant)}`],
    classNameProp,
  );
  let RootProps;
  let children = childrenProp;

  if (ComponentProp) {
    ComponentRoot = ComponentProp;
    RootProps = {
      ...other,
      className,
    };
  } else if (href) {
    ComponentRoot = NextLink;
    RootProps = {
      href,
      prefetch,
      passHref: true,
    };
    // const active = context.url.pathname === href;
    const active = true;
    children = (
      <OnClick
        component="a"
        className={classNames(className, {
          [activeClassName]: active && activeClassName,
        })}
        onCustomClick={onClick}
        {...other}
      >
        {children}
      </OnClick>
    );
  } else {
    ComponentRoot = 'a';
    RootProps = {
      ...other,
      className,
    };
  }

  return <ComponentRoot {...RootProps}>{children}</ComponentRoot>;
}

Link.defaultProps = {
  variant: 'default',
  activeClassName: 'active',
};

Link.contextTypes = {
  url: PropTypes.shape({
    pathname: PropTypes.string.isRequired,
  }).isRequired,
};

Link.propTypes = {
  activeClassName: PropTypes.string,
  children: PropTypes.node.isRequired,
  classes: PropTypes.object.isRequired,
  className: PropTypes.string,
  component: PropTypes.any,
  href: PropTypes.string,
  onClick: PropTypes.func,
  prefetch: PropTypes.bool,
  variant: PropTypes.oneOf(['default', 'primary', 'secondary', 'button']),
};

export default withStyles(styles)(Link);
<commit_msg>Fix active state for Link component<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import NextLink from 'next/link';
import { withStyles } from 'material-ui/styles';
import { capitalize } from 'material-ui/utils/helpers';

const styles = theme => ({
  root: {
    textDecoration: 'none',
    '&:hover': {
      textDecoration: 'underline',
    },
  },
  variantDefault: {
    color: 'inherit',
  },
  variantPrimary: {
    color: theme.palette.primary.main,
  },
  variantSecondary: {
    color: theme.palette.secondary.main,
  },
  variantButton: {
    '&:hover': {
      textDecoration: 'inherit',
    },
  },
});

class OnClick extends React.Component {
  handleClick = event => {
    if (this.props.onClick) {
      this.props.onClick(event);
    }

    if (this.props.onCustomClick) {
      this.props.onCustomClick(event);
    }
  };

  render() {
    const { component: ComponentProp, onCustomClick, ...props } = this.props;
    return <ComponentProp {...props} onClick={this.handleClick} />;
  }
}

OnClick.propTypes = {
  component: PropTypes.string.isRequired,
  onClick: PropTypes.func,
  onCustomClick: PropTypes.func,
};

function Link(props, context) {
  const {
    activeClassName,
    children: childrenProp,
    component: ComponentProp,
    classes,
    className: classNameProp,
    variant,
    href,
    onClick,
    prefetch,
    ...other
  } = props;

  let ComponentRoot;
  const className = classNames(
    classes.root,
    classes[`variant${capitalize(variant)}`],
    classNameProp,
  );
  let RootProps;
  let children = childrenProp;

  if (ComponentProp) {
    ComponentRoot = ComponentProp;
    RootProps = {
      ...other,
      className,
    };
  } else if (href) {
    ComponentRoot = NextLink;
    RootProps = {
      href,
      prefetch,
      passHref: true,
    };
    const active = context.url.pathname === href;
    children = (
      <OnClick
        component="a"
        className={classNames(className, {
          [activeClassName]: active && activeClassName,
        })}
        onCustomClick={onClick}
        {...other}
      >
        {children}
      </OnClick>
    );
  } else {
    ComponentRoot = 'a';
    RootProps = {
      ...other,
      className,
    };
  }

  return <ComponentRoot {...RootProps}>{children}</ComponentRoot>;
}

Link.defaultProps = {
  variant: 'default',
  activeClassName: 'active',
};

Link.contextTypes = {
  url: PropTypes.shape({
    pathname: PropTypes.string.isRequired,
  }).isRequired,
};

Link.propTypes = {
  activeClassName: PropTypes.string,
  children: PropTypes.node.isRequired,
  classes: PropTypes.object.isRequired,
  className: PropTypes.string,
  component: PropTypes.any,
  href: PropTypes.string,
  onClick: PropTypes.func,
  prefetch: PropTypes.bool,
  variant: PropTypes.oneOf(['default', 'primary', 'secondary', 'button']),
};

export default withStyles(styles)(Link);
<|endoftext|>


<commit_before>Element.prototype.cloneNode = function(deep) {
	var tmp, clone;
	tmp = document.createElement('div');
	tmp.innerHTML = this.outerHTML;
	clone = tmp.firstChild;

	if ('checked' in this) clone.checked = this.checked;

	if (!deep && clone.innerHTML) {
		clone.innerHTML = '';
	}

	return clone;
}
<commit_msg>DOM4 spec<commit_after>Element.prototype.cloneNode = function(deep) {
	var tmp, clone;
	tmp = document.createElement('div');
	tmp.innerHTML = this.outerHTML;
	clone = tmp.firstChild;

	if ('checked' in this) clone.checked = this.checked;

	if (deep === false && clone.innerHTML) {
		clone.innerHTML = '';
	}

	return clone;
}
<|endoftext|>


<commit_before><commit_msg>         <commit_after><|endoftext|>


<commit_before>import inatjs from "inaturalistjs";
import {
  loadingDiscussionItem,
  fetchCurrentObservation
} from "./current_observation_actions";
import { fetchObservationsStats } from "./observations_stats_actions";

const POST_IDENTIFICATION = "post_identification";

function postIdentification( params ) {
  return function ( dispatch ) {
    const body = Object.assign( {}, params );
    body.user_id = 1;
    return inatjs.identifications.create( body )
      .then( response => {
        dispatch( fetchObservationsStats( ) );
        return response;
      } );
  };
}

function agreeWithObservaiton( observation ) {
  return function ( dispatch ) {
    dispatch( loadingDiscussionItem( ) );
    return dispatch(
      postIdentification( { observation_id: observation.id, taxon_id: observation.taxon.id } )
    ).then( ( ) => {
      dispatch( fetchCurrentObservation( observation ) );
    } );
  };
}

function agreeWithCurrentObservation( ) {
  return function ( dispatch, getState ) {
    return agreeWithObservaiton( getState( ).currentObservation.observation );
  };
}

export {
  postIdentification,
  POST_IDENTIFICATION,
  agreeWithObservaiton,
  agreeWithCurrentObservation
};
<commit_msg>Fixed broken agreeing in obs modal.<commit_after>import inatjs from "inaturalistjs";
import {
  loadingDiscussionItem,
  fetchCurrentObservation
} from "./current_observation_actions";
import { fetchObservationsStats } from "./observations_stats_actions";

const POST_IDENTIFICATION = "post_identification";

function postIdentification( params ) {
  return function ( dispatch ) {
    const body = Object.assign( {}, params );
    body.user_id = 1;
    return inatjs.identifications.create( body )
      .then( response => {
        dispatch( fetchObservationsStats( ) );
        return response;
      } );
  };
}

function agreeWithObservaiton( observation ) {
  return function ( dispatch ) {
    dispatch( loadingDiscussionItem( ) );
    return dispatch(
      postIdentification( { observation_id: observation.id, taxon_id: observation.taxon.id } )
    ).then( ( ) => {
      dispatch( fetchCurrentObservation( observation ) );
    } );
  };
}

function agreeWithCurrentObservation( ) {
  return function ( dispatch, getState ) {
    return dispatch( agreeWithObservaiton( getState( ).currentObservation.observation ) );
  };
}

export {
  postIdentification,
  POST_IDENTIFICATION,
  agreeWithObservaiton,
  agreeWithCurrentObservation
};
<|endoftext|>


<commit_before>import { Component } from '../src/index';
import { componnetReady } from '../src/utils';

describe('component', () => {
  let component;
  let hooks = {};

  beforeAll(() => {
    hooks = {
      nameObserver: jasmine.createSpy('nameObserver'),
      activeObserver: jasmine.createSpy('activeObserver'),
      created: jasmine.createSpy('created'),
      init: jasmine.createSpy('init'),
      connected: jasmine.createSpy('connected'),
      ready: jasmine.createSpy('ready'),
      disconnected: jasmine.createSpy('disconnected'),
    };
    Component('test-component', {
      mixins: [{
        properties: {
          fullName: {
            type: String,
            observer: hooks.nameObserver,
          },
          age: Number,
        },
        ready: hooks.ready,
        disconnected: hooks.disconnected,
      }],
      properties: {
        active: {
          type: Boolean,
          observer: '_activeChanged',
        },
        json: {
          type: Object,
          default: { x: 1 },
        },
        array: Array,
        customProp: {
          get() {
            console.log(this.fullName, this.age);
            return `${this.fullName}_${this.age}`;
          },
        },
        notAttribute: {
          type: Boolean,
          syncAttribute: false,
          default: false,
        },
      },
      created: hooks.created,
      init: hooks.init,
      connected: hooks.connected,
      _activeChanged: hooks.activeObserver,
    });
  });

  beforeEach(async () => {
    component = document.createElement('test-component');
    component.setAttribute('array', '[1,2]');
    document.body.appendChild(component);
    await componnetReady(component);
  });

  afterEach(() => {
    Object.keys(hooks).forEach((hookName) => {
      hooks[hookName].calls.reset();
    });
    if (component.parentNode) component.parentNode.removeChild(component);
    component = null;
  });

  it('inherits from HTMLElement', () => {
    expect(component instanceof HTMLElement).toBe(true);
  });

  it('parses attributes', () => {
    expect(component.fullName).toBe('');
    expect(component.hasAttribute('full-name')).toBe(false);

    component.fullName = 'farthinker';

    expect(component.fullName).toBe('farthinker');
    expect(component.getAttribute('full-name')).toBe('farthinker');
    expect(hooks.nameObserver).toHaveBeenCalledWith('farthinker', '');

    expect(component.active).toBe(false);
    expect(component.hasAttribute('active')).toBe(false);
    component.active = true;

    expect(component.active).toBe(true);
    expect(component.hasAttribute('active')).toBe(true);
    expect(hooks.activeObserver).toHaveBeenCalledWith(true, false);

    expect(component.json).toEqual({ x: 1 });
    component.json = { y: 2 };

    expect(component.json).toEqual({ y: 2 });
    expect(component.getAttribute('json')).toBe('{"y":2}');

    expect(component.array).toEqual([1, 2]);
    component.array = [1, 2, 3];

    expect(component.array).toEqual([1, 2, 3]);
    expect(component.getAttribute('array')).toBe('[1,2,3]');
  });

  it('call lifecycle hooks', (done) => {
    expect(hooks.created).toHaveBeenCalledBefore(hooks.init);
    expect(hooks.init).toHaveBeenCalledBefore(hooks.connected);
    expect(hooks.connected).toHaveBeenCalledBefore(hooks.ready);
    expect(hooks.ready).toHaveBeenCalledWith();

    component.parentNode.removeChild(component);
    setTimeout(() => {
      expect(hooks.disconnected).toHaveBeenCalledWith();
      done();
    });
  });

  it('call ready hook after all child component are ready', async () => {
    hooks.ready.calls.reset();
    const parentComponent = document.createElement('test-component');
    const childComponent = document.createElement('test-component');
    parentComponent.fullName = 'parent';
    childComponent.fullName = 'child';
    parentComponent.appendChild(childComponent);
    document.body.appendChild(parentComponent);
    await componnetReady(parentComponent);

    expect(hooks.ready).toHaveBeenCalledTimes(2);
    expect(hooks.ready.calls.first().object).toBe(childComponent);
    expect(hooks.ready.calls.mostRecent().object).toBe(parentComponent);
  });

  it('could has custom properties', () => {
    component.fullName = 'farthinker';
    component.age = 18;

    expect(component.customProp).toBe('farthinker_18');
  });

  it('could has independent properties', () => {
    expect(component.notAttribute).toBe(false);
    component.notAttribute = true;

    expect(component.notAttribute).toBe(true);
    expect(component.hasAttribute('not-attribute')).toBe(false);
  });
});
<commit_msg>CHG: remove debug code<commit_after>import { Component } from '../src/index';
import { componnetReady } from '../src/utils';

describe('component', () => {
  let component;
  let hooks = {};

  beforeAll(() => {
    hooks = {
      nameObserver: jasmine.createSpy('nameObserver'),
      activeObserver: jasmine.createSpy('activeObserver'),
      created: jasmine.createSpy('created'),
      init: jasmine.createSpy('init'),
      connected: jasmine.createSpy('connected'),
      ready: jasmine.createSpy('ready'),
      disconnected: jasmine.createSpy('disconnected'),
    };
    Component('test-component', {
      mixins: [{
        properties: {
          fullName: {
            type: String,
            observer: hooks.nameObserver,
          },
          age: Number,
        },
        ready: hooks.ready,
        disconnected: hooks.disconnected,
      }],
      properties: {
        active: {
          type: Boolean,
          observer: '_activeChanged',
        },
        json: {
          type: Object,
          default: { x: 1 },
        },
        array: Array,
        customProp: {
          get() {
            return `${this.fullName}_${this.age}`;
          },
        },
        notAttribute: {
          type: Boolean,
          syncAttribute: false,
          default: false,
        },
      },
      created: hooks.created,
      init: hooks.init,
      connected: hooks.connected,
      _activeChanged: hooks.activeObserver,
    });
  });

  beforeEach(async () => {
    component = document.createElement('test-component');
    component.setAttribute('array', '[1,2]');
    document.body.appendChild(component);
    await componnetReady(component);
  });

  afterEach(() => {
    Object.keys(hooks).forEach((hookName) => {
      hooks[hookName].calls.reset();
    });
    if (component.parentNode) component.parentNode.removeChild(component);
    component = null;
  });

  it('inherits from HTMLElement', () => {
    expect(component instanceof HTMLElement).toBe(true);
  });

  it('parses attributes', () => {
    expect(component.fullName).toBe('');
    expect(component.hasAttribute('full-name')).toBe(false);

    component.fullName = 'farthinker';

    expect(component.fullName).toBe('farthinker');
    expect(component.getAttribute('full-name')).toBe('farthinker');
    expect(hooks.nameObserver).toHaveBeenCalledWith('farthinker', '');

    expect(component.active).toBe(false);
    expect(component.hasAttribute('active')).toBe(false);
    component.active = true;

    expect(component.active).toBe(true);
    expect(component.hasAttribute('active')).toBe(true);
    expect(hooks.activeObserver).toHaveBeenCalledWith(true, false);

    expect(component.json).toEqual({ x: 1 });
    component.json = { y: 2 };

    expect(component.json).toEqual({ y: 2 });
    expect(component.getAttribute('json')).toBe('{"y":2}');

    expect(component.array).toEqual([1, 2]);
    component.array = [1, 2, 3];

    expect(component.array).toEqual([1, 2, 3]);
    expect(component.getAttribute('array')).toBe('[1,2,3]');
  });

  it('call lifecycle hooks', (done) => {
    expect(hooks.created).toHaveBeenCalledBefore(hooks.init);
    expect(hooks.init).toHaveBeenCalledBefore(hooks.connected);
    expect(hooks.connected).toHaveBeenCalledBefore(hooks.ready);
    expect(hooks.ready).toHaveBeenCalledWith();

    component.parentNode.removeChild(component);
    setTimeout(() => {
      expect(hooks.disconnected).toHaveBeenCalledWith();
      done();
    });
  });

  it('call ready hook after all child component are ready', async () => {
    hooks.ready.calls.reset();
    const parentComponent = document.createElement('test-component');
    const childComponent = document.createElement('test-component');
    parentComponent.fullName = 'parent';
    childComponent.fullName = 'child';
    parentComponent.appendChild(childComponent);
    document.body.appendChild(parentComponent);
    await componnetReady(parentComponent);

    expect(hooks.ready).toHaveBeenCalledTimes(2);
    expect(hooks.ready.calls.first().object).toBe(childComponent);
    expect(hooks.ready.calls.mostRecent().object).toBe(parentComponent);
  });

  it('could has custom properties', () => {
    component.fullName = 'farthinker';
    component.age = 18;

    expect(component.customProp).toBe('farthinker_18');
  });

  it('could has independent properties', () => {
    expect(component.notAttribute).toBe(false);
    component.notAttribute = true;

    expect(component.notAttribute).toBe(true);
    expect(component.hasAttribute('not-attribute')).toBe(false);
  });
});
<|endoftext|>


<commit_before>'use strict'

/**
 * Executes the solution
 */

 // Temporary, see: https://github.com/senecajs/seneca/issues/566
 process.removeAllListeners('warning')

var seneca = require('seneca')()
seneca.use(process.argv[2])

seneca.act({role: 'math', cmd: 'sum', left: process.argv[3], right: process.argv[4]}, function (err, result) {
  if (err) return console.error(err)
  console.log(result)
})
<commit_msg>Fixup the runner with the same fix as the verifier.<commit_after>'use strict'

/**
 * Executes the solution
 */

 // Temporary, see: https://github.com/senecajs/seneca/issues/566
 process.removeAllListeners('warning')

var seneca = require('seneca')()
seneca.use(process.argv[2])

seneca.act({role: 'math', cmd: 'sum', left: process.argv[3], right: process.argv[4]}, function (err, result) {
  if (err) return console.log(err)
  console.log(result)
})
<|endoftext|>