<commit_before>import { assign } from '@ember/polyfills';
import Service from '@ember/service';
import { computed, get, observer, set } from '@ember/object';
import { assert, warn } from '@ember/debug';
import intercom from 'intercom';
import { next } from '@ember/runloop';
import { typeOf } from '@ember/utils';
import { underscore } from '@ember/string';
import Evented from '@ember/object/evented';
import { alias } from '@ember/object/computed';

const WarnOption = {
  id: 'ember-intercom-io.missing-data'
};

/**
 * Normalization function for converting intercom data to a consistent format.
 *
 * Changes:
 * - underscore keys
 * - convert dates to unix timestamps
 *
 * @param  {Object} data
 *
 * @private
 * @return {Object}
 */
function normalizeIntercomMetadata(data) {
  let result = {};
  let val;
  Object.keys(data).forEach(key => {
    val = data[key];
    if (typeOf(val) === 'object') {
      result[underscore(key)] = normalizeIntercomMetadata(val);
    } else {
      if (typeOf(val) === 'date') {
        val = val.valueOf();
      }
      if (typeOf(val) !== 'undefined') {
        result[underscore(key)] = val;
      }
    }
  });

  return result;
}

export default Service.extend(Evented, {
  init() {
    this._super(...arguments);
    set(this, 'user', { email: null, name: null, hash: null, user_id: null });
  },

  api: intercom,
  user: null,
  /**
   * [description]
   * @return {[type]} [description]
   */
  _userHashProp: computed('user', 'config.userProperties.userHashProp', function() {
    return get(this, `user.${get(this, 'config.userProperties.userHashProp')}`);
  }),

  _userIdProp: computed('user', 'config.userProperties.userIdProp', function() {
    return get(this, `user.${get(this, 'config.userProperties.userIdProp')}`);
  }),

  _userNameProp: computed('user', 'config.userProperties.nameProp', function() {
    return get(this, `user.${get(this, 'config.userProperties.nameProp')}`);
  }),

  _userEmailProp: computed('user', 'config.userProperties.emailProp', function() {
    return get(this, `user.${get(this, 'config.userProperties.emailProp')}`);
  }),

  _userCreatedAtProp: computed('user', 'config.userProperties.createdAtProp', function() {
    return get(this, `user.${get(this, 'config.userProperties.createdAtProp')}`);
  }),

  /**
   * Indicates the open state of the Intercom panel
   *
   * @public
   * @type {Boolean}
   */
  isOpen: false,

  /**
   * Indicates whether the Intercom boot command has been called.
   *
   * @public
   * @readonly
   * @type {Boolean}
   */
  isBooted: false,
  _hasUserContext: computed('user', '_userNameProp', '_userEmailProp', '_userCreatedAtProp', function() {
    return (
      !!get(this, 'user') &&
      !!get(this, '_userNameProp') &&
      (!!get(this, '_userEmailProp') || !!get(this, '_userIdProp'))
    );
  }),
  /**
   * Reports the number of unread messages
   *
   * @public
   * @type {Number}
   */
  unreadCount: 0,

  /**
   * If true, will automatically update intercom when changes to user object are made.
   *
   * @type {Boolean}
   * @public
   */
  autoUpdate: true,

  /**
   * Hide the default Intercom launcher button
   *
   * @public
   * @type {Boolean}
   */
  hideDefaultLauncher: false,

  /**
   * @private
   * alias for appId
   * @type {[type]}
   */
  appId: alias('config.appId'),

  start(bootConfig = {}) {
    let _bootConfig = assign(get(this, '_intercomBootConfig'), bootConfig);
    this.boot(_bootConfig);
  },

  stop() {
    return this.shutdown();
  },

  /**
   * Boot intercom window
   * @param  {Object} [config={}] [description]
   * @public
   */
  boot(config = {}) {
    this._callIntercomMethod('boot', normalizeIntercomMetadata(config));
    this._addEventHandlers();
    this.set('isBooted', true);
  },

  /**
   * Update intercom data
   * @param  {Object} [config={}] [description]
   * @public
   */
  update(config = {}) {
    if (!this.get('isBooted')) {
      warn('Cannot call update before boot', WarnOption);
      return;
    }

    let _hasUserContext = this.get('_hasUserContext');
    if (_hasUserContext) {
      this._callIntercomMethod('update', normalizeIntercomMetadata(config));
    } else {
      warn(
        'Refusing to send update to Intercom because user context is incomplete. Missing userId or email',
        WarnOption
      );
    }
  },

  /**
   * shutdown Intercom window
   * @public
   */
  shutdown() {
    this.set('isBooted', false);
    this._hasEventHandlers = false;
    this._callIntercomMethod('shutdown');
  },

  /**
   * Show intercom window
   * @public
   */
  show() {
    return this._wrapIntercomCallInPromise('show', 'show');
  },

  /**
   * hide intercom window
   * @public
   */
  hide() {
    return this._wrapIntercomCallInPromise('hide', 'hide');
  },

  toggleOpen() {
    this.get('isOpen') ? this.hide() : this.show();
  },

  /**
   * Opens the message window with the message list visible.
   *
   * @public
   * @return {Promise}
   */
  showMessages() {
    return this._wrapIntercomCallInPromise('showMessages', 'show');
  },

  /**
   * Opens the message window with the new message view.
   *
   * @public
   * @return {Promise}
   */
  showNewMessage(initialText) {
    return this._wrapIntercomCallInPromise('showNewMessage', 'show', initialText);
  },

  /**
   * You can submit an event using the trackEvent method.
   * This will associate the event with the currently logged in user and
   * send it to Intercom.
   *
   * The final parameter is a map that can be used to send optional
   * metadata about the event.
   *
   * @param {String} eventName
   * @param {Object} metadata
   * @public
   */
  trackEvent() {
    this._callIntercomMethod('trackEvent', ...arguments);
  },

  /**
   * A visitor is someone who goes to your site but does not use the messenger.
   * You can track these visitors via the visitor user_id. This user_id
   * can be used to retrieve the visitor or lead through the REST API.
   *
   * @public
   * @return {String} The visitor ID
   */
  getVisitorId() {
    return this.get('api')('getVisitorId');
  },

  /**
   * Private on hide
   * @private
   * @return {[type]} [description]
   */
  _onHide() {
    this.set('isOpen', false);
    this.trigger('hide');
  },

  /**
   * handle onShow events
   * @private
   */
  _onShow() {
    this.set('isOpen', true);
    this.trigger('show');
  },

  /**
   * Handle onUnreadCountChange Events
   * @param  {[type]} count [description]
   * @private
   */
  _onUnreadCountChange(count) {
    this.set('unreadCount', Number(count));
  },

  _addEventHandlers() {
    if (this._hasEventHandlers) {
      return;
    }
    this._callIntercomMethod('onHide', () => next(this, '_onHide'));
    this._callIntercomMethod('onShow', () => next(this, '_onShow'));
    this._callIntercomMethod('onUnreadCountChange', count => {
      this._onUnreadCountChange(count);
    });
    this._hasEventHandlers = true;
  },

  _wrapIntercomCallInPromise(intercomMethod, eventName, ...args) {
    return new Promise(resolve => {
      let isOpen = this.get('isOpen');
      if ((eventName === 'show' && isOpen) || (eventName === 'hide' && !isOpen)) {
        next(this, resolve);
      } else {
        this.one(eventName, resolve);
      }
      this._callIntercomMethod(intercomMethod, ...args);
    });
  },

  _callIntercomMethod(methodName, ...args) {
    let intercom = this.get('api');
    intercom(methodName, ...args);
  },

  userDataDidChange: observer('user.@each', function() {
    if (this.get('autoUpdate') && this.get('isBooted')) {
      let user = this.get('_computedUser');
      let appId = this.get('appId');
      let config = { app_id: appId, ...user };
      this.update(config);
    }
  }),

  /**
   * Alias for computed user data with app-provided config values
   * @private
   * @type {[type]}
   */
  _computedUser: computed(
    'user.@each',
    'user',
    '_userHashProp',
    '_userIdProp',
    '_userNameProp',
    '_userEmailProp',
    '_userCreatedAtProp',
    function() {
      assert('You must supply an "ENV.intercom.appId" in your "config/environment.js" file.', this.get('appId'));

      let obj = {};
      if (this.get('user')) {
        let userProps = Object.values(get(this, 'config.userProperties')),
          user = get(this, 'user'),
          userKeys = Object.keys(user);

        userKeys.forEach(k => {
          if (!userProps.includes(k) && !obj.hasOwnProperty(k)) {
            obj[k] = user[k];
          }
        });

        obj.user_hash = get(this, '_userHashProp');
        obj.user_id = get(this, '_userIdProp');
        obj.name = get(this, '_userNameProp');
        obj.email = get(this, '_userEmailProp');
        if (get(this, '_userCreatedAtProp')) {
          // eslint-disable-next-line
          obj.created_at = get(this, '_userCreatedAtProp');
        }
      }
      return obj;
    }
  ),

  _intercomBootConfig: computed('config', 'user.@each', '_hasUserContext', 'hideDefaultLauncher', function() {
    let appId = get(this, 'config.appId');
    let user = get(this, '_computedUser');
    let _hasUserContext = get(this, '_hasUserContext');
    let hideDefaultLauncher = get(this, 'hideDefaultLauncher');

    assert('You must supply an "ENV.intercom.appId" in your "config/environment.js" file.', appId);

    let obj = { app_id: appId };
    if (hideDefaultLauncher) {
      obj.hideDefaultLauncher = true;
    }

    if (_hasUserContext) {
      obj = { ...obj, ...user };
    }

    return normalizeIntercomMetadata(obj);
  })
});
<commit_msg>chore: ESLint fixes<commit_after>import { assign } from '@ember/polyfills';
import Service from '@ember/service';
import { computed, get, observer, set } from '@ember/object';
import { assert, warn } from '@ember/debug';
import intercom from 'intercom';
import { next } from '@ember/runloop';
import { typeOf } from '@ember/utils';
import { underscore } from '@ember/string';
import Evented from '@ember/object/evented';
import { alias } from '@ember/object/computed';

const WarnOption = {
  id: 'ember-intercom-io.missing-data'
};

/**
 * Normalization function for converting intercom data to a consistent format.
 *
 * Changes:
 * - underscore keys
 * - convert dates to unix timestamps
 *
 * @param  {Object} data
 *
 * @private
 * @return {Object}
 */
function normalizeIntercomMetadata(data) {
  let result = {};
  let val;
  Object.keys(data).forEach(key => {
    val = data[key];
    if (typeOf(val) === 'object') {
      result[underscore(key)] = normalizeIntercomMetadata(val);
    } else {
      if (typeOf(val) === 'date') {
        val = val.valueOf();
      }
      if (typeOf(val) !== 'undefined') {
        result[underscore(key)] = val;
      }
    }
  });

  return result;
}

export default Service.extend(Evented, {
  init() {
    this._super(...arguments);
    set(this, 'user', { email: null, name: null, hash: null, user_id: null });
  },

  api: intercom,
  user: null,
  /**
   * [description]
   * @return {[type]} [description]
   */
  _userHashProp: computed('user', 'config.userProperties.userHashProp', function() {
    return get(this, `user.${get(this, 'config.userProperties.userHashProp')}`);
  }),

  _userIdProp: computed('user', 'config.userProperties.userIdProp', function() {
    return get(this, `user.${get(this, 'config.userProperties.userIdProp')}`);
  }),

  _userNameProp: computed('user', 'config.userProperties.nameProp', function() {
    return get(this, `user.${get(this, 'config.userProperties.nameProp')}`);
  }),

  _userEmailProp: computed('user', 'config.userProperties.emailProp', function() {
    return get(this, `user.${get(this, 'config.userProperties.emailProp')}`);
  }),

  _userCreatedAtProp: computed('user', 'config.userProperties.createdAtProp', function() {
    return get(this, `user.${get(this, 'config.userProperties.createdAtProp')}`);
  }),

  /**
   * Indicates the open state of the Intercom panel
   *
   * @public
   * @type {Boolean}
   */
  isOpen: false,

  /**
   * Indicates whether the Intercom boot command has been called.
   *
   * @public
   * @readonly
   * @type {Boolean}
   */
  isBooted: false,
  _hasUserContext: computed('user', '_userNameProp', '_userEmailProp', '_userCreatedAtProp', function() {
    return (
      !!get(this, 'user') &&
      !!get(this, '_userNameProp') &&
      (!!get(this, '_userEmailProp') || !!get(this, '_userIdProp'))
    );
  }),
  /**
   * Reports the number of unread messages
   *
   * @public
   * @type {Number}
   */
  unreadCount: 0,

  /**
   * If true, will automatically update intercom when changes to user object are made.
   *
   * @type {Boolean}
   * @public
   */
  autoUpdate: true,

  /**
   * Hide the default Intercom launcher button
   *
   * @public
   * @type {Boolean}
   */
  hideDefaultLauncher: false,

  /**
   * @private
   * alias for appId
   * @type {[type]}
   */
  appId: alias('config.appId'),

  start(bootConfig = {}) {
    let _bootConfig = assign(get(this, '_intercomBootConfig'), bootConfig);
    this.boot(_bootConfig);
  },

  stop() {
    return this.shutdown();
  },

  /**
   * Boot intercom window
   * @param  {Object} [config={}] [description]
   * @public
   */
  boot(config = {}) {
    this._callIntercomMethod('boot', normalizeIntercomMetadata(config));
    this._addEventHandlers();
    this.set('isBooted', true);
  },

  /**
   * Update intercom data
   * @param  {Object} [config={}] [description]
   * @public
   */
  update(config = {}) {
    if (!this.get('isBooted')) {
      warn('Cannot call update before boot', WarnOption);
      return;
    }

    let _hasUserContext = this.get('_hasUserContext');
    if (_hasUserContext) {
      this._callIntercomMethod('update', normalizeIntercomMetadata(config));
    } else {
      warn(
        'Refusing to send update to Intercom because user context is incomplete. Missing userId or email',
        WarnOption
      );
    }
  },

  /**
   * shutdown Intercom window
   * @public
   */
  shutdown() {
    this.set('isBooted', false);
    this._hasEventHandlers = false;
    this._callIntercomMethod('shutdown');
  },

  /**
   * Show intercom window
   * @public
   */
  show() {
    return this._wrapIntercomCallInPromise('show', 'show');
  },

  /**
   * hide intercom window
   * @public
   */
  hide() {
    return this._wrapIntercomCallInPromise('hide', 'hide');
  },

  toggleOpen() {
    this.get('isOpen') ? this.hide() : this.show();
  },

  /**
   * Opens the message window with the message list visible.
   *
   * @public
   * @return {Promise}
   */
  showMessages() {
    return this._wrapIntercomCallInPromise('showMessages', 'show');
  },

  /**
   * Opens the message window with the new message view.
   *
   * @public
   * @return {Promise}
   */
  showNewMessage(initialText) {
    return this._wrapIntercomCallInPromise('showNewMessage', 'show', initialText);
  },

  /**
   * You can submit an event using the trackEvent method.
   * This will associate the event with the currently logged in user and
   * send it to Intercom.
   *
   * The final parameter is a map that can be used to send optional
   * metadata about the event.
   *
   * @param {String} eventName
   * @param {Object} metadata
   * @public
   */
  trackEvent() {
    this._callIntercomMethod('trackEvent', ...arguments);
  },

  /**
   * A visitor is someone who goes to your site but does not use the messenger.
   * You can track these visitors via the visitor user_id. This user_id
   * can be used to retrieve the visitor or lead through the REST API.
   *
   * @public
   * @return {String} The visitor ID
   */
  getVisitorId() {
    return this.get('api')('getVisitorId');
  },

  /**
   * Private on hide
   * @private
   * @return {[type]} [description]
   */
  _onHide() {
    this.set('isOpen', false);
    this.trigger('hide');
  },

  /**
   * handle onShow events
   * @private
   */
  _onShow() {
    this.set('isOpen', true);
    this.trigger('show');
  },

  /**
   * Handle onUnreadCountChange Events
   * @param  {[type]} count [description]
   * @private
   */
  _onUnreadCountChange(count) {
    this.set('unreadCount', Number(count));
  },

  _addEventHandlers() {
    if (this._hasEventHandlers) {
      return;
    }
    this._callIntercomMethod('onHide', () => next(this, '_onHide'));
    this._callIntercomMethod('onShow', () => next(this, '_onShow'));
    this._callIntercomMethod('onUnreadCountChange', count => {
      this._onUnreadCountChange(count);
    });
    this._hasEventHandlers = true;
  },

  _wrapIntercomCallInPromise(intercomMethod, eventName, ...args) {
    return new Promise(resolve => {
      let isOpen = this.get('isOpen');
      if ((eventName === 'show' && isOpen) || (eventName === 'hide' && !isOpen)) {
        next(this, resolve);
      } else {
        this.one(eventName, resolve);
      }
      this._callIntercomMethod(intercomMethod, ...args);
    });
  },

  _callIntercomMethod(methodName, ...args) {
    let intercom = this.get('api');
    intercom(methodName, ...args);
  },

  // eslint-disable-next-line ember/no-observers
  userDataDidChange: observer('user.@each', function() {
    if (this.get('autoUpdate') && this.get('isBooted')) {
      let user = this.get('_computedUser');
      let appId = this.get('appId');
      let config = assign({ app_id: appId}, user );
      this.update(config);
    }
  }),

  /**
   * Alias for computed user data with app-provided config values
   * @private
   * @type {[type]}
   */
  _computedUser: computed(
    'user.@each',
    'user',
    '_userHashProp',
    '_userIdProp',
    '_userNameProp',
    '_userEmailProp',
    '_userCreatedAtProp',
    function() {
      assert('You must supply an "ENV.intercom.appId" in your "config/environment.js" file.', this.get('appId'));

      let obj = {};
      if (this.get('user')) {
        let userProps = Object.values(get(this, 'config.userProperties')),
          user = get(this, 'user'),
          userKeys = Object.keys(user);

        userKeys.forEach(k => {
          if (!userProps.includes(k) && !obj.hasOwnProperty(k)) {
            obj[k] = user[k];
          }
        });

        obj.user_hash = get(this, '_userHashProp');
        obj.user_id = get(this, '_userIdProp');
        obj.name = get(this, '_userNameProp');
        obj.email = get(this, '_userEmailProp');
        if (get(this, '_userCreatedAtProp')) {
          // eslint-disable-next-line
          obj.created_at = get(this, '_userCreatedAtProp');
        }
      }
      return obj;
    }
  ),

  _intercomBootConfig: computed('config', 'user.@each', '_hasUserContext', 'hideDefaultLauncher', function() {
    let appId = get(this, 'config.appId');
    let user = get(this, '_computedUser');
    let _hasUserContext = get(this, '_hasUserContext');
    let hideDefaultLauncher = get(this, 'hideDefaultLauncher');

    assert('You must supply an "ENV.intercom.appId" in your "config/environment.js" file.', appId);

    let obj = { app_id: appId };
    if (hideDefaultLauncher) {
      obj.hideDefaultLauncher = true;
    }

    if (_hasUserContext) {
      obj = assign({}, ...obj, ...user);
    }

    return normalizeIntercomMetadata(obj);
  })
});
<|endoftext|>


<commit_before>var plot = function (data, name) {
  var context = this;

  var xmax = ymax = Number.MIN_VALUE;
  var xmin = ymin = Number.MAX_VALUE;

  for ( var t in data ) {
    // compute the domain and range of the signal passed
    xmax = (xmax < t) ? t : xmax;
    xmin = (xmin > t) ? t : xmin;

    ymax = (ymax < data[t]) ? data[t] : ymax;
    ymin = (ymin > data[t]) ? data[t] : ymin;
  }

  context.xmax = xmax;
  context.xmin = xmin;
  context.ymax = ymax;
  context.ymin = ymin;

  crayon.bus.publish('DomainChanged', [context]);
  crayon.bus.publish('RangeChanged', [context]);
}

crayon.handle.plot = plot;
<commit_msg>fire FirstPlot event when plotting first time<commit_after>var plot = function (data, name) {
  var context = this;

  var xmax = ymax = Number.MIN_VALUE;
  var xmin = ymin = Number.MAX_VALUE;

  for ( var t in data ) {
    // compute the domain and range of the signal passed
    xmax = (xmax < t) ? t : xmax;
    xmin = (xmin > t) ? t : xmin;

    ymax = (ymax < data[t]) ? data[t] : ymax;
    ymin = (ymin > data[t]) ? data[t] : ymin;
  }

  context.xmax = xmax;
  context.xmin = xmin;
  context.ymax = ymax;
  context.ymin = ymin;

  if (!context.grid) { crayon.bus.publish('FirstPlot', [context]) }

  crayon.bus.publish('DomainChanged', [context]);
  crayon.bus.publish('RangeChanged', [context]);
}

crayon.handle.plot = plot;
<|endoftext|>


<commit_before>// load dependencies
var _ = require('lodash');
var striptags = require('striptags');
var similarity = require('compute-cosine-similarity');
var clusterfck = require('clusterfck');
var all = require('node-promise').all;
var Tokenizer = require('sentence-tokenizer');
var Utils = require('./utils/utils');
var Tfidf = require('./helpers/tfidf');
var Article = require('./model/articles');
var Cluster = require('./model/clusters');
var articleHelpers = require('./helpers/article');

module.exports = (function() {
    var Aggregator = function() {

        // global object members
        var keywordTotal = 4;
        var articleDocs;

        var processDocs = function() {

            // collect article content to parse for keywords
            var collection = articleDocs.map(function(item) {

                // combine title and content for keyword search
                return item.title + " " + striptags(item.content);
            });

            // get keywords for each collection item
            var collectionKeywords = [];
            for (var i = 0, n = collection.length; i < n; i++) {
                collectionKeywords.push(getKeywords(collection[i], collection));
            }

            // flatten keywords, keep only unique items, and set to lowercase
            collectionKeywords = _.uniq(_.flattenDeep(collectionKeywords).map(function(item) {
                return item.toLowerCase();
            }));

            // get article summaries
            getArticleSummaries(collectionKeywords);

            /*
             * Iterate over each article, then each keyword
             * Identify which keywords in a document are relevant across the whole collection
             * Algorithm employed to weight keywords is term frequency--inverse document frequency
             */
            keywordVectors = [];
            for (var i = 0, n = collection.length; i < n; i++) {

                // vectors for the other axis
                var vectors = [];

                // iterate through
                var doc = collection[i];
                for (var j = 0, x = collectionKeywords.length; j < x; j++) {

                    // default value
                    var vector = 0;

                    // get TF-IDF of keyword in document
                    var keyword = collectionKeywords[j];
                    if (Tfidf.frequency(keyword, doc) > 0) {
                        vector = Tfidf.tfidf(keyword, doc, collection);
                    }

                    // add to keyword axis
                    vectors.push(vector);
                }
                // add to collection axis
                keywordVectors.push(vectors);
            }

            // get a grid of the cosine similarity of keyword vector grid
            var similarityVectors = getCollectionSimilarity(keywordVectors);

            // identify clusters through hierarchical clustering
            // TODO: find a better ideal threshold
            var similarityThreshold = 1.2;
            var clusters = clusterfck.hcluster(similarityVectors, 'euclidean', 'average', similarityThreshold);

            // save clusters (and their article ids) to database
            saveClusters(clusters, similarityVectors).then(function(res) {
                cleanClusters();
            }, function(err) {
                console.log("cluster save error: ", err);
            });

        };

        var cleanClusters = function() {
            // TODO: logic for removing stale clusters
        };

        var saveClusters = function(clusters, similarityVectors) {
            var promises = Utils.createPromises(clusters.length);
            for (var i = 0; i < clusters.length; i++) {
                var promise = promises[i];

                // if cluster is bigger than 1, save to database
                if(clusters[i].size > 1) {
                    saveCluster(clusters[i], similarityVectors, promise);
                } else {
                    promise.resolve();
                }
            }

            return all(promises);
        };

        var saveCluster = function(cluster, similarityVectors, promise) {
            console.log("CLUSTER:");

            // get index of article doc by similarity vectors
            var indexes = [];
            indexes = recurseCluster(cluster, similarityVectors, indexes);

            // save cluster to database
            var clusterModel = new Cluster();
            var articlePromises = Utils.createPromises(indexes.length);

            // get article doc from index
            for (i = 0; i < indexes.length; i++) {
                var doc = articleDocs[indexes[i]];
                var articlePromise = articlePromises[i];

                // add article to cluster model
                clusterModel.articles.push(doc);

                // add cluster_id to article
                doc.cluster_id = clusterModel;

                // update article
                articleHelpers.updateArticle(doc, articlePromise);

                // print to console
                console.log("  " + doc.title);
            }

            // once articles are update, create new clusters
            all(articlePromises).then(function(res) {
                clusterModel.save(function(err) {
                    if (err) console.log(err);

                    promise.resolve(res);
                });
            }, function(err) {
                promise.reject(err);
            });
        };

        // recursively get article index for each node in cluster
        var recurseCluster = function(cluster, vectors, indexes) {

            // iterate through node properties
            for (var prop in cluster) {
                if (cluster.hasOwnProperty(prop)) {
                    if (prop === "value") {
                        indexes.push(vectors.indexOf(cluster[prop]));
                    } else if (prop === "left" || prop === "right") {
                        indexes = recurseCluster(cluster[prop], vectors, indexes);
                    }
                }
            }

            return indexes;
        };

        // get article summaries
        var getArticleSummaries = function(keywords) {
            var tokenizer = new Tokenizer();

            // TODO: add common abbreviations to database
            var abbreviations = [
                "I.O.U.",
                "M.D.",
                "N.B.",
                "P.O.",
                "U.K.",
                "U.S.",
                "U.S.A.",
                "P.S.",
                "Mr.",
                "Ms.",
                "Mrs.",
                "Dr.",
                "Jr.",
                "Lt.",
                "Gen.",
                "Sgt.",
                "Cpl.",
                "Pvt.",
                "Brig.",
                "Col.",
                "Maj.",
                "Jan.",
                "Feb.",
                "Mar.",
                "Apr.",
                "Aug.",
                "Sept.",
                "Oct.",
                "Nov.",
                "Dec."
            ];

            articleDocs.forEach(function(doc, i, docs) {

                var content = striptags(doc.content);
                var summary = "";
                if (content) {
                    // create ranked array of sentences
                    var ranked = [];
                    var rankedMax = 4;
                    var rankedHighest = 0;
                    var rankedStart = 0;

                    // tokenize sentences
                    tokenizer.setEntry(content);
                    var tokens = tokenizer.getSentences();

                    // iterate through each token to fix abbreviations and rank
                    var length = tokens.length;
                    tokens.forEach(function(token, i, tokens) {
                        var rank = 0;

                        // split token into basic words and check for abbreviations
                        var words = token.split(" ");
                        if (abbreviations.indexOf(words[words.length - 1]) !== -1) {
                            if (i < (length - 1)) {

                                // move content to next token
                                tokens[i + 1] = tokens[i] + " " + tokens[i + 1];
                                tokens[i] = "";
                                token = "";
                            }
                        }

                        // rank for each keyword across all documents
                        keywords.forEach(function(keyword) {
                            rank += Tfidf.frequency(keyword, content);
                        });

                        // update position in array based on rank
                        // logic assumes earlier sentences are still more important
                        if (token) {
                            if (rank > rankedHighest) {
                                rankedHighest = rank;
                                ranked.splice(rankedStart, 0, token);
                                rankedStart++;
                            } else {
                                ranked.push(token);
                            }
                        }
                    });

                    // determine max length
                    if (ranked < rankedMax) {
                        rankedMax = ranked.length;
                    }

                    // keep only the top sentences
                    ranked = ranked.slice(0, rankedMax);

                    // join as a string
                    summary = ranked.join(" ... ");
                }

                // add to doc for database save
                doc.summary = summary;
            });
        }

        // determine cosine similartiy of each document in collection based on keyword vectors for each
        var getCollectionSimilarity = function(keywordVectors) {
            var length = keywordVectors.length;

            // create new n-dimensional array
            var vectors = Utils.createNDimArray([length, length]);

            // iterate through each axis of new vector grid and calculate similarity of keywords
            for (var i = 0; i < length; i++) {
                for (var j = 0; j < length; j++) {
                    vectors[i][j] = similarity(keywordVectors[i], keywordVectors[j]);
                }
            }

            return vectors;
        };

        /*
         * Identify they key words in each document based on frequency
         * Identify which words in a document are relevant across the whole collection
         * Algorithm employed to weight keywords is term frequency--inverse document frequency
         */
        var getKeywords = function(doc, collection) {
            // console.log("process article");

            // associative array of words and their frequency
            var wordHash = [];

            var words = _.uniq(Utils.splitWords(doc));
            for (var i = 0, n = words.length; i < n; i++) {
                var word = words[i];
                var tfidf = Tfidf.tfidf(word, doc, collection);
                wordHash.push({
                    word: word,
                    freq: tfidf
                });
            }

            var keywords = identifyKeywords(wordHash);

            return keywords;
        };

        // iterate over hash to pick the most frequent words
        function identifyKeywords(wordHash) {

            // sort by frequency, remove frequency, slice first items
            var keywords = wordHash.sort(function(a, b) {
                if (a.freq > b.freq) {
                    return -1;
                }
                if (a.freq < b.freq) {
                    return 1;
                }

                return 0;
            }).map(function(obj) {
                return obj.word;
            }).slice(0, keywordTotal);

            // console.log("keywords: ", keywords);

            return keywords;
        }

        // handle errors
        var handleError = function(err) {
            console.log("process article error: ", err);
        };

        // initialize article aggregator
        Aggregator.prototype.init = function() {
            console.log("aggregator init");

            Article.find().exec(function(err, docs) {
                if (err) return handleError(err);

                // set global variables
                articleDocs = docs;

                // process all docs
                processDocs();
            });
        };
    };

    return Aggregator;
})();
<commit_msg>Fix rank calculation<commit_after>// load dependencies
var _ = require('lodash');
var striptags = require('striptags');
var similarity = require('compute-cosine-similarity');
var clusterfck = require('clusterfck');
var all = require('node-promise').all;
var Tokenizer = require('sentence-tokenizer');
var Utils = require('./utils/utils');
var Tfidf = require('./helpers/tfidf');
var Article = require('./model/articles');
var Cluster = require('./model/clusters');
var articleHelpers = require('./helpers/article');

module.exports = (function() {
    var Aggregator = function() {

        // global object members
        var keywordTotal = 4;
        var articleDocs;

        var processDocs = function() {

            // collect article content to parse for keywords
            var collection = articleDocs.map(function(item) {

                // combine title and content for keyword search
                return item.title + " " + striptags(item.content);
            });

            // get keywords for each collection item
            var collectionKeywords = [];
            for (var i = 0, n = collection.length; i < n; i++) {
                collectionKeywords.push(getKeywords(collection[i], collection));
            }

            // flatten keywords, keep only unique items, and set to lowercase
            collectionKeywords = _.uniq(_.flattenDeep(collectionKeywords).map(function(item) {
                return item.toLowerCase();
            }));

            // get article summaries
            getArticleSummaries(collectionKeywords);

            /*
             * Iterate over each article, then each keyword
             * Identify which keywords in a document are relevant across the whole collection
             * Algorithm employed to weight keywords is term frequency--inverse document frequency
             */
            keywordVectors = [];
            for (var i = 0, n = collection.length; i < n; i++) {

                // vectors for the other axis
                var vectors = [];

                // iterate through
                var doc = collection[i];
                for (var j = 0, x = collectionKeywords.length; j < x; j++) {

                    // default value
                    var vector = 0;

                    // get TF-IDF of keyword in document
                    var keyword = collectionKeywords[j];
                    if (Tfidf.frequency(keyword, doc) > 0) {
                        vector = Tfidf.tfidf(keyword, doc, collection);
                    }

                    // add to keyword axis
                    vectors.push(vector);
                }
                // add to collection axis
                keywordVectors.push(vectors);
            }

            // get a grid of the cosine similarity of keyword vector grid
            var similarityVectors = getCollectionSimilarity(keywordVectors);

            // identify clusters through hierarchical clustering
            // TODO: find a better ideal threshold
            var similarityThreshold = 1.2;
            var clusters = clusterfck.hcluster(similarityVectors, 'euclidean', 'average', similarityThreshold);

            // save clusters (and their article ids) to database
            saveClusters(clusters, similarityVectors).then(function(res) {
                cleanClusters();
            }, function(err) {
                console.log("cluster save error: ", err);
            });

        };

        var cleanClusters = function() {
            // TODO: logic for removing stale clusters
        };

        var saveClusters = function(clusters, similarityVectors) {
            var promises = Utils.createPromises(clusters.length);
            for (var i = 0; i < clusters.length; i++) {
                var promise = promises[i];

                // if cluster is bigger than 1, save to database
                if(clusters[i].size > 1) {
                    saveCluster(clusters[i], similarityVectors, promise);
                } else {
                    promise.resolve();
                }
            }

            return all(promises);
        };

        var saveCluster = function(cluster, similarityVectors, promise) {
            console.log("CLUSTER:");

            // get index of article doc by similarity vectors
            var indexes = [];
            indexes = recurseCluster(cluster, similarityVectors, indexes);

            // save cluster to database
            var clusterModel = new Cluster();
            var articlePromises = Utils.createPromises(indexes.length);

            // get article doc from index
            for (i = 0; i < indexes.length; i++) {
                var doc = articleDocs[indexes[i]];
                var articlePromise = articlePromises[i];

                // add article to cluster model
                clusterModel.articles.push(doc);

                // add cluster_id to article
                doc.cluster_id = clusterModel;

                // update article
                articleHelpers.updateArticle(doc, articlePromise);

                // print to console
                console.log("  " + doc.title);
            }

            // once articles are update, create new clusters
            all(articlePromises).then(function(res) {
                clusterModel.save(function(err) {
                    if (err) console.log(err);

                    promise.resolve(res);
                });
            }, function(err) {
                promise.reject(err);
            });
        };

        // recursively get article index for each node in cluster
        var recurseCluster = function(cluster, vectors, indexes) {

            // iterate through node properties
            for (var prop in cluster) {
                if (cluster.hasOwnProperty(prop)) {
                    if (prop === "value") {
                        indexes.push(vectors.indexOf(cluster[prop]));
                    } else if (prop === "left" || prop === "right") {
                        indexes = recurseCluster(cluster[prop], vectors, indexes);
                    }
                }
            }

            return indexes;
        };

        // get article summaries
        var getArticleSummaries = function(keywords) {
            var tokenizer = new Tokenizer();

            // TODO: add common abbreviations to database
            var abbreviations = [
                "I.O.U.",
                "M.D.",
                "N.B.",
                "P.O.",
                "U.K.",
                "U.S.",
                "U.S.A.",
                "P.S.",
                "Mr.",
                "Ms.",
                "Mrs.",
                "Dr.",
                "Jr.",
                "Lt.",
                "Gen.",
                "Sgt.",
                "Cpl.",
                "Pvt.",
                "Brig.",
                "Col.",
                "Maj.",
                "Jan.",
                "Feb.",
                "Mar.",
                "Apr.",
                "Aug.",
                "Sept.",
                "Oct.",
                "Nov.",
                "Dec."
            ];

            articleDocs.forEach(function(doc, i, docs) {

                var content = striptags(doc.content);
                var summary = "";
                if (content) {
                    // create ranked array of sentences
                    var ranked = [];
                    var rankedMax = 4;
                    var rankedPrevious = 0;
                    var rankedStart = 0;

                    // tokenize sentences
                    tokenizer.setEntry(content);
                    var tokens = tokenizer.getSentences();

                    // iterate through each token to fix abbreviations and rank
                    var length = tokens.length;
                    tokens.forEach(function(token, i, tokens) {
                        var rank = 0;

                        // split token into basic words and check for abbreviations
                        var words = token.split(" ");
                        if (abbreviations.indexOf(words[words.length - 1]) !== -1) {
                            if (i < (length - 1)) {

                                // move content to next token
                                tokens[i + 1] = tokens[i] + " " + tokens[i + 1];
                                tokens[i] = "";
                                token = "";
                            }
                        }

                        // rank for each keyword across all documents
                        keywords.forEach(function(keyword) {
                            rank += Tfidf.frequency(keyword, content);
                        });

                        // update position in array based on rank
                        // logic assumes earlier sentences are still more important
                        if (token) {
                            if (rank > rankedPrevious) {
                                ranked.splice(rankedStart, 0, token);
                                rankedStart++;
                            } else {
                                ranked.push(token);
                            }
                            rankedPrevious = rank;
                        }
                    });

                    // determine max length
                    if (ranked < rankedMax || i === 0) {
                        rankedMax = ranked.length;
                    }

                    // keep only the top sentences
                    ranked = ranked.slice(0, rankedMax);

                    // join as a string
                    summary = ranked.join(" ... ");
                }

                // add to doc for database save
                doc.summary = summary;
            });
        }

        // determine cosine similartiy of each document in collection based on keyword vectors for each
        var getCollectionSimilarity = function(keywordVectors) {
            var length = keywordVectors.length;

            // create new n-dimensional array
            var vectors = Utils.createNDimArray([length, length]);

            // iterate through each axis of new vector grid and calculate similarity of keywords
            for (var i = 0; i < length; i++) {
                for (var j = 0; j < length; j++) {
                    vectors[i][j] = similarity(keywordVectors[i], keywordVectors[j]);
                }
            }

            return vectors;
        };

        /*
         * Identify they key words in each document based on frequency
         * Identify which words in a document are relevant across the whole collection
         * Algorithm employed to weight keywords is term frequency--inverse document frequency
         */
        var getKeywords = function(doc, collection) {
            // console.log("process article");

            // associative array of words and their frequency
            var wordHash = [];

            var words = _.uniq(Utils.splitWords(doc));
            for (var i = 0, n = words.length; i < n; i++) {
                var word = words[i];
                var tfidf = Tfidf.tfidf(word, doc, collection);
                wordHash.push({
                    word: word,
                    freq: tfidf
                });
            }

            var keywords = identifyKeywords(wordHash);

            return keywords;
        };

        // iterate over hash to pick the most frequent words
        function identifyKeywords(wordHash) {

            // sort by frequency, remove frequency, slice first items
            var keywords = wordHash.sort(function(a, b) {
                if (a.freq > b.freq) {
                    return -1;
                }
                if (a.freq < b.freq) {
                    return 1;
                }

                return 0;
            }).map(function(obj) {
                return obj.word;
            }).slice(0, keywordTotal);

            // console.log("keywords: ", keywords);

            return keywords;
        }

        // handle errors
        var handleError = function(err) {
            console.log("process article error: ", err);
        };

        // initialize article aggregator
        Aggregator.prototype.init = function() {
            console.log("aggregator init");

            Article.find().exec(function(err, docs) {
                if (err) return handleError(err);

                // set global variables
                articleDocs = docs;

                // process all docs
                processDocs();
            });
        };
    };

    return Aggregator;
})();
<|endoftext|>


<commit_before>"use strict";

/*window.requestAnimationFrame = (function () {
 return  window.requestAnimationFrame ||
 window.webkitRequestAnimationFrame ||
 window.mozRequestAnimationFrame ||
 function (callback) {
 window.setTimeout(callback, 1000 / 60);
 };
 })();*/

$(document).ready(function () {
  function Vector(_x, _y) {
    var x = _x, y = _y;

    this.getX = function () {
      return x;
    }
    this.getY = function () {
      return y;
    }
    this.multiply = function (v) {
      return new Vector(x * v.getX(), y * v.getY());
    }
    this.multiplyScalar = function (scalar) {
      return new Vector(x * scalar, y * scalar);
    }
    this.plus = function (v) {
      return new Vector(x + v.getX(), y + v.getY());
    }
    this.minus = function (v) {
      return new Vector(x - v.getX(), y - v.getY());
    }
    this.abs = function () {
      return new Vector(Math.abs(x), Math.abs(y));
    }
    this.toString = function () {
      return "" + x + "/" + y;
    }
  }

  function BigImage(_cropped) {
    var cropped = _cropped,
      croppedImg = cropped.children("img"),
      lastPageX = 0,
      lastPageY = 0,
      moveCallback = null;

    this.onMove = function (callback) {
      moveCallback = callback;
    }

    this.initDrag = function (pageX, pageY) {
      lastPageX = pageX;
      lastPageY = pageY;
    }

    this.drag = function (pageX, pageY) {
      var diffX = lastPageX - pageX;
      var diffY = lastPageY - pageY;

      this.moveX(diffX);
      this.moveY(diffY);

      if (moveCallback !== null) moveCallback(new Vector(diffX, diffY));

      lastPageX = pageX;
      lastPageY = pageY;
    }

    this.moveX = function (delta) {
      var max = this.maxMarginLeft();
      this.move("margin-left", delta, max);
    }

    this.moveY = function (delta) {
      var max = this.maxMarginTop();
      this.move("margin-top", delta, max);
    }

    this.move = function (direction, delta, max) {
      var mt = -parseFloat(croppedImg.css(direction));
      var position = mt + delta;
      this.moveTo(direction, position, max);
    }

    this.moveTo = function (direction, position, max) {
      if (position < 0) position = 0;
      if (position > max) position = max;
      croppedImg.css(direction, -position + "px");
    }

    this.maxMarginTop = function () {
      return croppedImg.height() - cropped.height();
    }

    this.maxMarginLeft = function () {
      return this.fullImageWidth() - cropped.width();
    }

    this.widthVisibleRatio = function () {
      return cropped.width() / this.fullImageWidth();
    }

    this.marginLeftRatio = function () {
      return this.marginLeft() / croppedImg.width();
    }

    this.fullImageWidth = function () {
      return croppedImg.width();
    }

    this.marginLeft = function () {
      return parseFloat(croppedImg.css("margin-left"))
    }
  }

  function Thumbnail(_thumbnail) {
    var thumbnail = _thumbnail;
    var thumbnailImg = thumbnail.children("img");
    var rectangle = thumbnail.children(".rectangle");

    this.updateRectangle = function (bigImage) {
      var widthRatio = bigImage.widthVisibleRatio();
      rectangle.css("height", parseFloat(thumbnailImg.height()) + "px");
      rectangle.css("width", (thumbnailImg.width() * widthRatio) + "px");

      var marginLeftRatio = bigImage.marginLeftRatio();
      rectangle.css("margin-left", -marginLeftRatio * thumbnailImg.width() + "px")
    }

    this.calculateMovementToNewCenter = function (newCenter) {
      var w = rectangle.width();
      var l = parseFloat(rectangle.css("margin-left"));
      var currentRectangleCenter = l + w / 2;
      return newCenter - currentRectangleCenter;
    }
  }

  function AutoDrag(_bigImage, _thumbnail) {
    var _this = this;

    var bigImage = _bigImage;
    var thumbnail = _thumbnail;

    var autoDragEnabled = true;
    var autoDragSpeed = 0.1;

    var marginOffset = 0, timeOffset = 0, lastTime = 0, manualDrag = true;

    this.stopAutoDrag = function () {
      autoDragEnabled = false;
    }

    this.setManuallyDragged = function () {
      manualDrag = true;
    }

    this.initAutoDrag = function () {
      this.setStartParameters();
      if (manualDrag) {
        manualDrag = false;
        marginOffset = -bigImage.marginLeft();
      }

      var drag = function (time) {
        if (autoDragEnabled) requestAnimationFrame(drag);
        _this.autoDrag(time);
      }

      requestAnimationFrame(drag);
    }

    this.setStartParameters = function () {
      var max = bigImage.maxMarginLeft();
      marginOffset = _this.calcT(lastTime, max);
      timeOffset = lastTime;

      var setParams = function (time) {
        timeOffset = time;
      }
      requestAnimationFrame(setParams);
    }

    this.calcT = function (time, max) {
      lastTime = time;
      return (((time - timeOffset) * autoDragSpeed) + marginOffset) % (max * 2);
    }

    this.calculateMarginLeft = function (time, max) {
      var t = this.calcT(time, max);
      if (t > max) t = 2 * max - t;
      var newPosition = Math.abs(t);
      return newPosition;
    }

    this.autoDrag = function (time) {
      if (!autoDragEnabled) return;
      var max = bigImage.maxMarginLeft();
      var newPosition = this.calculateMarginLeft(time, max);
      bigImage.moveTo("margin-left", newPosition, max);
      thumbnail.updateRectangle(bigImage);
    }

    this.play = function () {
      if (autoDragEnabled) return;
      autoDragEnabled = true;
      this.initAutoDrag();
    }

    this.increaseSpeed = function () {
      this.setStartParameters();
      autoDragSpeed *= 1.5;
    }

    this.decreaseSpeed = function () {
      this.setStartParameters();
      autoDragSpeed /= 1.5;
    }
  }

  function Record(_time, _diff) {
    var time = _time, diff = _diff;

    this.getTime = function () {
      return time;
    }
    this.getDiff = function () {
      return diff;
    }
  }

  function Accelerator(_moveImage) {
    var moveImage = _moveImage;
    var recording;
    var records;
    var maxRecordLength = 100;

    // only movements in the last 500 milliseconds are important
    var relevantMovementsTimeAgo = 500;

    this.clear = function () {
      records = [new Record(new Date(), new Vector(0., 0.))];
      recording = false;
    };

    this.recordMove = function (movementVector) {
      if (!recording) return;

      records.push(new Record(new Date(), movementVector));
      if (records.length > maxRecordLength) records.shift();
    };

    this.calculateMovement = function () {
      var d = new Date();
      var v = new Vector(0, 0);
      var oldest = d;

      // could make this faster...
      records.forEach(function (el) {
        if (d - el.getTime() <= relevantMovementsTimeAgo) {
          v = v.plus(el.getDiff());
          if (el.getTime() < oldest) oldest = el.getTime();
        }
      });

      var quotient = d - oldest;
      if (quotient == 0) return v;

      // multiply to normalize => movement in pixels per millisecond
      return v.multiplyScalar(1. / quotient);
    }

    this.startRecording = function () {
      this.clear();
      recording = true;
    };

    this.play = function () {
      if (recording === false) return;

      recording = false;

      var epsilon = .03;
      var friction = 1 - 1 / 25;
      var acceleration = this.calculateMovement();
      var lastMove = new Date();

      var calcSpeed = function (vector) {
        return Math.sqrt(vector.getX() * vector.getX() + vector.getY() * vector.getY());
      };

      var updateLocation = function (vector) {
        var d = new Date();
        var diff = d - lastMove;

        moveImage(vector.getX() * diff, vector.getY() * diff);
        lastMove = d;
      };

      var tick = function () {
        if (calcSpeed(acceleration) > epsilon && !recording) requestAnimationFrame(tick);
        updateLocation(acceleration);
        acceleration = acceleration.multiplyScalar(friction);
      };
      tick();

      // TODO: play / accelerate image
    };
  }

  function PanoramaWidget() {
    var widget = this;
    var firstDrag = true;

    var body = $("body");

    var slider = $(".image-slider");

    var cropped = slider.children(".cropped");
    var bigImage = new BigImage(cropped);

    var thumbnailOuterDiv = slider.children(".thumbnail");
    var thumbnailDiv = thumbnailOuterDiv.children(".thumbnail-inner");
    var thumbnailImg = thumbnailDiv.children("img");
    var rectangle = thumbnailDiv.children(".rectangle");
    var thumbnail = new Thumbnail(thumbnailDiv);

    var autoDrag = new AutoDrag(bigImage, thumbnail);

    var accelerator = new Accelerator(function (x, y) {
      bigImage.moveX(x);
      bigImage.moveY(y);
      thumbnail.updateRectangle(bigImage);
    });
    bigImage.onMove(function (vector) {
      accelerator.recordMove(vector);
    });

    this.isTouchEvent = function (event) {
      return event.originalEvent && event.originalEvent.touches && event.originalEvent.touches.length > 0;
    }
    this.isSingleTouchEvent = function (event) {
      return event.originalEvent && event.originalEvent.touches && event.originalEvent.touches.length == 1;
    }
    this.isDoubleTouchEvent = function (event) {
      return event.originalEvent && event.originalEvent.touches && event.originalEvent.touches.length == 2;
    }
    this.convertSingleTouchEvent = function (event) {
      return event.originalEvent.touches[0];
    }

    this.registerEvents = function () {
      var manualDragBig = function (event) {
        var dragFunction = function (e) {
          if (firstDrag) {
            bigImage.initDrag(e.pageX, e.pageY);
            firstDrag = false;
          }
          bigImage.drag(e.pageX, e.pageY);
          thumbnail.updateRectangle(bigImage);

          autoDrag.setManuallyDragged();
        };

        var zoomFunction = function (event) {

        };

        if (!widget.isTouchEvent(event)) dragFunction(event);
        else if (widget.isSingleTouchEvent(event)) {
          dragFunction(widget.convertSingleTouchEvent(event));
        }
        else if (widget.isDoubleTouchEvent(event)) zoomFunction(event);
        else return;

        event.preventDefault();
      };

      var manualDragThumbnail = function (_event) {
        var event = _event;

        if (widget.isSingleTouchEvent(event)) event = widget.convertSingleTouchEvent(event);
        else if (widget.isTouchEvent(event)) return;

        _event.preventDefault();

        var pageX = event.pageX;
        pageX *= -bigImage.fullImageWidth() / thumbnailImg.width();
        // fullImageWidth
        if (firstDrag) {
          bigImage.initDrag(pageX, 0);
          firstDrag = false;
        }
        bigImage.drag(pageX, 0);
        thumbnail.updateRectangle(bigImage);

        autoDrag.setManuallyDragged();
      }

      slider.on("dragstart", function (e) {
        e.preventDefault();
      });

      var registerDrawEvent = function (element, f, startEvent, moveEvent) {
        element.on(startEvent, function () {
          body.on(moveEvent, f);
          element.addClass("grabbing");
          firstDrag = true;
          autoDrag.stopAutoDrag();
          accelerator.startRecording();
        });
      }

      var registerStopDrag = function (element, f, startEvent, moveEvent) {
        // could also use mouseout, then we wouldn't need to register the body events
        body.on(startEvent, function () {
          //if (!element.hasClass("grabbing")) return;
          body.off(moveEvent, f);
          element.removeClass("grabbing");
          firstDrag = false;
          accelerator.play();
        });
      }

      registerDrawEvent(cropped, manualDragBig, "mousedown", "mousemove");
      registerDrawEvent(cropped, manualDragBig, "touchstart", "touchmove");
      registerDrawEvent(rectangle, manualDragThumbnail, "mousedown", "mousemove");
      registerDrawEvent(rectangle, manualDragThumbnail, "touchstart", "touchmove");

      registerStopDrag(cropped, manualDragBig, "mouseup", "mousemove");
      registerStopDrag(cropped, manualDragBig, "touchend", "touchmove");
      registerStopDrag(rectangle, manualDragThumbnail, "mouseup", "mousemove");
      registerStopDrag(rectangle, manualDragThumbnail, "touchend", "touchmove");

      var clickFunction = function (offsetX) {
        autoDrag.stopAutoDrag();

        var newCenter = thumbnail.calculateMovementToNewCenter(offsetX);
        newCenter *= -bigImage.fullImageWidth() / thumbnailImg.width();

        bigImage.initDrag(0, 0);
        bigImage.drag(newCenter, 0);

        thumbnail.updateRectangle(bigImage);

        autoDrag.setManuallyDragged();
      }

      thumbnailImg.on("click ", function (event) {
        event.preventDefault();
        clickFunction(event.offsetX);
      });

      thumbnailImg.on("touchstart ", function (_event) {
        var event = _event;

        if (!widget.isSingleTouchEvent(event)) return;

        _event.preventDefault();
        event = widget.convertSingleTouchEvent(event);

        var offsetX = event.pageX - event.target.x;
        clickFunction(offsetX);
      });

      $(window).on("resize", function () {
        thumbnail.updateRectangle(bigImage);
      });
    }

    this.registerControls = function () {
      $("a[href=#play]").on("click touchstart", function (e) {
        e.preventDefault();
        autoDrag.play();
      });

      $("a[href=#stop]").on("click touchstart", function (e) {
        e.preventDefault();
        autoDrag.stopAutoDrag();
      });

      $("a[href=#faster]").on("click touchstart", function (e) {
        e.preventDefault();
        autoDrag.increaseSpeed();
      });

      $("a[href=#slower]").on("click touchstart", function (e) {
        e.preventDefault();
        autoDrag.decreaseSpeed();
      });
    }

    this.initAutoDrag = function () {
      autoDrag.initAutoDrag();
    }

  }

  var w = new PanoramaWidget();
  w.registerEvents();
  w.registerControls();
  w.initAutoDrag();

});
<commit_msg>minor bug<commit_after>"use strict";

/*window.requestAnimationFrame = (function () {
 return  window.requestAnimationFrame ||
 window.webkitRequestAnimationFrame ||
 window.mozRequestAnimationFrame ||
 function (callback) {
 window.setTimeout(callback, 1000 / 60);
 };
 })();*/

$(document).ready(function () {
  function Vector(_x, _y) {
    var x = _x, y = _y;

    this.getX = function () {
      return x;
    }
    this.getY = function () {
      return y;
    }
    this.multiply = function (v) {
      return new Vector(x * v.getX(), y * v.getY());
    }
    this.multiplyScalar = function (scalar) {
      return new Vector(x * scalar, y * scalar);
    }
    this.plus = function (v) {
      return new Vector(x + v.getX(), y + v.getY());
    }
    this.minus = function (v) {
      return new Vector(x - v.getX(), y - v.getY());
    }
    this.abs = function () {
      return new Vector(Math.abs(x), Math.abs(y));
    }
    this.toString = function () {
      return "" + x + "/" + y;
    }
  }

  function BigImage(_cropped) {
    var cropped = _cropped,
      croppedImg = cropped.children("img"),
      lastPageX = 0,
      lastPageY = 0,
      moveCallback = null;

    this.onMove = function (callback) {
      moveCallback = callback;
    }

    this.initDrag = function (pageX, pageY) {
      lastPageX = pageX;
      lastPageY = pageY;
    }

    this.drag = function (pageX, pageY) {
      var diffX = lastPageX - pageX;
      var diffY = lastPageY - pageY;

      this.moveX(diffX);
      this.moveY(diffY);

      if (moveCallback !== null) moveCallback(new Vector(diffX, diffY));

      lastPageX = pageX;
      lastPageY = pageY;
    }

    this.moveX = function (delta) {
      var max = this.maxMarginLeft();
      this.move("margin-left", delta, max);
    }

    this.moveY = function (delta) {
      var max = this.maxMarginTop();
      this.move("margin-top", delta, max);
    }

    this.move = function (direction, delta, max) {
      var mt = -parseFloat(croppedImg.css(direction));
      var position = mt + delta;
      this.moveTo(direction, position, max);
    }

    this.moveTo = function (direction, position, max) {
      if (position < 0) position = 0;
      if (position > max) position = max;
      croppedImg.css(direction, -position + "px");
    }

    this.maxMarginTop = function () {
      return croppedImg.height() - cropped.height();
    }

    this.maxMarginLeft = function () {
      return this.fullImageWidth() - cropped.width();
    }

    this.widthVisibleRatio = function () {
      return cropped.width() / this.fullImageWidth();
    }

    this.marginLeftRatio = function () {
      return this.marginLeft() / croppedImg.width();
    }

    this.fullImageWidth = function () {
      return croppedImg.width();
    }

    this.marginLeft = function () {
      return parseFloat(croppedImg.css("margin-left"))
    }
  }

  function Thumbnail(_thumbnail) {
    var thumbnail = _thumbnail;
    var thumbnailImg = thumbnail.children("img");
    var rectangle = thumbnail.children(".rectangle");

    this.updateRectangle = function (bigImage) {
      var widthRatio = bigImage.widthVisibleRatio();
      rectangle.css("height", parseFloat(thumbnailImg.height()) + "px");
      rectangle.css("width", (thumbnailImg.width() * widthRatio) + "px");

      var marginLeftRatio = bigImage.marginLeftRatio();
      rectangle.css("margin-left", -marginLeftRatio * thumbnailImg.width() + "px")
    }

    this.calculateMovementToNewCenter = function (newCenter) {
      var w = rectangle.width();
      var l = parseFloat(rectangle.css("margin-left"));
      var currentRectangleCenter = l + w / 2;
      return newCenter - currentRectangleCenter;
    }
  }

  function AutoDrag(_bigImage, _thumbnail) {
    var _this = this;

    var bigImage = _bigImage;
    var thumbnail = _thumbnail;

    var autoDragEnabled = true;
    var autoDragSpeed = 0.1;

    var marginOffset = 0, timeOffset = 0, lastTime = 0, manualDrag = true;

    this.stopAutoDrag = function () {
      autoDragEnabled = false;
    }

    this.setManuallyDragged = function () {
      manualDrag = true;
    }

    this.initAutoDrag = function () {
      this.setStartParameters();
      if (manualDrag) {
        manualDrag = false;
        marginOffset = -bigImage.marginLeft();
      }

      var drag = function (time) {
        if (autoDragEnabled) requestAnimationFrame(drag);
        _this.autoDrag(time);
      }

      requestAnimationFrame(drag);
    }

    this.setStartParameters = function () {
      var max = bigImage.maxMarginLeft();
      marginOffset = _this.calcT(lastTime, max);
      timeOffset = lastTime;

      var setParams = function (time) {
        timeOffset = time;
      }
      requestAnimationFrame(setParams);
    }

    this.calcT = function (time, max) {
      lastTime = time;
      return (((time - timeOffset) * autoDragSpeed) + marginOffset) % (max * 2);
    }

    this.calculateMarginLeft = function (time, max) {
      var t = this.calcT(time, max);
      if (t > max) t = 2 * max - t;
      var newPosition = Math.abs(t);
      return newPosition;
    }

    this.autoDrag = function (time) {
      if (!autoDragEnabled) return;
      var max = bigImage.maxMarginLeft();
      var newPosition = this.calculateMarginLeft(time, max);
      bigImage.moveTo("margin-left", newPosition, max);
      thumbnail.updateRectangle(bigImage);
    }

    this.play = function () {
      if (autoDragEnabled) return;
      autoDragEnabled = true;
      this.initAutoDrag();
    }

    this.increaseSpeed = function () {
      this.setStartParameters();
      autoDragSpeed *= 1.5;
    }

    this.decreaseSpeed = function () {
      this.setStartParameters();
      autoDragSpeed /= 1.5;
    }
  }

  function Record(_time, _diff) {
    var time = _time, diff = _diff;

    this.getTime = function () {
      return time;
    }
    this.getDiff = function () {
      return diff;
    }
  }

  function Accelerator(_moveImage) {
    var moveImage = _moveImage;
    var recording;
    var records;
    var maxRecordLength = 100;

    // only movements in the last 500 milliseconds are important
    var relevantMovementsTimeAgo = 500;

    this.clear = function () {
      records = [new Record(new Date(), new Vector(0., 0.))];
      recording = false;
    };

    this.recordMove = function (movementVector) {
      if (!recording) return;

      records.push(new Record(new Date(), movementVector));
      if (records.length > maxRecordLength) records.shift();
    };

    this.calculateMovement = function () {
      var d = new Date();
      var v = new Vector(0, 0);
      var oldest = d;

      // could make this faster...
      records.forEach(function (el) {
        if (d - el.getTime() <= relevantMovementsTimeAgo) {
          v = v.plus(el.getDiff());
          if (el.getTime() < oldest) oldest = el.getTime();
        }
      });

      var quotient = d - oldest;
      if (quotient == 0) return v;

      // multiply to normalize => movement in pixels per millisecond
      return v.multiplyScalar(1. / quotient);
    }

    this.startRecording = function () {
      this.clear();
      recording = true;
    };

    this.play = function () {
      if (recording === false) return;

      recording = false;

      var epsilon = .03;
      var friction = 1 - 1 / 25;
      var acceleration = this.calculateMovement();
      var lastMove = new Date();

      var calcSpeed = function (vector) {
        return Math.sqrt(vector.getX() * vector.getX() + vector.getY() * vector.getY());
      };

      var updateLocation = function (vector) {
        var d = new Date();
        var diff = d - lastMove;

        moveImage(vector.getX() * diff, vector.getY() * diff);
        lastMove = d;
      };

      var tick = function () {
        if (calcSpeed(acceleration) > epsilon && !recording) requestAnimationFrame(tick);
        updateLocation(acceleration);
        acceleration = acceleration.multiplyScalar(friction);
      };
      tick();

      // TODO: play / accelerate image
    };

    this.clear();
  }

  function PanoramaWidget() {
    var widget = this;
    var firstDrag = true;

    var body = $("body");

    var slider = $(".image-slider");

    var cropped = slider.children(".cropped");
    var bigImage = new BigImage(cropped);

    var thumbnailOuterDiv = slider.children(".thumbnail");
    var thumbnailDiv = thumbnailOuterDiv.children(".thumbnail-inner");
    var thumbnailImg = thumbnailDiv.children("img");
    var rectangle = thumbnailDiv.children(".rectangle");
    var thumbnail = new Thumbnail(thumbnailDiv);

    var autoDrag = new AutoDrag(bigImage, thumbnail);

    var accelerator = new Accelerator(function (x, y) {
      bigImage.moveX(x);
      bigImage.moveY(y);
      thumbnail.updateRectangle(bigImage);
    });
    bigImage.onMove(function (vector) {
      accelerator.recordMove(vector);
    });

    this.isTouchEvent = function (event) {
      return event.originalEvent && event.originalEvent.touches && event.originalEvent.touches.length > 0;
    }
    this.isSingleTouchEvent = function (event) {
      return event.originalEvent && event.originalEvent.touches && event.originalEvent.touches.length == 1;
    }
    this.isDoubleTouchEvent = function (event) {
      return event.originalEvent && event.originalEvent.touches && event.originalEvent.touches.length == 2;
    }
    this.convertSingleTouchEvent = function (event) {
      return event.originalEvent.touches[0];
    }

    this.registerEvents = function () {
      var manualDragBig = function (event) {
        var dragFunction = function (e) {
          if (firstDrag) {
            bigImage.initDrag(e.pageX, e.pageY);
            firstDrag = false;
          }
          bigImage.drag(e.pageX, e.pageY);
          thumbnail.updateRectangle(bigImage);

          autoDrag.setManuallyDragged();
        };

        var zoomFunction = function (event) {

        };

        if (!widget.isTouchEvent(event)) dragFunction(event);
        else if (widget.isSingleTouchEvent(event)) {
          dragFunction(widget.convertSingleTouchEvent(event));
        }
        else if (widget.isDoubleTouchEvent(event)) zoomFunction(event);
        else return;

        event.preventDefault();
      };

      var manualDragThumbnail = function (_event) {
        var event = _event;

        if (widget.isSingleTouchEvent(event)) event = widget.convertSingleTouchEvent(event);
        else if (widget.isTouchEvent(event)) return;

        _event.preventDefault();

        var pageX = event.pageX;
        pageX *= -bigImage.fullImageWidth() / thumbnailImg.width();
        // fullImageWidth
        if (firstDrag) {
          bigImage.initDrag(pageX, 0);
          firstDrag = false;
        }
        bigImage.drag(pageX, 0);
        thumbnail.updateRectangle(bigImage);

        autoDrag.setManuallyDragged();
      }

      slider.on("dragstart", function (e) {
        e.preventDefault();
      });

      var registerDrawEvent = function (element, f, startEvent, moveEvent) {
        element.on(startEvent, function () {
          body.on(moveEvent, f);
          element.addClass("grabbing");
          firstDrag = true;
          autoDrag.stopAutoDrag();
          accelerator.startRecording();
        });
      }

      var registerStopDrag = function (element, f, startEvent, moveEvent) {
        // could also use mouseout, then we wouldn't need to register the body events
        body.on(startEvent, function () {
          //if (!element.hasClass("grabbing")) return;
          body.off(moveEvent, f);
          element.removeClass("grabbing");
          firstDrag = false;
          accelerator.play();
        });
      }

      registerDrawEvent(cropped, manualDragBig, "mousedown", "mousemove");
      registerDrawEvent(cropped, manualDragBig, "touchstart", "touchmove");
      registerDrawEvent(rectangle, manualDragThumbnail, "mousedown", "mousemove");
      registerDrawEvent(rectangle, manualDragThumbnail, "touchstart", "touchmove");

      registerStopDrag(cropped, manualDragBig, "mouseup", "mousemove");
      registerStopDrag(cropped, manualDragBig, "touchend", "touchmove");
      registerStopDrag(rectangle, manualDragThumbnail, "mouseup", "mousemove");
      registerStopDrag(rectangle, manualDragThumbnail, "touchend", "touchmove");

      var clickFunction = function (offsetX) {
        autoDrag.stopAutoDrag();

        var newCenter = thumbnail.calculateMovementToNewCenter(offsetX);
        newCenter *= -bigImage.fullImageWidth() / thumbnailImg.width();

        bigImage.initDrag(0, 0);
        bigImage.drag(newCenter, 0);

        thumbnail.updateRectangle(bigImage);

        autoDrag.setManuallyDragged();
      }

      thumbnailImg.on("click ", function (event) {
        event.preventDefault();
        clickFunction(event.offsetX);
      });

      thumbnailImg.on("touchstart ", function (_event) {
        var event = _event;

        if (!widget.isSingleTouchEvent(event)) return;

        _event.preventDefault();
        event = widget.convertSingleTouchEvent(event);

        var offsetX = event.pageX - event.target.x;
        clickFunction(offsetX);
      });

      $(window).on("resize", function () {
        thumbnail.updateRectangle(bigImage);
      });
    }

    this.registerControls = function () {
      $("a[href=#play]").on("click touchstart", function (e) {
        e.preventDefault();
        autoDrag.play();
      });

      $("a[href=#stop]").on("click touchstart", function (e) {
        e.preventDefault();
        autoDrag.stopAutoDrag();
      });

      $("a[href=#faster]").on("click touchstart", function (e) {
        e.preventDefault();
        autoDrag.increaseSpeed();
      });

      $("a[href=#slower]").on("click touchstart", function (e) {
        e.preventDefault();
        autoDrag.decreaseSpeed();
      });
    }

    this.initAutoDrag = function () {
      autoDrag.initAutoDrag();
    }

  }

  var w = new PanoramaWidget();
  w.registerEvents();
  w.registerControls();
  w.initAutoDrag();

});
<|endoftext|>


<commit_before>angular.module('app', [
  'app.models',
  'app.services',
  'app.landing',
  'app.setup'
]);<commit_msg>depend on newly broken out modules<commit_after>angular.module('app', [
  'app.models',
  'app.services',
  'app.landing',
  'app.setup',
  'app.round',
  'app.scoreboard',
  'app.saved-games'
]);<|endoftext|>