<commit_before>define([
  'extensions/views/view',
  'd3loader!',
  './xaxis',
  './yaxis',
  './yaxisRight',
  './line',
  './stack',
  './linelabel',
  './hover',
  './callout',
  './tooltip',
  'extensions/views/graph/table'
],
function (View, d3, XAxis, YAxis, YAxisRight, Line, Stack, LineLabel, Hover, Callout, Tooltip, GraphTable) {

  var scaleFromStartAndEndDates = {

    getModel: function (groupIndex, modelIndex) {
      if (!this.collection.at(groupIndex) && this.encompassStack) {
        return this.collection.at(groupIndex - 1, modelIndex);
      } else {
        return this.collection.at(groupIndex, modelIndex);
      }
    },

    getXPos: function (groupIndex, modelIndex) {
      groupIndex = groupIndex || 0;
      var model = this.getModel(groupIndex, modelIndex);
      return this.modelToDate(model);
    },
    calcXScale: function () {
      var total = this.collection.first().get('values'),
          start = this.modelToDate(total.first()).toDate(),
          end = this.modelToDate(total.last()).toDate();

      return this.d3.time.scale()
              .domain([start, end])
              .range([0, this.innerWidth]);
    }
  };

  var scaleByStartDate = _.extend({}, scaleFromStartAndEndDates, {
    modelToDate: function (model) {
      return this.getMoment(model.get('_start_at'));
    }
  });

  var scaleByTimestamp = _.extend({}, scaleFromStartAndEndDates, {
    modelToDate: function (model) {
      return this.getMoment(model.get('_timestamp'));
    }
  });

  var Graph = View.extend({

    d3: d3,

    valueAttr: '_count',

    minYDomainExtent: 6,
    numYTicks: 7,

    sharedComponents: {
      xaxis: XAxis,
      yaxis: YAxis,
      yaxisRight: YAxisRight,
      line: Line,
      stack: Stack,
      linelabel: LineLabel,
      callout: Callout,
      hover: Hover,
      tooltip: Tooltip
    },

    initialize: function (options) {
      View.prototype.initialize.apply(this, arguments);

      var collection = this.collection = options.collection;
      this.listenTo(collection, 'reset add remove sync', this.render);

      this.prepareGraphArea();

      this.scales = {};
      this.margin = {};

      if (this.collection.options.axes) {
        this.table = new GraphTable(_.extend(options, {$el: this.figure, valueAttr: this.valueAttr}));
      }

      // initialize graph components
      this.componentInstances = [];
      var defaultComponentOptions = this.getDefaultComponentOptions();
      _.each(this.prop('components'), function (definition) {
        var options = _.extend({}, defaultComponentOptions, definition.options);
        this.componentInstances.push(new definition.view(options));
      }, this);

      if (isClient) {
        $(window).on('resize.' + this.cid, _.bind(this.render, this));
      }
    },

    /**
     * Defines default options that get passed to all graph components.
     * This object will be extended with component-specific options.
     * @returns {Object} Default options that get passed to components
     */
    getDefaultComponentOptions: function () {
      return {
        graph: this,
        collection: this.collection,
        el: this.figure,
        svg: this.svg,
        wrapper: this.wrapper,
        margin: this.margin,
        scales: this.scales
      };
    },

    showLineLabels: function () {
      return this.model && this.model.get('show-line-labels');
    },

    isOneHundredPercent: function () {
      return this.model && this.model.get('one-hundred-percent');
    },

    prepareGraphArea: function () {
      var figure = this.figure = $('
').addClass('graph');
      if (this.showLineLabels()) {
        figure.addClass('graph-with-labels');
      }
      figure.appendTo(this.$el);

      var graphWrapper = this.graphWrapper = $('

');
      graphWrapper.appendTo(figure);

      this.innerEl = $('

');
      this.innerEl.appendTo(graphWrapper);

      var svg = this.svg = this.d3.select(graphWrapper[0]).append('svg');

      // Apply attributes to SVGs so that they're ignored by screenreaders.
      // The WAI-ARIA 1.0 spec says that authors MAY use aria-hidden only
      // if they're hiding content to improve the experience for users
      // of assistive technologies.
      svg.attr('role', 'presentation');
      svg.attr('aria-hidden', 'true');

      this.wrapper = svg.append('g')
        .classed('wrapper', true);
    },

    /**
     * Calculates current factor between size in displayed pixels and logical
     * size.
     */
    scaleFactor: function () {
      return $(this.svg.node()).width() / this.width;
    },

    // Not implemented; override in configuration or subclass
    calcXScale: function () {
      throw 'No x scale defined.';
    },

    // Not implemented; override in configuration or subclass
    calcYScale: function () {
      throw 'No y scale defined.';
    },

    getConfigNames: function () {
      return ['overlay'];
    },

    pxToValue: function (cssVal) {
      if (!_.isString(cssVal)) {
        return null;
      }
      var matches = cssVal.match(/([0-9\.]+)px/);
      return matches ? parseFloat(matches[1]) : null;
    },

    resize: function () {
      var $svg = $(this.svg.node());
      $svg.attr('style', '');
      var width = this.width = $svg.parent().width(),
          height;

      // when both max-width and max-height are defined, scale graph according
      // to this aspect ratio
      var maxWidth = this.pxToValue($svg.css('max-width'));
      var maxHeight = this.pxToValue($svg.css('max-height'));
      var minHeight = this.pxToValue($svg.css('min-height'));

      if (_.isNumber(maxWidth) && _.isNumber(maxHeight)) {
        var aspectRatio = maxWidth / maxHeight;
        height = width / aspectRatio;
        if (_.isNumber(minHeight)) {
          height = Math.max(height, minHeight);
        }
      } else {
        height = $svg.height();
      }
      this.height = height;

      // configure SVG for automatic resize
      this.svg.attr({
        width: '100%',
        height: '100%',
        viewBox: '0 0 ' + width + ' ' + height,
        style: 'max-width:' + width + 'px; max-height:' + height + 'px; display:block;'
      });
      $svg.height(height);

      var innerEl = this.innerEl;
      this.margin.top = innerEl.position().top;
      this.margin.left = innerEl.position().left;

      this.innerWidth = innerEl.outerWidth();
      this.innerHeight = innerEl.outerHeight();

      this.margin.bottom = height - this.margin.top - this.innerHeight;
      this.margin.right = width - this.margin.left - this.innerWidth;

      this.wrapper.attr('transform', [
        'translate(',
        this.margin.left,
        ', ',
        this.margin.top,
        ')'
      ].join(''));
    },

    applyConfig: function (name) {
      var config = this.configs[name];
      if (!config) {
        return;
      }

      if (config.initialize) {
        config.initialize.call(this);
      }

      _.each(config, function (value, key) {
        if (key === 'initialize') {
          return;
        }
        this[key] = value;
      }, this);
    },

    /**
     * The linelabel figcaption is positioned on top of the graph
     * at small screen sizes using position static.
     */
    lineLabelOnTop: function () {
      return this.$el.find('figcaption').css('position') === 'static';
    },

    /**
     * Hide callout during resize if present. Works around bug in iOS Webkit
     * that incorrectly calculates height of inner element.
     */
    resizeWithCalloutHidden: function () {
      var callout = this.$el.find('.callout');
      var calloutHidden = callout.hasClass('performance-hidden');
      callout.addClass('performance-hidden');

      this.resize();

      if (!calloutHidden) {
        callout.removeClass('performance-hidden');
      }
    },

    /**
     * Applies current configuration, then renders components in defined order
     */
    render: function () {
      if (isServer) {
        return;
      }

      View.prototype.render.apply(this, arguments);

      if (this.collection.options) {
        this.currency = this.collection.options.currency;
      }

      this.resizeWithCalloutHidden();

      var configNames = this.getConfigNames();
      if (_.isString(configNames)) {
        configNames = [configNames];
      }

      _.each(configNames, function (configName) {
        this.applyConfig(configName);
      }, this);

      this.scales.x = this.calcXScale();
      this.scales.y = this.calcYScale();

      _.each(this.componentInstances, function (component) {
        _.each(configNames, function (configName) {
          component.applyConfig(configName);
        });
        component.render();
      }, this);
      if (this.table) {
        this.table.render();
      }
    },

    remove: function () {
      if (this.table) {
        this.table.remove();
      }
      _.invoke(this.componentInstances, 'remove');
      $(window).off('resize.' + this.cid);
      return View.prototype.remove.apply(this, arguments);
    },

    configs: {
      hour: scaleByTimestamp,
      day: scaleByStartDate,
      week: scaleByStartDate,
      month: scaleByStartDate,
      quarter: scaleByStartDate,

      ymin: {
        initialize: function () {
          var d3 = this.d3;
          var valueAttr = this.valueAttr;
          var min = d3.min(this.collection.models, function (group) {
            return d3.min(group.get('values').models, function (value) {
              return value.get(valueAttr);
            });
          });
          this.getYMin = min;
        }
      },

      overlay: {
        getYPos: function (groupIndex, modelIndex) {
          var group = this.collection.at(groupIndex);
          var model = group.get('values').at(modelIndex);
          return model.get(this.valueAttr);
        },
        calcYScale: function () {
          var d3 = this.d3;
          var valueAttr = this.valueAttr;
          var max = d3.max(this.collection.models, function (group) {
            return d3.max(group.get('values').models, function (value) {
              return value.get(valueAttr);
            });
          });

          var yScale = this.d3.scale.linear();
          var yMin = this.getYMin || 0;
          var tickValues = this.calculateLinearTicks([yMin, Math.max(max, this.minYDomainExtent)], this.numYTicks);
          yScale.domain(tickValues.extent);
          yScale.rangeRound([this.innerHeight, 0]);
          yScale.tickValueList = tickValues.values;
          return yScale;
        }
      },

      stack: {
        initialize: function () {
          var valueAttr = this.valueAttr;
          var stack = this.d3.layout.stack()
            .values(function (group) {
              return group.get('values').models;
            })
            .y(function (model) {
              return model.get(valueAttr);
            });

          if (this.isOneHundredPercent()) {
            stack.offset(function (data) {
              var lineCount = data.length,
                  lineLength = data[0].length,
                  i, j, sumOfYValues, y0 = [];

              for (j = 0; j < lineLength; ++j) {
                sumOfYValues = 0;
                for (i = 0; i < lineCount; i++) {
                  sumOfYValues += data[i][j][1];
                }
                for (i = 0; i < lineCount; i++) {
                  if (sumOfYValues) {
                    data[i][j][1] = data[i][j][1] / sumOfYValues;
                  } else {
                    data[i][j][1] = null;
                  }
                }
              }
              for (j = 0; j < lineLength; ++j) {
                y0[j] = 0;
              }
              return y0;
            });
          }

          if (this.outStack) {
            stack.out(_.bind(this.outStack, this));
          }

          this.layers = stack(this.collection.models.slice().reverse());
        },
        getYPos: function (groupIndex, modelIndex) {
          if (!this.collection.at(groupIndex)) {
            if (this.collection.at(groupIndex - 1) && this.encompassStack) {
              return 0;
            } else {
              return null;
            }
          }
          var model = this.collection.at(groupIndex).get('values').at(modelIndex);
          var yProperty = this.stackYProperty || 'y';
          if (model[yProperty] === null) {
            return null;
          }
          var y0Property = this.stackY0Property || 'y0';
          return model[y0Property] + model[yProperty];
        },
        getY0Pos: function (groupIndex, modelIndex) {
          var y0Property = this.stackY0Property || 'y0';
          var model = this.collection.at(groupIndex).get('values').at(modelIndex);
          return model[y0Property];
        },
        calcYScale: function () {
          var d3 = this.d3;
          var valueAttr = this.valueAttr;

          var sumAtIndex = _.bind(function (i) {
            return this.collection.reduce(function (memo, group) {
              return memo + group.get('values').at(i).get(valueAttr);
            }, 0);
          }, this);

          var sums = [];
          for (var i = 0; i < this.collection.at(0).get('values').length; i++) {
            sums.push(sumAtIndex(i));
          }
          var max = d3.max(sums);
          var yScale = this.d3.scale.linear();
          var yMin = this.getYMin || 0;
          var tickValues = this.calculateLinearTicks([yMin, Math.max(max, this.minYDomainExtent)], this.numYTicks);
          if (this.isOneHundredPercent()) {
            yScale.domain([0, 1]);
          } else {
            yScale.domain(tickValues.extent);
          }
          yScale.rangeRound([this.innerHeight, 0]);
          yScale.tickValueList = tickValues.values;
          return yScale;
        }
      }
    }
  });

  return Graph;
});
<commit_msg>Release references to components when view is removed<commit_after>define([
  'extensions/views/view',
  'd3loader!',
  './xaxis',
  './yaxis',
  './yaxisRight',
  './line',
  './stack',
  './linelabel',
  './hover',
  './callout',
  './tooltip',
  'extensions/views/graph/table'
],
function (View, d3, XAxis, YAxis, YAxisRight, Line, Stack, LineLabel, Hover, Callout, Tooltip, GraphTable) {

  var scaleFromStartAndEndDates = {

    getModel: function (groupIndex, modelIndex) {
      if (!this.collection.at(groupIndex) && this.encompassStack) {
        return this.collection.at(groupIndex - 1, modelIndex);
      } else {
        return this.collection.at(groupIndex, modelIndex);
      }
    },

    getXPos: function (groupIndex, modelIndex) {
      groupIndex = groupIndex || 0;
      var model = this.getModel(groupIndex, modelIndex);
      return this.modelToDate(model);
    },
    calcXScale: function () {
      var total = this.collection.first().get('values'),
          start = this.modelToDate(total.first()).toDate(),
          end = this.modelToDate(total.last()).toDate();

      return this.d3.time.scale()
              .domain([start, end])
              .range([0, this.innerWidth]);
    }
  };

  var scaleByStartDate = _.extend({}, scaleFromStartAndEndDates, {
    modelToDate: function (model) {
      return this.getMoment(model.get('_start_at'));
    }
  });

  var scaleByTimestamp = _.extend({}, scaleFromStartAndEndDates, {
    modelToDate: function (model) {
      return this.getMoment(model.get('_timestamp'));
    }
  });

  var Graph = View.extend({

    d3: d3,

    valueAttr: '_count',

    minYDomainExtent: 6,
    numYTicks: 7,

    sharedComponents: {
      xaxis: XAxis,
      yaxis: YAxis,
      yaxisRight: YAxisRight,
      line: Line,
      stack: Stack,
      linelabel: LineLabel,
      callout: Callout,
      hover: Hover,
      tooltip: Tooltip
    },

    initialize: function (options) {
      View.prototype.initialize.apply(this, arguments);

      var collection = this.collection = options.collection;
      this.listenTo(collection, 'reset add remove sync', this.render);

      this.prepareGraphArea();

      this.scales = {};
      this.margin = {};

      if (this.collection.options.axes) {
        this.table = new GraphTable(_.extend(options, {$el: this.figure, valueAttr: this.valueAttr}));
      }

      // initialize graph components
      this.componentInstances = [];
      var defaultComponentOptions = this.getDefaultComponentOptions();
      _.each(this.prop('components'), function (definition) {
        var options = _.extend({}, defaultComponentOptions, definition.options);
        this.componentInstances.push(new definition.view(options));
      }, this);

      if (isClient) {
        $(window).on('resize.' + this.cid, _.bind(this.render, this));
      }
    },

    /**
     * Defines default options that get passed to all graph components.
     * This object will be extended with component-specific options.
     * @returns {Object} Default options that get passed to components
     */
    getDefaultComponentOptions: function () {
      return {
        graph: this,
        collection: this.collection,
        el: this.figure,
        svg: this.svg,
        wrapper: this.wrapper,
        margin: this.margin,
        scales: this.scales
      };
    },

    showLineLabels: function () {
      return this.model && this.model.get('show-line-labels');
    },

    isOneHundredPercent: function () {
      return this.model && this.model.get('one-hundred-percent');
    },

    prepareGraphArea: function () {
      var figure = this.figure = $('
').addClass('graph');
      if (this.showLineLabels()) {
        figure.addClass('graph-with-labels');
      }
      figure.appendTo(this.$el);

      var graphWrapper = this.graphWrapper = $('

');
      graphWrapper.appendTo(figure);

      this.innerEl = $('

');
      this.innerEl.appendTo(graphWrapper);

      var svg = this.svg = this.d3.select(graphWrapper[0]).append('svg');

      // Apply attributes to SVGs so that they're ignored by screenreaders.
      // The WAI-ARIA 1.0 spec says that authors MAY use aria-hidden only
      // if they're hiding content to improve the experience for users
      // of assistive technologies.
      svg.attr('role', 'presentation');
      svg.attr('aria-hidden', 'true');

      this.wrapper = svg.append('g')
        .classed('wrapper', true);
    },

    /**
     * Calculates current factor between size in displayed pixels and logical
     * size.
     */
    scaleFactor: function () {
      return $(this.svg.node()).width() / this.width;
    },

    // Not implemented; override in configuration or subclass
    calcXScale: function () {
      throw 'No x scale defined.';
    },

    // Not implemented; override in configuration or subclass
    calcYScale: function () {
      throw 'No y scale defined.';
    },

    getConfigNames: function () {
      return ['overlay'];
    },

    pxToValue: function (cssVal) {
      if (!_.isString(cssVal)) {
        return null;
      }
      var matches = cssVal.match(/([0-9\.]+)px/);
      return matches ? parseFloat(matches[1]) : null;
    },

    resize: function () {
      var $svg = $(this.svg.node());
      $svg.attr('style', '');
      var width = this.width = $svg.parent().width(),
          height;

      // when both max-width and max-height are defined, scale graph according
      // to this aspect ratio
      var maxWidth = this.pxToValue($svg.css('max-width'));
      var maxHeight = this.pxToValue($svg.css('max-height'));
      var minHeight = this.pxToValue($svg.css('min-height'));

      if (_.isNumber(maxWidth) && _.isNumber(maxHeight)) {
        var aspectRatio = maxWidth / maxHeight;
        height = width / aspectRatio;
        if (_.isNumber(minHeight)) {
          height = Math.max(height, minHeight);
        }
      } else {
        height = $svg.height();
      }
      this.height = height;

      // configure SVG for automatic resize
      this.svg.attr({
        width: '100%',
        height: '100%',
        viewBox: '0 0 ' + width + ' ' + height,
        style: 'max-width:' + width + 'px; max-height:' + height + 'px; display:block;'
      });
      $svg.height(height);

      var innerEl = this.innerEl;
      this.margin.top = innerEl.position().top;
      this.margin.left = innerEl.position().left;

      this.innerWidth = innerEl.outerWidth();
      this.innerHeight = innerEl.outerHeight();

      this.margin.bottom = height - this.margin.top - this.innerHeight;
      this.margin.right = width - this.margin.left - this.innerWidth;

      this.wrapper.attr('transform', [
        'translate(',
        this.margin.left,
        ', ',
        this.margin.top,
        ')'
      ].join(''));
    },

    applyConfig: function (name) {
      var config = this.configs[name];
      if (!config) {
        return;
      }

      if (config.initialize) {
        config.initialize.call(this);
      }

      _.each(config, function (value, key) {
        if (key === 'initialize') {
          return;
        }
        this[key] = value;
      }, this);
    },

    /**
     * The linelabel figcaption is positioned on top of the graph
     * at small screen sizes using position static.
     */
    lineLabelOnTop: function () {
      return this.$el.find('figcaption').css('position') === 'static';
    },

    /**
     * Hide callout during resize if present. Works around bug in iOS Webkit
     * that incorrectly calculates height of inner element.
     */
    resizeWithCalloutHidden: function () {
      var callout = this.$el.find('.callout');
      var calloutHidden = callout.hasClass('performance-hidden');
      callout.addClass('performance-hidden');

      this.resize();

      if (!calloutHidden) {
        callout.removeClass('performance-hidden');
      }
    },

    /**
     * Applies current configuration, then renders components in defined order
     */
    render: function () {
      if (isServer) {
        return;
      }

      View.prototype.render.apply(this, arguments);

      if (this.collection.options) {
        this.currency = this.collection.options.currency;
      }

      this.resizeWithCalloutHidden();

      var configNames = this.getConfigNames();
      if (_.isString(configNames)) {
        configNames = [configNames];
      }

      _.each(configNames, function (configName) {
        this.applyConfig(configName);
      }, this);

      this.scales.x = this.calcXScale();
      this.scales.y = this.calcYScale();

      _.each(this.componentInstances, function (component) {
        _.each(configNames, function (configName) {
          component.applyConfig(configName);
        });
        component.render();
      }, this);
      if (this.table) {
        this.table.render();
      }
    },

    remove: function () {
      if (this.table) {
        this.table.remove();
      }
      _.invoke(this.componentInstances, 'remove');
      this.componentInstances = [];
      $(window).off('resize.' + this.cid);
      return View.prototype.remove.apply(this, arguments);
    },

    configs: {
      hour: scaleByTimestamp,
      day: scaleByStartDate,
      week: scaleByStartDate,
      month: scaleByStartDate,
      quarter: scaleByStartDate,

      ymin: {
        initialize: function () {
          var d3 = this.d3;
          var valueAttr = this.valueAttr;
          var min = d3.min(this.collection.models, function (group) {
            return d3.min(group.get('values').models, function (value) {
              return value.get(valueAttr);
            });
          });
          this.getYMin = min;
        }
      },

      overlay: {
        getYPos: function (groupIndex, modelIndex) {
          var group = this.collection.at(groupIndex);
          var model = group.get('values').at(modelIndex);
          return model.get(this.valueAttr);
        },
        calcYScale: function () {
          var d3 = this.d3;
          var valueAttr = this.valueAttr;
          var max = d3.max(this.collection.models, function (group) {
            return d3.max(group.get('values').models, function (value) {
              return value.get(valueAttr);
            });
          });

          var yScale = this.d3.scale.linear();
          var yMin = this.getYMin || 0;
          var tickValues = this.calculateLinearTicks([yMin, Math.max(max, this.minYDomainExtent)], this.numYTicks);
          yScale.domain(tickValues.extent);
          yScale.rangeRound([this.innerHeight, 0]);
          yScale.tickValueList = tickValues.values;
          return yScale;
        }
      },

      stack: {
        initialize: function () {
          var valueAttr = this.valueAttr;
          var stack = this.d3.layout.stack()
            .values(function (group) {
              return group.get('values').models;
            })
            .y(function (model) {
              return model.get(valueAttr);
            });

          if (this.isOneHundredPercent()) {
            stack.offset(function (data) {
              var lineCount = data.length,
                  lineLength = data[0].length,
                  i, j, sumOfYValues, y0 = [];

              for (j = 0; j < lineLength; ++j) {
                sumOfYValues = 0;
                for (i = 0; i < lineCount; i++) {
                  sumOfYValues += data[i][j][1];
                }
                for (i = 0; i < lineCount; i++) {
                  if (sumOfYValues) {
                    data[i][j][1] = data[i][j][1] / sumOfYValues;
                  } else {
                    data[i][j][1] = null;
                  }
                }
              }
              for (j = 0; j < lineLength; ++j) {
                y0[j] = 0;
              }
              return y0;
            });
          }

          if (this.outStack) {
            stack.out(_.bind(this.outStack, this));
          }

          this.layers = stack(this.collection.models.slice().reverse());
        },
        getYPos: function (groupIndex, modelIndex) {
          if (!this.collection.at(groupIndex)) {
            if (this.collection.at(groupIndex - 1) && this.encompassStack) {
              return 0;
            } else {
              return null;
            }
          }
          var model = this.collection.at(groupIndex).get('values').at(modelIndex);
          var yProperty = this.stackYProperty || 'y';
          if (model[yProperty] === null) {
            return null;
          }
          var y0Property = this.stackY0Property || 'y0';
          return model[y0Property] + model[yProperty];
        },
        getY0Pos: function (groupIndex, modelIndex) {
          var y0Property = this.stackY0Property || 'y0';
          var model = this.collection.at(groupIndex).get('values').at(modelIndex);
          return model[y0Property];
        },
        calcYScale: function () {
          var d3 = this.d3;
          var valueAttr = this.valueAttr;

          var sumAtIndex = _.bind(function (i) {
            return this.collection.reduce(function (memo, group) {
              return memo + group.get('values').at(i).get(valueAttr);
            }, 0);
          }, this);

          var sums = [];
          for (var i = 0; i < this.collection.at(0).get('values').length; i++) {
            sums.push(sumAtIndex(i));
          }
          var max = d3.max(sums);
          var yScale = this.d3.scale.linear();
          var yMin = this.getYMin || 0;
          var tickValues = this.calculateLinearTicks([yMin, Math.max(max, this.minYDomainExtent)], this.numYTicks);
          if (this.isOneHundredPercent()) {
            yScale.domain([0, 1]);
          } else {
            yScale.domain(tickValues.extent);
          }
          yScale.rangeRound([this.innerHeight, 0]);
          yScale.tickValueList = tickValues.values;
          return yScale;
        }
      }
    }
  });

  return Graph;
});
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import '../../App.css';
import Header from './header'
import DepartureItem from './departure-item'
let request = require('superagent');

class BusStop extends Component {

  constructor(props) {
    super(props);
    this.state = {
      result: []
    }
  }

  addDepartureItem() {
    if (this.state.result.Departure) {
      return this.state.result.map( (e, i) => <DepartureItem key={i} data={e} />);
    }
  }

  fetchData() {
    request
      .get(this.props.url + '/vasttrafik/' + this.props.data.id)
      .accept('json')
      .end(function(err, res){
        if (res) {
          res = JSON.parse(res.text)
          console.log(res.Departure);
          this.update(res.Departure)
        }
  });
  }

  update(response) {
    this.setState({
      result: response
    });
  }

  componentWillMount() {
    this.fetchData();
  }

  componentDidMount() {
     window.setInterval(function() {
      this.fetchData();
    }.bind(this), 10000);
  }

  render() {
    return (

          {this.addDepartureItem()}

    );
  }
}

export default BusStop;
<commit_msg>working Västtrafik widget<commit_after>import React, { Component } from 'react';
import '../../App.css';
import Header from './header'
import DepartureItem from './departure-item'
let request = require('superagent');

class BusStop extends Component {

  constructor(props) {
    super(props);
    this.state = {
      result: []
    }
  }

  addDepartureItem() {
    if (this.state.result) {
      return this.state.result.map( (e, i) => <DepartureItem key={i} data={e} />);
    }
  }

  fetchData() {
    request
      .get(this.props.url + '/vasttrafik/' + this.props.data.id)
      .accept('json')
      .end( (err, res) => {
        if (res) {
          res = JSON.parse(res.text);
          console.log(res.Departure);
          this.setState({
            result: res.Departure
          });
        }
      });
  }

  componentWillMount() {
    this.fetchData();
  }

  componentDidMount() {
     window.setInterval(function() {
      this.fetchData();
    }.bind(this), 10000);
  }

  render() {
    return (

          {this.addDepartureItem()}

    );
  }
}

export default BusStop;
<|endoftext|>


<commit_before>var express = require('express')
var app = express()
var http=require('http')

var inbounds=0

var xml2js = require('xml2js')

app.set('view engine', 'pug')

app.get('/', function (req, res) {
  res.render(
      'index',
      { title: 'INBOUND SMS', message: 'Inbound Number '+inbounds+' Received!'})
})

app.get('/inbound', function (req, res) {
    console.log('inbound text received')
    inbounds++
    console.log(req.query.xml)
    xml2js.parseString(req.query.xml, { explicitArray : false, ignoreAttrs : true }, function (err, result) {
        var jsonResults=JSON.stringify(result)
        jsonResults=jsonResults.trim()
        console.log(jsonResults)
        var obj = JSON.parse(jsonResults)
        obj=obj.trim()
        console.log(obj.trumpia)
    });
    res.render(
        'index',
        { title: 'INBOUND SMS', message: 'Inbound Number '+inbounds+' Received!'})
})

app.listen(8080, function () {
    console.log('Example app listening on port 8080!')
})
<commit_msg>commit<commit_after>var express = require('express')
var app = express()
var http=require('http')

var inbounds=0

var xml2js = require('xml2js')

app.set('view engine', 'pug')

app.get('/', function (req, res) {
  res.render(
      'index',
      { title: 'INBOUND SMS', message: 'Inbound Number '+inbounds+' Received!'})
})

app.get('/inbound', function (req, res) {
    console.log('inbound text received')
    inbounds++
    console.log(req.query.xml)
    xml2js.parseString(req.query.xml, { explicitArray : false, ignoreAttrs : true }, function (err, result) {
        var jsonResults=JSON.stringify(result)
        console.log(jsonResults)
        var obj = JSON.parse(jsonResults)
        obj=JSON.trim(obj)
        console.log(obj.trumpia)
    });
    res.render(
        'index',
        { title: 'INBOUND SMS', message: 'Inbound Number '+inbounds+' Received!'})
})

app.listen(8080, function () {
    console.log('Example app listening on port 8080!')
})
<|endoftext|>


<commit_before>
// Very common passwords, and passwords based on app name
// Should be extended to at least top 1k common passwords in non demo apps
export const restrictedPasswords = [
  'booktrader',
  'book',
  'trade',
  '123456',
  'password',
  '12345678',
  'qwerty',
  '12345',
  '123456789',
  'football',
  '1234',
  '1234567',
  'baseball',
  'welcome',
  '1234567890',
  'abc123',
  '123abc',
  '111111',
  '1qaz2wsx',
  'dragon',
  'master',
  'monkey',
  'letmein',
  'login',
  'princess',
  'qwertyuiop',
  'passw0rd',
  'starwars',
  'default',
];
<commit_msg>Create constant for appURL<commit_after>
// Very common passwords, and passwords based on app name
// Should be extended to at least top 1k common passwords in non demo apps
export const restrictedPasswords = [
  'booktrader',
  'book',
  'trade',
  '123456',
  'password',
  '12345678',
  'qwerty',
  '12345',
  '123456789',
  'football',
  '1234',
  '1234567',
  'baseball',
  'welcome',
  '1234567890',
  'abc123',
  '123abc',
  '111111',
  '1qaz2wsx',
  'dragon',
  'master',
  'monkey',
  'letmein',
  'login',
  'princess',
  'qwertyuiop',
  'passw0rd',
  'starwars',
  'default',
];

export const appUrl = location.origin;
<|endoftext|>


<commit_before>const koa = require('koa')
const qs = require('koa-qs')
const serve = require('koa-static')
const compress = require('koa-compress')
const log = require('debug')('quad-blog:server')
const responseTime = require('koa-response-time')

log('loading api and models')
const modelLoader = require('./models')
const apiLoader = require('./api')

log('loading and building config')
const configMaker = require('./config')
const config = configMaker()

log('initializing models and api')
const models = modelLoader(config.database)
const api = apiLoader({models})

/**
 * Server
 */
const server = qs(koa())
Object.assign(server, {
  experimental: true,
  env: config.env,
  config,
  models,
  api
})

/**
 * Middleware
 */

// request logging
if (server.env === 'development') {
  const logger = require('koa-logger')
  server.use(logger())
}

// x-response-time
server.use(responseTime())

// compression
server.use(compress())

// file server
server.use(serve('./public'))

/**
 * API
 */
log('mounting API')
server.use(api)

/**
 * Server initializer
 * Listen for connections
 * @returns {object} server instance
 */
server.init = function init (port=config.port) {
  log('initializing server using port ${port}')
  server.server = server.listen(port, () => log(`listening on port ${port}`))
  return server.server
}

// Export server so it can be started externally
module.exports = server

// Initialize server if called directly
if (require.main === module) {
  server.init()
}
<commit_msg>Disable delegate compression to nginx<commit_after>const koa = require('koa')
const qs = require('koa-qs')
const serve = require('koa-static')
const compress = require('koa-compress')
const log = require('debug')('quad-blog:server')
const responseTime = require('koa-response-time')

log('loading api and models')
const modelLoader = require('./models')
const apiLoader = require('./api')

log('loading and building config')
const configMaker = require('./config')
const config = configMaker()

log('initializing models and api')
const models = modelLoader(config.database)
const api = apiLoader({models})

/**
 * Server
 */
const server = qs(koa())
Object.assign(server, {
  experimental: true,
  env: config.env,
  config,
  models,
  api
})

/**
 * Middleware
 */

// request logging
if (server.env === 'development') {
  const logger = require('koa-logger')
  server.use(logger())
}

// x-response-time
server.use(responseTime())

// compression
// server.use(compress())

// file server
server.use(serve('./public'))

/**
 * API
 */
log('mounting API')
server.use(api)

/**
 * Server initializer
 * Listen for connections
 * @returns {object} server instance
 */
server.init = function init (port=config.port) {
  log('initializing server using port ${port}')
  server.server = server.listen(port, () => log(`listening on port ${port}`))
  return server.server
}

// Export server so it can be started externally
module.exports = server

// Initialize server if called directly
if (require.main === module) {
  server.init()
}
<|endoftext|>


<commit_before>/**
 * Created by danli on 4/14/2017.
 */
import { Template } from 'meteor/templating';
import { Meteor } from 'meteor/meteor';
import { FlowRouter } from 'meteor/kadira:flow-router';

Template.Cas_Login.events({
  /**
   * Handle the click on the logout link.
   * @param event The click event.
   * @returns {boolean} False.
   */
  'click .cas-logout': function casLogout(event) {
    event.preventDefault();
    Meteor.logout();
    FlowRouter.go('Landing_Page');
    return false;
  },

  /**
   * Handle the click on the login link.
   * @param event The click event.
   * @returns {boolean} False.
   */
  'click .cas-login': function casLogin(event, instance) {
    event.preventDefault();
    const callback = function loginCallback(error) {
      if (error) {
        console.log(error);
      }
    };
    if (Meteor.loginWithCas(callback)) {
      FlowRouter.go('Home_Page');
    }
    return false;
  },
});

// Here's how to do the required initialization for Semantic UI dropdown menus.
Template.Cas_Login.onRendered(function enableDropDown() {
  this.$('.dropdown').dropdown({
    action: 'select',
  });
});<commit_msg>Update routing for login.<commit_after>/**
 * Created by danli on 4/14/2017.
 */
import { Template } from 'meteor/templating';
import { Meteor } from 'meteor/meteor';
import { FlowRouter } from 'meteor/kadira:flow-router';

Template.Cas_Login.events({
  /**
   * Handle the click on the logout link.
   * @param event The click event.
   * @returns {boolean} False.
   */
  'click .cas-logout': function casLogout(event) {
    event.preventDefault();
    Meteor.logout();
    FlowRouter.go('Landing_Page');
    return false;
  },

  /**
   * Handle the click on the login link.
   * @param event The click event.
   * @returns {boolean} False.
   */
  'click .cas-login': function casLogin(event, instance) {
    event.preventDefault();
    const callback = function loginCallback(error) {
      if (error) {
        console.log(error);
      } else {
        FlowRouter.go('Home_Page');
      }
    };
    Meteor.loginWithCas(callback);
    return false;
  },
});

// Here's how to do the required initialization for Semantic UI dropdown menus.
Template.Cas_Login.onRendered(function enableDropDown() {
  this.$('.dropdown').dropdown({
    action: 'select',
  });
});<|endoftext|>


<commit_before>var port = "8001";
var host = "0.0.0.0";
var Express = require('express');

var app = Express();

var rules = require('./app/index.js');
app.use(rules);

app.listen(port, host);

console.log("Yay, it worked!");<commit_msg>change host and port to work for heroku<commit_after>var port = process.env.PORT || "8001";
var host = process.env.HOST || "0.0.0.0";
var Express = require('express');

var app = Express();

var rules = require('./app/index.js');
app.use(rules);

app.listen(port, host);

console.log("Yay, it worked!");<|endoftext|>


<commit_before>var utility = require('./utility');
var db = require('./db');
var async = require('async');
var redis = require('./redis').client;

function mergeObjects(merge, val) {
    for (var attr in val) {
        if (val[attr].constructor === Array) {
            merge[attr] = merge[attr].concat(val[attr]);
        }
        else if (typeof val[attr] === "object") {
            mergeObjects(merge[attr], val[attr]);
        }
        else {
            //does property exist?
            if (!merge[attr]) {
                merge[attr] = val[attr];
            }
            else {
                merge[attr] += val[attr];
            }
        }
    }
}

function mergeMatchData(match, constants) {
    var heroes = match.parsed_data.heroes;
    //loop through all units
    //look up corresponding hero_id
    //hero if can find in constants
    //find player slot associated with that unit(hero_to_slot)
    //merge into player's primary unit
    //if not hero attempt to associate with a hero
    for (var key in heroes) {
        var val = heroes[key];
        var primary = key;
        if (constants.hero_names[key]) {
            //is a hero
            //merging multiple heroes together, only occurs in ARDM
            //doesn't work for 1v1, but ARDM is only played with 10 players
            var hero_id = constants.hero_names[key].id;
            var slot = match.parsed_data.hero_to_slot[hero_id];
            if (match.players[slot]) {
                var primary_id = match.players[slot].hero_id;
                primary = constants.heroes[primary_id].name;
                //build hero_ids for each player
                if (!match.players[slot].hero_ids) {
                    match.players[slot].hero_ids = [];
                }
                match.players[slot].hero_ids.push(hero_id);
            }
            else {
                console.log("couldn't find slot for hero id %s", hero_id);
            }
        }
        else {
            //is not a hero
            primary = getAssociatedHero(key, heroes);
        }
        if (key !== primary) {
            //merge the objects into primary, but not with itself
            mergeObjects(heroes[primary], val);
        }
    }
    return match;
}

function getAssociatedHero(unit, heroes) {
    //assume all illusions belong to that hero
    if (unit.slice(0, "illusion_".length) === "illusion_") {
        unit = unit.slice("illusion_".length);
    }
    //attempt to recover hero name from unit
    if (unit.slice(0, "npc_dota_".length) === "npc_dota_") {
        //split by _
        var split = unit.split("_");
        //get the third element
        var identifiers = [split[2], split[2] + "_" + split[3]];
        identifiers.forEach(function(id) {
            //append to npc_dota_hero_, see if matches
            var attempt = "npc_dota_hero_" + id;
            if (heroes[attempt]) {
                unit = attempt;
            }
        });
    }
    return unit;
}

function generateGraphData(match, constants) {
    var oneVone = (match.players.length === 2);
    if (oneVone) {
        //rebuild parsed data players array if 1v1 match
        match.parsed_data.players = [match.parsed_data.players[0], match.parsed_data.players[5]];
    }
    //compute graphs
    var goldDifference = ['Gold'];
    var xpDifference = ['XP'];
    for (var i = 0; i < match.parsed_data.times.length; i++) {
        var goldtotal = 0;
        var xptotal = 0;
        match.parsed_data.players.forEach(function(elem, j) {
            if (match.players[j].player_slot < 64) {
                goldtotal += elem.gold[i];
                xptotal += elem.xp[i];
            }
            else {
                xptotal -= elem.xp[i];
                goldtotal -= elem.gold[i];
            }
        });
        goldDifference.push(goldtotal);
        xpDifference.push(xptotal);
    }
    var time = ["time"].concat(match.parsed_data.times);
    var data = {
        difference: [time, goldDifference, xpDifference],
        gold: [time],
        xp: [time],
        lh: [time]
    };
    match.parsed_data.players.forEach(function(elem, i) {
        var hero = constants.heroes[match.players[i].hero_id].localized_name + (oneVone ? " - " + match.players[i].personaname : "");
        data.gold.push([hero].concat(elem.gold));
        data.xp.push([hero].concat(elem.xp));
        data.lh.push([hero].concat(elem.lh));
    });

    //data for income chart
    var gold_reasons = [];
    var columns = [];
    var categories = [];
    var orderedPlayers = match.players.slice(0);
    orderedPlayers.sort(function(a, b) {
        return b.gold_per_min - a.gold_per_min;
    });
    //console.log(orderedPlayers);
    orderedPlayers.forEach(function(player) {
        var hero = constants.heroes[player.hero_id];
        categories.push(hero.localized_name);
    });
    for (var key in constants.gold_reasons) {
        var reason = constants.gold_reasons[key];
        gold_reasons.push(reason);
        var col = [reason];
        orderedPlayers.forEach(function(player) {
            var hero = constants.heroes[player.hero_id];
            var parsedHero = match.parsed_data.heroes[hero.name];
            col.push(parsedHero.gold_log[key] || 0);
        });
        columns.push(col);
    }

    data.cats = categories;
    data.goldCols = columns;
    data.gold_reasons = gold_reasons;
    match.graphData = data;
    return match;
}

function fillPlayerNames(players, cb) {
    async.mapSeries(players, function(player, cb) {
        db.players.findOne({
            account_id: player.account_id
        }, function(err, dbPlayer) {
            if (dbPlayer) {
                for (var prop in dbPlayer) {
                    player[prop] = dbPlayer[prop];
                }
            }
            cb(err);
        });
    }, function(err) {
        cb(err);
    });
}

function computeStatistics(player, cb) {
    var counts = {};
    var against = {};
    var together = {};
    db.matches.find({
        'players.account_id': player.account_id
    }, {
        fields: {
            players: 1,
            match_id: 1,
            radiant_win: 1
        }
    }).each(function(match) {
        var playerRadiant = player.radiantMap[match.match_id];
        for (var j = 0; j < match.players.length; j++) {
            var tm = match.players[j];
            var tm_hero = tm.hero_id;
            if (utility.isRadiant(tm) === playerRadiant) {
                //count teammate players
                if (!counts[tm.account_id]) {
                    counts[tm.account_id] = {
                        account_id: tm.account_id,
                        win: 0,
                        lose: 0,
                        games: 0
                    };
                }
                counts[tm.account_id].games += 1;
                playerRadiant === match.radiant_win ? counts[tm.account_id].win += 1 : counts[tm.account_id].lose += 1;
                //count teammate heroes
                if (!together[tm_hero]) {
                    together[tm_hero] = {
                        games: 0,
                        win: 0,
                        lose: 0
                    };
                }
                together[tm_hero].games += 1;
                playerRadiant === match.radiant_win ? together[tm_hero].win += 1 : together[tm_hero].lose += 1;
            }
            else {
                //count enemy heroes
                if (!against[tm_hero]) {
                    against[tm_hero] = {
                        games: 0,
                        win: 0,
                        lose: 0
                    };
                }
                against[tm_hero].games += 1;
                playerRadiant === match.radiant_win ? against[tm_hero].win += 1 : against[tm_hero].lose += 1;
            }
        }
    }).error(function(err) {
        return cb(err);
    }).success(function() {
        player.together = together;
        player.against = against;
        player.teammates = [];
        for (var id in counts) {
            var count = counts[id];
            player.teammates.push(count);
        }
        fillPlayerNames(player.teammates, function(err) {
            cb(err);
        });
    });
}

function fillPlayerMatches(player, constants, matchups, cb) {
    var account_id = player.account_id;
    db.matches.find({
        'players.account_id': account_id
    }, {
        fields: {
            start_time: 1,
            match_id: 1,
            game_mode: 1,
            duration: 1,
            cluster: 1,
            radiant_win: 1,
            parse_status: 1,
            "players.$": 1
        },
        sort: {
            match_id: -1
        }
    }, function(err, matches) {
        if (err) {
            cb(err);
        }
        player.win = 0;
        player.lose = 0;
        player.games = 0;
        player.histogramData = {};
        player.radiantMap = {};
        var calheatmap = {};
        var arr = Array.apply(null, new Array(120)).map(Number.prototype.valueOf, 0);
        var arr2 = Array.apply(null, new Array(120)).map(Number.prototype.valueOf, 0);
        var heroes = {};
        for (var id in constants.heroes) {
            heroes[id] = {
                hero_id: id,
                games: 0,
                win: 0,
                lose: 0
            };
        }
        player.heroes = [];
        for (var i = 0; i < matches.length; i++) {
            var p = matches[i].players[0];
            player.radiantMap[matches[i].match_id] = utility.isRadiant(p);
            matches[i].player_win = (utility.isRadiant(p) === matches[i].radiant_win); //did the player win?
            //aggregate only if valid match
            if (constants.modes[matches[i].game_mode].balanced) {
                calheatmap[matches[i].start_time] = 1;
                var mins = Math.floor(matches[i].duration / 60) % 120;
                arr[mins] += 1;
                var gpm = Math.floor(matches[i].players[0].gold_per_min / 10) % 120;
                arr2[gpm] += 1;
                player.games += 1;
                matches[i].player_win ? player.win += 1 : player.lose += 1;
                if (heroes[p.hero_id]) {
                    heroes[p.hero_id].games += 1;
                    matches[i].player_win ? heroes[p.hero_id].win += 1 : heroes[p.hero_id].lose += 1;
                }
            }
        }
        for (var id in heroes) {
            player.heroes.push(heroes[id]);
        }
        player.heroes.sort(function(a, b) {
            return b.games - a.games;
        });
        player.matches = matches;
        player.histogramData.durations = arr;
        player.histogramData.gpms = arr2;
        player.histogramData.calheatmap = calheatmap;
        if (matchups) {
            computeStatistics(player, function(err) {
                cb(err);
            });
        }
        else {
            cb(err);
        }
    });
}

function getRatingData(req, cb) {
    if (!req.user){
        return cb(null);
    }
    var account_id = req.user.account_id;
    async.parallel({
        "bots": function(cb) {
            redis.get("bots", function(err, bots) {
                bots = JSON.parse(bots);
                //sort list of bots descending, but > 200 go to end
                bots.sort(function(a, b) {
                    if (a.friends > 200) {
                        return 1;
                    }
                    if (b.friends > 200) {
                        return -1;
                    }
                    return (b.friends - a.friends);
                });
                cb(err, bots);
            });
        },
        "ratingPlayers": function(cb) {
            redis.get("ratingPlayers", function(err, rps) {
                cb(err, JSON.parse(rps));
            });
        },
        "ratings": function(cb) {
            db.ratings.find({
                    account_id: account_id
                }, {
                    sort: {
                        match_id: 1
                    }
                },
                function(err, docs) {
                    cb(err, docs);
                });
        }
    }, function(err, results) {
        cb(err, results);
    });
}

module.exports = {
    fillPlayerNames: fillPlayerNames,
    mergeMatchData: mergeMatchData,
    generateGraphData: generateGraphData,
    computeStatistics: computeStatistics,
    fillPlayerMatches: fillPlayerMatches,
    getRatingData: getRatingData
};<commit_msg>added logging for fillplayer<commit_after>var utility = require('./utility');
var db = require('./db');
var async = require('async');
var redis = require('./redis').client;

function mergeObjects(merge, val) {
    for (var attr in val) {
        if (val[attr].constructor === Array) {
            merge[attr] = merge[attr].concat(val[attr]);
        }
        else if (typeof val[attr] === "object") {
            mergeObjects(merge[attr], val[attr]);
        }
        else {
            //does property exist?
            if (!merge[attr]) {
                merge[attr] = val[attr];
            }
            else {
                merge[attr] += val[attr];
            }
        }
    }
}

function mergeMatchData(match, constants) {
    var heroes = match.parsed_data.heroes;
    //loop through all units
    //look up corresponding hero_id
    //hero if can find in constants
    //find player slot associated with that unit(hero_to_slot)
    //merge into player's primary unit
    //if not hero attempt to associate with a hero
    for (var key in heroes) {
        var val = heroes[key];
        var primary = key;
        if (constants.hero_names[key]) {
            //is a hero
            //merging multiple heroes together, only occurs in ARDM
            //doesn't work for 1v1, but ARDM is only played with 10 players
            var hero_id = constants.hero_names[key].id;
            var slot = match.parsed_data.hero_to_slot[hero_id];
            if (match.players[slot]) {
                var primary_id = match.players[slot].hero_id;
                primary = constants.heroes[primary_id].name;
                //build hero_ids for each player
                if (!match.players[slot].hero_ids) {
                    match.players[slot].hero_ids = [];
                }
                match.players[slot].hero_ids.push(hero_id);
            }
            else {
                console.log("couldn't find slot for hero id %s", hero_id);
            }
        }
        else {
            //is not a hero
            primary = getAssociatedHero(key, heroes);
        }
        if (key !== primary) {
            //merge the objects into primary, but not with itself
            mergeObjects(heroes[primary], val);
        }
    }
    return match;
}

function getAssociatedHero(unit, heroes) {
    //assume all illusions belong to that hero
    if (unit.slice(0, "illusion_".length) === "illusion_") {
        unit = unit.slice("illusion_".length);
    }
    //attempt to recover hero name from unit
    if (unit.slice(0, "npc_dota_".length) === "npc_dota_") {
        //split by _
        var split = unit.split("_");
        //get the third element
        var identifiers = [split[2], split[2] + "_" + split[3]];
        identifiers.forEach(function(id) {
            //append to npc_dota_hero_, see if matches
            var attempt = "npc_dota_hero_" + id;
            if (heroes[attempt]) {
                unit = attempt;
            }
        });
    }
    return unit;
}

function generateGraphData(match, constants) {
    var oneVone = (match.players.length === 2);
    if (oneVone) {
        //rebuild parsed data players array if 1v1 match
        match.parsed_data.players = [match.parsed_data.players[0], match.parsed_data.players[5]];
    }
    //compute graphs
    var goldDifference = ['Gold'];
    var xpDifference = ['XP'];
    for (var i = 0; i < match.parsed_data.times.length; i++) {
        var goldtotal = 0;
        var xptotal = 0;
        match.parsed_data.players.forEach(function(elem, j) {
            if (match.players[j].player_slot < 64) {
                goldtotal += elem.gold[i];
                xptotal += elem.xp[i];
            }
            else {
                xptotal -= elem.xp[i];
                goldtotal -= elem.gold[i];
            }
        });
        goldDifference.push(goldtotal);
        xpDifference.push(xptotal);
    }
    var time = ["time"].concat(match.parsed_data.times);
    var data = {
        difference: [time, goldDifference, xpDifference],
        gold: [time],
        xp: [time],
        lh: [time]
    };
    match.parsed_data.players.forEach(function(elem, i) {
        var hero = constants.heroes[match.players[i].hero_id].localized_name + (oneVone ? " - " + match.players[i].personaname : "");
        data.gold.push([hero].concat(elem.gold));
        data.xp.push([hero].concat(elem.xp));
        data.lh.push([hero].concat(elem.lh));
    });

    //data for income chart
    var gold_reasons = [];
    var columns = [];
    var categories = [];
    var orderedPlayers = match.players.slice(0);
    orderedPlayers.sort(function(a, b) {
        return b.gold_per_min - a.gold_per_min;
    });
    //console.log(orderedPlayers);
    orderedPlayers.forEach(function(player) {
        var hero = constants.heroes[player.hero_id];
        categories.push(hero.localized_name);
    });
    for (var key in constants.gold_reasons) {
        var reason = constants.gold_reasons[key];
        gold_reasons.push(reason);
        var col = [reason];
        orderedPlayers.forEach(function(player) {
            var hero = constants.heroes[player.hero_id];
            var parsedHero = match.parsed_data.heroes[hero.name];
            col.push(parsedHero.gold_log[key] || 0);
        });
        columns.push(col);
    }

    data.cats = categories;
    data.goldCols = columns;
    data.gold_reasons = gold_reasons;
    match.graphData = data;
    return match;
}

function fillPlayerNames(players, cb) {
    async.mapSeries(players, function(player, cb) {
        console.log("querying for player %s", player.account_id);
        db.players.findOne({
            account_id: player.account_id
        }, function(err, dbPlayer) {
            console.log("got result for player %s", player.account_id);
            if (dbPlayer) {
                for (var prop in dbPlayer) {
                    player[prop] = dbPlayer[prop];
                }
            }
            cb(err);
        });
    }, function(err) {
        cb(err);
    });
}

function computeStatistics(player, cb) {
    var counts = {};
    var against = {};
    var together = {};
    db.matches.find({
        'players.account_id': player.account_id
    }, {
        fields: {
            players: 1,
            match_id: 1,
            radiant_win: 1
        }
    }).each(function(match) {
        var playerRadiant = player.radiantMap[match.match_id];
        for (var j = 0; j < match.players.length; j++) {
            var tm = match.players[j];
            var tm_hero = tm.hero_id;
            if (utility.isRadiant(tm) === playerRadiant) {
                //count teammate players
                if (!counts[tm.account_id]) {
                    counts[tm.account_id] = {
                        account_id: tm.account_id,
                        win: 0,
                        lose: 0,
                        games: 0
                    };
                }
                counts[tm.account_id].games += 1;
                playerRadiant === match.radiant_win ? counts[tm.account_id].win += 1 : counts[tm.account_id].lose += 1;
                //count teammate heroes
                if (!together[tm_hero]) {
                    together[tm_hero] = {
                        games: 0,
                        win: 0,
                        lose: 0
                    };
                }
                together[tm_hero].games += 1;
                playerRadiant === match.radiant_win ? together[tm_hero].win += 1 : together[tm_hero].lose += 1;
            }
            else {
                //count enemy heroes
                if (!against[tm_hero]) {
                    against[tm_hero] = {
                        games: 0,
                        win: 0,
                        lose: 0
                    };
                }
                against[tm_hero].games += 1;
                playerRadiant === match.radiant_win ? against[tm_hero].win += 1 : against[tm_hero].lose += 1;
            }
        }
    }).error(function(err) {
        return cb(err);
    }).success(function() {
        player.together = together;
        player.against = against;
        player.teammates = [];
        for (var id in counts) {
            var count = counts[id];
            player.teammates.push(count);
        }
        fillPlayerNames(player.teammates, function(err) {
            cb(err);
        });
    });
}

function fillPlayerMatches(player, constants, matchups, cb) {
    var account_id = player.account_id;
    db.matches.find({
        'players.account_id': account_id
    }, {
        fields: {
            start_time: 1,
            match_id: 1,
            game_mode: 1,
            duration: 1,
            cluster: 1,
            radiant_win: 1,
            parse_status: 1,
            "players.$": 1
        },
        sort: {
            match_id: -1
        }
    }, function(err, matches) {
        if (err) {
            cb(err);
        }
        player.win = 0;
        player.lose = 0;
        player.games = 0;
        player.histogramData = {};
        player.radiantMap = {};
        var calheatmap = {};
        var arr = Array.apply(null, new Array(120)).map(Number.prototype.valueOf, 0);
        var arr2 = Array.apply(null, new Array(120)).map(Number.prototype.valueOf, 0);
        var heroes = {};
        for (var id in constants.heroes) {
            heroes[id] = {
                hero_id: id,
                games: 0,
                win: 0,
                lose: 0
            };
        }
        player.heroes = [];
        for (var i = 0; i < matches.length; i++) {
            var p = matches[i].players[0];
            player.radiantMap[matches[i].match_id] = utility.isRadiant(p);
            matches[i].player_win = (utility.isRadiant(p) === matches[i].radiant_win); //did the player win?
            //aggregate only if valid match
            if (constants.modes[matches[i].game_mode].balanced) {
                calheatmap[matches[i].start_time] = 1;
                var mins = Math.floor(matches[i].duration / 60) % 120;
                arr[mins] += 1;
                var gpm = Math.floor(matches[i].players[0].gold_per_min / 10) % 120;
                arr2[gpm] += 1;
                player.games += 1;
                matches[i].player_win ? player.win += 1 : player.lose += 1;
                if (heroes[p.hero_id]) {
                    heroes[p.hero_id].games += 1;
                    matches[i].player_win ? heroes[p.hero_id].win += 1 : heroes[p.hero_id].lose += 1;
                }
            }
        }
        for (var id in heroes) {
            player.heroes.push(heroes[id]);
        }
        player.heroes.sort(function(a, b) {
            return b.games - a.games;
        });
        player.matches = matches;
        player.histogramData.durations = arr;
        player.histogramData.gpms = arr2;
        player.histogramData.calheatmap = calheatmap;
        if (matchups) {
            computeStatistics(player, function(err) {
                cb(err);
            });
        }
        else {
            cb(err);
        }
    });
}

function getRatingData(req, cb) {
    if (!req.user){
        return cb(null);
    }
    var account_id = req.user.account_id;
    async.parallel({
        "bots": function(cb) {
            redis.get("bots", function(err, bots) {
                bots = JSON.parse(bots);
                //sort list of bots descending, but > 200 go to end
                bots.sort(function(a, b) {
                    if (a.friends > 200) {
                        return 1;
                    }
                    if (b.friends > 200) {
                        return -1;
                    }
                    return (b.friends - a.friends);
                });
                cb(err, bots);
            });
        },
        "ratingPlayers": function(cb) {
            redis.get("ratingPlayers", function(err, rps) {
                cb(err, JSON.parse(rps));
            });
        },
        "ratings": function(cb) {
            db.ratings.find({
                    account_id: account_id
                }, {
                    sort: {
                        match_id: 1
                    }
                },
                function(err, docs) {
                    cb(err, docs);
                });
        }
    }, function(err, results) {
        cb(err, results);
    });
}

module.exports = {
    fillPlayerNames: fillPlayerNames,
    mergeMatchData: mergeMatchData,
    generateGraphData: generateGraphData,
    computeStatistics: computeStatistics,
    fillPlayerMatches: fillPlayerMatches,
    getRatingData: getRatingData
};<|endoftext|>


<commit_before>import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { ipcRenderer } from 'electron'
import Chip from 'material-ui/Chip'
import IconButton from 'material-ui/IconButton'
import CircularProgress from 'material-ui/CircularProgress'
import OpenInBrowser from 'material-ui/svg-icons/action/open-in-browser'
import ModeEdit from 'material-ui/svg-icons/editor/mode-edit'

class ContentViewer extends Component {

	constructor(props, context) {
		super(props, context)

		this.state = {
			postId: props.post.id
		}
	}

	componentDidMount() {
		const { currentBlog, post } = this.props
		if (post) {
			ipcRenderer.send("fetch-content", currentBlog.name, post.id)
		}
	}

	componentWillReceiveProps(nextProps) {
		const { currentBlog, post } = nextProps
		const { postId } = this.state
		if (postId != post.id) {
			this.setState({postId : post.id})
			ipcRenderer.send("fetch-content", currentBlog.name, post.id)
		}
	}

  render() {
    const { currentBlog, post, categories, onRequestEditPost } = this.props

    let category = categories.find(category => post.categoryId == category.id)
    let tags = []
    if (post.tags && post.tags.tag) {
      tags = tags.concat(post.tags.tag)
    }

		let buttonStyle = {
			verticalAlign:'middle',
			width: '24px',
			height: '24px',
			padding: 0,
			marginLeft: '5px'
		}

    return (

            {category &&
              {category.label}
            }

{post.title}

              {post.date} / {post.postUrl}
							<IconButton href={post.postUrl} tooltip="브라우저에서 보기" style={buttonStyle}><OpenInBrowser /></IconButton>
							<IconButton onClick={onRequestEditPost} tooltip="수정하기" style={buttonStyle}><ModeEdit /></IconButton>

					{!post.content &&

							<CircularProgress size={50} thickness={5} />

					}

            {tags.map((item, i) => <Chip key={i} style={{marginRight:'4px'}}>{item}</Chip>)}

    )
  }
}

ContentViewer.propTypes = {
	currentBlog: PropTypes.object.isRequired,
  post: PropTypes.object.isRequired,
  categories: PropTypes.array.isRequired,
	onRequestEditPost: PropTypes.func.isRequired
}

const mapStateToProps = (state) => {
  return {
		currentBlog: state.currentBlog,
		post: state.currentPost,
		categories: state.categories
  }
}

const mapDispatchToProps = (dispatch) => {
  return {
    dispatch: dispatch
  }
}

export default connect(
	mapStateToProps,
	mapDispatchToProps
)(ContentViewer)
<commit_msg>postUrl 숨김<commit_after>import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { ipcRenderer } from 'electron'
import Chip from 'material-ui/Chip'
import IconButton from 'material-ui/IconButton'
import CircularProgress from 'material-ui/CircularProgress'
import OpenInBrowser from 'material-ui/svg-icons/action/open-in-browser'
import ModeEdit from 'material-ui/svg-icons/editor/mode-edit'

class ContentViewer extends Component {

	constructor(props, context) {
		super(props, context)

		this.state = {
			postId: props.post.id
		}
	}

	componentDidMount() {
		const { currentBlog, post } = this.props
		if (post) {
			ipcRenderer.send("fetch-content", currentBlog.name, post.id)
		}
	}

	componentWillReceiveProps(nextProps) {
		const { currentBlog, post } = nextProps
		const { postId } = this.state
		if (postId != post.id) {
			this.setState({postId : post.id})
			ipcRenderer.send("fetch-content", currentBlog.name, post.id)
		}
	}

  render() {
    const { currentBlog, post, categories, onRequestEditPost } = this.props

    let category = categories.find(category => post.categoryId == category.id)
    let tags = []
    if (post.tags && post.tags.tag) {
      tags = tags.concat(post.tags.tag)
    }

		let buttonStyle = {
			verticalAlign:'middle',
			width: '24px',
			height: '24px',
			padding: 0,
			marginLeft: '5px'
		}

    return (

            {category &&
              {category.label}
            }

{post.title}

              {post.date}
							<IconButton href={post.postUrl} tooltip="브라우저에서 보기" style={buttonStyle}><OpenInBrowser /></IconButton>
							<IconButton onClick={onRequestEditPost} tooltip="수정하기" style={buttonStyle}><ModeEdit /></IconButton>

					{!post.content &&

							<CircularProgress size={50} thickness={5} />

					}

            {tags.map((item, i) => <Chip key={i} style={{marginRight:'4px'}}>{item}</Chip>)}

    )
  }
}

ContentViewer.propTypes = {
	currentBlog: PropTypes.object.isRequired,
  post: PropTypes.object.isRequired,
  categories: PropTypes.array.isRequired,
	onRequestEditPost: PropTypes.func.isRequired
}

const mapStateToProps = (state) => {
  return {
		currentBlog: state.currentBlog,
		post: state.currentPost,
		categories: state.categories
  }
}

const mapDispatchToProps = (dispatch) => {
  return {
    dispatch: dispatch
  }
}

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


<commit_before>/*global privlyUrlReceiptNode:false, pendingPost:false, chrome:false, ls:true,  */
(function() {

var BUTTON_WIDTH = 16;
var BUTTON_HEIGHT = 16;
var BUTTON_MARGIN = 5;
var INTERVAL_UPDATE_POSITION = 500;
var HIDE_DELAY = 5000; // fade out after 5000 ms
var HIDE_DELAY_SHORT = 500; // fade out after 500 ms when lose focus

// create a unique className for privly button
// will be used to easily find the button from target container
var privlyButtonClassName = "privlyButton-" + Math.floor(Math.random() * 0xFFFFFFFF).toString(16);

/**
 * Helper methods to calculate correct position of an element.
 * Mainly ported from jQuery.fn.position()
 */
var PositionHelper = {
  /**
   * Get computed style of an element
   */
  css: function(elem, property, numeric) {
    var value = getComputedStyle(elem)[property];
    if (numeric) {
      if (value === "") {
        return 0;
      } else {
        return parseFloat(value);
      }
    } else {
      return value;
    }
  },

  /**
   * jQuery.fn.offsetParent()
   * Ported from: https://github.com/jquery/jquery/blob/master/src/offset.js
   */
  offsetParent: function(elem) {
    var offsetParent = elem.offsetParent || document.documentElement;
    while (offsetParent && (offsetParent.nodeName !== "HTML" && PositionHelper.css(offsetParent, "position") === "static")) {
      offsetParent = offsetParent.offsetParent;
    }
    return offsetParent || document.documentElement;
  },

  /**
   * jQuery.fn.offset()
   * Ported from: https://github.com/jquery/jquery/blob/master/src/offset.js
   */
  offset: function(elem) {
    var box = elem.getBoundingClientRect();
    return {
      top: box.top + window.pageYOffset - document.documentElement.clientTop,
      left: box.left + window.pageXOffset - document.documentElement.clientLeft
    }
  },

  /**
   * jQuery.fn.position()
   * Ported from: https://github.com/jquery/jquery/blob/master/src/offset.js
   */
  position: function(elem) {
    var offset, parentOffset = {
      top: 0,
      left: 0
    };

    // Fixed elements are offset from window (parentOffset = {top:0, left: 0}, because it is it's only offset parent
    if (PositionHelper.css(elem, "position") === "fixed") {
      // We assume that getBoundingClientRect is available when computed position is fixed
      offset = elem.getBoundingClientRect();
    } else {
      // Get *real* offsetParent
      var offsetParent = PositionHelper.offsetParent(elem);

      // Get correct offsets
      var offset = PositionHelper.offset(elem);
      if (offsetParent.nodeName !== "HTML") {
        parentOffset = PositionHelper.offset(offsetParent);
      }

      // Add offsetParent borders
      parentOffset.top += PositionHelper.css(offsetParent, "borderTopWidth", true);
      parentOffset.left += PositionHelper.css(offsetParent, "borderLeftWidth", true);
    }

    // Subtract parent offsets and element margins
    return {
      top: offset.top - parentOffset.top - PositionHelper.css(elem, "marginTop", true),
      left: offset.left - parentOffset.left - PositionHelper.css(elem, "marginLeft", true)
    };
  }
};

var PrivlyButton = function(target, btn) {
  this._target = target;
  if (btn !== undefined && btn instanceof Element) {
    this._button = btn;
  } else {
    // Create a div to handle the privly button
    var button = document.createElement("div");
    button.className = privlyButtonClassName;
    button.style.position = "absolute";
    button.style.cursor = "pointer";
    button.style.zIndex = 99999;
    button.style.transition = "opacity 0.1s linear";
    button.style.background = "url(" + chrome.extension.getURL("images/logo_16.png") + ") no-repeat";
    button.style.width = String(BUTTON_WIDTH) + "px";
    button.style.height = String(BUTTON_HEIGHT) + "px";
    button.title = "New Privly message";
    button.addEventListener("click", this.onClick.bind(this));
    button.addEventListener("mouseover", this.onMouseOver.bind(this));
    button.addEventListener("mouseout", this.onMouseOut.bind(this));
    target.parentNode.appendChild(button);
    this._button = button;
    this.hide();
  }
};

/**
 * Show new post window
 */
PrivlyButton.prototype.onClick = function() {
  if (!pendingPost) {
    chrome.runtime.sendMessage({ask: "newPost"}, function(response) {});
    privlyUrlReceiptNode = this._target;
  } else {
    chrome.runtime.sendMessage({ask: "showNotification"}, function(response) {});
  }
}

/**
 * Fade in button
 */
PrivlyButton.prototype.onMouseOver = function() {
  this.show();
};

/**
 * Fade out button after 5 seconds
 */
PrivlyButton.prototype.onMouseOut = function() {
  this.postponeHide();
};

/**
 * Re-calculate position of the button
 */
PrivlyButton.prototype.updatePosition = function() {
  // we do not use offsetWidth and offsetHeight here, since it
  // will give us incorrect bounding box for wrapped inline elements.
  var box = this._target.getClientRects()[0];
  var targetRightTopCoverPosition = PositionHelper.position(this._target);
  targetRightTopCoverPosition.top += PositionHelper.css(this._target, "marginTop", true);
  targetRightTopCoverPosition.left += PositionHelper.css(this._target, "marginLeft", true);
  targetRightTopCoverPosition.left += box.width;

  // calculate proper margins
  var horizontalMargin = BUTTON_MARGIN;
  if (box.width < (BUTTON_WIDTH + BUTTON_MARGIN * 2)) {
    horizontalMargin = Math.floor((box.width - BUTTON_WIDTH) / 2);
  }

  var verticalMargin = BUTTON_MARGIN;
  if (box.height < (BUTTON_HEIGHT + BUTTON_MARGIN * 2)) {
    verticalMargin = Math.floor((box.height - BUTTON_HEIGHT) / 2);
  }

  // set position of the button
  this._button.style.left = String(targetRightTopCoverPosition.left - horizontalMargin - BUTTON_WIDTH) + 'px';
  this._button.style.top = String(targetRightTopCoverPosition.top + verticalMargin) + 'px';
}

/**
 * Fade in button and make button clickable
 */
PrivlyButton.prototype.show = function() {
  this._button.style.opacity = 0.7;
  this._button.style.pointerEvents = 'auto';
  this.cancelPostponeHide();
}

/**
 * Enable position auto updater
 */
PrivlyButton.prototype.attachAutoPosition = function() {
  if (this._button.dataset.posTimer !== undefined) {
    return;
  }
  var timerId = setInterval((function() {
    this.updatePosition();
  }).bind(this), INTERVAL_UPDATE_POSITION);
  this._button.dataset.posTimer = timerId;
}

/**
 * Disable position auto updater
 */
PrivlyButton.prototype.detachAutoPosition = function() {
  if (this._button.dataset.posTimer !== undefined) {
    clearInterval(parseInt(this._button.dataset.posTimer));
    delete this._button.dataset.posTimer;
  }
}

/**
 * Postpone to hide the button
 */
PrivlyButton.prototype.postponeHide = function(delay) {
  // cancel existing postpone process
  if (this._button.dataset.timer !== undefined) {
    this.cancelPostponeHide();
  }
  var timerId = setTimeout((function() {
    this.hide();
    delete this._button.dataset.timer;
  }).bind(this), delay || HIDE_DELAY);
  this._button.dataset.timer = timerId;
}

/**
 * Cancel postponed hiding
 */
PrivlyButton.prototype.cancelPostponeHide = function() {
  if (this._button.dataset.timer !== undefined) {
    clearTimeout(parseInt(this._button.dataset.timer));
    delete this._button.dataset.timer;
  }
}

/**
 * Fade out button and make button unclickable
 */
PrivlyButton.prototype.hide = function() {
  this.cancelPostponeHide();
  this._button.style.opacity = 0;
  this._button.style.pointerEvents = 'none';
}

/**
 * Should privly button be placed for this target element?
 *
 * @param  {Element}  target
 * @return {Boolean}
 */
PrivlyButton.isTargetValid = function(target) {
  return (target.nodeName === "TEXTAREA" || target.isContentEditable);
};

/**
 * Get the most outside target element
 *
 * @param  {Element} target
 * @return {Element}
 */
PrivlyButton.getOuterTarget = function(target) {
  while (target.parentNode && target.parentNode.isContentEditable) {
    target = target.parentNode;
  }
  return target;
};

/**
 * Get the privly button attached to the target element
 *
 * @param  {Element} target
 * @return {object} the privly button instance. return null if not attached.
 */
PrivlyButton.getAttachedButton = function(target) {
  var container = target.parentNode;
  var buttons = container.getElementsByClassName(privlyButtonClassName);
  if (buttons.length === 0) {
    return null;
  } else {
    return new PrivlyButton(target, buttons[0]);
  }
}

function initEventListeners() {
  function onTargetActivated(event) {
    if (PrivlyButton.isTargetValid(event.target)) {
      var target = PrivlyButton.getOuterTarget(event.target);
      var button = PrivlyButton.getAttachedButton(target);
      if (button === null) {
        // this target does not has a privly button attached
        button = new PrivlyButton(target);
      }
      button.updatePosition();
      button.attachAutoPosition();
      button.show();
      button.postponeHide();
    }
  }
  function onTargetDeactivated(event) {
    if (PrivlyButton.isTargetValid(event.target)) {
      var target = PrivlyButton.getOuterTarget(event.target);
      var button = PrivlyButton.getAttachedButton(target);
      if (button !== null) {
        button.detachAutoPosition();
        // If we click the button, onBlur will be triggered before onClick,
        // so we have to shortly postpone the hiding process.
        button.postponeHide(HIDE_DELAY_SHORT);
      }
    }
  }
  // would onMouseDown be a better choice?
  document.body.addEventListener("click", onTargetActivated, false);
  document.body.addEventListener("focus", onTargetActivated, true);
  document.body.addEventListener("blur", onTargetDeactivated, true);
}

chrome.runtime.sendMessage({ask: "PrivlyBtnStatus"}, function(response) {
  // Call the addPrivlyButton function only if the checkbox in the options
  // page is not checked
  if (response.tell === "unchecked") {
    initEventListeners();
  }
});

}());<commit_msg>better comments<commit_after>/*global privlyUrlReceiptNode:false, pendingPost:false, chrome:false, ls:true,  */

// This content-script provides Privly Posting Button feature.
// It listens events of editable elements (textarea / contentEditable elements)
// and create or show the posting button.

(function() {

var BUTTON_WIDTH = 16;
var BUTTON_HEIGHT = 16;
var BUTTON_MARGIN = 5;
var INTERVAL_UPDATE_POSITION = 500;
var HIDE_DELAY = 5000; // fade out after 5000 ms
var HIDE_DELAY_SHORT = 500; // fade out after 500 ms when lose focus

// create a unique className for privly button
// will be used to easily find the button from target container
var privlyButtonClassName = "privlyButton-" + Math.floor(Math.random() * 0xFFFFFFFF).toString(16);

/**
 * Helper methods to calculate correct position of an element.
 * Mainly ported from jQuery.fn.position()
 */
var PositionHelper = {
  /**
   * Get computed style of an element
   *
   * @param  {Element} elem
   * @param  {string}  property
   * @param  {boolean} numeric  Should value return as a numeric value?
   * @return {string|Number}
   */
  css: function(elem, property, numeric) {
    var value = getComputedStyle(elem)[property];
    if (numeric) {
      if (value === "") {
        return 0;
      } else {
        return parseFloat(value);
      }
    } else {
      return value;
    }
  },

  /**
   * Get the closest ancestor element that is positioned
   *
   * jQuery.fn.offsetParent()
   * Ported from: https://github.com/jquery/jquery/blob/master/src/offset.js
   *
   * @param  {Element} elem
   * @return {Element}
   */
  offsetParent: function(elem) {
    var offsetParent = elem.offsetParent || document.documentElement;
    while (offsetParent && (offsetParent.nodeName !== "HTML" && PositionHelper.css(offsetParent, "position") === "static")) {
      offsetParent = offsetParent.offsetParent;
    }
    return offsetParent || document.documentElement;
  },

  /**
   * Get the current coordinates of the element, relative to the document.
   *
   * jQuery.fn.offset()
   * Ported from: https://github.com/jquery/jquery/blob/master/src/offset.js
   *
   * @param  {Element} elem
   * @return {Object}
   */
  offset: function(elem) {
    var box = elem.getBoundingClientRect();
    return {
      top: box.top + window.pageYOffset - document.documentElement.clientTop,
      left: box.left + window.pageXOffset - document.documentElement.clientLeft
    }
  },

  /**
   * Get the current coordinates of the element, relative to the offset parent.
   *
   * jQuery.fn.position()
   * Ported from: https://github.com/jquery/jquery/blob/master/src/offset.js
   *
   * @param  {Element} elem
   * @return {Object}
   */
  position: function(elem) {
    var offset, parentOffset = {
      top: 0,
      left: 0
    };

    // Fixed elements are offset from window (parentOffset = {top:0, left: 0}, because it is it's only offset parent
    if (PositionHelper.css(elem, "position") === "fixed") {
      // We assume that getBoundingClientRect is available when computed position is fixed
      offset = elem.getBoundingClientRect();
    } else {
      // Get *real* offsetParent
      var offsetParent = PositionHelper.offsetParent(elem);

      // Get correct offsets
      var offset = PositionHelper.offset(elem);
      if (offsetParent.nodeName !== "HTML") {
        parentOffset = PositionHelper.offset(offsetParent);
      }

      // Add offsetParent borders
      parentOffset.top += PositionHelper.css(offsetParent, "borderTopWidth", true);
      parentOffset.left += PositionHelper.css(offsetParent, "borderLeftWidth", true);
    }

    // Subtract parent offsets and element margins
    return {
      top: offset.top - parentOffset.top - PositionHelper.css(elem, "marginTop", true),
      left: offset.left - parentOffset.left - PositionHelper.css(elem, "marginLeft", true)
    };
  }
};

/**
 * PrivlyButton Prototype Class
 *
 * @param {Element} target
 * @param {Element} btn   Optional, the Privly button element
 */
var PrivlyButton = function(target, btn) {
  this._target = target;
  if (btn !== undefined && btn instanceof Element) {
    this._button = btn;
  } else {
    // Create a div to handle the privly button
    var button = document.createElement("div");
    button.className = privlyButtonClassName;
    button.style.position = "absolute";
    button.style.cursor = "pointer";
    button.style.zIndex = 99999;
    button.style.transition = "opacity 0.1s linear";
    button.style.background = "url(" + chrome.extension.getURL("images/logo_16.png") + ") no-repeat";
    button.style.width = String(BUTTON_WIDTH) + "px";
    button.style.height = String(BUTTON_HEIGHT) + "px";
    button.title = "New Privly message";

    // onClick will happen before onBlur, thus a holding-click will not trigger
    // onClick event here. Would onMouseDown be a better choice?
    button.addEventListener("click", this.onClick.bind(this));
    button.addEventListener("mouseover", this.onMouseOver.bind(this));
    button.addEventListener("mouseout", this.onMouseOut.bind(this));
    target.parentNode.appendChild(button);
    this._button = button;
    this.hide();
  }
};

/**
 * Show new post window
 */
PrivlyButton.prototype.onClick = function() {
  if (!pendingPost) {
    chrome.runtime.sendMessage({ask: "newPost"}, function(response) {});
    privlyUrlReceiptNode = this._target;
  } else {
    chrome.runtime.sendMessage({ask: "showNotification"}, function(response) {});
  }
}

/**
 * Fade in button
 */
PrivlyButton.prototype.onMouseOver = function() {
  this.show();
};

/**
 * Fade out button after 5 seconds
 */
PrivlyButton.prototype.onMouseOut = function() {
  this.postponeHide();
};

/**
 * Re-calculate position of the button
 */
PrivlyButton.prototype.updatePosition = function() {
  // we do not use offsetWidth and offsetHeight here, since it
  // will give us incorrect bounding box for wrapped inline elements.
  var box = this._target.getClientRects()[0];
  var targetRightTopCoverPosition = PositionHelper.position(this._target);
  targetRightTopCoverPosition.top += PositionHelper.css(this._target, "marginTop", true);
  targetRightTopCoverPosition.left += PositionHelper.css(this._target, "marginLeft", true);
  targetRightTopCoverPosition.left += box.width;

  // calculate proper margins
  var horizontalMargin = BUTTON_MARGIN;
  if (box.width < (BUTTON_WIDTH + BUTTON_MARGIN * 2)) {
    horizontalMargin = Math.floor((box.width - BUTTON_WIDTH) / 2);
  }

  var verticalMargin = BUTTON_MARGIN;
  if (box.height < (BUTTON_HEIGHT + BUTTON_MARGIN * 2)) {
    verticalMargin = Math.floor((box.height - BUTTON_HEIGHT) / 2);
  }

  // set position of the button
  this._button.style.left = String(targetRightTopCoverPosition.left - horizontalMargin - BUTTON_WIDTH) + 'px';
  this._button.style.top = String(targetRightTopCoverPosition.top + verticalMargin) + 'px';
}

/**
 * Fade in button and make button clickable
 */
PrivlyButton.prototype.show = function() {
  this._button.style.opacity = 0.7;
  this._button.style.pointerEvents = 'auto';
  this.cancelPostponeHide();
}

/**
 * Enable position auto updater
 */
PrivlyButton.prototype.attachAutoPosition = function() {
  if (this._button.dataset.posTimer !== undefined) {
    return;
  }
  var timerId = setInterval((function() {
    this.updatePosition();
  }).bind(this), INTERVAL_UPDATE_POSITION);
  this._button.dataset.posTimer = timerId;
}

/**
 * Disable position auto updater
 */
PrivlyButton.prototype.detachAutoPosition = function() {
  if (this._button.dataset.posTimer !== undefined) {
    clearInterval(parseInt(this._button.dataset.posTimer));
    delete this._button.dataset.posTimer;
  }
}

/**
 * Postpone to hide the button
 */
PrivlyButton.prototype.postponeHide = function(delay) {
  // cancel existing postpone process
  if (this._button.dataset.timer !== undefined) {
    this.cancelPostponeHide();
  }
  var timerId = setTimeout((function() {
    this.hide();
    delete this._button.dataset.timer;
  }).bind(this), delay || HIDE_DELAY);
  this._button.dataset.timer = timerId;
}

/**
 * Cancel postponed hiding
 */
PrivlyButton.prototype.cancelPostponeHide = function() {
  if (this._button.dataset.timer !== undefined) {
    clearTimeout(parseInt(this._button.dataset.timer));
    delete this._button.dataset.timer;
  }
}

/**
 * Fade out button and make button unclickable
 */
PrivlyButton.prototype.hide = function() {
  this.cancelPostponeHide();
  this._button.style.opacity = 0;
  this._button.style.pointerEvents = 'none';
}

/**
 * Should privly button be placed for this target element?
 *
 * @param  {Element}  target
 * @return {Boolean}
 */
PrivlyButton.isTargetValid = function(target) {
  return (target.nodeName === "TEXTAREA" || target.isContentEditable);
};

/**
 * Get the most outside target element
 *
 * @param  {Element} target
 * @return {Element}
 */
PrivlyButton.getOuterTarget = function(target) {
  while (target.parentNode && target.parentNode.isContentEditable) {
    target = target.parentNode;
  }
  return target;
};

/**
 * Get the privly button attached to the target element
 *
 * @param  {Element} target
 * @return {object} the privly button instance. return null if not attached.
 */
PrivlyButton.getAttachedButton = function(target) {
  var container = target.parentNode;
  var buttons = container.getElementsByClassName(privlyButtonClassName);
  if (buttons.length === 0) {
    return null;
  } else {
    return new PrivlyButton(target, buttons[0]);
  }
}

/**
 * Create event listeners
 */
function initEventListeners() {

  /**
   * Event handler when tagret is activated (click or focus)
   *
   * @param  {Event} event
   */
  function onTargetActivated(event) {
    if (PrivlyButton.isTargetValid(event.target)) {
      var target = PrivlyButton.getOuterTarget(event.target);
      var button = PrivlyButton.getAttachedButton(target);
      if (button === null) {
        // this target does not has a privly button attached
        button = new PrivlyButton(target);
      }
      button.updatePosition();
      button.attachAutoPosition();
      button.show();
      button.postponeHide();
    }
  }

  /**
   * Event handler when target is deactivated (blur)
   *
   * @param  {Event} event
   */
  function onTargetDeactivated(event) {
    if (PrivlyButton.isTargetValid(event.target)) {
      var target = PrivlyButton.getOuterTarget(event.target);
      var button = PrivlyButton.getAttachedButton(target);
      if (button !== null) {
        button.detachAutoPosition();
        // If we click the button, onBlur will be triggered before onClick,
        // so we have to shortly postpone the hiding process.
        button.postponeHide(HIDE_DELAY_SHORT);
      }
    }
  }

  document.body.addEventListener("click", onTargetActivated, false);
  document.body.addEventListener("focus", onTargetActivated, true);
  document.body.addEventListener("blur", onTargetDeactivated, true);
}

chrome.runtime.sendMessage({ask: "PrivlyBtnStatus"}, function(response) {
  // Call the addPrivlyButton function only if the checkbox in the options
  // page is not checked
  if (response.tell === "unchecked") {
    initEventListeners();
  }
});

}());
<|endoftext|>


<commit_before>var express = require('express'),
    app = express(),
    fs = require('fs'),
    http = require('http').Server(app),
    https = require('https'),
    io = require('socket.io')(http),
    device = require('express-device'),
    router = express.Router(),
    // io = require('socket.io')(https),
    httpsOptions = {
        key: fs.readFileSync('key.pem'),
        cert: fs.readFileSync('cert.pem')
    };

app.use(device.capture());
app.enable('trust proxy');

app.use(function (req, res, next) {
    if (req.secure) {
        // request was via https, so do no special handling
        next();
    } else {
        // request was via http, so redirect to https
        res.redirect('https://' + req.headers.host + req.url);
    }
});

app.use(router);
app.use(express.static(__dirname + '/client'));

router.get('/', function (req, res) {
    console.log('\x1b[35m', req.device.type);

    if (req.device.type == 'phone') {
        res.redirect('/photo');
    }

    res.sendFile(__dirname + '/client/index.html');
});

router.get('/photo', function (req, res) {
    console.log('\x1b[35m', req.device.type);

    if (req.device.type != 'phone') {
        res.redirect('/');
    }

    res.sendFile(__dirname + '/client/photo/index.html');
});

io.on('connection', function (socket) {
    console.log('\x1b[32m', 'a user connected');

    socket.on('disconnect', function () {
        console.log('\x1b[31m', 'user disconnected');
    });

    socket.on('photo flick', function (data) {
        io.emit('photo flick', data);
    });
});

https.createServer(httpsOptions, app).listen(process.env.PORT || 8888, function () {
    console.log('listening on *:8888');
});

// http.listen(process.env.PORT || 8888, function () {
//     console.log('listening on *:8888');
// });

// Reset = "\x1b[0m"
// Bright = "\x1b[1m"
// Dim = "\x1b[2m"
// Underscore = "\x1b[4m"
// Blink = "\x1b[5m"
// Reverse = "\x1b[7m"
// Hidden = "\x1b[8m"

// FgBlack = "\x1b[30m"
// FgRed = "\x1b[31m"
// FgGreen = "\x1b[32m"
// FgYellow = "\x1b[33m"
// FgBlue = "\x1b[34m"
// FgMagenta = "\x1b[35m"
// FgCyan = "\x1b[36m"
// FgWhite = "\x1b[37m"

// BgBlack = "\x1b[40m"
// BgRed = "\x1b[41m"
// BgGreen = "\x1b[42m"
// BgYellow = "\x1b[43m"
// BgBlue = "\x1b[44m"
// BgMagenta = "\x1b[45m"
// BgCyan = "\x1b[46m"
// BgWhite = "\x1b[47m"<commit_msg>- removed https redirect<commit_after>var express = require('express'),
    app = express(),
    fs = require('fs'),
    http = require('http').Server(app),
    https = require('https'),
    io = require('socket.io')(http),
    device = require('express-device'),
    router = express.Router(),
    httpsOptions = {
        key: fs.readFileSync('key.pem'),
        cert: fs.readFileSync('cert.pem')
    };

app.use(device.capture());
app.enable('trust proxy');

app.use(router);
app.use(express.static(__dirname + '/client'));

router.get('/', function (req, res) {
    console.log('\x1b[35m', req.device.type);

    if (req.device.type == 'phone') {
        res.redirect('/photo');
    }

    res.sendFile(__dirname + '/client/index.html');
});

router.get('/photo', function (req, res) {
    console.log('\x1b[35m', req.device.type);

    if (req.device.type != 'phone') {
        res.redirect('/');
    }

    res.sendFile(__dirname + '/client/photo/index.html');
});

io.on('connection', function (socket) {
    console.log('\x1b[32m', 'a user connected');

    socket.on('disconnect', function () {
        console.log('\x1b[31m', 'user disconnected');
    });

    socket.on('photo flick', function (data) {
        io.emit('photo flick', data);
    });
});

http.listen(process.env.PORT || 8888, function () {
    console.log('listening on *:8888');
});

// Reset = "\x1b[0m"
// Bright = "\x1b[1m"
// Dim = "\x1b[2m"
// Underscore = "\x1b[4m"
// Blink = "\x1b[5m"
// Reverse = "\x1b[7m"
// Hidden = "\x1b[8m"

// FgBlack = "\x1b[30m"
// FgRed = "\x1b[31m"
// FgGreen = "\x1b[32m"
// FgYellow = "\x1b[33m"
// FgBlue = "\x1b[34m"
// FgMagenta = "\x1b[35m"
// FgCyan = "\x1b[36m"
// FgWhite = "\x1b[37m"

// BgBlack = "\x1b[40m"
// BgRed = "\x1b[41m"
// BgGreen = "\x1b[42m"
// BgYellow = "\x1b[43m"
// BgBlue = "\x1b[44m"
// BgMagenta = "\x1b[45m"
// BgCyan = "\x1b[46m"
// BgWhite = "\x1b[47m"<|endoftext|>