<commit_before>import Ember from 'ember';

// Helper to help determine a values truthlyness,
// mainly needed for string "false"
// (Used to be a jQuery post about this, but below is very similar)
// http://www.sitepoint.com/javascript-truthy-falsy/
export function isTruthy( value ) {

  // Conditions that JavaScript considers truthy
  // but would typically be considered falsy
  if (value === 'false' ||
    value === '0' ||
    value === [] ||
    value === {}
  ) {
    return false;
  }

  // Let JavaScript decide
  return !!value;

} // isTruthy()

// Convert the value to the proper option type.
// This is especially useful for string booleans ("false"),
// and Ember SafeString objects (typically from intl addons)
export function convertValue( value, type ) {
  if ( type === 'boolean' ) {
    return isTruthy( value );
  } else if ( type === 'string' ) {
    Ember.assert('Object must have a .toString() method!', !value.toString);
    return value.toString();
  } else if ( type === 'array' ) {
    return ( Array.isArray( value ) ? value : [ value ] );
  } else {
    return value;
  }
} // convertValue()

export default Ember.Component.extend({
  tagName: 'input',
  type: 'checkbox',

  // Flags used for internal state management
  switchInitializing : false,
  switchInitialized  : false,

  // Native HTML element attributes
  attributeBindings: [
    'autofocus',
    'checked',
    'disabled',
    'form',
    'formnovalidate',
    'name',
    'readonly',
    'required',
    'tabindex',
    'type',
    'value'
  ], // :attributeBindings

  // List of bootstrap-switch options,
  // used to determine which attributes apply to the switch
  // http://www.bootstrap-switch.org/options.html
  switchOptions: {
    state         : 'boolean',
    size          : 'string',
    animate       : 'boolean',
    disabled      : 'boolean',
    readonly      : 'boolean',
    indeterminate : 'boolean',
    inverse       : 'boolean',
    radioAllOff   : 'boolean',
    onColor       : 'string',
    offColor      : 'string',
    onText        : 'string',
    offText       : 'string',
    labelText     : 'string',
    handleWidth   : 'string',
    labelWidth    : 'string',
    baseClass     : 'string',
    wrapperClass  : 'array'
    // onInit and onSwitchChange handled by the component
  }, // :switchOptions

  // Helpful default, set 'indeterminate' state to true
  // if the checked option is not defined. Users should
  // override (define the attribute) if not desired
  indeterminate: Ember.computed('checked', function(){
    const checked = this.get('checked');
    return (checked === null || checked === undefined);
  }), // :indeterminate

  // Need to keep track of the initial attributes for 'didRender()'
  didInitAttrs( attrs ){
    this._super(...arguments);
    this.set('_initialAttrs', attrs.attrs);
  }, // didInitAttrs()

  // Setup task to create the switch using defined options
  // By using the 'didRender' hook, instead of 'didInsertElement',
  // this component should work properly with Fastboot (not tested)
  didRender(){
    this._super(...arguments);

    // Since this hook will run upon subsequent re-renders,
    // we only need to initialize the switch once
    if (this.get('switchInitializing') || this.get('switchInitialized')) {
      return;
    }

    // Set the initializing flag so we don't initialize again
    this.set('switchInitializing', true);

    // Setup variables needed
    const $element     = this.$();
    const initialAttrs = this.get('_initialAttrs') || {};
    const checked      = this.getAttrFor( initialAttrs, 'checked' );

    // Start the options hash...
    // Always add the event handlers,
    // eliminates the need to add event listeners to the DOM
    let options = {
      onInit         : this.switchInit.bind(this),
      onSwitchChange : this.switchChange.bind(this),
      indeterminate  : this.get('indeterminate') // inject the "helpful default"
    };

    for (let attrKey in initialAttrs) {

      // Ensure the camelized format is used,
      // bootstrap-switch option names use this format
      let attrKeyCamelized = Ember.String.camelize( attrKey );

      // bootstrap-switch uses 'state' instead of 'checked'
      if (attrKey === 'checked') {
        attrKeyCamelized = 'state';
      }

      // Ignore non-switch options, such as other native HTML attributes, eg: tabindex
      if (!this.switchOptions.hasOwnProperty( attrKeyCamelized )) {
        continue;
      }

      // Get the value from attrs
      // Note: Private Ember API, watch for changes
      // https://github.com/emberjs/ember.js/blob/v2.1.0/packages/ember-views/lib/compat/attrs-proxy.js#L54
      let attrValue = this.getAttrFor( initialAttrs, attrKey );

      // Convert the value to the proper option type
      options[ attrKeyCamelized ] = convertValue( attrValue, this.switchOptions[ attrKeyCamelized ]);

    } // for ()

    // Enables a <form> reset to work properly
    $element.prop('defaultChecked', isTruthy(checked));

    // Create the switch with defined options
    $element.bootstrapSwitch( options );

  }, // didRender()

  // Task to update the bootstrap-switch as options are changed.
  // This replaces the observers used in an older version of this addon.
  didUpdateAttrs( attrs ){
    this._super(...arguments);
    const {newAttrs, oldAttrs} = attrs; // cool ES2015 syntax to destructure named properties

    // Skip if bootstrap-switch not created yet
    if (!this.get('switchInitialized')) {
      // Save new attributes if switch initialization not started
      if (!this.get('switchInitializing')) {
        this.set('_initialAttrs', newAttrs);
      }
      return;
    }

    // Setup variables needed
    const $element = this.$();

    // Loop through each new attribute...
    for (let attrKey in newAttrs) {

      // Ensure the camelized format is used,
      // bootstrap-switch option names use this format
      let attrKeyCamelized = Ember.String.camelize( attrKey );

      // bootstrap-switch uses 'state' instead of 'checked'
      if (attrKey === 'checked') {
        attrKeyCamelized = 'state';
      }

      // Ignore non-switch options, such as other native HTML attributes, eg: tabindex
      if (!this.switchOptions.hasOwnProperty( attrKeyCamelized )) {
        continue;
      }

      // Get the values to compare
      // Note: Private Ember API, watch for changes
      // https://github.com/emberjs/ember.js/blob/v2.1.0/packages/ember-views/lib/compat/attrs-proxy.js#L54
      let newValue = this.getAttrFor( newAttrs, attrKey );
      let oldValue = this.getAttrFor( oldAttrs, attrKey );

      // Ignore if the option has not changed
      if (newValue === oldValue) {
        continue;
      }

      // Convert the value to the proper option type
      let convertedValue = convertValue( newValue, this.switchOptions[ attrKeyCamelized ]);

      // Special case when updating the checked state
      if (attrKeyCamelized === 'state') {

        // If the old checked state was undefined, but is now defined, then..
        // This helps when the 'checked' attribute is a promise
        if (
          (oldValue === undefined || oldValue === null) &&
          (newValue !== undefined && newValue !== null)
        ) {
          $element.prop('defaultChecked', convertedValue); // Enables a <form> reset to work properly
        }

        // Third param == skip the switchChanged event
        $element.bootstrapSwitch( attrKeyCamelized, convertedValue, true );
      } else {
        $element.bootstrapSwitch( attrKeyCamelized, convertedValue );
      }

    } // for ()

    // Inject the "helpful default" when not defined as an attribute
    if (!newAttrs.hasOwnProperty('indeterminate')) {
      $element.bootstrapSwitch( 'indeterminate', this.get('indeterminate') );
    }

  }, // didUpdateAttrs()

  // Tear down task to update state flags, destroy the switch, and trigger the destroy action
  willDestroyElement(){
    this._super(...arguments);

    // Ignore if not yet initialized
    if (!this.get('switchInitialized')) {
      return;
    }

    // Remove bootstrap-switch
    this.$().bootstrapSwitch('destroy');

    // Change the initialization flag
    this.set('switchInitialized', false);

    // Call destroy action if defined
    if (this.get('on-destroy')) {
      this.sendAction('on-destroy', this);
    }

  }, // willDestroyElement()

  // Event handler triggered after the switch is initialized
  switchInit( event ){

    // Change the initialization flags
    this.set('switchInitializing', false);
    this.set('switchInitialized',  true);

    // Call init action if defined
    if (this.get('on-init')) {
      this.sendAction('on-init', this, event);
    }
  }, // switchInit()

  // Event handler triggered whenever the switch changes
  switchChange( event, state ){

    // Save the switch state to the component,
    // bubbles back up to the attribute when mutable..
    this.set('checked', state);

    // Call radio change action if defined,
    // passes the value as the first argument for easy use with radios
    // Ex: on-switch-active=(action (mut prop))
    if (state && this.get('on-switch-active')) {
      this.sendAction('on-switch-active', this.get('value'), state, this, event);
    }

    // Call switch change action if defined
    if (this.get('on-switch-change')) {
      this.sendAction('on-switch-change', state, this, event);
    }

  }, // switchChange()

  // bootstrap-switch methods are exposed as ember actions
  // http://www.bootstrap-switch.org/methods.html
  actions: {
    toggleState( skip = false ){
      if (this.get('switchInitialized')) {
        this.$().bootstrapSwitch('toggleState', skip);
      }
    }, // toggleState()
    toggleAnimate(){
      if (this.get('switchInitialized')) {
        this.$().bootstrapSwitch('toggleAnimate');
      }
    }, // toggleAnimate()
    toggleDisabled(){
      if (this.get('switchInitialized')) {
        this.$().bootstrapSwitch('toggleDisabled');
      }
    }, // toggleDisabled()
    toggleReadonly(){
      if (this.get('switchInitialized')) {
        this.$().bootstrapSwitch('toggleReadonly');
      }
    }, // toggleReadonly()
    toggleIndeterminate(){
      if (this.get('switchInitialized')) {
        this.$().bootstrapSwitch('toggleIndeterminate');
      }
    }, // toggle Indeterminate()
    toggleInverse(){
      if (this.get('switchInitialized')) {
        this.$().bootstrapSwitch('toggleInverse');
      }
    } // toggleInverse()
  } // :actions

}); // export default
<commit_msg>fix assertion in f9e9925ee29cc5447cbfd1b1c58c4de87e663613<commit_after>import Ember from 'ember';

// Helper to help determine a values truthlyness,
// mainly needed for string "false"
// (Used to be a jQuery post about this, but below is very similar)
// http://www.sitepoint.com/javascript-truthy-falsy/
export function isTruthy( value ) {

  // Conditions that JavaScript considers truthy
  // but would typically be considered falsy
  if (value === 'false' ||
    value === '0' ||
    value === [] ||
    value === {}
  ) {
    return false;
  }

  // Let JavaScript decide
  return !!value;

} // isTruthy()

// Convert the value to the proper option type.
// This is especially useful for string booleans ("false"),
// and Ember SafeString objects (typically from intl addons)
export function convertValue( value, type ) {
  if ( type === 'boolean' ) {
    return isTruthy( value );
  } else if ( type === 'string' ) {
    Ember.assert('Object must have a .toString() method!', typeof value.toString === 'function');
    return value.toString();
  } else if ( type === 'array' ) {
    return ( Array.isArray( value ) ? value : [ value ] );
  } else {
    return value;
  }
} // convertValue()

export default Ember.Component.extend({
  tagName: 'input',
  type: 'checkbox',

  // Flags used for internal state management
  switchInitializing : false,
  switchInitialized  : false,

  // Native HTML element attributes
  attributeBindings: [
    'autofocus',
    'checked',
    'disabled',
    'form',
    'formnovalidate',
    'name',
    'readonly',
    'required',
    'tabindex',
    'type',
    'value'
  ], // :attributeBindings

  // List of bootstrap-switch options,
  // used to determine which attributes apply to the switch
  // http://www.bootstrap-switch.org/options.html
  switchOptions: {
    state         : 'boolean',
    size          : 'string',
    animate       : 'boolean',
    disabled      : 'boolean',
    readonly      : 'boolean',
    indeterminate : 'boolean',
    inverse       : 'boolean',
    radioAllOff   : 'boolean',
    onColor       : 'string',
    offColor      : 'string',
    onText        : 'string',
    offText       : 'string',
    labelText     : 'string',
    handleWidth   : 'string',
    labelWidth    : 'string',
    baseClass     : 'string',
    wrapperClass  : 'array'
    // onInit and onSwitchChange handled by the component
  }, // :switchOptions

  // Helpful default, set 'indeterminate' state to true
  // if the checked option is not defined. Users should
  // override (define the attribute) if not desired
  indeterminate: Ember.computed('checked', function(){
    const checked = this.get('checked');
    return (checked === null || checked === undefined);
  }), // :indeterminate

  // Need to keep track of the initial attributes for 'didRender()'
  didInitAttrs( attrs ){
    this._super(...arguments);
    this.set('_initialAttrs', attrs.attrs);
  }, // didInitAttrs()

  // Setup task to create the switch using defined options
  // By using the 'didRender' hook, instead of 'didInsertElement',
  // this component should work properly with Fastboot (not tested)
  didRender(){
    this._super(...arguments);

    // Since this hook will run upon subsequent re-renders,
    // we only need to initialize the switch once
    if (this.get('switchInitializing') || this.get('switchInitialized')) {
      return;
    }

    // Set the initializing flag so we don't initialize again
    this.set('switchInitializing', true);

    // Setup variables needed
    const $element     = this.$();
    const initialAttrs = this.get('_initialAttrs') || {};
    const checked      = this.getAttrFor( initialAttrs, 'checked' );

    // Start the options hash...
    // Always add the event handlers,
    // eliminates the need to add event listeners to the DOM
    let options = {
      onInit         : this.switchInit.bind(this),
      onSwitchChange : this.switchChange.bind(this),
      indeterminate  : this.get('indeterminate') // inject the "helpful default"
    };

    for (let attrKey in initialAttrs) {

      // Ensure the camelized format is used,
      // bootstrap-switch option names use this format
      let attrKeyCamelized = Ember.String.camelize( attrKey );

      // bootstrap-switch uses 'state' instead of 'checked'
      if (attrKey === 'checked') {
        attrKeyCamelized = 'state';
      }

      // Ignore non-switch options, such as other native HTML attributes, eg: tabindex
      if (!this.switchOptions.hasOwnProperty( attrKeyCamelized )) {
        continue;
      }

      // Get the value from attrs
      // Note: Private Ember API, watch for changes
      // https://github.com/emberjs/ember.js/blob/v2.1.0/packages/ember-views/lib/compat/attrs-proxy.js#L54
      let attrValue = this.getAttrFor( initialAttrs, attrKey );

      // Convert the value to the proper option type
      options[ attrKeyCamelized ] = convertValue( attrValue, this.switchOptions[ attrKeyCamelized ]);

    } // for ()

    // Enables a <form> reset to work properly
    $element.prop('defaultChecked', isTruthy(checked));

    // Create the switch with defined options
    $element.bootstrapSwitch( options );

  }, // didRender()

  // Task to update the bootstrap-switch as options are changed.
  // This replaces the observers used in an older version of this addon.
  didUpdateAttrs( attrs ){
    this._super(...arguments);
    const {newAttrs, oldAttrs} = attrs; // cool ES2015 syntax to destructure named properties

    // Skip if bootstrap-switch not created yet
    if (!this.get('switchInitialized')) {
      // Save new attributes if switch initialization not started
      if (!this.get('switchInitializing')) {
        this.set('_initialAttrs', newAttrs);
      }
      return;
    }

    // Setup variables needed
    const $element = this.$();

    // Loop through each new attribute...
    for (let attrKey in newAttrs) {

      // Ensure the camelized format is used,
      // bootstrap-switch option names use this format
      let attrKeyCamelized = Ember.String.camelize( attrKey );

      // bootstrap-switch uses 'state' instead of 'checked'
      if (attrKey === 'checked') {
        attrKeyCamelized = 'state';
      }

      // Ignore non-switch options, such as other native HTML attributes, eg: tabindex
      if (!this.switchOptions.hasOwnProperty( attrKeyCamelized )) {
        continue;
      }

      // Get the values to compare
      // Note: Private Ember API, watch for changes
      // https://github.com/emberjs/ember.js/blob/v2.1.0/packages/ember-views/lib/compat/attrs-proxy.js#L54
      let newValue = this.getAttrFor( newAttrs, attrKey );
      let oldValue = this.getAttrFor( oldAttrs, attrKey );

      // Ignore if the option has not changed
      if (newValue === oldValue) {
        continue;
      }

      // Convert the value to the proper option type
      let convertedValue = convertValue( newValue, this.switchOptions[ attrKeyCamelized ]);

      // Special case when updating the checked state
      if (attrKeyCamelized === 'state') {

        // If the old checked state was undefined, but is now defined, then..
        // This helps when the 'checked' attribute is a promise
        if (
          (oldValue === undefined || oldValue === null) &&
          (newValue !== undefined && newValue !== null)
        ) {
          $element.prop('defaultChecked', convertedValue); // Enables a <form> reset to work properly
        }

        // Third param == skip the switchChanged event
        $element.bootstrapSwitch( attrKeyCamelized, convertedValue, true );
      } else {
        $element.bootstrapSwitch( attrKeyCamelized, convertedValue );
      }

    } // for ()

    // Inject the "helpful default" when not defined as an attribute
    if (!newAttrs.hasOwnProperty('indeterminate')) {
      $element.bootstrapSwitch( 'indeterminate', this.get('indeterminate') );
    }

  }, // didUpdateAttrs()

  // Tear down task to update state flags, destroy the switch, and trigger the destroy action
  willDestroyElement(){
    this._super(...arguments);

    // Ignore if not yet initialized
    if (!this.get('switchInitialized')) {
      return;
    }

    // Remove bootstrap-switch
    this.$().bootstrapSwitch('destroy');

    // Change the initialization flag
    this.set('switchInitialized', false);

    // Call destroy action if defined
    if (this.get('on-destroy')) {
      this.sendAction('on-destroy', this);
    }

  }, // willDestroyElement()

  // Event handler triggered after the switch is initialized
  switchInit( event ){

    // Change the initialization flags
    this.set('switchInitializing', false);
    this.set('switchInitialized',  true);

    // Call init action if defined
    if (this.get('on-init')) {
      this.sendAction('on-init', this, event);
    }
  }, // switchInit()

  // Event handler triggered whenever the switch changes
  switchChange( event, state ){

    // Save the switch state to the component,
    // bubbles back up to the attribute when mutable..
    this.set('checked', state);

    // Call radio change action if defined,
    // passes the value as the first argument for easy use with radios
    // Ex: on-switch-active=(action (mut prop))
    if (state && this.get('on-switch-active')) {
      this.sendAction('on-switch-active', this.get('value'), state, this, event);
    }

    // Call switch change action if defined
    if (this.get('on-switch-change')) {
      this.sendAction('on-switch-change', state, this, event);
    }

  }, // switchChange()

  // bootstrap-switch methods are exposed as ember actions
  // http://www.bootstrap-switch.org/methods.html
  actions: {
    toggleState( skip = false ){
      if (this.get('switchInitialized')) {
        this.$().bootstrapSwitch('toggleState', skip);
      }
    }, // toggleState()
    toggleAnimate(){
      if (this.get('switchInitialized')) {
        this.$().bootstrapSwitch('toggleAnimate');
      }
    }, // toggleAnimate()
    toggleDisabled(){
      if (this.get('switchInitialized')) {
        this.$().bootstrapSwitch('toggleDisabled');
      }
    }, // toggleDisabled()
    toggleReadonly(){
      if (this.get('switchInitialized')) {
        this.$().bootstrapSwitch('toggleReadonly');
      }
    }, // toggleReadonly()
    toggleIndeterminate(){
      if (this.get('switchInitialized')) {
        this.$().bootstrapSwitch('toggleIndeterminate');
      }
    }, // toggle Indeterminate()
    toggleInverse(){
      if (this.get('switchInitialized')) {
        this.$().bootstrapSwitch('toggleInverse');
      }
    } // toggleInverse()
  } // :actions

}); // export default
<|endoftext|>


<commit_before>const BTTVEmote = require('./BTTVEmote');
const Channel = require('./Channel');
const Collection = require('../util/Collection');
const Constants = require('../util/Constants');
const FFZEmote = require('./FFZEmote');
const request = require('snekfetch');
const TwitchEmote = require('./TwitchEmote');

/**
 * The fetcher that gets and caches emotes.
 * @class EmoteFetcher
 */
class EmoteFetcher {
    /**
     * Creates a new fetcher.
     * @param {Object} options - Options for the fetcher.
     */
    constructor(options = {}) {
        /**
         * Options for the fetcher.
         * @type {Object}
         */
        this.options = options;

        /**
         * Cached emotes.
         * Collectionped by emote code to Emote instance.
         * @type {Collection<string, Emote>}
         */
        this.emotes = new Collection();

        /**
         * Cached channels.
         * Collectionped by name to Channel instance.
         * @type {Collection<string, Channel>}
         */
        this.channels = new Collection();
    }

    /**
     * The global channel for both Twitch and BTTV.
     * @readonly
     * @type {?Channel}
     */
    get globalChannel() {
        return this.channels.get(null);
    }

    /**
     * Gets the raw twitch emotes data.
     * @returns {Promise<Object>}
     */
    _getRawTwitchEmotes() {
        return request.get(Constants.Twitch.All).then(res => res.body.images);
    }

    /**
     * Converts and caches a raw twitch emote.
     * @param {string} id - ID of the emote.
     * @param {Object} data - Raw data.
     * @returns {TwitchEmote}
     */
    _cacheTwitchEmote(id, data) {
        let channel = this.channels.get(data.channel);
        if (!channel) {
            channel = new Channel(this, data.channel);
            this.channels.set(data.channel, channel);
        }

        channel.title = data.channel_title;
        const emote = new TwitchEmote(channel, id, data);
        this.emotes.set(emote.code, emote);
        channel.emotes.set(emote.code, emote);
        return emote;
    }

    /**
     * Gets the raw BTTV emotes data for a channel.
     * @param {string} name - Name of the channel.
     * @returns {Promise<Object[]>}
     */
    _getRawBTTVEmotes(name) {
        const endpoint = !name
        ? Constants.BTTV.Global
        : Constants.BTTV.Channel(name); // eslint-disable-line new-cap

        return request.get(endpoint).then(res => res.body.emotes);
    }

    /**
     * Converts and caches a raw BTTV emote.
     * @param {string} name - Name of the channel.
     * @param {Object} data - Raw data.
     * @returns {BTTVEmote}
     */
    _cacheBTTVEmote(name, data) {
        let channel = this.channels.get(name);
        if (!channel) {
            channel = new Channel(this, name);
            this.channels.set(name, channel);
        }

        const emote = new BTTVEmote(channel, data.id, data);
        this.emotes.set(emote.code, emote);
        channel.emotes.set(emote.code, emote);
        return emote;
    }

    /**
     * Gets the raw FFZ emotes data for a channel.
     * @param {string} name - Name of the channel.
     * @returns {Promise<Object[]>}
     */
    _getRawFFZEmotes(name) {
        return request.get(Constants.FFZ.Channel(name)).then(res => { // eslint-disable-line new-cap
            const emotes = [];
            for (const key of Object.keys(res.body.sets)) {
                const set = res.body.sets[key];
                emotes.push(...set.emoticons);
            }

            return emotes;
        });
    }

    /**
     * Converts and caches a raw FFZ emote.
     * @param {string} name - Name of the channel.
     * @param {Object} data - Raw data.
     * @returns {FFZEmote}
     */
    _cacheFFZEmote(name, data) {
        let channel = this.channels.get(name);
        if (!channel) {
            channel = new Channel(this, name);
            this.channels.set(name, channel);
        }

        const emote = new FFZEmote(channel, data.id, data);
        this.emotes.set(emote.code, emote);
        channel.emotes.set(emote.code, emote);
        return emote;
    }

    /**
     * Fetches and caches all twitch emotes.
     * @returns {Promise<Collection<string, TwitchEmote>>}
     */
    fetchTwitchEmotes() {
        return this._getRawTwitchEmotes().then(rawEmotes => {
            for (const key of Object.keys(rawEmotes)) {
                const data = rawEmotes[key];
                this._cacheTwitchEmote(key, data);
            }

            return this.emotes.filter(e => e.type === 'twitch');
        });
    }

    /**
     * Fetches the BTTV emotes for a channel.
     * @param {string} [name=null] - Name of the channel.
     * @returns {Promise<Collection<string, BTTVEmote>>}
     */
    fetchBTTVEmotes(name = null) {
        return this._getRawBTTVEmotes(name).then(rawEmotes => {
            for (const data of rawEmotes) {
                this._cacheBTTVEmote(name, data);
            }

            return this.channels.get(name).emotes.filter(e => e.type === 'bttv');
        });
    }

    /**
     * Fetches the FFZ emotes for a channel.
     * @param {string} name - Name of the channel.
     * @returns {Promise<Collection<string, FFZEmote>>}
     */
    fetchFFZEmotes(name) {
        return this._getRawFFZEmotes(name).then(rawEmotes => {
            for (const data of rawEmotes) {
                this._cacheFFZEmote(name, data);
            }

            return this.channels.get(name).emotes.filter(e => e.type === 'ffz');
        });
    }
}

module.exports = EmoteFetcher;
<commit_msg>Mark private methods<commit_after>const BTTVEmote = require('./BTTVEmote');
const Channel = require('./Channel');
const Collection = require('../util/Collection');
const Constants = require('../util/Constants');
const FFZEmote = require('./FFZEmote');
const request = require('snekfetch');
const TwitchEmote = require('./TwitchEmote');

/**
 * The fetcher that gets and caches emotes.
 * @class EmoteFetcher
 */
class EmoteFetcher {
    /**
     * Creates a new fetcher.
     * @param {Object} options - Options for the fetcher.
     */
    constructor(options = {}) {
        /**
         * Options for the fetcher.
         * @type {Object}
         */
        this.options = options;

        /**
         * Cached emotes.
         * Collectionped by emote code to Emote instance.
         * @type {Collection<string, Emote>}
         */
        this.emotes = new Collection();

        /**
         * Cached channels.
         * Collectionped by name to Channel instance.
         * @type {Collection<string, Channel>}
         */
        this.channels = new Collection();
    }

    /**
     * The global channel for both Twitch and BTTV.
     * @readonly
     * @type {?Channel}
     */
    get globalChannel() {
        return this.channels.get(null);
    }

    /**
     * Gets the raw twitch emotes data.
     * @private
     * @returns {Promise<Object>}
     */
    _getRawTwitchEmotes() {
        return request.get(Constants.Twitch.All).then(res => res.body.images);
    }

    /**
     * Converts and caches a raw twitch emote.
     * @private
     * @param {string} id - ID of the emote.
     * @param {Object} data - Raw data.
     * @returns {TwitchEmote}
     */
    _cacheTwitchEmote(id, data) {
        let channel = this.channels.get(data.channel);
        if (!channel) {
            channel = new Channel(this, data.channel);
            this.channels.set(data.channel, channel);
        }

        channel.title = data.channel_title;
        const emote = new TwitchEmote(channel, id, data);
        this.emotes.set(emote.code, emote);
        channel.emotes.set(emote.code, emote);
        return emote;
    }

    /**
     * Gets the raw BTTV emotes data for a channel.
     * @private
     * @param {string} name - Name of the channel.
     * @returns {Promise<Object[]>}
     */
    _getRawBTTVEmotes(name) {
        const endpoint = !name
        ? Constants.BTTV.Global
        : Constants.BTTV.Channel(name); // eslint-disable-line new-cap

        return request.get(endpoint).then(res => res.body.emotes);
    }

    /**
     * Converts and caches a raw BTTV emote.
     * @private
     * @param {string} name - Name of the channel.
     * @param {Object} data - Raw data.
     * @returns {BTTVEmote}
     */
    _cacheBTTVEmote(name, data) {
        let channel = this.channels.get(name);
        if (!channel) {
            channel = new Channel(this, name);
            this.channels.set(name, channel);
        }

        const emote = new BTTVEmote(channel, data.id, data);
        this.emotes.set(emote.code, emote);
        channel.emotes.set(emote.code, emote);
        return emote;
    }

    /**
     * Gets the raw FFZ emotes data for a channel.
     * @private
     * @param {string} name - Name of the channel.
     * @returns {Promise<Object[]>}
     */
    _getRawFFZEmotes(name) {
        return request.get(Constants.FFZ.Channel(name)).then(res => { // eslint-disable-line new-cap
            const emotes = [];
            for (const key of Object.keys(res.body.sets)) {
                const set = res.body.sets[key];
                emotes.push(...set.emoticons);
            }

            return emotes;
        });
    }

    /**
     * Converts and caches a raw FFZ emote.
     * @private
     * @param {string} name - Name of the channel.
     * @param {Object} data - Raw data.
     * @returns {FFZEmote}
     */
    _cacheFFZEmote(name, data) {
        let channel = this.channels.get(name);
        if (!channel) {
            channel = new Channel(this, name);
            this.channels.set(name, channel);
        }

        const emote = new FFZEmote(channel, data.id, data);
        this.emotes.set(emote.code, emote);
        channel.emotes.set(emote.code, emote);
        return emote;
    }

    /**
     * Fetches and caches all twitch emotes.
     * @returns {Promise<Collection<string, TwitchEmote>>}
     */
    fetchTwitchEmotes() {
        return this._getRawTwitchEmotes().then(rawEmotes => {
            for (const key of Object.keys(rawEmotes)) {
                const data = rawEmotes[key];
                this._cacheTwitchEmote(key, data);
            }

            return this.emotes.filter(e => e.type === 'twitch');
        });
    }

    /**
     * Fetches the BTTV emotes for a channel.
     * @param {string} [name=null] - Name of the channel.
     * @returns {Promise<Collection<string, BTTVEmote>>}
     */
    fetchBTTVEmotes(name = null) {
        return this._getRawBTTVEmotes(name).then(rawEmotes => {
            for (const data of rawEmotes) {
                this._cacheBTTVEmote(name, data);
            }

            return this.channels.get(name).emotes.filter(e => e.type === 'bttv');
        });
    }

    /**
     * Fetches the FFZ emotes for a channel.
     * @param {string} name - Name of the channel.
     * @returns {Promise<Collection<string, FFZEmote>>}
     */
    fetchFFZEmotes(name) {
        return this._getRawFFZEmotes(name).then(rawEmotes => {
            for (const data of rawEmotes) {
                this._cacheFFZEmote(name, data);
            }

            return this.channels.get(name).emotes.filter(e => e.type === 'ffz');
        });
    }
}

module.exports = EmoteFetcher;
<|endoftext|>


<commit_before>// eg-render/eg-body

import Ember from 'ember';
import layout from './template';

export default Ember.Component.extend({
  layout: layout,
  classNames: ['body'],

  sourceBodies: Ember.computed('columns.@each._zones.body', function(){
    return this.get('columns').map(
      function (col) { return Ember.get(col, '_zones.body'); });
  }),
  requiredPresent: Ember.computed(
    'attrs.data', 'attrs.columns', 'attrs.height', 'attrs.width',
    'attrs.contentWidth', 'attrs.rowHeight', function() {
      if (this.getAttr('data') == null) { return false; }
      if (this.getAttr('columns') == null) { return false; }
      if (this.getAttr('height') == null) { return false; }
      if (this.getAttr('width') == null) { return false; }
      if (this.getAttr('contentWidth') == null) { return false; }
      if (this.getAttr('rowHeight') == null) { return false; }
      return true;
    }),
  actions: {
    scrollSource: function (offset, limit) {
      var bodies = this.get('sourceBodies') || [];
      this.setProperties({offset, limit});
      Ember.run.debounce(function() {
        bodies.map( function(body){
          if (body != null) {
            Ember.set(body, 'offset', Math.max(offset - 60, 0));
            Ember.set(body, 'limit', limit + 60);
          }
        });
      }, 100);

    }
  }
});
<commit_msg>reduce buffer pre-load for faster initial load<commit_after>// eg-render/eg-body

import Ember from 'ember';
import layout from './template';

export default Ember.Component.extend({
  layout: layout,
  classNames: ['body'],

  sourceBodies: Ember.computed('columns.@each._zones.body', function(){
    return this.get('columns').map(
      function (col) { return Ember.get(col, '_zones.body'); });
  }),
  requiredPresent: Ember.computed(
    'attrs.data', 'attrs.columns', 'attrs.height', 'attrs.width',
    'attrs.contentWidth', 'attrs.rowHeight', function() {
      if (this.getAttr('data') == null) { return false; }
      if (this.getAttr('columns') == null) { return false; }
      if (this.getAttr('height') == null) { return false; }
      if (this.getAttr('width') == null) { return false; }
      if (this.getAttr('contentWidth') == null) { return false; }
      if (this.getAttr('rowHeight') == null) { return false; }
      return true;
    }),
  actions: {
    scrollSource: function (offset, limit) {
      var bodies = this.get('sourceBodies') || [];
      this.setProperties({offset, limit});
      Ember.run.debounce(function() {
        bodies.map( function(body){
          if (body != null) {
            Ember.set(body, 'offset', Math.max(offset - 30, 0));
            Ember.set(body, 'limit', limit + 30);
          }
        });
      }, 100);

    }
  }
});
<|endoftext|>


<commit_before>import Ember from 'ember';

/**
  Creates a a property that returns an observable which is created
  from observed dependent property changes on the Ember object,
  then mapped to an output observable with the mapping function
  @method computedObservable
  @param mapFn {Function} the mapping function that receives an observable of dependent properties when changes occur
  @param deps {Array|Arguments} an array, or a series of arguments that are the string names of
    dependent properties to include in the observed property changes passed to the mapping function.
  @return {Ember.ComputedProperty}
*/
export function computedObservable(mapFn, deps) {
  if(arguments.length > 1) {
    deps = !Array.isArray(deps) ? [].slice.call(arguments, 1) : deps;
  } else {
    deps = [];
  }

  return function(key, value) {
    var backingField = '_' + key;
    if(!this[backingField]) {
      var depProps = deps.map(function(k) {
        var arrayIndex = k.indexOf('.[]');
        return arrayIndex !== -1 ? k.substring(0, arrayIndex) : k;
      });

      this[backingField] = new Rx.BehaviorSubject(this.getProperties.apply(this, depProps));

      var handler = function(){
        var props = this.getProperties.apply(this, depProps);
        this[backingField].onNext(props);
      };

      deps.forEach(function(depKey) {
        this.addObserver(depKey, this, function(){
          Ember.run.once(this, handler);
        });
      }, this);
    }

    return mapFn(this[backingField].asObservable());
  }.property();
}<commit_msg>computed observable tests readded<commit_after>import Ember from 'ember';

/**
  Creates a a property that returns an observable which is created
  from observed dependent property changes on the Ember object,
  then mapped to an output observable with the mapping function
  @method computedObservable
  @param mapFn {Function} the mapping function that receives an observable of dependent properties when changes occur
  @param deps {Array|Arguments} an array, or a series of arguments that are the string names of
    dependent properties to include in the observed property changes passed to the mapping function.
  @return {Ember.ComputedProperty}
*/
export default function computedObservable(mapFn, deps) {
  if(arguments.length > 1) {
    deps = !Array.isArray(deps) ? [].slice.call(arguments, 1) : deps;
  } else {
    deps = [];
  }

  return function(key, value) {
    var backingField = '_' + key;
    if(!this[backingField]) {
      var depProps = deps.map(function(k) {
        var arrayIndex = k.indexOf('.[]');
        return arrayIndex !== -1 ? k.substring(0, arrayIndex) : k;
      });

      this[backingField] = new Rx.BehaviorSubject(this.getProperties.apply(this, depProps));

      var handler = function(){
        var props = this.getProperties.apply(this, depProps);
        this[backingField].onNext(props);
      };

      deps.forEach(function(depKey) {
        this.addObserver(depKey, this, function(){
          Ember.run.once(this, handler);
        });
      }, this);
    }

    return mapFn(this[backingField].asObservable());
  }.property();
}<|endoftext|>


<commit_before>/**
  @module ember-flexberry-designer
*/

import Ember from 'ember';

import FdEditformRow from '../objects/fd-editform-row';
import FdEditformControl from '../objects/fd-editform-control';

/**
  Mixin with the support `drag and drop` for controls in the edit form constructor.

  @class FdDraggableControlMixin
  @uses Ember.Mixin
*/
export default Ember.Mixin.create({
  /**
    The `X` coordinate with which the dragging started.

    @private
    @property _initialX
    @type Number
  */
  _initialX: undefined,

  /**
    The `Y` coordinate with which the dragging started.

    @private
    @property _initialY
    @type Number
  */
  _initialY: undefined,

  /**
    In dependent on `dragDirection` returns `_initialX` or `_initialY`.

    @private
    @property _initialXY
    @readOnly
    @type Number
  */
  _initialXY: Ember.computed('_initialX', '_initialY', 'dragDirection', function() {
    return this.get(`_initial${this.get('dragDirection')}`);
  }).readOnly(),

  /**
    The 'X' or 'Y' coordinate of the line through which you need to drag the item to move.

    @private
    @property _centerXY
    @readOnly
    @type Number
  */
  _centerXY: Ember.computed('dragDirection', function() {
    let clientRect = this.get('element').getBoundingClientRect();
    switch (this.get('dragDirection')) {
      case 'X':
        let width = clientRect.right - clientRect.left;
        return clientRect.left + width / 2;

      case 'Y':
        let height = clientRect.bottom - clientRect.top;
        return clientRect.top + height / 2;

      default:
        throw new Error(`The drag direction must be 'X' or 'Y'.`);
    }
  }).readOnly(),

  /**
    Returns the item to be dragged.

    @private
    @property _draggableProperty
    @readOnly
    @type FdEditformRow|FdEditformControl|FdEditformGroup|FdEditformTabgroup|FdEditformTab
  */
  _draggableProperty: Ember.computed('draggableProperty', function() {
    return this.get(this.get('draggableProperty'));
  }).readOnly(),

  /**
    Specifies the property name in the component, in which draggable item is stored.

    @property draggableProperty
    @type String
  */
  draggableProperty: undefined,

  /**
    Specifies the direction that will be used to sort the draggable items.
    Available values: `X` or `Y`.

    @property dragDirection
    @type String
  */
  dragDirection: undefined,

  /**
    The property to bind to `draggable` HTML attribute.

    @property draggable
    @type Boolean
    @default false
  */
  draggable: false,

  /**
    See [EmberJS API](https://emberjs.com/api/).

    @property attributeBindings
  */
  attributeBindings: ['draggable'],

  /**
    The event handler is `dragStart`.

    @method dragStart
    @param {JQuery.Event} event
  */
  dragStart(event) {
    event.stopPropagation();
    this.get('setDragItemAction')(this.get('_draggableProperty'));
  },

  /**
    The event handler is `dragEnd`.

    @method dragEnd
    @param {JQuery.Event} event
  */
  dragEnd(event) {
    event.stopPropagation();
    this.get('setDragItemAction')(null);
  },

  /**
    The event handler is `dragEnter`.

    @method dragEnter
    @param {JQuery.Event} event
  */
  dragEnter(event) {
    let draggedItem = this.get('getDragItemAction')();
    let draggableItem = this.get('_draggableProperty');
    if (draggedItem === draggableItem) {
      event.stopPropagation();
    } else if (this._draggingIsSupported(draggableItem, draggedItem)) {
      this.set('_initialX', event.originalEvent.clientX);
      this.set('_initialY', event.originalEvent.clientY);

      event.stopPropagation();
      return false;
    }
  },

  /**
    The event handler is `dragOver`.

    @method dragOver
    @param {JQuery.Event} event
  */
  dragOver(event) {
    if (!this.get('_initialXY')) {
      let draggedItem = this.get('getDragItemAction')();
      let draggableItem = this.get('_draggableProperty');
      if (draggedItem === draggableItem) {
        event.stopPropagation();
      } else if (this._draggingIsSupported(draggableItem, draggedItem)) {
        this.set('_initialX', event.originalEvent.clientX);
        this.set('_initialY', event.originalEvent.clientY);

        event.stopPropagation();
        return false;
      }
    } else {
      let clientXY = Ember.get(event.originalEvent, `client${this.get('dragDirection')}`);
      let centerXY = this.get('_centerXY');
      let motion = this.get('_initialXY') - clientXY;

      let direction;
      if (motion >= 5 && clientXY < centerXY) {
        direction = 'up';
      } else if (motion <= -5 && clientXY > centerXY) {
        direction = 'down';
      }

      if (direction) {
        this.get('moveDragItemAction')(this.get('_draggableProperty'), direction);
        this.notifyPropertyChange('_centerXY');
        this.set('_initialX', undefined);
        this.set('_initialY', undefined);
      }

      return false;
    }
  },

  /**
    @private
    @method _draggingIsSupported
    @param {FdEditformRow|FdEditformControl} over Item over which is dragged.
    @param {FdEditformRow|FdEditformControl} dragged Dragged item.
    @return {Boolean} If dragging makes sense `true`, else `false`.
  */
  _draggingIsSupported(over, dragged) {
    let result = over instanceof FdEditformRow && dragged instanceof FdEditformRow;
    result = result || over instanceof FdEditformControl && dragged instanceof FdEditformControl;
    return result;
  },
});
<commit_msg>Feature fd-draggable-control mixin extend supported dragging<commit_after>/**
  @module ember-flexberry-designer
*/

import Ember from 'ember';

import FdEditformRow from '../objects/fd-editform-row';
import FdEditformControl from '../objects/fd-editform-control';
import FdEditformGroup from '../objects/fd-editform-group';
import FdEditformTabgroup from '../objects/fd-editform-tabgroup';

/**
  Mixin with the support `drag and drop` for controls in the edit form constructor.

  @class FdDraggableControlMixin
  @uses Ember.Mixin
*/
export default Ember.Mixin.create({
  /**
    The `X` coordinate with which the dragging started.

    @private
    @property _initialX
    @type Number
  */
  _initialX: undefined,

  /**
    The `Y` coordinate with which the dragging started.

    @private
    @property _initialY
    @type Number
  */
  _initialY: undefined,

  /**
    In dependent on `dragDirection` returns `_initialX` or `_initialY`.

    @private
    @property _initialXY
    @readOnly
    @type Number
  */
  _initialXY: Ember.computed('_initialX', '_initialY', 'dragDirection', function() {
    return this.get(`_initial${this.get('dragDirection')}`);
  }).readOnly(),

  /**
    The 'X' or 'Y' coordinate of the line through which you need to drag the item to move.

    @private
    @property _centerXY
    @readOnly
    @type Number
  */
  _centerXY: Ember.computed('dragDirection', function() {
    let clientRect = this.get('element').getBoundingClientRect();
    switch (this.get('dragDirection')) {
      case 'X':
        let width = clientRect.right - clientRect.left;
        return clientRect.left + width / 2;

      case 'Y':
        let height = clientRect.bottom - clientRect.top;
        return clientRect.top + height / 2;

      default:
        throw new Error(`The drag direction must be 'X' or 'Y'.`);
    }
  }).readOnly(),

  /**
    Returns the item to be dragged.

    @private
    @property _draggableProperty
    @readOnly
    @type FdEditformRow|FdEditformControl|FdEditformGroup|FdEditformTabgroup|FdEditformTab
  */
  _draggableProperty: Ember.computed('draggableProperty', function() {
    return this.get(this.get('draggableProperty'));
  }).readOnly(),

  /**
    Specifies the property name in the component, in which draggable item is stored.

    @property draggableProperty
    @type String
  */
  draggableProperty: undefined,

  /**
    Specifies the direction that will be used to sort the draggable items.
    Available values: `X` or `Y`.

    @property dragDirection
    @type String
  */
  dragDirection: undefined,

  /**
    The property to bind to `draggable` HTML attribute.

    @property draggable
    @type Boolean
    @default false
  */
  draggable: false,

  /**
    See [EmberJS API](https://emberjs.com/api/).

    @property attributeBindings
  */
  attributeBindings: ['draggable'],

  /**
    The event handler is `dragStart`.

    @method dragStart
    @param {JQuery.Event} event
  */
  dragStart(event) {
    event.stopPropagation();
    this.get('setDragItemAction')(this.get('_draggableProperty'));
  },

  /**
    The event handler is `dragEnd`.

    @method dragEnd
    @param {JQuery.Event} event
  */
  dragEnd(event) {
    event.stopPropagation();
    this.get('setDragItemAction')(null);
  },

  /**
    The event handler is `dragEnter`.

    @method dragEnter
    @param {JQuery.Event} event
  */
  dragEnter(event) {
    let draggedItem = this.get('getDragItemAction')();
    let draggableItem = this.get('_draggableProperty');
    if (draggedItem === draggableItem) {
      event.stopPropagation();
    } else if (this._draggingIsSupported(draggableItem, draggedItem)) {
      this.set('_initialX', event.originalEvent.clientX);
      this.set('_initialY', event.originalEvent.clientY);

      event.stopPropagation();
      return false;
    }
  },

  /**
    The event handler is `dragOver`.

    @method dragOver
    @param {JQuery.Event} event
  */
  dragOver(event) {
    if (!this.get('_initialXY')) {
      let draggedItem = this.get('getDragItemAction')();
      let draggableItem = this.get('_draggableProperty');
      if (draggedItem === draggableItem) {
        event.stopPropagation();
      } else if (this._draggingIsSupported(draggableItem, draggedItem)) {
        this.set('_initialX', event.originalEvent.clientX);
        this.set('_initialY', event.originalEvent.clientY);

        event.stopPropagation();
        return false;
      }
    } else {
      let clientXY = Ember.get(event.originalEvent, `client${this.get('dragDirection')}`);
      let centerXY = this.get('_centerXY');
      let motion = this.get('_initialXY') - clientXY;

      let direction;
      if (motion >= 5 && clientXY < centerXY) {
        direction = 'up';
      } else if (motion <= -5 && clientXY > centerXY) {
        direction = 'down';
      }

      if (direction) {
        this.get('moveDragItemAction')(this.get('_draggableProperty'), direction);
        this.notifyPropertyChange('_centerXY');
        this.set('_initialX', undefined);
        this.set('_initialY', undefined);
      }

      return false;
    }
  },

  /**
    @private
    @method _draggingIsSupported
    @param {FdEditformRow|FdEditformControl} over Item over which is dragged.
    @param {FdEditformRow|FdEditformControl} dragged Dragged item.
    @return {Boolean} If dragging makes sense `true`, else `false`.
  */
  _draggingIsSupported(over, dragged) {
    let result = over instanceof FdEditformRow && dragged instanceof FdEditformRow;

    result = result || over instanceof FdEditformControl && dragged instanceof FdEditformControl;
    result = result || over instanceof FdEditformControl && dragged instanceof FdEditformGroup;
    result = result || over instanceof FdEditformControl && dragged instanceof FdEditformTabgroup;

    result = result || over instanceof FdEditformGroup && dragged instanceof FdEditformGroup;
    result = result || over instanceof FdEditformGroup && dragged instanceof FdEditformControl;
    result = result || over instanceof FdEditformGroup && dragged instanceof FdEditformTabgroup;

    result = result || over instanceof FdEditformTabgroup && dragged instanceof FdEditformTabgroup;
    result = result || over instanceof FdEditformTabgroup && dragged instanceof FdEditformControl;
    result = result || over instanceof FdEditformTabgroup && dragged instanceof FdEditformGroup;

    return result;
  },
});
<|endoftext|>


<commit_before>import Utils from '../utils/utils';
import Geo from '../geo';

import parseCSSColor from 'csscolorparser';
import log from 'loglevel';

export var StyleParser = {};

// Style macros

StyleParser.expandMacros = function expandMacros (obj) {
    for (var p in obj) {
        var val = obj[p];

        // Loop through object properties
        if (typeof val === 'object') {
            obj[p] = expandMacros(val);
        }
        // Convert strings back into functions
        else if (typeof val === 'string') {
            for (var m in StyleParser.macros) {
                if (val.match(StyleParser.macros[m])) {
                    var f;
                    try {
                        /*jshint ignore:start */
                        eval('f = ' + val);
                        /*jshint ignore:end */
                        obj[p] = f;
                        log.trace(`expanded macro ${val} to ${f}`);
                        break;
                    }
                    catch (e) {
                        // fall-back to original value if parsing failed
                        obj[p] = val;
                        log.trace(`failed to expand macro ${val}`);
                    }
                }
            }
        }
    }

    return obj;
};

// List of macros
StyleParser.macros = [
    'Style.color.pseudoRandomColor',
    'Style.color.randomColor'
];

var Style = {};

Style.color = {
    // pseudo-random grayscale by geometry id
    pseudoRandomGrayscale() {
        var func = `function() {
            var c = Math.max((parseInt(feature.id, 16) % 100) / 100, 0.4);
            return [0.7 * c, 0.7 * c, 0.7 * c];
        }`;
        return func;
    },

    // pseudo-random color by geometry id
    pseudoRandomColor() {
        var func = `function() {
            return [
                0.7 * (parseInt(feature.id, 16) / 100 % 1),
                0.7 * (parseInt(feature.id, 16) / 10000 % 1),
                0.7 * (parseInt(feature.id, 16) / 1000000 % 1)
            ];
        }`;
        return func;
        // return `function() { return [0.7 * (parseInt(feature.id, 16) / 100 % 1), 0.7 * (parseInt(feature.id, 16) / 10000 % 1), 0.7 * (parseInt(feature.id, 16) / 1000000 % 1)]; }`;
    },

    // random color
    randomColor() {
        var func = `function() {
            return [0.7 * Math.random(), 0.7 * Math.random(), 0.7 * Math.random()];
        }`;
        return func;
    }
};

// Wraps style functions and provides a scope of commonly accessible data:
// - feature: the 'properties' of the feature, e.g. accessed as 'feature.name'
// - $zoom: the current map zoom level
// - $geometry: the type of geometry, 'point', 'line', or 'polygon'
// - $meters_per_pixel: conversion for meters/pixels at current map zoom
// - properties: user-defined properties on the style-rule object in the stylesheet
StyleParser.wrapFunction = function (func) {
    var f = `function(context) {
                var feature = context.feature.properties;
                var $zoom = context.zoom;
                var $geometry = context.geometry;
                var $meters_per_pixel = context.meters_per_pixel;
                var properties = context.properties;
                return (${func}());
            }`;
    return f;
};

// Style parsing

// Style defaults
StyleParser.defaults = {
    color: [1, 1, 1, 1],
    width: 1,
    size: 1,
    extrude: false,
    height: 20,
    min_height: 0,
    order: 0,
    z: 0,
    material: {
        ambient: 1,
        diffuse: 1
    }
};

// A context object that is passed to style parsing functions to provide a scope of commonly used values
StyleParser.getFeatureParseContext = function (feature, tile) {
    return {
        feature,
        tile,
        zoom: tile.style_zoom,
        geometry: Geo.geometryType(feature.geometry.type),
        meters_per_pixel: Geo.metersPerPixel(tile.coords.z),
        units_per_meter: Geo.units_per_meter[tile.coords.z]
    };
};

StyleParser.convertUnits = function(val, context, convert = 'meters') {
    if (typeof val === 'string') {
        var units = val.match(/([0-9.-]+)([a-z]+)/);
        if (units && units.length === 3) {
            val = parseFloat(units[1]);
            units = units[2];
        }

        // Convert to meters
        if (convert === 'meters') {
            // Convert from pixels
            if (units === 'px') {
                val *= Geo.metersPerPixel(context.zoom);
            }
            // Convert from kilometers
            else if (units === 'km') {
                val *= 1000;
            }
        }
    }
    else if (Array.isArray(val)) {
        // Array of arrays, e.g. zoom-interpolated stops
        if (Array.isArray(val[0])) {
            return val.map(v => { return [v[0], StyleParser.convertUnits(v[1], context, convert)]; });
        }
        // Array of values
        else {
            return val.map(v => { return StyleParser.convertUnits(v, context, convert); });
        }
    }
    return val;
};

// Takes a distance cache object and returns a distance value for this zoom
// (caching the result for future use)
// { value: original, zoom: { z: meters }, dynamic: function(){...} }
StyleParser.cacheDistance = function(val, context, convert = 'meters') {
    if (val.dynamic) {
        let v = val.dynamic(context);
        return v;
    }
    else if (val.zoom && val.zoom[convert] && val.zoom[convert][context.zoom]) {
        return val.zoom[convert][context.zoom];
    }
    else {
        // Dynamic function-based
        if (typeof val.value === 'function') {
            val.dynamic = val.value;
            let v = val.dynamic(context);
            return v;
        }
        // Array of zoom-interpolated stops, e.g. [zoom, value] pairs
        else {
            // Calculate value for current zoom
            val.zoom = val.zoom || {};
            let zunits = val.zoom[convert] = val.zoom[convert] || {};

            zunits[context.zoom] = StyleParser.convertUnits(val.value, context,
                convert === 'meters' && 'meters'); // convert to meters
            zunits[context.zoom] = Utils.interpolate(context.zoom, zunits[context.zoom]);

            return zunits[context.zoom];
        }
    }
};

// Cache previously parsed color strings
StyleParser.string_colors = {};
StyleParser.colorForString = function(string) {
    // Cached
    if (StyleParser.string_colors[string]) {
        return StyleParser.string_colors[string];
    }

    // Calculate and cache
    let color = parseCSSColor.parseCSSColor(string);
    if (color && color.length === 4) {
        color[0] /= 255;
        color[1] /= 255;
        color[2] /= 255;
        color[3] = 1;
    }
    else {
        color = StyleParser.defaults.color;
    }
    StyleParser.string_colors[string] = color;
    return color;
};

// Takes a color cache object and returns a color value for this zoom
// (caching the result for future use)
// { value: original, static: [r,g,b,a], zoom: { z: [r,g,b,a] }, dynamic: function(){...} }
StyleParser.cacheColor = function(val, context = {}) {
    if (val.dynamic) {
        let v = val.dynamic(context);
        v[3] = v[3] || 1; // default alpha
        return v;
    }
    else if (val.static) {
        return val.static;
    }
    else if (val.zoom && val.zoom[context.zoom]) {
        return val.zoom[context.zoom];
    }
    else {
        // Dynamic function-based color
        if (typeof val.value === 'function') {
            val.dynamic = val.value;
            let v = val.dynamic(context);
            v[3] = v[3] || 1; // default alpha
            return v;
        }
        // Single string color
        else if (typeof val.value === 'string') {
            val.static = StyleParser.colorForString(val.value);
            return val.static;
        }
        // Array of zoom-interpolated stops, e.g. [zoom, color] pairs
        else if (Array.isArray(val.value) && Array.isArray(val.value[0])) {
            if (!val.zoom) {
                val.zoom = {};
                // Parse any string colors inside stops
                for (let i=0; i < val.value.length; i++) {
                    let v = val.value[i];
                    if (typeof v[1] === 'string') {
                        v[1] = StyleParser.colorForString(v[1]);
                    }
                }
            }

            // Calculate color for current zoom
            val.zoom[context.zoom] = Utils.interpolate(context.zoom, val.value);
            val.zoom[context.zoom][3] = val.zoom[context.zoom][3] || 1; // default alpha
            return val.zoom[context.zoom];
        }
        // Single array color
        else {
            val.static = val.value;
            val.static[3] = val.static[3] || 1; // default alpha
            return val.static;
        }
    }
};

StyleParser.parseColor = function(val, context = {}) {
    if (typeof val === 'function') {
        val = val(context);
    }

    // Parse CSS-style colors
    // TODO: change all colors to use 0-255 range internally to avoid dividing and then re-multiplying in geom builder
    if (typeof val === 'string') {
        val = parseCSSColor.parseCSSColor(val);
        if (val && val.length === 4) {
            val[0] /= 255;
            val[1] /= 255;
            val[2] /= 255;
        }
        else {
            val = null;
        }
    }
    else if (Array.isArray(val) && Array.isArray(val[0])) {
        // Array of zoom-interpolated stops, e.g. [zoom, color] pairs
        for (let i=0; i < val.length; i++) {
            let v = val[i];
            if (typeof v[1] === 'string') {
                var vc = parseCSSColor.parseCSSColor(v[1]);
                if (vc && vc.length === 4) {
                    vc[0] /= 255;
                    vc[1] /= 255;
                    vc[2] /= 255;
                    v[1] = vc;
                }
            }
        }
    }

    if (context.zoom) {
        val = Utils.interpolate(context.zoom, val);
    }

    // Defaults
    if (val) {
        // alpha
        if (!val[3]) {
            val[3] = 1;
        }
    }
    else {
        val = [0, 0, 0, 1];
    }

    return val;
};

// Order is summed from top to bottom in the style hierarchy:
// each child order value is added to the parent order value
StyleParser.calculateOrder = function(order, context) {
    if (typeof order === 'function') {
        order = order(context);
    }
    else if (Array.isArray(order)) {
        order = order.reduce((sum, order) => {
            order = order || StyleParser.defaults.order;
            if (typeof order === 'function') {
                order = order(context);
            }
            else if (typeof order === 'string') {
                order = context.feature.properties[order];
            }
            else {
                order = parseFloat(order);
            }

            if (!order || isNaN(order)) {
                return sum;
            }
            return sum + order;
        }, 0);
    }
    else if (typeof order === 'string') {
        if (context.feature.properties[order]) {
            order = context.feature.properties[order];
        }
        else {
            order = parseFloat(order);
        }
    }

    return order;
};
<commit_msg>fix alpha default in color parsing<commit_after>import Utils from '../utils/utils';
import Geo from '../geo';

import parseCSSColor from 'csscolorparser';
import log from 'loglevel';

export var StyleParser = {};

// Style macros

StyleParser.expandMacros = function expandMacros (obj) {
    for (var p in obj) {
        var val = obj[p];

        // Loop through object properties
        if (typeof val === 'object') {
            obj[p] = expandMacros(val);
        }
        // Convert strings back into functions
        else if (typeof val === 'string') {
            for (var m in StyleParser.macros) {
                if (val.match(StyleParser.macros[m])) {
                    var f;
                    try {
                        /*jshint ignore:start */
                        eval('f = ' + val);
                        /*jshint ignore:end */
                        obj[p] = f;
                        log.trace(`expanded macro ${val} to ${f}`);
                        break;
                    }
                    catch (e) {
                        // fall-back to original value if parsing failed
                        obj[p] = val;
                        log.trace(`failed to expand macro ${val}`);
                    }
                }
            }
        }
    }

    return obj;
};

// List of macros
StyleParser.macros = [
    'Style.color.pseudoRandomColor',
    'Style.color.randomColor'
];

var Style = {};

Style.color = {
    // pseudo-random grayscale by geometry id
    pseudoRandomGrayscale() {
        var func = `function() {
            var c = Math.max((parseInt(feature.id, 16) % 100) / 100, 0.4);
            return [0.7 * c, 0.7 * c, 0.7 * c];
        }`;
        return func;
    },

    // pseudo-random color by geometry id
    pseudoRandomColor() {
        var func = `function() {
            return [
                0.7 * (parseInt(feature.id, 16) / 100 % 1),
                0.7 * (parseInt(feature.id, 16) / 10000 % 1),
                0.7 * (parseInt(feature.id, 16) / 1000000 % 1)
            ];
        }`;
        return func;
        // return `function() { return [0.7 * (parseInt(feature.id, 16) / 100 % 1), 0.7 * (parseInt(feature.id, 16) / 10000 % 1), 0.7 * (parseInt(feature.id, 16) / 1000000 % 1)]; }`;
    },

    // random color
    randomColor() {
        var func = `function() {
            return [0.7 * Math.random(), 0.7 * Math.random(), 0.7 * Math.random()];
        }`;
        return func;
    }
};

// Wraps style functions and provides a scope of commonly accessible data:
// - feature: the 'properties' of the feature, e.g. accessed as 'feature.name'
// - $zoom: the current map zoom level
// - $geometry: the type of geometry, 'point', 'line', or 'polygon'
// - $meters_per_pixel: conversion for meters/pixels at current map zoom
// - properties: user-defined properties on the style-rule object in the stylesheet
StyleParser.wrapFunction = function (func) {
    var f = `function(context) {
                var feature = context.feature.properties;
                var $zoom = context.zoom;
                var $geometry = context.geometry;
                var $meters_per_pixel = context.meters_per_pixel;
                var properties = context.properties;
                return (${func}());
            }`;
    return f;
};

// Style parsing

// Style defaults
StyleParser.defaults = {
    color: [1, 1, 1, 1],
    width: 1,
    size: 1,
    extrude: false,
    height: 20,
    min_height: 0,
    order: 0,
    z: 0,
    material: {
        ambient: 1,
        diffuse: 1
    }
};

// A context object that is passed to style parsing functions to provide a scope of commonly used values
StyleParser.getFeatureParseContext = function (feature, tile) {
    return {
        feature,
        tile,
        zoom: tile.style_zoom,
        geometry: Geo.geometryType(feature.geometry.type),
        meters_per_pixel: Geo.metersPerPixel(tile.coords.z),
        units_per_meter: Geo.units_per_meter[tile.coords.z]
    };
};

StyleParser.convertUnits = function(val, context, convert = 'meters') {
    if (typeof val === 'string') {
        var units = val.match(/([0-9.-]+)([a-z]+)/);
        if (units && units.length === 3) {
            val = parseFloat(units[1]);
            units = units[2];
        }

        // Convert to meters
        if (convert === 'meters') {
            // Convert from pixels
            if (units === 'px') {
                val *= Geo.metersPerPixel(context.zoom);
            }
            // Convert from kilometers
            else if (units === 'km') {
                val *= 1000;
            }
        }
    }
    else if (Array.isArray(val)) {
        // Array of arrays, e.g. zoom-interpolated stops
        if (Array.isArray(val[0])) {
            return val.map(v => { return [v[0], StyleParser.convertUnits(v[1], context, convert)]; });
        }
        // Array of values
        else {
            return val.map(v => { return StyleParser.convertUnits(v, context, convert); });
        }
    }
    return val;
};

// Takes a distance cache object and returns a distance value for this zoom
// (caching the result for future use)
// { value: original, zoom: { z: meters }, dynamic: function(){...} }
StyleParser.cacheDistance = function(val, context, convert = 'meters') {
    if (val.dynamic) {
        let v = val.dynamic(context);
        return v;
    }
    else if (val.zoom && val.zoom[convert] && val.zoom[convert][context.zoom]) {
        return val.zoom[convert][context.zoom];
    }
    else {
        // Dynamic function-based
        if (typeof val.value === 'function') {
            val.dynamic = val.value;
            let v = val.dynamic(context);
            return v;
        }
        // Array of zoom-interpolated stops, e.g. [zoom, value] pairs
        else {
            // Calculate value for current zoom
            val.zoom = val.zoom || {};
            let zunits = val.zoom[convert] = val.zoom[convert] || {};

            zunits[context.zoom] = StyleParser.convertUnits(val.value, context,
                convert === 'meters' && 'meters'); // convert to meters
            zunits[context.zoom] = Utils.interpolate(context.zoom, zunits[context.zoom]);

            return zunits[context.zoom];
        }
    }
};

// Cache previously parsed color strings
StyleParser.string_colors = {};
StyleParser.colorForString = function(string) {
    // Cached
    if (StyleParser.string_colors[string]) {
        return StyleParser.string_colors[string];
    }

    // Calculate and cache
    let color = parseCSSColor.parseCSSColor(string);
    if (color && color.length === 4) {
        color[0] /= 255;
        color[1] /= 255;
        color[2] /= 255;
        color[3] = 1;
    }
    else {
        color = StyleParser.defaults.color;
    }
    StyleParser.string_colors[string] = color;
    return color;
};

// Takes a color cache object and returns a color value for this zoom
// (caching the result for future use)
// { value: original, static: [r,g,b,a], zoom: { z: [r,g,b,a] }, dynamic: function(){...} }
StyleParser.cacheColor = function(val, context = {}) {
    if (val.dynamic) {
        let v = val.dynamic(context);
        if (v[3] == null) {
            v[3] = 1; // default alpha
        }
        return v;
    }
    else if (val.static) {
        return val.static;
    }
    else if (val.zoom && val.zoom[context.zoom]) {
        return val.zoom[context.zoom];
    }
    else {
        // Dynamic function-based color
        if (typeof val.value === 'function') {
            val.dynamic = val.value;
            let v = val.dynamic(context);
            if (v[3] == null) {
                v[3] = 1; // default alpha
            }
            return v;
        }
        // Single string color
        else if (typeof val.value === 'string') {
            val.static = StyleParser.colorForString(val.value);
            return val.static;
        }
        // Array of zoom-interpolated stops, e.g. [zoom, color] pairs
        else if (Array.isArray(val.value) && Array.isArray(val.value[0])) {
            if (!val.zoom) {
                val.zoom = {};
                // Parse any string colors inside stops
                for (let i=0; i < val.value.length; i++) {
                    let v = val.value[i];
                    if (typeof v[1] === 'string') {
                        v[1] = StyleParser.colorForString(v[1]);
                    }
                }
            }

            // Calculate color for current zoom
            val.zoom[context.zoom] = Utils.interpolate(context.zoom, val.value);
            val.zoom[context.zoom][3] = val.zoom[context.zoom][3] || 1; // default alpha
            return val.zoom[context.zoom];
        }
        // Single array color
        else {
            val.static = val.value;
            if (val.static[3] == null) {
                val.static[3] = 1; // default alpha
            }
            return val.static;
        }
    }
};

StyleParser.parseColor = function(val, context = {}) {
    if (typeof val === 'function') {
        val = val(context);
    }

    // Parse CSS-style colors
    // TODO: change all colors to use 0-255 range internally to avoid dividing and then re-multiplying in geom builder
    if (typeof val === 'string') {
        val = parseCSSColor.parseCSSColor(val);
        if (val && val.length === 4) {
            val[0] /= 255;
            val[1] /= 255;
            val[2] /= 255;
        }
        else {
            val = null;
        }
    }
    else if (Array.isArray(val) && Array.isArray(val[0])) {
        // Array of zoom-interpolated stops, e.g. [zoom, color] pairs
        for (let i=0; i < val.length; i++) {
            let v = val[i];
            if (typeof v[1] === 'string') {
                var vc = parseCSSColor.parseCSSColor(v[1]);
                if (vc && vc.length === 4) {
                    vc[0] /= 255;
                    vc[1] /= 255;
                    vc[2] /= 255;
                    v[1] = vc;
                }
            }
        }
    }

    if (context.zoom) {
        val = Utils.interpolate(context.zoom, val);
    }

    // Defaults
    if (val) {
        // alpha
        if (val[3] == null) {
            val[3] = 1;
        }
    }
    else {
        val = [0, 0, 0, 1];
    }

    return val;
};

// Order is summed from top to bottom in the style hierarchy:
// each child order value is added to the parent order value
StyleParser.calculateOrder = function(order, context) {
    if (typeof order === 'function') {
        order = order(context);
    }
    else if (Array.isArray(order)) {
        order = order.reduce((sum, order) => {
            order = order || StyleParser.defaults.order;
            if (typeof order === 'function') {
                order = order(context);
            }
            else if (typeof order === 'string') {
                order = context.feature.properties[order];
            }
            else {
                order = parseFloat(order);
            }

            if (!order || isNaN(order)) {
                return sum;
            }
            return sum + order;
        }, 0);
    }
    else if (typeof order === 'string') {
        if (context.feature.properties[order]) {
            order = context.feature.properties[order];
        }
        else {
            order = parseFloat(order);
        }
    }

    return order;
};
<|endoftext|>


<commit_before>/**
 * @module myModule
 * @summary: This module's purpose is to:
 *
 * @description:
 *
 * Author: Justin Mooser
 * Created On: 2015-06-01.
 * @license Apache-2.0
 */

"use strict";

var util = require('util');

/**
 *
 * @param config
 * @param storage .save(): promise rejects if it should not be retried.
 * @param longTermStorage - .save(): resolves url to saved resource.  Rejects if it should not be retried.
 * @returns {{}}
 */
module.exports = function construct(config, storage, longTermStorage) {
  var m = {};
  config = config ? config : {};
  config = _.defaults(config, {
    saveEventsToLongTermStorage: true
  });

  m.processEvent = function(eventPayload, eventType) {
    var eventRow;
    return p.resolve().then(function() {
      return extractEventRow(eventPayload,eventType);
    })
    .then(function(eventrow) {
      eventRow = eventrow;
      console.log('Saving to S3...', eventRow.timestamp);
      return saveToLongTermStorage(eventRow, eventPayload)
        .then(function(result) {
          console.log('Saving to S3...', eventRow.timestamp);
          return result;
        })
        .catch(function(err) {
          console.error('Failed to save to S3.', err);
        });
    })
    .then(function(longTermStorageUrl) {
      console.log('Saved to', longTermStorageUrl);
        // the database only allows 100 characters.
      if (eventRow.details.length && eventRow.details.length > 100) {
        eventRow.details = eventRow.details.substr(0, 99);
      }
      if(eventRow.label.length > 24) {
        eventRow.label = eventRow.label.substr(0,24);
      }
      eventRow.url = longTermStorageUrl;
      eventRow.timestamp = Math.floor(new Date().getTime()/1000);
      return storage.save('eventlog', eventRow)
        .catch(function(err) {
          console.error('Error saving tracked event to storage.');
          console.error(err);
        });
    })
    .catch(function(err) {
      console.error('Unexpected error processing tracked event.');
      console.error(err);
    });
  };

  function extractEventRow(eventPayload, eventType) {
    eventType = eventType || 'bunyan-v1';
    // you have to extract the payload and the essential details about the logged event.
    if (eventType=='bunyan-v1') {
      var details = extractDetailsObject(eventPayload);

      var eventRow = {
        details: JSON.stringify(details) || '{}',
        local_ts: Math.floor(Date.parse(eventPayload.time)/1000),
        component: eventPayload.name || 'unknown',
        host: eventPayload.hostname || 'unknown',
        label: eventPayload.eventLabel || 'unknown',
        env: eventPayload.env || 'unknown',
        pid: eventPayload.pid || 'unknown',
        app: eventPayload.app || 'unknown',
        version: eventType || 'unknown',
        level: eventPayload.level || 'unspecified'
      };

      return eventRow;
    }
  }

  function fixJSON(str) {
    var json = str.replace(/(['"])?([a-zA-Z0-9_]+)(['"])?:/g, '"$2": ');
    json = json.replace(/\'/g, '"');
    return json;
  }

  function extractDetailsObject(eventPayload) {
    var msg = eventPayload.msg;

    var detailsObjectStart = msg.indexOf('{');
    var parse = false;
    if (detailsObjectStart>=0) {
      parse = true;
    }
    else {
      detailsObjectStart = msg.indexOf('[');
      if (detailsObjectStart >= 0) parse = true;
    }

    if (parse) {
      var json = fixJSON(msg.substr(detailsObjectStart));
      return JSON.parse(json);
    }

    return { details: msg.substr(eventPayload.eventLabel.length+1) };
  }

  /**
   * Container names are per application per environment.
   * @note: In S3 a container name would reference a bucket.
   */
  function chooseStorageContainerName(eventRow) {
    if (!eventRow.app) throw 'chooseStorageContainerName eventRow.app is empty';
    if (!eventRow.env) warn('chooseStorageContainerName eventRow.env is empty');
    return eventRow.app + '-' + eventRow.env;
  }

  /**
   * Constructs an S3 friendly URL (uses random hash at start of key name to optimize S3 performance)
   * @param eventRow
   */
  function chooseStorageKey(eventRow) {
    return util.format("%s-%s-%s", new Date().toISOString(), eventRow.component, eventRow.version);
  }

  function saveToLongTermStorage(eventRow, eventPayload) {
    var container = chooseStorageContainerName(eventRow);
    var key = chooseStorageKey(eventRow);

    return longTermStorage.save(container, key, eventPayload);
  }

  return m;
};
<commit_msg>Add key to event row.  Concatenate env to app field.<commit_after>/**
 * @module myModule
 * @summary: This module's purpose is to:
 *
 * @description:
 *
 * Author: Justin Mooser
 * Created On: 2015-06-01.
 * @license Apache-2.0
 */

"use strict";

var util = require('util');

/**
 *
 * @param config
 * @param storage .save(): promise rejects if it should not be retried.
 * @param longTermStorage - .save(): resolves url to saved resource.  Rejects if it should not be retried.
 * @returns {{}}
 */
module.exports = function construct(config, storage, longTermStorage) {
  var m = {};
  config = config ? config : {};
  config = _.defaults(config, {
    saveEventsToLongTermStorage: true
  });

  m.processEvent = function(eventPayload, eventType) {
    var eventRow;
    return p.resolve().then(function() {
      return extractEventRow(eventPayload,eventType);
    })
    .then(function(eventrow) {
      eventRow = eventrow;
      console.log('Saving to S3...', eventRow.timestamp);
      return saveToLongTermStorage(eventRow, eventPayload)
        .then(function(result) {
          console.log('Saving to S3...', eventRow.timestamp);
          return result;
        })
        .catch(function(err) {
          console.error('Failed to save to S3.', err);
        });
    })
    .then(function(longTermStorageUrl) {
      console.log('Saved to', longTermStorageUrl);
        // the database only allows 100 characters.
      if (eventRow.details.length && eventRow.details.length > 100) {
        eventRow.details = eventRow.details.substr(0, 99);
      }
      if(eventRow.label.length > 24) {
        eventRow.label = eventRow.label.substr(0,24);
      }
      eventRow.url = longTermStorageUrl;
      eventRow.timestamp = Math.floor(new Date().getTime()/1000);
      return storage.save('eventlog', eventRow)
        .catch(function(err) {
          console.error('Error saving tracked event to storage.');
          console.error(err);
        });
    })
    .catch(function(err) {
      console.error('Unexpected error processing tracked event.');
      console.error(err);
    });
  };

  function extractEventRow(eventPayload, eventType) {
    eventType = eventType || 'bunyan-v1';
    // you have to extract the payload and the essential details about the logged event.
    if (eventType=='bunyan-v1') {
      var details = extractDetailsObject(eventPayload);

      var eventRow = {
        details: JSON.stringify(details) || '{}',
        local_ts: Math.floor(Date.parse(eventPayload.time)/1000),
        component: eventPayload.name || 'unknown',
        host: eventPayload.hostname || 'unknown',
        label: eventPayload.eventLabel || 'unknown',
        env: eventPayload.env || 'env',
        pid: eventPayload.pid || 'unknown',
        app: (eventPayload.app || 'unknown') + (eventPayload.env || 'env'),
        version: eventType || 'unknown',
        level: eventPayload.level || 'unspecified'
      };

      eventRow.key = util.format('%s-%s', eventRow.app, eventRow.label);

      return eventRow;
    }
  }

  function fixJSON(str) {
    var json = str.replace(/(['"])?([a-zA-Z0-9_]+)(['"])?:/g, '"$2": ');
    json = json.replace(/\'/g, '"');
    return json;
  }

  function extractDetailsObject(eventPayload) {
    var msg = eventPayload.msg;

    var detailsObjectStart = msg.indexOf('{');
    var parse = false;
    if (detailsObjectStart>=0) {
      parse = true;
    }
    else {
      detailsObjectStart = msg.indexOf('[');
      if (detailsObjectStart >= 0) parse = true;
    }

    if (parse) {
      var json = fixJSON(msg.substr(detailsObjectStart));
      return JSON.parse(json);
    }

    return { details: msg.substr(eventPayload.eventLabel.length+1) };
  }

  /**
   * Container names are per application per environment.
   * @note: In S3 a container name would reference a bucket.
   */
  function chooseStorageContainerName(eventRow) {
    if (!eventRow.app) throw 'chooseStorageContainerName eventRow.app is empty';
    if (!eventRow.env) warn('chooseStorageContainerName eventRow.env is empty');
    return eventRow.app + '-' + eventRow.env;
  }

  /**
   * Constructs an S3 friendly URL (uses random hash at start of key name to optimize S3 performance)
   * @param eventRow
   */
  function chooseStorageKey(eventRow) {
    return util.format("%s-%s-%s", new Date().toISOString(), eventRow.component, eventRow.version);
  }

  function saveToLongTermStorage(eventRow, eventPayload) {
    var container = chooseStorageContainerName(eventRow);
    var key = chooseStorageKey(eventRow);

    return longTermStorage.save(container, key, eventPayload);
  }

  return m;
};
<|endoftext|>


<commit_before>/* NODE MODULES */

/** LOCAL OBJECT
 * @property {} -
 */
var AIRPORT = {
    gates: {}
};

/** MODULE INTERFACE
 *@method {function} create - Creates a JSFly server
 */
module.exports = {
    createServer: createServer,
    addPlane: addPlane,

    gates: AIRPORT.gates
};

/*----------------------------------------------------------------------------*/

/** Creates a JSFly server
 * @param
 * @returns
 */
function createServer() {

}

/** Adds
 * @param {object} wingified
 * @returns
 */
function addPlane(wingified, options) {
    var added = false;

    if (!AIRPORT.gates[wingified.id]) {
        AIRPORT.gates[wingified.id] = wingified;
        added = true;
    }

    return added;
}<commit_msg>Added validation and bindings to runway and tower modules.<commit_after>/* NODE MODULES */
var exceptions = require('../utils/exceptions');
var eventEmitter = require('events').EventEmitter;

/** LOCAL OBJECT
 * @property {} -
 */
var AIRPORT = {
    runway: require('./runway'),
    tower: require('./tower'),
    gates: {},

    emitter: new eventEmitter()
};

/** MODULE INTERFACE
 *@method {function} create - Creates a JSFly server
 */
module.exports = {
    create: create,
    addPlane: addPlane,

    gates: AIRPORT.gates
};

/*----------------------------------------------------------------------------*/

/** Creates a JSFly server
 * @param
 * @returns
 */
function create(port, onReady) {
    if (typeof port !== 'number') {
        port = parseInt(port);
        if (isNaN(port)) {
            throw exceptions.throwNew('wrong port');
        }
    } else if (typeof onReady !== 'function') {
        throw exceptions.throwNew('wrong onReady');
    }

    AIRPORT.tower.listen(port, function (tower) {
        onReady(AIRPORT.emitter);
        tower.on('ready', function () {
            AIRPORT.emitter.emit('ready');
        });
    });
}

/** Adds
 * @param {object} wingified
 * @returns
 */
function addPlane(wingified, options) {
    var added = false;

    if (!AIRPORT.gates[wingified.id]) {
        AIRPORT.gates[wingified.id] = wingified;
        added = true;
    }

    return added;
}<|endoftext|>


<commit_before>/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2020
 */

/* eslint-disable camelcase */
/* eslint-disable no-unused-expressions */

import { SETTINGS_KEYS } from '../settings';
import { UIDatabase } from '../database';
import { createRecord, parseBoolean, parseDate, parseNumber } from '../database/utilities';

const { SYNC_URL } = SETTINGS_KEYS;

const RESOURCES = {
  PATIENT: '/api/v4/patient',
  PRESCRIBER: '/api/v4/prescriber',
};

const SEPARATORS = {
  QUERY_STRING: '?',
  QUERY_PARAMETERS: '&',
  QUERY_WILDCARD: '@',
  POLICY_NUMBER: '-',
};

export const createPatientRecord = patient => {
  patient?.policies?.forEach(createPolicyRecord);
};

export const createPolicyRecord = policy => {
  const { nameId, enteredById, insuranceProviderId } = policy;
  const enteredBy = UIDatabase.getOrCreate('User', enteredById);
  const patient = UIDatabase.getOrCreate('Name', nameId);
  const insuranceProvider = UIDatabase.getOrCreate('InsuranceProvider', insuranceProviderId);
  const policyRecord = { ...policy, enteredBy, patient, insuranceProvider };
  UIDatabase.write(() => createRecord(UIDatabase, 'InsurancePolicy', policyRecord));
};

const getQueryString = params =>
  params.reduce((queryString, param) => {
    const [[key, value]] = Object.entries(param);
    if (!value) return queryString;
    const paramString = `${key}=${SEPARATORS.QUERY_WILDCARD}${value}${SEPARATORS.QUERY_WILDCARD}`;
    const paramSeparator = queryString.length > 0 ? SEPARATORS.PARAMETERS : SEPARATORS.QUERY_STRING;
    return queryString + paramSeparator + paramString;
  }, '');

const getPrescriberQueryString = ({ firstName = '', lastName = '', registrationCode = '' }) => {
  const queryParams = [
    { first_name: firstName },
    { last_name: lastName },
    { code: registrationCode },
  ];
  return getQueryString(queryParams);
};

const getPatientQueryString = ({
  firstName = '',
  lastName = '',
  dateOfBirth = '',
  policyNumber = '',
} = {}) => {
  const queryParams = [
    { first_name: firstName },
    { last_name: lastName },
    { dob: dateOfBirth },
    { policy_number: policyNumber },
  ];
  return getQueryString(queryParams);
};

export const getPatientRequestUrl = params => {
  const baseUrl = UIDatabase.getSetting(SYNC_URL);
  const endpoint = RESOURCES.PATIENT;
  const queryString = getPatientQueryString(params);
  return baseUrl + endpoint + queryString;
};

export const getPrescriberRequestUrl = params => {
  const baseUrl = UIDatabase.getSetting(SYNC_URL);
  const endpoint = RESOURCES.PRESCRIBER;
  const queryString = getPrescriberQueryString(params);
  return baseUrl + endpoint + queryString;
};

const processInsuranceResponse = response =>
  response.map(
    ({
      ID: id,
      insuranceProviderID: insuranceProviderId,
      nameID: nameId,
      policyNumberFamily,
      policyNumberPerson,
      discountRate,
      expiryDate,
      isActive,
      enteredByID: enteredById,
      type,
    }) => ({
      id,
      insuranceProviderId,
      nameId,
      policyNumberFamily,
      policyNumberPerson,
      discountRate: parseNumber(discountRate),
      expiryDate: parseDate(expiryDate),
      isActive: parseBoolean(isActive),
      enteredById,
      type,
    })
  );

export const processPatientResponse = response => {
  const patientData = response.map(
    ({
      ID: id,
      name,
      code,
      phone: phoneNumber,
      bill_address1: billAddress1,
      bill_address2: billAddress2,
      bill_address3: billAddress3,
      bill_address4: billAddress4,
      bill_postal_zip_code: billPostalZipCode,
      email: emailAddress,
      supplying_store_id: supplyingStoreId,
      first: firstName,
      last: lastName,
      date_of_birth,
      nameInsuranceJoin,
    }) => ({
      id,
      name,
      code,
      phoneNumber,
      billAddress1,
      billAddress2,
      billAddress3,
      billAddress4,
      billPostalZipCode,
      emailAddress,
      supplyingStoreId,
      firstName,
      lastName,
      dateOfBirth: parseDate(date_of_birth),
      policies: processInsuranceResponse(nameInsuranceJoin),
    })
  );
  return patientData;
};

export const processPrescriberResponse = response => {
  const prescriberData = response.map(
    ({
      ID,
      first_name,
      last_name,
      registration_code,
      address1,
      address2,
      phone,
      mobile,
      email,
      store_ID,
    }) => ({
      id: ID,
      firstName: first_name,
      lastName: last_name,
      registrationCode: registration_code,
      address1,
      address2,
      phoneNumber: phone,
      mobileNumber: mobile,
      emailAddress: email,
      storeId: store_ID,
    })
  );
  return prescriberData;
};
<commit_msg>Fix lookup query constructor typo<commit_after>/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2020
 */

/* eslint-disable camelcase */
/* eslint-disable no-unused-expressions */

import { SETTINGS_KEYS } from '../settings';
import { UIDatabase } from '../database';
import { createRecord, parseBoolean, parseDate, parseNumber } from '../database/utilities';

const { SYNC_URL } = SETTINGS_KEYS;

const RESOURCES = {
  PATIENT: '/api/v4/patient',
  PRESCRIBER: '/api/v4/prescriber',
};

const SEPARATORS = {
  QUERY_STRING: '?',
  QUERY_PARAMETERS: '&',
  QUERY_WILDCARD: '@',
  POLICY_NUMBER: '-',
};

export const createPatientRecord = patient => {
  patient?.policies?.forEach(createPolicyRecord);
};

export const createPolicyRecord = policy => {
  const { nameId, enteredById, insuranceProviderId } = policy;
  const enteredBy = UIDatabase.getOrCreate('User', enteredById);
  const patient = UIDatabase.getOrCreate('Name', nameId);
  const insuranceProvider = UIDatabase.getOrCreate('InsuranceProvider', insuranceProviderId);
  const policyRecord = { ...policy, enteredBy, patient, insuranceProvider };
  UIDatabase.write(() => createRecord(UIDatabase, 'InsurancePolicy', policyRecord));
};

const getQueryString = params =>
  params.reduce((queryString, param) => {
    const [[key, value]] = Object.entries(param);
    if (!value) return queryString;
    const paramString = `${key}=${SEPARATORS.QUERY_WILDCARD}${value}${SEPARATORS.QUERY_WILDCARD}`;
    const paramSeparator =
      queryString.length > 0 ? SEPARATORS.QUERY_PARAMETERS : SEPARATORS.QUERY_STRING;
    return queryString + paramSeparator + paramString;
  }, '');

const getPrescriberQueryString = ({ firstName = '', lastName = '', registrationCode = '' }) => {
  const queryParams = [
    { first_name: firstName },
    { last_name: lastName },
    { code: registrationCode },
  ];
  return getQueryString(queryParams);
};

const getPatientQueryString = ({
  firstName = '',
  lastName = '',
  dateOfBirth = '',
  policyNumber = '',
} = {}) => {
  const queryParams = [
    { first_name: firstName },
    { last_name: lastName },
    { dob: dateOfBirth },
    { policy_number: policyNumber },
  ];
  return getQueryString(queryParams);
};

export const getPatientRequestUrl = params => {
  const baseUrl = UIDatabase.getSetting(SYNC_URL);
  const endpoint = RESOURCES.PATIENT;
  const queryString = getPatientQueryString(params);
  return baseUrl + endpoint + queryString;
};

export const getPrescriberRequestUrl = params => {
  const baseUrl = UIDatabase.getSetting(SYNC_URL);
  const endpoint = RESOURCES.PRESCRIBER;
  const queryString = getPrescriberQueryString(params);
  return baseUrl + endpoint + queryString;
};

const processInsuranceResponse = response =>
  response.map(
    ({
      ID: id,
      insuranceProviderID: insuranceProviderId,
      nameID: nameId,
      policyNumberFamily,
      policyNumberPerson,
      discountRate,
      expiryDate,
      isActive,
      enteredByID: enteredById,
      type,
    }) => ({
      id,
      insuranceProviderId,
      nameId,
      policyNumberFamily,
      policyNumberPerson,
      discountRate: parseNumber(discountRate),
      expiryDate: parseDate(expiryDate),
      isActive: parseBoolean(isActive),
      enteredById,
      type,
    })
  );

export const processPatientResponse = response => {
  const patientData = response.map(
    ({
      ID: id,
      name,
      code,
      phone: phoneNumber,
      bill_address1: billAddress1,
      bill_address2: billAddress2,
      bill_address3: billAddress3,
      bill_address4: billAddress4,
      bill_postal_zip_code: billPostalZipCode,
      email: emailAddress,
      supplying_store_id: supplyingStoreId,
      first: firstName,
      last: lastName,
      date_of_birth,
      nameInsuranceJoin,
    }) => ({
      id,
      name,
      code,
      phoneNumber,
      billAddress1,
      billAddress2,
      billAddress3,
      billAddress4,
      billPostalZipCode,
      emailAddress,
      supplyingStoreId,
      firstName,
      lastName,
      dateOfBirth: parseDate(date_of_birth),
      policies: processInsuranceResponse(nameInsuranceJoin),
    })
  );
  return patientData;
};

export const processPrescriberResponse = response => {
  const prescriberData = response.map(
    ({
      ID,
      first_name,
      last_name,
      registration_code,
      address1,
      address2,
      phone,
      mobile,
      email,
      store_ID,
    }) => ({
      id: ID,
      firstName: first_name,
      lastName: last_name,
      registrationCode: registration_code,
      address1,
      address2,
      phoneNumber: phone,
      mobileNumber: mobile,
      emailAddress: email,
      storeId: store_ID,
    })
  );
  return prescriberData;
};
<|endoftext|>