<commit_before>var path = require('path');
var crypto = require('crypto');
var Promise = require('bluebird');
var redis = Promise.promisifyAll(require('redis'));
var through2 = require('through2');
var utils = require('yomypopcorn-utils');

exports = module.exports = db;

var padTime = utils.padTime;
var removeNonScalars = utils.removeNonScalars;

function db (options) {
  options = options || {};

  if (!options.socket) {
    options.host = options.host || '127.0.0.1';
    options.port = parseInt(options.port, 10) || 6379;
  }

  var redisOptions = {};
  if (options.password) { redisOptions.auth_pass = options.password; }

  var client;

  if (options.socket) {
    client = redis.createClient(options.socket, redisOptions);
  } else {
    client = redis.createClient(options.port, options.host, redisOptions);
  }

  function makeShowKey (showId) {
    if (typeof showId === 'object') { showId = showId.id; }
    return 'show:' + showId;
  }

  function makeEpisodeKey (showId, episodeSien) {
    if (typeof episodeSien === 'object') { episodeSien = episodeSien.sien; }
    if (typeof episodeSien === 'undefined') { episodeSien = 'latest'; }

    return makeShowKey(showId) + ':episode:' + episodeSien;
  }

  function makeEpisodesKey (showId) {
    return makeShowKey(showId) + ':episodes';
  }

  function makeSubscriptionsKey (userId) {
    return makeUserKey(userId) + ':subscriptions';
  }

  function makeSubscribersKey (showId) {
    return makeShowKey(showId) + ':subscribers'
  }

  function makeUserKey (userId) {
    return 'user:' + userId
  }

  function makeFeedKey(userId) {
    return makeUserKey(userId) + ':feed';
  }

  function makeLogKey (type, time) {
    return 'log:' + type + ':' + padTime(time);
  }

  function makeActiveShowsKey () {
    return 'shows:active';
  }

  function makeInactiveShowsKey () {
    return 'shows:inactive';
  }

  function close () {
    client.quit();
  }

  function addTimestamp (obj) {
    return getTime()
      .then(function (time) {
        obj.timestamp = time;
        return obj;
      });
  }

  function saveShow (show, callback) {
    var saveDetails = function (show) {
      var key = makeShowKey(show);
      show = removeNonScalars(show);

      return client.hmsetAsync(key, show)
        .return(show);
    };

    var removeFromWrongSet = function (show) {
      var remKey = show.active ? makeInactiveShowsKey() : makeActiveShowsKey();

      return client.sremAsync(remKey, show.id)
        .return(show);
    };

    var addToRightSet = function (show) {
      var addKey = show.active ? makeActiveShowsKey() : makeInactiveShowsKey();

      return client.saddAsync(addKey, show.id)
        .return(show);
    };

    return addTimestamp(show)
      .then(saveDetails)
      .then(removeFromWrongSet)
      .then(addToRightSet)
      .nodeify(callback);
  }

  function saveEpisode (showId, episode, callback) {
    showId = typeof showId === 'object' ? showId.id : showId;

    var addShowId = function (showId) {
      return function (episode) {
        episode.show_id = showId;
        return episode;
      };
    };

    var saveDetails = function (episode) {
      var key = makeEpisodeKey(episode.show_id, episode);

      return client.existsAsync(key)
        .then(function (exists) {
          if (exists) { return episode; }

          return client.hmsetAsync(key, episode)
            .return(episode);
        });
    };

    var addToSet = function (episode) {
      var episodeKey = makeEpisodeKey(episode.show_id, episode);
      var key = makeEpisodesKey(showId);

      return client.saddAsync(key, episodeKey)
        .return(episode);
    };

    return Promise.resolve(episode)
      .then(addShowId(showId))
      .then(addTimestamp)
      .then(saveDetails)
      .then(addToSet)
      .nodeify(callback);
  }

  function saveLatestEpisode (showId, episodeSien, callback) {
    var key = makeEpisodeKey(showId);
    var episodeKey = makeEpisodeKey(showId, episodeSien);

    return client.hgetallAsync(episodeKey)
      .then(function (episode) {
        return client.hmsetAsync(key, episode)
          .return(episode);
      })
      .nodeify(callback);
  }

  function getShow (showId, callback) {
    var key = makeShowKey(showId);

    return client.hgetallAsync(key)
      .nodeify(callback);
  }

  function getLatestEpisode (showId, callback) {
    var key = makeEpisodeKey(showId);

    return client.hgetallAsync(key)
      .nodeify(callback);
  }

  function getTime (callback) {
    return client.timeAsync()
      .then(function (t) {
        return (t[0] * 1000) + Math.round(t[1] / 1000);
      })
      .nodeify(callback);
  }

  function logScan (callback) {
    var writeLog = function (time) {
      var key = 'latest_scan:start';
      return client.setAsync(key, time);
    };

    return getTime()
      .then(writeLog)
      .nodeify(callback);
  }

  function getActiveShowIds (callback) {
    return client.smembersAsync(makeActiveShowsKey())
      .nodeify(callback);
  }

  function getInactiveShowIds (callback) {
    return client.smembersAsync(makeInactiveShowsKey())
      .nodeify(callback);
  }

  function createActiveShowsStream () {
    var stream = through2.obj();

    function writeData () {
      getActiveShowIds(function (err, showIds) {
        if (err) { return stream.end(); }

        showIds.forEach(function (showId) {
          stream.write({
            imdb_id: showId
          });
        });

        stream.end();
      });
    }

    process.nextTick(writeData);

    return stream;
  }

  function log (type, data, callback) {
    var writeLog = function (time) {
      var key = makeLogKey(type, time);
      data.time = time;

      return client.hmsetAsync(key, data);
    };

    return getTime()
      .then(writeLog)
      .nodeify(callback);
  }

  function getSubscriptions (userId, callback) {
    var key = makeSubscriptionsKey(userId);

    return client.smembersAsync(key)
      .nodeify(callback);
  }

  function getSubscribers (showId, callback) {
    var key = makeSubscribersKey(showId);

    return client.smembersAsync(key)
      .nodeify(callback);
  }

  function subscribeShow (userId, showId, callback) {
    var subscribe = function () {
      var subscriptionsKey = makeSubscriptionsKey(userId);
      var subscribersKey = makeSubscribersKey(showId);

      var multi = Promise.promisifyAll(client.multi());

      multi.sadd(subscribersKey, userId);
      multi.sadd(subscriptionsKey, showId);

      return multi.execAsync();
    };

    return subscribe(userId, showId)
      .then(addLatestEpisodeToFeed.bind(null, userId, showId))
      .nodeify(callback);
  }

  function unsubscribeShow (userId, showId, callback) {
    var unsubscribe = function (userId, showId) {
      var subscriptionsKey = makeSubscriptionsKey(userId);
      var subscribersKey = makeSubscribersKey(showId);

      var multi = Promise.promisifyAll(client.multi());

      multi.srem(subscribersKey, userId);
      multi.srem(subscriptionsKey, showId);

      return multi.execAsync();
    };

    return unsubscribe(userId, showId)
      .then(removeShowFromFeed.bind(null, userId, showId))
      .nodeify(callback);
  }

  function removeShowFromFeed (userId, showId, callback) {
    var feedKey = makeFeedKey(userId);
    var showKey = makeShowKey(showId);

    client.smembersAsync(feedKey)
      .then(function (episodeKeys) {
        var multi = Promise.promisifyAll(client.multi());

        episodeKeys = episodeKeys.filter(function (key) {
          return key.indexOf(showKey) !== -1;
        });

        episodeKeys.forEach(function (episodeKey) {
          multi.srem(feedKey, episodeKey);
        });

        return multi.execAsync();
      })
      .nodeify(callback);
  }

  function addLatestEpisodeToFeed (userId, show, callback) {
    return getLatestEpisode(show)
      .then(function (episode) {
        if (!episode) { return Promise.reject(); }

        var episodeKey = makeEpisodeKey(show, episode);
        var feedKey = makeFeedKey(userId);

        return client.saddAsync(feedKey, episodeKey);
      })
      .nodeify(callback);
  }

  function getFeed(userId, callback) {
    var getFeedItemKeys = function (userId) {
      var feedKey = makeFeedKey(userId);

      return client.smembersAsync(feedKey);
    };

    var getEpisodeByKey = function (episodeKey) {
      return client.hgetallAsync(episodeKey);
    };

    var getFeedItem = function (episodeKey) {
      return getEpisodeByKey(episodeKey)
        .then(function (episode) {
          return getShow(episode.show_id)
            .then(function (show) {
              return {
                show_id: show.id,
                imdb_id: show.imdb_id,
                title: show.title,
                episide_title: episode.title,
                sien: episode.sien,
                season: episode.season,
                episode: episode.episode,
                poster: show.poster,
                first_aired: episode.first_aired,
                timestamp: episode.timestamp
              };
            });
        });
    };

    var filterAndSort = function (feedItems) {
      return feedItems
        .filter(function (result) {
          return !!result;
        })
        .sort(function (a, b) {
          var ats = a.timestamp || a.first_aired;
          var bts = b.timestamp || b.first_aired;
          return bts - ats;
        });
    };

    return getFeedItemKeys(userId)
      .map(getFeedItem)
      .then(filterAndSort)
      .nodeify(callback);
  }

  function createToken (length, callback) {
    return new Promise(function (resolve, reject) {
      crypto.randomBytes(length, function (err, token) {
        if (err) { return reject(err); }
        if (!token) { return reject(new Error('Failed to generate token')); }

        resolve(token.toString('hex'));
      });
    }).nodeify(callback);
  }

  return {
    saveShow: saveShow,
    saveEpisode: saveEpisode,
    saveLatestEpisode: saveLatestEpisode,
    getShow: getShow,
    getLatestEpisode: getLatestEpisode,
    getActiveShowIds: getActiveShowIds,
    getInactiveShowIds: getInactiveShowIds,
    createActiveShowsStream: createActiveShowsStream,
    log: log,
    getSubscribers: getSubscribers,
    getSubscriptions: getSubscriptions,
    subscribeShow: subscribeShow,
    unsubscribeShow: unsubscribeShow,
    addLatestEpisodeToFeed: addLatestEpisodeToFeed,
    getFeed: getFeed,
    getTime: getTime,
    logScan: logScan,
    close: close
  };
}
<commit_msg>addLatestEpisodeToFeed: don't reject promise when there is no latest episode. fixes yomypopcorn/yo-service#31<commit_after>var path = require('path');
var crypto = require('crypto');
var Promise = require('bluebird');
var redis = Promise.promisifyAll(require('redis'));
var through2 = require('through2');
var utils = require('yomypopcorn-utils');

exports = module.exports = db;

var padTime = utils.padTime;
var removeNonScalars = utils.removeNonScalars;

function db (options) {
  options = options || {};

  if (!options.socket) {
    options.host = options.host || '127.0.0.1';
    options.port = parseInt(options.port, 10) || 6379;
  }

  var redisOptions = {};
  if (options.password) { redisOptions.auth_pass = options.password; }

  var client;

  if (options.socket) {
    client = redis.createClient(options.socket, redisOptions);
  } else {
    client = redis.createClient(options.port, options.host, redisOptions);
  }

  function makeShowKey (showId) {
    if (typeof showId === 'object') { showId = showId.id; }
    return 'show:' + showId;
  }

  function makeEpisodeKey (showId, episodeSien) {
    if (typeof episodeSien === 'object') { episodeSien = episodeSien.sien; }
    if (typeof episodeSien === 'undefined') { episodeSien = 'latest'; }

    return makeShowKey(showId) + ':episode:' + episodeSien;
  }

  function makeEpisodesKey (showId) {
    return makeShowKey(showId) + ':episodes';
  }

  function makeSubscriptionsKey (userId) {
    return makeUserKey(userId) + ':subscriptions';
  }

  function makeSubscribersKey (showId) {
    return makeShowKey(showId) + ':subscribers'
  }

  function makeUserKey (userId) {
    return 'user:' + userId
  }

  function makeFeedKey(userId) {
    return makeUserKey(userId) + ':feed';
  }

  function makeLogKey (type, time) {
    return 'log:' + type + ':' + padTime(time);
  }

  function makeActiveShowsKey () {
    return 'shows:active';
  }

  function makeInactiveShowsKey () {
    return 'shows:inactive';
  }

  function close () {
    client.quit();
  }

  function addTimestamp (obj) {
    return getTime()
      .then(function (time) {
        obj.timestamp = time;
        return obj;
      });
  }

  function saveShow (show, callback) {
    var saveDetails = function (show) {
      var key = makeShowKey(show);
      show = removeNonScalars(show);

      return client.hmsetAsync(key, show)
        .return(show);
    };

    var removeFromWrongSet = function (show) {
      var remKey = show.active ? makeInactiveShowsKey() : makeActiveShowsKey();

      return client.sremAsync(remKey, show.id)
        .return(show);
    };

    var addToRightSet = function (show) {
      var addKey = show.active ? makeActiveShowsKey() : makeInactiveShowsKey();

      return client.saddAsync(addKey, show.id)
        .return(show);
    };

    return addTimestamp(show)
      .then(saveDetails)
      .then(removeFromWrongSet)
      .then(addToRightSet)
      .nodeify(callback);
  }

  function saveEpisode (showId, episode, callback) {
    showId = typeof showId === 'object' ? showId.id : showId;

    var addShowId = function (showId) {
      return function (episode) {
        episode.show_id = showId;
        return episode;
      };
    };

    var saveDetails = function (episode) {
      var key = makeEpisodeKey(episode.show_id, episode);

      return client.existsAsync(key)
        .then(function (exists) {
          if (exists) { return episode; }

          return client.hmsetAsync(key, episode)
            .return(episode);
        });
    };

    var addToSet = function (episode) {
      var episodeKey = makeEpisodeKey(episode.show_id, episode);
      var key = makeEpisodesKey(showId);

      return client.saddAsync(key, episodeKey)
        .return(episode);
    };

    return Promise.resolve(episode)
      .then(addShowId(showId))
      .then(addTimestamp)
      .then(saveDetails)
      .then(addToSet)
      .nodeify(callback);
  }

  function saveLatestEpisode (showId, episodeSien, callback) {
    var key = makeEpisodeKey(showId);
    var episodeKey = makeEpisodeKey(showId, episodeSien);

    return client.hgetallAsync(episodeKey)
      .then(function (episode) {
        return client.hmsetAsync(key, episode)
          .return(episode);
      })
      .nodeify(callback);
  }

  function getShow (showId, callback) {
    var key = makeShowKey(showId);

    return client.hgetallAsync(key)
      .nodeify(callback);
  }

  function getLatestEpisode (showId, callback) {
    var key = makeEpisodeKey(showId);

    return client.hgetallAsync(key)
      .nodeify(callback);
  }

  function getTime (callback) {
    return client.timeAsync()
      .then(function (t) {
        return (t[0] * 1000) + Math.round(t[1] / 1000);
      })
      .nodeify(callback);
  }

  function logScan (callback) {
    var writeLog = function (time) {
      var key = 'latest_scan:start';
      return client.setAsync(key, time);
    };

    return getTime()
      .then(writeLog)
      .nodeify(callback);
  }

  function getActiveShowIds (callback) {
    return client.smembersAsync(makeActiveShowsKey())
      .nodeify(callback);
  }

  function getInactiveShowIds (callback) {
    return client.smembersAsync(makeInactiveShowsKey())
      .nodeify(callback);
  }

  function createActiveShowsStream () {
    var stream = through2.obj();

    function writeData () {
      getActiveShowIds(function (err, showIds) {
        if (err) { return stream.end(); }

        showIds.forEach(function (showId) {
          stream.write({
            imdb_id: showId
          });
        });

        stream.end();
      });
    }

    process.nextTick(writeData);

    return stream;
  }

  function log (type, data, callback) {
    var writeLog = function (time) {
      var key = makeLogKey(type, time);
      data.time = time;

      return client.hmsetAsync(key, data);
    };

    return getTime()
      .then(writeLog)
      .nodeify(callback);
  }

  function getSubscriptions (userId, callback) {
    var key = makeSubscriptionsKey(userId);

    return client.smembersAsync(key)
      .nodeify(callback);
  }

  function getSubscribers (showId, callback) {
    var key = makeSubscribersKey(showId);

    return client.smembersAsync(key)
      .nodeify(callback);
  }

  function subscribeShow (userId, showId, callback) {
    var subscribe = function () {
      var subscriptionsKey = makeSubscriptionsKey(userId);
      var subscribersKey = makeSubscribersKey(showId);

      var multi = Promise.promisifyAll(client.multi());

      multi.sadd(subscribersKey, userId);
      multi.sadd(subscriptionsKey, showId);

      return multi.execAsync();
    };

    return subscribe(userId, showId)
      .then(addLatestEpisodeToFeed.bind(null, userId, showId))
      .nodeify(callback);
  }

  function unsubscribeShow (userId, showId, callback) {
    var unsubscribe = function (userId, showId) {
      var subscriptionsKey = makeSubscriptionsKey(userId);
      var subscribersKey = makeSubscribersKey(showId);

      var multi = Promise.promisifyAll(client.multi());

      multi.srem(subscribersKey, userId);
      multi.srem(subscriptionsKey, showId);

      return multi.execAsync();
    };

    return unsubscribe(userId, showId)
      .then(removeShowFromFeed.bind(null, userId, showId))
      .nodeify(callback);
  }

  function removeShowFromFeed (userId, showId, callback) {
    var feedKey = makeFeedKey(userId);
    var showKey = makeShowKey(showId);

    client.smembersAsync(feedKey)
      .then(function (episodeKeys) {
        var multi = Promise.promisifyAll(client.multi());

        episodeKeys = episodeKeys.filter(function (key) {
          return key.indexOf(showKey) !== -1;
        });

        episodeKeys.forEach(function (episodeKey) {
          multi.srem(feedKey, episodeKey);
        });

        return multi.execAsync();
      })
      .nodeify(callback);
  }

  function addLatestEpisodeToFeed (userId, show, callback) {
    return getLatestEpisode(show)
      .then(function (episode) {
        if (!episode) { return Promise.resolve(); }

        var episodeKey = makeEpisodeKey(show, episode);
        var feedKey = makeFeedKey(userId);

        return client.saddAsync(feedKey, episodeKey);
      })
      .nodeify(callback);
  }

  function getFeed(userId, callback) {
    var getFeedItemKeys = function (userId) {
      var feedKey = makeFeedKey(userId);

      return client.smembersAsync(feedKey);
    };

    var getEpisodeByKey = function (episodeKey) {
      return client.hgetallAsync(episodeKey);
    };

    var getFeedItem = function (episodeKey) {
      return getEpisodeByKey(episodeKey)
        .then(function (episode) {
          return getShow(episode.show_id)
            .then(function (show) {
              return {
                show_id: show.id,
                imdb_id: show.imdb_id,
                title: show.title,
                episide_title: episode.title,
                sien: episode.sien,
                season: episode.season,
                episode: episode.episode,
                poster: show.poster,
                first_aired: episode.first_aired,
                timestamp: episode.timestamp
              };
            });
        });
    };

    var filterAndSort = function (feedItems) {
      return feedItems
        .filter(function (result) {
          return !!result;
        })
        .sort(function (a, b) {
          var ats = a.timestamp || a.first_aired;
          var bts = b.timestamp || b.first_aired;
          return bts - ats;
        });
    };

    return getFeedItemKeys(userId)
      .map(getFeedItem)
      .then(filterAndSort)
      .nodeify(callback);
  }

  function createToken (length, callback) {
    return new Promise(function (resolve, reject) {
      crypto.randomBytes(length, function (err, token) {
        if (err) { return reject(err); }
        if (!token) { return reject(new Error('Failed to generate token')); }

        resolve(token.toString('hex'));
      });
    }).nodeify(callback);
  }

  return {
    saveShow: saveShow,
    saveEpisode: saveEpisode,
    saveLatestEpisode: saveLatestEpisode,
    getShow: getShow,
    getLatestEpisode: getLatestEpisode,
    getActiveShowIds: getActiveShowIds,
    getInactiveShowIds: getInactiveShowIds,
    createActiveShowsStream: createActiveShowsStream,
    log: log,
    getSubscribers: getSubscribers,
    getSubscriptions: getSubscriptions,
    subscribeShow: subscribeShow,
    unsubscribeShow: unsubscribeShow,
    addLatestEpisodeToFeed: addLatestEpisodeToFeed,
    getFeed: getFeed,
    getTime: getTime,
    logScan: logScan,
    close: close
  };
}
<|endoftext|>


<commit_before>/*jshint browser:true*/
/*global module, require*/

'use strict';

/**
 * @fileoverview
 * @module goinstant/widgets/chat/view
 * @exports view
 */

/** Module dependencies */
var classes = require('classes');
var async = require('async');
var _ = require('lodash');
var trim = require('trim');
var moment = require('moment');

var XRegExp = require('../vendor/xregexp/xregexp-all').XRegExp;

/** Templates */
var listTemplate = require('../templates/list-template.html');
var messageTemplate = require('../templates/message-template.html');

/**
 * @const
 */
var WRAPPER_CLASS = 'gi-chat';
var CHAT_WRAPPER_CLASS = 'gi-chat-wrapper';
var MESSAGE_CLASS = 'gi-message';
var LOCAL_MESSAGE_CLASS = 'gi-local-message';
var MESSAGE_LIST_CLASS = 'gi-message-list';
var MESSAGE_INPUT_CLASS = 'gi-message-input';
var MESSAGE_BTN_CLASS = 'gi-message-btn';
var OVERRIDE_CLASS = 'gi-override';
var COLLAPSE_BTN_CLASS = 'gi-collapse';
var COLLAPSE_WRAPPER_CLASS = 'gi-collapse-wrapper';
var ANCHOR_CLASS = 'gi-anchor';
var RELATIVE_CLASS = 'gi-relative';
var ALIGN_LEFT_CLASS = 'gi-left';
var ALIGN_RIGHT_CLASS = 'gi-right';
var DATA_GOINSTANT_ID = 'data-goinstant-id';
var COLLAPSED_CLASS = 'collapsed';
var IMAGE_CLASS = 'gi-image';
var LINK_CLASS = 'gi-link';

var TEXT_URL_REGEX = /(\b(?:http[s]?:\/\/|www\.)[-A-Za-z0-9+&@#\/%?=~_|()!:,.;]*[-A-Za-z0-9+&@#/%=~_|()]\b)/ig;

var TIME_FORMAT = 'dddd, h:mm a'; // Sunday, 9:43 pm
var LAST_WEEK_FORMAT = 'M-DD-YY, h:mm a'; // 12-16-13, 9:43 pm

var MINUTE = 60000;
var DAY = 86400000;

module.exports = View;

/**
 * @constructor
 */
function View(userCache, options) {
  this._userCache = userCache;

  this.collapsed = options.collapsed;
  this._position = options.position;
  this._container = options.container;
  this._truncateLength = options.truncateLength;
  this._avatars = options.avatars;

  this._wrapper = null;
  this._chatWrapper = null;
  this._messageList = null;

  this._collapseBtn = null;
  this._messageInput = null;
  this._messageBtn = null;
  this._collapseWrapper = null;

  this._urlRegex = TEXT_URL_REGEX;

  _.bindAll(this, [
    'toggleCollapse',
    'appendMessage',
    'prependMessage'
  ]);
}

/**
 * Initializes the chat view
 * @public
 */
View.prototype.initialize = function() {
  this._wrapper = document.createElement('div');
  this._wrapper.setAttribute('class', WRAPPER_CLASS + ' ' + OVERRIDE_CLASS);

  this._wrapper.innerHTML = listTemplate;

  this._chatWrapper = this._wrapper.querySelector('.' + CHAT_WRAPPER_CLASS);
  this._messageList = this._wrapper.querySelector('.' + MESSAGE_LIST_CLASS);
  this._messageInput = this._wrapper.querySelector('.' + MESSAGE_INPUT_CLASS);
  this._messageBtn = this._wrapper.querySelector('.' + MESSAGE_BTN_CLASS);
  this._collapseBtn = this._wrapper.querySelector('.' + COLLAPSE_BTN_CLASS);
  this._collapseWrapper = this._wrapper.querySelector('.' +
                                                      COLLAPSE_WRAPPER_CLASS);

  // Pass the position either default or user set as a class
  if (!this._container && this._position === 'right') {
    classes(this._wrapper).add(ALIGN_RIGHT_CLASS);

  } else if (!this._container) {
    classes(this._wrapper).add(ALIGN_LEFT_CLASS);
  }

  // If the collapsed param is specified, we use it, otherwise
  // get the collapse key and use its value
  if (_.isBoolean(this.collapsed)) {
    this._setCollapse(this.collapsed);
  } else {
    var localUser = this._userCache.getLocalUser();
    var collapseStatus =
      localUser.goinstant &&
      localUser.goinstant.widgets &&
      localUser.goinstant.widgets.chat &&
      localUser.goinstant.widgets.chat.collapsed;

    if (collapseStatus === undefined || collapseStatus === null) {
      collapseStatus = false;
    }

    this._setCollapse(collapseStatus);
  }
};

View.prototype.append = function() {
  // Check if user passed a container and if so, append user list to it
  if (this._container) {
    this._container.appendChild(this._wrapper);

    classes(this._wrapper).add(RELATIVE_CLASS);

  } else {
    document.body.appendChild(this._wrapper);

    classes(this._wrapper).add(ANCHOR_CLASS);
  }
};

/**
 * Gets the els that chat binds to
 * @public
 * @returns chatInterface An object containing the collapse button, message
 *                        input and message button
 */
View.prototype.getUI = function() {
  var chatInterface = {
    collapseBtn: this._collapseBtn,
    messageInput: this._messageInput,
    messageBtn: this._messageBtn,
    collapseWrapper: this._collapseWrapper
  };

  return chatInterface;
};

/**
 * Toggles the chat collapse
 * @public
 */
View.prototype.toggleCollapse = function() {
  this._setCollapse(!this.collapsed);
};

/**
 * Sets the collapse state of the chat widget
 * @private
 * @param {boolean} toggle true = collapses, false, opens
 */
View.prototype._setCollapse = function(toggle) {
  if (toggle) {
    classes(this._chatWrapper).add(COLLAPSED_CLASS);
    classes(this._collapseBtn).add(COLLAPSED_CLASS);

    this.collapsed = true;

  } else {
    classes(this._chatWrapper).remove(COLLAPSED_CLASS);
    classes(this._collapseBtn).remove(COLLAPSED_CLASS);

    this.collapsed = false;

    this._scrollChatToBottom();
  }
};

/**
 * Adds the message to the bottom of the list
 * @public
 * @param {object} message The message object
 */
View.prototype.appendMessage = function(message) {
  var self = this;

  var el = this._createMessage(message);
  self._messageList.appendChild(el);
  self._scrollChatToBottom();
};

/**
 * Adds the message to the top of the list
 * @public
 * @param {object} message The message object
 */
View.prototype.prependMessage = function(message) {
  var self = this;

  var el = this._createMessage(message);

  var firstChild = self._messageList.firstChild;

  if (firstChild) {
    self._messageList.insertBefore(el, firstChild);
    self._scrollChatToBottom();

    return;
  }

  self._messageList.appendChild(el);
  self._scrollChatToBottom();
};

/**
 * Creates a new message in the chat widget
 * @private
 * @param {object} message Contaisn the data needed to create the message
 * @returns {HTMLElement} The populated message el
 */
View.prototype._createMessage = function(message) {
  var user = message.user;

  var shortName = truncate(user.displayName, this._truncateLength);

  var formattedTime = formatTime(message.timestamp);

  // message vars
  var vars = {
    id: message.id,
    shortName: shortName,
    time: formattedTime
  };

  // message template
  var template = _.template(messageTemplate, vars);
  var itemEl = document.createElement('li');
  itemEl.innerHTML = template;

  // message text. avoid template, susceptible to XSS
  var textEl = itemEl.querySelector('.gi-text');
  itemEl.title = user.displayName;
  itemEl.id = message.id;
  itemEl.setAttribute(DATA_GOINSTANT_ID, message.id);

  // avatar color
  var colorEl = itemEl.querySelector('.gi-color');
  colorEl.style.backgroundColor = user.avatarColor;

  // avatar URL. avoid template, susceptible to XSS
  if (this._avatars && user.avatarUrl) {
    var avatarEl = colorEl.querySelector('.gi-avatar');

    var imgEl = document.createElement('img');
    imgEl.className = 'gi-avatar-img';
    imgEl.src = _.escape(user.avatarUrl);

    colorEl.style.backgroundImage = 'none';
    avatarEl.appendChild(imgEl);
  }

  // message classes
  classes(itemEl).add(user.id);
  classes(itemEl).add(MESSAGE_CLASS);

  var localUser = this._userCache.getLocalUser();
  if (user.id === localUser.id) {
    classes(itemEl).add(LOCAL_MESSAGE_CLASS);
  }

  message.text = _.unescape(message.text);

  this._renderText(textEl, message.text);

  return itemEl;
};

/**
 * Renders the text with links and images
 * @private
 * @param {HTMLElement} textEl The message element
 * @param {string} text The message text
 */
View.prototype._renderText = function(textEl, text) {
  var nodes = XRegExp.split(text, TEXT_URL_REGEX);
  var tasks = [];

  var imageEl = textEl.parentNode.querySelector('.gi-images');

  var self = this;

  _.each(nodes, function(node, index) {

    // Check if the node is just text
    if (!node.match(TEXT_URL_REGEX)) {
      nodes[index] = document.createTextNode(node);
      return;
    }

    // Make URL absolute
    if (node.substring(0, 4).toLowerCase() !== 'http') {
      node = 'http://' + node;
    }

    // Check if the url node is an img
    tasks.push(_.bind(self._validateImage, self, imageEl, node));

    var linkEl = document.createElement('a');
    var textNode = document.createTextNode(node);

    classes(linkEl).add(LINK_CLASS);
    linkEl.href = node;
    linkEl.target = '_blank'; // Opens page in new window/tab
    linkEl.appendChild(textNode);
    nodes[index] = linkEl;
  });

  // Add in-line textNodes and links first
  _.each(nodes, function(node) {
    if (node === '') {
      return;
    }

    textEl.appendChild(node);
  });

  // Handle images after the message has been appended to prevent delay.
  async.parallel(tasks, function() {

    // Remove the images container if empty
    if (!imageEl.children.length) {
      imageEl.parentNode.removeChild(imageEl);
    }

    self._scrollChatToBottom();
  });
};

/**
 * Handles loading a URL as an image
 * @private
 * @param {string} url The potential image URL
 * @param {function} cb A callback function to be called after attempting to
 *                      load the image
 */
View.prototype._validateImage = function(imageEl, url, cb) {
  if (!url || !_.isString(url)) {
    return cb(null);
  }

  var validated = false;

  var img = new Image();
  classes(img).add(IMAGE_CLASS);

  // img must be appended before setting the src.
  // Fixes an issue in IE9 where onload wouldn't trigger
  imageEl.appendChild(img);

  // Timeout after 10 seconds attempting to load image
  var timeoutId = window.setTimeout(clearImg, 10000);

  img.onerror = clearImg;

  img.onabort = clearImg;

  function clearImg() {
    if (validated) {
      return;
    }

    validated = true;

    window.clearTimeout(timeoutId);
    img.parentNode.removeChild(img);

    return cb(null);
  }

  img.onload = function() {
    if (validated) {
      // Timeout would have occurred, took too long to load image
      return;
    }

    validated = true;

    window.clearTimeout(timeoutId);

    return cb(null);
  };

  // Certain inputs can cause IE8 to throw an error when setting img.src
  try {
    img.src = url;

  } catch (err) {
    clearImg();
  }
};

/**
 * Scrolls the chat to the bottom
 * @private
 */
View.prototype._scrollChatToBottom = function() {
  this._messageList.scrollTop = this._messageList.scrollHeight;
};

/**
 * Destroys the chat view
 * @public
 */
View.prototype.destroy = function() {
  if (document.body.querySelector('.' + CHAT_WRAPPER_CLASS)) {
    this._wrapper.parentNode.removeChild(this._wrapper);
  }

  if (this._wrapper) {
    this._wrapper = null;
  }
};

/**
 * Truncates a displayName
 * @private
 * @param {string} str The displayName to truncate
 * @param {number} limit The maximum number of characters the resulting string
 *                       can be
 */
function truncate(str, limit) {
  var shortened = '';

  if (str.length > limit) {
    var substring = str.substring(0, limit);

    if (str[limit] === ' ') {
      return trim(substring);

    } else {
      return substring + '...';
    }
  }

  return shortened || str;
}

/**
 * Formats the message time based on days from now
 * @private
 * @param {number} timestamp The message timestamp
 * @returns {string} The correctly formatted time
 */
function formatTime(timestamp) {
  var now = new Date().getTime();

  // Calculate todays 00:00 in ms
  var zone = moment().zone() * MINUTE;
  var dayStart = now - (now % DAY) + zone;
  var diff = dayStart - timestamp;

  // Calculate how many days ago the message was created
  var days = Math.floor(diff / DAY);

  // Use 'day of week, h:mm' or 'mm-dd-yy, h:mm'
  var format = (days <= 6) ? TIME_FORMAT : LAST_WEEK_FORMAT;

  return moment(timestamp).format(format);

}
<commit_msg>fix scroll to bottom at initialize<commit_after>/*jshint browser:true*/
/*global module, require*/

'use strict';

/**
 * @fileoverview
 * @module goinstant/widgets/chat/view
 * @exports view
 */

/** Module dependencies */
var classes = require('classes');
var async = require('async');
var _ = require('lodash');
var trim = require('trim');
var moment = require('moment');

var XRegExp = require('../vendor/xregexp/xregexp-all').XRegExp;

/** Templates */
var listTemplate = require('../templates/list-template.html');
var messageTemplate = require('../templates/message-template.html');

/**
 * @const
 */
var WRAPPER_CLASS = 'gi-chat';
var CHAT_WRAPPER_CLASS = 'gi-chat-wrapper';
var MESSAGE_CLASS = 'gi-message';
var LOCAL_MESSAGE_CLASS = 'gi-local-message';
var MESSAGE_LIST_CLASS = 'gi-message-list';
var MESSAGE_INPUT_CLASS = 'gi-message-input';
var MESSAGE_BTN_CLASS = 'gi-message-btn';
var OVERRIDE_CLASS = 'gi-override';
var COLLAPSE_BTN_CLASS = 'gi-collapse';
var COLLAPSE_WRAPPER_CLASS = 'gi-collapse-wrapper';
var ANCHOR_CLASS = 'gi-anchor';
var RELATIVE_CLASS = 'gi-relative';
var ALIGN_LEFT_CLASS = 'gi-left';
var ALIGN_RIGHT_CLASS = 'gi-right';
var DATA_GOINSTANT_ID = 'data-goinstant-id';
var COLLAPSED_CLASS = 'collapsed';
var IMAGE_CLASS = 'gi-image';
var LINK_CLASS = 'gi-link';

var TEXT_URL_REGEX = /(\b(?:http[s]?:\/\/|www\.)[-A-Za-z0-9+&@#\/%?=~_|()!:,.;]*[-A-Za-z0-9+&@#/%=~_|()]\b)/ig;

var TIME_FORMAT = 'dddd, h:mm a'; // Sunday, 9:43 pm
var LAST_WEEK_FORMAT = 'M-DD-YY, h:mm a'; // 12-16-13, 9:43 pm

var MINUTE = 60000;
var DAY = 86400000;

module.exports = View;

/**
 * @constructor
 */
function View(userCache, options) {
  this._userCache = userCache;

  this.collapsed = options.collapsed;
  this._position = options.position;
  this._container = options.container;
  this._truncateLength = options.truncateLength;
  this._avatars = options.avatars;

  this._wrapper = null;
  this._chatWrapper = null;
  this._messageList = null;

  this._collapseBtn = null;
  this._messageInput = null;
  this._messageBtn = null;
  this._collapseWrapper = null;

  this._urlRegex = TEXT_URL_REGEX;

  _.bindAll(this, [
    'toggleCollapse',
    'appendMessage',
    'prependMessage'
  ]);
}

/**
 * Initializes the chat view
 * @public
 */
View.prototype.initialize = function() {
  this._wrapper = document.createElement('div');
  this._wrapper.setAttribute('class', WRAPPER_CLASS + ' ' + OVERRIDE_CLASS);

  this._wrapper.innerHTML = listTemplate;

  this._chatWrapper = this._wrapper.querySelector('.' + CHAT_WRAPPER_CLASS);
  this._messageList = this._wrapper.querySelector('.' + MESSAGE_LIST_CLASS);
  this._messageInput = this._wrapper.querySelector('.' + MESSAGE_INPUT_CLASS);
  this._messageBtn = this._wrapper.querySelector('.' + MESSAGE_BTN_CLASS);
  this._collapseBtn = this._wrapper.querySelector('.' + COLLAPSE_BTN_CLASS);
  this._collapseWrapper = this._wrapper.querySelector('.' +
                                                      COLLAPSE_WRAPPER_CLASS);

  // Pass the position either default or user set as a class
  if (!this._container && this._position === 'right') {
    classes(this._wrapper).add(ALIGN_RIGHT_CLASS);

  } else if (!this._container) {
    classes(this._wrapper).add(ALIGN_LEFT_CLASS);
  }

  // If the collapsed param is specified, we use it, otherwise
  // get the collapse key and use its value
  if (_.isBoolean(this.collapsed)) {
    this._setCollapse(this.collapsed);
  } else {
    var localUser = this._userCache.getLocalUser();
    var collapseStatus =
      localUser.goinstant &&
      localUser.goinstant.widgets &&
      localUser.goinstant.widgets.chat &&
      localUser.goinstant.widgets.chat.collapsed;

    if (collapseStatus === undefined || collapseStatus === null) {
      collapseStatus = false;
    }

    this._setCollapse(collapseStatus);
  }
};

View.prototype.append = function() {
  // Check if user passed a container and if so, append user list to it
  if (this._container) {
    this._container.appendChild(this._wrapper);

    classes(this._wrapper).add(RELATIVE_CLASS);

  } else {
    document.body.appendChild(this._wrapper);

    classes(this._wrapper).add(ANCHOR_CLASS);
  }

  this._scrollChatToBottom();
};

/**
 * Gets the els that chat binds to
 * @public
 * @returns chatInterface An object containing the collapse button, message
 *                        input and message button
 */
View.prototype.getUI = function() {
  var chatInterface = {
    collapseBtn: this._collapseBtn,
    messageInput: this._messageInput,
    messageBtn: this._messageBtn,
    collapseWrapper: this._collapseWrapper
  };

  return chatInterface;
};

/**
 * Toggles the chat collapse
 * @public
 */
View.prototype.toggleCollapse = function() {
  this._setCollapse(!this.collapsed);
};

/**
 * Sets the collapse state of the chat widget
 * @private
 * @param {boolean} toggle true = collapses, false, opens
 */
View.prototype._setCollapse = function(toggle) {
  if (toggle) {
    classes(this._chatWrapper).add(COLLAPSED_CLASS);
    classes(this._collapseBtn).add(COLLAPSED_CLASS);

    this.collapsed = true;

  } else {
    classes(this._chatWrapper).remove(COLLAPSED_CLASS);
    classes(this._collapseBtn).remove(COLLAPSED_CLASS);

    this.collapsed = false;

    this._scrollChatToBottom();
  }
};

/**
 * Adds the message to the bottom of the list
 * @public
 * @param {object} message The message object
 */
View.prototype.appendMessage = function(message) {
  var self = this;

  var el = this._createMessage(message);
  self._messageList.appendChild(el);
  self._scrollChatToBottom();
};

/**
 * Adds the message to the top of the list
 * @public
 * @param {object} message The message object
 */
View.prototype.prependMessage = function(message) {
  var self = this;

  var el = this._createMessage(message);

  var firstChild = self._messageList.firstChild;

  if (firstChild) {
    self._messageList.insertBefore(el, firstChild);
    self._scrollChatToBottom();

    return;
  }

  self._messageList.appendChild(el);
  self._scrollChatToBottom();
};

/**
 * Creates a new message in the chat widget
 * @private
 * @param {object} message Contaisn the data needed to create the message
 * @returns {HTMLElement} The populated message el
 */
View.prototype._createMessage = function(message) {
  var user = message.user;

  var shortName = truncate(user.displayName, this._truncateLength);

  var formattedTime = formatTime(message.timestamp);

  // message vars
  var vars = {
    id: message.id,
    shortName: shortName,
    time: formattedTime
  };

  // message template
  var template = _.template(messageTemplate, vars);
  var itemEl = document.createElement('li');
  itemEl.innerHTML = template;

  // message text. avoid template, susceptible to XSS
  var textEl = itemEl.querySelector('.gi-text');
  itemEl.title = user.displayName;
  itemEl.id = message.id;
  itemEl.setAttribute(DATA_GOINSTANT_ID, message.id);

  // avatar color
  var colorEl = itemEl.querySelector('.gi-color');
  colorEl.style.backgroundColor = user.avatarColor;

  // avatar URL. avoid template, susceptible to XSS
  if (this._avatars && user.avatarUrl) {
    var avatarEl = colorEl.querySelector('.gi-avatar');

    var imgEl = document.createElement('img');
    imgEl.className = 'gi-avatar-img';
    imgEl.src = _.escape(user.avatarUrl);

    colorEl.style.backgroundImage = 'none';
    avatarEl.appendChild(imgEl);
  }

  // message classes
  classes(itemEl).add(user.id);
  classes(itemEl).add(MESSAGE_CLASS);

  var localUser = this._userCache.getLocalUser();
  if (user.id === localUser.id) {
    classes(itemEl).add(LOCAL_MESSAGE_CLASS);
  }

  message.text = _.unescape(message.text);

  this._renderText(textEl, message.text);

  return itemEl;
};

/**
 * Renders the text with links and images
 * @private
 * @param {HTMLElement} textEl The message element
 * @param {string} text The message text
 */
View.prototype._renderText = function(textEl, text) {
  var nodes = XRegExp.split(text, TEXT_URL_REGEX);
  var tasks = [];

  var imageEl = textEl.parentNode.querySelector('.gi-images');

  var self = this;

  _.each(nodes, function(node, index) {

    // Check if the node is just text
    if (!node.match(TEXT_URL_REGEX)) {
      nodes[index] = document.createTextNode(node);
      return;
    }

    // Make URL absolute
    if (node.substring(0, 4).toLowerCase() !== 'http') {
      node = 'http://' + node;
    }

    // Check if the url node is an img
    tasks.push(_.bind(self._validateImage, self, imageEl, node));

    var linkEl = document.createElement('a');
    var textNode = document.createTextNode(node);

    classes(linkEl).add(LINK_CLASS);
    linkEl.href = node;
    linkEl.target = '_blank'; // Opens page in new window/tab
    linkEl.appendChild(textNode);
    nodes[index] = linkEl;
  });

  // Add in-line textNodes and links first
  _.each(nodes, function(node) {
    if (node === '') {
      return;
    }

    textEl.appendChild(node);
  });

  // Handle images after the message has been appended to prevent delay.
  async.parallel(tasks, function() {

    // Remove the images container if empty
    if (!imageEl.children.length) {
      imageEl.parentNode.removeChild(imageEl);
    }

    self._scrollChatToBottom();
  });
};

/**
 * Handles loading a URL as an image
 * @private
 * @param {string} url The potential image URL
 * @param {function} cb A callback function to be called after attempting to
 *                      load the image
 */
View.prototype._validateImage = function(imageEl, url, cb) {
  if (!url || !_.isString(url)) {
    return cb(null);
  }

  var validated = false;

  var img = new Image();
  classes(img).add(IMAGE_CLASS);

  // img must be appended before setting the src.
  // Fixes an issue in IE9 where onload wouldn't trigger
  imageEl.appendChild(img);

  // Timeout after 10 seconds attempting to load image
  var timeoutId = window.setTimeout(clearImg, 10000);

  img.onerror = clearImg;

  img.onabort = clearImg;

  function clearImg() {
    if (validated) {
      return;
    }

    validated = true;

    window.clearTimeout(timeoutId);
    img.parentNode.removeChild(img);

    return cb(null);
  }

  img.onload = function() {
    if (validated) {
      // Timeout would have occurred, took too long to load image
      return;
    }

    validated = true;

    window.clearTimeout(timeoutId);

    return cb(null);
  };

  // Certain inputs can cause IE8 to throw an error when setting img.src
  try {
    img.src = url;

  } catch (err) {
    clearImg();
  }
};

/**
 * Scrolls the chat to the bottom
 * @private
 */
View.prototype._scrollChatToBottom = function() {
  this._messageList.scrollTop = this._messageList.scrollHeight;
};

/**
 * Destroys the chat view
 * @public
 */
View.prototype.destroy = function() {
  if (document.body.querySelector('.' + CHAT_WRAPPER_CLASS)) {
    this._wrapper.parentNode.removeChild(this._wrapper);
  }

  if (this._wrapper) {
    this._wrapper = null;
  }
};

/**
 * Truncates a displayName
 * @private
 * @param {string} str The displayName to truncate
 * @param {number} limit The maximum number of characters the resulting string
 *                       can be
 */
function truncate(str, limit) {
  var shortened = '';

  if (str.length > limit) {
    var substring = str.substring(0, limit);

    if (str[limit] === ' ') {
      return trim(substring);

    } else {
      return substring + '...';
    }
  }

  return shortened || str;
}

/**
 * Formats the message time based on days from now
 * @private
 * @param {number} timestamp The message timestamp
 * @returns {string} The correctly formatted time
 */
function formatTime(timestamp) {
  var now = new Date().getTime();

  // Calculate todays 00:00 in ms
  var zone = moment().zone() * MINUTE;
  var dayStart = now - (now % DAY) + zone;
  var diff = dayStart - timestamp;

  // Calculate how many days ago the message was created
  var days = Math.floor(diff / DAY);

  // Use 'day of week, h:mm' or 'mm-dd-yy, h:mm'
  var format = (days <= 6) ? TIME_FORMAT : LAST_WEEK_FORMAT;

  return moment(timestamp).format(format);

}
<|endoftext|>


<commit_before>'use strict';
const forEach = require('lodash.foreach');
const get = require('lodash.get');
const version = require('../../../package').version;
const engineUtils = require('../../support/engineUtils');
const log = require('intel');
const { parseCpuTrace } = require('../../chrome/cpu');
const Statistics = require('../../support/statistics').Statistics;
const ScreenshotManager = require('../../screenshot/');

/**
 * Create a new Collector instance. The collector will collect metrics
 * per iteration and store what's needed to disk.
 * @class
 */
class Collector {
  constructor(url, storageManager, options) {
    this.options = options;
    this.storageManager = storageManager;
    this.screenshotManager = new ScreenshotManager(storageManager, options);
    this.collectChromeTimeline = options.chrome && options.chrome.timeline;
    this.statistics = new Statistics();
    // Put all the result from the iterations here
    this.results = {
      info: {
        browsertime: {
          version
        },
        url,
        timestamp: engineUtils.timestamp(),
        connectivity: {
          engine: get(this.options, 'connectivity.engine'),
          profile: get(this.options, 'connectivity.profile')
        }
      },
      timestamps: [],
      browserScripts: [],
      visualMetrics: [],
      cpu: [],
      errors: []
    };
  }

  /**
   * Get the result from the collector. Will add and summarize all the statistics.
   * @returns {json} A JSON blob with all the collected metrics
   */
  getResults() {
    this.results.statistics = this.statistics.summarizeDeep(this.options);
    return this.results;
  }

  /**
   * Collect all individual runs, add it to the statistics, and store
   * data that needs to be on disk (trace logs, sceenshots etc).
   * @param {*} data - The data collected by the browser for one iteration
   * @param {Integer} index - Which iteration it is
   */
  async perIteration(data, index) {
    const results = this.results;
    const statistics = this.statistics;
    // If we don't have an error, use an empty array. In the future we want to push
    // more than one errors per run (maybe).
    results.errors.push(data.error || []);
    // From each iteration, collect the result we want
    results.timestamps.push(data.timestamp);
    if (data.browserScripts) {
      results.browserScripts.push(data.browserScripts);
      // Add all browserscripts to the stats
      statistics.addDeep(data.browserScripts, (keyPath, value) => {
        if (keyPath.endsWith('userTimings.marks')) {
          return value.reduce((result, mark) => {
            result[mark.name] = mark.startTime;
            return result;
          }, {});
        } else if (keyPath.endsWith('userTimings.measures')) {
          return value.reduce((result, mark) => {
            result[mark.name] = mark.duration;
            return result;
          }, {});
        } else if (keyPath.endsWith('resourceTimings')) {
          return {};
        }
        return value;
      });
    }

    if (
      data.browserScripts &&
      data.browserScripts.timings &&
      data.browserScripts.timings.pageTimings &&
      this.options.iterations > 1
    ) {
      const pt = data.browserScripts.timings.pageTimings;
      log.info(
        `BackEndTime: ${pt.backEndTime} DomInteractiveTime: ${
          pt.domInteractiveTime
        } DomContentLoadedTime: ${pt.domContentLoadedTime} FrontEndTime: ${
          pt.frontEndTime
        } PageLoadTime: ${pt.pageLoadTime}
        `
      );
    }

    if (data.visualMetrics) {
      if (this.options.iterations > 1) {
        log.info(
          `VisualMetrics FirstVisualChange: ${
            data.visualMetrics.FirstVisualChange
          } SpeedIndex: ${
            data.visualMetrics.SpeedIndex
          } PerceptualSpeedIndex: ${
            data.visualMetrics.PerceptualSpeedIndex
          } LastVisualChange: ${data.visualMetrics.LastVisualChange}
          `
        );
      }
      results.visualMetrics.push(data.visualMetrics);
      statistics.addDeep({
        visualMetrics: data.visualMetrics
      });
    }
    // Put all work that involves storing metrics to disk in a
    // array and the promise them all later on
    const extraWork = [];
    if (this.options.screenshot && data.screenshot) {
      extraWork.push(this.screenshotManager.save(`${index}`, data.screenshot));
    }

    // Collect CPU data. There are some extra work going on here now
    // (we store the trace file twice)
    // in the future we could use the already stored trace log
    // but let us change that later on
    if (this.collectChromeTimeline && data.extraJson[`trace-${index}.json`]) {
      extraWork.push(
        parseCpuTrace(
          data.extraJson[`trace-${index}.json`],
          this.storageManager.baseDir,
          index
        )
          .then(timelineData => {
            results.cpu.push(timelineData);
            statistics.addDeep({ cpu: timelineData });
          })
          .catch(e => {
            log.error('Failed to parse CPU usage from Chrome.', e);
            // We don't want to rethrow this .. log and continue
            // with the rest of the metrics
          })
      );
    }

    // Store all extra JSON metrics
    forEach(data.extraJson, (file, filename) =>
      extraWork.push(this.storageManager.writeJson(filename, file, true))
    );

    return Promise.all(extraWork);
  }
}

module.exports = Collector;
<commit_msg>log First Paint if Visual Metrics isn't availible (#569)<commit_after>'use strict';
const forEach = require('lodash.foreach');
const get = require('lodash.get');
const version = require('../../../package').version;
const engineUtils = require('../../support/engineUtils');
const log = require('intel');
const { parseCpuTrace } = require('../../chrome/cpu');
const Statistics = require('../../support/statistics').Statistics;
const ScreenshotManager = require('../../screenshot/');

/**
 * Create a new Collector instance. The collector will collect metrics
 * per iteration and store what's needed to disk.
 * @class
 */
class Collector {
  constructor(url, storageManager, options) {
    this.options = options;
    this.storageManager = storageManager;
    this.screenshotManager = new ScreenshotManager(storageManager, options);
    this.collectChromeTimeline = options.chrome && options.chrome.timeline;
    this.statistics = new Statistics();
    // Put all the result from the iterations here
    this.results = {
      info: {
        browsertime: {
          version
        },
        url,
        timestamp: engineUtils.timestamp(),
        connectivity: {
          engine: get(this.options, 'connectivity.engine'),
          profile: get(this.options, 'connectivity.profile')
        }
      },
      timestamps: [],
      browserScripts: [],
      visualMetrics: [],
      cpu: [],
      errors: []
    };
  }

  /**
   * Get the result from the collector. Will add and summarize all the statistics.
   * @returns {json} A JSON blob with all the collected metrics
   */
  getResults() {
    this.results.statistics = this.statistics.summarizeDeep(this.options);
    return this.results;
  }

  /**
   * Collect all individual runs, add it to the statistics, and store
   * data that needs to be on disk (trace logs, sceenshots etc).
   * @param {*} data - The data collected by the browser for one iteration
   * @param {Integer} index - Which iteration it is
   */
  async perIteration(data, index) {
    const results = this.results;
    const statistics = this.statistics;
    // If we don't have an error, use an empty array. In the future we want to push
    // more than one errors per run (maybe).
    results.errors.push(data.error || []);
    // From each iteration, collect the result we want
    results.timestamps.push(data.timestamp);
    if (data.browserScripts) {
      results.browserScripts.push(data.browserScripts);
      // Add all browserscripts to the stats
      statistics.addDeep(data.browserScripts, (keyPath, value) => {
        if (keyPath.endsWith('userTimings.marks')) {
          return value.reduce((result, mark) => {
            result[mark.name] = mark.startTime;
            return result;
          }, {});
        } else if (keyPath.endsWith('userTimings.measures')) {
          return value.reduce((result, mark) => {
            result[mark.name] = mark.duration;
            return result;
          }, {});
        } else if (keyPath.endsWith('resourceTimings')) {
          return {};
        }
        return value;
      });
    }

    if (
      data.browserScripts &&
      data.browserScripts.timings &&
      data.browserScripts.timings.pageTimings &&
      this.options.iterations > 1
    ) {
      const pt = data.browserScripts.timings.pageTimings;
      log.info(
        `BackEndTime: ${pt.backEndTime} DomInteractiveTime: ${
          pt.domInteractiveTime
        } DomContentLoadedTime: ${pt.domContentLoadedTime} FirstPaint: ${
          data.browserScripts.timings.firstPaint
        } PageLoadTime: ${pt.pageLoadTime}
        `
      );
    }

    if (data.visualMetrics) {
      if (this.options.iterations > 1) {
        log.info(
          `VisualMetrics FirstVisualChange: ${
            data.visualMetrics.FirstVisualChange
          } SpeedIndex: ${
            data.visualMetrics.SpeedIndex
          } PerceptualSpeedIndex: ${
            data.visualMetrics.PerceptualSpeedIndex
          } LastVisualChange: ${data.visualMetrics.LastVisualChange}
          `
        );
      }
      results.visualMetrics.push(data.visualMetrics);
      statistics.addDeep({
        visualMetrics: data.visualMetrics
      });
    }
    // Put all work that involves storing metrics to disk in a
    // array and the promise them all later on
    const extraWork = [];
    if (this.options.screenshot && data.screenshot) {
      extraWork.push(this.screenshotManager.save(`${index}`, data.screenshot));
    }

    // Collect CPU data. There are some extra work going on here now
    // (we store the trace file twice)
    // in the future we could use the already stored trace log
    // but let us change that later on
    if (this.collectChromeTimeline && data.extraJson[`trace-${index}.json`]) {
      extraWork.push(
        parseCpuTrace(
          data.extraJson[`trace-${index}.json`],
          this.storageManager.baseDir,
          index
        )
          .then(timelineData => {
            results.cpu.push(timelineData);
            statistics.addDeep({ cpu: timelineData });
          })
          .catch(e => {
            log.error('Failed to parse CPU usage from Chrome.', e);
            // We don't want to rethrow this .. log and continue
            // with the rest of the metrics
          })
      );
    }

    // Store all extra JSON metrics
    forEach(data.extraJson, (file, filename) =>
      extraWork.push(this.storageManager.writeJson(filename, file, true))
    );

    return Promise.all(extraWork);
  }
}

module.exports = Collector;
<|endoftext|>


<commit_before>//Test HTTP Server
var http = require('http');
var dispatch = require('dispatch');
var request = require('superagent');
var passport = require('passport');
var express = require('express');
var http = require('http');
var path = require('path');
var fs = require('fs');
var app = express();
var redis = require('redis');
var session = require('express-session');
var refresh = require('passport-oauth2-refresh');
var bodyParser = require('body-parser');
var Venmo = require('venmo');
var url = require('url');

var VenmoStrategy = require('passport-venmo').Strategy;

//TODO: Switch To Using Environment Variables So Real Values Don't Show Up in Github
var DISPENSOR_PASSKEY = process.env.DISPENSOR_PASSKEY;
var Venmo_CLIENT_ID = process.env.VENMO_CLIENT_ID;
var Venmo_CLIENT_SECRET = process.env.VENMO_CLIENT_SECRET;

//Redis Client
var rclient;
if(process.env.REDISTOGO_URL){
  info   = url.parse(process.env.REDISTOGO_URL, true);
  rclient = redis.createClient(info.port, info.hostname);
}
else {
  rclient = redis.createClient();
}

rclient.on('connect', function() {
    console.log('Redis Connected...');
});

var redisSessionStore = rclient.redisSessionStore;
var sess = {
  store: redisSessionStore,
  secret: 'keyboard cat',
  resave: true,
  saveUninitialized: false,
  cookie: {
    expires: false,
  }
};

app.set('port', process.env.PORT || 8080);
app.set('view engine', 'html');
app.use(session(sess));
app.use(passport.initialize());
app.use(passport.session());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended:true}));

//Serve Static Web For Web App
app.use('/', express.static(path.resolve(__dirname,'../public')));

//console.log(path.resolve(__dirname,'../public'))
var venmoStrategy = new VenmoStrategy({
    clientID: Venmo_CLIENT_ID,
    clientSecret: Venmo_CLIENT_SECRET,
    callbackURL: "http://localhost:8080/auth/venmo/callback"
  },

  function(accessToken, refreshToken, profile, done) {
    console.log("Profile ID :" + profile.id);
    console.log("Access Token: " + accessToken);
    console.log("Refresh Token: " + refreshToken);
    var user = { VenmoId: profile.id, AccessToken:accessToken, RefreshToken:refreshToken};
    rclient.set('venmoCharger', JSON.stringify(user), function (err, reply) {
      if (err) throw err;
      console.log('Venmo Charge Account Saved! User is: ');
      //console.log(user);
      done(err, user);
    });
  }
);

passport.use(venmoStrategy);
refresh.use(venmoStrategy);

passport.serializeUser(function(user, done) {
    console.log('passport serializing...' + user.VenmoId);
    done(null, user.VenmoId);
});

passport.deserializeUser(function(id, done) {
    rclient.get('venmoCharger', function(err, user) {
        if (err) throw err;
        console.log('passport deserializing...'+JSON.parse(user).VenmoId);
        done(err, JSON.parse(user));
    });
});

//Constants
var oathURI = "https://api.venmo.com/v1/oauth/authorize";
var redirectURI = "http://localhost:8080/redirectTo";
var accessTokenURI = "https://api.venmo.com/v1/oauth/access_token";

//TODO: Come Up With Some Better User Flow Here For Updating the Charge Account
app.post(  '/auth/venmo',
          checkPassKey,
          passport.authenticate('venmo', {
            scope: ['make_payments', 'access_feed', 'access_profile', 'access_email', 'access_phone', 'access_balance', 'access_friends'],
            failureRedirect: '/'
          }), function(req, res) {
                res.redirect('/');
              }
        );

app.get(  '/users/venmo',
          function(req, res) {
            getVenmoChargeAccount( function(user) {
                        var venmo = new Venmo(user.AccessToken);
                        venmo.getCurrentUser(function(err,data){
                          var condensed = { username: data.user.username,
                                            displayname: data.user.display_name,
                                            email: data.user.email
                                          };
                          res.json(condensed);
                        });
                    });
          });

app.get( '/auth/venmo/callback',
          passport.authenticate('venmo', {
            failureRedirect: '/'
          }), function(req, res) {
            //Nothing If we got Here (Maybe Render The Client Information if Its Been Inputed Properly?)
            //res.json({user: req.user ? JSON.stringify(req.user) : 'null'});
            res.redirect('/');
          }
        );

app.get( '/auth/venmo/refresh',
          passport.authenticate('venmo', {
            failureRedirect: '/'
          }), function(req, res) {
            res.json({user: req.user ? JSON.stringify(req.user) : 'null'});
          }
        );

function checkPassKey(req,res,next) {
  var passKey = req.body.pwd;
  if(passKeyCheck(passKey)){
    next();
  } else {
    console.log("Incorrect Passkey Was Input");
    res.redirect('/');
  }
}

function passKeyCheck(passkey){
  if(passkey === DISPENSOR_PASSKEY)
    return true;
  else
    return false;
}

function ensureRefreshedToken(req, res, next) {
   //Get Old Access Token
   rclient.get('venmoCharger', function(err, user) {
        if (err) throw err;
        user = JSON.parse(user);
        var oldRefreshToken = user.RefreshToken;
        refresh.requestNewAccessToken('venmo', oldRefreshToken, function(err, accessToken, refreshToken) {
          //Store New Access Token
          if(refreshToken){
            var newUser = _.extend(user,{AccessToken:accessToken, RefreshToken:refreshToken});
              rclient.set('venmoCharger', JSON.stringify(newUser), function (err, reply) {
              if (err) throw err;
              console.log('Venmo Charge Account Refreshed! User is: ');
              console.log(user);
              next();
            });
          } else {
            console.log("Refresh Token was not aquired, Venmo Charge User has not changed.");
            next();
          }
          });
    });
}

//Launch the express server
var server = app.listen(app.get('port'), function() {
  console.log('Dispensor Venmo server listening on port ' + server.address().port);
});

getVenmoChargeAccount = function(callback) {
  rclient.get('venmoCharger', function(err, user) {
    if (err) throw err;
    //console.log(JSON.parse(user));
    callback(JSON.parse(user));
  });
};

//Module Exports -> Export Current Venmo Charge User
module.exports = {
  getVenmoChargeAccount: getVenmoChargeAccount
};
<commit_msg>redis fix for ready check<commit_after>//Test HTTP Server
var http = require('http');
var dispatch = require('dispatch');
var request = require('superagent');
var passport = require('passport');
var express = require('express');
var http = require('http');
var path = require('path');
var fs = require('fs');
var app = express();
var redis = require('redis');
var session = require('express-session');
var refresh = require('passport-oauth2-refresh');
var bodyParser = require('body-parser');
var Venmo = require('venmo');
var url = require('url');

var VenmoStrategy = require('passport-venmo').Strategy;

//TODO: Switch To Using Environment Variables So Real Values Don't Show Up in Github
var DISPENSOR_PASSKEY = process.env.DISPENSOR_PASSKEY;
var Venmo_CLIENT_ID = process.env.VENMO_CLIENT_ID;
var Venmo_CLIENT_SECRET = process.env.VENMO_CLIENT_SECRET;

//Redis Client
var rclient;
if(process.env.REDISTOGO_URL){
  info   = url.parse(process.env.REDISTOGO_URL, true);
  rclient = redis.createClient(info.port, info.hostname, {no_ready_check: true});
}
else {
  rclient = redis.createClient();
}

rclient.on('connect', function() {
    console.log('Redis Connected...');
});

var redisSessionStore = rclient.redisSessionStore;
var sess = {
  store: redisSessionStore,
  secret: 'keyboard cat',
  resave: true,
  saveUninitialized: false,
  cookie: {
    expires: false,
  }
};

app.set('port', process.env.PORT || 8080);
app.set('view engine', 'html');
app.use(session(sess));
app.use(passport.initialize());
app.use(passport.session());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended:true}));

//Serve Static Web For Web App
app.use('/', express.static(path.resolve(__dirname,'../public')));

//console.log(path.resolve(__dirname,'../public'))
var venmoStrategy = new VenmoStrategy({
    clientID: Venmo_CLIENT_ID,
    clientSecret: Venmo_CLIENT_SECRET,
    callbackURL: "http://localhost:8080/auth/venmo/callback"
  },

  function(accessToken, refreshToken, profile, done) {
    console.log("Profile ID :" + profile.id);
    console.log("Access Token: " + accessToken);
    console.log("Refresh Token: " + refreshToken);
    var user = { VenmoId: profile.id, AccessToken:accessToken, RefreshToken:refreshToken};
    rclient.set('venmoCharger', JSON.stringify(user), function (err, reply) {
      if (err) throw err;
      console.log('Venmo Charge Account Saved! User is: ');
      //console.log(user);
      done(err, user);
    });
  }
);

passport.use(venmoStrategy);
refresh.use(venmoStrategy);

passport.serializeUser(function(user, done) {
    console.log('passport serializing...' + user.VenmoId);
    done(null, user.VenmoId);
});

passport.deserializeUser(function(id, done) {
    rclient.get('venmoCharger', function(err, user) {
        if (err) throw err;
        console.log('passport deserializing...'+JSON.parse(user).VenmoId);
        done(err, JSON.parse(user));
    });
});

//Constants
var oathURI = "https://api.venmo.com/v1/oauth/authorize";
var redirectURI = "http://localhost:8080/redirectTo";
var accessTokenURI = "https://api.venmo.com/v1/oauth/access_token";

//TODO: Come Up With Some Better User Flow Here For Updating the Charge Account
app.post(  '/auth/venmo',
          checkPassKey,
          passport.authenticate('venmo', {
            scope: ['make_payments', 'access_feed', 'access_profile', 'access_email', 'access_phone', 'access_balance', 'access_friends'],
            failureRedirect: '/'
          }), function(req, res) {
                res.redirect('/');
              }
        );

app.get(  '/users/venmo',
          function(req, res) {
            getVenmoChargeAccount( function(user) {
                        var venmo = new Venmo(user.AccessToken);
                        venmo.getCurrentUser(function(err,data){
                          var condensed = { username: data.user.username,
                                            displayname: data.user.display_name,
                                            email: data.user.email
                                          };
                          res.json(condensed);
                        });
                    });
          });

app.get( '/auth/venmo/callback',
          passport.authenticate('venmo', {
            failureRedirect: '/'
          }), function(req, res) {
            //Nothing If we got Here (Maybe Render The Client Information if Its Been Inputed Properly?)
            //res.json({user: req.user ? JSON.stringify(req.user) : 'null'});
            res.redirect('/');
          }
        );

app.get( '/auth/venmo/refresh',
          passport.authenticate('venmo', {
            failureRedirect: '/'
          }), function(req, res) {
            res.json({user: req.user ? JSON.stringify(req.user) : 'null'});
          }
        );

function checkPassKey(req,res,next) {
  var passKey = req.body.pwd;
  if(passKeyCheck(passKey)){
    next();
  } else {
    console.log("Incorrect Passkey Was Input");
    res.redirect('/');
  }
}

function passKeyCheck(passkey){
  if(passkey === DISPENSOR_PASSKEY)
    return true;
  else
    return false;
}

function ensureRefreshedToken(req, res, next) {
   //Get Old Access Token
   rclient.get('venmoCharger', function(err, user) {
        if (err) throw err;
        user = JSON.parse(user);
        var oldRefreshToken = user.RefreshToken;
        refresh.requestNewAccessToken('venmo', oldRefreshToken, function(err, accessToken, refreshToken) {
          //Store New Access Token
          if(refreshToken){
            var newUser = _.extend(user,{AccessToken:accessToken, RefreshToken:refreshToken});
              rclient.set('venmoCharger', JSON.stringify(newUser), function (err, reply) {
              if (err) throw err;
              console.log('Venmo Charge Account Refreshed! User is: ');
              console.log(user);
              next();
            });
          } else {
            console.log("Refresh Token was not aquired, Venmo Charge User has not changed.");
            next();
          }
          });
    });
}

//Launch the express server
var server = app.listen(app.get('port'), function() {
  console.log('Dispensor Venmo server listening on port ' + server.address().port);
});

getVenmoChargeAccount = function(callback) {
  rclient.get('venmoCharger', function(err, user) {
    if (err) throw err;
    //console.log(JSON.parse(user));
    callback(JSON.parse(user));
  });
};

//Module Exports -> Export Current Venmo Charge User
module.exports = {
  getVenmoChargeAccount: getVenmoChargeAccount
};
<|endoftext|>


<commit_before>(function() {
  'use strict';

  window.Ractive.controller('layout', function(component, data, el, config, done) {
    var uses = ['usebrand', 'usetitle', 'useprofile', 'usehelp', 'usemask'],
        Title = null;

    $.each(['left', 'right'], function(i, dir) {
      data[dir + 'ContentUsed'] = config && config.partials && config.partials[dir + '-content'];
      data[dir + 'ContentExpended'] = data[dir + '-content-expended'] && data[dir + '-content-expended'] == 'false' ? false : true;
      data[dir + 'ContentTitle'] = data[dir + '-content-title'] || null;
      data[dir + 'ContentWidth'] = data[dir + '-content-width'] ? parseInt(data[dir + '-content-width'], 10) : 0;
      data[dir + 'ContentPos'] = 0;
      data[dir + 'ContentCollapsedAfter'] = false;
      data[dir + 'ContentAreaWidth'] = data[dir + 'ContentUsed'] ? data[dir + 'ContentWidth'] : 30;
    });

    data.titleAreaHeight = 90;

    data.loaded = false;

    for (var i = 0; i < uses.length; i++) {
      var use = uses[i];

      data[use] = typeof data[use] == 'undefined' ? true : data[use];
      data[use] = data[use] == 'true' ? true : data[use];
      data[use] = data[use] == 'false' ? false : data[use];
    }

    var layout = component({
          data: data
        }),
        page = layout.parentRequire,
        _$el = {
          window: $(window),
          content: $(layout.el).find('.layout-content')
        };

    function _refreshDirContent($bar, dir, expended) {
      if (!$bar || !$bar.length) {
        return;
      }

      var $dirContent = $bar.parents('.layout-' + dir + '-container');

      layout.set(dir + 'ContentExpended', expended);
      layout.set(dir + 'ContentAreaWidth', expended ? $dirContent.width() : $bar.width());
      layout.set(dir + 'ContentPos', expended ? 0 : -$dirContent.width() + $bar.width());

      layout.fire('contentResize', {
        width: _$el.window.width() - layout.get('leftContentAreaWidth') - layout.get('rightContentAreaWidth'),
        height: _$el.content.outerHeight(),
        waiting: 550
      });

      if (expended) {
        layout.set(dir + 'ContentCollapsedAfter', false);
      }
      else {
        setTimeout(function() {
          layout.set(dir + 'ContentCollapsedAfter', true);
        }, 550);
      }
    }

    layout.on('toggleLeftBar toggleRightBar', function(event) {
      var dir = event.name == 'toggleLeftBar' ? 'left' : 'right',
          expended = !layout.get(dir + 'ContentExpended');

      _refreshDirContent($(event.node), dir, expended);
    });

    _refreshDirContent($(layout.el).find('.layout-left-bar-container'), 'left', data.leftContentExpended);
    _refreshDirContent($(layout.el).find('.layout-right-bar-container'), 'right', data.rightContentExpended);

    layout.on('titleOpen', function(args) {
      layout.set('titleAreaHeight', args.height + 50);
    });

    layout.on('titleClose', function(args) {
      layout.set('titleAreaHeight', args.height + 50);
    });

    layout.selectApp = function(name, fireFunc, callback) {
      if (!Title) {
        return;
      }

      Title.selectApp(name, fireFunc, callback);
    };

    layout.addApp = function(app, indexOrPosition) {
      if (!Title) {
        return;
      }

      Title.addTitle(app, indexOrPosition);
    };

    layout.removeApp = function(name) {
      if (!Title) {
        return;
      }

      Title.removeTitle(name);
    };

    setTimeout(function() {
      if (data.onloaded) {
        data.onloaded(layout);
      }

      layout.set('loaded', true);

      setTimeout(function() {
        layout.set('beforeStart', true);

        setTimeout(function() {
          $('.layout-mask').remove();

          var beforeRequire = data.beforerequire || function(l, callback) {
            callback();
          };

          beforeRequire(layout, function() {
            layout.set('beforerequire', null);
            data.beforerequire = null;

            layout.set('start', true);

            layout.require().then(function() {
              var Title = layout.findChild('name', 'dropdown-title');

              if (Title) {
                Title.on('open', function(args) {
                  layout.fire('titleOpen', args);
                });

                Title.on('close', function(args) {
                  layout.fire('titleClose', args);
                });

                Title.on('titleSelected', function(args) {
                  var cls = page.get('cls') || [];
                  cls = cls.filter(function(value) {
                    return value.indexOf('app-') !== 0;
                  });

                  cls.push('app-' + args.title.name
                    .toLowerCase()
                    .replace(/[^a-z0-9]/gi, '')
                  );

                  page.set('cls', cls);
                });

                Title.fireSelected();
              }

              done();
            });
          });
        }, 550);

      }, 1500);
    });
  });

})();
<commit_msg>fix(layout): double bind the left and right content options<commit_after>(function() {
  'use strict';

  window.Ractive.controller('layout', function(component, data, el, config, done) {
    var _uses = ['usebrand', 'usetitle', 'useprofile', 'usehelp', 'usemask'],
        _dirs = ['left', 'right'],
        _freezeSet = false,
        Title = null;

    $.each(_dirs, function(i, dir) {
      data[dir + 'ContentUsed'] = config && config.partials && config.partials[dir + '-content'];
      data[dir + 'ContentPos'] = 0;
      data[dir + 'ContentCollapsedAfter'] = false;
      data[dir + 'ContentAreaWidth'] = data[dir + 'ContentUsed'] ? data[dir + 'ContentWidth'] : 30;
    });

    data.titleAreaHeight = 90;

    data.loaded = false;

    for (var i = 0; i < _uses.length; i++) {
      var use = _uses[i];

      data[use] = typeof data[use] == 'undefined' ? true : data[use];
      data[use] = data[use] == 'true' ? true : data[use];
      data[use] = data[use] == 'false' ? false : data[use];
    }

    var layout = component({
          data: data
        }),
        page = layout.parentRequire,
        _$el = {
          window: $(window),
          layout: $(layout.el),
          content: $(layout.el).find('.layout-content')
        };

    function _refreshDirContent(dir, expended) {
      _freezeSet = true;

      var $dirContent = _$el.layout.find('.layout-' + dir + '-container'),
          $bar = $dirContent.find('.layout-' + dir + '-bar-container');

      layout.set(dir + 'ContentExpended', expended);
      layout.set(dir + '-content-expended', expended);
      layout.set(dir + 'ContentAreaWidth', expended ? $dirContent.width() : $bar.width());
      layout.set(dir + 'ContentPos', expended ? 0 : -$dirContent.width() + $bar.width());

      layout.fire('contentResize', {
        width: _$el.window.width() - layout.get('leftContentAreaWidth') - layout.get('rightContentAreaWidth'),
        height: _$el.content.outerHeight(),
        waiting: 550
      });

      if (expended) {
        layout.set(dir + 'ContentCollapsedAfter', false);
      }
      else {
        setTimeout(function() {
          layout.set(dir + 'ContentCollapsedAfter', true);
        }, 550);
      }

      _freezeSet = false;
    }

    $.each(_dirs, function(i, dir) {
      layout.observe(dir + '-content-expended', function(newValue) {
        if (_freezeSet) {
          return;
        }

        var expended = newValue === false || (newValue && newValue == 'false') ? false : true;

        _refreshDirContent(dir, expended);
      });

      layout.observe(dir + '-content-title', function(newValue) {
        if (_freezeSet) {
          return;
        }

        layout.set(dir + 'ContentTitle', newValue || null);
      });

      layout.observe(dir + '-content-width', function(newValue) {
        if (_freezeSet) {
          return;
        }

        layout.set(dir + 'ContentWidth', newValue ? parseInt(newValue, 10) : 0);

        _refreshDirContent(dir, !!layout.get(dir + 'ContentExpended'));
      });
    });

    layout.on('toggleLeftBar toggleRightBar', function(event) {
      var dir = event.name == 'toggleLeftBar' ? 'left' : 'right',
          expended = !layout.get(dir + 'ContentExpended');

      _refreshDirContent(dir, expended);
    });

    _refreshDirContent('left', data.leftContentExpended);
    _refreshDirContent('right', data.rightContentExpended);

    layout.on('titleOpen', function(args) {
      layout.set('titleAreaHeight', args.height + 50);
    });

    layout.on('titleClose', function(args) {
      layout.set('titleAreaHeight', args.height + 50);
    });

    layout.selectApp = function(name, fireFunc, callback) {
      if (!Title) {
        return;
      }

      Title.selectApp(name, fireFunc, callback);
    };

    layout.addApp = function(app, indexOrPosition) {
      if (!Title) {
        return;
      }

      Title.addTitle(app, indexOrPosition);
    };

    layout.removeApp = function(name) {
      if (!Title) {
        return;
      }

      Title.removeTitle(name);
    };

    setTimeout(function() {
      if (data.onloaded) {
        data.onloaded(layout);
      }

      layout.set('loaded', true);

      setTimeout(function() {
        layout.set('beforeStart', true);

        setTimeout(function() {
          $('.layout-mask').remove();

          var beforeRequire = data.beforerequire || function(l, callback) {
            callback();
          };

          beforeRequire(layout, function() {
            layout.set('beforerequire', null);
            data.beforerequire = null;

            layout.set('start', true);

            layout.require().then(function() {
              var Title = layout.findChild('name', 'dropdown-title');

              if (Title) {
                Title.on('open', function(args) {
                  layout.fire('titleOpen', args);
                });

                Title.on('close', function(args) {
                  layout.fire('titleClose', args);
                });

                Title.on('titleSelected', function(args) {
                  var cls = page.get('cls') || [];
                  cls = cls.filter(function(value) {
                    return value.indexOf('app-') !== 0;
                  });

                  cls.push('app-' + args.title.name
                    .toLowerCase()
                    .replace(/[^a-z0-9]/gi, '')
                  );

                  page.set('cls', cls);
                });

                Title.fireSelected();
              }

              done();
            });
          });
        }, 550);

      }, 1500);
    });
  });

})();
<|endoftext|>


<commit_before>/*
 Copyright 2017 Michael Telatynski <7t3chguy@gmail.com>

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
 */

function memberEventDiff(ev) {
    const diff = {
        isMemberEvent: ev.getType() === 'm.room.member' && ev.getStateKey() !== undefined,
    };

    if (!diff.isMemberEvent) return diff;

    const content = ev.getContent();
    const prevContent = ev.getPrevContent();

    diff.isJoin = content.membership === 'join' && prevContent.membership !== 'ban';
    diff.isPart = content.membership === 'leave' && ev.getStateKey() === ev.getSender();

    const isJoinToJoin = content.membership === prevContent.membership && content.membership === 'join';
    diff.isDisplaynameChange = isJoinToJoin && content.displayname !== prevContent.displayname;
    diff.isAvatarChange = isJoinToJoin && content.avatar_url !== prevContent.avatar_url;
    return diff;
}

export default function(ev, syncedSettings) {
    // Hide redacted events
    if (syncedSettings['hideRedactions'] && ev.isRedacted()) return true;

    const eventDiff = memberEventDiff(ev);

    if (eventDiff.isMemberEvent) {
        if (syncedSettings['hideJoinLeaves'] && (eventDiff.isJoin || eventDiff.isPart)) return true;
        const isMemberAvatarDisplaynameChange = eventDiff.isAvatarChange || eventDiff.isDisplaynameChange;
        if (syncedSettings['hideAvatarDisplaynameChanges'] && isMemberAvatarDisplaynameChange) return true;
    }

    return false;
}
<commit_msg>export shouldHideEvent fn named<commit_after>/*
 Copyright 2017 Michael Telatynski <7t3chguy@gmail.com>

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
 */

function memberEventDiff(ev) {
    const diff = {
        isMemberEvent: ev.getType() === 'm.room.member' && ev.getStateKey() !== undefined,
    };

    if (!diff.isMemberEvent) return diff;

    const content = ev.getContent();
    const prevContent = ev.getPrevContent();

    diff.isJoin = content.membership === 'join' && prevContent.membership !== 'ban';
    diff.isPart = content.membership === 'leave' && ev.getStateKey() === ev.getSender();

    const isJoinToJoin = content.membership === prevContent.membership && content.membership === 'join';
    diff.isDisplaynameChange = isJoinToJoin && content.displayname !== prevContent.displayname;
    diff.isAvatarChange = isJoinToJoin && content.avatar_url !== prevContent.avatar_url;
    return diff;
}

export default function shouldHideEvent(ev, syncedSettings) {
    // Hide redacted events
    if (syncedSettings['hideRedactions'] && ev.isRedacted()) return true;

    const eventDiff = memberEventDiff(ev);

    if (eventDiff.isMemberEvent) {
        if (syncedSettings['hideJoinLeaves'] && (eventDiff.isJoin || eventDiff.isPart)) return true;
        const isMemberAvatarDisplaynameChange = eventDiff.isAvatarChange || eventDiff.isDisplaynameChange;
        if (syncedSettings['hideAvatarDisplaynameChanges'] && isMemberAvatarDisplaynameChange) return true;
    }

    return false;
}
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import { connect } from 'react-redux';
import { actions } from 'react-native-navigation-redux-helpers';
import {Image, View} from 'react-native';
import { Container, Header, Title, Button, Left, Right, Body, Icon, List, ListItem, Content, Text , Card, CardItem, Thumbnail} from 'native-base';
import { Actions } from 'react-native-router-flux';
import Video from 'react-native-video';
import VideoPlayer from 'react-native-video-controls';

import styles from './styles';
import { openDrawer, closeDrawer } from '../../actions/drawer';

export default class VideoPlayerElement extends Component {

    render(){
        return (

          <Card style={styles.mb}>
                <CardItem cardBody style={{
                  flex: 1,
                  flexDirection: 'column',
                  justifyContent: 'center',
                  alignItems: 'center',
                }}>
                <VideoPlayer
                    source={{uri:this.props.video.src }}
                    navigator={ this.props.navigator }
                    style={styles.backgroundVideo}
                    resizeMode={ 'contain' }
                    seekColor={ '#FFF' }
                />
                </CardItem>

                <CardItem style={{ paddingVertical: 0 }}>
                    <Left>
                        <Button iconLeft transparent>
                            <Icon active name="thumbs-up" />
                            <Text> {this.props.video.likes} Likes</Text>
                        </Button>
                    </Left>
                    <Body>
                    <Button iconLeft transparent>
                        <Icon active name="chatbubbles" />
                        <Text> {this.props.video.comments} Comments</Text>
                    </Button>
                    </Body>
                    <Right>
                        <Text> {this.props.video.hours}h ago</Text>
                    </Right>
                </CardItem>
            </Card>

        );
    }
}
<commit_msg>pause video-player<commit_after>import React, { Component } from 'react';
import { connect } from 'react-redux';
import { actions } from 'react-native-navigation-redux-helpers';
import {Image, View} from 'react-native';
import { Container, Header, Title, Button, Left, Right, Body, Icon, List, ListItem, Content, Text , Card, CardItem, Thumbnail} from 'native-base';
import { Actions } from 'react-native-router-flux';
import Video from 'react-native-video';
import VideoPlayer from 'react-native-video-controls';

import styles from './styles';
import { openDrawer, closeDrawer } from '../../actions/drawer';

export default class VideoPlayerElement extends Component {

    render(){
        return (

          <Card style={styles.mb}>
                <CardItem cardBody style={{
                  flex: 1,
                  flexDirection: 'column',
                  justifyContent: 'center',
                  alignItems: 'center',
                }}>
                <VideoPlayer
                    source={{uri:this.props.video.src }}
                    navigator={ this.props.navigator }
                    style={styles.backgroundVideo}
                    resizeMode={ 'contain' }
                    seekColor={ '#FFF' }
                    paused={ true }
                />
                </CardItem>

                <CardItem style={{ paddingVertical: 0 }}>
                    <Left>
                        <Button iconLeft transparent>
                            <Icon active name="thumbs-up" />
                            <Text> {this.props.video.likes} Likes</Text>
                        </Button>
                    </Left>
                    <Body>
                    <Button iconLeft transparent>
                        <Icon active name="chatbubbles" />
                        <Text> {this.props.video.comments} Comments</Text>
                    </Button>
                    </Body>
                    <Right>
                        <Text> {this.props.video.hours}h ago</Text>
                    </Right>
                </CardItem>
            </Card>

        );
    }
}
<|endoftext|>


<commit_before>//Generic list of keyframes with timestamps and values

var lerpNumbers = require('./lerp-numbers')
var range = require('unlerp')

function sort(a, b) {
    return a.time - b.time
}

function Keyframes(frames, sorted) {
    if (!(this instanceof Keyframes))
        return new Keyframes(frames)
    this.frames = frames||[]
    if (!sorted)
        this.sort()
}

//Finds the index of the nearest keyframe to the given time stamp.
//If radius is specified, it will return the nearest only within that radius
Keyframes.prototype.getIndex = function(time, radius) {
    radius = typeof radius === 'number' ? radius : Number.MAX_VALUE
    var minDist = Number.MAX_VALUE
    var nearest = -1
    for (var i=0; i<this.frames.length; i++) {
        var dist = Math.abs(this.frames[i].time - time)
        if (dist < minDist && dist <= radius) {
            minDist = dist
            nearest = i
        }
    }
    return nearest
}

//Gets the keyframe at the index
Keyframes.prototype.get = function(time, radius) {
    var idx = this.getIndex(time, radius)
    return idx === -1 ? null : this.frames[idx]
}

//lerps the value at the specified time stamp
//returns null if no keyframes exist
Keyframes.prototype.value = function(time, ease) {
    if (this.frames.length === 0)
        return null

    var prev = -1
    //get last keyframe to time
    for (var i=this.frames.length-1; i>=0; i--) {
        if (time >= this.frames[i].time) {
            prev = i
            break
        }
    }

    //start or end keyframes
    if (prev === -1 || prev === this.frames.length-1) {
        if (prev < 0)
            prev = 0
        return this.frames[prev].value
    }
    else {
        var startFrame = this.frames[prev]
        var endFrame = this.frames[prev+1]

        //clamp and get range
        time = Math.max(startFrame.time, Math.min(time, endFrame.time))
        var t = range(startFrame.time, endFrame.time, time)

        //We ease from left keyframe to right, with a custom easing
        //equation if specified
        if (typeof ease === 'function')
            return ease(startFrame, endFrame, t)

        //Otherwise we assume the values are simple numbers and lerp them
        return lerpNumbers(startFrame.value, endFrame.value, t)
    }
}

Keyframes.prototype.next = function(time) {
    if (this.frames.length <= 1)
        return null

    var cur = -1
    //get last keyframe to time
    for (var i=0; i<this.frames.length; i++) {
        if (time < this.frames[i].time) {
            cur = i
            break
        }
    }
    return cur===-1 ? null : this.frames[cur]
}

Keyframes.prototype.previous = function(time) {
    if (this.frames.length <= 1)
        return null

    var cur = -1
    //get last keyframe to time
    for (var i=this.frames.length-1; i>=0; i--) {
        if (time > this.frames[i].time) {
            cur = i
            break
        }
    }
    return cur===-1 ? null : this.frames[cur]
}

//Adds a frame at the given time stamp
Keyframes.prototype.add = function(frame) {
    this.frames.push(frame)
    this.sort()
}

//convenience for .frames.splice
//if items are inserted, a sort will be applied after insertion
Keyframes.prototype.splice = function(index, howmany, itemsN) {
    this.frames.splice.apply(this.frames, arguments)
    if (arguments.length > 2)
        this.sort()
}

//sorts the keyframes. you should do this after
//adding new keyframes that are not in linear time
Keyframes.prototype.sort = function() {
    this.frames.sort(sort)
}

// //Clears the keyframe list
// Keyframes.prototype.clear = function() {
//     this.frames.length = 0
// }

Object.defineProperty(Keyframes.prototype, "count", {
    get: function() {
        return this.frames.length
    }
})

module.exports = Keyframes<commit_msg>adding clear()<commit_after>//Generic list of keyframes with timestamps and values

var lerpNumbers = require('./lerp-numbers')
var range = require('unlerp')

function sort(a, b) {
    return a.time - b.time
}

function Keyframes(frames, sorted) {
    if (!(this instanceof Keyframes))
        return new Keyframes(frames)
    this.frames = frames||[]
    if (!sorted)
        this.sort()
}

//Finds the index of the nearest keyframe to the given time stamp.
//If radius is specified, it will return the nearest only within that radius
Keyframes.prototype.getIndex = function(time, radius) {
    radius = typeof radius === 'number' ? radius : Number.MAX_VALUE
    var minDist = Number.MAX_VALUE
    var nearest = -1
    for (var i=0; i<this.frames.length; i++) {
        var dist = Math.abs(this.frames[i].time - time)
        if (dist < minDist && dist <= radius) {
            minDist = dist
            nearest = i
        }
    }
    return nearest
}

//Gets the keyframe at the index
Keyframes.prototype.get = function(time, radius) {
    var idx = this.getIndex(time, radius)
    return idx === -1 ? null : this.frames[idx]
}

//lerps the value at the specified time stamp
//returns null if no keyframes exist
Keyframes.prototype.value = function(time, ease) {
    if (this.frames.length === 0)
        return null

    var prev = -1
    //get last keyframe to time
    for (var i=this.frames.length-1; i>=0; i--) {
        if (time >= this.frames[i].time) {
            prev = i
            break
        }
    }

    //start or end keyframes
    if (prev === -1 || prev === this.frames.length-1) {
        if (prev < 0)
            prev = 0
        return this.frames[prev].value
    }
    else {
        var startFrame = this.frames[prev]
        var endFrame = this.frames[prev+1]

        //clamp and get range
        time = Math.max(startFrame.time, Math.min(time, endFrame.time))
        var t = range(startFrame.time, endFrame.time, time)

        //We ease from left keyframe to right, with a custom easing
        //equation if specified
        if (typeof ease === 'function')
            return ease(startFrame, endFrame, t)

        //Otherwise we assume the values are simple numbers and lerp them
        return lerpNumbers(startFrame.value, endFrame.value, t)
    }
}

Keyframes.prototype.next = function(time) {
    if (this.frames.length <= 1)
        return null

    var cur = -1
    //get last keyframe to time
    for (var i=0; i<this.frames.length; i++) {
        if (time < this.frames[i].time) {
            cur = i
            break
        }
    }
    return cur===-1 ? null : this.frames[cur]
}

Keyframes.prototype.previous = function(time) {
    if (this.frames.length <= 1)
        return null

    var cur = -1
    //get last keyframe to time
    for (var i=this.frames.length-1; i>=0; i--) {
        if (time > this.frames[i].time) {
            cur = i
            break
        }
    }
    return cur===-1 ? null : this.frames[cur]
}

//Adds a frame at the given time stamp
Keyframes.prototype.add = function(frame) {
    this.frames.push(frame)
    this.sort()
}

//convenience for .frames.splice
//if items are inserted, a sort will be applied after insertion
Keyframes.prototype.splice = function(index, howmany, itemsN) {
    this.frames.splice.apply(this.frames, arguments)
    if (arguments.length > 2)
        this.sort()
}

//sorts the keyframes. you should do this after
//adding new keyframes that are not in linear time
Keyframes.prototype.sort = function() {
    this.frames.sort(sort)
}

//Clears the keyframe list
Keyframes.prototype.clear = function() {
    this.frames.length = 0
}

Object.defineProperty(Keyframes.prototype, "count", {
    get: function() {
        return this.frames.length
    }
})

module.exports = Keyframes<|endoftext|>